fix(auth): fixed bug where users where not properly logged in on their first login
Build and Push Docker image / build-and-push (push) Successful in 2m2s
Details
Build and Push Docker image / build-and-push (push) Successful in 2m2s
Details
This commit is contained in:
parent
54057be1f6
commit
4ea290ddf4
|
@ -9,5 +9,5 @@ export const PB_STORAGE_KEY = "stuve-it-login-record"
|
|||
|
||||
// general
|
||||
export const APP_NAME = "StuVe IT"
|
||||
export const APP_VERSION = "0.8.9 (beta)"
|
||||
export const APP_VERSION = "0.8.11 (beta)"
|
||||
export const APP_URL = "https://it.stuve.uni-ulm.de"
|
|
@ -33,8 +33,7 @@ export default function EmailTokenVerification() {
|
|||
|
||||
const verifyTokenMutation = useMutation({
|
||||
mutationFn: async (token: string) => {
|
||||
const res = await pb.collection("users").confirmVerification(token)
|
||||
console.log({res})
|
||||
await pb.collection("users").confirmVerification(token)
|
||||
},
|
||||
onSuccess: () => {
|
||||
showSuccessNotification("E-Mail erfolgreich verifiziert")
|
||||
|
@ -51,7 +50,7 @@ export default function EmailTokenVerification() {
|
|||
|
||||
useEffect(() => {
|
||||
const token = searchParams.get(EMAIL_TOKEN_KEY)
|
||||
if (token !== null) {
|
||||
if (token !== null && !user && !verifyTokenMutation.isPending) {
|
||||
verifyTokenMutation.mutate(token)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
|
|
@ -8,6 +8,7 @@ import {useMutation} from "@tanstack/react-query";
|
|||
import {showSuccessNotification} from "@/components/util.tsx";
|
||||
import {Link} from "react-router-dom";
|
||||
|
||||
|
||||
export default function RegisterModal() {
|
||||
const {value, handler} = useRegister()
|
||||
|
||||
|
@ -31,7 +32,8 @@ export default function RegisterModal() {
|
|||
passwordConfirm: (val, values) => val !== values.password ? "Die Passwörter stimmen nicht überein" : null,
|
||||
terms: (val) => !val ? "Du musst die AGB akzeptieren" : null,
|
||||
privacy: (val) => !val ? "Du musst die Datenschutzerklärung akzeptieren" : null
|
||||
}
|
||||
},
|
||||
|
||||
})
|
||||
|
||||
const registerMutation = useMutation({
|
||||
|
@ -79,10 +81,28 @@ export default function RegisterModal() {
|
|||
|
||||
<Collapse in={formValues.values.email.length > 0} className={"stack"}>
|
||||
|
||||
<Group grow>
|
||||
<TextInput
|
||||
label={"Vorname"}
|
||||
description={"Ist für eingeloggte Personen sichtbar"}
|
||||
placeholder={"Vorname"}
|
||||
required
|
||||
{...formValues.getInputProps("givenName")}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label={"Nachname"}
|
||||
description={"Ist für eingeloggte Personen sichtbar"}
|
||||
placeholder={"Nachname"}
|
||||
required
|
||||
{...formValues.getInputProps("sn")}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
<TextInput
|
||||
label={"Anmeldename"}
|
||||
description={"Dein Anmeldename ist für eingeloggte Personen sichtbar"}
|
||||
placeholder={"Anmeldename"}
|
||||
placeholder={"vorname.nachname"}
|
||||
leftSection={<IconUser/>}
|
||||
required
|
||||
{...formValues.getInputProps("username")}
|
||||
|
|
|
@ -94,6 +94,7 @@ const PocketData = () => {
|
|||
pb.authStore.save(token, record)
|
||||
return record
|
||||
} catch (e) {
|
||||
console.error("refresh token error", e)
|
||||
pb.authStore.clear()
|
||||
return null
|
||||
}
|
||||
|
@ -111,12 +112,14 @@ const PocketData = () => {
|
|||
const record = await pb.collection(PB_USER_COLLECTION).getOne(pb.authStore.model?.id ?? "", {
|
||||
expand: "memberOf"
|
||||
})
|
||||
if (record.verified) {
|
||||
if (record.verified === true) {
|
||||
pb.authStore.save(pb.authStore.token, record)
|
||||
return record
|
||||
} else {
|
||||
} else if (record.verified === false) {
|
||||
pb.authStore.clear()
|
||||
return null
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof ClientResponseError && e.status === 401) {
|
||||
|
@ -179,7 +182,7 @@ const PocketData = () => {
|
|||
ldapLogin,
|
||||
guestLogin,
|
||||
logout,
|
||||
user: user as UserModal | null,
|
||||
user: pb.authStore.isValid ? user as UserModal | null : null,
|
||||
pb,
|
||||
refreshUser: refreshUserQuery.refetch,
|
||||
useSubscription,
|
||||
|
|
|
@ -2,17 +2,17 @@ import PromptLoginModal from "@/components/users/modals/PromptLoginModal.tsx";
|
|||
import {Link, useNavigate} from "react-router-dom";
|
||||
import {useState} from "react";
|
||||
import {useDebouncedValue, useToggle} from "@mantine/hooks";
|
||||
import {useQuery} from "@tanstack/react-query";
|
||||
import {useInfiniteQuery} from "@tanstack/react-query";
|
||||
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
||||
import {
|
||||
ActionIcon,
|
||||
Anchor,
|
||||
Breadcrumbs,
|
||||
Button,
|
||||
Center,
|
||||
Group,
|
||||
Loader,
|
||||
LoadingOverlay,
|
||||
Pagination,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
|
@ -21,7 +21,7 @@ import {
|
|||
Tooltip
|
||||
} from "@mantine/core";
|
||||
import ShowHelp from "@/components/ShowHelp.tsx";
|
||||
import {IconConfetti, IconDatabaseOff, IconSortAscending, IconSortDescending} from "@tabler/icons-react";
|
||||
import {IconArrowDown, IconConfetti, IconDatabaseOff, IconSortAscending, IconSortDescending} from "@tabler/icons-react";
|
||||
import UserEntryRow from "@/pages/events/entries/UserEntryRow.tsx";
|
||||
|
||||
export default function UserEntries() {
|
||||
|
@ -30,23 +30,24 @@ export default function UserEntries() {
|
|||
|
||||
const {pb, user} = usePB()
|
||||
|
||||
const [page, setPage] = useState(1)
|
||||
|
||||
const [sortDirection, toggleSortDirection] = useToggle(['DESC', 'ASC']);
|
||||
|
||||
const [eventSearch, setEventSearch] = useState("")
|
||||
|
||||
const [debouncedSearch] = useDebouncedValue(eventSearch, 200)
|
||||
|
||||
const entriesQuery = useQuery({
|
||||
queryKey: ["userEntries", user?.id, page, debouncedSearch, sortDirection],
|
||||
queryFn: async () => (
|
||||
await pb.collection("eventListSlotEntriesWithUser").getList(page, 50, {
|
||||
const entriesQuery = useInfiniteQuery({
|
||||
queryKey: ["userEntries", user?.id, debouncedSearch, sortDirection],
|
||||
queryFn: async ({pageParam}) => (
|
||||
await pb.collection("eventListSlotEntriesWithUser").getList(pageParam, 50, {
|
||||
filter: `user='${user?.id}'${debouncedSearch ? `&&event.name~'${debouncedSearch}'` : ""}`,
|
||||
sort: sortDirection === "ASC" ? "-event.startDate" : "event.startDate",
|
||||
expand: "user"
|
||||
})
|
||||
),
|
||||
getNextPageParam: (lastPage) =>
|
||||
lastPage.page >= lastPage.totalPages ? undefined : lastPage.page + 1,
|
||||
initialPageParam: 1,
|
||||
enabled: !!user
|
||||
})
|
||||
|
||||
|
@ -54,6 +55,9 @@ export default function UserEntries() {
|
|||
return <PromptLoginModal onAbort={() => navigate("/")}/>
|
||||
}
|
||||
|
||||
const entries = entriesQuery.data?.pages.flatMap(page => page.items) ?? []
|
||||
const totalItems = entriesQuery.data?.pages[0].totalItems ?? 0
|
||||
|
||||
return <>
|
||||
{entriesQuery.isLoading && <LoadingOverlay/>}
|
||||
|
||||
|
@ -104,13 +108,13 @@ export default function UserEntries() {
|
|||
</Text>
|
||||
|
||||
<Text c={"dimmed"} size={"xs"}>
|
||||
{entriesQuery.data?.totalItems ?? 0} {eventSearch ? "Ergebnisse" : "Anmeldungen"}
|
||||
{totalItems ?? 0} {eventSearch ? "Ergebnisse" : "Anmeldungen"}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<PocketBaseErrorAlert error={entriesQuery.error}/>
|
||||
|
||||
{entriesQuery.data?.totalItems === 0 && <Stack align={"center"}>
|
||||
{totalItems === 0 && <Stack align={"center"}>
|
||||
<ThemeIcon variant={"transparent"} color={"gray"} size={"md"}>
|
||||
<IconDatabaseOff/>
|
||||
</ThemeIcon>
|
||||
|
@ -119,14 +123,25 @@ export default function UserEntries() {
|
|||
</Stack>
|
||||
}
|
||||
|
||||
{entriesQuery.data?.items.map(entry => (
|
||||
{entries.map(entry => (
|
||||
<UserEntryRow entry={entry} refetch={() => entriesQuery.refetch()} key={entry.id}/>
|
||||
))}
|
||||
|
||||
<Center>
|
||||
<Pagination total={entriesQuery.data?.totalPages ?? 1} value={page} onChange={setPage} size={"xs"}/>
|
||||
{
|
||||
entriesQuery.hasNextPage &&
|
||||
<Center p={"xs"}>
|
||||
<Button
|
||||
disabled={entriesQuery.isFetchingNextPage || !entriesQuery.hasNextPage}
|
||||
loading={entriesQuery.isFetchingNextPage}
|
||||
variant={"transparent"}
|
||||
size={"compact-xs"}
|
||||
leftSection={<IconArrowDown/>}
|
||||
onClick={() => entriesQuery.fetchNextPage()}
|
||||
>
|
||||
Mehr laden
|
||||
</Button>
|
||||
</Center>
|
||||
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
}
|
|
@ -18,6 +18,7 @@ import {RenderDateRange} from "@/pages/events/e/:eventId/EventLists/EventListCom
|
|||
import {
|
||||
EntryQuestionAndStatusData
|
||||
} from "@/pages/events/e/:eventId/EventLists/EventListComponents/EntryQuestionAndStatusData.tsx";
|
||||
import {Link} from "react-router-dom";
|
||||
|
||||
|
||||
export default function UserEntryRow({entry, refetch}: {
|
||||
|
@ -80,7 +81,9 @@ export default function UserEntryRow({entry, refetch}: {
|
|||
<IconList/>
|
||||
</ThemeIcon>
|
||||
}>
|
||||
<Link to={`/events/s/${entry.event}?lists=${entry.eventList}`} >
|
||||
{entry.listName}
|
||||
</Link>
|
||||
</TextWithIcon>
|
||||
|
||||
<Group gap={"xs"} justify={"center"}>
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
import {EventListModel, EventListSlotsWithEntriesCountModel} from "@/models/EventTypes.ts";
|
||||
import classes from "./EventListSlotView.module.css";
|
||||
import {useDisclosure} from "@mantine/hooks";
|
||||
import {Alert, Collapse, ThemeIcon, Tooltip, UnstyledButton} from "@mantine/core";
|
||||
import {IconChevronDown, IconChevronRight, IconInfoCircle} from "@tabler/icons-react";
|
||||
import {Alert, Button, Collapse, Group, List, ThemeIcon, Tooltip, UnstyledButton} from "@mantine/core";
|
||||
import {IconChevronDown, IconChevronRight, IconEye, IconInfoCircle} from "@tabler/icons-react";
|
||||
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {useMutation, useQuery} from "@tanstack/react-query";
|
||||
import {FieldEntries} from "@/components/formUtil/FromInput/types.ts";
|
||||
import {showSuccessNotification} from "@/components/util.tsx";
|
||||
import InnerHtml from "@/components/InnerHtml";
|
||||
import FormInput from "@/components/formUtil/FromInput";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import {Link, useNavigate} from "react-router-dom";
|
||||
import {getListSchemas} from "@/pages/events/util.ts";
|
||||
import {RenderDateRange} from "@/pages/events/e/:eventId/EventLists/EventListComponents/RenderDateRange.tsx";
|
||||
import SlotProgress from "@/pages/events/e/:eventId/EventLists/EventListComponents/SlotProgress.tsx";
|
||||
import EventLoginWarning from "@/pages/events/s/EventLoginWarning.tsx";
|
||||
import {pprintDateRange} from "@/lib/datetime.ts";
|
||||
|
||||
export default function EventListSlotView({slot, list, refetch}: {
|
||||
list: EventListModel,
|
||||
|
@ -32,6 +33,24 @@ export default function EventListSlotView({slot, list, refetch}: {
|
|||
|
||||
const navigate = useNavigate()
|
||||
|
||||
const slotOccupiedByUserQuery = useQuery({
|
||||
queryKey: ["eventListSlotEntries", {eventListsSlot: slot.id, user: user?.id}],
|
||||
queryFn: async () => {
|
||||
return await pb.collection("eventListSlotEntriesWithUser").getList(1, 50, {
|
||||
filter: `event='${list.event}'&&
|
||||
user='${user?.id}'&&
|
||||
(
|
||||
slotStartDate>='${slot.startDate}'&&slotStartDate<='${slot.endDate}'||
|
||||
slotEndDate>='${slot.startDate}'&&slotEndDate<='${slot.endDate}'||
|
||||
slotStartDate<='${slot.startDate}'&&slotEndDate>='${slot.endDate}'
|
||||
)
|
||||
`,
|
||||
requestKey: `slotOccupiedByUserQuery-${slot.id}-${user?.id}`
|
||||
})
|
||||
},
|
||||
enabled: !!user && !list.allowOverlappingEntries
|
||||
})
|
||||
|
||||
const createEntryMutation = useMutation({
|
||||
mutationFn: async (data: FieldEntries) => {
|
||||
await pb.collection("eventListSlotEntries").create({
|
||||
|
@ -57,11 +76,9 @@ export default function EventListSlotView({slot, list, refetch}: {
|
|||
</Tooltip>
|
||||
|
||||
<div className={classes.slotInfo}>
|
||||
|
||||
<RenderDateRange start={new Date(slot.startDate)} end={new Date(slot.endDate)}/>
|
||||
|
||||
<SlotProgress slot={slot}/>
|
||||
|
||||
</div>
|
||||
</UnstyledButton>
|
||||
|
||||
|
@ -77,7 +94,35 @@ export default function EventListSlotView({slot, list, refetch}: {
|
|||
|
||||
{
|
||||
!user ? <EventLoginWarning/> :
|
||||
list.onlyStuVeAccounts && user?.REALM !== "LDAP" ? <>
|
||||
(slotOccupiedByUserQuery.data?.totalItems && !list.allowOverlappingEntries) ? <>
|
||||
<Alert>
|
||||
<div className={"stack"}>
|
||||
Du hast dich bei diesem Event bereits in einen überschneidenden Zeitslot eingetragen
|
||||
und
|
||||
diese Liste erlaubt keine überschneidenden Einträge.
|
||||
<List size={"sm"} c={"dimmed"}>
|
||||
{slotOccupiedByUserQuery.data.items.map((entry) => (
|
||||
<List.Item key={entry.id}>
|
||||
{entry.listName}
|
||||
{", Zeitslot "}
|
||||
{pprintDateRange(entry.slotStartDate, entry.slotEndDate)}
|
||||
</List.Item>
|
||||
))}
|
||||
</List>
|
||||
<Group>
|
||||
<Button
|
||||
component={Link}
|
||||
to={`/events/entries`}
|
||||
variant={"light"}
|
||||
leftSection={<IconEye/>}
|
||||
size={"xs"}
|
||||
>
|
||||
Anmeldungen-Seite
|
||||
</Button>
|
||||
</Group>
|
||||
</div>
|
||||
</Alert>
|
||||
</> : list.onlyStuVeAccounts && user?.REALM !== "LDAP" ? <>
|
||||
<Alert color={"red"}>
|
||||
Für diese Liste sind nur StuVe-Accounts zugelassen
|
||||
</Alert>
|
||||
|
@ -89,8 +134,7 @@ export default function EventListSlotView({slot, list, refetch}: {
|
|||
<Alert color={"red"} title={"Zeitslot voll"}>
|
||||
Dieser Zeitslot ist bereits voll
|
||||
</Alert>
|
||||
</>
|
||||
:
|
||||
</> :
|
||||
<FormInput
|
||||
disabled={!user || slotIsFull}
|
||||
schema={questionSchema}
|
||||
|
|
|
@ -62,15 +62,6 @@ export default function EventListView({event, listId}: { event: EventModel, list
|
|||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
!list.allowOverlappingEntries &&
|
||||
<Alert>
|
||||
Wenn du bei diesem Event bereits für einen anderen Zeitslot angemeldet bist,
|
||||
kannst du in dieser Liste nur einen Zeitslot wählen,
|
||||
der nicht mit deinen anderen Anmeldungen kollidiert.
|
||||
</Alert>
|
||||
}
|
||||
|
||||
{
|
||||
list.onlyStuVeAccounts &&
|
||||
<Alert>
|
||||
|
@ -78,7 +69,6 @@ export default function EventListView({event, listId}: { event: EventModel, list
|
|||
</Alert>
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
(list.open && slots.length !== 0) && <>
|
||||
{slots.map(slot => (
|
||||
|
|
|
@ -14,21 +14,27 @@ import EventLoginWarning from "@/pages/events/s/EventLoginWarning.tsx";
|
|||
|
||||
export default function SharedEvent() {
|
||||
|
||||
const {pb} = usePB()
|
||||
|
||||
const {pb, user} = usePB()
|
||||
|
||||
const {eventId} = useParams() as { eventId: string }
|
||||
|
||||
const [searchParams] = useSearchParams()
|
||||
const listIds = searchParams.get('lists')?.split(",") ?? []
|
||||
|
||||
|
||||
const eventQuery = useQuery({
|
||||
queryKey: ["event", eventId],
|
||||
queryFn: async () => (await pb.collection("events").getOne(eventId)),
|
||||
enabled: !!eventId
|
||||
})
|
||||
|
||||
const userEntriesQuery = useQuery({
|
||||
queryKey: ["userEntries", user?.id ?? "anonym"],
|
||||
queryFn: async () => (await pb.collection("eventListSlotEntriesWithUser").getList(1, 0, {
|
||||
filter: `user='${user?.id ?? "anonym"}'&&event='${eventId}'`,
|
||||
})),
|
||||
enabled: !!user
|
||||
})
|
||||
|
||||
const {isPrivilegedUser, canEditEvent} = useEventRights(eventQuery.data)
|
||||
|
||||
if (eventQuery.isLoading) {
|
||||
|
@ -68,6 +74,34 @@ export default function SharedEvent() {
|
|||
|
||||
<EventLoginWarning/>
|
||||
|
||||
{
|
||||
(userEntriesQuery.data?.totalItems ?? 0) > 0 && <>
|
||||
<div className={"section-transparent"}>
|
||||
<Alert
|
||||
color={"green"}
|
||||
title={
|
||||
`Du hast für dieses Event bereits
|
||||
${userEntriesQuery.data?.totalItems === 1 ? "eine Anmeldung" :
|
||||
`${userEntriesQuery.data?.totalItems} Anmeldungen`}`
|
||||
}
|
||||
>
|
||||
Du hast dich bereits für dieses Event angemeldet. Du kannst deine Anmeldungen auf der
|
||||
<Button
|
||||
component={Link}
|
||||
to={`/events/entries`}
|
||||
variant={"light"}
|
||||
leftSection={<IconEye/>}
|
||||
size={"compact-xs"}
|
||||
ms="xs"
|
||||
me={"xs"}
|
||||
>
|
||||
Anmeldungen-Seite
|
||||
</Button> einsehen.
|
||||
</Alert>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
{eventIsArchived && <div className={"section-transparent"}>
|
||||
<Alert color={"orange"} icon={<IconArchive/>}>
|
||||
Dieses Event ist archiviert und wird nicht mehr verwaltet
|
||||
|
|
Loading…
Reference in New Issue