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
|
// general
|
||||||
export const APP_NAME = "StuVe IT"
|
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"
|
export const APP_URL = "https://it.stuve.uni-ulm.de"
|
|
@ -33,8 +33,7 @@ export default function EmailTokenVerification() {
|
||||||
|
|
||||||
const verifyTokenMutation = useMutation({
|
const verifyTokenMutation = useMutation({
|
||||||
mutationFn: async (token: string) => {
|
mutationFn: async (token: string) => {
|
||||||
const res = await pb.collection("users").confirmVerification(token)
|
await pb.collection("users").confirmVerification(token)
|
||||||
console.log({res})
|
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
showSuccessNotification("E-Mail erfolgreich verifiziert")
|
showSuccessNotification("E-Mail erfolgreich verifiziert")
|
||||||
|
@ -51,7 +50,7 @@ export default function EmailTokenVerification() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = searchParams.get(EMAIL_TOKEN_KEY)
|
const token = searchParams.get(EMAIL_TOKEN_KEY)
|
||||||
if (token !== null) {
|
if (token !== null && !user && !verifyTokenMutation.isPending) {
|
||||||
verifyTokenMutation.mutate(token)
|
verifyTokenMutation.mutate(token)
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// 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 {showSuccessNotification} from "@/components/util.tsx";
|
||||||
import {Link} from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
|
|
||||||
|
|
||||||
export default function RegisterModal() {
|
export default function RegisterModal() {
|
||||||
const {value, handler} = useRegister()
|
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,
|
passwordConfirm: (val, values) => val !== values.password ? "Die Passwörter stimmen nicht überein" : null,
|
||||||
terms: (val) => !val ? "Du musst die AGB akzeptieren" : null,
|
terms: (val) => !val ? "Du musst die AGB akzeptieren" : null,
|
||||||
privacy: (val) => !val ? "Du musst die Datenschutzerklärung akzeptieren" : null
|
privacy: (val) => !val ? "Du musst die Datenschutzerklärung akzeptieren" : null
|
||||||
}
|
},
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const registerMutation = useMutation({
|
const registerMutation = useMutation({
|
||||||
|
@ -79,10 +81,28 @@ export default function RegisterModal() {
|
||||||
|
|
||||||
<Collapse in={formValues.values.email.length > 0} className={"stack"}>
|
<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
|
<TextInput
|
||||||
label={"Anmeldename"}
|
label={"Anmeldename"}
|
||||||
description={"Dein Anmeldename ist für eingeloggte Personen sichtbar"}
|
description={"Dein Anmeldename ist für eingeloggte Personen sichtbar"}
|
||||||
placeholder={"Anmeldename"}
|
placeholder={"vorname.nachname"}
|
||||||
leftSection={<IconUser/>}
|
leftSection={<IconUser/>}
|
||||||
required
|
required
|
||||||
{...formValues.getInputProps("username")}
|
{...formValues.getInputProps("username")}
|
||||||
|
|
|
@ -94,6 +94,7 @@ const PocketData = () => {
|
||||||
pb.authStore.save(token, record)
|
pb.authStore.save(token, record)
|
||||||
return record
|
return record
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error("refresh token error", e)
|
||||||
pb.authStore.clear()
|
pb.authStore.clear()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -111,12 +112,14 @@ const PocketData = () => {
|
||||||
const record = await pb.collection(PB_USER_COLLECTION).getOne(pb.authStore.model?.id ?? "", {
|
const record = await pb.collection(PB_USER_COLLECTION).getOne(pb.authStore.model?.id ?? "", {
|
||||||
expand: "memberOf"
|
expand: "memberOf"
|
||||||
})
|
})
|
||||||
if (record.verified) {
|
if (record.verified === true) {
|
||||||
pb.authStore.save(pb.authStore.token, record)
|
pb.authStore.save(pb.authStore.token, record)
|
||||||
return record
|
return record
|
||||||
} else {
|
} else if (record.verified === false) {
|
||||||
pb.authStore.clear()
|
pb.authStore.clear()
|
||||||
return null
|
return null
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof ClientResponseError && e.status === 401) {
|
if (e instanceof ClientResponseError && e.status === 401) {
|
||||||
|
@ -179,7 +182,7 @@ const PocketData = () => {
|
||||||
ldapLogin,
|
ldapLogin,
|
||||||
guestLogin,
|
guestLogin,
|
||||||
logout,
|
logout,
|
||||||
user: user as UserModal | null,
|
user: pb.authStore.isValid ? user as UserModal | null : null,
|
||||||
pb,
|
pb,
|
||||||
refreshUser: refreshUserQuery.refetch,
|
refreshUser: refreshUserQuery.refetch,
|
||||||
useSubscription,
|
useSubscription,
|
||||||
|
|
|
@ -2,17 +2,17 @@ import PromptLoginModal from "@/components/users/modals/PromptLoginModal.tsx";
|
||||||
import {Link, useNavigate} from "react-router-dom";
|
import {Link, useNavigate} from "react-router-dom";
|
||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
import {useDebouncedValue, useToggle} from "@mantine/hooks";
|
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 {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
||||||
import {
|
import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
Anchor,
|
Anchor,
|
||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
|
Button,
|
||||||
Center,
|
Center,
|
||||||
Group,
|
Group,
|
||||||
Loader,
|
Loader,
|
||||||
LoadingOverlay,
|
LoadingOverlay,
|
||||||
Pagination,
|
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
|
@ -21,7 +21,7 @@ import {
|
||||||
Tooltip
|
Tooltip
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import ShowHelp from "@/components/ShowHelp.tsx";
|
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";
|
import UserEntryRow from "@/pages/events/entries/UserEntryRow.tsx";
|
||||||
|
|
||||||
export default function UserEntries() {
|
export default function UserEntries() {
|
||||||
|
@ -30,23 +30,24 @@ export default function UserEntries() {
|
||||||
|
|
||||||
const {pb, user} = usePB()
|
const {pb, user} = usePB()
|
||||||
|
|
||||||
const [page, setPage] = useState(1)
|
|
||||||
|
|
||||||
const [sortDirection, toggleSortDirection] = useToggle(['DESC', 'ASC']);
|
const [sortDirection, toggleSortDirection] = useToggle(['DESC', 'ASC']);
|
||||||
|
|
||||||
const [eventSearch, setEventSearch] = useState("")
|
const [eventSearch, setEventSearch] = useState("")
|
||||||
|
|
||||||
const [debouncedSearch] = useDebouncedValue(eventSearch, 200)
|
const [debouncedSearch] = useDebouncedValue(eventSearch, 200)
|
||||||
|
|
||||||
const entriesQuery = useQuery({
|
const entriesQuery = useInfiniteQuery({
|
||||||
queryKey: ["userEntries", user?.id, page, debouncedSearch, sortDirection],
|
queryKey: ["userEntries", user?.id, debouncedSearch, sortDirection],
|
||||||
queryFn: async () => (
|
queryFn: async ({pageParam}) => (
|
||||||
await pb.collection("eventListSlotEntriesWithUser").getList(page, 50, {
|
await pb.collection("eventListSlotEntriesWithUser").getList(pageParam, 50, {
|
||||||
filter: `user='${user?.id}'${debouncedSearch ? `&&event.name~'${debouncedSearch}'` : ""}`,
|
filter: `user='${user?.id}'${debouncedSearch ? `&&event.name~'${debouncedSearch}'` : ""}`,
|
||||||
sort: sortDirection === "ASC" ? "-event.startDate" : "event.startDate",
|
sort: sortDirection === "ASC" ? "-event.startDate" : "event.startDate",
|
||||||
expand: "user"
|
expand: "user"
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
getNextPageParam: (lastPage) =>
|
||||||
|
lastPage.page >= lastPage.totalPages ? undefined : lastPage.page + 1,
|
||||||
|
initialPageParam: 1,
|
||||||
enabled: !!user
|
enabled: !!user
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -54,6 +55,9 @@ export default function UserEntries() {
|
||||||
return <PromptLoginModal onAbort={() => navigate("/")}/>
|
return <PromptLoginModal onAbort={() => navigate("/")}/>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const entries = entriesQuery.data?.pages.flatMap(page => page.items) ?? []
|
||||||
|
const totalItems = entriesQuery.data?.pages[0].totalItems ?? 0
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
{entriesQuery.isLoading && <LoadingOverlay/>}
|
{entriesQuery.isLoading && <LoadingOverlay/>}
|
||||||
|
|
||||||
|
@ -104,13 +108,13 @@ export default function UserEntries() {
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text c={"dimmed"} size={"xs"}>
|
<Text c={"dimmed"} size={"xs"}>
|
||||||
{entriesQuery.data?.totalItems ?? 0} {eventSearch ? "Ergebnisse" : "Anmeldungen"}
|
{totalItems ?? 0} {eventSearch ? "Ergebnisse" : "Anmeldungen"}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<PocketBaseErrorAlert error={entriesQuery.error}/>
|
<PocketBaseErrorAlert error={entriesQuery.error}/>
|
||||||
|
|
||||||
{entriesQuery.data?.totalItems === 0 && <Stack align={"center"}>
|
{totalItems === 0 && <Stack align={"center"}>
|
||||||
<ThemeIcon variant={"transparent"} color={"gray"} size={"md"}>
|
<ThemeIcon variant={"transparent"} color={"gray"} size={"md"}>
|
||||||
<IconDatabaseOff/>
|
<IconDatabaseOff/>
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
|
@ -119,14 +123,25 @@ export default function UserEntries() {
|
||||||
</Stack>
|
</Stack>
|
||||||
}
|
}
|
||||||
|
|
||||||
{entriesQuery.data?.items.map(entry => (
|
{entries.map(entry => (
|
||||||
<UserEntryRow entry={entry} refetch={() => entriesQuery.refetch()} key={entry.id}/>
|
<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>
|
<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>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
|
@ -18,6 +18,7 @@ import {RenderDateRange} from "@/pages/events/e/:eventId/EventLists/EventListCom
|
||||||
import {
|
import {
|
||||||
EntryQuestionAndStatusData
|
EntryQuestionAndStatusData
|
||||||
} from "@/pages/events/e/:eventId/EventLists/EventListComponents/EntryQuestionAndStatusData.tsx";
|
} from "@/pages/events/e/:eventId/EventLists/EventListComponents/EntryQuestionAndStatusData.tsx";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
|
||||||
|
|
||||||
export default function UserEntryRow({entry, refetch}: {
|
export default function UserEntryRow({entry, refetch}: {
|
||||||
|
@ -80,7 +81,9 @@ export default function UserEntryRow({entry, refetch}: {
|
||||||
<IconList/>
|
<IconList/>
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
}>
|
}>
|
||||||
{entry.listName}
|
<Link to={`/events/s/${entry.event}?lists=${entry.eventList}`} >
|
||||||
|
{entry.listName}
|
||||||
|
</Link>
|
||||||
</TextWithIcon>
|
</TextWithIcon>
|
||||||
|
|
||||||
<Group gap={"xs"} justify={"center"}>
|
<Group gap={"xs"} justify={"center"}>
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
import {EventListModel, EventListSlotsWithEntriesCountModel} from "@/models/EventTypes.ts";
|
import {EventListModel, EventListSlotsWithEntriesCountModel} from "@/models/EventTypes.ts";
|
||||||
import classes from "./EventListSlotView.module.css";
|
import classes from "./EventListSlotView.module.css";
|
||||||
import {useDisclosure} from "@mantine/hooks";
|
import {useDisclosure} from "@mantine/hooks";
|
||||||
import {Alert, Collapse, ThemeIcon, Tooltip, UnstyledButton} from "@mantine/core";
|
import {Alert, Button, Collapse, Group, List, ThemeIcon, Tooltip, UnstyledButton} from "@mantine/core";
|
||||||
import {IconChevronDown, IconChevronRight, IconInfoCircle} from "@tabler/icons-react";
|
import {IconChevronDown, IconChevronRight, IconEye, IconInfoCircle} from "@tabler/icons-react";
|
||||||
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
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 {FieldEntries} from "@/components/formUtil/FromInput/types.ts";
|
||||||
import {showSuccessNotification} from "@/components/util.tsx";
|
import {showSuccessNotification} from "@/components/util.tsx";
|
||||||
import InnerHtml from "@/components/InnerHtml";
|
import InnerHtml from "@/components/InnerHtml";
|
||||||
import FormInput from "@/components/formUtil/FromInput";
|
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 {getListSchemas} from "@/pages/events/util.ts";
|
||||||
import {RenderDateRange} from "@/pages/events/e/:eventId/EventLists/EventListComponents/RenderDateRange.tsx";
|
import {RenderDateRange} from "@/pages/events/e/:eventId/EventLists/EventListComponents/RenderDateRange.tsx";
|
||||||
import SlotProgress from "@/pages/events/e/:eventId/EventLists/EventListComponents/SlotProgress.tsx";
|
import SlotProgress from "@/pages/events/e/:eventId/EventLists/EventListComponents/SlotProgress.tsx";
|
||||||
import EventLoginWarning from "@/pages/events/s/EventLoginWarning.tsx";
|
import EventLoginWarning from "@/pages/events/s/EventLoginWarning.tsx";
|
||||||
|
import {pprintDateRange} from "@/lib/datetime.ts";
|
||||||
|
|
||||||
export default function EventListSlotView({slot, list, refetch}: {
|
export default function EventListSlotView({slot, list, refetch}: {
|
||||||
list: EventListModel,
|
list: EventListModel,
|
||||||
|
@ -32,6 +33,24 @@ export default function EventListSlotView({slot, list, refetch}: {
|
||||||
|
|
||||||
const navigate = useNavigate()
|
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({
|
const createEntryMutation = useMutation({
|
||||||
mutationFn: async (data: FieldEntries) => {
|
mutationFn: async (data: FieldEntries) => {
|
||||||
await pb.collection("eventListSlotEntries").create({
|
await pb.collection("eventListSlotEntries").create({
|
||||||
|
@ -57,11 +76,9 @@ export default function EventListSlotView({slot, list, refetch}: {
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<div className={classes.slotInfo}>
|
<div className={classes.slotInfo}>
|
||||||
|
|
||||||
<RenderDateRange start={new Date(slot.startDate)} end={new Date(slot.endDate)}/>
|
<RenderDateRange start={new Date(slot.startDate)} end={new Date(slot.endDate)}/>
|
||||||
|
|
||||||
<SlotProgress slot={slot}/>
|
<SlotProgress slot={slot}/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</UnstyledButton>
|
</UnstyledButton>
|
||||||
|
|
||||||
|
@ -77,7 +94,35 @@ export default function EventListSlotView({slot, list, refetch}: {
|
||||||
|
|
||||||
{
|
{
|
||||||
!user ? <EventLoginWarning/> :
|
!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"}>
|
<Alert color={"red"}>
|
||||||
Für diese Liste sind nur StuVe-Accounts zugelassen
|
Für diese Liste sind nur StuVe-Accounts zugelassen
|
||||||
</Alert>
|
</Alert>
|
||||||
|
@ -89,8 +134,7 @@ export default function EventListSlotView({slot, list, refetch}: {
|
||||||
<Alert color={"red"} title={"Zeitslot voll"}>
|
<Alert color={"red"} title={"Zeitslot voll"}>
|
||||||
Dieser Zeitslot ist bereits voll
|
Dieser Zeitslot ist bereits voll
|
||||||
</Alert>
|
</Alert>
|
||||||
</>
|
</> :
|
||||||
:
|
|
||||||
<FormInput
|
<FormInput
|
||||||
disabled={!user || slotIsFull}
|
disabled={!user || slotIsFull}
|
||||||
schema={questionSchema}
|
schema={questionSchema}
|
||||||
|
|
|
@ -62,15 +62,6 @@ export default function EventListView({event, listId}: { event: EventModel, list
|
||||||
</Alert>
|
</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 &&
|
list.onlyStuVeAccounts &&
|
||||||
<Alert>
|
<Alert>
|
||||||
|
@ -78,7 +69,6 @@ export default function EventListView({event, listId}: { event: EventModel, list
|
||||||
</Alert>
|
</Alert>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
(list.open && slots.length !== 0) && <>
|
(list.open && slots.length !== 0) && <>
|
||||||
{slots.map(slot => (
|
{slots.map(slot => (
|
||||||
|
|
|
@ -14,21 +14,27 @@ import EventLoginWarning from "@/pages/events/s/EventLoginWarning.tsx";
|
||||||
|
|
||||||
export default function SharedEvent() {
|
export default function SharedEvent() {
|
||||||
|
|
||||||
const {pb} = usePB()
|
const {pb, user} = usePB()
|
||||||
|
|
||||||
|
|
||||||
const {eventId} = useParams() as { eventId: string }
|
const {eventId} = useParams() as { eventId: string }
|
||||||
|
|
||||||
const [searchParams] = useSearchParams()
|
const [searchParams] = useSearchParams()
|
||||||
const listIds = searchParams.get('lists')?.split(",") ?? []
|
const listIds = searchParams.get('lists')?.split(",") ?? []
|
||||||
|
|
||||||
|
|
||||||
const eventQuery = useQuery({
|
const eventQuery = useQuery({
|
||||||
queryKey: ["event", eventId],
|
queryKey: ["event", eventId],
|
||||||
queryFn: async () => (await pb.collection("events").getOne(eventId)),
|
queryFn: async () => (await pb.collection("events").getOne(eventId)),
|
||||||
enabled: !!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)
|
const {isPrivilegedUser, canEditEvent} = useEventRights(eventQuery.data)
|
||||||
|
|
||||||
if (eventQuery.isLoading) {
|
if (eventQuery.isLoading) {
|
||||||
|
@ -68,6 +74,34 @@ export default function SharedEvent() {
|
||||||
|
|
||||||
<EventLoginWarning/>
|
<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"}>
|
{eventIsArchived && <div className={"section-transparent"}>
|
||||||
<Alert color={"orange"} icon={<IconArchive/>}>
|
<Alert color={"orange"} icon={<IconArchive/>}>
|
||||||
Dieses Event ist archiviert und wird nicht mehr verwaltet
|
Dieses Event ist archiviert und wird nicht mehr verwaltet
|
||||||
|
|
Loading…
Reference in New Issue