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 Layout from "@/components/layout";
|
||||||
import QRCodeGenerator from "./pages/util/qr/index.page.tsx";
|
import QRCodeGenerator from "./pages/util/qr/index.page.tsx";
|
||||||
import EventsRouter from "./pages/events/EventsRouter.tsx";
|
import EventsRouter from "./pages/events/EventsRouter.tsx";
|
||||||
import PrivacyPolicy from "./pages/privacy-policy.page.tsx";
|
import LegalPage from "@/pages/LegalPage.tsx";
|
||||||
import TermsAndConditions from "./pages/terms-and-conditions.page.tsx";
|
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
|
@ -17,16 +16,8 @@ const router = createBrowserRouter([
|
||||||
element: <HomePage/>
|
element: <HomePage/>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "privacy-policy",
|
path: "legal/:page",
|
||||||
element: <PrivacyPolicy/>
|
element: <LegalPage/>
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "imprint",
|
|
||||||
element: <PrivacyPolicy/>
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "terms-and-conditions",
|
|
||||||
element: <TermsAndConditions/>
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "events/*",
|
path: "events/*",
|
||||||
|
|
|
@ -36,7 +36,7 @@ export default function LoginModal() {
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
authMethod: "ldap" as "ldap" | "guest",
|
authMethod: "ldap" as "ldap" | "guest",
|
||||||
privacy: false
|
terms: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -94,23 +94,23 @@ export default function LoginModal() {
|
||||||
required
|
required
|
||||||
label={
|
label={
|
||||||
<Text>
|
<Text>
|
||||||
Ich akzeptiere die <Anchor
|
Ich habe die <Anchor
|
||||||
component={Link}
|
component={Link}
|
||||||
target={"_blank"}
|
target={"_blank"}
|
||||||
to={"/privacy-policy"}
|
to={"/legal/terms-and-conditions"}
|
||||||
>
|
>
|
||||||
Datenschutzerklärung
|
AGB der StuVe
|
||||||
</Anchor>.
|
</Anchor> gelesen und nehme sie zur Kenntnis
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
{...formValues.getInputProps("privacy", {type: "checkbox"})}
|
{...formValues.getInputProps("terms", {type: "checkbox"})}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PocketBaseErrorAlert error={loginMutation.error}/>
|
<PocketBaseErrorAlert error={loginMutation.error}/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
loading={loginMutation.isPending}
|
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"}
|
type={"submit"}
|
||||||
>
|
>
|
||||||
Einloggen
|
Einloggen
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import {useRegister} from "@/components/auth/modals/hooks.ts";
|
import {useRegister} from "@/components/auth/modals/hooks.ts";
|
||||||
import {isEmail, useForm} from "@mantine/form";
|
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 {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
||||||
import {IconAt, IconKey, IconUser, IconUserPlus, IconX} from "@tabler/icons-react";
|
import {IconAt, IconKey, IconUser, IconUserPlus, IconX} from "@tabler/icons-react";
|
||||||
import PasswordStrengthMeter, {getPasswordStrength} from "@/components/input/PasswordStrengthMeter.tsx";
|
import PasswordStrengthMeter, {getPasswordStrength} from "@/components/input/PasswordStrengthMeter.tsx";
|
||||||
import {useMutation} from "@tanstack/react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import {showSuccessNotification} from "@/components/util.tsx";
|
import {showSuccessNotification} from "@/components/util.tsx";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
|
||||||
export default function RegisterModal() {
|
export default function RegisterModal() {
|
||||||
const {value, handler} = useRegister()
|
const {value, handler} = useRegister()
|
||||||
|
@ -20,12 +21,16 @@ export default function RegisterModal() {
|
||||||
passwordConfirm: "",
|
passwordConfirm: "",
|
||||||
sn: "",
|
sn: "",
|
||||||
givenName: "",
|
givenName: "",
|
||||||
|
terms: false,
|
||||||
|
privacy: false
|
||||||
},
|
},
|
||||||
validate: {
|
validate: {
|
||||||
email: isEmail("Ungültige E-Mail Adresse"),
|
email: isEmail("Ungültige E-Mail Adresse"),
|
||||||
username: (val) => val.length < 3 ? "Der Anmeldename muss mindestens 3 Zeichen lang sein" : null,
|
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,
|
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>
|
||||||
</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>
|
<Group>
|
||||||
<Button
|
<Button
|
||||||
leftSection={<IconX/>}
|
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 {FormSchema} from "../formBuilder/types.ts";
|
||||||
import {Button, Group,} from "@mantine/core";
|
import {Button, Group,} from "@mantine/core";
|
||||||
import {useForm} from "@mantine/form";
|
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 {
|
import {
|
||||||
CheckboxField,
|
CheckboxField,
|
||||||
DateField,
|
DateField, DateRangeField,
|
||||||
DateRangeField,
|
|
||||||
EmailField,
|
EmailField,
|
||||||
FormTextareaField,
|
FormTextareaField,
|
||||||
FormTextField,
|
FormTextField,
|
||||||
NumberField,
|
NumberField, SelectField
|
||||||
SelectField
|
} from "@/components/formUtil/FromInput/formFieldComponents.tsx";
|
||||||
} from "./formFieldComponents.tsx";
|
|
||||||
import {createValidationFromSchema} from "./validation.ts";
|
|
||||||
import ShowDebug from "../../ShowDebug.tsx";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function creates default values based on a data type (e.g. "" for text, false for checkbox)
|
* This function creates default values based on a data type (e.g. "" for text, false for checkbox)
|
|
@ -6,8 +6,8 @@ import {
|
||||||
NumberFieldSchema,
|
NumberFieldSchema,
|
||||||
TextFieldSchema
|
TextFieldSchema
|
||||||
} from "../formBuilder/types.ts"
|
} from "../formBuilder/types.ts"
|
||||||
import {FieldEntry} from "./types.ts";
|
|
||||||
import {pprintDateTime} from "@/lib/datetime.ts";
|
import {pprintDateTime} from "@/lib/datetime.ts";
|
||||||
|
import {FieldEntry} from "@/components/formUtil/FromInput/types.ts";
|
||||||
|
|
||||||
|
|
||||||
const textValidator = (field: TextFieldSchema, value: string) => {
|
const textValidator = (field: TextFieldSchema, value: string) => {
|
|
@ -9,6 +9,7 @@ import {
|
||||||
Fieldset,
|
Fieldset,
|
||||||
Group,
|
Group,
|
||||||
NumberInput,
|
NumberInput,
|
||||||
|
Spoiler,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
|
@ -33,6 +34,8 @@ import {useDisclosure} from "@mantine/hooks";
|
||||||
import classes from "./editField.module.css"
|
import classes from "./editField.module.css"
|
||||||
import {Draggable} from "@hello-pangea/dnd";
|
import {Draggable} from "@hello-pangea/dnd";
|
||||||
import {humanReadableField} from "../util.ts";
|
import {humanReadableField} from "../util.ts";
|
||||||
|
import TextEditor from "@/components/input/Editor";
|
||||||
|
import InnerHtml from "@/components/InnerHtml";
|
||||||
|
|
||||||
type EditFieldProps = {
|
type EditFieldProps = {
|
||||||
index: number;
|
index: number;
|
||||||
|
@ -132,8 +135,8 @@ const SelectFieldSettings = ({field, formValues, index}: Omit<EditFieldProps, "f
|
||||||
placeholder={"Option"}
|
placeholder={"Option"}
|
||||||
|
|
||||||
rightSection={
|
rightSection={
|
||||||
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
|
disabled={field.meta?.required}
|
||||||
color={"red"}
|
color={"red"}
|
||||||
variant={"transparent"}
|
variant={"transparent"}
|
||||||
aria-label={"delete option"}
|
aria-label={"delete option"}
|
||||||
|
@ -153,6 +156,7 @@ const SelectFieldSettings = ({field, formValues, index}: Omit<EditFieldProps, "f
|
||||||
|
|
||||||
<Group>
|
<Group>
|
||||||
<Button
|
<Button
|
||||||
|
disabled={field.meta?.required}
|
||||||
variant={"light"} size="xs"
|
variant={"light"} size="xs"
|
||||||
leftSection={<IconPlus/>}
|
leftSection={<IconPlus/>}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -297,12 +301,24 @@ export default function EditField({field, formValues, index}: EditFieldProps) {
|
||||||
</Badge>
|
</Badge>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Group>
|
{
|
||||||
<TextInput
|
field.meta?.required ? <>
|
||||||
label={"Beschreibung"}
|
<Fieldset legend={"Beschreibung"}>
|
||||||
{...formValues.getInputProps(`fields.${index}.description`)}
|
<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
|
<TextInput
|
||||||
label={"Platzhalter"}
|
label={"Platzhalter"}
|
||||||
{...formValues.getInputProps(`fields.${index}.placeholder`)}
|
{...formValues.getInputProps(`fields.${index}.placeholder`)}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import EditField from "./editField.tsx";
|
||||||
import {IconDatabaseOff, IconEye, IconSquarePlus2} from "@tabler/icons-react";
|
import {IconDatabaseOff, IconEye, IconSquarePlus2} from "@tabler/icons-react";
|
||||||
import FormTypeIcon from "../FormTypeIcon.tsx";
|
import FormTypeIcon from "../FormTypeIcon.tsx";
|
||||||
import {useDisclosure} from "@mantine/hooks";
|
import {useDisclosure} from "@mantine/hooks";
|
||||||
import FormInput from "../fromInput";
|
import FormInput from "../FromInput";
|
||||||
import {DragDropContext, Droppable} from "@hello-pangea/dnd";
|
import {DragDropContext, Droppable} from "@hello-pangea/dnd";
|
||||||
import {useEffect} from "react";
|
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 {ReactNode} from "react";
|
||||||
import {Anchor, Badge, Code, Group, List, Spoiler, ThemeIcon, Tooltip} from "@mantine/core";
|
import {Anchor, Badge, Code, Group, List, Spoiler, ThemeIcon, Tooltip} from "@mantine/core";
|
||||||
import {
|
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 {objectMap} from "@/lib/util.ts";
|
||||||
import {FormSchemaField} from "../formBuilder/types.ts";
|
import {FormSchemaField} from "../formBuilder/types.ts";
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {FieldDataType} from "../formBuilder/types.ts";
|
import {FieldDataType} from "../formBuilder/types.ts";
|
||||||
import {FieldEntryValue} from "../fromInput/types.ts";
|
import {FieldEntryValue} from "@/components/formUtil/FromInput/types.ts";
|
||||||
import dayjs from "dayjs";
|
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);
|
padding: var(--mantine-spacing-md);
|
||||||
/*noinspection CssInvalidFunction*/
|
/*noinspection CssInvalidFunction*/
|
||||||
background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-8));
|
background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-8));
|
||||||
|
|
||||||
|
|
||||||
|
&[aria-error="true"] {
|
||||||
|
border-color: var(--mantine-color-error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.textContainer {
|
.textContainer {
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import {Checkbox, CheckboxProps, Input, InputWrapperProps} from '@mantine/core';
|
import {Checkbox, CheckboxProps, Input, InputWrapperProps} from '@mantine/core';
|
||||||
import classes from './index.module.css';
|
import classes from './index.module.css';
|
||||||
|
import InnerHtml from "@/components/InnerHtml";
|
||||||
|
|
||||||
export type CheckboxCardProps = InputWrapperProps & CheckboxProps
|
export type CheckboxCardProps = InputWrapperProps & CheckboxProps
|
||||||
|
|
||||||
export function CheckboxCard(props: CheckboxCardProps) {
|
export function CheckboxCard(props: CheckboxCardProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.container}>
|
<div className={classes.container} aria-error={!!props.error}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={props.checked}
|
checked={props.checked}
|
||||||
onChange={props.onChange}
|
onChange={props.onChange}
|
||||||
|
@ -19,7 +20,8 @@ export function CheckboxCard(props: CheckboxCardProps) {
|
||||||
|
|
||||||
<div className={classes.textContainer}>
|
<div className={classes.textContainer}>
|
||||||
<Input.Label required={props.required}>{props.label}</Input.Label>
|
<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>}
|
{props.error && <Input.Error>{props.error}</Input.Error>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {BubbleMenu, Editor, useEditor} from "@tiptap/react";
|
||||||
import {StarterKit} from "@tiptap/starter-kit";
|
import {StarterKit} from "@tiptap/starter-kit";
|
||||||
import {Underline} from "@tiptap/extension-underline";
|
import {Underline} from "@tiptap/extension-underline";
|
||||||
import Placeholder from '@tiptap/extension-placeholder';
|
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 classes from './index.module.css';
|
||||||
import {Box, Input, InputWrapperProps, Loader} from "@mantine/core";
|
import {Box, Input, InputWrapperProps, Loader} from "@mantine/core";
|
||||||
import {useEffect} from "react";
|
import {useEffect} from "react";
|
||||||
|
@ -14,7 +14,6 @@ const Bubble = ({editor}: { editor: Editor }) => (
|
||||||
<RichTextEditor.ControlsGroup>
|
<RichTextEditor.ControlsGroup>
|
||||||
<RichTextEditor.Bold/>
|
<RichTextEditor.Bold/>
|
||||||
<RichTextEditor.Italic/>
|
<RichTextEditor.Italic/>
|
||||||
<RichTextEditor.Link/>
|
|
||||||
</RichTextEditor.ControlsGroup>
|
</RichTextEditor.ControlsGroup>
|
||||||
</BubbleMenu>
|
</BubbleMenu>
|
||||||
)
|
)
|
||||||
|
@ -45,6 +44,11 @@ const Toolbar = ({fullToolbar}: { fullToolbar: boolean, editor: Editor }) => (
|
||||||
<RichTextEditor.BulletList/>
|
<RichTextEditor.BulletList/>
|
||||||
<RichTextEditor.OrderedList/>
|
<RichTextEditor.OrderedList/>
|
||||||
</RichTextEditor.ControlsGroup>
|
</RichTextEditor.ControlsGroup>
|
||||||
|
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.Link/>
|
||||||
|
<RichTextEditor.Unlink/>
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
</>
|
</>
|
||||||
:
|
:
|
||||||
<>
|
<>
|
||||||
|
@ -54,6 +58,10 @@ const Toolbar = ({fullToolbar}: { fullToolbar: boolean, editor: Editor }) => (
|
||||||
<RichTextEditor.Underline/>
|
<RichTextEditor.Underline/>
|
||||||
<RichTextEditor.Code/>
|
<RichTextEditor.Code/>
|
||||||
</RichTextEditor.ControlsGroup>
|
</RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.ControlsGroup>
|
||||||
|
<RichTextEditor.Link/>
|
||||||
|
<RichTextEditor.Unlink/>
|
||||||
|
</RichTextEditor.ControlsGroup>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -79,6 +87,7 @@ export default function TextEditor({
|
||||||
maxHeight,
|
maxHeight,
|
||||||
hideToolbar,
|
hideToolbar,
|
||||||
noBorder,
|
noBorder,
|
||||||
|
disabled,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
value: string;
|
value: string;
|
||||||
|
@ -88,14 +97,17 @@ export default function TextEditor({
|
||||||
maxHeight?: number | string;
|
maxHeight?: number | string;
|
||||||
hideToolbar?: boolean;
|
hideToolbar?: boolean;
|
||||||
noBorder?: boolean;
|
noBorder?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
} & Omit<InputWrapperProps, "onChange">) {
|
} & Omit<InputWrapperProps, "onChange">) {
|
||||||
|
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
extensions: [
|
extensions: [
|
||||||
StarterKit,
|
StarterKit,
|
||||||
Underline,
|
Underline,
|
||||||
Placeholder.configure({placeholder})
|
Link,
|
||||||
|
Placeholder.configure({placeholder: placeholder})
|
||||||
],
|
],
|
||||||
|
editable: !disabled,
|
||||||
content: value,
|
content: value,
|
||||||
onUpdate: ({editor}) => {
|
onUpdate: ({editor}) => {
|
||||||
const cleanHtml = sanitizeHtml(editor.getHTML())
|
const cleanHtml = sanitizeHtml(editor.getHTML())
|
||||||
|
|
|
@ -50,14 +50,12 @@ export default function Footer() {
|
||||||
<div className={classes.links}>
|
<div className={classes.links}>
|
||||||
<h5 className={classes.hideMobile}>Rechtliches</h5>
|
<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={"/legal/privacy-policy"}>Datenschutzerklärung</Link>
|
||||||
|
|
||||||
<Link to={"/imprint"}>Impressum</Link>
|
|
||||||
|
|
||||||
|
<Link to={"/legal/imprint"}>Impressum</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Divider/>
|
<Divider/>
|
||||||
|
|
|
@ -9,6 +9,7 @@ type Setting = {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Settings = {
|
type Settings = {
|
||||||
|
imprint: Setting
|
||||||
privacyPolicy: Setting
|
privacyPolicy: Setting
|
||||||
agb: Setting
|
agb: Setting
|
||||||
stexGroupId: Setting
|
stexGroupId: Setting
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {GuestUserModel, LdapUserModel} from "./AuthTypes.ts";
|
import {GuestUserModel, LdapUserModel} from "./AuthTypes.ts";
|
||||||
import {RecordModel} from "pocketbase";
|
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";
|
import {FormSchema} from "@/components/formUtil/formBuilder/types.ts";
|
||||||
|
|
||||||
export type EventModel = {
|
export type EventModel = {
|
||||||
|
|
|
@ -16,11 +16,20 @@ export type SettingsModel = {
|
||||||
public?: boolean
|
public?: boolean
|
||||||
} & RecordModel
|
} & RecordModel
|
||||||
|
|
||||||
|
|
||||||
|
export type LegalSettingsModal = {
|
||||||
|
name: string,
|
||||||
|
updated: string,
|
||||||
|
legalText: string
|
||||||
|
} & RecordModel
|
||||||
|
|
||||||
export interface TypedPocketBase extends PocketBase {
|
export interface TypedPocketBase extends PocketBase {
|
||||||
collection(idOrName: string): RecordService<RecordModel>
|
collection(idOrName: string): RecordService<RecordModel>
|
||||||
|
|
||||||
collection(idOrName: 'settings'): RecordService<SettingsModel>
|
collection(idOrName: 'settings'): RecordService<SettingsModel>
|
||||||
|
|
||||||
|
collection(idOrName: 'legalSettings'): RecordService<LegalSettingsModal>
|
||||||
|
|
||||||
collection(idOrName: 'users'): RecordService<UserModal>
|
collection(idOrName: 'users'): RecordService<UserModal>
|
||||||
|
|
||||||
collection(idOrName: 'guest_users'): RecordService<GuestUserModel>
|
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
|
<Button
|
||||||
mt={"sm"}
|
mt={"sm"}
|
||||||
variant={"light"}
|
variant={"light"}
|
||||||
component={Link} to={"/terms-and-conditions"} target={"_blank"}
|
component={Link} to={"/legal/terms-and-conditions"} target={"_blank"}
|
||||||
>
|
>
|
||||||
AGB der StuVe
|
AGB der StuVe
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import {EventListSlotEntriesWithUserModel} from "@/models/EventTypes.ts";
|
import {EventListSlotEntriesWithUserModel} from "@/models/EventTypes.ts";
|
||||||
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
||||||
import {useMutation} from "@tanstack/react-query";
|
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 {showSuccessNotification} from "@/components/util.tsx";
|
||||||
import {Modal} from "@mantine/core";
|
import {Modal} from "@mantine/core";
|
||||||
import FormInput from "@/components/formUtil/fromInput";
|
import FormInput from "@/components/formUtil/FromInput";
|
||||||
|
|
||||||
export const UpdateEventListSlotEntryFormModal = ({opened, close, refetch, entry}: {
|
export const UpdateEventListSlotEntryFormModal = ({opened, close, refetch, entry}: {
|
||||||
opened: boolean,
|
opened: boolean,
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import {EventListSlotEntriesWithUserModel} from "@/models/EventTypes.ts";
|
import {EventListSlotEntriesWithUserModel} from "@/models/EventTypes.ts";
|
||||||
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
||||||
import {useMutation} from "@tanstack/react-query";
|
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 {showSuccessNotification} from "@/components/util.tsx";
|
||||||
import {Alert, Modal} from "@mantine/core";
|
import {Alert, Modal} from "@mantine/core";
|
||||||
import FormInput from "@/components/formUtil/fromInput";
|
|
||||||
import InnerHtml from "@/components/InnerHtml";
|
import InnerHtml from "@/components/InnerHtml";
|
||||||
import {RenderDateRange} from "./RenderDateRange.tsx";
|
import {RenderDateRange} from "./RenderDateRange.tsx";
|
||||||
|
import FormInput from "@/components/formUtil/FromInput";
|
||||||
|
|
||||||
export const UpdateEventListSlotEntryStatusModal = ({opened, close, refetch, entry}: {
|
export const UpdateEventListSlotEntryStatusModal = ({opened, close, refetch, entry}: {
|
||||||
opened: boolean,
|
opened: boolean,
|
||||||
|
|
|
@ -7,10 +7,10 @@ import {Alert, Collapse, ThemeIcon, Tooltip, UnstyledButton} from "@mantine/core
|
||||||
import {IconChevronDown, IconChevronRight, IconInfoCircle} from "@tabler/icons-react";
|
import {IconChevronDown, IconChevronRight, 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} 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 {useUser} from "@/lib/user.ts";
|
import {useUser} from "@/lib/user.ts";
|
||||||
|
|
||||||
export default function EventListSlotView({slot, refetch}: {
|
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