From a8969e3f3148e0e976cbd5e5e2f6d57e41cc1f38 Mon Sep 17 00:00:00 2001 From: Plebysnek <115377141+Plebysnacc@users.noreply.github.com> Date: Fri, 11 Apr 2025 15:46:19 +0200 Subject: [PATCH 01/19] Implement basic form Missing: - Submit - QoL --- .../app/(form-tutor)/form-tutor/columns.tsx | 119 +++++++++---- .../(form-tutor)/form-tutor/data-table.tsx | 162 +++++++++++------ .../app/(form-tutor)/form-tutor/layout.tsx | 21 --- frontend/app/(form-tutor)/form-tutor/page.tsx | 166 +++++++++++++++++- frontend/lib/eventBroker.ts | 18 -- frontend/package.json | 12 +- 6 files changed, 358 insertions(+), 140 deletions(-) delete mode 100644 frontend/app/(form-tutor)/form-tutor/layout.tsx delete mode 100644 frontend/lib/eventBroker.ts diff --git a/frontend/app/(form-tutor)/form-tutor/columns.tsx b/frontend/app/(form-tutor)/form-tutor/columns.tsx index 9b6b24b..bac5dab 100644 --- a/frontend/app/(form-tutor)/form-tutor/columns.tsx +++ b/frontend/app/(form-tutor)/form-tutor/columns.tsx @@ -1,70 +1,113 @@ -"use client"; - -import { ColumnDef } from "@tanstack/react-table"; -import { Checkbox } from "@/components/ui/checkbox"; -import { eventBroker } from "@/lib/eventBroker"; +import { DataTableColumnHeader } from "@/components/data-table-column-header"; import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; import { Event } from "@/lib/gql/generated/graphql"; +import { formatDateToDDMM, formatDateToHHMM } from "@/lib/utils"; +import { ColumnDef } from "@tanstack/react-table"; +import { MoreHorizontal } from "lucide-react"; export const columns: ColumnDef[] = [ { - accessorKey: "isSelected", - header: "", + id: "select", + header: ({ table }) => ( + table.toggleAllPageRowsSelected(!!value)} + aria-label="Alle auswählen" + /> + ), cell: ({ row }) => ( { - row.toggleSelected(!!value); - - if (row.getIsSelected()) { - eventBroker.removeEvent(row.original.ID); - } else { - eventBroker.addEvent(row.original.ID); - } + row.toggleSelected(!!value) }} - aria-label="Ich kann diese Veranstaltung halten" + aria-label="Reihe auswählen" /> ), }, { accessorKey: "title", - header: () =>
Veranstaltung
, - cell: ({ row }) => ( -
-
{row.original.title}
- {row.original.type.name} -
+ header: ({ column }) => ( + ), + cell: ({ row }) => row.original.title, }, { - accessorKey: "from", - header: () =>
Datum
, + accessorKey: "date", + header: ({ column }) => ( + + ), cell: ({ row }) => { - const date = new Date(row.getValue("from")); - return date.toLocaleDateString(); + return formatDateToDDMM(new Date(row.original.from)); }, }, { accessorKey: "from", - header: () =>
Von
, + header: ({ column }) => ( + + ), cell: ({ row }) => { - const time = new Date(row.getValue("from")); - return time.toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - }); + return formatDateToHHMM(new Date(row.original.from)); }, }, { accessorKey: "to", - header: () =>
Bis
, + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return formatDateToHHMM(new Date(row.original.to)); + }, + }, + { + accessorKey: "type", + header: "Art", + cell: ({ row }) => ( + + {row.original.type.name} + + ), + }, + { + accessorKey: "topic", + header: "Thema", + cell: ({ row }) => ( + + {row.original.topic.name} + + ), + }, + { + id: "actions", + enableHiding: false, cell: ({ row }) => { - const time = new Date(row.getValue("to")); - return time.toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - }); + return ( + + + + + + Optionen + Bearbeiten + Löschen + + + ); }, }, ]; diff --git a/frontend/app/(form-tutor)/form-tutor/data-table.tsx b/frontend/app/(form-tutor)/form-tutor/data-table.tsx index 11eed61..9a10175 100644 --- a/frontend/app/(form-tutor)/form-tutor/data-table.tsx +++ b/frontend/app/(form-tutor)/form-tutor/data-table.tsx @@ -1,11 +1,16 @@ -"use client" +"use client"; import { ColumnDef, + ColumnFiltersState, + SortingState, + VisibilityState, flexRender, getCoreRowModel, - useReactTable, -} from "@tanstack/react-table" + getFilteredRowModel, + getSortedRowModel, + useReactTable, RowSelectionState, +} from "@tanstack/react-table"; import { Table, @@ -14,65 +19,118 @@ import { TableHead, TableHeader, TableRow, -} from "@/components/ui/table" +} from "@/components/ui/table"; +import React, {useEffect} from "react"; +import { Input } from "@/components/ui/input"; +import {DataTablePagination} from "@/components/data-table-pagination"; +import {DataTableViewOptions} from "@/components/data-table-view-options"; -interface DataTableProps { - columns: ColumnDef[] - data: TData[] +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; + onChangeSelectedAction: React.Dispatch>; } -export function DataTable({ - columns, - data, -}: DataTableProps) { +export function DataTable({ + columns, data, onChangeSelectedAction, + }: DataTableProps) { + + const [sorting, setSorting] = React.useState([]); + const [columnFilters, setColumnFilters] = React.useState([]); + const [columnVisibility, setColumnVisibility] = React.useState({}); + const [rowSelection, setRowSelection] = React.useState({}); + const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), - }) + onSortingChange: setSorting, + getSortedRowModel: getSortedRowModel(), + onColumnFiltersChange: setColumnFilters, + getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, + onRowSelectionChange: (newRowSelection) => { + setRowSelection(newRowSelection); + }, + state: { + sorting, + columnFilters, + columnVisibility, + rowSelection, + }, + }); + + useEffect(() => { + const selectedIDs: number[] = table.getSelectedRowModel().rows + // @ts-ignore + .map((row) => row.original.ID) + onChangeSelectedAction(selectedIDs) + }, [table, rowSelection, onChangeSelectedAction]); return ( -
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() +
+
+ + table.getColumn("title")?.setFilterValue(event.target.value) + } + className="max-w-sm" + /> + +
+
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() )} - - ))} - - ))} - - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} + + ))} + + )) + ) : ( + + + Keine Ergebnisse. + - )) - ) : ( - - - No results. - - - )} - -
+ )} + + +
+ - ) + ); } diff --git a/frontend/app/(form-tutor)/form-tutor/layout.tsx b/frontend/app/(form-tutor)/form-tutor/layout.tsx deleted file mode 100644 index 828a327..0000000 --- a/frontend/app/(form-tutor)/form-tutor/layout.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import type { Metadata } from "next"; -import { Inter } from "next/font/google"; - -const inter = Inter({ subsets: ["latin"] }); - -export const metadata: Metadata = { - title: "Tutor:innen Anmeldung", - description: "Anmeldung für Tutor:innen des Vorkurs", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - {children} - - ); -} diff --git a/frontend/app/(form-tutor)/form-tutor/page.tsx b/frontend/app/(form-tutor)/form-tutor/page.tsx index 4870c0b..64383b3 100644 --- a/frontend/app/(form-tutor)/form-tutor/page.tsx +++ b/frontend/app/(form-tutor)/form-tutor/page.tsx @@ -1,9 +1,165 @@ "use client" -export default function FormTutor() { +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import { z } from "zod" + +import { Button } from "@/components/ui/button" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { Input } from "@/components/ui/input" +import { columns } from "@/app/(form-tutor)/form-tutor/columns" +import {DataTable} from "@/app/(form-tutor)/form-tutor/data-table"; +import {useEffect, useState} from "react"; +import {getClient} from "@/lib/graphql"; +import { + Event, + TableEventsDocument, + TableEventsQuery, + TableEventsQueryVariables +} from "@/lib/gql/generated/graphql"; + +const tutorRegistrationFormSchema = z.object({ + firstname: z.string().min(2, { + message: "First name must be at least 2 characters long.", + }), + lastname: z.string().min(2, { + message: "Last name must be at least 2 characters long.", + }), + email: z.string().email("Invalid email address").min(1, { + message: "Please enter an email", + }), + selectedEvents: z.array(z.string()).min(1, { + message: "Please check at least one event", + }) +}) + +export function TutorRegistrationForm() { + const form = useForm>({ + resolver: zodResolver(tutorRegistrationFormSchema), + defaultValues: { + firstname: "", + lastname: "", + email: "", + selectedEvents: [], + }, + }) + const [events, setEvents] = useState([]) + const [loading , setLoading] = useState(true) + const [selectedEvents, setSelectedEvents] = useState([]) + + useEffect(() => { + const fetchData = async () => { + setLoading(true); + + const client = getClient(); + + const vars: TableEventsQueryVariables = { + needsTutors: true, + onlyFuture: true, + }; + + const eventData = await client.request( + TableEventsDocument, + vars + ); + + if (eventData.events.length) { + setEvents(eventData.events); + } + + setLoading(false); + }; + + fetchData(); + }, []); + + + function onSubmit(values: z.infer) { + + console.log(values) + } + + + return ( - <> -

Deine Verfügbarkeiten

- - ); +
+

Tutor Registration

+

We are happy to hear that you wanna support us as a tutor in le Vorkurs, fill out the below form, so we can put you into a shift

+
+ + ( + + First Name + + + + + + + )} + /> + ( + + Last Name + + + + + + )} + /> + ( + + email + + + + + + )} + /> + ( + + I can help here: + + {loading?( +

Loading...

+ ):( + + )} +
+ +
+ )} + /> + + + +

{selectedEvents.map((selectedEvent) => ( + selectedEvent + ))} +

+
+ ) } + +export default TutorRegistrationForm \ No newline at end of file diff --git a/frontend/lib/eventBroker.ts b/frontend/lib/eventBroker.ts deleted file mode 100644 index ae12be0..0000000 --- a/frontend/lib/eventBroker.ts +++ /dev/null @@ -1,18 +0,0 @@ -class EventBroker { - selectedEvents: number[] = [] - - addEvent(eventID: number):void { - this.selectedEvents.push(eventID) - } - - removeEvent(eventID: number): void { - const index = this.selectedEvents.indexOf(eventID) - if (index > -1) this.selectedEvents.splice(index, 1) - } - - getEvents(): number[] { - return this.selectedEvents - } -} - -export let eventBroker = new EventBroker() \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 0a09c5b..21ca244 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,7 +10,7 @@ "codegen": "graphql-codegen --config codegen.ts" }, "dependencies": { - "@hookform/resolvers": "^3.9.0", + "@hookform/resolvers": "^3.10.0", "@opentelemetry/api-logs": "^0.54.0", "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/sdk-logs": "^0.54.0", @@ -20,7 +20,7 @@ "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-hover-card": "^1.1.2", "@radix-ui/react-icons": "^1.3.0", - "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-label": "^2.1.3", "@radix-ui/react-navigation-menu": "^1.2.1", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-progress": "^1.1.0", @@ -28,11 +28,11 @@ "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slider": "^1.2.0", - "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-switch": "^1.1.3", "@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.8", - "@tanstack/react-table": "^8.19.2", + "@tanstack/react-table": "^8.21.2", "@vercel/otel": "^1.10.0", "axios": "^1.7.7", "class-variance-authority": "^0.7.1", @@ -46,7 +46,7 @@ "react": "^18", "react-day-picker": "^8.10.1", "react-dom": "^18", - "react-hook-form": "^7.52.2", + "react-hook-form": "^7.55.0", "react-leaflet": "^4.2.1", "react-textarea-autosize": "^8.5.4", "shadcn-ui": "^0.9.2", @@ -56,7 +56,7 @@ "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "undici": "^6.20.1", - "zod": "^3.23.8" + "zod": "^3.24.2" }, "devDependencies": { "@graphql-codegen/cli": "5.0.2", From ca733e5bd20788b6ca880e7f4f08ed07aeac6c4f Mon Sep 17 00:00:00 2001 From: Plebysnek <115377141+Plebysnacc@users.noreply.github.com> Date: Fri, 11 Apr 2025 15:49:31 +0200 Subject: [PATCH 02/19] add TODO for ts-ignore --- frontend/app/(form-tutor)/form-tutor/data-table.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/app/(form-tutor)/form-tutor/data-table.tsx b/frontend/app/(form-tutor)/form-tutor/data-table.tsx index 9a10175..1132d11 100644 --- a/frontend/app/(form-tutor)/form-tutor/data-table.tsx +++ b/frontend/app/(form-tutor)/form-tutor/data-table.tsx @@ -62,6 +62,7 @@ export function DataTable({ useEffect(() => { const selectedIDs: number[] = table.getSelectedRowModel().rows + // TODO: what do we do // @ts-ignore .map((row) => row.original.ID) onChangeSelectedAction(selectedIDs) From 6f83bc1b2c64cabdbe4a16e9a58beda282e85460 Mon Sep 17 00:00:00 2001 From: Plebysnek <115377141+Plebysnacc@users.noreply.github.com> Date: Fri, 11 Apr 2025 16:09:17 +0200 Subject: [PATCH 03/19] fix type issues with data and events --- frontend/app/(form-tutor)/form-tutor/data-table.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/app/(form-tutor)/form-tutor/data-table.tsx b/frontend/app/(form-tutor)/form-tutor/data-table.tsx index 1132d11..89c1133 100644 --- a/frontend/app/(form-tutor)/form-tutor/data-table.tsx +++ b/frontend/app/(form-tutor)/form-tutor/data-table.tsx @@ -24,10 +24,11 @@ import React, {useEffect} from "react"; import { Input } from "@/components/ui/input"; import {DataTablePagination} from "@/components/data-table-pagination"; import {DataTableViewOptions} from "@/components/data-table-view-options"; +import {Event} from "@/lib/gql/generated/graphql" interface DataTableProps { - columns: ColumnDef[]; - data: TData[]; + columns: ColumnDef[]; + data: Event[]; onChangeSelectedAction: React.Dispatch>; } @@ -61,10 +62,10 @@ export function DataTable({ }); useEffect(() => { - const selectedIDs: number[] = table.getSelectedRowModel().rows - // TODO: what do we do - // @ts-ignore - .map((row) => row.original.ID) + const selectedIDs: number[] = table + .getSelectedRowModel() + .rows + .map(row => row.original.ID) onChangeSelectedAction(selectedIDs) }, [table, rowSelection, onChangeSelectedAction]); From 5145599cb9199c6a4d2907f8374e518efc90f9f5 Mon Sep 17 00:00:00 2001 From: Plebysnek <115377141+Plebysnacc@users.noreply.github.com> Date: Fri, 11 Apr 2025 16:46:00 +0200 Subject: [PATCH 04/19] simplify types in table, start submitAction work --- .../{data-table.tsx => event-table.tsx} | 13 ++++----- frontend/app/(form-tutor)/form-tutor/page.tsx | 28 +++++++++---------- 2 files changed, 19 insertions(+), 22 deletions(-) rename frontend/app/(form-tutor)/form-tutor/{data-table.tsx => event-table.tsx} (93%) diff --git a/frontend/app/(form-tutor)/form-tutor/data-table.tsx b/frontend/app/(form-tutor)/form-tutor/event-table.tsx similarity index 93% rename from frontend/app/(form-tutor)/form-tutor/data-table.tsx rename to frontend/app/(form-tutor)/form-tutor/event-table.tsx index 89c1133..294ab55 100644 --- a/frontend/app/(form-tutor)/form-tutor/data-table.tsx +++ b/frontend/app/(form-tutor)/form-tutor/event-table.tsx @@ -26,15 +26,15 @@ import {DataTablePagination} from "@/components/data-table-pagination"; import {DataTableViewOptions} from "@/components/data-table-view-options"; import {Event} from "@/lib/gql/generated/graphql" -interface DataTableProps { - columns: ColumnDef[]; +interface DataTableProps { + columns: ColumnDef[]; data: Event[]; onChangeSelectedAction: React.Dispatch>; } -export function DataTable({ +export function EventTable({ columns, data, onChangeSelectedAction, - }: DataTableProps) { + }: DataTableProps) { const [sorting, setSorting] = React.useState([]); const [columnFilters, setColumnFilters] = React.useState([]); @@ -50,9 +50,7 @@ export function DataTable({ onColumnFiltersChange: setColumnFilters, getFilteredRowModel: getFilteredRowModel(), onColumnVisibilityChange: setColumnVisibility, - onRowSelectionChange: (newRowSelection) => { - setRowSelection(newRowSelection); - }, + onRowSelectionChange: setRowSelection, state: { sorting, columnFilters, @@ -66,6 +64,7 @@ export function DataTable({ .getSelectedRowModel() .rows .map(row => row.original.ID) + onChangeSelectedAction(selectedIDs) }, [table, rowSelection, onChangeSelectedAction]); diff --git a/frontend/app/(form-tutor)/form-tutor/page.tsx b/frontend/app/(form-tutor)/form-tutor/page.tsx index 64383b3..f5045fd 100644 --- a/frontend/app/(form-tutor)/form-tutor/page.tsx +++ b/frontend/app/(form-tutor)/form-tutor/page.tsx @@ -15,7 +15,7 @@ import { } from "@/components/ui/form" import { Input } from "@/components/ui/input" import { columns } from "@/app/(form-tutor)/form-tutor/columns" -import {DataTable} from "@/app/(form-tutor)/form-tutor/data-table"; +import {EventTable} from "@/app/(form-tutor)/form-tutor/event-table"; import {useEffect, useState} from "react"; import {getClient} from "@/lib/graphql"; import { @@ -35,9 +35,6 @@ const tutorRegistrationFormSchema = z.object({ email: z.string().email("Invalid email address").min(1, { message: "Please enter an email", }), - selectedEvents: z.array(z.string()).min(1, { - message: "Please check at least one event", - }) }) export function TutorRegistrationForm() { @@ -47,12 +44,12 @@ export function TutorRegistrationForm() { firstname: "", lastname: "", email: "", - selectedEvents: [], }, }) const [events, setEvents] = useState([]) const [loading , setLoading] = useState(true) - const [selectedEvents, setSelectedEvents] = useState([]) + const [selectedEventIds, setSelectedEventIds] = useState([]) + const [showEventSelectedError, setShowEventSelectedError] = useState(false) useEffect(() => { const fetchData = async () => { @@ -82,8 +79,12 @@ export function TutorRegistrationForm() { function onSubmit(values: z.infer) { + setShowEventSelectedError(selectedEventIds.length == 0) - console.log(values) + if(!showEventSelectedError) { + setSelectedEventIds([]) + form.reset() + } } @@ -135,8 +136,7 @@ export function TutorRegistrationForm() { )} /> ( I can help here: @@ -144,20 +144,18 @@ export function TutorRegistrationForm() { {loading?(

Loading...

):( - + )} - + {(showEventSelectedError) && ( +

Ich bin ein Fehler!

+ )}
)} /> -

{selectedEvents.map((selectedEvent) => ( - selectedEvent - ))} -

) } From d3be1b2b134139a7fec268e29f26ddbaae8f6e80 Mon Sep 17 00:00:00 2001 From: Plebysnek <115377141+Plebysnacc@users.noreply.github.com> Date: Fri, 11 Apr 2025 22:44:58 +0200 Subject: [PATCH 05/19] Implement form reset on submission --- .../(form-tutor)/form-tutor/event-table.tsx | 21 ++++++++++----- frontend/app/(form-tutor)/form-tutor/page.tsx | 26 ++++++++++++++----- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/frontend/app/(form-tutor)/form-tutor/event-table.tsx b/frontend/app/(form-tutor)/form-tutor/event-table.tsx index 294ab55..5c3d36e 100644 --- a/frontend/app/(form-tutor)/form-tutor/event-table.tsx +++ b/frontend/app/(form-tutor)/form-tutor/event-table.tsx @@ -20,7 +20,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; -import React, {useEffect} from "react"; +import React, { useEffect } from "react"; import { Input } from "@/components/ui/input"; import {DataTablePagination} from "@/components/data-table-pagination"; import {DataTableViewOptions} from "@/components/data-table-view-options"; @@ -29,17 +29,23 @@ import {Event} from "@/lib/gql/generated/graphql" interface DataTableProps { columns: ColumnDef[]; data: Event[]; - onChangeSelectedAction: React.Dispatch>; + setSelectedEvents: React.Dispatch>; + rowSelection: RowSelectionState; + setRowSelection: React.Dispatch>; } export function EventTable({ - columns, data, onChangeSelectedAction, - }: DataTableProps) { + columns, + data, + setSelectedEvents, + rowSelection, + setRowSelection, + }: DataTableProps) { const [sorting, setSorting] = React.useState([]); const [columnFilters, setColumnFilters] = React.useState([]); const [columnVisibility, setColumnVisibility] = React.useState({}); - const [rowSelection, setRowSelection] = React.useState({}); + const table = useReactTable({ data, @@ -65,8 +71,9 @@ export function EventTable({ .rows .map(row => row.original.ID) - onChangeSelectedAction(selectedIDs) - }, [table, rowSelection, onChangeSelectedAction]); + setSelectedEvents(selectedIDs) + }, [table, rowSelection, setSelectedEvents]); + return (
diff --git a/frontend/app/(form-tutor)/form-tutor/page.tsx b/frontend/app/(form-tutor)/form-tutor/page.tsx index f5045fd..22aff2a 100644 --- a/frontend/app/(form-tutor)/form-tutor/page.tsx +++ b/frontend/app/(form-tutor)/form-tutor/page.tsx @@ -24,6 +24,7 @@ import { TableEventsQuery, TableEventsQueryVariables } from "@/lib/gql/generated/graphql"; +import {RowSelectionState} from "@tanstack/react-table"; const tutorRegistrationFormSchema = z.object({ firstname: z.string().min(2, { @@ -50,6 +51,8 @@ export function TutorRegistrationForm() { const [loading , setLoading] = useState(true) const [selectedEventIds, setSelectedEventIds] = useState([]) const [showEventSelectedError, setShowEventSelectedError] = useState(false) + const [rowSelection, setRowSelection] = useState({}); + useEffect(() => { const fetchData = async () => { @@ -77,17 +80,20 @@ export function TutorRegistrationForm() { fetchData(); }, []); - - function onSubmit(values: z.infer) { - setShowEventSelectedError(selectedEventIds.length == 0) - + const resetForm = () => { if(!showEventSelectedError) { - setSelectedEventIds([]) + setSelectedEventIds([]); + setRowSelection({}); form.reset() } } + function onSubmit(values: z.infer) { + setShowEventSelectedError(selectedEventIds.length == 0) + resetForm() + } + return (
@@ -144,9 +150,17 @@ export function TutorRegistrationForm() { {loading?(

Loading...

):( - + )} + {/* FIXME: this error does not show correctly*/} {(showEventSelectedError) && (

Ich bin ein Fehler!

)} From c759d55fe4e788e96b29f6aef60f70043555fe18 Mon Sep 17 00:00:00 2001 From: Plebysnek <115377141+Plebysnacc@users.noreply.github.com> Date: Sat, 12 Apr 2025 10:35:52 +0200 Subject: [PATCH 06/19] rework event id selection --- .../(form-tutor)/form-tutor/event-table.tsx | 15 +-------- frontend/app/(form-tutor)/form-tutor/page.tsx | 31 +++++++++---------- 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/frontend/app/(form-tutor)/form-tutor/event-table.tsx b/frontend/app/(form-tutor)/form-tutor/event-table.tsx index 5c3d36e..5a319e8 100644 --- a/frontend/app/(form-tutor)/form-tutor/event-table.tsx +++ b/frontend/app/(form-tutor)/form-tutor/event-table.tsx @@ -20,7 +20,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; -import React, { useEffect } from "react"; +import React from "react"; import { Input } from "@/components/ui/input"; import {DataTablePagination} from "@/components/data-table-pagination"; import {DataTableViewOptions} from "@/components/data-table-view-options"; @@ -29,7 +29,6 @@ import {Event} from "@/lib/gql/generated/graphql" interface DataTableProps { columns: ColumnDef[]; data: Event[]; - setSelectedEvents: React.Dispatch>; rowSelection: RowSelectionState; setRowSelection: React.Dispatch>; } @@ -37,7 +36,6 @@ interface DataTableProps { export function EventTable({ columns, data, - setSelectedEvents, rowSelection, setRowSelection, }: DataTableProps) { @@ -45,8 +43,6 @@ export function EventTable({ const [sorting, setSorting] = React.useState([]); const [columnFilters, setColumnFilters] = React.useState([]); const [columnVisibility, setColumnVisibility] = React.useState({}); - - const table = useReactTable({ data, columns, @@ -65,15 +61,6 @@ export function EventTable({ }, }); - useEffect(() => { - const selectedIDs: number[] = table - .getSelectedRowModel() - .rows - .map(row => row.original.ID) - - setSelectedEvents(selectedIDs) - }, [table, rowSelection, setSelectedEvents]); - return (
diff --git a/frontend/app/(form-tutor)/form-tutor/page.tsx b/frontend/app/(form-tutor)/form-tutor/page.tsx index 22aff2a..8b0676c 100644 --- a/frontend/app/(form-tutor)/form-tutor/page.tsx +++ b/frontend/app/(form-tutor)/form-tutor/page.tsx @@ -34,7 +34,10 @@ const tutorRegistrationFormSchema = z.object({ message: "Last name must be at least 2 characters long.", }), email: z.string().email("Invalid email address").min(1, { - message: "Please enter an email", + message: "Please enter an email.", + }), + selectedEventIds: z.array(z.number()).min(1, { + message: "Please select at least one event.", }), }) @@ -45,15 +48,13 @@ export function TutorRegistrationForm() { firstname: "", lastname: "", email: "", + selectedEventIds: [], }, }) const [events, setEvents] = useState([]) const [loading , setLoading] = useState(true) - const [selectedEventIds, setSelectedEventIds] = useState([]) - const [showEventSelectedError, setShowEventSelectedError] = useState(false) const [rowSelection, setRowSelection] = useState({}); - useEffect(() => { const fetchData = async () => { setLoading(true); @@ -79,18 +80,20 @@ export function TutorRegistrationForm() { fetchData(); }, []); + + // FIXME: the error message does not disappear if we select an event. Maybe the bug is in here + useEffect(() => { + const selectedIds = Object.keys(rowSelection).filter(k => rowSelection[k]).map(Number); + form.setValue("selectedEventIds", selectedIds) + }, [rowSelection, form]) const resetForm = () => { - if(!showEventSelectedError) { - setSelectedEventIds([]); - setRowSelection({}); - form.reset() - } + setRowSelection({}); + form.reset() } function onSubmit(values: z.infer) { - setShowEventSelectedError(selectedEventIds.length == 0) resetForm() } @@ -142,7 +145,7 @@ export function TutorRegistrationForm() { )} /> ( I can help here: @@ -154,16 +157,12 @@ export function TutorRegistrationForm() { key={'table'} columns={columns} data={events} - setSelectedEvents={setSelectedEventIds} rowSelection={rowSelection} setRowSelection={setRowSelection} {...field}/> )} - {/* FIXME: this error does not show correctly*/} - {(showEventSelectedError) && ( -

Ich bin ein Fehler!

- )} +
)} /> From b737ca626302ac0728f2ed557795cc95b24cd95f Mon Sep 17 00:00:00 2001 From: Plebysnek <115377141+Plebysnacc@users.noreply.github.com> Date: Sat, 12 Apr 2025 11:44:00 +0200 Subject: [PATCH 07/19] fix validation message erros --- .../(form-tutor)/form-tutor/event-table.tsx | 2 ++ frontend/app/(form-tutor)/form-tutor/page.tsx | 24 ++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/frontend/app/(form-tutor)/form-tutor/event-table.tsx b/frontend/app/(form-tutor)/form-tutor/event-table.tsx index 5a319e8..bb49706 100644 --- a/frontend/app/(form-tutor)/form-tutor/event-table.tsx +++ b/frontend/app/(form-tutor)/form-tutor/event-table.tsx @@ -46,6 +46,8 @@ export function EventTable({ const table = useReactTable({ data, columns, + // Makes the row IDs use the Event IDs + getRowId: row => String(row.ID), getCoreRowModel: getCoreRowModel(), onSortingChange: setSorting, getSortedRowModel: getSortedRowModel(), diff --git a/frontend/app/(form-tutor)/form-tutor/page.tsx b/frontend/app/(form-tutor)/form-tutor/page.tsx index 8b0676c..c86734c 100644 --- a/frontend/app/(form-tutor)/form-tutor/page.tsx +++ b/frontend/app/(form-tutor)/form-tutor/page.tsx @@ -54,6 +54,7 @@ export function TutorRegistrationForm() { const [events, setEvents] = useState([]) const [loading , setLoading] = useState(true) const [rowSelection, setRowSelection] = useState({}); + const [hasTriedToSubmit, setHasTriedToSubmit] = useState(false) useEffect(() => { const fetchData = async () => { @@ -80,21 +81,27 @@ export function TutorRegistrationForm() { fetchData(); }, []); - - // FIXME: the error message does not disappear if we select an event. Maybe the bug is in here + useEffect(() => { - const selectedIds = Object.keys(rowSelection).filter(k => rowSelection[k]).map(Number); - form.setValue("selectedEventIds", selectedIds) - }, [rowSelection, form]) + if(!hasTriedToSubmit) return + + const selectedIds = Object.keys(rowSelection).filter(k => rowSelection[k]).map(Number) + form.setValue("selectedEventIds", selectedIds, {shouldValidate: true}) + }, [rowSelection, form, hasTriedToSubmit]) const resetForm = () => { setRowSelection({}); form.reset() } - - function onSubmit(values: z.infer) { + function onValidSubmit(values: z.infer) { resetForm() + // This is a little ugly, it just removes the error from the table - form.clearErrors did not work + setHasTriedToSubmit(false) + } + + function onInvalidSubmit() { + setHasTriedToSubmit(true); } @@ -103,7 +110,7 @@ export function TutorRegistrationForm() {

Tutor Registration

We are happy to hear that you wanna support us as a tutor in le Vorkurs, fill out the below form, so we can put you into a shift

- + + {Object.keys(rowSelection).filter(k => rowSelection[k]).map(Number)} )} /> From fe6f05817ef8acabae7c057718b78ff23b3d6962 Mon Sep 17 00:00:00 2001 From: Plebysnek <115377141+Plebysnacc@users.noreply.github.com> Date: Sat, 12 Apr 2025 11:52:47 +0200 Subject: [PATCH 08/19] remove unneeded features from event-table --- .../app/(form-tutor)/form-tutor/columns.tsx | 30 ------ .../form-tutor/event-table-pagination.tsx | 91 +++++++++++++++++++ .../(form-tutor)/form-tutor/event-table.tsx | 4 +- 3 files changed, 93 insertions(+), 32 deletions(-) create mode 100644 frontend/app/(form-tutor)/form-tutor/event-table-pagination.tsx diff --git a/frontend/app/(form-tutor)/form-tutor/columns.tsx b/frontend/app/(form-tutor)/form-tutor/columns.tsx index bac5dab..0cf71b3 100644 --- a/frontend/app/(form-tutor)/form-tutor/columns.tsx +++ b/frontend/app/(form-tutor)/form-tutor/columns.tsx @@ -1,18 +1,9 @@ import { DataTableColumnHeader } from "@/components/data-table-column-header"; import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; import { Event } from "@/lib/gql/generated/graphql"; import { formatDateToDDMM, formatDateToHHMM } from "@/lib/utils"; import { ColumnDef } from "@tanstack/react-table"; -import { MoreHorizontal } from "lucide-react"; export const columns: ColumnDef[] = [ { @@ -89,25 +80,4 @@ export const columns: ColumnDef[] = [ ), }, - { - id: "actions", - enableHiding: false, - cell: ({ row }) => { - return ( - - - - - - Optionen - Bearbeiten - Löschen - - - ); - }, - }, ]; diff --git a/frontend/app/(form-tutor)/form-tutor/event-table-pagination.tsx b/frontend/app/(form-tutor)/form-tutor/event-table-pagination.tsx new file mode 100644 index 0000000..724b990 --- /dev/null +++ b/frontend/app/(form-tutor)/form-tutor/event-table-pagination.tsx @@ -0,0 +1,91 @@ +import { + ChevronLeftIcon, + ChevronRightIcon, + DoubleArrowLeftIcon, + DoubleArrowRightIcon, +} from "@radix-ui/react-icons" +import { Table } from "@tanstack/react-table" +import { Event } from "@/lib/gql/generated/graphql" +import { Button } from "@/components/ui/button" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" + +interface DataTablePaginationProps { + table: Table +} + +export function EventTablePagination({ + table, + }: DataTablePaginationProps) { + return ( +
+
+

Einträge pro Seite

+ +
+
+ Seite {table.getState().pagination.pageIndex + 1} von{" "} + {table.getPageCount()} +
+
+ + + + +
+
+ ) +} diff --git a/frontend/app/(form-tutor)/form-tutor/event-table.tsx b/frontend/app/(form-tutor)/form-tutor/event-table.tsx index bb49706..31ae49a 100644 --- a/frontend/app/(form-tutor)/form-tutor/event-table.tsx +++ b/frontend/app/(form-tutor)/form-tutor/event-table.tsx @@ -25,6 +25,7 @@ import { Input } from "@/components/ui/input"; import {DataTablePagination} from "@/components/data-table-pagination"; import {DataTableViewOptions} from "@/components/data-table-view-options"; import {Event} from "@/lib/gql/generated/graphql" +import {EventTablePagination} from "@/app/(form-tutor)/form-tutor/event-table-pagination"; interface DataTableProps { columns: ColumnDef[]; @@ -75,7 +76,6 @@ export function EventTable({ } className="max-w-sm" /> -
@@ -127,7 +127,7 @@ export function EventTable({
- +
); } From bacc8017c5d4fba13f7e69333e2842aa9b515ffe Mon Sep 17 00:00:00 2001 From: Plebysnek <115377141+Plebysnacc@users.noreply.github.com> Date: Sat, 12 Apr 2025 16:06:07 +0200 Subject: [PATCH 09/19] add submission feedback --- .../(form-tutor)/form-tutor/event-table.tsx | 4 - frontend/app/(form-tutor)/form-tutor/page.tsx | 189 ++---------------- .../succeeded-submission-window.tsx | 16 ++ .../form-tutor/tutor-registration-form.tsx | 169 ++++++++++++++++ frontend/app/layout.tsx | 3 +- 5 files changed, 199 insertions(+), 182 deletions(-) create mode 100644 frontend/app/(form-tutor)/form-tutor/succeeded-submission-window.tsx create mode 100644 frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx diff --git a/frontend/app/(form-tutor)/form-tutor/event-table.tsx b/frontend/app/(form-tutor)/form-tutor/event-table.tsx index 31ae49a..d07ee3e 100644 --- a/frontend/app/(form-tutor)/form-tutor/event-table.tsx +++ b/frontend/app/(form-tutor)/form-tutor/event-table.tsx @@ -1,5 +1,3 @@ -"use client"; - import { ColumnDef, ColumnFiltersState, @@ -22,8 +20,6 @@ import { } from "@/components/ui/table"; import React from "react"; import { Input } from "@/components/ui/input"; -import {DataTablePagination} from "@/components/data-table-pagination"; -import {DataTableViewOptions} from "@/components/data-table-view-options"; import {Event} from "@/lib/gql/generated/graphql" import {EventTablePagination} from "@/app/(form-tutor)/form-tutor/event-table-pagination"; diff --git a/frontend/app/(form-tutor)/form-tutor/page.tsx b/frontend/app/(form-tutor)/form-tutor/page.tsx index c86734c..29afcf9 100644 --- a/frontend/app/(form-tutor)/form-tutor/page.tsx +++ b/frontend/app/(form-tutor)/form-tutor/page.tsx @@ -1,184 +1,19 @@ -"use client" +"use client"; -import { zodResolver } from "@hookform/resolvers/zod" -import { useForm } from "react-hook-form" -import { z } from "zod" - -import { Button } from "@/components/ui/button" -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form" -import { Input } from "@/components/ui/input" -import { columns } from "@/app/(form-tutor)/form-tutor/columns" -import {EventTable} from "@/app/(form-tutor)/form-tutor/event-table"; -import {useEffect, useState} from "react"; -import {getClient} from "@/lib/graphql"; -import { - Event, - TableEventsDocument, - TableEventsQuery, - TableEventsQueryVariables -} from "@/lib/gql/generated/graphql"; -import {RowSelectionState} from "@tanstack/react-table"; - -const tutorRegistrationFormSchema = z.object({ - firstname: z.string().min(2, { - message: "First name must be at least 2 characters long.", - }), - lastname: z.string().min(2, { - message: "Last name must be at least 2 characters long.", - }), - email: z.string().email("Invalid email address").min(1, { - message: "Please enter an email.", - }), - selectedEventIds: z.array(z.number()).min(1, { - message: "Please select at least one event.", - }), -}) - -export function TutorRegistrationForm() { - const form = useForm>({ - resolver: zodResolver(tutorRegistrationFormSchema), - defaultValues: { - firstname: "", - lastname: "", - email: "", - selectedEventIds: [], - }, - }) - const [events, setEvents] = useState([]) - const [loading , setLoading] = useState(true) - const [rowSelection, setRowSelection] = useState({}); - const [hasTriedToSubmit, setHasTriedToSubmit] = useState(false) - - useEffect(() => { - const fetchData = async () => { - setLoading(true); - - const client = getClient(); - - const vars: TableEventsQueryVariables = { - needsTutors: true, - onlyFuture: true, - }; - - const eventData = await client.request( - TableEventsDocument, - vars - ); - - if (eventData.events.length) { - setEvents(eventData.events); - } - - setLoading(false); - }; - - fetchData(); - }, []); - - useEffect(() => { - if(!hasTriedToSubmit) return - - const selectedIds = Object.keys(rowSelection).filter(k => rowSelection[k]).map(Number) - form.setValue("selectedEventIds", selectedIds, {shouldValidate: true}) - }, [rowSelection, form, hasTriedToSubmit]) - - const resetForm = () => { - setRowSelection({}); - form.reset() - } - - function onValidSubmit(values: z.infer) { - resetForm() - // This is a little ugly, it just removes the error from the table - form.clearErrors did not work - setHasTriedToSubmit(false) - } - - function onInvalidSubmit() { - setHasTriedToSubmit(true); - } +import {useState} from "react"; +import TutorRegistrationForm from "@/app/(form-tutor)/form-tutor/tutor-registration-form"; +import {SuccceededSubmissionWindow} from "@/app/(form-tutor)/form-tutor/succeeded-submission-window"; +export default function TutorRegistration() { + const [submissionSuccess, setSubmissionSuccess] = useState(false) return ( -
-

Tutor Registration

-

We are happy to hear that you wanna support us as a tutor in le Vorkurs, fill out the below form, so we can put you into a shift

- - - ( - - First Name - - - - - - - )} - /> - ( - - Last Name - - - - - - )} - /> - ( - - email - - - - - - )} - /> - ( - - I can help here: - - {loading?( -

Loading...

- ):( - - )} -
- - {Object.keys(rowSelection).filter(k => rowSelection[k]).map(Number)} -
- )} - /> - - - +
+ {(!submissionSuccess) ? ( + + ) : ( + + )}
) } - -export default TutorRegistrationForm \ No newline at end of file diff --git a/frontend/app/(form-tutor)/form-tutor/succeeded-submission-window.tsx b/frontend/app/(form-tutor)/form-tutor/succeeded-submission-window.tsx new file mode 100644 index 0000000..64507f2 --- /dev/null +++ b/frontend/app/(form-tutor)/form-tutor/succeeded-submission-window.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import {Button} from "@/components/ui/button"; + +interface SucceededSubmissionWindowProps { + setSubmissionSuccess: React.Dispatch> +} + +export function SuccceededSubmissionWindow( {setSubmissionSuccess}: SucceededSubmissionWindowProps) { + + return ( +
+

Success! Everything worked

+ +
+ ) +} \ No newline at end of file diff --git a/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx b/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx new file mode 100644 index 0000000..1ad6211 --- /dev/null +++ b/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx @@ -0,0 +1,169 @@ +import {zodResolver} from "@hookform/resolvers/zod" +import {useForm} from "react-hook-form" +import {z} from "zod" + +import {Button} from "@/components/ui/button" +import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage,} from "@/components/ui/form" +import {Input} from "@/components/ui/input" +import {columns} from "@/app/(form-tutor)/form-tutor/columns" +import {EventTable} from "@/app/(form-tutor)/form-tutor/event-table" +import React, {useEffect, useState} from "react" +import {getClient} from "@/lib/graphql" +import {Event, TableEventsDocument, TableEventsQuery, TableEventsQueryVariables} from "@/lib/gql/generated/graphql" +import {RowSelectionState} from "@tanstack/react-table" + +const tutorRegistrationFormSchema = z.object({ + firstname: z.string().min(2, { + message: "First name must be at least 2 characters long.", + }), + lastname: z.string().min(2, { + message: "Last name must be at least 2 characters long.", + }), + email: z.string().email("Invalid email address").min(1, { + message: "Please enter an email.", + }), + selectedEventIds: z.array(z.number()).min(1, { + message: "Please select at least one event.", + }), +}) + +interface TutorRegistrationFormProps { + setSubmissionSuccess: React.Dispatch> +} + +export function TutorRegistrationForm({setSubmissionSuccess}: TutorRegistrationFormProps) { + const form = useForm>({ + resolver: zodResolver(tutorRegistrationFormSchema), + defaultValues: { + firstname: "", + lastname: "", + email: "", + selectedEventIds: [], + }, + }) + const [events, setEvents] = useState([]) + const [loading, setLoading] = useState(true) + const [rowSelection, setRowSelection] = useState({}); + const [hasTriedToSubmit, setHasTriedToSubmit] = useState(false) + + useEffect(() => { + const fetchData = async () => { + setLoading(true); + + const client = getClient(); + + const vars: TableEventsQueryVariables = { + needsTutors: true, + onlyFuture: true, + }; + + const eventData = await client.request( + TableEventsDocument, + vars + ); + + if (eventData.events.length) { + setEvents(eventData.events); + } + + setLoading(false); + }; + // FIXME: maybe try-catch it with a status for failed loading... + fetchData(); + }, []); + + useEffect(() => { + const selectedIds = Object.keys(rowSelection).filter(k => rowSelection[k]).map(Number) + form.setValue("selectedEventIds", selectedIds, {shouldValidate: hasTriedToSubmit}) + }, [rowSelection, form, hasTriedToSubmit]) + + const resetForm = () => { + setRowSelection({}); + form.reset() + } + + function onValidSubmit(values: z.infer) { + resetForm() + // TODO: throw values into the backend, check if it succeeded + console.log(values) + setSubmissionSuccess(true) + } + + + return ( + <> +

Tutor Registration

+

We are happy to hear that you wanna support us as a tutor in le Vorkurs, fill out the + below form, so we can put you into a shift

+
+ { /* the second callback triggers on an invalid submission */ } + setHasTriedToSubmit(true))} className="space-y-5 w-[50%]"> + ( + + First Name + + + + + + )} + /> + ( + + Last Name + + + + + + )} + /> + ( + + email + + + + + + )} + /> + ( + + I can help here: + + {loading ? ( +

Loading...

+ ) : ( + + )} +
+ +
+ )} + /> + + + + + ) +} + +export default TutorRegistrationForm \ No newline at end of file diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index be7cb03..54fa006 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -8,7 +8,7 @@ import { siteConfig } from "@/config/site"; import { TailwindIndicator } from "@/components/tailwind-indicator"; import { UserProvider, ThemeProvider } from "@/components/providers"; import Header from "@/components/header"; -import { Suspense } from "react"; +import React, { Suspense } from "react"; const fontSans = FontSans({ subsets: ["latin"], @@ -22,6 +22,7 @@ const fontHeading = localFont({ export const metadata = { title: { + template: "", default: siteConfig.name, }, description: siteConfig.description, From f8bb92015c4a375e20db8d8d7e818e7afa3db179 Mon Sep 17 00:00:00 2001 From: Plebysnek <115377141+Plebysnacc@users.noreply.github.com> Date: Sat, 12 Apr 2025 16:37:51 +0200 Subject: [PATCH 10/19] remove code duplication in table pagination --- .../form-tutor/event-table-pagination.tsx | 91 ------------------- .../(form-tutor)/form-tutor/event-table.tsx | 4 +- frontend/components/data-table-pagination.tsx | 18 ++-- 3 files changed, 13 insertions(+), 100 deletions(-) delete mode 100644 frontend/app/(form-tutor)/form-tutor/event-table-pagination.tsx diff --git a/frontend/app/(form-tutor)/form-tutor/event-table-pagination.tsx b/frontend/app/(form-tutor)/form-tutor/event-table-pagination.tsx deleted file mode 100644 index 724b990..0000000 --- a/frontend/app/(form-tutor)/form-tutor/event-table-pagination.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { - ChevronLeftIcon, - ChevronRightIcon, - DoubleArrowLeftIcon, - DoubleArrowRightIcon, -} from "@radix-ui/react-icons" -import { Table } from "@tanstack/react-table" -import { Event } from "@/lib/gql/generated/graphql" -import { Button } from "@/components/ui/button" -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select" - -interface DataTablePaginationProps { - table: Table -} - -export function EventTablePagination({ - table, - }: DataTablePaginationProps) { - return ( -
-
-

Einträge pro Seite

- -
-
- Seite {table.getState().pagination.pageIndex + 1} von{" "} - {table.getPageCount()} -
-
- - - - -
-
- ) -} diff --git a/frontend/app/(form-tutor)/form-tutor/event-table.tsx b/frontend/app/(form-tutor)/form-tutor/event-table.tsx index d07ee3e..3ef3fa5 100644 --- a/frontend/app/(form-tutor)/form-tutor/event-table.tsx +++ b/frontend/app/(form-tutor)/form-tutor/event-table.tsx @@ -21,7 +21,7 @@ import { import React from "react"; import { Input } from "@/components/ui/input"; import {Event} from "@/lib/gql/generated/graphql" -import {EventTablePagination} from "@/app/(form-tutor)/form-tutor/event-table-pagination"; +import {DataTablePagination} from "@/components/data-table-pagination"; interface DataTableProps { columns: ColumnDef[]; @@ -123,7 +123,7 @@ export function EventTable({
- +
); } diff --git a/frontend/components/data-table-pagination.tsx b/frontend/components/data-table-pagination.tsx index c809dc1..37e8a57 100644 --- a/frontend/components/data-table-pagination.tsx +++ b/frontend/components/data-table-pagination.tsx @@ -14,21 +14,25 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select" +import {cn} from "@/lib/utils"; interface DataTablePaginationProps { table: Table + enableSelectionCounter?: boolean, } export function DataTablePagination({ - table, + table, enableSelectionCounter = true }: DataTablePaginationProps) { return ( -
-
- {table.getFilteredSelectedRowModel().rows.length} von{" "} - {table.getFilteredRowModel().rows.length} Zeile(n) ausgewählt. -
-
+
+ { enableSelectionCounter && ( +
+ {table.getFilteredSelectedRowModel().rows.length} von{" "} + {table.getFilteredRowModel().rows.length} Zeile(n) ausgewählt. +
+ )} +

Einträge pro Seite

- + )} /> ( + render={({ field }) => ( - Last Name + Nachname - + )} /> ( + render={({ field }) => ( - email + E-Mail - + )} /> ( + render={({ field }) => ( - I can help here: + Bei diesen Veranstaltungen kann ich helfen {loading ? ( -

Loading...

+

Loading...

) : ( + {...field} + /> )}
- +
)} /> - + - - ) +
+ ); } - -export default TutorRegistrationForm \ No newline at end of file diff --git a/frontend/public/check_circle.svg b/frontend/public/check_circle.svg new file mode 100644 index 0000000..294d863 --- /dev/null +++ b/frontend/public/check_circle.svg @@ -0,0 +1 @@ + \ No newline at end of file From 20d215814cf8495c25606f02722b6eca87bce83b Mon Sep 17 00:00:00 2001 From: Plebysnek <115377141+Plebysnacc@users.noreply.github.com> Date: Sun, 13 Apr 2025 13:24:17 +0200 Subject: [PATCH 12/19] onSubmit with light error handling prototype --- .../form-tutor/tutor-registration-form.tsx | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx b/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx index 7942ea7..892372d 100644 --- a/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx +++ b/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx @@ -8,9 +8,16 @@ import { columns } from "@/app/(form-tutor)/form-tutor/columns"; import { EventTable } from "@/app/(form-tutor)/form-tutor/event-table"; import React, { useEffect, useState } from "react"; import { getClient } from "@/lib/graphql"; -import { Event, TableEventsDocument, TableEventsQuery, TableEventsQueryVariables } from "@/lib/gql/generated/graphql"; +import { + AddTutorDocument, AddTutorMutation, + Event, + TableEventsDocument, + TableEventsQuery, + TableEventsQueryVariables +} from "@/lib/gql/generated/graphql"; import { RowSelectionState } from "@tanstack/react-table"; import { cn } from "@/lib/utils/tailwindUtils"; +import {toast} from "sonner"; interface TutorRegistrationFormProps { setSubmissionSuccess: React.Dispatch>; @@ -19,26 +26,26 @@ interface TutorRegistrationFormProps { export default function TutorRegistrationForm({ setSubmissionSuccess, setUserMail }: TutorRegistrationFormProps) { const tutorRegistrationFormSchema = z.object({ - firstname: z.string().min(2, { + firstName: z.string().min(2, { message: "Bitte gib mindestens 2 Zeichen ein.", }), - lastname: z.string().min(2, { + lastName: z.string().min(2, { message: "Bitte gib mindestens 2 Zeichen ein.", }), email: z.string().email("Ungültiges E-Mail Format.").min(1, { message: "Bitte gib eine E-Mail an", }), - selectedEventIds: z.array(z.number()).min(1, { + eventsAvailable: z.array(z.number()).min(1, { message: "Bitte wähle mindestens eine Veranstaltung aus.", }), }); const form = useForm>({ resolver: zodResolver(tutorRegistrationFormSchema), defaultValues: { - firstname: "", - lastname: "", + firstName: "", + lastName: "", email: "", - selectedEventIds: [], + eventsAvailable: [], }, }); const [events, setEvents] = useState([]); @@ -65,14 +72,20 @@ export default function TutorRegistrationForm({ setSubmissionSuccess, setUserMai useEffect(() => { const selectedIds = Object.keys(rowSelection).filter(k => rowSelection[k]).map(Number); - form.setValue("selectedEventIds", selectedIds, { shouldValidate: hasTriedToSubmit }); + form.setValue("eventsAvailable", selectedIds, { shouldValidate: hasTriedToSubmit }); }, [rowSelection, form, hasTriedToSubmit]); - function onValidSubmit(values: z.infer) { - setUserMail(values.email); - console.log(values); - setSubmissionSuccess(true); - resetForm(); + async function onValidSubmit(tutorData: z.infer) { + try { + const client = getClient(); + await client.request(AddTutorDocument, tutorData) + setUserMail(tutorData.email); + setSubmissionSuccess(true); + resetForm(); + } catch (error) { + console.log(error); + toast('Ein Fehler ist aufgetreten, sollte diese Mail schon verwendet werden, logge dich bitte mit deinem Konto ein'); + } } const resetForm = () => { @@ -92,7 +105,7 @@ export default function TutorRegistrationForm({ setSubmissionSuccess, setUserMai
setHasTriedToSubmit(true))} className="space-y-6 w-full"> ( Vorname @@ -105,7 +118,7 @@ export default function TutorRegistrationForm({ setSubmissionSuccess, setUserMai /> ( Nachname @@ -130,7 +143,7 @@ export default function TutorRegistrationForm({ setSubmissionSuccess, setUserMai )} /> ( Bei diesen Veranstaltungen kann ich helfen From ee878cf70ecf1c73431bf84840589cb2ee8b0afc Mon Sep 17 00:00:00 2001 From: Plebysnek <115377141+Plebysnacc@users.noreply.github.com> Date: Sun, 13 Apr 2025 16:04:33 +0200 Subject: [PATCH 13/19] implement onSubmit error handling --- .../form-tutor/tutor-registration-form.tsx | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx b/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx index 892372d..e0ce7d1 100644 --- a/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx +++ b/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx @@ -79,12 +79,9 @@ export default function TutorRegistrationForm({ setSubmissionSuccess, setUserMai try { const client = getClient(); await client.request(AddTutorDocument, tutorData) - setUserMail(tutorData.email); - setSubmissionSuccess(true); - resetForm(); + finishSubmission() } catch (error) { - console.log(error); - toast('Ein Fehler ist aufgetreten, sollte diese Mail schon verwendet werden, logge dich bitte mit deinem Konto ein'); + handleError(String(error)) } } @@ -93,6 +90,21 @@ export default function TutorRegistrationForm({ setSubmissionSuccess, setUserMai form.reset(); }; + const handleError = (error: string) => { + console.log(error); + if(error.includes("Error: constraint failed: UNIQUE constraint failed: users.mail")){ + form.setError('email', {type: 'custom', message:'Diese E-Mail wird bereits verwendet, bitte melde dich an um deine Verfügbarkeiten zu ändern'}) + } else { + finishSubmission() + } + } + + const finishSubmission = () => { + setUserMail(form.getValues('email')) + setSubmissionSuccess(true) + resetForm(); + } + return (

Tutor:innen Registrierung

From f2481a566fa96a4c1c849bd6c468d5527a1a6bed Mon Sep 17 00:00:00 2001 From: Plebysnek <115377141+Plebysnacc@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:03:43 +0200 Subject: [PATCH 14/19] prototype the backend push --- .../form-tutor/tutor-registration-form.tsx | 34 ++++++++++++++++--- frontend/app/layout.tsx | 2 +- frontend/lib/gql/mutations/user.graphql | 11 ++++++ 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx b/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx index e0ce7d1..f3134d9 100644 --- a/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx +++ b/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx @@ -9,15 +9,18 @@ import { EventTable } from "@/app/(form-tutor)/form-tutor/event-table"; import React, { useEffect, useState } from "react"; import { getClient } from "@/lib/graphql"; import { + AddEventAvailabilityOfTutorDocument, + AddEventAvailabilityOfTutorMutation, AddEventAvailabilityOfTutorMutationVariables, AddTutorDocument, AddTutorMutation, - Event, + Event, RegistrationDocument, RegistrationMutation, TableEventsDocument, TableEventsQuery, - TableEventsQueryVariables + TableEventsQueryVariables, TutorRegistrationDocument, TutorRegistrationMutation, TutorRegistrationMutationVariables } from "@/lib/gql/generated/graphql"; import { RowSelectionState } from "@tanstack/react-table"; import { cn } from "@/lib/utils/tailwindUtils"; import {toast} from "sonner"; +import {useUser} from "@/components/providers"; interface TutorRegistrationFormProps { setSubmissionSuccess: React.Dispatch>; @@ -77,10 +80,33 @@ export default function TutorRegistrationForm({ setSubmissionSuccess, setUserMai async function onValidSubmit(tutorData: z.infer) { try { - const client = getClient(); - await client.request(AddTutorDocument, tutorData) + let client = getClient(); + const tutorRegistrationData: TutorRegistrationMutationVariables = { + firstName: tutorData.firstName, + lastName: tutorData.lastName, + email: tutorData.email, + }; + + const sid = await client.request( + TutorRegistrationDocument, + tutorRegistrationData + ); + + client = getClient(sid.addUser) + + const availabilityData: AddEventAvailabilityOfTutorMutationVariables = { + email: tutorData.email, + eventsAvailable: tutorData.eventsAvailable + } + + await client.request( + AddEventAvailabilityOfTutorDocument, + availabilityData + ); + finishSubmission() } catch (error) { + toast.error handleError(String(error)) } } diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index 54fa006..fe3e196 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -62,7 +62,7 @@ export default function RootLayout({ children }: RootLayoutProps) { >
{children} - + diff --git a/frontend/lib/gql/mutations/user.graphql b/frontend/lib/gql/mutations/user.graphql index 2c8c4ef..4997060 100644 --- a/frontend/lib/gql/mutations/user.graphql +++ b/frontend/lib/gql/mutations/user.graphql @@ -10,3 +10,14 @@ mutation addTutor($firstName: String!, $lastName: String!, $email: String!, $eve fn } } + +mutation TutorRegistration($firstName: String!, $lastName: String!, $email: String!) { + addUser(user: {fn: $firstName, sn: $lastName, mail: $email}) +} + +mutation addEventAvailabilityOfTutor($email: String!, $eventsAvailable: [Int!]!) { + addTutorAvailabilityForEvent( + availability: {userMail: $email, eventID: $eventsAvailable} + ){ + fn + }} From 2a0a88ce134b382ff56a632b0bdf4160678dd62e Mon Sep 17 00:00:00 2001 From: Daniel Heidemann Date: Thu, 17 Apr 2025 16:38:10 +0200 Subject: [PATCH 15/19] fix: users query for availabilities --- server/graph/schema.resolvers.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/graph/schema.resolvers.go b/server/graph/schema.resolvers.go index c42bac2..bb1370e 100644 --- a/server/graph/schema.resolvers.go +++ b/server/graph/schema.resolvers.go @@ -882,7 +882,8 @@ func (r *queryResolver) Users(ctx context.Context, mail []string) ([]*models.Use Model(&users). Relation("EventsAssigned"). Relation("EventsRegistered"). - Relation("Applications") + Relation("Applications"). + Relation("Availabilities") if mail != nil { query = query. From 2b949c28513f53c5b15e214d4c4b531df610dcdc Mon Sep 17 00:00:00 2001 From: Plebysnek <115377141+Plebysnacc@users.noreply.github.com> Date: Thu, 17 Apr 2025 17:32:33 +0200 Subject: [PATCH 16/19] add better handling of email display --- .../form-tutor/succeeded-submission-window.tsx | 10 ++++++---- .../form-tutor/tutor-registration-form.tsx | 12 +++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/frontend/app/(form-tutor)/form-tutor/succeeded-submission-window.tsx b/frontend/app/(form-tutor)/form-tutor/succeeded-submission-window.tsx index b06c280..cdc6758 100644 --- a/frontend/app/(form-tutor)/form-tutor/succeeded-submission-window.tsx +++ b/frontend/app/(form-tutor)/form-tutor/succeeded-submission-window.tsx @@ -6,8 +6,10 @@ interface SucceededSubmissionWindowProps { } export function SuccceededSubmissionWindow({userMail}: SucceededSubmissionWindowProps) { + const formattedMail: string = userMail.replace(/([.@])/g, "$1\u200B") + return ( -
+

Erfolgreich Registriert!

-
+

In Kürze schicken wir eine E-Mail an

-
- {userMail} +
+ {formattedMail}

In dieser bekommst du einen Login-Link, mit welchem du dich dann in diesem System anmelden kannst. diff --git a/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx b/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx index f3134d9..c76ee25 100644 --- a/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx +++ b/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx @@ -11,8 +11,7 @@ import { getClient } from "@/lib/graphql"; import { AddEventAvailabilityOfTutorDocument, AddEventAvailabilityOfTutorMutation, AddEventAvailabilityOfTutorMutationVariables, - AddTutorDocument, AddTutorMutation, - Event, RegistrationDocument, RegistrationMutation, + Event, TableEventsDocument, TableEventsQuery, TableEventsQueryVariables, TutorRegistrationDocument, TutorRegistrationMutation, TutorRegistrationMutationVariables @@ -20,7 +19,6 @@ import { import { RowSelectionState } from "@tanstack/react-table"; import { cn } from "@/lib/utils/tailwindUtils"; import {toast} from "sonner"; -import {useUser} from "@/components/providers"; interface TutorRegistrationFormProps { setSubmissionSuccess: React.Dispatch>; @@ -146,9 +144,9 @@ export default function TutorRegistrationForm({ setSubmissionSuccess, setUserMai name="firstName" render={({ field }) => ( - Vorname + Vorname - + @@ -161,7 +159,7 @@ export default function TutorRegistrationForm({ setSubmissionSuccess, setUserMai Nachname - + @@ -174,7 +172,7 @@ export default function TutorRegistrationForm({ setSubmissionSuccess, setUserMai E-Mail - + From a831e614c4e726366ec4a246d5caff80152c12e4 Mon Sep 17 00:00:00 2001 From: Plebysnek <115377141+Plebysnacc@users.noreply.github.com> Date: Thu, 17 Apr 2025 18:00:19 +0200 Subject: [PATCH 17/19] change up the email error handling --- .../form-tutor/tutor-registration-form.tsx | 11 +- frontend/package-lock.json | 176 ++++++++++++++---- 2 files changed, 152 insertions(+), 35 deletions(-) diff --git a/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx b/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx index c76ee25..40ec4c0 100644 --- a/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx +++ b/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx @@ -20,6 +20,7 @@ import { RowSelectionState } from "@tanstack/react-table"; import { cn } from "@/lib/utils/tailwindUtils"; import {toast} from "sonner"; + interface TutorRegistrationFormProps { setSubmissionSuccess: React.Dispatch>; setUserMail: React.Dispatch>; @@ -115,11 +116,17 @@ export default function TutorRegistrationForm({ setSubmissionSuccess, setUserMai }; const handleError = (error: string) => { - console.log(error); if(error.includes("Error: constraint failed: UNIQUE constraint failed: users.mail")){ form.setError('email', {type: 'custom', message:'Diese E-Mail wird bereits verwendet, bitte melde dich an um deine Verfügbarkeiten zu ändern'}) - } else { + + // If the server has problems sending the mail, the user will still be added, should be discussed later + } else if (error.includes("failed to send email:") || error.includes("Error: dial tcp")) { finishSubmission() + + } else { + toast.error( + 'Ein Problem ist aufgetreten, sollte es später nicht funktionieren, melde dich bei vorkurs@mathphys.info' + ); } } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 537d0b4..cecfec3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,7 +8,7 @@ "name": "pepp", "version": "0.1.0", "dependencies": { - "@hookform/resolvers": "^3.9.0", + "@hookform/resolvers": "^3.10.0", "@opentelemetry/api-logs": "^0.54.0", "@opentelemetry/instrumentation": "^0.54.0", "@opentelemetry/sdk-logs": "^0.54.0", @@ -18,7 +18,7 @@ "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-hover-card": "^1.1.2", "@radix-ui/react-icons": "^1.3.0", - "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-label": "^2.1.3", "@radix-ui/react-navigation-menu": "^1.2.1", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-progress": "^1.1.0", @@ -26,11 +26,11 @@ "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slider": "^1.2.0", - "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-switch": "^1.1.3", "@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.8", - "@tanstack/react-table": "^8.19.2", + "@tanstack/react-table": "^8.21.2", "@vercel/otel": "^1.10.0", "axios": "^1.7.7", "class-variance-authority": "^0.7.1", @@ -44,7 +44,7 @@ "react": "^18", "react-day-picker": "^8.10.1", "react-dom": "^18", - "react-hook-form": "^7.52.2", + "react-hook-form": "^7.55.0", "react-leaflet": "^4.2.1", "react-textarea-autosize": "^8.5.4", "shadcn-ui": "^0.9.2", @@ -54,7 +54,7 @@ "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "undici": "^6.20.1", - "zod": "^3.23.8" + "zod": "^3.24.2" }, "devDependencies": { "@graphql-codegen/cli": "5.0.2", @@ -2236,9 +2236,9 @@ } }, "node_modules/@hookform/resolvers": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.9.0.tgz", - "integrity": "sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", + "integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==", "license": "MIT", "peerDependencies": { "react-hook-form": "^7.0.0" @@ -3691,6 +3691,24 @@ } } }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz", @@ -3991,12 +4009,35 @@ } }, "node_modules/@radix-ui/react-label": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz", - "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.3.tgz", + "integrity": "sha512-zwSQ1NzSKG95yA0tvBMgv6XPHoqapJCcg9nsUBaQQ66iRBhZNhlpaQG2ERYYX4O4stkYFK5rxj5NsWfO9CS+Hg==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.0.0" + "@radix-ui/react-primitive": "2.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.3.tgz", + "integrity": "sha512-Pf/t/GkndH7CQ8wE2hbkXA+WyZ83fhQQn5DDmwDiDo6AwN/fhaH8oqZ0jRjMrO2iaMhDi6P1HRx6AZwyMinY1g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.0" }, "peerDependencies": { "@types/react": "*", @@ -4780,6 +4821,21 @@ } } }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz", @@ -4803,6 +4859,24 @@ } } }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slider": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.2.0.tgz", @@ -4837,12 +4911,12 @@ } }, "node_modules/@radix-ui/react-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", - "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", + "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.1" + "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", @@ -4855,9 +4929,9 @@ } }, "node_modules/@radix-ui/react-slot/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", - "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -4957,6 +5031,24 @@ } } }, + "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tabs": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.1.tgz", @@ -5249,6 +5341,24 @@ } } }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", + "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-visually-hidden": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.2.tgz", @@ -5465,12 +5575,12 @@ "license": "0BSD" }, "node_modules/@tanstack/react-table": { - "version": "8.19.3", - "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.19.3.tgz", - "integrity": "sha512-MtgPZc4y+cCRtU16y1vh1myuyZ2OdkWgMEBzyjYsoMWMicKZGZvcDnub3Zwb6XF2pj9iRMvm1SO1n57lS0vXLw==", + "version": "8.21.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.2.tgz", + "integrity": "sha512-11tNlEDTdIhMJba2RBH+ecJ9l1zgS2kjmexDPAraulc8jeNA4xocSNeyzextT0XJyASil4XsCYlJmf5jEWAtYg==", "license": "MIT", "dependencies": { - "@tanstack/table-core": "8.19.3" + "@tanstack/table-core": "8.21.2" }, "engines": { "node": ">=12" @@ -5485,9 +5595,9 @@ } }, "node_modules/@tanstack/table-core": { - "version": "8.19.3", - "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.19.3.tgz", - "integrity": "sha512-IqREj9ADoml9zCAouIG/5kCGoyIxPFdqdyoxis9FisXFi5vT+iYfEfLosq4xkU/iDbMcEuAj+X8dWRLvKYDNoQ==", + "version": "8.21.2", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.2.tgz", + "integrity": "sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==", "license": "MIT", "engines": { "node": ">=12" @@ -11855,9 +11965,9 @@ } }, "node_modules/react-hook-form": { - "version": "7.52.2", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.52.2.tgz", - "integrity": "sha512-pqfPEbERnxxiNMPd0bzmt1tuaPcVccywFDpyk2uV5xCIBphHV5T8SVnX9/o3kplPE1zzKt77+YIoq+EMwJp56A==", + "version": "7.55.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.55.0.tgz", + "integrity": "sha512-XRnjsH3GVMQz1moZTW53MxfoWN7aDpUg/GpVNc4A3eXRVNdGXfbzJ4vM4aLQ8g6XCUh1nIbx70aaNCl7kxnjog==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -13975,9 +14085,9 @@ } }, "node_modules/zod": { - "version": "3.23.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", - "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "version": "3.24.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" From f2a204e80d3970015d0a57d59b33ac7778477921 Mon Sep 17 00:00:00 2001 From: Plebysnek <115377141+Plebysnacc@users.noreply.github.com> Date: Thu, 17 Apr 2025 23:13:23 +0200 Subject: [PATCH 18/19] translate submit button, reduce mobile padding --- .../app/(form-tutor)/form-tutor/tutor-registration-form.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx b/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx index 40ec4c0..2d40a50 100644 --- a/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx +++ b/frontend/app/(form-tutor)/form-tutor/tutor-registration-form.tsx @@ -137,7 +137,7 @@ export default function TutorRegistrationForm({ setSubmissionSuccess, setUserMai } return ( -

+

Tutor:innen Registrierung

Es freut uns, dass Du Interesse hast uns beim Vorkurs zu unterstützen. Bitte gib uns hier deine Kontaktdaten und @@ -213,7 +213,7 @@ export default function TutorRegistrationForm({ setSubmissionSuccess, setUserMai disabled={!form.formState.isValid && hasTriedToSubmit} type="submit" > - Submit + Abschicken

From cdb2b404aec9f0cb9241b4d3e009dd3405a555fb Mon Sep 17 00:00:00 2001 From: Plebysnek <115377141+Plebysnacc@users.noreply.github.com> Date: Thu, 17 Apr 2025 23:15:57 +0200 Subject: [PATCH 19/19] fix intendation in tutor mutation --- frontend/lib/gql/mutations/user.graphql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/lib/gql/mutations/user.graphql b/frontend/lib/gql/mutations/user.graphql index 4997060..44fa6a9 100644 --- a/frontend/lib/gql/mutations/user.graphql +++ b/frontend/lib/gql/mutations/user.graphql @@ -20,4 +20,5 @@ mutation addEventAvailabilityOfTutor($email: String!, $eventsAvailable: [Int!]!) availability: {userMail: $email, eventID: $eventsAvailable} ){ fn - }} + } +}