diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d574a2a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +.git +.next +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.env +.env.* +!.env.example +.DS_Store +coverage +uploads +var/uploads +deploy +public/og-candidates diff --git a/.gitignore b/.gitignore index 401454f..88005d2 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ # production /build +/uploads # misc .DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..67b6617 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +FROM node:22-alpine AS base + +WORKDIR /app + +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN apk add --no-cache openssl + +FROM base AS deps + +COPY package.json package-lock.json ./ +RUN npm ci + +FROM base AS builder + +ARG NEXT_PUBLIC_SITE_URL +ENV NEXT_PUBLIC_SITE_URL=$NEXT_PUBLIC_SITE_URL +ENV DATABASE_URL=postgresql://build:build@localhost:5432/build?schema=public + +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +RUN npm run build:prod + +FROM base AS runner + +ENV NODE_ENV=production +ENV PORT=3000 +ENV HOSTNAME=0.0.0.0 +ENV UPLOAD_DIR=/app/uploads/announcements + +COPY --from=builder /app ./ + +RUN mkdir -p /app/uploads/announcements && chmod +x /app/scripts/start-production.sh + +EXPOSE 3000 + +CMD ["./scripts/start-production.sh"] diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..f84d1a2 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,55 @@ +# Kita-Planer Deployment + +Dieses Verzeichnis enthaelt die ausfuellbaren Vorlagen fuer das Production-Deployment. + +## Dateien + +- `production.env.example`: Vorlage fuer alle benoetigten Environment Variables. +- `production-checklist.md`: Checkliste fuer Domain, Datenbank, Auth, Mail, Uploads und Rechtliches. +- `../Dockerfile`: Container-Build fuer Coolify oder andere Docker-Hosts. +- `../docker-compose.coolify.yml`: Compose-Vorlage fuer Coolify mit required Environment Variables. + +## Empfohlener Ablauf mit Coolify und Gitea + +1. Repository in deine Gitea-Instanz pushen. +2. In Coolify eine neue Application aus diesem Gitea-Repository anlegen. +3. Als Build Pack `Docker Compose` waehlen. +4. Als Compose-Datei `docker-compose.coolify.yml` eintragen. +5. Die bestehende Coolify-Postgres-Datenbank ueber ihre interne `DATABASE_URL` verbinden. +6. Environment Variables aus `production.env.example` in Coolify eintragen. +7. Domain auf den `app`-Service mit internem Port `3000` zeigen lassen. +8. Deploy starten. + +Beim Container-Start fuehrt `scripts/start-production.sh` automatisch aus: + +```bash +npx prisma db push +``` + +Coolify erkennt Variablen aus `docker-compose.coolify.yml` und zeigt required Variablen +wie `DATABASE_URL`, `AUTH_SECRET`, `AUTH_URL`, `NEXTAUTH_URL`, `NEXT_PUBLIC_SITE_URL`, +`RESEND_API_KEY`, `EMAIL_FROM` und `ADMIN_EMAIL` in der UI an. + +## Welche Coolify-Postgres-URL? + +Wenn die App und die Postgres-DB im selben Coolify-Projekt/Netzwerk laufen, nimm +die interne Datenbank-URL aus Coolify, nicht die oeffentliche externe URL. Das ist +stabiler und vermeidet unnoetigen Traffic ueber den Host. + +Das Format bleibt: + +```txt +postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public +``` + +## Wichtige Hinweise + +- Das aktuelle Projekt nutzt noch `prisma db push` statt versionierter Prisma-Migrationen. + Fuer den ersten MVP-Deploy ist das praktikabel, fuer laengerfristigen Betrieb sollten + Migrationen eingefuehrt werden. +- `UPLOAD_DIR` muss persistent sein. Die Coolify-Compose-Datei mountet dafuer ein + Docker-Volume nach `/app/uploads`. +- `NEXT_PUBLIC_SITE_URL` muss die echte Domain enthalten, damit OpenGraph-Bilder in + WhatsApp, LinkedIn und Co. korrekt als absolute URL aufgeloest werden. +- `NEXT_PUBLIC_SITE_URL` wird auch als Build-Arg an Docker uebergeben. In Coolify sollte + diese Variable deshalb fuer Build und Runtime verfuegbar sein. diff --git a/deploy/production-checklist.md b/deploy/production-checklist.md new file mode 100644 index 0000000..e4c1910 --- /dev/null +++ b/deploy/production-checklist.md @@ -0,0 +1,60 @@ +# Production Checklist + +## 1. Domain & Basis-URLs + +- [ ] Produktionsdomain festlegen: `https://[deine-domain.de]` +- [ ] `NEXT_PUBLIC_SITE_URL` auf die Produktionsdomain setzen +- [ ] `AUTH_URL` und `NEXTAUTH_URL` auf die Produktionsdomain setzen +- [ ] Nach Deployment die WhatsApp-/LinkedIn-Vorschau mit der finalen URL testen + +## 2. Datenbank + +- [ ] Interne `DATABASE_URL` der bestehenden Coolify-Postgres-DB kopieren +- [ ] `DATABASE_URL` in der Coolify-App eintragen +- [ ] Beim ersten App-Start wird das Schema automatisch mit `npx prisma db push --skip-generate` angewendet +- [ ] Fuer spaetere Releases idealerweise Prisma-Migrationen einfuehren + +## 3. Auth & Sicherheit + +- [ ] `AUTH_SECRET` neu generieren: `openssl rand -base64 32` +- [ ] Keine `.env`-Dateien mit echten Secrets committen +- [ ] Dashboard ist per Metadata auf `noindex,nofollow` gesetzt +- [ ] Produktionsdomain auf HTTPS betreiben + +## 4. Mail + +- [ ] Eigene Versanddomain bei Resend verifizieren +- [ ] `RESEND_API_KEY` fuer Produktion setzen +- [ ] `EMAIL_FROM` auf eine verifizierte Absenderadresse setzen +- [ ] `ADMIN_EMAIL` fuer Kontaktformular setzen +- [ ] Kontaktformular und Einladung/E-Mail-Flows einmal in Produktion testen + +## 5. Uploads + +- [ ] Coolify-Volume `kita_planer_uploads` fuer `/app/uploads` verwenden +- [ ] Sicherstellen, dass Uploads nicht aus Versehen oeffentlich ausgeliefert werden +- [ ] Backup-Strategie fuer Uploads klaeren + +## 6. Rechtliches + +- [ ] Platzhalter im Impressum ersetzen +- [ ] Platzhalter in der Datenschutzerklaerung ersetzen +- [ ] Hosting-Anbieter und Mail-Anbieter in Datenschutztext eintragen +- [ ] Rechtliche Texte vor Livegang pruefen lassen + +## 7. Build & Smoke Test + +- [ ] Dependencies installieren: `npm ci` +- [ ] Production Build erstellen: `npm run build:prod` +- [ ] App starten: `npm run start:prod` +- [ ] Startseite, Login, Dashboard und Kontaktformular testen + +## 8. Coolify + +- [ ] Code nach Gitea pushen +- [ ] In Coolify eine Application aus dem Gitea-Repository anlegen +- [ ] Build Pack `Docker Compose` waehlen +- [ ] Compose-Datei `docker-compose.coolify.yml` verwenden +- [ ] Environment Variables in Coolify eintragen, nicht ins Repo committen +- [ ] Domain auf den `app`-Service und Container-Port `3000` routen +- [ ] Upload-Volume `kita_planer_uploads` bleibt zwischen Deployments erhalten diff --git a/deploy/production.env.example b/deploy/production.env.example new file mode 100644 index 0000000..2d4ad83 --- /dev/null +++ b/deploy/production.env.example @@ -0,0 +1,34 @@ +# ===================================================================== +# Kita-Planer · Production Environment Template +# --------------------------------------------------------------------- +# Werte in der Hosting-Plattform als Environment Variables eintragen +# oder lokal als `.env.production` / Server-Env verwenden. +# Keine echten Secrets committen. +# ===================================================================== + +# PostgreSQL +# In Coolify am besten die interne URL deiner bestehenden Postgres-DB verwenden. +# Beispiel: postgresql://USER:PASSWORD@HOST:5432/kita_planer?schema=public +DATABASE_URL="postgresql://[USER]:[PASSWORD]@[HOST]:[PORT]/[DATABASE]?schema=public" + +# Auth.js / NextAuth +# Generieren mit: openssl rand -base64 32 +AUTH_SECRET="[GENERATE_ME]" + +# Oeffentliche Produktions-URL, ohne Slash am Ende. +# Beide Werte setzen, weil bestehender Code und Auth-Tooling beide Namen kennen. +AUTH_URL="https://[deine-domain.de]" +NEXTAUTH_URL="https://[deine-domain.de]" +NEXT_PUBLIC_SITE_URL="https://[deine-domain.de]" + +# Mailversand via Resend +RESEND_API_KEY="re_[PRODUCTION_API_KEY]" +EMAIL_FROM="Kita-Planer <[absender@deine-domain.de]>" +ADMIN_EMAIL="[kontakt@deine-domain.de]" + +# Persistenter Upload-Speicher fuer Anhaenge vom Schwarzen Brett. +# Muss auf dem Server dauerhaft erhalten bleiben und darf nicht im Repo liegen. +UPLOAD_DIR="/var/lib/kita-planer/uploads/announcements" + +# Optional: Nur bewusst fuer initiales Demo-Seeding in Produktion setzen. +# ALLOW_DATABASE_SEED="true" diff --git a/docker-compose.coolify.yml b/docker-compose.coolify.yml new file mode 100644 index 0000000..363d840 --- /dev/null +++ b/docker-compose.coolify.yml @@ -0,0 +1,28 @@ +services: + app: + build: + context: . + dockerfile: Dockerfile + args: + NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL:?} + environment: + NODE_ENV: production + PORT: 3000 + HOSTNAME: 0.0.0.0 + DATABASE_URL: ${DATABASE_URL:?} + AUTH_SECRET: ${AUTH_SECRET:?} + AUTH_URL: ${AUTH_URL:?} + NEXTAUTH_URL: ${NEXTAUTH_URL:?} + NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL:?} + RESEND_API_KEY: ${RESEND_API_KEY:?} + EMAIL_FROM: ${EMAIL_FROM:?} + ADMIN_EMAIL: ${ADMIN_EMAIL:?} + UPLOAD_DIR: /app/uploads/announcements + volumes: + - kita_planer_uploads:/app/uploads + ports: + - "3000" + restart: unless-stopped + +volumes: + kita_planer_uploads: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..39bf455 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3.8' + +services: + postgres: + image: postgres:16-alpine + container_name: kita_planer_db + environment: + POSTGRES_USER: kita + POSTGRES_PASSWORD: kita + POSTGRES_DB: kita_planer + ports: + - "5433:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + restart: unless-stopped + +volumes: + postgres_data: diff --git a/package-lock.json b/package-lock.json index 05e1fc6..d7b3fbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,13 @@ "version": "0.1.0", "dependencies": { "@auth/prisma-adapter": "^2.11.2", + "@hookform/resolvers": "^5.2.2", "@prisma/client": "^6.19.3", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-progress": "^1.1.8", + "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", "@react-email/components": "^1.0.12", @@ -26,6 +29,9 @@ "next-themes": "^0.4.6", "react": "19.2.4", "react-dom": "19.2.4", + "react-hook-form": "^7.75.0", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", "resend": "^6.12.3", "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", @@ -960,6 +966,56 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@hookform/resolvers": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz", + "integrity": "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -1842,12 +1898,41 @@ "@prisma/debug": "6.19.3" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, "node_modules/@radix-ui/primitive": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", "license": "MIT" }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-checkbox": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", @@ -1878,6 +1963,50 @@ } } }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", @@ -1962,6 +2091,21 @@ } } }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-dismissable-layer": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", @@ -2093,6 +2237,38 @@ } } }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-portal": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", @@ -2182,6 +2358,129 @@ } } }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.8.tgz", + "integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.3", + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", + "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-separator": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", @@ -2346,6 +2645,24 @@ } } }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-size": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", @@ -2364,6 +2681,35 @@ } } }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, "node_modules/@react-email/body": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@react-email/body/-/body-0.3.0.tgz", @@ -2759,6 +3105,12 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -3057,13 +3409,39 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3078,6 +3456,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.19.39", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", @@ -3092,7 +3485,6 @@ "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -3110,6 +3502,12 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.59.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz", @@ -3406,6 +3804,12 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "license": "ISC" + }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", @@ -3974,6 +4378,16 @@ "node": ">= 0.4" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -4170,6 +4584,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4187,6 +4611,46 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -4260,6 +4724,16 @@ "dev": true, "license": "MIT" }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4310,7 +4784,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -4388,7 +4861,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -4402,6 +4874,19 @@ } } }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4471,6 +4956,15 @@ "devOptional": true, "license": "MIT" }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", @@ -4494,6 +4988,19 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -5291,6 +5798,16 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -5308,6 +5825,12 @@ "devOptional": true, "license": "MIT" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-check": { "version": "3.23.2", "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", @@ -5808,6 +6331,46 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hermes-estree": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", @@ -5841,6 +6404,16 @@ "node": ">=14" } }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/htmlparser2": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", @@ -5897,6 +6470,12 @@ "node": ">=0.8.19" } }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -5912,6 +6491,30 @@ "node": ">= 0.4" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -6070,6 +6673,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6129,6 +6742,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -6182,6 +6805,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -6798,6 +7433,16 @@ "dev": true, "license": "MIT" }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -6840,6 +7485,16 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/marked": { "version": "15.0.12", "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", @@ -6862,6 +7517,288 @@ "node": ">= 0.4" } }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -6872,6 +7809,569 @@ "node": ">= 8" } }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -6913,7 +8413,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -7353,6 +8852,31 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/parseley": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", @@ -7585,6 +9109,16 @@ "react-is": "^16.13.1" } }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -7667,6 +9201,23 @@ "react": "^19.2.4" } }, + "node_modules/react-hook-form": { + "version": "7.75.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.75.0.tgz", + "integrity": "sha512-Ovv94H+0p3sJ7B9B5QxPuCP1u8V/cHuVGyH55cSwodYDtoJwK+fqk3vjfIgSX59I2U/bU4z0nRJ9HMLpNiWEmw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -7674,6 +9225,33 @@ "dev": true, "license": "MIT" }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/react-remove-scroll": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", @@ -7801,6 +9379,72 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/resend": { "version": "6.12.3", "resolved": "https://registry.npmjs.org/resend/-/resend-6.12.3.tgz", @@ -8209,6 +9853,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -8353,6 +10007,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -8376,6 +10044,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -8546,6 +10232,26 @@ "node": ">=8.0" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -8767,6 +10473,93 @@ "dev": true, "license": "MIT" }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unrs-resolver": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", @@ -8886,6 +10679,34 @@ } } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -9043,6 +10864,16 @@ "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index dba973d..3d015b9 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,9 @@ "scripts": { "dev": "NODE_ENV=development prisma db push && NODE_ENV=development prisma db seed && next dev", "build": "next build", + "build:prod": "prisma generate && next build", "start": "next start", + "start:prod": "next start", "lint": "eslint", "prisma:generate": "prisma generate", "prisma:migrate": "prisma migrate dev", @@ -18,10 +20,13 @@ }, "dependencies": { "@auth/prisma-adapter": "^2.11.2", + "@hookform/resolvers": "^5.2.2", "@prisma/client": "^6.19.3", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-progress": "^1.1.8", + "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", "@react-email/components": "^1.0.12", @@ -35,6 +40,9 @@ "next-themes": "^0.4.6", "react": "19.2.4", "react-dom": "19.2.4", + "react-hook-form": "^7.75.0", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", "resend": "^6.12.3", "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d56a1c8..a943947 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -32,6 +32,7 @@ enum UserRole { SUPERADMIN ADMIN KOORDINATOR + ERZIEHER ELTERN } @@ -73,6 +74,18 @@ enum NotdienstAlertStatus { CANCELLED } +enum DutyAssignmentStatus { + PLANNED + DONE + CANCELLED +} + +enum AbsenceReason { + ILLNESS + VACATION + OTHER +} + // ===================================================================== // TENANT // ===================================================================== @@ -96,10 +109,15 @@ model Kita { // Relations (Cascade auf alle Tenant-Daten) users User[] + families Family[] children Child[] educators Educator[] parentDuties ParentDuty[] parentDutyAssignments ParentDutyAssignment[] + dutyTypes DutyType[] + dutyAssignments DutyAssignment[] + absences Absence[] + announcements Announcement[] invitations Invitation[] termine Termin[] mitbringselItems MitbringselItem[] @@ -107,11 +125,33 @@ model Kita { notdienstAvailabilities NotdienstAvailability[] notdienstAssignments NotdienstAssignment[] notdienstAlerts NotdienstAlert[] - childParents ChildParent[] @@map("kitas") } +// ===================================================================== +// FAMILIES / HAUSHALTE +// ===================================================================== + +model Family { + id String @id @default(cuid()) + + kitaId String + kita Kita @relation(fields: [kitaId], references: [id], onDelete: Cascade) + + name String + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + users User[] + children Child[] + dutyAssignments DutyAssignment[] + + @@index([kitaId]) + @@map("families") +} + // ===================================================================== // USERS // ===================================================================== @@ -124,6 +164,11 @@ model User { kitaId String? kita Kita? @relation(fields: [kitaId], references: [id], onDelete: Cascade) + /// Optional, weil Admins/Superadmins keinem Haushalt angehören müssen. + /// Eltern-User werden beim Löschen ihrer Familie kaskadiert entfernt. + familyId String? + family Family? @relation(fields: [familyId], references: [id], onDelete: Cascade) + email String @unique passwordHash String @@ -157,13 +202,14 @@ model User { updatedAt DateTime @updatedAt // Relations - childLinks ChildParent[] dutyAssignments ParentDutyAssignment[] notdienstAvailabilities NotdienstAvailability[] notdienstAlertsAssigned NotdienstAlert[] @relation("NotdienstAlertParent") notdienstAlertsTriggered NotdienstAlert[] @relation("NotdienstAlertTrigger") notdienstPlansCreated NotdienstPlan[] @relation("NotdienstPlanCreator") + announcementsAuthored Announcement[] @relation("AnnouncementAuthor") + announcementReads AnnouncementRead[] termineCreated Termin[] @relation("TerminCreator") termineApproved Termin[] @relation("TerminApprover") @@ -172,6 +218,7 @@ model User { invitationsCreated Invitation[] @relation("InvitationCreator") @@index([kitaId]) + @@index([familyId]) @@index([kitaId, role]) @@map("users") } @@ -185,6 +232,9 @@ model Child { kitaId String kita Kita @relation(fields: [kitaId], references: [id], onDelete: Cascade) + familyId String + family Family @relation(fields: [familyId], references: [id], onDelete: Cascade) + firstName String lastName String dateOfBirth DateTime? @@ -195,34 +245,15 @@ model Child { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - parentLinks ChildParent[] notdienstAvailabilities NotdienstAvailability[] notdienstAssignments NotdienstAssignment[] + absences Absence[] @@index([kitaId]) + @@index([familyId]) @@map("children") } -/// Verknüpfung Kind ↔ Elternteil (m:n). -/// Kaskadiert über beide Seiten, damit das Löschen eines Users -/// oder Kindes keine Datenleichen hinterlässt. -model ChildParent { - id String @id @default(cuid()) - kitaId String - kita Kita @relation(fields: [kitaId], references: [id], onDelete: Cascade) - childId String - child Child @relation(fields: [childId], references: [id], onDelete: Cascade) - userId String - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - - createdAt DateTime @default(now()) - - @@unique([childId, userId]) - @@index([kitaId]) - @@index([userId]) - @@map("child_parents") -} - // ===================================================================== // EDUCATORS (ErzieherInnen — reine Stammdaten, keine Logins, Modul 4) // ===================================================================== @@ -285,6 +316,131 @@ model ParentDutyAssignment { @@map("parent_duty_assignments") } +// ===================================================================== +// DUTY PLANNING (Top-Down-Dienstplan fuer Haushalte) +// ===================================================================== + +model DutyType { + id String @id @default(cuid()) + kitaId String + kita Kita @relation(fields: [kitaId], references: [id], onDelete: Cascade) + + name String + description String? + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + assignments DutyAssignment[] + + @@unique([kitaId, name]) + @@index([kitaId]) + @@map("duty_types") +} + +model DutyAssignment { + id String @id @default(cuid()) + kitaId String + kita Kita @relation(fields: [kitaId], references: [id], onDelete: Cascade) + + familyId String + family Family @relation(fields: [familyId], references: [id], onDelete: Cascade) + + dutyTypeId String + dutyType DutyType @relation(fields: [dutyTypeId], references: [id], onDelete: Cascade) + + startDate DateTime @db.Date + endDate DateTime @db.Date + status DutyAssignmentStatus @default(PLANNED) + + reminderSentAt DateTime? + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@unique([kitaId, dutyTypeId, startDate]) + @@index([kitaId, startDate]) + @@index([familyId, startDate]) + @@map("duty_assignments") +} + +// ===================================================================== +// ABSENCES (Abwesenheits- und Krankmeldungen) +// ===================================================================== + +model Absence { + id String @id @default(cuid()) + kitaId String + kita Kita @relation(fields: [kitaId], references: [id], onDelete: Cascade) + + childId String + child Child @relation(fields: [childId], references: [id], onDelete: Cascade) + + startDate DateTime @db.Date + endDate DateTime @db.Date + reason AbsenceReason + note String? + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([kitaId, startDate, endDate]) + @@index([childId, startDate]) + @@map("absences") +} + +// ===================================================================== +// ANNOUNCEMENTS (Digitales Schwarzes Brett) +// ===================================================================== + +model Announcement { + id String @id @default(cuid()) + kitaId String + kita Kita @relation(fields: [kitaId], references: [id], onDelete: Cascade) + + title String + content String + + authorId String + author User @relation("AnnouncementAuthor", fields: [authorId], references: [id], onDelete: Cascade) + + createdAt DateTime @default(now()) + + attachments Attachment[] + reads AnnouncementRead[] + + @@index([kitaId, createdAt]) + @@index([authorId]) + @@map("announcements") +} + +model Attachment { + id String @id @default(cuid()) + announcementId String + announcement Announcement @relation(fields: [announcementId], references: [id], onDelete: Cascade) + + fileName String + fileUrl String + fileType String + + @@index([announcementId]) + @@map("attachments") +} + +model AnnouncementRead { + id String @id @default(cuid()) + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + announcementId String + announcement Announcement @relation(fields: [announcementId], references: [id], onDelete: Cascade) + + readAt DateTime @default(now()) + + @@unique([userId, announcementId]) + @@index([announcementId]) + @@map("announcement_reads") +} + // ===================================================================== // INVITATIONS (Invite-Only Onboarding, Modul 3) // ===================================================================== diff --git a/prisma/seed.ts b/prisma/seed.ts index 505ffd0..5aabec8 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1,4 +1,5 @@ import { + AbsenceReason, InvitationStatus, NotdienstAlertStatus, NotdienstPlanStatus, @@ -29,6 +30,7 @@ const RESET_MODE = process.argv.includes("--reset"); const DEMO_USER_EMAILS = [ "super@kita-planer.local", "admin@waldameisen.local", + "erzieher@waldameisen.local", "mueller@waldameisen.local", "schmidt@waldameisen.local", "yilmaz@waldameisen.local", @@ -52,6 +54,16 @@ function addDays(date: Date, days: number) { return dateOnly(next); } +function addWeeks(date: Date, weeks: number) { + return addDays(date, weeks * 7); +} + +function startOfIsoWeek(date: Date) { + const normalized = dateOnly(date); + const day = normalized.getDay() || 7; + return addDays(normalized, 1 - day); +} + function startOfMonth(date: Date) { return new Date(date.getFullYear(), date.getMonth(), 1); } @@ -156,6 +168,27 @@ async function createDemoUsers(passwordHash: string, consentAt: Date) { }, }); + const familyMueller = await prisma.family.create({ + data: { + kitaId: kita.id, + name: "Familie Mueller", + }, + }); + + const familySchmidtYilmaz = await prisma.family.create({ + data: { + kitaId: kita.id, + name: "Familie Schmidt-Yilmaz", + }, + }); + + const familyFischer = await prisma.family.create({ + data: { + kitaId: kita.id, + name: "Familie Fischer", + }, + }); + const admin = await prisma.user.create({ data: { kitaId: kita.id, @@ -175,9 +208,28 @@ async function createDemoUsers(passwordHash: string, consentAt: Date) { }, }); + const erzieherUser = await prisma.user.create({ + data: { + kitaId: kita.id, + email: "erzieher@waldameisen.local", + passwordHash, + firstName: "Eva", + lastName: "Erzieherin", + role: UserRole.ERZIEHER, + privacyPolicyAcceptedAt: consentAt, + privacyPolicyVersion: PRIVACY_POLICY_VERSION, + emailVerifiedAt: consentAt, + phone: "+49 30 1000 2000", + street: "Kitaweg 1", + postalCode: "10115", + city: "Berlin", + }, + }); + const koordinator = await prisma.user.create({ data: { kitaId: kita.id, + familyId: familyMueller.id, email: "mueller@waldameisen.local", passwordHash, firstName: "Maria", @@ -197,6 +249,7 @@ async function createDemoUsers(passwordHash: string, consentAt: Date) { const elternSchmidt = await prisma.user.create({ data: { kitaId: kita.id, + familyId: familySchmidtYilmaz.id, email: "schmidt@waldameisen.local", passwordHash, firstName: "Lukas", @@ -211,6 +264,7 @@ async function createDemoUsers(passwordHash: string, consentAt: Date) { const elternYilmaz = await prisma.user.create({ data: { kitaId: kita.id, + familyId: familySchmidtYilmaz.id, email: "yilmaz@waldameisen.local", passwordHash, firstName: "Aylin", @@ -230,6 +284,7 @@ async function createDemoUsers(passwordHash: string, consentAt: Date) { const pendingParent = await prisma.user.create({ data: { kitaId: kita.id, + familyId: familyFischer.id, email: "pending@waldameisen.local", passwordHash: "", firstName: "Lena", @@ -240,8 +295,12 @@ async function createDemoUsers(passwordHash: string, consentAt: Date) { return { kita, + familyMueller, + familySchmidtYilmaz, + familyFischer, superAdmin, admin, + erzieherUser, koordinator, elternSchmidt, elternYilmaz, @@ -251,72 +310,58 @@ async function createDemoUsers(passwordHash: string, consentAt: Date) { async function createChildren({ kita, - koordinator, - elternSchmidt, - elternYilmaz, - pendingParent, + familyMueller, + familySchmidtYilmaz, + familyFischer, }: SeedContext) { const anna = await prisma.child.create({ data: { kitaId: kita.id, + familyId: familyMueller.id, firstName: "Anna", lastName: "Mueller", dateOfBirth: new Date("2021-03-15"), - parentLinks: { - create: { kitaId: kita.id, userId: koordinator.id }, - }, }, }); const ben = await prisma.child.create({ data: { kitaId: kita.id, + familyId: familyMueller.id, firstName: "Ben", lastName: "Mueller", dateOfBirth: new Date("2023-07-22"), notes: "Geschwisterkind von Anna.", - parentLinks: { - create: { kitaId: kita.id, userId: koordinator.id }, - }, }, }); const clara = await prisma.child.create({ data: { kitaId: kita.id, + familyId: familySchmidtYilmaz.id, firstName: "Clara", lastName: "Schmidt", dateOfBirth: new Date("2022-11-03"), - parentLinks: { - create: { kitaId: kita.id, userId: elternSchmidt.id }, - }, }, }); const emil = await prisma.child.create({ data: { kitaId: kita.id, + familyId: familySchmidtYilmaz.id, firstName: "Emil", lastName: "Yilmaz", dateOfBirth: new Date("2021-09-09"), - parentLinks: { - create: [ - { kitaId: kita.id, userId: elternYilmaz.id }, - { kitaId: kita.id, userId: elternSchmidt.id }, - ], - }, }, }); const nina = await prisma.child.create({ data: { kitaId: kita.id, + familyId: familyFischer.id, firstName: "Nina", lastName: "Fischer", dateOfBirth: new Date("2022-05-30"), - parentLinks: { - create: { kitaId: kita.id, userId: pendingParent.id }, - }, }, }); @@ -384,6 +429,83 @@ async function createParentDuties({ }); } +async function createAbsences( + { kita }: SeedContext, + children: Awaited>, +) { + const today = dateOnly(new Date()); + + await prisma.absence.createMany({ + data: [ + { + kitaId: kita.id, + childId: children.nina.id, + startDate: today, + endDate: today, + reason: AbsenceReason.ILLNESS, + note: "Fieber, bleibt heute zuhause.", + }, + { + kitaId: kita.id, + childId: children.emil.id, + startDate: addDays(today, 1), + endDate: addDays(today, 2), + reason: AbsenceReason.VACATION, + note: "Familienbesuch.", + }, + ], + }); +} + +async function createDutyPlan({ + kita, + familyMueller, + familySchmidtYilmaz, + familyFischer, +}: SeedContext) { + const waesche = await prisma.dutyType.create({ + data: { + kitaId: kita.id, + name: "Waeschedienst", + description: "Woechentlicher Dienstplan fuer Kita-Waesche.", + }, + }); + + const einkauf = await prisma.dutyType.create({ + data: { + kitaId: kita.id, + name: "Einkauf", + description: "Woechentlicher Einkauf nach Kita-Liste.", + }, + }); + + const families = [familyMueller, familySchmidtYilmaz, familyFischer]; + const currentWeek = startOfIsoWeek(new Date()); + + await prisma.dutyAssignment.createMany({ + data: Array.from({ length: 8 }).flatMap((_, index) => { + const startDate = addWeeks(currentWeek, index); + const endDate = addDays(startDate, 6); + return [ + { + kitaId: kita.id, + dutyTypeId: waesche.id, + familyId: families[index % families.length].id, + startDate, + endDate, + }, + { + kitaId: kita.id, + dutyTypeId: einkauf.id, + familyId: families[(index + 1) % families.length].id, + startDate, + endDate, + }, + ]; + }), + }); +} + async function createInvites({ kita, admin, pendingParent }: SeedContext) { const expires = addDays(new Date(), 7); @@ -408,6 +530,39 @@ async function createInvites({ kita, admin, pendingParent }: SeedContext) { }); } +async function createAnnouncements({ + kita, + admin, + koordinator, +}: SeedContext) { + const sommerfest = await prisma.announcement.create({ + data: { + kitaId: kita.id, + title: "Sommerfest: Helferliste und Ablauf", + content: + "## Liebe Familien,\n\nunser Sommerfest findet naechsten Monat im Kita-Garten statt. Bitte merkt euch den Termin vor. Details zu Aufbau, Kuchen und Getraenken folgen ueber das Schwarze Brett.", + authorId: admin.id, + }, + }); + + await prisma.announcement.create({ + data: { + kitaId: kita.id, + title: "Neue Garderoben-Regelung", + content: + "Ab Montag bitten wir alle Familien, Wechselkleidung wieder in die beschrifteten Boxen zu legen. So bleibt der Morgen fuer Kinder und Team entspannter.", + authorId: admin.id, + }, + }); + + await prisma.announcementRead.create({ + data: { + userId: koordinator.id, + announcementId: sommerfest.id, + }, + }); +} + async function createTermine({ kita, admin, @@ -601,7 +756,10 @@ async function createDemoData() { const educators = await createEducators(context.kita.id); await createParentDuties(context); + await createDutyPlan(context); + await createAbsences(context, children); await createInvites(context); + await createAnnouncements(context); await createTermine(context); await createNotdienstData(context, children, educators); @@ -622,6 +780,7 @@ function printSummary( kita, superAdmin, admin, + erzieherUser, koordinator, elternSchmidt, elternYilmaz, @@ -636,6 +795,7 @@ function printSummary( console.log(` Logins (Passwort jeweils: ${DEFAULT_PASSWORD})`); console.log(` Superadmin: ${superAdmin.email}`); console.log(` Admin: ${admin.email}`); + console.log(` Erzieherin: ${erzieherUser.email}`); console.log(` Koordinator: ${koordinator.email}`); console.log(` Eltern: ${elternSchmidt.email}`); console.log(` Eltern: ${elternYilmaz.email}`); diff --git a/public/og-candidates/01-holzkloetze-kind.png b/public/og-candidates/01-holzkloetze-kind.png new file mode 100644 index 0000000..6c05c98 Binary files /dev/null and b/public/og-candidates/01-holzkloetze-kind.png differ diff --git a/public/og-image.png b/public/og-image.png new file mode 100644 index 0000000..6c05c98 Binary files /dev/null and b/public/og-image.png differ diff --git a/scripts/start-production.sh b/scripts/start-production.sh new file mode 100755 index 0000000..0ee8b4a --- /dev/null +++ b/scripts/start-production.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -eu + +echo "Applying Prisma schema..." +npx prisma db push --skip-generate + +echo "Starting Kita-Planer..." +exec npm run start:prod diff --git a/src/actions/absences.ts b/src/actions/absences.ts new file mode 100644 index 0000000..aaa68f2 --- /dev/null +++ b/src/actions/absences.ts @@ -0,0 +1,198 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { AbsenceReason, UserRole } from "@prisma/client"; +import { z } from "zod"; + +import { requireKitaSession, requireRole } from "@/lib/auth-utils"; +import { prisma } from "@/lib/prisma"; + +const reportAbsenceSchema = z.object({ + childId: z.string().min(1), + startDate: z.string().min(1), + endDate: z.string().min(1), + reason: z.nativeEnum(AbsenceReason), + note: z.string().trim().max(500).optional(), +}); + +const dailyAbsenceSchema = z.object({ + date: z.string().min(1), +}); + +export type DailyAbsenceDto = { + id: string; + childName: string; + familyName: string; + reason: AbsenceReason; + note: string | null; + startDate: string; + endDate: string; +}; + +function parseDateOnly(value: string) { + const date = new Date(`${value.slice(0, 10)}T00:00:00`); + if (Number.isNaN(date.getTime())) { + throw new Error("Ungueltiges Datum."); + } + return date; +} + +function toDateKey(date: Date) { + return date.toISOString().slice(0, 10); +} + +export async function reportAbsence(rawPayload: unknown) { + const session = await requireKitaSession(); + const parsed = reportAbsenceSchema.safeParse(rawPayload); + + if (!parsed.success) { + return { error: "Ungültige Eingabedaten." }; + } + + if (!session.user.familyId) { + return { error: "Dein Account ist noch keinem Haushalt zugeordnet." }; + } + + const startDate = parseDateOnly(parsed.data.startDate); + const endDate = parseDateOnly(parsed.data.endDate); + + if (endDate < startDate) { + return { error: "Das Enddatum darf nicht vor dem Startdatum liegen." }; + } + + const child = await prisma.child.findFirst({ + where: { + id: parsed.data.childId, + kitaId: session.user.kitaId, + familyId: session.user.familyId, + active: true, + }, + select: { id: true }, + }); + + if (!child) { + return { error: "Dieses Kind gehört nicht zu deinem Haushalt." }; + } + + const overlappingAbsence = await prisma.absence.findFirst({ + where: { + kitaId: session.user.kitaId, + childId: child.id, + startDate: { lte: endDate }, + endDate: { gte: startDate }, + }, + select: { id: true }, + }); + + if (overlappingAbsence) { + return { error: "Für diesen Zeitraum existiert bereits eine Abmeldung." }; + } + + await prisma.absence.create({ + data: { + kitaId: session.user.kitaId, + childId: child.id, + startDate, + endDate, + reason: parsed.data.reason, + note: parsed.data.note || null, + }, + }); + + revalidatePath("/dashboard"); + revalidatePath("/dashboard/admin/abwesenheiten"); + return { success: true }; +} + +export async function deleteAbsence(id: string) { + const session = await requireKitaSession(); + + if (!session.user.familyId) { + return { error: "Dein Account ist noch keinem Haushalt zugeordnet." }; + } + + const absence = await prisma.absence.findFirst({ + where: { + id, + kitaId: session.user.kitaId, + child: { + familyId: session.user.familyId, + }, + }, + select: { id: true }, + }); + + if (!absence) { + return { error: "Diese Abmeldung gehört nicht zu deinem Haushalt." }; + } + + await prisma.absence.delete({ + where: { id: absence.id }, + }); + + revalidatePath("/dashboard"); + revalidatePath("/dashboard/admin/abwesenheiten"); + return { success: true }; +} + +export async function getDailyAbsences( + rawPayload: unknown, +): Promise<{ absences?: DailyAbsenceDto[]; error?: string }> { + const session = await requireRole([ + UserRole.ADMIN, + UserRole.KOORDINATOR, + UserRole.ERZIEHER, + ]); + + if (!session.user.kitaId) { + return { error: "Kein Mandant zugeordnet." }; + } + + const parsed = dailyAbsenceSchema.safeParse(rawPayload); + if (!parsed.success) { + return { error: "Ungültiges Datum." }; + } + + const date = parseDateOnly(parsed.data.date); + const rows = await prisma.absence.findMany({ + where: { + kitaId: session.user.kitaId, + startDate: { lte: date }, + endDate: { gte: date }, + }, + select: { + id: true, + reason: true, + note: true, + startDate: true, + endDate: true, + child: { + select: { + firstName: true, + lastName: true, + family: { + select: { + name: true, + }, + }, + }, + }, + }, + orderBy: [ + { child: { lastName: "asc" } }, + { child: { firstName: "asc" } }, + ], + }); + + return { + absences: rows.map((absence) => ({ + id: absence.id, + childName: `${absence.child.firstName} ${absence.child.lastName}`, + familyName: absence.child.family.name, + reason: absence.reason, + note: absence.note, + startDate: toDateKey(absence.startDate), + endDate: toDateKey(absence.endDate), + })), + }; +} diff --git a/src/actions/announcements.ts b/src/actions/announcements.ts new file mode 100644 index 0000000..d781ef8 --- /dev/null +++ b/src/actions/announcements.ts @@ -0,0 +1,245 @@ +"use server"; + +import { randomUUID } from "crypto"; +import { mkdir, rm, writeFile } from "fs/promises"; +import path from "path"; +import { createElement } from "react"; +import { revalidatePath } from "next/cache"; +import { Prisma, UserRole } from "@prisma/client"; +import { z } from "zod"; + +import { NewsEmail } from "@/emails/NewsEmail"; +import { requireKitaSession, requireRole } from "@/lib/auth-utils"; +import { getAppEmailConfigError, sendAppEmail } from "@/lib/mail"; +import { prisma } from "@/lib/prisma"; + +const MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024; +const ALLOWED_TYPES = new Map([ + ["application/pdf", ".pdf"], + ["image/jpeg", ".jpg"], + ["image/png", ".png"], +]); + +const ALLOWED_EXTENSIONS = new Set([".pdf", ".jpg", ".jpeg", ".png"]); + +const audienceSchema = z.enum(["ELTERN", "ERZIEHER", "ALLE"]); + +const announcementSchema = z.object({ + title: z.string().trim().min(3).max(160), + content: z.string().trim().min(3).max(20_000), + sendEmail: z.boolean(), + audience: audienceSchema, +}); + +function getUploadDir() { + return path.resolve(process.env.UPLOAD_DIR ?? "uploads", "announcements"); +} + +function getSafeFileName(name: string) { + return name.replace(/[^\w.\- äöüÄÖÜß]/g, "_").slice(0, 160); +} + +function validateAttachment(file: File) { + const extension = path.extname(file.name).toLowerCase(); + const typeAllowed = ALLOWED_TYPES.has(file.type); + const extensionAllowed = ALLOWED_EXTENSIONS.has(extension); + + if (!typeAllowed && !extensionAllowed) { + return "Nur PDF, JPG und PNG sind erlaubt."; + } + + if (file.size > MAX_ATTACHMENT_SIZE) { + return "Anhänge dürfen maximal 10 MB groß sein."; + } + + return null; +} + +async function getRecipients(kitaId: string, audience: z.infer) { + const where: Prisma.UserWhereInput = + audience === "ELTERN" + ? { + kitaId, + familyId: { not: null }, + emailVerifiedAt: { not: null }, + } + : audience === "ERZIEHER" + ? { + kitaId, + role: UserRole.ERZIEHER, + emailVerifiedAt: { not: null }, + } + : { + kitaId, + emailVerifiedAt: { not: null }, + OR: [{ familyId: { not: null } }, { role: UserRole.ERZIEHER }], + }; + + const users = await prisma.user.findMany({ + where, + select: { email: true }, + }); + + return Array.from(new Set(users.map((user) => user.email))); +} + +export async function createAnnouncement(formData: FormData) { + const session = await requireRole([UserRole.ADMIN]); + const kitaId = session.user.kitaId; + + if (!kitaId) { + return { error: "Kein Mandant zugeordnet." }; + } + + const parsed = announcementSchema.safeParse({ + title: formData.get("title"), + content: formData.get("content"), + sendEmail: formData.get("sendEmail") === "on", + audience: formData.get("audience") || "ELTERN", + }); + + if (!parsed.success) { + return { error: "Bitte Titel und Inhalt ausfüllen." }; + } + + const attachmentFiles = formData + .getAll("attachments") + .filter((value): value is File => value instanceof File && value.size > 0); + + for (const file of attachmentFiles) { + const error = validateAttachment(file); + if (error) { + return { error }; + } + } + + const attachmentInputs = attachmentFiles.map((file) => { + const id = randomUUID(); + return { + id, + file, + fileName: getSafeFileName(file.name), + fileType: file.type || "application/octet-stream", + fileUrl: `/api/announcements/attachments/${id}`, + }; + }); + + const announcement = await prisma.announcement.create({ + data: { + kitaId, + title: parsed.data.title, + content: parsed.data.content, + authorId: session.user.id, + attachments: { + create: attachmentInputs.map((attachment) => ({ + id: attachment.id, + fileName: attachment.fileName, + fileType: attachment.fileType, + fileUrl: attachment.fileUrl, + })), + }, + }, + select: { + id: true, + title: true, + content: true, + }, + }); + + if (attachmentInputs.length > 0) { + const uploadDir = getUploadDir(); + try { + await mkdir(uploadDir, { recursive: true }); + await Promise.all( + attachmentInputs.map(async (attachment) => { + const arrayBuffer = await attachment.file.arrayBuffer(); + await writeFile( + path.join(uploadDir, attachment.id), + Buffer.from(arrayBuffer), + ); + }), + ); + } catch (error) { + await prisma.announcement.delete({ where: { id: announcement.id } }); + await Promise.all( + attachmentInputs.map((attachment) => + rm(path.join(uploadDir, attachment.id), { force: true }), + ), + ); + console.error("Ankündigungs-Anhang konnte nicht gespeichert werden:", error); + return { error: "Anhang konnte nicht gespeichert werden." }; + } + } + + let emailError: string | null = null; + if (parsed.data.sendEmail) { + const configError = getAppEmailConfigError(); + if (configError) { + emailError = configError; + } else { + const recipients = await getRecipients(kitaId, parsed.data.audience); + if (recipients.length > 0) { + const result = await sendAppEmail({ + to: recipients, + subject: `Neue Kita-Ankündigung: ${announcement.title}`, + react: createElement(NewsEmail, { + title: announcement.title, + content: announcement.content, + dashboardUrl: `${process.env.NEXTAUTH_URL ?? process.env.AUTH_URL ?? "http://localhost:3000"}/dashboard`, + }), + }); + + if (!result.success) { + emailError = result.error; + } + } + } + } + + revalidatePath("/dashboard"); + revalidatePath("/dashboard/admin/news"); + + if (emailError) { + return { + success: true, + warning: `Ankündigung gespeichert, E-Mail-Versand fehlgeschlagen: ${emailError}`, + }; + } + + return { success: true }; +} + +export async function markAnnouncementRead(announcementId: string) { + const session = await requireKitaSession(); + + const announcement = await prisma.announcement.findFirst({ + where: { + id: announcementId, + kitaId: session.user.kitaId, + }, + select: { id: true }, + }); + + if (!announcement) { + return { error: "Ankündigung wurde nicht gefunden." }; + } + + await prisma.announcementRead.upsert({ + where: { + userId_announcementId: { + userId: session.user.id, + announcementId: announcement.id, + }, + }, + create: { + userId: session.user.id, + announcementId: announcement.id, + }, + update: { + readAt: new Date(), + }, + }); + + revalidatePath("/dashboard"); + return { success: true }; +} diff --git a/src/actions/contact.ts b/src/actions/contact.ts new file mode 100644 index 0000000..49c1757 --- /dev/null +++ b/src/actions/contact.ts @@ -0,0 +1,50 @@ +"use server"; + +import { createElement } from "react"; + +import { ContactRequestEmail } from "@/emails/ContactRequestEmail"; +import { + contactRequestSchema, + contactRequestTypeLabels, + type ContactRequestInput, +} from "@/lib/contact-schema"; +import { getAppEmailConfigError, sendAppEmail } from "@/lib/mail"; + +const DEFAULT_ADMIN_EMAIL = "kontakt@kita-planer.local"; + +export async function submitContactRequest(data: ContactRequestInput) { + const parsed = contactRequestSchema.safeParse(data); + + if (!parsed.success) { + return { + success: false, + error: "Bitte prüfe deine Eingaben und versuche es erneut.", + }; + } + + const configError = getAppEmailConfigError(); + if (configError) { + return { + success: false, + error: configError, + }; + } + + const adminEmail = process.env.ADMIN_EMAIL || DEFAULT_ADMIN_EMAIL; + const inquiryLabel = contactRequestTypeLabels[parsed.data.requestType]; + const result = await sendAppEmail({ + to: adminEmail, + subject: `Kontaktanfrage: ${inquiryLabel} von ${parsed.data.name}`, + replyTo: parsed.data.email, + react: createElement(ContactRequestEmail, parsed.data), + }); + + if (!result.success) { + return { + success: false, + error: result.error, + }; + } + + return { success: true }; +} diff --git a/src/app/alert/[token]/actions.ts b/src/app/alert/[token]/actions.ts index 745d292..6b5536d 100644 --- a/src/app/alert/[token]/actions.ts +++ b/src/app/alert/[token]/actions.ts @@ -28,7 +28,7 @@ export async function confirmAlertAction(token: string) { revalidatePath("/dashboard"); return { success: true }; - } catch (error: any) { + } catch { return { error: "Fehler bei der Bestätigung." }; } } diff --git a/src/app/alert/[token]/page.tsx b/src/app/alert/[token]/page.tsx index f91a909..174a015 100644 --- a/src/app/alert/[token]/page.tsx +++ b/src/app/alert/[token]/page.tsx @@ -25,10 +25,29 @@ export default async function AlertPage({ const alert = await prisma.notdienstAlert.findUnique({ where: { confirmationToken: token }, - include: { - parentUser: true, - kita: true, - assignment: { include: { child: true } }, + select: { + status: true, + parentUser: { + select: { + firstName: true, + }, + }, + kita: { + select: { + name: true, + }, + }, + assignment: { + select: { + date: true, + child: { + select: { + firstName: true, + lastName: true, + }, + }, + }, + }, }, }); diff --git a/src/app/api/announcements/attachments/[id]/route.ts b/src/app/api/announcements/attachments/[id]/route.ts new file mode 100644 index 0000000..5ed780f --- /dev/null +++ b/src/app/api/announcements/attachments/[id]/route.ts @@ -0,0 +1,66 @@ +import { readFile } from "fs/promises"; +import path from "path"; +import { NextResponse } from "next/server"; + +import { auth } from "@/auth"; +import { prisma } from "@/lib/prisma"; + +export const runtime = "nodejs"; + +function getUploadPath(attachmentId: string) { + const uploadDir = path.resolve( + process.env.UPLOAD_DIR ?? "uploads", + "announcements", + ); + return path.join(uploadDir, attachmentId); +} + +function contentDisposition(fileName: string) { + const fallback = fileName.replace(/[^\w.\-]/g, "_"); + return `inline; filename="${fallback}"; filename*=UTF-8''${encodeURIComponent(fileName)}`; +} + +export async function GET( + _request: Request, + { params }: { params: Promise<{ id: string }> }, +) { + const session = await auth(); + const user = session?.user; + + if (!user?.id || !user.kitaId) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const { id } = await params; + const attachment = await prisma.attachment.findFirst({ + where: { + id, + announcement: { + kitaId: user.kitaId, + }, + }, + select: { + id: true, + fileName: true, + fileType: true, + }, + }); + + if (!attachment) { + return NextResponse.json({ error: "Not found" }, { status: 404 }); + } + + try { + const file = await readFile(getUploadPath(attachment.id)); + return new Response(new Uint8Array(file), { + headers: { + "Content-Type": attachment.fileType, + "Content-Disposition": contentDisposition(attachment.fileName), + "Cache-Control": "private, max-age=300", + }, + }); + } catch (error) { + console.error("Anhang konnte nicht gelesen werden:", error); + return NextResponse.json({ error: "File not found" }, { status: 404 }); + } +} diff --git a/src/app/contact-form.tsx b/src/app/contact-form.tsx new file mode 100644 index 0000000..4ce31dd --- /dev/null +++ b/src/app/contact-form.tsx @@ -0,0 +1,185 @@ +"use client"; + +import { useTransition } from "react"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Loader2, Send } from "lucide-react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; + +import { submitContactRequest } from "@/actions/contact"; +import { + contactRequestSchema, + contactRequestTypeLabels, + type ContactRequestInput, +} from "@/lib/contact-schema"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Textarea } from "@/components/ui/textarea"; + +export function ContactForm() { + const [isPending, startTransition] = useTransition(); + const form = useForm({ + resolver: zodResolver(contactRequestSchema), + defaultValues: { + name: "", + kitaName: "", + email: "", + requestType: "DEMO", + message: "", + }, + }); + + function onSubmit(values: ContactRequestInput) { + startTransition(async () => { + const result = await submitContactRequest(values); + + if (!result.success) { + toast.error(result.error); + return; + } + + toast.success("Danke! Wir melden uns in Kürze bei euch."); + form.reset({ + name: "", + kitaName: "", + email: "", + requestType: "DEMO", + message: "", + }); + }); + } + + return ( +
+ + ( + + Name + + + + + + )} + /> + + ( + + Name der Kita / Initiative + + + + + + )} + /> + + ( + + E-Mail-Adresse + + + + + + )} + /> + + ( + + Art der Anfrage + + + + )} + /> + + ( + + Nachricht + +