diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1118afa..1e73a81 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,8 +1,6 @@ name: Build Nginx-based docker image on: push: - branches: - - main concurrency: group: ${{ github.ref }} cancel-in-progress: true @@ -24,13 +22,32 @@ jobs: registry: ${{ secrets.PRIVATE_REGISTRY_HOST }} username: ${{ secrets.PRIVATE_REGISTRY_USERNAME }} password: ${{ secrets.PRIVATE_REGISTRY_TOKEN }} - - - name: Build and push + + - name: Build and push intermediate stages (deps) uses: docker/build-push-action@v6 with: context: . - push: ${{ github.event_name != 'pull_request' }} - tags: | - ${{ secrets.PRIVATE_REGISTRY_HOST }}/${{ env.GITHUB_REPOSITORY }}:latest - cache-from: type=registry,ref=user/app:latest + target: deps + push: ${{ github.ref == 'refs/heads/main' }} + tags: ${{ secrets.PRIVATE_REGISTRY_HOST }}/${{ env.GITHUB_REPOSITORY }}:deps + cache-from: type=registry,ref=${{ secrets.PRIVATE_REGISTRY_HOST }}/${{ env.GITHUB_REPOSITORY }}:deps + cache-to: type=inline + + - name: Build and push intermediate stages (builder) + uses: docker/build-push-action@v6 + with: + context: . + target: builder + push: ${{ github.ref == 'refs/heads/main' }} + tags: ${{ secrets.PRIVATE_REGISTRY_HOST }}/${{ env.GITHUB_REPOSITORY }}:builder + cache-from: type=registry,ref=${{ secrets.PRIVATE_REGISTRY_HOST }}/${{ env.GITHUB_REPOSITORY }}:builder + cache-to: type=inline + + - name: Build and push (final) + uses: docker/build-push-action@v6 + with: + context: . + push: ${{ github.ref == 'refs/heads/main' }} + tags: ${{ secrets.PRIVATE_REGISTRY_HOST }}/${{ env.GITHUB_REPOSITORY }}:latest + cache-from: type=registry,ref=${{ secrets.PRIVATE_REGISTRY_HOST }}/${{ env.GITHUB_REPOSITORY }}:latest cache-to: type=inline \ No newline at end of file diff --git a/.github/workflows/copyright_notice.yml b/.github/workflows/copyright_notice.yml index 321c44e..0536d2d 100644 --- a/.github/workflows/copyright_notice.yml +++ b/.github/workflows/copyright_notice.yml @@ -16,7 +16,7 @@ jobs: ref: ${{ github.head_ref }} - uses: VinnyBabuManjaly/copyright-action@v1.0.0 with: - CopyrightString: '/* Copyright (C) 2024 Manuel Bustillo*/\n\n' + CopyrightString: '/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/\n\n' FileType: '.tsx, .jsx, .ts' Path: 'app/, config/, db/' IgnorePath: 'testfolder1/a/, testfolder3' diff --git a/.nvmrc b/.nvmrc index 19e7d57..41106da 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -23.3.0 \ No newline at end of file +23.11.1 \ No newline at end of file diff --git a/app/[slug]/dashboard/expenses/page.tsx b/app/[slug]/dashboard/expenses/page.tsx index 8b24064..d289255 100644 --- a/app/[slug]/dashboard/expenses/page.tsx +++ b/app/[slug]/dashboard/expenses/page.tsx @@ -1,15 +1,44 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ -import { lusitana } from '@/app/ui/fonts'; +'use client' + +import { AbstractApi } from '@/app/api/abstract-api'; +import { Expense, ExpenseSerializer } from '@/app/lib/expense'; +import { classNames } from '@/app/ui/components/button'; +import ExpenseFormDialog from '@/app/ui/components/expense-form-dialog'; import ExpensesTable from '@/app/ui/expenses/table'; - -export default function Page () { +import SkeletonTable from '@/app/ui/guests/skeleton-row'; +import { Suspense, useEffect, useState } from 'react'; + +export default function Page() { + const refreshExpenses = () => { + new AbstractApi().getAll(new ExpenseSerializer(), (expenses: Expense[]) => { + setExpenses(expenses); + }); + } + + const [expenses, setExpenses] = useState([]); + const [expenseBeingEdited, setExpenseBeingEdited] = useState(undefined); + useEffect(() => { refreshExpenses() }, []); + return (
-
-

Expenses

-

Summary

- +
+ + { refreshExpenses(); setExpenseBeingEdited(undefined) }} + expense={expenseBeingEdited} + visible={expenseBeingEdited !== undefined} + onHide={() => { setExpenseBeingEdited(undefined) }} + /> + }> + setExpenseBeingEdited(expense)} + /> +
); diff --git a/app/[slug]/dashboard/guests/page.tsx b/app/[slug]/dashboard/guests/page.tsx index 09db838..d42b0ba 100644 --- a/app/[slug]/dashboard/guests/page.tsx +++ b/app/[slug]/dashboard/guests/page.tsx @@ -1,36 +1,73 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ 'use client'; -import { loadGroups } from '@/app/api/groups'; -import { loadGuests } from '@/app/api/guests'; -import { Group, Guest } from '@/app/lib/definitions'; +import { AbstractApi, } from '@/app/api/abstract-api'; +import { Group, GroupSerializer } from '@/app/lib/group'; +import { Guest, GuestSerializer } from '@/app/lib/guest'; +import { getCsrfToken, getSlug } from '@/app/lib/utils'; +import AffinitiesFormDialog from '@/app/ui/components/affinities-form-dialog'; import { classNames } from '@/app/ui/components/button'; +import GroupFormDialog from '@/app/ui/components/group-form-dialog'; import GuestFormDialog from '@/app/ui/components/guest-form-dialog'; import GroupsTable from '@/app/ui/groups/table'; import SkeletonTable from '@/app/ui/guests/skeleton-row'; import GuestsTable from '@/app/ui/guests/table'; import { TabPanel, TabView } from 'primereact/tabview'; -import { Suspense, useState } from 'react'; +import { Toast } from 'primereact/toast'; +import { Suspense, useRef, useState } from 'react'; export default function Page() { + const toast = useRef(null); + function refreshGuests() { - loadGuests((guests) => { - setGuests(guests); + new AbstractApi().getAll(new GuestSerializer(), (objects: Guest[]) => { + setGuests(objects); setGuestsLoaded(true); }); } function refreshGroups() { - loadGroups((groups) => { - setGroups(groups); + new AbstractApi().getAll(new GroupSerializer(), (objects: Group[]) => { + setGroups(objects); setGroupsLoaded(true); }); } + function resetAffinities() { + fetch(`/api/${getSlug()}/groups/affinities/reset`, { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'X-CSRF-TOKEN': getCsrfToken(), + } + }) + .then(response => { + if (response.ok) { + showAffinitiesResetSuccess(); + } else { + console.error('Failed to reset affinities'); + } + }) + .catch(error => { + console.error('Error resetting affinities:', error); + }); + } + + function showAffinitiesResetSuccess() { + toast.current?.show({ + severity: 'success', + summary: 'Affinities reset', + detail: 'All affinities have been reset to default values.' + }); + } + const [groupsLoaded, setGroupsLoaded] = useState(false); const [groups, setGroups] = useState>([]); + const [groupBeingEdited, setGroupBeingEdited] = useState(undefined); + + const [groupAffinitiesBeingEditted, setGroupAffinitiesBeingEditted] = useState(undefined); const [guestsLoaded, setGuestsLoaded] = useState(false); const [guests, setGuests] = useState>([]); @@ -54,14 +91,46 @@ export default function Page() { onHide={() => { setGuestBeingEdited(undefined) }} /> }> - setGuestBeingEdited(guest)} /> + setGuestBeingEdited(guest)} + />
-
+
+ +
+ + + +
+ + { refreshGroups(); setGroupBeingEdited(undefined) }} + group={groupBeingEdited} + visible={groupBeingEdited !== undefined} + onHide={() => { setGroupBeingEdited(undefined) }} + /> + + { setGroupAffinitiesBeingEditted(undefined) }} + /> + }> - + setGroupBeingEdited(group)} + onEditAffinities={(group) => setGroupAffinitiesBeingEditted(group)} + />
diff --git a/app/[slug]/dashboard/layout.tsx b/app/[slug]/dashboard/layout.tsx index 69055f9..d765b83 100644 --- a/app/[slug]/dashboard/layout.tsx +++ b/app/[slug]/dashboard/layout.tsx @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ import SideNav from '@/app/ui/dashboard/sidenav'; diff --git a/app/[slug]/dashboard/page.tsx b/app/[slug]/dashboard/page.tsx index 51c96c1..83f446e 100644 --- a/app/[slug]/dashboard/page.tsx +++ b/app/[slug]/dashboard/page.tsx @@ -1,9 +1,26 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ +'use client' + +import { GlobalSummary as Summary } from '@/app/lib/definitions'; +import { getSlug } from '@/app/lib/utils'; import GlobalSummary from '@/app/ui/dashboard/global-summary'; +import { useEffect, useState } from 'react'; export default function Page() { - return( - + const [globalSummary, setGlobalSummary] = useState(undefined); + + function refreshSummary() { + fetch(`/api/${getSlug()}/summary`) + .then((response) => response.json()) + .then((data) => { + setGlobalSummary(data); + }) + } + + useEffect(refreshSummary, []); + + return ( + globalSummary && ); } \ No newline at end of file diff --git a/app/[slug]/dashboard/tables/page.tsx b/app/[slug]/dashboard/tables/page.tsx index 7f55a77..8fd4a81 100644 --- a/app/[slug]/dashboard/tables/page.tsx +++ b/app/[slug]/dashboard/tables/page.tsx @@ -1,16 +1,42 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ 'use client'; +import { AbstractApi } from '@/app/api/abstract-api'; +import { TableSimulation, TableSimulationSerializer } from '@/app/lib/tableSimulation'; import Arrangement from '@/app/ui/arrangements/arrangement'; -import React, { useState } from 'react'; import ArrangementsTable from '@/app/ui/arrangements/arrangements-table'; +import { classNames } from '@/app/ui/components/button'; +import { Toast } from 'primereact/toast'; +import React, { useRef, useState } from 'react'; export default function Page() { + const toast = useRef(null); + + const show = () => { + toast.current?.show({ + severity: 'success', + summary: 'Simulation created', + detail: 'Table distributions will be calculated shortly, please come back in some minutes' + }); + }; + const [currentArrangement, setCurrentArrangement] = useState(null); + function createSimulation() { + const api = new AbstractApi(); + const serializer = new TableSimulationSerializer(); + api.create(serializer, new TableSimulation(), show); + } + return ( <> + +
+ + +
+ {currentArrangement && } diff --git a/app/[slug]/page.tsx b/app/[slug]/page.tsx index 34e8adf..f189a6a 100644 --- a/app/[slug]/page.tsx +++ b/app/[slug]/page.tsx @@ -1,30 +1,40 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ + +'use client'; -'use client'; -import Link from 'next/link'; -import styles from '@/app/ui/home.module.css'; import LoginForm from '@/app/ui/components/login-form'; +import RegistrationForm from '@/app/ui/components/registration-form'; +import { useParams } from 'next/navigation' +import { useEffect } from 'react'; +import { retrieveCSRFToken } from '../api/authentication'; +import { getCsrfToken } from '../lib/utils'; +export default async function Page() { + const params = useParams<{ slug: string }>() + useEffect(() => { + if (getCsrfToken() == 'unknown') { + retrieveCSRFToken(); + } + }, []); -export default async function Page({ params }: { params: Promise<{ slug: string }> }) { + if (typeof window !== 'undefined') { + localStorage.setItem('slug', await params.slug); + } - localStorage.setItem('slug', (await params).slug) return (
- Already have an account? Sign in -
Don't have an account? Register now! +
-
); } diff --git a/app/api/abstract-api.tsx b/app/api/abstract-api.tsx new file mode 100644 index 0000000..7201a08 --- /dev/null +++ b/app/api/abstract-api.tsx @@ -0,0 +1,76 @@ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ + +import { Entity } from '@/app/lib/definitions'; +import { getCsrfToken, getSlug } from '@/app/lib/utils'; + +export interface Api { + getAll(serializable: Serializable ,callback: (objets: T[]) => void): void; + get(serializable: Serializable, id: string, callback: (object: T) => void): void; + create(serializable: Serializable, object: T, callback: () => void): void; + update(serializable: Serializable, object: T, callback: () => void): void; + destroy(serializable: Serializable, object: T, callback: () => void): void; +} + +export interface Serializable { + fromJson(json: any): T; + toJson(object: T): string; + apiPath(): string; +} + +export class AbstractApi implements Api { + getAll(serializable: Serializable, callback: (objets: T[]) => void): void { + fetch(`/api/${getSlug()}/${serializable.apiPath()}`) + .then((response) => response.json()) + .then((data) => { + callback(data.map((record: any) => { + return serializable.fromJson(record); + })); + }, (error) => { + return []; + }); + } + + get(serializable: Serializable, id: string, callback: (object: T) => void): void { + fetch(`/api/${getSlug()}/${serializable.apiPath()}/${id}`) + .then((response) => response.json()) + .then((data) => { + callback(serializable.fromJson(data)); + }, (error) => { + return []; + }); + } + + update(serializable: Serializable, object: T, callback: () => void): void { + fetch(`/api/${getSlug()}/${serializable.apiPath()}/${object.id}`, { + method: 'PUT', + body: serializable.toJson(object), + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': getCsrfToken(), + } + }).then(callback) + .catch((error) => console.error(error)); + } + + create(serializable: Serializable, object: T, callback: () => void): void { + fetch(`/api/${getSlug()}/${serializable.apiPath()}`, { + method: 'POST', + body: serializable.toJson(object), + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': getCsrfToken(), + } + }).then(callback) + .catch((error) => console.error(error)); + } + + destroy(serializable: Serializable, object: T, callback: () => void): void { + fetch(`/api/${getSlug()}/${serializable.apiPath()}/${object.id}`, { + method: 'DELETE', + headers: { + 'X-CSRF-TOKEN': getCsrfToken(), + } + }).then(callback) + .catch((error) => console.error(error)); + } +} diff --git a/app/api/authentication.tsx b/app/api/authentication.tsx index 6b57d06..e361260 100644 --- a/app/api/authentication.tsx +++ b/app/api/authentication.tsx @@ -1,10 +1,9 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ -import { getCsrfToken, getSlug } from '@/app/lib/utils'; -import { User } from '@/app/lib/definitions'; +import { asArray, getCsrfToken, getSlug } from '@/app/lib/utils'; +import { Captcha, StructuredErrors, User } from '@/app/lib/definitions'; export function login({ email, password, onLogin }: { email: string, password: string, onLogin: (user: User) => void }) { - console.log(email, password); return fetch(`/api/${getSlug()}/users/sign_in`, { method: 'POST', body: JSON.stringify({ user: { email, password } }), @@ -34,3 +33,54 @@ export function logout({ onLogout }: { onLogout: () => void }) { }).then(onLogout) .catch((error) => console.error(error)); } + +function flattenErrors(errors: StructuredErrors): string[] { + if (errors instanceof Array) { + return errors; + } + return Object.keys(errors).map((key) => { + return `${key}: ${asArray(errors[key]).join(', ')}`; + }); +} + +// At this moment we're making an initial request to get a valid CSRF token +export function retrieveCSRFToken() { + return fetch(`/api/token`, { + headers: { + 'Accept': 'application/json', + } + }).then((response) => { return null }); +} + +export function register({ slug, email, password, passwordConfirmation, captcha, onRegister, onError }: { + slug: string, + email: string, + password: string, + passwordConfirmation: string, + captcha: Captcha, + onRegister: () => void, + onError: (errors: string[]) => void +}) { + fetch(`/api/${slug}/users`, { + method: 'POST', + body: JSON.stringify( + { + user: { email, password, password_confirmation: passwordConfirmation }, + captcha: { id: captcha.id, answer: captcha.answer } + } + ), + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': getCsrfToken(), + } + }).then((response) => { + if (response.ok) { + response.json().then(onRegister); + } else { + response.json().then((data: any) => { + onError(data.errors && flattenErrors(data.errors) || [data.error]); + }); + } + }) +} diff --git a/app/api/captcha.tsx b/app/api/captcha.tsx new file mode 100644 index 0000000..ff6ecb7 --- /dev/null +++ b/app/api/captcha.tsx @@ -0,0 +1,19 @@ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ + +import { data } from "autoprefixer"; +import { getCsrfToken } from "../lib/utils"; + +export function getCaptchaChallenge({onRetrieve}: {onRetrieve: (id: string, url: string) => void}){ + return fetch('/api/captcha', { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'X-CSRF-TOKEN': getCsrfToken(), + } + }) + .then((response) => response.json()) + .then((data: any) => { + onRetrieve(data.id, data.media_url) + }) + .catch((error) => console.error(error)); +} \ No newline at end of file diff --git a/app/api/expenses.tsx b/app/api/expenses.tsx deleted file mode 100644 index a23cd16..0000000 --- a/app/api/expenses.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ - -import { Expense } from '@/app/lib/definitions'; -import { getCsrfToken, getSlug } from '@/app/lib/utils'; - -export function loadExpenses(onLoad?: (expenses: Expense[]) => void) { - fetch(`/api/${getSlug()}/expenses`) - .then((response) => response.json()) - .then((data) => { - onLoad && onLoad(data.map((record: any) => { - return ({ - id: record.id, - name: record.name, - amount: record.amount, - pricingType: record.pricing_type - }); - })); - }, (error) => { - return []; - }); -} - -export function updateExpense(expense: Expense) { - fetch(`/api/${getSlug()}/expenses/${expense.id}`, - { - method: 'PUT', - body: JSON.stringify({ - expense: { - name: expense.name, - amount: expense.amount, - pricing_type: expense.pricingType, - } - }), - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-TOKEN': getCsrfToken(), - } - }) - .catch((error) => console.error(error)); -} \ No newline at end of file diff --git a/app/api/groups.tsx b/app/api/groups.tsx deleted file mode 100644 index 807c19f..0000000 --- a/app/api/groups.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ - -import { Group } from '@/app/lib/definitions'; -import { getSlug } from '../lib/utils'; - -export function loadGroups(onLoad?: (groups: Group[]) => void) { - fetch(`/api/${getSlug()}/groups`) - .then((response) => response.json()) - .then((data) => { - onLoad && onLoad(data.map((record: any) => { - return ({ - id: record.id, - name: record.name, - color: record.color, - attendance: { - considered: record.considered, - invited: record.invited, - confirmed: record.confirmed, - tentative: record.tentative, - declined: record.declined, - total: record.total, - } - }); - })); - }, (error) => { - return []; - }); -} \ No newline at end of file diff --git a/app/api/guests.tsx b/app/api/guests.tsx deleted file mode 100644 index ce4f232..0000000 --- a/app/api/guests.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ - -import { Guest } from '@/app/lib/definitions'; -import { getCsrfToken, getSlug } from '@/app/lib/utils'; - -export function loadGuests(onLoad?: (guests: Guest[]) => void) { - fetch(`/api/${getSlug()}/guests`) - .then((response) => response.json()) - .then((data) => { - onLoad && onLoad(data.map((record: any) => { - return ({ - id: record.id, - name: record.name, - status: record.status, - group_name: record.group.name, - groupId: record.group.id, - }); - })); - }, (error) => { - return []; - }); -}; - -export function updateGuest(guest: Guest) { - return fetch(`/api/${getSlug()}/guests/${guest.id}`, - { - method: 'PUT', - body: JSON.stringify({ guest: { name: guest.name, status: guest.status, group_id: guest.groupId } }), - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-TOKEN': getCsrfToken(), - } - }) - .catch((error) => console.error(error)); -} - -export function createGuest(guest: Guest, onCreate?: () => void) { - fetch(`/api/${getSlug()}/guests`, { - method: 'POST', - body: JSON.stringify({ name: guest.name, group_id: guest.groupId, status: guest.status }), - headers: { - 'Content-Type': 'application/json', - 'X-CSRF-TOKEN': getCsrfToken(), - } - }) - .then((response) => response.json()) - .then((data) => { - onCreate && onCreate(); - }) - .catch((error) => console.error(error)); -} - -export function destroyGuest(guest: Guest, onDestroy?: () => void) { - fetch(`/api/${getSlug()}/guests/${guest.id}`, { - method: 'DELETE', - headers: { - 'X-CSRF-TOKEN': getCsrfToken(), - } - }) - .then((response) => response.json()) - .then((data) => { - onDestroy && onDestroy(); - }) - .catch((error) => console.error(error)); -} \ No newline at end of file diff --git a/app/api/health/route.ts b/app/api/health/route.ts new file mode 100644 index 0000000..f43cbdb --- /dev/null +++ b/app/api/health/route.ts @@ -0,0 +1,7 @@ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ + +import { NextResponse } from "next/server"; + +export function GET() { + return NextResponse.json({}); +} \ No newline at end of file diff --git a/app/api/tableSimulations.tsx b/app/api/tableSimulations.tsx index 5f6d2d9..5b0f538 100644 --- a/app/api/tableSimulations.tsx +++ b/app/api/tableSimulations.tsx @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ import { TableArrangement } from '@/app/lib/definitions'; import { getSlug } from '../lib/utils'; @@ -12,6 +12,7 @@ export function loadTableSimulations(onLoad?: (tableSimulations: TableArrangemen id: record.id, name: record.name, discomfort: record.discomfort, + valid: record.valid, }); })); }, (error) => { diff --git a/app/layout.tsx b/app/layout.tsx index e2cd6af..63ee3fd 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ import '@/app/ui/global.css' diff --git a/app/lib/affinities.tsx b/app/lib/affinities.tsx new file mode 100644 index 0000000..5576f1b --- /dev/null +++ b/app/lib/affinities.tsx @@ -0,0 +1,5 @@ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ + +export class Affinities { + [key:string]: number; +} \ No newline at end of file diff --git a/app/lib/definitions.ts b/app/lib/definitions.ts index 45b5536..5e6ffe8 100644 --- a/app/lib/definitions.ts +++ b/app/lib/definitions.ts @@ -1,67 +1,50 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ -// This file contains type definitions for your data. -// It describes the shape of the data, and what data type each property should accept. -// For simplicity of teaching, we're manually defining these types. -// However, these types are generated automatically if you're using an ORM such as Prisma. +import { AttendanceSummary } from "./group"; +import { Guest } from "./guest"; -export const guestStatuses = ['considered', 'invited', 'confirmed', 'declined', 'tentative'] as const; -export type GuestStatus = typeof guestStatuses[number]; -export type Guest = { +export interface Entity { id?: string; - name?: string; - group_name?: string; - groupId?: string; - color?: string; - status?: GuestStatus } -export type Expense = { - id: string; - name: string; - amount: number; - pricingType: 'fixed' | 'per person'; -}; - export type TableArrangement = { id: string; number: number; name: string; guests?: Guest[]; - discomfort?: number + discomfort?: number; + valid?: boolean; } -export type Group = { - id: string; - name: string; - guest_count: number; - icon: string; - children: Group[]; - color?: string; - attendance?: AttendanceSummary -}; - -export type AttendanceSummary = { - considered: number; - invited: number; - confirmed: number; - declined: number; - tentative: number; - total: number; -} - -export type guestsTable = { - id: string; - customer_id: string; - name: string; - email: string; - image_url: string; - date: string; - amount: number; - status: 'pending' | 'paid'; -}; - export type User = { id: string; email: string; +} + +export type Captcha = { + id: string; + answer: string; +} + +export type StructuredErrors = { + [key: string]: string[] | string; +}; + +export type GlobalSummary = { + expenses: ExpenseSummary; + guests: AttendanceSummary +} + +export type ExpenseSummary = { + projected: ExpensePossibleSummary; + confirmed: ExpensePossibleSummary; + status: StatusSummary; +} + +export type ExpensePossibleSummary = { + total: number; + guests: number; +} +export type StatusSummary = { + paid: number; } \ No newline at end of file diff --git a/app/lib/expense.tsx b/app/lib/expense.tsx new file mode 100644 index 0000000..b024e1b --- /dev/null +++ b/app/lib/expense.tsx @@ -0,0 +1,41 @@ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ + +import { Serializable } from "../api/abstract-api"; +import { Entity } from "./definitions"; + +export const pricingTypes = ['fixed', 'per_person'] as const; +export type PricingType = typeof pricingTypes[number]; + +export class Expense implements Entity { + id?: string; + name?: string; + amount?: number; + pricingType?: PricingType; + + constructor(id?: string, name?: string, amount?: number, pricingType?: PricingType) { + this.id = id; + this.name = name || ''; + this.amount = amount || 0; + this.pricingType = pricingType || 'fixed'; + } +} + +export class ExpenseSerializer implements Serializable{ + fromJson(data: any): Expense { + return new Expense(data.id, data.name, data.amount, data.pricing_type); + } + + toJson(expense: Expense): string { + return JSON.stringify({ + expense: { + name: expense.name, + amount: expense.amount, + pricing_type: expense.pricingType + } + }); + } + + apiPath(): string { + return 'expenses'; + } +} \ No newline at end of file diff --git a/app/lib/group.tsx b/app/lib/group.tsx new file mode 100644 index 0000000..aae5d18 --- /dev/null +++ b/app/lib/group.tsx @@ -0,0 +1,64 @@ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ + +import { Entity } from "./definitions"; + +export type AttendanceSummary = { + considered: number; + invited: number; + confirmed: number; + declined: number; + tentative: number; + total: number; +} + +export class Group implements Entity { + id?: string; + name?: string; + guest_count?: number; + icon?: string; + children?: Group[]; + parentId?: string; + color?: string; + attendance?: AttendanceSummary + + constructor(id?: string, name?: string, guest_count?: number, icon?: string, children?: Group[], parentId?: string, color?: string, attendance?: AttendanceSummary) { + this.id = id; + this.name = name; + this.guest_count = guest_count; + this.icon = icon; + this.children = children; + this.parentId = parentId; + this.color = color; + this.attendance = attendance; + } +} + +export class GroupSerializer { + fromJson(data: any): Group { + return new Group( + data.id, + data.name, + data.guest_count, + data.icon, + data.children, + data.parent_id, + data.color, + data.attendance + ); + } + + toJson(group: Group): string { + return JSON.stringify({ + group: { + name: group.name, + color: group.color, + icon: group.icon, + parent_id: group.parentId + } + }); + } + + apiPath(): string { + return 'groups'; + } +} \ No newline at end of file diff --git a/app/lib/guest.tsx b/app/lib/guest.tsx new file mode 100644 index 0000000..80fcfb7 --- /dev/null +++ b/app/lib/guest.tsx @@ -0,0 +1,42 @@ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ + +import { Serializable } from "../api/abstract-api"; +import { Entity } from "./definitions"; + +export const guestStatuses = ['considered', 'invited', 'confirmed', 'declined', 'tentative'] as const; +export type GuestStatus = typeof guestStatuses[number]; + +export class Guest implements Entity { + id?: string; + name?: string; + group_name?: string; + groupId?: string; + color?: string; + status?: GuestStatus; + children?: Guest[]; + + constructor(id?: string, name?: string, group_name?: string, groupId?: string, color?: string, status?: GuestStatus, children?: Guest[]) { + this.id = id; + this.name = name; + this.group_name = group_name; + this.groupId = groupId; + this.color = color; + this.status = status; + this.children = children; + } +} + +export class GuestSerializer implements Serializable { + fromJson(data: any): Guest { + return new Guest(data.id, data.name, data.group?.name, data.group?.id, data.color, data.status, data.children); + } + + toJson(guest: Guest): string { + return JSON.stringify({ guest: { name: guest.name, status: guest.status, group_id: guest.groupId } }); + } + + apiPath(): string { + return 'guests'; + } +} + diff --git a/app/lib/tableSimulation.tsx b/app/lib/tableSimulation.tsx new file mode 100644 index 0000000..7a01b4d --- /dev/null +++ b/app/lib/tableSimulation.tsx @@ -0,0 +1,71 @@ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ + +import { Serializable } from "../api/abstract-api"; +import { Entity } from "./definitions"; +import { Guest } from "./guest"; + +export type Discomfort = { + discomfort: number; + breakdown: { + tableSizePenalty: number; + cohesionPenalty: number; + } +} + +export type Table = { + number: number; + guests: Guest[]; + discomfort: Discomfort; +} + +export class TableSimulation implements Entity { + id?: string; + tables: Table[]; + + constructor(id?: string, tables?: Table[]) { + this.id = id; + this.tables = tables || []; + } +} + +export class TableSimulationSerializer implements Serializable { + fromJson(data: any): TableSimulation { + return new TableSimulation(data.id, data.tables.map((table: any) => { + return { + number: table.number, + guests: table.guests.map((guest: any) => new Guest(guest.id, guest.name, guest.group?.name, guest.group?.id, guest.color)), + discomfort: { + discomfort: table.discomfort.discomfort, + breakdown: { + tableSizePenalty: table.discomfort.breakdown.table_size_penalty, + cohesionPenalty: table.discomfort.breakdown.cohesion_penalty, + } + }, + } + })); + } + + toJson(simulation: TableSimulation): string { + return JSON.stringify({ simulation: { tables: simulation.tables.map((table) => { + return { + number: table.number, + guests: table.guests.map((guest) => { + return { + id: guest.id, + name: guest.name, + group_id: guest.groupId, + color: guest.color, + status: guest.status, + children: guest.children, + } + }), + discomfort: table.discomfort, + } + }) } }); + } + + apiPath(): string { + return 'tables_arrangements'; + } +} + diff --git a/app/lib/utils.ts b/app/lib/utils.ts index 069744a..c590751 100644 --- a/app/lib/utils.ts +++ b/app/lib/utils.ts @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ export const getCsrfToken = () => { return document.cookie @@ -13,3 +13,8 @@ export const getSlug = () => localStorage.getItem('slug') || 'default'; export const capitalize = (val:string) => { return String(val).charAt(0).toUpperCase() + String(val).slice(1); } + +// From https://stackoverflow.com/a/62118163/3607039 +export function asArray(value: T | T[]): T[] { + return ([] as T[]).concat(value) +} \ No newline at end of file diff --git a/app/types.tsx b/app/types.tsx index 283e371..5c95b52 100644 --- a/app/types.tsx +++ b/app/types.tsx @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ import * as HeroIcon from '@heroicons/react/24/outline' import { ComponentProps } from 'react' diff --git a/app/ui/arrangements/arrangement.tsx b/app/ui/arrangements/arrangement.tsx index 749c577..d8bf0b1 100644 --- a/app/ui/arrangements/arrangement.tsx +++ b/app/ui/arrangements/arrangement.tsx @@ -1,44 +1,38 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ 'use client'; -import React, { useState } from 'react'; -import { TableArrangement, Guest } from '@/app/lib/definitions'; -import { lusitana } from '@/app/ui/fonts'; +import { AbstractApi } from '@/app/api/abstract-api'; +import { TableArrangement } from '@/app/lib/definitions'; +import { TableSimulation, TableSimulationSerializer } from '@/app/lib/tableSimulation'; +import { getSlug } from '@/app/lib/utils'; import { Table } from '@/app/ui/components/table'; +import { lusitana } from '@/app/ui/fonts'; +import { useState, useEffect } from 'react'; export default function Arrangement({ id }: { id: string }) { - const [tables, setTables] = useState>([]); + const [simulation, setSimulation] = useState(undefined); - function loadTables() { - fetch(`/api/tables_arrangements/${id}`) - .then((response) => response.json()) - .then((data) => { - setTables(data.map((record: any) => { - return ({ - id: record.number, - guests: record.guests - }); - })); - }, (error) => { - return []; - }); - } + function loadSimulation() { + new AbstractApi().get(new TableSimulationSerializer(), id, (object: TableSimulation) => { + setSimulation(object); + }); + } - tables.length === 0 && loadTables(); + useEffect(loadSimulation, []); - return ( -
-
-

Table distributions

-
- {tables.map((table) => ( - - ))} - - + return ( +
+
+

Table distributions

+
+ {simulation && simulation.tables.map((table) => ( +
+ ))} - ) + + + ) } \ No newline at end of file diff --git a/app/ui/arrangements/arrangements-table.tsx b/app/ui/arrangements/arrangements-table.tsx index 57dd67d..07a3eff 100644 --- a/app/ui/arrangements/arrangements-table.tsx +++ b/app/ui/arrangements/arrangements-table.tsx @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ 'use client' @@ -7,42 +7,57 @@ import { TableArrangement } from '@/app/lib/definitions'; import { classNames } from "../components/button"; import TableOfContents from "../components/table-of-contents"; import { loadTableSimulations } from "@/app/api/tableSimulations"; +import { ArchiveBoxXMarkIcon, CheckBadgeIcon } from "@heroicons/react/24/outline"; +import { Tooltip } from "primereact/tooltip"; +import clsx from "clsx"; -export default function ArrangementsTable ({onArrangementSelected}: {onArrangementSelected: (arrangementId: string) => void}) { - const [arrangements, setArrangements] = useState>([]); - const [arrangementsLoaded, setArrangementsLoaded] = useState(false); +export default function ArrangementsTable({ onArrangementSelected }: { onArrangementSelected: (arrangementId: string) => void }) { + const [arrangements, setArrangements] = useState>([]); + const [arrangementsLoaded, setArrangementsLoaded] = useState(false); - function refreshSimulations() { - loadTableSimulations((arrangements) => { - setArrangements(arrangements); - setArrangementsLoaded(true); - }); - } + function refreshSimulations() { + loadTableSimulations((arrangements) => { + setArrangements(arrangements); + setArrangementsLoaded(true); + }); + } - function arrangementClicked(e: React.MouseEvent) { - onArrangementSelected(e.currentTarget.getAttribute('data-arrangement-id') || ''); - } - - !arrangementsLoaded && refreshSimulations(); + function arrangementClicked(e: React.MouseEvent) { + onArrangementSelected(e.currentTarget.getAttribute('data-arrangement-id') || ''); + } - return( - ( - - - - - - )} - /> - ); + !arrangementsLoaded && refreshSimulations(); + + return ( + ( + + + + + + + )} + /> + ); } \ No newline at end of file diff --git a/app/ui/button.tsx b/app/ui/button.tsx index d30b980..1f1034f 100644 --- a/app/ui/button.tsx +++ b/app/ui/button.tsx @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ import clsx from 'clsx'; diff --git a/app/ui/components/affinities-form-dialog.tsx b/app/ui/components/affinities-form-dialog.tsx new file mode 100644 index 0000000..dc8bc72 --- /dev/null +++ b/app/ui/components/affinities-form-dialog.tsx @@ -0,0 +1,82 @@ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ + +'use client'; + +import { Affinities } from '@/app/lib/affinities'; +import { Group } from '@/app/lib/group'; +import { getCsrfToken, getSlug } from '@/app/lib/utils'; +import { classNames } from '@/app/ui/components/button'; +import { Dialog } from 'primereact/dialog'; +import { useEffect, useState } from 'react'; +import AffinitySlider from './form/affinitySlider'; + +export default function AffinitiesFormDialog({ groups, group, visible, onHide }: { + groups: Group[], + group?: Group, + visible: boolean, + onHide: () => void, +}) { + const [affinities, setAffinities] = useState({}); + const [isLoadingAffinities, setIsLoadingAffinities] = useState(true); + + useEffect(() => { + setIsLoadingAffinities(true); + if (group?.id === undefined) { + setAffinities({}); + setIsLoadingAffinities(false) + } else { + fetch(`/api/${getSlug()}/groups/${group?.id}/affinities`) + .then((response) => response.json()) + .then((data) => { + setAffinities(data); + setIsLoadingAffinities(false) + }); + } + }, [group]); + + function submitAffinities() { + const formattedAffinities = Object.entries(affinities).map(([groupId, affinity]) => ({ group_id: groupId, affinity: affinity })); + fetch(`/api/${getSlug()}/groups/${group?.id}/affinities/bulk_update`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': getCsrfToken(), + }, + body: JSON.stringify({ affinities: formattedAffinities }) + }).then(() => { + onHide(); + }); + } + + function resetAffinities() { + fetch(`/api/${getSlug()}/groups/${group?.id}/affinities/default`, { + method: 'GET', + headers: { + 'Accept': 'application/json', + } + }).then((response) => response.json()) + .then(setAffinities); + } + + return ( + + {!isLoadingAffinities &&
+ Describe the affinity with the rest of the groups + + { + groups.filter((currentGroup) => currentGroup.id !== group?.id).map((group) => +
+ {group.name} + setAffinities({ ...affinities, [group.id || "default"]: value })} /> +
) + } + +
+ + +
+
+ } +
+ ); +} \ No newline at end of file diff --git a/app/ui/components/button.tsx b/app/ui/components/button.tsx index 6b65ba0..2ecfabb 100644 --- a/app/ui/components/button.tsx +++ b/app/ui/components/button.tsx @@ -1,14 +1,15 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ import clsx from "clsx"; -type ButtonColor = 'primary' | 'blue' | 'green' | 'red' | 'yellow'; +type ButtonColor = 'primary' | 'blue' | 'green' | 'red' | 'yellow' | 'gray'; export function classNames(type: ButtonColor) { return (clsx("text-white py-1 px-2 mx-1 rounded disabled:opacity-50 disabled:cursor-not-allowed", { 'bg-blue-400 hover:bg-blue-600': type === 'primary' || type === 'blue', 'bg-green-500 hover:bg-green-600': type === 'green', 'bg-red-500 hover:bg-red-600': type === 'red', - 'bg-yellow-500 hover:bg-yellow-700': type === 'yellow' + 'bg-yellow-500 hover:bg-yellow-700': type === 'yellow', + 'bg-gray-500 hover:bg-gray-700': type === 'gray' })) } \ No newline at end of file diff --git a/app/ui/components/dashboard-cards.tsx b/app/ui/components/dashboard-cards.tsx index 6c72ac4..26c7bf7 100644 --- a/app/ui/components/dashboard-cards.tsx +++ b/app/ui/components/dashboard-cards.tsx @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ import clsx from "clsx" import { Icon } from "../../types"; @@ -21,7 +21,7 @@ const colorClasses = (style: Style) => { } } -export async function MainCard({ amount, title, subtitle, style, iconName }: +export function MainCard({ amount, title, subtitle, style, iconName }: { amount: string, title: string, @@ -42,7 +42,7 @@ export async function MainCard({ amount, title, subtitle, style, iconName }: ); } -export async function SecondaryCard({ amount, title, iconName, style }: { amount: string, title: string, iconName: keyof typeof HeroIcon, style: Style }) { +export function SecondaryCard({ amount, title, iconName, style }: { amount: string, title: string, iconName: keyof typeof HeroIcon, style: Style }) { return (
diff --git a/app/ui/components/expense-form-dialog.tsx b/app/ui/components/expense-form-dialog.tsx new file mode 100644 index 0000000..18c7299 --- /dev/null +++ b/app/ui/components/expense-form-dialog.tsx @@ -0,0 +1,83 @@ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ + +'use client'; + +import { AbstractApi } from '@/app/api/abstract-api'; +import { Expense, ExpenseSerializer, PricingType, pricingTypes } from '@/app/lib/expense'; +import { capitalize } from '@/app/lib/utils'; +import { classNames } from '@/app/ui/components/button'; +import { Dialog } from 'primereact/dialog'; +import { Dropdown } from 'primereact/dropdown'; +import { FloatLabel } from 'primereact/floatlabel'; +import { InputText } from 'primereact/inputtext'; +import { useState } from 'react'; + +export default function ExpenseFormDialog({ onCreate, onHide, expense, visible }: { + onCreate?: () => void, + onHide: () => void, + expense?: Expense, + visible: boolean, +}) { + + const [name, setName] = useState(expense?.name || ''); + const [amount, setAmount] = useState(expense?.amount || 0); + const [pricingType, setPricingType] = useState(expense?.pricingType || 'fixed'); + + const api = new AbstractApi(); + const serializer = new ExpenseSerializer(); + + function resetForm() { + setName(''); + setAmount(0); + setPricingType('fixed'); + } + + function submitGroup() { + if (expense?.id !== undefined) { + expense.name = name; + expense.amount = amount; + expense.pricingType = pricingType; + + api.update(serializer, expense, () => { + resetForm(); + onCreate && onCreate(); + }); + } else { + + api.create(serializer, new Expense(undefined, name, amount, pricingType), () => { + resetForm(); + onCreate && onCreate(); + }); + } + } + + return ( + <> + + +
+ + setName(e.target.value)} /> + + + + setAmount(parseFloat(e.target.value))} /> + + + + setPricingType(e.target.value)} options={ + pricingTypes.map((type) => { + return { label: capitalize(type), value: type }; + }) + } /> + + + + +
+
+ + ); +} \ No newline at end of file diff --git a/app/ui/components/form/affinitySlider.tsx b/app/ui/components/form/affinitySlider.tsx new file mode 100644 index 0000000..99f7ce6 --- /dev/null +++ b/app/ui/components/form/affinitySlider.tsx @@ -0,0 +1,40 @@ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ + +import { Slider } from 'primereact/slider'; + +export default function AffinitySlider({ value, onChange }: { value: number, onChange: (value: number) => void }) { + + const toNumber = (value : number | [number, number]) => { + if(value instanceof Array) { + return value[0]; + } + return value; + } + + const label = (value: number) => { + if (value < 0.2) { + return 'Nemesis'; + } else if (value < 0.5) { + return 'Enemies'; + } else if (value < 0.9) { + return 'Bad vibes'; + } else if (value < 1.1) { + return 'Neutral'; + } else if (value < 1.5) { + return 'Good vibes'; + } else if (value < 1.8) { + return 'Good friends'; + } else { + return 'Besties'; + } + } + + return ( + <> + onChange(toNumber(e.value))} className='w-80 bg-gray-400' /> + + {label(value)} + + + ) +} \ No newline at end of file diff --git a/app/ui/components/form/inlineTextField.tsx b/app/ui/components/form/inlineTextField.tsx index fad7f57..8e07935 100644 --- a/app/ui/components/form/inlineTextField.tsx +++ b/app/ui/components/form/inlineTextField.tsx @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ import React, { useState } from 'react'; diff --git a/app/ui/components/group-form-dialog.tsx b/app/ui/components/group-form-dialog.tsx new file mode 100644 index 0000000..03d5c92 --- /dev/null +++ b/app/ui/components/group-form-dialog.tsx @@ -0,0 +1,95 @@ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ + +'use client'; + +import { AbstractApi } from '@/app/api/abstract-api'; +import { Group, GroupSerializer } from '@/app/lib/group'; +import { classNames } from '@/app/ui/components/button'; +import { ColorPicker } from 'primereact/colorpicker'; +import { Dialog } from 'primereact/dialog'; +import { Dropdown } from 'primereact/dropdown'; +import { FloatLabel } from 'primereact/floatlabel'; +import { InputText } from 'primereact/inputtext'; +import { useState } from 'react'; + +export default function GroupFormDialog({ groups, onCreate, onHide, group, visible }: { + groups: Group[], + onCreate?: () => void, + onHide: () => void, + group?: Group, + visible: boolean, +}) { + + const [name, setName] = useState(group?.name || ''); + const [icon, setIcon] = useState(group?.icon || ''); + const [color, setColor] = useState(group?.color || ''); + const [parentId, setParentId] = useState(group?.parentId || ''); + + const api = new AbstractApi(); + const serializer = new GroupSerializer(); + + function resetForm() { + setName(''); + setIcon(''); + setColor(''); + setParentId(''); + } + + function submitGroup() { + if (!(name)) { + return + } + + if (group?.id !== undefined) { + group.name = name; + group.icon = icon; + group.color = color; + group.parentId = parentId; + + api.update(serializer, group, () => { + resetForm(); + onCreate && onCreate(); + }); + } else { + + api.create(serializer, new Group(undefined, name, undefined, icon, undefined, parentId, color), () => { + resetForm(); + onCreate && onCreate(); + }); + } + } + + return ( + <> + + +
+ + setName(e.target.value)} /> + + + + setIcon(e.target.value)} /> + + + + setColor(`#${e.value}`)} /> + + + setParentId(e.target.value)} options={ + groups.map((group) => { + return { label: group.name, value: group.id }; + }) + } /> + + + + +
+
+ + ); +} \ No newline at end of file diff --git a/app/ui/components/guest-form-dialog.tsx b/app/ui/components/guest-form-dialog.tsx index 9da1ff9..c0b20e0 100644 --- a/app/ui/components/guest-form-dialog.tsx +++ b/app/ui/components/guest-form-dialog.tsx @@ -1,9 +1,10 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ 'use client'; -import { createGuest, updateGuest } from '@/app/api/guests'; -import { Group, Guest, GuestStatus, guestStatuses } from '@/app/lib/definitions'; +import { AbstractApi } from '@/app/api/abstract-api'; +import { Group } from '@/app/lib/group'; +import { Guest, GuestSerializer, GuestStatus, guestStatuses } from '@/app/lib/guest'; import { capitalize } from '@/app/lib/utils'; import { classNames } from '@/app/ui/components/button'; import { Dialog } from 'primereact/dialog'; @@ -24,6 +25,9 @@ export default function GuestFormDialog({ groups, onCreate, onHide, guest, visib const [group, setGroup] = useState(guest?.groupId || null); const [status, setStatus] = useState(guest?.status || null); + const api = new AbstractApi(); + const serializer = new GuestSerializer(); + function resetForm() { setName(''); setGroup(null); @@ -37,14 +41,15 @@ export default function GuestFormDialog({ groups, onCreate, onHide, guest, visib if (guest?.id !== undefined) { guest.name = name; - guest.groupId = group; + guest.groupId = group; guest.status = status; - updateGuest(guest).then(() => { + + api.update(serializer, guest, () => { resetForm(); onCreate && onCreate(); }); } else { - guest && createGuest({name: name, groupId: group, status: status}, () => { + api.create(serializer, new Guest(undefined, name, undefined, group, undefined, status), ()=> { resetForm(); onCreate && onCreate(); }); @@ -54,7 +59,7 @@ export default function GuestFormDialog({ groups, onCreate, onHide, guest, visib return ( <> - +
setName(e.target.value)} /> diff --git a/app/ui/components/login-form.tsx b/app/ui/components/login-form.tsx index 63ad65d..7b808c2 100644 --- a/app/ui/components/login-form.tsx +++ b/app/ui/components/login-form.tsx @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ 'use client'; diff --git a/app/ui/components/registration-form.tsx b/app/ui/components/registration-form.tsx new file mode 100644 index 0000000..3de63ea --- /dev/null +++ b/app/ui/components/registration-form.tsx @@ -0,0 +1,98 @@ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ + +'use client'; + +import { FloatLabel } from 'primereact/floatlabel'; +import { InputText } from 'primereact/inputtext'; +import { useState, useEffect } from 'react'; +import { classNames } from './button'; +import { getSlug } from '@/app/lib/utils'; +import { register } from '@/app/api/authentication'; +import { getCaptchaChallenge } from '@/app/api/captcha'; + +export default function RegistrationForm() { + const [submitted, setSubmitted] = useState(false); + const [errors, setErrors] = useState([]); + + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [passwordConfirmation, setPasswordConfirmation] = useState(""); + const [slug, setSlug] = useState(getSlug()); + + const [captchaId, setCaptchaId] = useState(""); + const [captchaUrl, setCaptchaUrl] = useState(""); + const [captchaAnswer, setCaptchaAnswer] = useState(""); + + const refreshCaptcha = () => { + getCaptchaChallenge({ + onRetrieve: (id, url) => { + console.log(id, url); + setCaptchaId(id); + setCaptchaUrl(url); + setCaptchaAnswer(""); + } + }); + } + + useEffect(refreshCaptcha, []) + + return ( + submitted ? ( +
+
Registration successful. Check your email for a confirmation link.
+
+ ) : ( + +
+ + setEmail(e.target.value)} /> + + + + setPassword(e.target.value)} /> + + + + setPasswordConfirmation(e.target.value)} /> + + + + setSlug(e.target.value)} /> + + + captcha + + setCaptchaAnswer(e.target.value)} /> + + + + {errors.map((error, index) => ( +
{error}
+ ))} + + + +
+ ) + + ); +} \ No newline at end of file diff --git a/app/ui/components/table-of-contents.tsx b/app/ui/components/table-of-contents.tsx index 70c39e2..410f7b6 100644 --- a/app/ui/components/table-of-contents.tsx +++ b/app/ui/components/table-of-contents.tsx @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ export default function TableOfContents({ headers, caption, elements, rowRender }: { headers: string[], caption: string, elements: Type[], rowRender: (element: Type) => JSX.Element }) { return ( diff --git a/app/ui/components/table.tsx b/app/ui/components/table.tsx index 9c1098d..ec8a999 100644 --- a/app/ui/components/table.tsx +++ b/app/ui/components/table.tsx @@ -1,68 +1,107 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ + +import { Guest } from "@/app/lib/guest"; +import { Table as TableType } from "@/app/lib/tableSimulation"; +import { RectangleGroupIcon, UserGroupIcon } from "@heroicons/react/24/outline"; +import { v4 as uuidv4 } from 'uuid'; +import { Tooltip } from "primereact/tooltip"; +import clsx from "clsx"; -import { Guest } from "@/app/lib/definitions"; function Dish({ guest, rotation }: { guest: Guest, rotation?: number }) { - rotation = rotation || 0 - return ( -
- {guest.name} -
- ) + rotation = rotation || 0 + return ( +
+ {guest.name} +
+ ) } - function GuestRow({ guests }: { guests: Guest[] }) { - return ( -
- {guests.map((guest) => )} -
- ) + return ( +
+ {guests.map((guest) => )} +
+ ) } -function RectangularTable({ guests }: { guests: Guest[] }) { - const halfwayThrough = Math.floor(guests.length / 2) - const arrayFirstHalf = guests.slice(0, halfwayThrough); - const arraySecondHalf = guests.slice(halfwayThrough, guests.length); +function RectangularTable({ table }: { table: TableType }) { + const guests = table.guests; + const halfwayThrough = Math.floor(guests.length / 2) + const arrayFirstHalf = guests.slice(0, halfwayThrough); + const arraySecondHalf = guests.slice(halfwayThrough, guests.length); - return ( -
- - -
- ) + return ( +
+ + +
+ ) } -function RoundedTable({ guests }: { guests: Guest[] }) { - const size = 500 - const rotation = 360 / guests.length - return ( -
- { - guests.map((guest, index) => { - return ( -
- -
- ) - }) - } -
- ) +function RoundedTable({ table }: { table: TableType }) { + const guests = table.guests; + const size = 500 + const rotation = 360 / guests.length + + const className = (penalty: number) => { + return clsx("px-2 tooltip-cohesion", { + "hidden": penalty === 0, + "text-orange-300": penalty <= 5, + "text-orange-500": penalty > 5 && penalty <= 10, + "text-orange-700": penalty > 10, + }) + } + + + return ( +
+ + { + guests.map((guest, index) => { + return ( +
+ +
+ ) + }) + } + +
+
{`Table #${table.number}`}
+ + + + + + + + +
+
+ ) } -export function Table({ guests, style }: { guests: Guest[], style: "rectangular" | "rounded" }) { - return ( - <> - {style === "rectangular" && } - {style === "rounded" && } - - ) +export function Table({ table, style }: { table: TableType, style: "rectangular" | "rounded" }) { + return ( + <> + {style === "rectangular" && } + {style === "rounded" && } + + ) } \ No newline at end of file diff --git a/app/ui/dashboard/global-summary.tsx b/app/ui/dashboard/global-summary.tsx index 9b00359..810a95b 100644 --- a/app/ui/dashboard/global-summary.tsx +++ b/app/ui/dashboard/global-summary.tsx @@ -1,45 +1,41 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ +import { GlobalSummary as Summary} from '@/app/lib/definitions'; import { MainCard, SecondaryCard } from '../components/dashboard-cards'; -export default async function GlobalSummary() { - return ( -
- -
- - -
- - -
-
- - -
- -
- -
- - -
- - -
- -
- - - -
-
- -
- - - -
+export default function GlobalSummary({ summary }: { summary: Summary }) { + return ( +
+
+
+ +
- ); +
+ + + +
+
+ + +
+ +
+ +
+ + +
+ + +
+ +
+ + +
+
+
+ ); } \ No newline at end of file diff --git a/app/ui/dashboard/loading.tsx b/app/ui/dashboard/loading.tsx index 2614837..ca17e8b 100644 --- a/app/ui/dashboard/loading.tsx +++ b/app/ui/dashboard/loading.tsx @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ export default function Loading() { return
Loading...
; diff --git a/app/ui/dashboard/nav-links.tsx b/app/ui/dashboard/nav-links.tsx index 0b881dc..ef3e4ea 100644 --- a/app/ui/dashboard/nav-links.tsx +++ b/app/ui/dashboard/nav-links.tsx @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ 'use client' diff --git a/app/ui/dashboard/sidenav.tsx b/app/ui/dashboard/sidenav.tsx index ec4e61c..fbba770 100644 --- a/app/ui/dashboard/sidenav.tsx +++ b/app/ui/dashboard/sidenav.tsx @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ 'use client'; @@ -17,7 +17,7 @@ export default function SideNav() {

Wedding Planner

diff --git a/app/ui/expenses/table.tsx b/app/ui/expenses/table.tsx index 121990c..a90abbd 100644 --- a/app/ui/expenses/table.tsx +++ b/app/ui/expenses/table.tsx @@ -1,42 +1,44 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ 'use client' -import { loadExpenses, updateExpense } from '@/app/api/expenses'; -import { Expense } from '@/app/lib/definitions'; -import { useState } from "react"; -import InlineTextField from "../components/form/inlineTextField"; +import { AbstractApi } from '@/app/api/abstract-api'; +import { Expense, ExpenseSerializer } from '@/app/lib/expense'; +import { PencilIcon, TrashIcon } from '@heroicons/react/24/outline'; import TableOfContents from "../components/table-of-contents"; -export default function ExpensesTable() { - const [expenses, setExpenses] = useState>([]); - const [expensesLoaded, setExpensesLoaded] = useState(false); - function refreshExpenses() { - loadExpenses((expenses) => { - setExpenses(expenses); - setExpensesLoaded(true); - }); - } +export default function ExpensesTable({ expenses, onUpdate, onEdit }: { + expenses: Expense[], + onUpdate: () => void, + onEdit: (expense: Expense) => void, +}) { - !expensesLoaded && refreshExpenses(); + const api = new AbstractApi(); + const serializer = new ExpenseSerializer(); return ( (
+ )} /> diff --git a/app/ui/fonts.ts b/app/ui/fonts.ts index e520f6f..f32d5f9 100644 --- a/app/ui/fonts.ts +++ b/app/ui/fonts.ts @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ import { Inter, Lusitana, Gloria_Hallelujah} from 'next/font/google'; diff --git a/app/ui/groups/table.tsx b/app/ui/groups/table.tsx index 354c229..d22909c 100644 --- a/app/ui/groups/table.tsx +++ b/app/ui/groups/table.tsx @@ -1,45 +1,105 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ 'use client'; -import { Group } from '@/app/lib/definitions'; -import TableOfContents from '../components/table-of-contents'; +import { AbstractApi } from '@/app/api/abstract-api'; +import { Group, GroupSerializer } from '@/app/lib/group'; +import { AdjustmentsHorizontalIcon, PencilIcon, TrashIcon } from '@heroicons/react/24/outline'; +import { Column } from 'primereact/column'; +import { TreeNode } from 'primereact/treenode'; +import { TreeTable } from 'primereact/treetable'; -export default function GroupsTable({ groups }: { groups: Group[] }) { +export default function GroupsTable({ groups, onUpdate, onEdit, onEditAffinities }: { + groups: Group[], + onUpdate: () => void, + onEdit: (group: Group) => void, + onEditAffinities: (group: Group) => void, +}) { + + const api = new AbstractApi(); + const serializer = new GroupSerializer(); + + const actions = (group: Group) => ( +
+ { api.destroy(serializer, group, onUpdate) }} /> + onEdit(group)} /> + onEditAffinities(group)} /> +
+ ); + + const index = groups.reduce((acc, group) => { + if (group.id) { + acc.set(group.id, group) + } + + return acc; + }, new Map()); + + groups.forEach(group => group.children = []); + groups.forEach(group => { + if (group.parentId) { + const parent = index.get(group.parentId); + if (parent) { + if (!parent.children) { + parent.children = []; + } + parent.children.push(group); + } + } + }); + + const renderTree = (group: Group): TreeNode => { + const childrenAttendance = (group.children || []).reduce((acc, child) => { + acc.confirmed += child.attendance?.confirmed || 0; + acc.tentative += child.attendance?.tentative || 0; + acc.invited += child.attendance?.invited || 0; + acc.declined += child.attendance?.declined || 0; + acc.considered += child.attendance?.considered || 0; + acc.total += child.attendance?.total || 0; + return acc; + }, { confirmed: 0, tentative: 0, invited: 0, declined: 0, considered: 0, total: 0 }); + + return { + id: group.id, + key: group.id, + label: group.name, + data: { + name: group.name, + color:
, + confirmed: childrenAttendance.confirmed + (group.attendance?.confirmed || 0), + tentative: childrenAttendance.tentative + (group.attendance?.tentative || 0), + pending: childrenAttendance.invited + (group.attendance?.invited || 0), + declined: childrenAttendance.declined + (group.attendance?.declined || 0), + considered: childrenAttendance.considered + (group.attendance?.considered || 0), + total: childrenAttendance.total + (group.attendance?.total || 0), + actions: actions(group), + }, + children: group.children?.map(renderTree), + } + } + + const nodes: TreeNode[] = groups + .filter(group => !group.parentId) + .map(renderTree) + + const headers = ['Name', 'Color', 'Confirmed', 'Tentative', 'Pending', 'Declined', 'Considered', 'Total', 'Actions']; + const rowClassName = () => { + return { 'border-b odd:bg-white even:bg-gray-50 hover:bg-gray-100': true }; + } return ( - ( -
- - - - - - - - - - )} - /> + <> + + + + + + + + + + + + ) } diff --git a/app/ui/guests/skeleton-row.tsx b/app/ui/guests/skeleton-row.tsx index 87de2cc..3a46273 100644 --- a/app/ui/guests/skeleton-row.tsx +++ b/app/ui/guests/skeleton-row.tsx @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ import Skeleton from '@/app/ui/skeleton'; diff --git a/app/ui/guests/table.tsx b/app/ui/guests/table.tsx index 304c357..93972d0 100644 --- a/app/ui/guests/table.tsx +++ b/app/ui/guests/table.tsx @@ -1,10 +1,9 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ 'use client'; -import { destroyGuest, updateGuest } from '@/app/api/guests'; -import { Guest, GuestStatus } from '@/app/lib/definitions'; -import { classNames } from '@/app/ui/components/button'; +import { AbstractApi } from '@/app/api/abstract-api'; +import { Guest , GuestSerializer} from '@/app/lib/guest'; import { PencilIcon, TrashIcon } from '@heroicons/react/24/outline'; import clsx from 'clsx'; import TableOfContents from '../components/table-of-contents'; @@ -14,6 +13,10 @@ export default function guestsTable({ guests, onUpdate, onEdit }: { onUpdate: () => void, onEdit: (guest: Guest) => void }) { + + const api = new AbstractApi(); + const serializer = new GuestSerializer(); + return ( diff --git a/app/ui/search.tsx b/app/ui/search.tsx index b50d837..6ac5d51 100644 --- a/app/ui/search.tsx +++ b/app/ui/search.tsx @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ 'use client'; diff --git a/app/ui/skeleton.tsx b/app/ui/skeleton.tsx index 1a2bdc8..4c64e31 100644 --- a/app/ui/skeleton.tsx +++ b/app/ui/skeleton.tsx @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ +/* Copyright (C) 2024-2025 LibreWeddingPlanner contributors*/ export default function Skeleton({ className }: { className: string }) { return
; diff --git a/package.json b/package.json index 41ea854..b837c0a 100644 --- a/package.json +++ b/package.json @@ -8,30 +8,31 @@ "dependencies": { "@heroicons/react": "^2.1.4", "@tailwindcss/forms": "^0.5.7", - "autoprefixer": "10.4.20", + "autoprefixer": "10.4.21", "bcrypt": "^5.1.1", "clsx": "^2.1.1", - "next": "15.0.3", - "next-auth": "5.0.0-beta.25", - "postcss": "8.4.49", + "next": "15.3.3", + "next-auth": "5.0.0-beta.28", + "postcss": "8.5.3", "primeicons": "^7.0.0", "primereact": "^10.8.2", "react": "19.0.0-rc-f38c22b244-20240704", "react-dom": "19.0.0-rc-f38c22b244-20240704", - "tailwindcss": "3.4.15", - "typescript": "5.7.2", + "tailwindcss": "3.4.17", + "typescript": "5.8.3", "use-debounce": "^10.0.1", + "uuid": "11.1.0", "zod": "^3.23.8" }, "devDependencies": { "@playwright/test": "^1.46.0", "@types/bcrypt": "^5.0.2", - "@types/node": "22.10.1", - "@types/react": "18.3.12", - "@types/react-dom": "18.3.1" + "@types/node": "22.15.29", + "@types/react": "18.3.23", + "@types/react-dom": "18.3.7" }, "engines": { "node": ">=23.0.0" }, - "packageManager": "pnpm@9.14.4+sha512.c8180b3fbe4e4bca02c94234717896b5529740a6cbadf19fa78254270403ea2f27d4e1d46a08a0f56c89b63dc8ebfd3ee53326da720273794e6200fcf0d184ab" + "packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f249b78..f744bc0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,10 +13,10 @@ importers: version: 2.2.0(react@19.0.0-rc-f38c22b244-20240704) '@tailwindcss/forms': specifier: ^0.5.7 - version: 0.5.9(tailwindcss@3.4.15) + version: 0.5.10(tailwindcss@3.4.17) autoprefixer: - specifier: 10.4.20 - version: 10.4.20(postcss@8.4.49) + specifier: 10.4.21 + version: 10.4.21(postcss@8.5.3) bcrypt: specifier: ^5.1.1 version: 5.1.1 @@ -24,20 +24,20 @@ importers: specifier: ^2.1.1 version: 2.1.1 next: - specifier: 15.0.3 - version: 15.0.3(@playwright/test@1.49.0)(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704) + specifier: 15.3.3 + version: 15.3.3(@playwright/test@1.52.0)(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704) next-auth: - specifier: 5.0.0-beta.25 - version: 5.0.0-beta.25(next@15.0.3(@playwright/test@1.49.0)(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704) + specifier: 5.0.0-beta.28 + version: 5.0.0-beta.28(next@15.3.3(@playwright/test@1.52.0)(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704) postcss: - specifier: 8.4.49 - version: 8.4.49 + specifier: 8.5.3 + version: 8.5.3 primeicons: specifier: ^7.0.0 version: 7.0.0 primereact: specifier: ^10.8.2 - version: 10.8.5(@types/react@18.3.12)(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704) + version: 10.9.5(@types/react@18.3.23)(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704) react: specifier: 19.0.0-rc-f38c22b244-20240704 version: 19.0.0-rc-f38c22b244-20240704 @@ -45,33 +45,36 @@ importers: specifier: 19.0.0-rc-f38c22b244-20240704 version: 19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704) tailwindcss: - specifier: 3.4.15 - version: 3.4.15 + specifier: 3.4.17 + version: 3.4.17 typescript: - specifier: 5.7.2 - version: 5.7.2 + specifier: 5.8.3 + version: 5.8.3 use-debounce: specifier: ^10.0.1 version: 10.0.4(react@19.0.0-rc-f38c22b244-20240704) + uuid: + specifier: 11.1.0 + version: 11.1.0 zod: specifier: ^3.23.8 - version: 3.23.8 + version: 3.24.4 devDependencies: '@playwright/test': specifier: ^1.46.0 - version: 1.49.0 + version: 1.52.0 '@types/bcrypt': specifier: ^5.0.2 version: 5.0.2 '@types/node': - specifier: 22.10.1 - version: 22.10.1 + specifier: 22.15.29 + version: 22.15.29 '@types/react': - specifier: 18.3.12 - version: 18.3.12 + specifier: 18.3.23 + version: 18.3.23 '@types/react-dom': - specifier: 18.3.1 - version: 18.3.1 + specifier: 18.3.7 + version: 18.3.7(@types/react@18.3.23) packages: @@ -79,8 +82,8 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@auth/core@0.37.2': - resolution: {integrity: sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw==} + '@auth/core@0.39.1': + resolution: {integrity: sha512-McD8slui0oOA1pjR5sPjLPl5Zm//nLP/8T3kr8hxIsvNLvsiudYvPHhDFPjh1KcZ2nFxCkZmP6bRxaaPd/AnLA==} peerDependencies: '@simplewebauthn/browser': ^9.0.1 '@simplewebauthn/server': ^9.0.2 @@ -93,119 +96,124 @@ packages: nodemailer: optional: true - '@babel/runtime@7.26.0': - resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} + '@babel/runtime@7.27.0': + resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} engines: {node: '>=6.9.0'} - '@emnapi/runtime@1.2.0': - resolution: {integrity: sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==} + '@emnapi/runtime@1.4.0': + resolution: {integrity: sha512-64WYIf4UYcdLnbKn/umDlNjQDSS8AgZrI/R9+x5ilkUVFxXcA1Ebl+gQLc/6mERA4407Xof0R7wEyEuj091CVw==} '@heroicons/react@2.2.0': resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==} peerDependencies: react: '>= 16 || ^19.0.0-rc' - '@img/sharp-darwin-arm64@0.33.5': - resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + '@img/sharp-darwin-arm64@0.34.1': + resolution: {integrity: sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-x64@0.33.5': - resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + '@img/sharp-darwin-x64@0.34.1': + resolution: {integrity: sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.0.4': - resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + '@img/sharp-libvips-darwin-arm64@1.1.0': + resolution: {integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.0.4': - resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + '@img/sharp-libvips-darwin-x64@1.1.0': + resolution: {integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.0.4': - resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + '@img/sharp-libvips-linux-arm64@1.1.0': + resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm@1.0.5': - resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + '@img/sharp-libvips-linux-arm@1.1.0': + resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==} cpu: [arm] os: [linux] - '@img/sharp-libvips-linux-s390x@1.0.4': - resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + '@img/sharp-libvips-linux-ppc64@1.1.0': + resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.1.0': + resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==} cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-x64@1.0.4': - resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + '@img/sharp-libvips-linux-x64@1.1.0': + resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==} cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': + resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + '@img/sharp-libvips-linuxmusl-x64@1.1.0': + resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==} cpu: [x64] os: [linux] - '@img/sharp-linux-arm64@0.33.5': - resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + '@img/sharp-linux-arm64@0.34.1': + resolution: {integrity: sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linux-arm@0.33.5': - resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + '@img/sharp-linux-arm@0.34.1': + resolution: {integrity: sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - '@img/sharp-linux-s390x@0.33.5': - resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + '@img/sharp-linux-s390x@0.34.1': + resolution: {integrity: sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - '@img/sharp-linux-x64@0.33.5': - resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + '@img/sharp-linux-x64@0.34.1': + resolution: {integrity: sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.33.5': - resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + '@img/sharp-linuxmusl-arm64@0.34.1': + resolution: {integrity: sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-x64@0.33.5': - resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + '@img/sharp-linuxmusl-x64@0.34.1': + resolution: {integrity: sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-wasm32@0.33.5': - resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + '@img/sharp-wasm32@0.34.1': + resolution: {integrity: sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - '@img/sharp-win32-ia32@0.33.5': - resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + '@img/sharp-win32-ia32@0.34.1': + resolution: {integrity: sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-x64@0.33.5': - resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + '@img/sharp-win32-x64@0.34.1': + resolution: {integrity: sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] @@ -236,53 +244,53 @@ packages: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true - '@next/env@15.0.3': - resolution: {integrity: sha512-t9Xy32pjNOvVn2AS+Utt6VmyrshbpfUMhIjFO60gI58deSo/KgLOp31XZ4O+kY/Is8WAGYwA5gR7kOb1eORDBA==} + '@next/env@15.3.3': + resolution: {integrity: sha512-OdiMrzCl2Xi0VTjiQQUK0Xh7bJHnOuET2s+3V+Y40WJBAXrJeGA3f+I8MZJ/YQ3mVGi5XGR1L66oFlgqXhQ4Vw==} - '@next/swc-darwin-arm64@15.0.3': - resolution: {integrity: sha512-s3Q/NOorCsLYdCKvQlWU+a+GeAd3C8Rb3L1YnetsgwXzhc3UTWrtQpB/3eCjFOdGUj5QmXfRak12uocd1ZiiQw==} + '@next/swc-darwin-arm64@15.3.3': + resolution: {integrity: sha512-WRJERLuH+O3oYB4yZNVahSVFmtxRNjNF1I1c34tYMoJb0Pve+7/RaLAJJizyYiFhjYNGHRAE1Ri2Fd23zgDqhg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.0.3': - resolution: {integrity: sha512-Zxl/TwyXVZPCFSf0u2BNj5sE0F2uR6iSKxWpq4Wlk/Sv9Ob6YCKByQTkV2y6BCic+fkabp9190hyrDdPA/dNrw==} + '@next/swc-darwin-x64@15.3.3': + resolution: {integrity: sha512-XHdzH/yBc55lu78k/XwtuFR/ZXUTcflpRXcsu0nKmF45U96jt1tsOZhVrn5YH+paw66zOANpOnFQ9i6/j+UYvw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.0.3': - resolution: {integrity: sha512-T5+gg2EwpsY3OoaLxUIofmMb7ohAUlcNZW0fPQ6YAutaWJaxt1Z1h+8zdl4FRIOr5ABAAhXtBcpkZNwUcKI2fw==} + '@next/swc-linux-arm64-gnu@15.3.3': + resolution: {integrity: sha512-VZ3sYL2LXB8znNGcjhocikEkag/8xiLgnvQts41tq6i+wql63SMS1Q6N8RVXHw5pEUjiof+II3HkDd7GFcgkzw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.0.3': - resolution: {integrity: sha512-WkAk6R60mwDjH4lG/JBpb2xHl2/0Vj0ZRu1TIzWuOYfQ9tt9NFsIinI1Epma77JVgy81F32X/AeD+B2cBu/YQA==} + '@next/swc-linux-arm64-musl@15.3.3': + resolution: {integrity: sha512-h6Y1fLU4RWAp1HPNJWDYBQ+e3G7sLckyBXhmH9ajn8l/RSMnhbuPBV/fXmy3muMcVwoJdHL+UtzRzs0nXOf9SA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.0.3': - resolution: {integrity: sha512-gWL/Cta1aPVqIGgDb6nxkqy06DkwJ9gAnKORdHWX1QBbSZZB+biFYPFti8aKIQL7otCE1pjyPaXpFzGeG2OS2w==} + '@next/swc-linux-x64-gnu@15.3.3': + resolution: {integrity: sha512-jJ8HRiF3N8Zw6hGlytCj5BiHyG/K+fnTKVDEKvUCyiQ/0r5tgwO7OgaRiOjjRoIx2vwLR+Rz8hQoPrnmFbJdfw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.0.3': - resolution: {integrity: sha512-QQEMwFd8r7C0GxQS62Zcdy6GKx999I/rTO2ubdXEe+MlZk9ZiinsrjwoiBL5/57tfyjikgh6GOU2WRQVUej3UA==} + '@next/swc-linux-x64-musl@15.3.3': + resolution: {integrity: sha512-HrUcTr4N+RgiiGn3jjeT6Oo208UT/7BuTr7K0mdKRBtTbT4v9zJqCDKO97DUqqoBK1qyzP1RwvrWTvU6EPh/Cw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.0.3': - resolution: {integrity: sha512-9TEp47AAd/ms9fPNgtgnT7F3M1Hf7koIYYWCMQ9neOwjbVWJsHZxrFbI3iEDJ8rf1TDGpmHbKxXf2IFpAvheIQ==} + '@next/swc-win32-arm64-msvc@15.3.3': + resolution: {integrity: sha512-SxorONgi6K7ZUysMtRF3mIeHC5aA3IQLmKFQzU0OuhuUYwpOBc1ypaLJLP5Bf3M9k53KUUUj4vTPwzGvl/NwlQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.0.3': - resolution: {integrity: sha512-VNAz+HN4OGgvZs6MOoVfnn41kBzT+M+tB+OK4cww6DNyWS6wKaDpaAm/qLeOUbnMh0oVx1+mg0uoYARF69dJyA==} + '@next/swc-win32-x64-msvc@15.3.3': + resolution: {integrity: sha512-4QZG6F8enl9/S2+yIiOiju0iCTFd93d8VC1q9LZS4p/Xuk81W2QDjCFeoogmrWWkAD59z8ZxepBQap2dKS5ruw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -306,42 +314,43 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.49.0': - resolution: {integrity: sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw==} + '@playwright/test@1.52.0': + resolution: {integrity: sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==} engines: {node: '>=18'} hasBin: true '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - '@swc/helpers@0.5.13': - resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==} + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} - '@tailwindcss/forms@0.5.9': - resolution: {integrity: sha512-tM4XVr2+UVTxXJzey9Twx48c1gcxFStqn1pQz0tRsX8o3DvxhN5oY5pvyAbUx7VTaZxpej4Zzvc6h+1RJBzpIg==} + '@tailwindcss/forms@0.5.10': + resolution: {integrity: sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==} peerDependencies: - tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20' + tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1' '@types/bcrypt@5.0.2': resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==} - '@types/cookie@0.6.0': - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - - '@types/node@22.10.1': - resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==} + '@types/node@22.15.29': + resolution: {integrity: sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==} '@types/prop-types@15.7.12': resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - '@types/react-dom@18.3.1': - resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 - '@types/react-transition-group@4.4.11': - resolution: {integrity: sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==} + '@types/react-transition-group@4.4.12': + resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==} + peerDependencies: + '@types/react': '*' - '@types/react@18.3.12': - resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} + '@types/react@18.3.23': + resolution: {integrity: sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==} abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} @@ -384,8 +393,8 @@ packages: arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - autoprefixer@10.4.20: - resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} + autoprefixer@10.4.21: + resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: @@ -412,8 +421,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.23.3: - resolution: {integrity: sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==} + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -425,8 +434,8 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001651: - resolution: {integrity: sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==} + caniuse-lite@1.0.30001702: + resolution: {integrity: sha512-LoPe/D7zioC0REI5W73PeR1e1MLCipRGq/VkovJnd6Df+QVqT+vT33OXCp8QUd7kA7RZrHWxb1B36OQKI/0gOA==} chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} @@ -471,10 +480,6 @@ packages: console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} - cookie@0.7.1: - resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} - engines: {node: '>= 0.6'} - cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -515,8 +520,8 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.6: - resolution: {integrity: sha512-jwXWsM5RPf6j9dPYzaorcBSUg6AiqocPEyMpkchkvntaH9HGfOOMZwxMJjDY/XEs3T5dM7uyH1VhRMkqUU9qVw==} + electron-to-chromium@1.5.113: + resolution: {integrity: sha512-wjT2O4hX+wdWPJ76gWSkMhcHAV2PTMX+QetUCPYEdCIe+cxmgzzSSiGRCKW8nuh4mwKZlpv0xvoW7OF2X+wmHg==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -524,8 +529,8 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} fast-glob@3.3.2: @@ -643,18 +648,14 @@ packages: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true - jose@5.9.6: - resolution: {integrity: sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==} + jose@6.0.10: + resolution: {integrity: sha512-skIAxZqcMkOrSwjJvplIPYrlXGpxTPnro2/QWTDCxAdWQrSTV5/KqspMWmi5WAx5+ULswASJiZ0a+1B/Lxt9cw==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} - - lilconfig@3.1.1: - resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==} + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} lines-and-columns@1.2.4: @@ -718,13 +719,13 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - next-auth@5.0.0-beta.25: - resolution: {integrity: sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog==} + next-auth@5.0.0-beta.28: + resolution: {integrity: sha512-2RDR1h3DJb4nizcd5UBBwC2gtyP7j/jTvVLvEtDaFSKUWNfou3Gek2uTNHSga/Q4I/GF+OJobA4mFbRaWJgIDQ==} peerDependencies: '@simplewebauthn/browser': ^9.0.1 '@simplewebauthn/server': ^9.0.2 @@ -739,16 +740,16 @@ packages: nodemailer: optional: true - next@15.0.3: - resolution: {integrity: sha512-ontCbCRKJUIoivAdGB34yCaOcPgYXr9AAkV/IwqFfWWTXEPUgLYkSkqBhIk9KK7gGmgjc64B+RdoeIDM13Irnw==} + next@15.3.3: + resolution: {integrity: sha512-JqNj29hHNmCLtNvd090SyRbXJiivQ+58XjCcrC50Crb5g5u2zi7Y2YivbsEfzk6AtVI80akdOQbaMZwWB1Hthw==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 '@playwright/test': ^1.41.2 babel-plugin-react-compiler: '*' - react: ^18.2.0 || 19.0.0-rc-66855b96-20241106 - react-dom: ^18.2.0 || 19.0.0-rc-66855b96-20241106 + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 sass: ^1.3.0 peerDependenciesMeta: '@opentelemetry/api': @@ -772,8 +773,8 @@ packages: encoding: optional: true - node-releases@2.0.18: - resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} nopt@5.0.0: resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} @@ -792,8 +793,8 @@ packages: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} deprecated: This package is no longer supported. - oauth4webapi@3.1.1: - resolution: {integrity: sha512-0h4FZjsntbKQ5IHGM9mFT7uOwQCRdcTG7YhC0xXlWIcCch24wUa6Vggaipa3Sw6Ab7nEnmO4rctROmyuHBfP7Q==} + oauth4webapi@3.5.0: + resolution: {integrity: sha512-DF3mLWNuxPkxJkHmWxbSFz4aE5CjWOsm465VBfBdWzmzX4Mg3vF8icxK+iKqfdWrIumBJ2TaoNQWx+SQc2bsPQ==} object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -821,9 +822,6 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - picocolors@1.0.1: - resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -839,13 +837,13 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} - playwright-core@1.49.0: - resolution: {integrity: sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==} + playwright-core@1.52.0: + resolution: {integrity: sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==} engines: {node: '>=18'} hasBin: true - playwright@1.49.0: - resolution: {integrity: sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A==} + playwright@1.52.0: + resolution: {integrity: sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==} engines: {node: '>=18'} hasBin: true @@ -890,26 +888,23 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.4.49: - resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} - preact-render-to-string@5.2.3: - resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==} + preact-render-to-string@6.5.11: + resolution: {integrity: sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==} peerDependencies: preact: '>=10' - preact@10.11.3: - resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} - - pretty-format@3.8.0: - resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} + preact@10.24.3: + resolution: {integrity: sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==} primeicons@7.0.0: resolution: {integrity: sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==} - primereact@10.8.5: - resolution: {integrity: sha512-B1LeJdNGGAB8P1VRJE0TDYz6rZSDwxE7Ft8PLFjRYLO44+mIJNDsLYSB56LRJOoqUNRhQrRIe/5ctS5eyQAzwQ==} + primereact@10.9.5: + resolution: {integrity: sha512-4O6gm0LrKF7Ml8zQmb8mGiWS/ugJ94KBOAS/CAxWFQh9qyNgfNw/qcpCeomPIkjWd98jrM2XDiEbgq+W0395Hw==} engines: {node: '>=14.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -988,16 +983,16 @@ packages: engines: {node: '>=10'} hasBin: true - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} hasBin: true set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - sharp@0.33.5: - resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + sharp@0.34.1: + resolution: {integrity: sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@2.0.0: @@ -1067,8 +1062,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tailwindcss@3.4.15: - resolution: {integrity: sha512-r4MeXnfBmSOuKUWmXe6h2CcyfzJCEk4F0pptO5jlnYSIViUkVmsawj80N5h2lO3gwcmSb4n3PuN+e+GC1Guylw==} + tailwindcss@3.4.17: + resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==} engines: {node: '>=14.0.0'} hasBin: true @@ -1093,19 +1088,19 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - tslib@2.6.3: - resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - typescript@5.7.2: - resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} hasBin: true - undici-types@6.20.0: - resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - update-browserslist-db@1.1.0: - resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -1119,6 +1114,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -1152,109 +1151,110 @@ packages: engines: {node: '>= 14'} hasBin: true - zod@3.23.8: - resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} + zod@3.24.4: + resolution: {integrity: sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==} snapshots: '@alloc/quick-lru@5.2.0': {} - '@auth/core@0.37.2': + '@auth/core@0.39.1': dependencies: '@panva/hkdf': 1.2.1 - '@types/cookie': 0.6.0 - cookie: 0.7.1 - jose: 5.9.6 - oauth4webapi: 3.1.1 - preact: 10.11.3 - preact-render-to-string: 5.2.3(preact@10.11.3) + jose: 6.0.10 + oauth4webapi: 3.5.0 + preact: 10.24.3 + preact-render-to-string: 6.5.11(preact@10.24.3) - '@babel/runtime@7.26.0': + '@babel/runtime@7.27.0': dependencies: regenerator-runtime: 0.14.1 - '@emnapi/runtime@1.2.0': + '@emnapi/runtime@1.4.0': dependencies: - tslib: 2.6.3 + tslib: 2.8.1 optional: true '@heroicons/react@2.2.0(react@19.0.0-rc-f38c22b244-20240704)': dependencies: react: 19.0.0-rc-f38c22b244-20240704 - '@img/sharp-darwin-arm64@0.33.5': + '@img/sharp-darwin-arm64@0.34.1': optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-arm64': 1.1.0 optional: true - '@img/sharp-darwin-x64@0.33.5': + '@img/sharp-darwin-x64@0.34.1': optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.1.0 optional: true - '@img/sharp-libvips-darwin-arm64@1.0.4': + '@img/sharp-libvips-darwin-arm64@1.1.0': optional: true - '@img/sharp-libvips-darwin-x64@1.0.4': + '@img/sharp-libvips-darwin-x64@1.1.0': optional: true - '@img/sharp-libvips-linux-arm64@1.0.4': + '@img/sharp-libvips-linux-arm64@1.1.0': optional: true - '@img/sharp-libvips-linux-arm@1.0.5': + '@img/sharp-libvips-linux-arm@1.1.0': optional: true - '@img/sharp-libvips-linux-s390x@1.0.4': + '@img/sharp-libvips-linux-ppc64@1.1.0': optional: true - '@img/sharp-libvips-linux-x64@1.0.4': + '@img/sharp-libvips-linux-s390x@1.1.0': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + '@img/sharp-libvips-linux-x64@1.1.0': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.0.4': + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': optional: true - '@img/sharp-linux-arm64@0.33.5': + '@img/sharp-libvips-linuxmusl-x64@1.1.0': + optional: true + + '@img/sharp-linux-arm64@0.34.1': optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-arm64': 1.1.0 optional: true - '@img/sharp-linux-arm@0.33.5': + '@img/sharp-linux-arm@0.34.1': optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm': 1.1.0 optional: true - '@img/sharp-linux-s390x@0.33.5': + '@img/sharp-linux-s390x@0.34.1': optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.1.0 optional: true - '@img/sharp-linux-x64@0.33.5': + '@img/sharp-linux-x64@0.34.1': optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.1.0 optional: true - '@img/sharp-linuxmusl-arm64@0.33.5': + '@img/sharp-linuxmusl-arm64@0.34.1': optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 optional: true - '@img/sharp-linuxmusl-x64@0.33.5': + '@img/sharp-linuxmusl-x64@0.34.1': optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 optional: true - '@img/sharp-wasm32@0.33.5': + '@img/sharp-wasm32@0.34.1': dependencies: - '@emnapi/runtime': 1.2.0 + '@emnapi/runtime': 1.4.0 optional: true - '@img/sharp-win32-ia32@0.33.5': + '@img/sharp-win32-ia32@0.34.1': optional: true - '@img/sharp-win32-x64@0.33.5': + '@img/sharp-win32-x64@0.34.1': optional: true '@isaacs/cliui@8.0.2': @@ -1298,30 +1298,30 @@ snapshots: - encoding - supports-color - '@next/env@15.0.3': {} + '@next/env@15.3.3': {} - '@next/swc-darwin-arm64@15.0.3': + '@next/swc-darwin-arm64@15.3.3': optional: true - '@next/swc-darwin-x64@15.0.3': + '@next/swc-darwin-x64@15.3.3': optional: true - '@next/swc-linux-arm64-gnu@15.0.3': + '@next/swc-linux-arm64-gnu@15.3.3': optional: true - '@next/swc-linux-arm64-musl@15.0.3': + '@next/swc-linux-arm64-musl@15.3.3': optional: true - '@next/swc-linux-x64-gnu@15.0.3': + '@next/swc-linux-x64-gnu@15.3.3': optional: true - '@next/swc-linux-x64-musl@15.0.3': + '@next/swc-linux-x64-musl@15.3.3': optional: true - '@next/swc-win32-arm64-msvc@15.0.3': + '@next/swc-win32-arm64-msvc@15.3.3': optional: true - '@next/swc-win32-x64-msvc@15.0.3': + '@next/swc-win32-x64-msvc@15.3.3': optional: true '@nodelib/fs.scandir@2.1.5': @@ -1341,42 +1341,40 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.49.0': + '@playwright/test@1.52.0': dependencies: - playwright: 1.49.0 + playwright: 1.52.0 '@swc/counter@0.1.3': {} - '@swc/helpers@0.5.13': + '@swc/helpers@0.5.15': dependencies: - tslib: 2.6.3 + tslib: 2.8.1 - '@tailwindcss/forms@0.5.9(tailwindcss@3.4.15)': + '@tailwindcss/forms@0.5.10(tailwindcss@3.4.17)': dependencies: mini-svg-data-uri: 1.4.4 - tailwindcss: 3.4.15 + tailwindcss: 3.4.17 '@types/bcrypt@5.0.2': dependencies: - '@types/node': 22.10.1 + '@types/node': 22.15.29 - '@types/cookie@0.6.0': {} - - '@types/node@22.10.1': + '@types/node@22.15.29': dependencies: - undici-types: 6.20.0 + undici-types: 6.21.0 '@types/prop-types@15.7.12': {} - '@types/react-dom@18.3.1': + '@types/react-dom@18.3.7(@types/react@18.3.23)': dependencies: - '@types/react': 18.3.12 + '@types/react': 18.3.23 - '@types/react-transition-group@4.4.11': + '@types/react-transition-group@4.4.12(@types/react@18.3.23)': dependencies: - '@types/react': 18.3.12 + '@types/react': 18.3.23 - '@types/react@18.3.12': + '@types/react@18.3.23': dependencies: '@types/prop-types': 15.7.12 csstype: 3.1.3 @@ -1415,14 +1413,14 @@ snapshots: arg@5.0.2: {} - autoprefixer@10.4.20(postcss@8.4.49): + autoprefixer@10.4.21(postcss@8.5.3): dependencies: - browserslist: 4.23.3 - caniuse-lite: 1.0.30001651 + browserslist: 4.24.4 + caniuse-lite: 1.0.30001702 fraction.js: 4.3.7 normalize-range: 0.1.2 - picocolors: 1.0.1 - postcss: 8.4.49 + picocolors: 1.1.1 + postcss: 8.5.3 postcss-value-parser: 4.2.0 balanced-match@1.0.2: {} @@ -1450,12 +1448,12 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.23.3: + browserslist@4.24.4: dependencies: - caniuse-lite: 1.0.30001651 - electron-to-chromium: 1.5.6 - node-releases: 2.0.18 - update-browserslist-db: 1.1.0(browserslist@4.23.3) + caniuse-lite: 1.0.30001702 + electron-to-chromium: 1.5.113 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.24.4) busboy@1.6.0: dependencies: @@ -1463,7 +1461,7 @@ snapshots: camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001651: {} + caniuse-lite@1.0.30001702: {} chokidar@3.6.0: dependencies: @@ -1509,8 +1507,6 @@ snapshots: console-control-strings@1.1.0: {} - cookie@0.7.1: {} - cross-spawn@7.0.3: dependencies: path-key: 3.1.1 @@ -1535,18 +1531,18 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.0 csstype: 3.1.3 eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.6: {} + electron-to-chromium@1.5.113: {} emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} - escalade@3.1.2: {} + escalade@3.2.0: {} fast-glob@3.3.2: dependencies: @@ -1673,13 +1669,11 @@ snapshots: jiti@1.21.6: {} - jose@5.9.6: {} + jose@6.0.10: {} js-tokens@4.0.0: {} - lilconfig@2.1.0: {} - - lilconfig@3.1.1: {} + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -1733,36 +1727,36 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.3.7: {} + nanoid@3.3.8: {} - next-auth@5.0.0-beta.25(next@15.0.3(@playwright/test@1.49.0)(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704): + next-auth@5.0.0-beta.28(next@15.3.3(@playwright/test@1.52.0)(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704): dependencies: - '@auth/core': 0.37.2 - next: 15.0.3(@playwright/test@1.49.0)(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704) + '@auth/core': 0.39.1 + next: 15.3.3(@playwright/test@1.52.0)(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704) react: 19.0.0-rc-f38c22b244-20240704 - next@15.0.3(@playwright/test@1.49.0)(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704): + next@15.3.3(@playwright/test@1.52.0)(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704): dependencies: - '@next/env': 15.0.3 + '@next/env': 15.3.3 '@swc/counter': 0.1.3 - '@swc/helpers': 0.5.13 + '@swc/helpers': 0.5.15 busboy: 1.6.0 - caniuse-lite: 1.0.30001651 + caniuse-lite: 1.0.30001702 postcss: 8.4.31 react: 19.0.0-rc-f38c22b244-20240704 react-dom: 19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704) styled-jsx: 5.1.6(react@19.0.0-rc-f38c22b244-20240704) optionalDependencies: - '@next/swc-darwin-arm64': 15.0.3 - '@next/swc-darwin-x64': 15.0.3 - '@next/swc-linux-arm64-gnu': 15.0.3 - '@next/swc-linux-arm64-musl': 15.0.3 - '@next/swc-linux-x64-gnu': 15.0.3 - '@next/swc-linux-x64-musl': 15.0.3 - '@next/swc-win32-arm64-msvc': 15.0.3 - '@next/swc-win32-x64-msvc': 15.0.3 - '@playwright/test': 1.49.0 - sharp: 0.33.5 + '@next/swc-darwin-arm64': 15.3.3 + '@next/swc-darwin-x64': 15.3.3 + '@next/swc-linux-arm64-gnu': 15.3.3 + '@next/swc-linux-arm64-musl': 15.3.3 + '@next/swc-linux-x64-gnu': 15.3.3 + '@next/swc-linux-x64-musl': 15.3.3 + '@next/swc-win32-arm64-msvc': 15.3.3 + '@next/swc-win32-x64-msvc': 15.3.3 + '@playwright/test': 1.52.0 + sharp: 0.34.1 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -1773,7 +1767,7 @@ snapshots: dependencies: whatwg-url: 5.0.0 - node-releases@2.0.18: {} + node-releases@2.0.19: {} nopt@5.0.0: dependencies: @@ -1790,7 +1784,7 @@ snapshots: gauge: 3.0.2 set-blocking: 2.0.0 - oauth4webapi@3.1.1: {} + oauth4webapi@3.5.0: {} object-assign@4.1.1: {} @@ -1811,8 +1805,6 @@ snapshots: lru-cache: 10.2.2 minipass: 7.1.2 - picocolors@1.0.1: {} - picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -1821,36 +1813,36 @@ snapshots: pirates@4.0.6: {} - playwright-core@1.49.0: {} + playwright-core@1.52.0: {} - playwright@1.49.0: + playwright@1.52.0: dependencies: - playwright-core: 1.49.0 + playwright-core: 1.52.0 optionalDependencies: fsevents: 2.3.2 - postcss-import@15.1.0(postcss@8.4.49): + postcss-import@15.1.0(postcss@8.5.3): dependencies: - postcss: 8.4.49 + postcss: 8.5.3 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.8 - postcss-js@4.0.1(postcss@8.4.49): + postcss-js@4.0.1(postcss@8.5.3): dependencies: camelcase-css: 2.0.1 - postcss: 8.4.49 + postcss: 8.5.3 - postcss-load-config@4.0.2(postcss@8.4.49): + postcss-load-config@4.0.2(postcss@8.5.3): dependencies: - lilconfig: 3.1.1 + lilconfig: 3.1.3 yaml: 2.4.3 optionalDependencies: - postcss: 8.4.49 + postcss: 8.5.3 - postcss-nested@6.2.0(postcss@8.4.49): + postcss-nested@6.2.0(postcss@8.5.3): dependencies: - postcss: 8.4.49 + postcss: 8.5.3 postcss-selector-parser: 6.1.2 postcss-selector-parser@6.1.2: @@ -1862,35 +1854,32 @@ snapshots: postcss@8.4.31: dependencies: - nanoid: 3.3.7 + nanoid: 3.3.8 picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.4.49: + postcss@8.5.3: dependencies: - nanoid: 3.3.7 + nanoid: 3.3.8 picocolors: 1.1.1 source-map-js: 1.2.1 - preact-render-to-string@5.2.3(preact@10.11.3): + preact-render-to-string@6.5.11(preact@10.24.3): dependencies: - preact: 10.11.3 - pretty-format: 3.8.0 + preact: 10.24.3 - preact@10.11.3: {} - - pretty-format@3.8.0: {} + preact@10.24.3: {} primeicons@7.0.0: {} - primereact@10.8.5(@types/react@18.3.12)(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704): + primereact@10.9.5(@types/react@18.3.23)(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704): dependencies: - '@types/react-transition-group': 4.4.11 + '@types/react-transition-group': 4.4.12(@types/react@18.3.23) react: 19.0.0-rc-f38c22b244-20240704 react-dom: 19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704) react-transition-group: 4.4.5(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704) optionalDependencies: - '@types/react': 18.3.12 + '@types/react': 18.3.23 prop-types@15.8.1: dependencies: @@ -1909,7 +1898,7 @@ snapshots: react-transition-group@4.4.5(react-dom@19.0.0-rc-f38c22b244-20240704(react@19.0.0-rc-f38c22b244-20240704))(react@19.0.0-rc-f38c22b244-20240704): dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.27.0 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -1958,36 +1947,37 @@ snapshots: semver@7.6.2: {} - semver@7.6.3: + semver@7.7.1: optional: true set-blocking@2.0.0: {} - sharp@0.33.5: + sharp@0.34.1: dependencies: color: 4.2.3 detect-libc: 2.0.3 - semver: 7.6.3 + semver: 7.7.1 optionalDependencies: - '@img/sharp-darwin-arm64': 0.33.5 - '@img/sharp-darwin-x64': 0.33.5 - '@img/sharp-libvips-darwin-arm64': 1.0.4 - '@img/sharp-libvips-darwin-x64': 1.0.4 - '@img/sharp-libvips-linux-arm': 1.0.5 - '@img/sharp-libvips-linux-arm64': 1.0.4 - '@img/sharp-libvips-linux-s390x': 1.0.4 - '@img/sharp-libvips-linux-x64': 1.0.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - '@img/sharp-linux-arm': 0.33.5 - '@img/sharp-linux-arm64': 0.33.5 - '@img/sharp-linux-s390x': 0.33.5 - '@img/sharp-linux-x64': 0.33.5 - '@img/sharp-linuxmusl-arm64': 0.33.5 - '@img/sharp-linuxmusl-x64': 0.33.5 - '@img/sharp-wasm32': 0.33.5 - '@img/sharp-win32-ia32': 0.33.5 - '@img/sharp-win32-x64': 0.33.5 + '@img/sharp-darwin-arm64': 0.34.1 + '@img/sharp-darwin-x64': 0.34.1 + '@img/sharp-libvips-darwin-arm64': 1.1.0 + '@img/sharp-libvips-darwin-x64': 1.1.0 + '@img/sharp-libvips-linux-arm': 1.1.0 + '@img/sharp-libvips-linux-arm64': 1.1.0 + '@img/sharp-libvips-linux-ppc64': 1.1.0 + '@img/sharp-libvips-linux-s390x': 1.1.0 + '@img/sharp-libvips-linux-x64': 1.1.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 + '@img/sharp-linux-arm': 0.34.1 + '@img/sharp-linux-arm64': 0.34.1 + '@img/sharp-linux-s390x': 0.34.1 + '@img/sharp-linux-x64': 0.34.1 + '@img/sharp-linuxmusl-arm64': 0.34.1 + '@img/sharp-linuxmusl-x64': 0.34.1 + '@img/sharp-wasm32': 0.34.1 + '@img/sharp-win32-ia32': 0.34.1 + '@img/sharp-win32-x64': 0.34.1 optional: true shebang-command@2.0.0: @@ -2050,7 +2040,7 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - tailwindcss@3.4.15: + tailwindcss@3.4.17: dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -2061,16 +2051,16 @@ snapshots: glob-parent: 6.0.2 is-glob: 4.0.3 jiti: 1.21.6 - lilconfig: 2.1.0 + lilconfig: 3.1.3 micromatch: 4.0.8 normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.1.1 - postcss: 8.4.49 - postcss-import: 15.1.0(postcss@8.4.49) - postcss-js: 4.0.1(postcss@8.4.49) - postcss-load-config: 4.0.2(postcss@8.4.49) - postcss-nested: 6.2.0(postcss@8.4.49) + postcss: 8.5.3 + postcss-import: 15.1.0(postcss@8.5.3) + postcss-js: 4.0.1(postcss@8.5.3) + postcss-load-config: 4.0.2(postcss@8.5.3) + postcss-nested: 6.2.0(postcss@8.5.3) postcss-selector-parser: 6.1.2 resolve: 1.22.8 sucrase: 3.35.0 @@ -2102,16 +2092,16 @@ snapshots: ts-interface-checker@0.1.13: {} - tslib@2.6.3: {} + tslib@2.8.1: {} - typescript@5.7.2: {} + typescript@5.8.3: {} - undici-types@6.20.0: {} + undici-types@6.21.0: {} - update-browserslist-db@1.1.0(browserslist@4.23.3): + update-browserslist-db@1.1.3(browserslist@4.24.4): dependencies: - browserslist: 4.23.3 - escalade: 3.1.2 + browserslist: 4.24.4 + escalade: 3.2.0 picocolors: 1.1.1 use-debounce@10.0.4(react@19.0.0-rc-f38c22b244-20240704): @@ -2120,6 +2110,8 @@ snapshots: util-deprecate@1.0.2: {} + uuid@11.1.0: {} + webidl-conversions@3.0.1: {} whatwg-url@5.0.0: @@ -2153,4 +2145,4 @@ snapshots: yaml@2.4.3: {} - zod@3.23.8: {} + zod@3.24.4: {}
- {arrangement.name} - - {arrangement.discomfort} - - -
+ {arrangement.name} + + {arrangement.discomfort} + + + + + + { + arrangement.valid ? + : + + } +
- { expense.name = value; updateExpense(expense) }} /> + {expense.name} - { expense.amount = parseFloat(value); updateExpense(expense) }} /> + {expense.amount} {expense.pricingType} +
+ { api.destroy(serializer, expense, onUpdate) }} /> + onEdit(expense)} /> +
+
- {group.name} - -
-
- {group.attendance?.confirmed} - - {group.attendance?.tentative} - - {group.attendance?.invited} - - {group.attendance?.declined} - - {group.attendance?.considered} - - {group.attendance?.total} -
- { destroyGuest(guest, () => onUpdate()) }} /> + { api.destroy(serializer, guest, onUpdate)}} /> onEdit(guest)} />