fix(app): added legal pages and made formUtil description HTML
Build and Push Docker image / build-and-push (push) Successful in 1m43s
Details
Build and Push Docker image / build-and-push (push) Successful in 1m43s
Details
This commit is contained in:
parent
5d2eba55b0
commit
1afa7596d0
|
@ -4,8 +4,7 @@ import NotFound from "./pages/not-found/index.page.tsx";
|
|||
import Layout from "@/components/layout";
|
||||
import QRCodeGenerator from "./pages/util/qr/index.page.tsx";
|
||||
import EventsRouter from "./pages/events/EventsRouter.tsx";
|
||||
import PrivacyPolicy from "./pages/privacy-policy.page.tsx";
|
||||
import TermsAndConditions from "./pages/terms-and-conditions.page.tsx";
|
||||
import LegalPage from "@/pages/LegalPage.tsx";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
|
@ -17,16 +16,8 @@ const router = createBrowserRouter([
|
|||
element: <HomePage/>
|
||||
},
|
||||
{
|
||||
path: "privacy-policy",
|
||||
element: <PrivacyPolicy/>
|
||||
},
|
||||
{
|
||||
path: "imprint",
|
||||
element: <PrivacyPolicy/>
|
||||
},
|
||||
{
|
||||
path: "terms-and-conditions",
|
||||
element: <TermsAndConditions/>
|
||||
path: "legal/:page",
|
||||
element: <LegalPage/>
|
||||
},
|
||||
{
|
||||
path: "events/*",
|
||||
|
|
|
@ -36,7 +36,7 @@ export default function LoginModal() {
|
|||
username: "",
|
||||
password: "",
|
||||
authMethod: "ldap" as "ldap" | "guest",
|
||||
privacy: false
|
||||
terms: false
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -94,23 +94,23 @@ export default function LoginModal() {
|
|||
required
|
||||
label={
|
||||
<Text>
|
||||
Ich akzeptiere die <Anchor
|
||||
Ich habe die <Anchor
|
||||
component={Link}
|
||||
target={"_blank"}
|
||||
to={"/privacy-policy"}
|
||||
to={"/legal/terms-and-conditions"}
|
||||
>
|
||||
Datenschutzerklärung
|
||||
</Anchor>.
|
||||
AGB der StuVe
|
||||
</Anchor> gelesen und nehme sie zur Kenntnis
|
||||
</Text>
|
||||
}
|
||||
{...formValues.getInputProps("privacy", {type: "checkbox"})}
|
||||
{...formValues.getInputProps("terms", {type: "checkbox"})}
|
||||
/>
|
||||
|
||||
<PocketBaseErrorAlert error={loginMutation.error}/>
|
||||
|
||||
<Button
|
||||
loading={loginMutation.isPending}
|
||||
disabled={formValues.values.username === "" || formValues.values.password === "" || !formValues.values.privacy}
|
||||
disabled={formValues.values.username === "" || formValues.values.password === "" || !formValues.values.terms}
|
||||
type={"submit"}
|
||||
>
|
||||
Einloggen
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import {useRegister} from "@/components/auth/modals/hooks.ts";
|
||||
import {isEmail, useForm} from "@mantine/form";
|
||||
import {Alert, Button, Collapse, Group, Modal, PasswordInput, TextInput} from "@mantine/core";
|
||||
import {Alert, Anchor, Button, Checkbox, Collapse, Group, Modal, PasswordInput, Text, TextInput} from "@mantine/core";
|
||||
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
||||
import {IconAt, IconKey, IconUser, IconUserPlus, IconX} from "@tabler/icons-react";
|
||||
import PasswordStrengthMeter, {getPasswordStrength} from "@/components/input/PasswordStrengthMeter.tsx";
|
||||
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()
|
||||
|
@ -20,12 +21,16 @@ export default function RegisterModal() {
|
|||
passwordConfirm: "",
|
||||
sn: "",
|
||||
givenName: "",
|
||||
terms: false,
|
||||
privacy: false
|
||||
},
|
||||
validate: {
|
||||
email: isEmail("Ungültige E-Mail Adresse"),
|
||||
username: (val) => val.length < 3 ? "Der Anmeldename muss mindestens 3 Zeichen lang sein" : null,
|
||||
password: (val) => getPasswordStrength(val) !== 100 ? "Das Passwort ist zu schwach" : null,
|
||||
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,
|
||||
privacy: (val) => !val ? "Du musst die Datenschutzerklärung akzeptieren" : null
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -106,6 +111,38 @@ export default function RegisterModal() {
|
|||
</Collapse>
|
||||
</Collapse>
|
||||
|
||||
<Checkbox
|
||||
required
|
||||
label={
|
||||
<Text>
|
||||
Ich habe die <Anchor
|
||||
component={Link}
|
||||
target={"_blank"}
|
||||
to={"/legal/privacy-policy"}
|
||||
>
|
||||
Datenschutzerklärung
|
||||
</Anchor> gelesen und akzeptiere sie.
|
||||
</Text>
|
||||
}
|
||||
{...formValues.getInputProps("privacy", {type: "checkbox"})}
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
required
|
||||
label={
|
||||
<Text>
|
||||
Ich habe die <Anchor
|
||||
component={Link}
|
||||
target={"_blank"}
|
||||
to={"/legal/terms-and-conditions"}
|
||||
>
|
||||
AGB der StuVe
|
||||
</Anchor> gelesen und nehme sie zur Kenntnis
|
||||
</Text>
|
||||
}
|
||||
{...formValues.getInputProps("terms", {type: "checkbox"})}
|
||||
/>
|
||||
|
||||
<Group>
|
||||
<Button
|
||||
leftSection={<IconX/>}
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
import {DatePickerInput, DatePickerInputProps, DateTimePicker, DateTimePickerProps} from "@mantine/dates";
|
||||
import {
|
||||
Input,
|
||||
MultiSelect,
|
||||
MultiSelectProps,
|
||||
NumberInput,
|
||||
NumberInputProps,
|
||||
Select,
|
||||
SelectProps,
|
||||
Spoiler,
|
||||
Stack,
|
||||
Text,
|
||||
Textarea,
|
||||
TextareaProps,
|
||||
TextInput,
|
||||
TextInputProps
|
||||
} from "@mantine/core";
|
||||
import {IconAt} from "@tabler/icons-react";
|
||||
import {
|
||||
CheckboxFieldSchema,
|
||||
DateFieldSchema,
|
||||
DateRangeFieldSchema,
|
||||
EmailFieldSchema,
|
||||
FormSchemaField,
|
||||
NumberFieldSchema,
|
||||
SelectFieldSchema,
|
||||
TextFieldSchema
|
||||
} from "../formBuilder/types.ts";
|
||||
import {CheckboxCard, CheckboxCardProps} from "@/components/input/CheckboxCard";
|
||||
import InnerHtml from "@/components/InnerHtml";
|
||||
import {ReactNode} from "react";
|
||||
|
||||
|
||||
export const FormTextField = ({field, ...props}: { field: TextFieldSchema } & TextInputProps) => {
|
||||
if (field.multiline) {
|
||||
console.error("for multiline text fields use FormTextareaField instead of FormTextField")
|
||||
return null
|
||||
}
|
||||
|
||||
return <Wrapper field={field}>
|
||||
<TextInput
|
||||
required={field.required}
|
||||
placeholder={field.placeholder ?? "Text eingeben"}
|
||||
{...props}
|
||||
/>
|
||||
</Wrapper>
|
||||
|
||||
}
|
||||
export const FormTextareaField = ({field, ...props}: { field: TextFieldSchema } & TextareaProps) => {
|
||||
if (!field.multiline) {
|
||||
console.error("for single line text fields use FormTextField instead of FormTextareaField")
|
||||
return null
|
||||
}
|
||||
return <Wrapper field={field}>
|
||||
<Textarea
|
||||
resize={"vertical"}
|
||||
required={field.required}
|
||||
placeholder={field.placeholder ?? "Text eingeben"}
|
||||
{...props}
|
||||
/>
|
||||
</Wrapper>
|
||||
}
|
||||
|
||||
export const EmailField = ({field, ...props}: { field: EmailFieldSchema } & TextInputProps) => {
|
||||
return <Wrapper field={field}>
|
||||
<TextInput
|
||||
leftSection={<IconAt/>}
|
||||
required={field.required}
|
||||
placeholder={field.placeholder ?? "Email eingeben"}
|
||||
type={"email"}
|
||||
{...props}
|
||||
/>
|
||||
</Wrapper>
|
||||
}
|
||||
|
||||
export const NumberField = ({field, ...props}: { field: NumberFieldSchema } & NumberInputProps) => {
|
||||
|
||||
return <Wrapper field={field}>
|
||||
<NumberInput
|
||||
required={field.required}
|
||||
placeholder={field.placeholder ?? "Zahl eingeben"}
|
||||
{...props}
|
||||
/>
|
||||
</Wrapper>
|
||||
}
|
||||
|
||||
export const DateField = ({field, ...props}: { field: DateFieldSchema } & DateTimePickerProps) => {
|
||||
return <Wrapper field={field}>
|
||||
<DateTimePicker
|
||||
clearable
|
||||
required={field.required}
|
||||
placeholder={field.placeholder ?? "Datum auswählen"}
|
||||
{...props}
|
||||
/>
|
||||
</Wrapper>
|
||||
}
|
||||
|
||||
export const DateRangeField = ({field, ...props}: { field: DateRangeFieldSchema } & DatePickerInputProps<"range">) => {
|
||||
return <Wrapper field={field}>
|
||||
{
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
<DatePickerInput
|
||||
type="range"
|
||||
clearable
|
||||
required={field.required}
|
||||
placeholder={field.placeholder ?? "Zeitraum auswählen"}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
</Wrapper>
|
||||
}
|
||||
|
||||
export const SelectField = ({field, ...props}: {
|
||||
field: SelectFieldSchema
|
||||
} & SelectProps & MultiSelectProps) => {
|
||||
|
||||
const Component = field.multiple ? MultiSelect : Select
|
||||
|
||||
return <>
|
||||
<Wrapper field={field}>
|
||||
<Component
|
||||
clearable
|
||||
required={field.required}
|
||||
placeholder={field.placeholder ?? "Auswählen"}
|
||||
{...props}
|
||||
data={field.options}
|
||||
/>
|
||||
</Wrapper>
|
||||
</>
|
||||
}
|
||||
|
||||
const Wrapper = ({field, children}: {
|
||||
field: FormSchemaField,
|
||||
children: ReactNode
|
||||
}) => {
|
||||
return <Stack gap={5}>
|
||||
<Input.Label required>{field.label}</Input.Label>
|
||||
|
||||
{field.description && <>
|
||||
<Input.Description>
|
||||
<Spoiler
|
||||
maxHeight={40}
|
||||
showLabel={<Text span size={"xs"}>Mehr anzeigen</Text>}
|
||||
hideLabel={<Text span size={"xs"}>Weniger anzeigen</Text>}
|
||||
>
|
||||
<InnerHtml html={field.description}/>
|
||||
</Spoiler>
|
||||
</Input.Description>
|
||||
</>}
|
||||
|
||||
{children}
|
||||
</Stack>
|
||||
}
|
||||
|
||||
|
||||
export const CheckboxField = ({field, ...props}: { field: CheckboxFieldSchema } & CheckboxCardProps) => {
|
||||
return <CheckboxCard
|
||||
label={field.label}
|
||||
description={field.description}
|
||||
required={field.required}
|
||||
{...props}
|
||||
/>
|
||||
}
|
|
@ -1,19 +1,18 @@
|
|||
import {FormSchema} from "../formBuilder/types.ts";
|
||||
import {Button, Group,} from "@mantine/core";
|
||||
import {useForm} from "@mantine/form";
|
||||
import {FieldEntries} from "./types.ts";
|
||||
|
||||
import ShowDebug from "../../ShowDebug.tsx";
|
||||
import {FieldEntries} from "@/components/formUtil/FromInput/types.ts";
|
||||
import {createValidationFromSchema} from "@/components/formUtil/FromInput/validation.ts";
|
||||
import {
|
||||
CheckboxField,
|
||||
DateField,
|
||||
DateRangeField,
|
||||
DateField, DateRangeField,
|
||||
EmailField,
|
||||
FormTextareaField,
|
||||
FormTextField,
|
||||
NumberField,
|
||||
SelectField
|
||||
} from "./formFieldComponents.tsx";
|
||||
import {createValidationFromSchema} from "./validation.ts";
|
||||
import ShowDebug from "../../ShowDebug.tsx";
|
||||
NumberField, SelectField
|
||||
} from "@/components/formUtil/FromInput/formFieldComponents.tsx";
|
||||
|
||||
/**
|
||||
* This function creates default values based on a data type (e.g. "" for text, false for checkbox)
|
|
@ -6,8 +6,8 @@ import {
|
|||
NumberFieldSchema,
|
||||
TextFieldSchema
|
||||
} from "../formBuilder/types.ts"
|
||||
import {FieldEntry} from "./types.ts";
|
||||
import {pprintDateTime} from "@/lib/datetime.ts";
|
||||
import {FieldEntry} from "@/components/formUtil/FromInput/types.ts";
|
||||
|
||||
|
||||
const textValidator = (field: TextFieldSchema, value: string) => {
|
|
@ -9,6 +9,7 @@ import {
|
|||
Fieldset,
|
||||
Group,
|
||||
NumberInput,
|
||||
Spoiler,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
|
@ -33,6 +34,8 @@ import {useDisclosure} from "@mantine/hooks";
|
|||
import classes from "./editField.module.css"
|
||||
import {Draggable} from "@hello-pangea/dnd";
|
||||
import {humanReadableField} from "../util.ts";
|
||||
import TextEditor from "@/components/input/Editor";
|
||||
import InnerHtml from "@/components/InnerHtml";
|
||||
|
||||
type EditFieldProps = {
|
||||
index: number;
|
||||
|
@ -132,8 +135,8 @@ const SelectFieldSettings = ({field, formValues, index}: Omit<EditFieldProps, "f
|
|||
placeholder={"Option"}
|
||||
|
||||
rightSection={
|
||||
|
||||
<ActionIcon
|
||||
disabled={field.meta?.required}
|
||||
color={"red"}
|
||||
variant={"transparent"}
|
||||
aria-label={"delete option"}
|
||||
|
@ -153,6 +156,7 @@ const SelectFieldSettings = ({field, formValues, index}: Omit<EditFieldProps, "f
|
|||
|
||||
<Group>
|
||||
<Button
|
||||
disabled={field.meta?.required}
|
||||
variant={"light"} size="xs"
|
||||
leftSection={<IconPlus/>}
|
||||
onClick={() => {
|
||||
|
@ -297,12 +301,24 @@ export default function EditField({field, formValues, index}: EditFieldProps) {
|
|||
</Badge>
|
||||
</Tooltip>
|
||||
|
||||
<Group>
|
||||
<TextInput
|
||||
label={"Beschreibung"}
|
||||
{...formValues.getInputProps(`fields.${index}.description`)}
|
||||
{
|
||||
field.meta?.required ? <>
|
||||
<Fieldset legend={"Beschreibung"}>
|
||||
<Spoiler hideLabel={"Beschreibung ausblenden"}
|
||||
showLabel={"Beschreibung anzeigen"}>
|
||||
<InnerHtml html={field.description ?? ""}/>
|
||||
</Spoiler>
|
||||
</Fieldset>
|
||||
</> : <TextEditor
|
||||
placeholder={"Beschreibung ..."}
|
||||
value={formValues.values.fields[index].description ?? ""}
|
||||
onChange={(value) => {
|
||||
formValues.setFieldValue(`fields.${index}.description`, value)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
<Group>
|
||||
<TextInput
|
||||
label={"Platzhalter"}
|
||||
{...formValues.getInputProps(`fields.${index}.placeholder`)}
|
||||
|
|
|
@ -6,7 +6,7 @@ import EditField from "./editField.tsx";
|
|||
import {IconDatabaseOff, IconEye, IconSquarePlus2} from "@tabler/icons-react";
|
||||
import FormTypeIcon from "../FormTypeIcon.tsx";
|
||||
import {useDisclosure} from "@mantine/hooks";
|
||||
import FormInput from "../fromInput";
|
||||
import FormInput from "../FromInput";
|
||||
import {DragDropContext, Droppable} from "@hello-pangea/dnd";
|
||||
import {useEffect} from "react";
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {FieldEntry} from "../fromInput/types.ts";
|
||||
import {FieldEntry} from "@/components/formUtil/FromInput/types.ts";
|
||||
import {ReactNode} from "react";
|
||||
import {Anchor, Badge, Code, Group, List, Spoiler, ThemeIcon, Tooltip} from "@mantine/core";
|
||||
import {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {FieldEntries, FieldEntry} from "../fromInput/types.ts"
|
||||
import {FieldEntries, FieldEntry} from "@/components/formUtil/FromInput/types.ts"
|
||||
import {objectMap} from "@/lib/util.ts";
|
||||
import {FormSchemaField} from "../formBuilder/types.ts";
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {FieldDataType} from "../formBuilder/types.ts";
|
||||
import {FieldEntryValue} from "../fromInput/types.ts";
|
||||
import {FieldEntryValue} from "@/components/formUtil/FromInput/types.ts";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
import {DatePickerInput, DatePickerInputProps, DateTimePicker, DateTimePickerProps} from "@mantine/dates";
|
||||
import {
|
||||
MultiSelect,
|
||||
MultiSelectProps,
|
||||
NumberInput,
|
||||
NumberInputProps,
|
||||
Select,
|
||||
SelectProps,
|
||||
Textarea,
|
||||
TextareaProps,
|
||||
TextInput,
|
||||
TextInputProps
|
||||
} from "@mantine/core";
|
||||
import {IconAt} from "@tabler/icons-react";
|
||||
import {
|
||||
CheckboxFieldSchema,
|
||||
DateFieldSchema,
|
||||
DateRangeFieldSchema,
|
||||
EmailFieldSchema,
|
||||
FormSchemaField,
|
||||
NumberFieldSchema,
|
||||
SelectFieldSchema,
|
||||
TextFieldSchema
|
||||
} from "../formBuilder/types.ts";
|
||||
import {CheckboxCard, CheckboxCardProps} from "@/components/input/CheckboxCard";
|
||||
|
||||
/**
|
||||
* This function unpacks a field into a set of props that can be used in the input components
|
||||
* @param field
|
||||
*/
|
||||
const unpackField = (field: FormSchemaField) => {
|
||||
return {
|
||||
label: field.label || undefined,
|
||||
placeholder: field.placeholder || undefined,
|
||||
description: field.description || undefined,
|
||||
required: field.required || false,
|
||||
}
|
||||
}
|
||||
|
||||
export const FormTextField = ({field, ...props}: { field: TextFieldSchema } & TextInputProps) => {
|
||||
if (field.multiline) {
|
||||
console.error("for multiline text fields use FormTextareaField instead of FormTextField")
|
||||
return null
|
||||
}
|
||||
|
||||
return <TextInput
|
||||
{...unpackField(field)}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
}
|
||||
export const FormTextareaField = ({field, ...props}: { field: TextFieldSchema } & TextareaProps) => {
|
||||
if (!field.multiline) {
|
||||
console.error("for single line text fields use FormTextField instead of FormTextareaField")
|
||||
return null
|
||||
}
|
||||
return <Textarea
|
||||
resize={"vertical"}
|
||||
{...unpackField(field)}
|
||||
{...props}/>
|
||||
}
|
||||
export const EmailField = ({field, ...props}: { field: EmailFieldSchema } & TextInputProps) => {
|
||||
return <TextInput
|
||||
leftSection={<IconAt/>}
|
||||
{...unpackField(field)}
|
||||
type={"email"}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
export const NumberField = ({field, ...props}: { field: NumberFieldSchema } & NumberInputProps) => {
|
||||
return <NumberInput
|
||||
{...unpackField(field)}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
export const DateField = ({field, ...props}: { field: DateFieldSchema } & DateTimePickerProps) => {
|
||||
return <DateTimePicker
|
||||
clearable
|
||||
{...unpackField(field)}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
|
||||
export const DateRangeField = ({field, ...props}: { field: DateRangeFieldSchema } & DatePickerInputProps<"range">) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
return <DatePickerInput
|
||||
type="range"
|
||||
clearable
|
||||
{...unpackField(field)}
|
||||
{...props}
|
||||
/>
|
||||
}
|
||||
|
||||
export const SelectField = ({field, ...props}: { field: SelectFieldSchema } & SelectProps & MultiSelectProps) => {
|
||||
|
||||
const Component = field.multiple ? MultiSelect : Select
|
||||
|
||||
return <Component
|
||||
clearable
|
||||
{...unpackField(field)}
|
||||
{...props}
|
||||
data={field.options}
|
||||
/>
|
||||
}
|
||||
export const CheckboxField = ({field, ...props}: { field: CheckboxFieldSchema } & CheckboxCardProps) => {
|
||||
return <CheckboxCard
|
||||
{...unpackField(field)}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
/*
|
||||
return <Checkbox
|
||||
{...unpackField(field)}
|
||||
{...props}
|
||||
/>
|
||||
*/
|
||||
}
|
|
@ -9,6 +9,11 @@
|
|||
padding: var(--mantine-spacing-md);
|
||||
/*noinspection CssInvalidFunction*/
|
||||
background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-8));
|
||||
|
||||
|
||||
&[aria-error="true"] {
|
||||
border-color: var(--mantine-color-error);
|
||||
}
|
||||
}
|
||||
|
||||
.textContainer {
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import {Checkbox, CheckboxProps, Input, InputWrapperProps} from '@mantine/core';
|
||||
import classes from './index.module.css';
|
||||
import InnerHtml from "@/components/InnerHtml";
|
||||
|
||||
export type CheckboxCardProps = InputWrapperProps & CheckboxProps
|
||||
|
||||
export function CheckboxCard(props: CheckboxCardProps) {
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<div className={classes.container} aria-error={!!props.error}>
|
||||
<Checkbox
|
||||
checked={props.checked}
|
||||
onChange={props.onChange}
|
||||
|
@ -19,7 +20,8 @@ export function CheckboxCard(props: CheckboxCardProps) {
|
|||
|
||||
<div className={classes.textContainer}>
|
||||
<Input.Label required={props.required}>{props.label}</Input.Label>
|
||||
{props.description && <Input.Description>{props.description}</Input.Description>}
|
||||
{props.description &&
|
||||
<Input.Description><InnerHtml html={props.description.toString() ?? ""}/></Input.Description>}
|
||||
{props.error && <Input.Error>{props.error}</Input.Error>}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@ import {BubbleMenu, Editor, useEditor} from "@tiptap/react";
|
|||
import {StarterKit} from "@tiptap/starter-kit";
|
||||
import {Underline} from "@tiptap/extension-underline";
|
||||
import Placeholder from '@tiptap/extension-placeholder';
|
||||
import {RichTextEditor, RichTextEditorContent} from "@mantine/tiptap";
|
||||
import {Link, RichTextEditor, RichTextEditorContent} from "@mantine/tiptap";
|
||||
import classes from './index.module.css';
|
||||
import {Box, Input, InputWrapperProps, Loader} from "@mantine/core";
|
||||
import {useEffect} from "react";
|
||||
|
@ -14,7 +14,6 @@ const Bubble = ({editor}: { editor: Editor }) => (
|
|||
<RichTextEditor.ControlsGroup>
|
||||
<RichTextEditor.Bold/>
|
||||
<RichTextEditor.Italic/>
|
||||
<RichTextEditor.Link/>
|
||||
</RichTextEditor.ControlsGroup>
|
||||
</BubbleMenu>
|
||||
)
|
||||
|
@ -45,6 +44,11 @@ const Toolbar = ({fullToolbar}: { fullToolbar: boolean, editor: Editor }) => (
|
|||
<RichTextEditor.BulletList/>
|
||||
<RichTextEditor.OrderedList/>
|
||||
</RichTextEditor.ControlsGroup>
|
||||
|
||||
<RichTextEditor.ControlsGroup>
|
||||
<RichTextEditor.Link/>
|
||||
<RichTextEditor.Unlink/>
|
||||
</RichTextEditor.ControlsGroup>
|
||||
</>
|
||||
:
|
||||
<>
|
||||
|
@ -54,6 +58,10 @@ const Toolbar = ({fullToolbar}: { fullToolbar: boolean, editor: Editor }) => (
|
|||
<RichTextEditor.Underline/>
|
||||
<RichTextEditor.Code/>
|
||||
</RichTextEditor.ControlsGroup>
|
||||
<RichTextEditor.ControlsGroup>
|
||||
<RichTextEditor.Link/>
|
||||
<RichTextEditor.Unlink/>
|
||||
</RichTextEditor.ControlsGroup>
|
||||
</>
|
||||
|
||||
}
|
||||
|
@ -79,6 +87,7 @@ export default function TextEditor({
|
|||
maxHeight,
|
||||
hideToolbar,
|
||||
noBorder,
|
||||
disabled,
|
||||
...props
|
||||
}: {
|
||||
value: string;
|
||||
|
@ -88,14 +97,17 @@ export default function TextEditor({
|
|||
maxHeight?: number | string;
|
||||
hideToolbar?: boolean;
|
||||
noBorder?: boolean;
|
||||
disabled?: boolean;
|
||||
} & Omit<InputWrapperProps, "onChange">) {
|
||||
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Underline,
|
||||
Placeholder.configure({placeholder})
|
||||
Link,
|
||||
Placeholder.configure({placeholder: placeholder})
|
||||
],
|
||||
editable: !disabled,
|
||||
content: value,
|
||||
onUpdate: ({editor}) => {
|
||||
const cleanHtml = sanitizeHtml(editor.getHTML())
|
||||
|
|
|
@ -50,14 +50,12 @@ export default function Footer() {
|
|||
<div className={classes.links}>
|
||||
<h5 className={classes.hideMobile}>Rechtliches</h5>
|
||||
|
||||
<Link to={"/terms-and-conditions"}>AGB der StuVe</Link>
|
||||
<Link to={"/legal/terms-and-conditions"}>AGB der StuVe</Link>
|
||||
|
||||
<Link to={"/privacy-policy"}>Datenschutzerklärung</Link>
|
||||
|
||||
<Link to={"/imprint"}>Impressum</Link>
|
||||
<Link to={"/legal/privacy-policy"}>Datenschutzerklärung</Link>
|
||||
|
||||
<Link to={"/legal/imprint"}>Impressum</Link>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<Divider/>
|
||||
|
|
|
@ -9,6 +9,7 @@ type Setting = {
|
|||
}
|
||||
|
||||
type Settings = {
|
||||
imprint: Setting
|
||||
privacyPolicy: Setting
|
||||
agb: Setting
|
||||
stexGroupId: Setting
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {GuestUserModel, LdapUserModel} from "./AuthTypes.ts";
|
||||
import {RecordModel} from "pocketbase";
|
||||
import {FieldEntries} from "@/components/formUtil/fromInput/types.ts";
|
||||
import {FieldEntries} from "@/components/formUtil/FromInput/types.ts";
|
||||
import {FormSchema} from "@/components/formUtil/formBuilder/types.ts";
|
||||
|
||||
export type EventModel = {
|
||||
|
|
|
@ -16,11 +16,20 @@ export type SettingsModel = {
|
|||
public?: boolean
|
||||
} & RecordModel
|
||||
|
||||
|
||||
export type LegalSettingsModal = {
|
||||
name: string,
|
||||
updated: string,
|
||||
legalText: string
|
||||
} & RecordModel
|
||||
|
||||
export interface TypedPocketBase extends PocketBase {
|
||||
collection(idOrName: string): RecordService<RecordModel>
|
||||
|
||||
collection(idOrName: 'settings'): RecordService<SettingsModel>
|
||||
|
||||
collection(idOrName: 'legalSettings'): RecordService<LegalSettingsModal>
|
||||
|
||||
collection(idOrName: 'users'): RecordService<UserModal>
|
||||
|
||||
collection(idOrName: 'guest_users'): RecordService<GuestUserModel>
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import {LoadingOverlay} from "@mantine/core";
|
||||
import InnerHtml from "@/components/InnerHtml";
|
||||
import {pprintDate} from "@/lib/datetime.ts";
|
||||
import {useParams} from "react-router-dom";
|
||||
import {useQuery} from "@tanstack/react-query";
|
||||
import {usePB} from "@/lib/pocketbase.tsx";
|
||||
import NotFound from "@/pages/not-found/index.page.tsx";
|
||||
|
||||
export default function LegalPage() {
|
||||
const {page} = useParams() as { page: string }
|
||||
|
||||
const {pb} = usePB()
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: ["legalSettings", page],
|
||||
queryFn: async () => await pb.collection("legalSettings").getFirstListItem(`name='${page}'`),
|
||||
enabled: !!page
|
||||
})
|
||||
|
||||
if (query.isPending) return <LoadingOverlay visible={true}/>
|
||||
|
||||
if (query.isError || !query.data) return <NotFound/>
|
||||
|
||||
return <>
|
||||
<div className={"section"}>
|
||||
Zuletzt Bearbeitet: {pprintDate(query.data.updated)}
|
||||
</div>
|
||||
|
||||
<div className={"section"}>
|
||||
<InnerHtml html={query.data.legalText}/>
|
||||
</div>
|
||||
</>
|
||||
}
|
|
@ -34,7 +34,7 @@ export default function EventAGB({event}: { event: EventModel }) {
|
|||
<Button
|
||||
mt={"sm"}
|
||||
variant={"light"}
|
||||
component={Link} to={"/terms-and-conditions"} target={"_blank"}
|
||||
component={Link} to={"/legal/terms-and-conditions"} target={"_blank"}
|
||||
>
|
||||
AGB der StuVe
|
||||
</Button>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import {EventListSlotEntriesWithUserModel} from "@/models/EventTypes.ts";
|
||||
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
||||
import {useMutation} 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 {Modal} from "@mantine/core";
|
||||
import FormInput from "@/components/formUtil/fromInput";
|
||||
import FormInput from "@/components/formUtil/FromInput";
|
||||
|
||||
export const UpdateEventListSlotEntryFormModal = ({opened, close, refetch, entry}: {
|
||||
opened: boolean,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import {EventListSlotEntriesWithUserModel} from "@/models/EventTypes.ts";
|
||||
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
||||
import {useMutation} 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 {Alert, Modal} from "@mantine/core";
|
||||
import FormInput from "@/components/formUtil/fromInput";
|
||||
import InnerHtml from "@/components/InnerHtml";
|
||||
import {RenderDateRange} from "./RenderDateRange.tsx";
|
||||
import FormInput from "@/components/formUtil/FromInput";
|
||||
|
||||
export const UpdateEventListSlotEntryStatusModal = ({opened, close, refetch, entry}: {
|
||||
opened: boolean,
|
||||
|
|
|
@ -7,10 +7,10 @@ import {Alert, Collapse, ThemeIcon, Tooltip, UnstyledButton} from "@mantine/core
|
|||
import {IconChevronDown, IconChevronRight, IconInfoCircle} from "@tabler/icons-react";
|
||||
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
||||
import {useMutation} 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 InnerHtml from "@/components/InnerHtml";
|
||||
import FormInput from "@/components/formUtil/fromInput";
|
||||
import FormInput from "@/components/formUtil/FromInput";
|
||||
import {useUser} from "@/lib/user.ts";
|
||||
|
||||
export default function EventListSlotView({slot, refetch}: {
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
import {useSettings} from "@/lib/settings.ts";
|
||||
import {LoadingOverlay} from "@mantine/core";
|
||||
import InnerHtml from "@/components/InnerHtml";
|
||||
import {pprintDate} from "@/lib/datetime.ts";
|
||||
|
||||
export default function PrivacyPolicy() {
|
||||
const settings = useSettings()
|
||||
|
||||
if (!settings) return <LoadingOverlay visible={true}/>
|
||||
|
||||
return <>
|
||||
<div className={"section"}>
|
||||
Zuletzt Bearbeitet: {pprintDate(settings.privacyPolicy.updated)}
|
||||
</div>
|
||||
|
||||
<div className={"section"}>
|
||||
<InnerHtml html={settings.privacyPolicy.value}/>
|
||||
</div>
|
||||
</>
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import {useSettings} from "@/lib/settings.ts";
|
||||
import {LoadingOverlay} from "@mantine/core";
|
||||
import InnerHtml from "@/components/InnerHtml";
|
||||
import {pprintDate} from "@/lib/datetime.ts";
|
||||
|
||||
export default function TermsAndConditions() {
|
||||
const settings = useSettings()
|
||||
|
||||
if (!settings) return <LoadingOverlay visible={true}/>
|
||||
|
||||
return <>
|
||||
<div className={"section"}>
|
||||
Zuletzt Bearbeitet: {pprintDate(settings.agb.updated)}
|
||||
</div>
|
||||
|
||||
<div className={"section"}>
|
||||
<InnerHtml html={settings.agb.value}/>
|
||||
</div>
|
||||
</>
|
||||
}
|
Loading…
Reference in New Issue