diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..139a534
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,7 @@
+node_modules
+.next
+.git
+.gitignore
+npm-debug.log
+Dockerfile
+.dockerignore
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..44dcb4e
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,7 @@
+DATABASE_URL="postgresql://postgres:postgres@localhost:5432/moh_sass?schema=public"
+NEXT_PUBLIC_APP_URL="http://localhost:3000"
+NEXT_TELEMETRY_DISABLED="1"
+ADMIN_PASSWORD="123Yolo!321"
+ADMIN_AUTH_SECRET="Us0z76jwlTQLOeQWAGGxAxDcc0rHwp4q"
+ROOT_BASIC_AUTH_USER="root"
+ROOT_BASIC_AUTH_PASS="123Yolo!321"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..3a6b02b
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,33 @@
+name: CI
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ quality:
+ runs-on: ubuntu-latest
+ env:
+ DATABASE_URL: postgresql://postgres:postgres@localhost:5432/moh_sass?schema=public
+ NEXT_TELEMETRY_DISABLED: "1"
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ cache: npm
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Generate Prisma client
+ run: npm run prisma:generate
+
+ - name: Lint
+ run: npm run lint
+
+ - name: Build
+ run: npm run build
diff --git a/.gitignore b/.gitignore
index fd3dbb5..a84107a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,7 @@ yarn-debug.log*
yarn-error.log*
# local env files
+.env
.env*.local
# vercel
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..a1338fb
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,12 @@
+FROM node:22-alpine
+
+WORKDIR /app
+
+COPY package*.json ./
+RUN npm install
+
+COPY . .
+
+EXPOSE 3000
+
+CMD ["npm", "run", "dev", "--", "--hostname", "0.0.0.0", "--port", "3000"]
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..0a6d610
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,70 @@
+.PHONY: start stop restart logs build ps port health clean-orphans app-shell db-shell db-init db-migrate db-seed prisma-generate prisma-migrate help
+
+MIGRATION_NAME ?= init
+
+start:
+ docker compose up -d --build
+
+stop:
+ docker compose down
+
+restart: stop start
+
+logs:
+ docker compose logs -f --tail=200
+
+build:
+ docker compose build
+
+ps:
+ docker compose ps
+
+port:
+ docker compose port app 3000
+
+clean-orphans:
+ docker compose up -d --remove-orphans
+
+app-shell:
+ docker compose exec app sh
+
+db-shell:
+ docker compose exec db psql -U postgres -d moh_sass
+
+db-init:
+ docker compose exec app sh -lc "npx prisma generate && npx prisma migrate deploy && npx prisma db seed"
+
+db-migrate:
+ docker compose exec app npx prisma migrate deploy
+
+db-seed:
+ docker compose exec app npx prisma db seed
+
+prisma-generate:
+ docker compose exec app npx prisma generate
+
+prisma-migrate:
+ docker compose exec app npx prisma migrate dev --name $(MIGRATION_NAME)
+
+health:
+ @PORT=$$(docker compose port app 3000 | sed 's/.*://'); \
+ curl -sS "http://localhost:$$PORT/api/health"
+
+help:
+ @echo "Available targets:"
+ @echo " make start Start all containers"
+ @echo " make stop Stop and remove containers"
+ @echo " make restart Restart all containers"
+ @echo " make logs Follow container logs"
+ @echo " make build Build images"
+ @echo " make ps Show container status"
+ @echo " make port Show random host port for app"
+ @echo " make clean-orphans Remove orphaned old containers"
+ @echo " make app-shell Open shell in app container"
+ @echo " make db-shell Open PostgreSQL shell"
+ @echo " make db-init Generate client, apply migrations, run seed"
+ @echo " make db-migrate Apply prisma migrations"
+ @echo " make db-seed Seed database data"
+ @echo " make prisma-generate Run prisma generate"
+ @echo " make prisma-migrate Create/apply dev migration"
+ @echo " make health Check app health endpoint"
diff --git a/README.md b/README.md
index e215bc4..3660b7e 100644
--- a/README.md
+++ b/README.md
@@ -34,3 +34,37 @@ You can check out [the Next.js GitHub repository](https://github.com/vercel/next
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
+
+## Infrastructure Basics
+
+1. Copy environment variables:
+
+```bash
+cp .env.example .env
+```
+
+2. Start containers:
+
+```bash
+make start
+make port
+```
+
+3. Initialize database:
+
+```bash
+make db-init
+```
+
+4. Run quality checks:
+
+```bash
+npm run lint
+npm run build
+```
+
+5. Health check:
+
+```bash
+make health
+```
diff --git a/app/[locale]/(site)/about/page.tsx b/app/[locale]/(site)/about/page.tsx
new file mode 100644
index 0000000..8866996
--- /dev/null
+++ b/app/[locale]/(site)/about/page.tsx
@@ -0,0 +1,122 @@
+import { Compass, Layers3, Users } from "lucide-react";
+
+import { MotionFade } from "@/components/motion-fade";
+import { resolveLocale } from "@/lib/site-data";
+
+type AboutPageProps = {
+ params: {
+ locale: string;
+ };
+};
+
+export default function AboutPage({ params: { locale } }: AboutPageProps) {
+ const localeKey = resolveLocale(locale);
+
+ const copy =
+ localeKey === "de"
+ ? {
+ title: "Ueber uns",
+ intro:
+ "Wir bauen klare digitale Erlebnisse fuer Marken, Produkte und Teams.",
+ valuesTitle: "Wie wir arbeiten",
+ valueA: "Strategie zuerst",
+ valueAText: "Jedes Projekt startet mit Zielbild, Scope und Prioritaeten.",
+ valueB: "Saubere Systeme",
+ valueBText: "Wir setzen auf wartbare Komponenten und klare Strukturen.",
+ valueC: "Enge Zusammenarbeit",
+ valueCText: "Kurze Schleifen mit direktem Feedback im gesamten Ablauf.",
+ processTitle: "Unser Ablauf",
+ processOne: "Discovery und Zieldefinition",
+ processTwo: "Design und Prototyping",
+ processThree: "Build, Test und Launch",
+ }
+ : {
+ title: "About",
+ intro:
+ "We build clear digital experiences for brands, products and teams.",
+ valuesTitle: "How we work",
+ valueA: "Strategy first",
+ valueAText: "Each project starts with goals, scope and priorities.",
+ valueB: "Clean systems",
+ valueBText: "We rely on maintainable components and clear structure.",
+ valueC: "Close collaboration",
+ valueCText: "Short loops with direct feedback across the full process.",
+ processTitle: "Our process",
+ processOne: "Discovery and goal definition",
+ processTwo: "Design and prototyping",
+ processThree: "Build, test and launch",
+ };
+
+ return (
+
+
+
+
+ {copy.title}
+
+
+ {copy.intro}
+
+
+
+
+
+
+
+ {copy.valuesTitle}
+
+
+
+
+
+ {copy.valueA}
+
+
+ {copy.valueAText}
+
+
+
+
+
+ {copy.valueB}
+
+
+ {copy.valueBText}
+
+
+
+
+
+ {copy.valueC}
+
+
+ {copy.valueCText}
+
+
+
+
+
+
+
+
+
+ {copy.processTitle}
+
+
+ {[copy.processOne, copy.processTwo, copy.processThree].map((step, index) => (
+ -
+
+ {index + 1}
+
+
{step}
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/app/[locale]/(site)/contact/page.tsx b/app/[locale]/(site)/contact/page.tsx
new file mode 100644
index 0000000..dbe8f52
--- /dev/null
+++ b/app/[locale]/(site)/contact/page.tsx
@@ -0,0 +1,120 @@
+import { Mail, MapPin, Phone } from "lucide-react";
+import Link from "next/link";
+
+import { MotionFade } from "@/components/motion-fade";
+import { resolveLocale } from "@/lib/site-data";
+
+type ContactPageProps = {
+ params: {
+ locale: string;
+ };
+};
+
+export default function ContactPage({ params: { locale } }: ContactPageProps) {
+ const localeKey = resolveLocale(locale);
+
+ const copy =
+ localeKey === "de"
+ ? {
+ title: "Kontakt",
+ intro: "Schreib uns kurz dein Ziel und wir melden uns zeitnah.",
+ name: "Name",
+ email: "E-Mail",
+ message: "Nachricht",
+ submit: "Senden",
+ preview: "Success Seite ansehen",
+ phone: "+49 30 123456",
+ mail: "hello@moh-sass.dev",
+ city: "Berlin, Germany",
+ }
+ : {
+ title: "Contact",
+ intro: "Share your goal and we will get back quickly.",
+ name: "Name",
+ email: "Email",
+ message: "Message",
+ submit: "Submit",
+ preview: "Open success page",
+ phone: "+49 30 123456",
+ mail: "hello@moh-sass.dev",
+ city: "Berlin, Germany",
+ };
+
+ return (
+
+
+
+
+ {copy.title}
+
+
+ {copy.intro}
+
+
+
+ -
+
+ {copy.phone}
+
+ -
+
+ {copy.mail}
+
+ -
+
+ {copy.city}
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/[locale]/(site)/layout.tsx b/app/[locale]/(site)/layout.tsx
new file mode 100644
index 0000000..401e0a9
--- /dev/null
+++ b/app/[locale]/(site)/layout.tsx
@@ -0,0 +1,18 @@
+import type { ReactNode } from "react";
+
+import { Footer } from "@/components/footer";
+import { Navbar } from "@/components/navbar";
+
+type SiteLayoutProps = {
+ children: ReactNode;
+};
+
+export default function SiteLayout({ children }: SiteLayoutProps) {
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/app/[locale]/(site)/page.tsx b/app/[locale]/(site)/page.tsx
new file mode 100644
index 0000000..b94a163
--- /dev/null
+++ b/app/[locale]/(site)/page.tsx
@@ -0,0 +1,206 @@
+import {
+ ArrowRight,
+ BriefcaseBusiness,
+ Boxes,
+ Mail,
+ Sparkles,
+} from "lucide-react";
+import Link from "next/link";
+
+import { MotionFade } from "@/components/motion-fade";
+import { pickText, portfolioItems, productItems, resolveLocale } from "@/lib/site-data";
+
+type HomePageProps = {
+ params: {
+ locale: string;
+ };
+};
+
+export default function HomePage({ params: { locale } }: HomePageProps) {
+ const localeKey = resolveLocale(locale);
+ const featuredProjects = portfolioItems.slice(0, 3);
+ const featuredProducts = productItems.slice(0, 3);
+
+ const copy =
+ localeKey === "de"
+ ? {
+ heroKicker: "Digital Studio",
+ heroTitle: "Webseiten und Produkte, die schnell liefern.",
+ heroText:
+ "Diese Startseite ist die Basis fuer ein mehrsprachiges Marketing- und Produkt-Setup.",
+ portfolioTitle: "Featured Projects",
+ portfolioText: "Platzhalter fuer ausgewaehlte Kundenprojekte.",
+ productsTitle: "Featured Products",
+ productsText: "Platzhalter fuer die wichtigsten Produktangebote.",
+ ctaTitle: "Bereit fuer den naechsten Schritt?",
+ ctaText: "Wir planen zusammen den passenden Scope fuer dein Projekt.",
+ toPortfolio: "Portfolio ansehen",
+ toProducts: "Produkte ansehen",
+ toContact: "Kontakt aufnehmen",
+ heroCardTitle: "Schneller Rollout",
+ heroCardText: "Struktur, Content und Komponenten fuer schnelles Wachstum.",
+ }
+ : {
+ heroKicker: "Digital Studio",
+ heroTitle: "Websites and products that ship fast.",
+ heroText:
+ "This homepage is a starter for a multilingual marketing and product setup.",
+ portfolioTitle: "Featured Projects",
+ portfolioText: "Placeholder area for highlighted client projects.",
+ productsTitle: "Featured Products",
+ productsText: "Placeholder area for top product offerings.",
+ ctaTitle: "Ready for your next step?",
+ ctaText: "We can shape the right project scope together.",
+ toPortfolio: "View portfolio",
+ toProducts: "View products",
+ toContact: "Contact us",
+ heroCardTitle: "Fast rollout",
+ heroCardText: "Structure, content and components for rapid growth.",
+ };
+
+ return (
+
+
+
+
+
+
+ {copy.heroKicker}
+
+
+ {copy.heroTitle}
+
+
+ {copy.heroText}
+
+
+
+ {copy.toPortfolio}
+
+
+
+ {copy.toProducts}
+
+
+
+
+
+
+
+ {copy.heroCardTitle}
+
+
+ {copy.heroCardText}
+
+
+
+
+
+
+
+
+
+
+ {copy.portfolioTitle}
+
+
+ {copy.portfolioText}
+
+
+
+
+
+ {featuredProjects.map((item) => (
+
+
+ {pickText(item.category, localeKey)} - {item.year}
+
+
+ {pickText(item.title, localeKey)}
+
+
+ {pickText(item.summary, localeKey)}
+
+
+ {copy.toPortfolio}
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+ {copy.productsTitle}
+
+
+ {copy.productsText}
+
+
+
+
+
+ {featuredProducts.map((item) => (
+
+
+ {pickText(item.segment, localeKey)}
+
+
+ {pickText(item.name, localeKey)}
+
+
+ {pickText(item.summary, localeKey)}
+
+
+ {pickText(item.price, localeKey)}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ {copy.ctaTitle}
+
+
+ {copy.ctaText}
+
+
+
+ {copy.toContact}
+
+
+
+
+
+
+ );
+}
diff --git a/app/[locale]/(site)/portfolio/[slug]/page.tsx b/app/[locale]/(site)/portfolio/[slug]/page.tsx
new file mode 100644
index 0000000..e08b371
--- /dev/null
+++ b/app/[locale]/(site)/portfolio/[slug]/page.tsx
@@ -0,0 +1,131 @@
+import { ArrowLeft, CalendarDays, FolderKanban, Tag } from "lucide-react";
+import Link from "next/link";
+import { notFound } from "next/navigation";
+
+import { MotionFade } from "@/components/motion-fade";
+import { routing } from "@/i18n/routing";
+import { getPortfolioItem, pickText, portfolioItems, resolveLocale } from "@/lib/site-data";
+
+type PortfolioItemPageProps = {
+ params: {
+ locale: string;
+ slug: string;
+ };
+};
+
+export function generateStaticParams() {
+ return routing.locales.flatMap((locale) =>
+ portfolioItems.map((item) => ({
+ locale,
+ slug: item.slug,
+ })),
+ );
+}
+
+export default function PortfolioItemPage({
+ params: { locale, slug },
+}: PortfolioItemPageProps) {
+ const localeKey = resolveLocale(locale);
+ const item = getPortfolioItem(slug);
+
+ if (!item) {
+ notFound();
+ }
+
+ const copy =
+ localeKey === "de"
+ ? {
+ back: "Zurueck zum Portfolio",
+ challenge: "Herausforderung",
+ solution: "Loesung",
+ outcome: "Ergebnis",
+ challengeText:
+ "Das Projekt brauchte eine klare Informationsarchitektur und schnellere Ladezeiten.",
+ solutionText:
+ "Wir haben Design, Komponenten und Content in einem modularen System aufgebaut.",
+ outcomeText:
+ "Das Team kann Inhalte schneller ausrollen und Nutzer finden schneller zum Ziel.",
+ }
+ : {
+ back: "Back to portfolio",
+ challenge: "Challenge",
+ solution: "Solution",
+ outcome: "Outcome",
+ challengeText:
+ "The project needed clearer information architecture and faster performance.",
+ solutionText:
+ "We built design, components and content in a modular system.",
+ outcomeText:
+ "The team ships content faster and users reach goals more quickly.",
+ };
+
+ return (
+
+
+
+
+
+ {copy.back}
+
+
+ {pickText(item.title, localeKey)}
+
+
+ {pickText(item.summary, localeKey)}
+
+
+
+
+
+ {pickText(item.category, localeKey)}
+
+
+
+ {item.year}
+
+
+
+ {item.slug}
+
+
+
+
+
+
+
+
+
+ {copy.challenge}
+
+
+ {copy.challengeText}
+
+
+
+
+
+
+ {copy.solution}
+
+
+ {copy.solutionText}
+
+
+
+
+
+
+ {copy.outcome}
+
+
+ {copy.outcomeText}
+
+
+
+
+
+ );
+}
diff --git a/app/[locale]/(site)/portfolio/page.tsx b/app/[locale]/(site)/portfolio/page.tsx
new file mode 100644
index 0000000..c1c3536
--- /dev/null
+++ b/app/[locale]/(site)/portfolio/page.tsx
@@ -0,0 +1,77 @@
+import { ArrowUpRight, CalendarDays, FolderKanban } from "lucide-react";
+import Link from "next/link";
+
+import { MotionFade } from "@/components/motion-fade";
+import { pickText, portfolioItems, resolveLocale } from "@/lib/site-data";
+
+type PortfolioPageProps = {
+ params: {
+ locale: string;
+ };
+};
+
+export default function PortfolioPage({ params: { locale } }: PortfolioPageProps) {
+ const localeKey = resolveLocale(locale);
+
+ const copy =
+ localeKey === "de"
+ ? {
+ title: "Portfolio",
+ intro: "Eine Auswahl von Projekten mit Fokus auf Klarheit und Ergebnis.",
+ open: "Projekt oeffnen",
+ }
+ : {
+ title: "Portfolio",
+ intro: "Selected projects with a focus on clarity and outcomes.",
+ open: "Open project",
+ };
+
+ return (
+
+
+
+
+
+
+ {copy.title}
+
+
+
+ {copy.intro}
+
+
+
+
+
+ {portfolioItems.map((item, index) => (
+
+
+
+
+ {pickText(item.category, localeKey)}
+
+
+
+ {item.year}
+
+
+
+ {pickText(item.title, localeKey)}
+
+
+ {pickText(item.summary, localeKey)}
+
+
+ {copy.open}
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/app/[locale]/(site)/products/[slug]/page.tsx b/app/[locale]/(site)/products/[slug]/page.tsx
new file mode 100644
index 0000000..1c78dc1
--- /dev/null
+++ b/app/[locale]/(site)/products/[slug]/page.tsx
@@ -0,0 +1,116 @@
+import { ArrowLeft, BadgeEuro, Boxes, Layers2 } from "lucide-react";
+import Link from "next/link";
+import { notFound } from "next/navigation";
+
+import { MotionFade } from "@/components/motion-fade";
+import { routing } from "@/i18n/routing";
+import { getProductItem, pickText, productItems, resolveLocale } from "@/lib/site-data";
+
+type ProductPageProps = {
+ params: {
+ locale: string;
+ slug: string;
+ };
+};
+
+export function generateStaticParams() {
+ return routing.locales.flatMap((locale) =>
+ productItems.map((item) => ({
+ locale,
+ slug: item.slug,
+ })),
+ );
+}
+
+export default function ProductPage({ params: { locale, slug } }: ProductPageProps) {
+ const localeKey = resolveLocale(locale);
+ const item = getProductItem(slug);
+
+ if (!item) {
+ notFound();
+ }
+
+ const copy =
+ localeKey === "de"
+ ? {
+ back: "Zurueck zu Produkten",
+ included: "Inklusive",
+ stepOne: "Kickoff und Scope Klarheit",
+ stepTwo: "Setup von Design und Komponenten",
+ stepThree: "Implementierung und Uebergabe",
+ action: "Kontakt fuer Angebot",
+ }
+ : {
+ back: "Back to products",
+ included: "Included",
+ stepOne: "Kickoff and scope clarity",
+ stepTwo: "Design and component setup",
+ stepThree: "Implementation and handover",
+ action: "Contact for proposal",
+ };
+
+ return (
+
+
+
+
+
+ {copy.back}
+
+
+
+ {pickText(item.name, localeKey)}
+
+
+ {pickText(item.summary, localeKey)}
+
+
+
+
+
+ {pickText(item.segment, localeKey)}
+
+
+
+ {pickText(item.price, localeKey)}
+
+
+
+ {item.slug}
+
+
+
+
+
+
+
+
+ {copy.included}
+
+
+ {[copy.stepOne, copy.stepTwo, copy.stepThree].map((step, index) => (
+ -
+
+ {index + 1}
+
+
{step}
+
+ ))}
+
+
+ {copy.action}
+
+
+
+
+ );
+}
diff --git a/app/[locale]/(site)/products/page.tsx b/app/[locale]/(site)/products/page.tsx
new file mode 100644
index 0000000..a345c55
--- /dev/null
+++ b/app/[locale]/(site)/products/page.tsx
@@ -0,0 +1,75 @@
+import { ArrowUpRight, Boxes, CircleDollarSign } from "lucide-react";
+import Link from "next/link";
+
+import { MotionFade } from "@/components/motion-fade";
+import { pickText, productItems, resolveLocale } from "@/lib/site-data";
+
+type ProductsPageProps = {
+ params: {
+ locale: string;
+ };
+};
+
+export default function ProductsPage({ params: { locale } }: ProductsPageProps) {
+ const localeKey = resolveLocale(locale);
+
+ const copy =
+ localeKey === "de"
+ ? {
+ title: "Produkte",
+ intro: "Pakete fuer Teams von Start bis Skalierung.",
+ open: "Produkt oeffnen",
+ }
+ : {
+ title: "Products",
+ intro: "Packages for teams from early stage to scale.",
+ open: "Open product",
+ };
+
+ return (
+
+
+
+
+
+
+ {copy.title}
+
+
+
+ {copy.intro}
+
+
+
+
+
+ {productItems.map((item, index) => (
+
+
+
+ {pickText(item.segment, localeKey)}
+
+
+ {pickText(item.name, localeKey)}
+
+
+ {pickText(item.summary, localeKey)}
+
+
+
+ {pickText(item.price, localeKey)}
+
+
+ {copy.open}
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/app/[locale]/(site)/root/page.tsx b/app/[locale]/(site)/root/page.tsx
new file mode 100644
index 0000000..a402aa5
--- /dev/null
+++ b/app/[locale]/(site)/root/page.tsx
@@ -0,0 +1,324 @@
+import {
+ BarChart3,
+ Boxes,
+ FolderKanban,
+ LockKeyhole,
+ LogOut,
+ Power,
+ ShieldAlert,
+ Users2,
+} from "lucide-react";
+import { revalidatePath } from "next/cache";
+import { redirect } from "next/navigation";
+
+import { MotionFade } from "@/components/motion-fade";
+import {
+ clearAdminSessionCookie,
+ getAdminLockState,
+ isAdminAuthConfigured,
+ isAdminAuthenticated,
+ isPasswordValid,
+ registerFailedAdminAttempt,
+ resetAdminFailedAttempts,
+ setAdminSessionCookie,
+} from "@/lib/admin-auth";
+import { getMaintenanceMode, setMaintenanceMode } from "@/lib/app-config";
+import { resolveLocale } from "@/lib/site-data";
+
+type RootPageProps = {
+ params: {
+ locale: string;
+ };
+ searchParams?: {
+ error?: string;
+ };
+};
+
+export const dynamic = "force-dynamic";
+
+export default async function RootPage({
+ params: { locale },
+ searchParams,
+}: RootPageProps) {
+ const localeKey = resolveLocale(locale);
+ const authConfigured = isAdminAuthConfigured();
+ const basicConfigured = Boolean(
+ process.env.ROOT_BASIC_AUTH_USER && process.env.ROOT_BASIC_AUTH_PASS,
+ );
+ const authenticated = isAdminAuthenticated();
+ const lockState = getAdminLockState();
+ const maintenanceEnabled = authenticated ? await getMaintenanceMode() : false;
+
+ async function loginAction(formData: FormData) {
+ "use server";
+
+ const password = String(formData.get("password") ?? "");
+ const currentLockState = getAdminLockState();
+
+ if (currentLockState.locked) {
+ redirect(`/${localeKey}/root?error=locked`);
+ }
+
+ if (!isAdminAuthConfigured() || !isPasswordValid(password)) {
+ const failState = registerFailedAdminAttempt();
+ if (failState.locked) {
+ redirect(`/${localeKey}/root?error=locked`);
+ }
+
+ redirect(`/${localeKey}/root?error=invalid`);
+ }
+
+ resetAdminFailedAttempts();
+ setAdminSessionCookie();
+ redirect(`/${localeKey}/root`);
+ }
+
+ async function logoutAction() {
+ "use server";
+
+ clearAdminSessionCookie();
+ redirect(`/${localeKey}/root`);
+ }
+
+ async function updateMaintenanceMode(formData: FormData) {
+ "use server";
+
+ if (!isAdminAuthenticated()) {
+ redirect(`/${localeKey}/root`);
+ }
+
+ const nextValue = formData.get("enabled") === "true";
+
+ await setMaintenanceMode(nextValue);
+ revalidatePath(`/${localeKey}/root`);
+ revalidatePath(`/${localeKey}/coming-soon`);
+ redirect(`/${localeKey}/root`);
+ }
+
+ const copy =
+ localeKey === "de"
+ ? {
+ title: "Root",
+ subtitle: "Interner Bereich fuer Kennzahlen und Wartungssteuerung.",
+ activeUsers: "Aktive Nutzer",
+ projects: "Laufende Projekte",
+ products: "Aktive Produkte",
+ conversion: "Conversion",
+ updates: "Letzte Updates",
+ updateOne: "Kontaktformular wurde ueberarbeitet.",
+ updateTwo: "Neue Produktseite fuer Growth Kit vorbereitet.",
+ updateThree: "Portfolio-Daten fuer Q2 aktualisiert.",
+ maintenanceTitle: "Wartungsmodus",
+ maintenanceText:
+ "Wenn aktiv, werden alle Seiten auf die Coming Soon Seite weitergeleitet.",
+ maintenanceOn: "Aktiv",
+ maintenanceOff: "Inaktiv",
+ enableMaintenance: "Wartungsmodus aktivieren",
+ disableMaintenance: "Wartungsmodus deaktivieren",
+ loginTitle: "Root Login",
+ loginText: "Nur autorisierte Nutzer duerfen diesen Bereich verwenden.",
+ passwordLabel: "Passwort",
+ loginButton: "Einloggen",
+ invalidLogin: "Falsches Passwort.",
+ lockedLogin: "Zu viele Fehlversuche. Bitte spaeter erneut versuchen.",
+ configMissing: "ADMIN_PASSWORD und ADMIN_AUTH_SECRET fehlen in env.",
+ basicAuthMissing:
+ "ROOT_BASIC_AUTH_USER und ROOT_BASIC_AUTH_PASS fehlen in env.",
+ logout: "Ausloggen",
+ }
+ : {
+ title: "Root",
+ subtitle: "Internal area for metrics and maintenance controls.",
+ activeUsers: "Active users",
+ projects: "Running projects",
+ products: "Active products",
+ conversion: "Conversion",
+ updates: "Latest updates",
+ updateOne: "Contact form layout updated.",
+ updateTwo: "New Growth Kit product page prepared.",
+ updateThree: "Portfolio data updated for Q2.",
+ maintenanceTitle: "Maintenance mode",
+ maintenanceText:
+ "When enabled, all site routes are redirected to the coming soon page.",
+ maintenanceOn: "Enabled",
+ maintenanceOff: "Disabled",
+ enableMaintenance: "Enable maintenance mode",
+ disableMaintenance: "Disable maintenance mode",
+ loginTitle: "Root login",
+ loginText: "Only authorized users can access this area.",
+ passwordLabel: "Password",
+ loginButton: "Sign in",
+ invalidLogin: "Invalid password.",
+ lockedLogin: "Too many failed attempts. Please try again later.",
+ configMissing: "ADMIN_PASSWORD and ADMIN_AUTH_SECRET are missing in env.",
+ basicAuthMissing:
+ "ROOT_BASIC_AUTH_USER and ROOT_BASIC_AUTH_PASS are missing in env.",
+ logout: "Sign out",
+ };
+
+ if (!authenticated) {
+ return (
+
+
+
+
+
+ {copy.loginTitle}
+
+ {copy.loginText}
+
+ {!authConfigured ? (
+
+ {copy.configMissing}
+
+ ) : null}
+
+ {!basicConfigured ? (
+
+ {copy.basicAuthMissing}
+
+ ) : null}
+
+ {searchParams?.error === "invalid" ? (
+
+ {copy.invalidLogin}
+
+ ) : null}
+
+ {searchParams?.error === "locked" || lockState.locked ? (
+
+ {copy.lockedLogin}
+
+ ) : null}
+
+
+
+
+
+ );
+ }
+
+ const stats = [
+ { label: copy.activeUsers, value: "1,280", icon: Users2 },
+ { label: copy.projects, value: "24", icon: FolderKanban },
+ { label: copy.products, value: "8", icon: Boxes },
+ { label: copy.conversion, value: "4.8%", icon: BarChart3 },
+ ];
+
+ return (
+
+
+
+
+
+ {copy.title}
+
+
+
+
+ {copy.subtitle}
+
+
+
+
+
+
+
+
+
+
+ {copy.maintenanceTitle}
+
+
+ {copy.maintenanceText}
+
+
+
+ {maintenanceEnabled ? copy.maintenanceOn : copy.maintenanceOff}
+
+
+
+
+
+
+
+
+ {stats.map((item, index) => {
+ const Icon = item.icon;
+ return (
+
+
+
+ {item.label}
+
+ {item.value}
+
+
+
+ );
+ })}
+
+
+
+
+ {copy.updates}
+
+ {[copy.updateOne, copy.updateTwo, copy.updateThree].map((item) => (
+ -
+ {item}
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/app/[locale]/(site)/success/page.tsx b/app/[locale]/(site)/success/page.tsx
new file mode 100644
index 0000000..150afec
--- /dev/null
+++ b/app/[locale]/(site)/success/page.tsx
@@ -0,0 +1,60 @@
+import { CheckCircle2 } from "lucide-react";
+import Link from "next/link";
+
+import { MotionFade } from "@/components/motion-fade";
+import { resolveLocale } from "@/lib/site-data";
+
+type SuccessPageProps = {
+ params: {
+ locale: string;
+ };
+};
+
+export default function SuccessPage({ params: { locale } }: SuccessPageProps) {
+ const localeKey = resolveLocale(locale);
+
+ const copy =
+ localeKey === "de"
+ ? {
+ title: "Danke fuer deine Nachricht",
+ text: "Wir haben deine Anfrage erhalten und melden uns zeitnah.",
+ home: "Zur Startseite",
+ contact: "Zur Kontaktseite",
+ }
+ : {
+ title: "Thank you for your message",
+ text: "We received your request and will reply shortly.",
+ home: "Go to homepage",
+ contact: "Back to contact",
+ };
+
+ return (
+
+
+
+
+
+ {copy.title}
+
+
+ {copy.text}
+
+
+
+ {copy.home}
+
+
+ {copy.contact}
+
+
+
+
+
+ );
+}
diff --git a/app/[locale]/coming-soon/page.tsx b/app/[locale]/coming-soon/page.tsx
new file mode 100644
index 0000000..8e49e96
--- /dev/null
+++ b/app/[locale]/coming-soon/page.tsx
@@ -0,0 +1,116 @@
+import { CalendarClock, Rocket, ShieldCheck, Sparkles } from "lucide-react";
+import Link from "next/link";
+
+import { MotionFade } from "@/components/motion-fade";
+import { resolveLocale } from "@/lib/site-data";
+
+type ComingSoonPageProps = {
+ params: {
+ locale: string;
+ };
+};
+
+export default function ComingSoonPage({ params: { locale } }: ComingSoonPageProps) {
+ const localeKey = resolveLocale(locale);
+
+ const copy =
+ localeKey === "de"
+ ? {
+ badge: "In Vorbereitung",
+ title: "Wir bauen gerade etwas Grosses.",
+ description:
+ "Die Website ist aktuell im Wartungsmodus. Wir finalisieren Inhalte, Feinschliff und Integrationen fuer den Launch.",
+ cardOneTitle: "Launch Fokus",
+ cardOneText: "Performance, UX und klare Conversion-Flows.",
+ cardTwoTitle: "Naechster Schritt",
+ cardTwoText: "Deployment-Checks und finale Inhalte.",
+ cardThreeTitle: "Status",
+ cardThreeText: "Systeme sind online und werden vorbereitet.",
+ adminLink: "Zum Root Bereich",
+ footer: "Danke fuer deine Geduld.",
+ }
+ : {
+ badge: "Preparing",
+ title: "We are building something big.",
+ description:
+ "The website is currently in maintenance mode. We are finalizing content, polish and integrations before launch.",
+ cardOneTitle: "Launch focus",
+ cardOneText: "Performance, UX and clear conversion flows.",
+ cardTwoTitle: "Next step",
+ cardTwoText: "Deployment checks and final content updates.",
+ cardThreeTitle: "Status",
+ cardThreeText: "Systems are online and being prepared.",
+ adminLink: "Open root area",
+ footer: "Thank you for your patience.",
+ };
+
+ return (
+
+
+
+
+
+
+
+ {copy.badge}
+
+
+
+ {copy.title}
+
+
+
+ {copy.description}
+
+
+
+
+
+ {copy.adminLink}
+
+
+
+
+
+
+
+
+
+
+ {copy.cardOneTitle}
+
+ {copy.cardOneText}
+
+
+
+
+
+
+
+ {copy.cardTwoTitle}
+
+ {copy.cardTwoText}
+
+
+
+
+
+
+
+ {copy.cardThreeTitle}
+
+ {copy.cardThreeText}
+
+
+
+
+
+ {copy.footer}
+
+
+
+ );
+}
diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx
new file mode 100644
index 0000000..f1910ce
--- /dev/null
+++ b/app/[locale]/layout.tsx
@@ -0,0 +1,44 @@
+import type { ReactNode } from "react";
+import { NextIntlClientProvider } from "next-intl";
+import { getMessages, setRequestLocale } from "next-intl/server";
+import { notFound } from "next/navigation";
+
+import { ThemeProvider } from "@/components/theme-provider";
+import { routing } from "@/i18n/routing";
+
+type LocaleLayoutProps = {
+ children: ReactNode;
+ params: {
+ locale: string;
+ };
+};
+
+export function generateStaticParams() {
+ return routing.locales.map((locale) => ({ locale }));
+}
+
+export default async function LocaleLayout({
+ children,
+ params: { locale },
+}: LocaleLayoutProps) {
+ if (!routing.locales.includes(locale as (typeof routing.locales)[number])) {
+ notFound();
+ }
+
+ setRequestLocale(locale);
+
+ const messages = await getMessages();
+
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/app/api/health/route.ts b/app/api/health/route.ts
new file mode 100644
index 0000000..6c802de
--- /dev/null
+++ b/app/api/health/route.ts
@@ -0,0 +1,35 @@
+import { NextResponse } from "next/server";
+
+import { prisma } from "@/lib/prisma";
+
+export const dynamic = "force-dynamic";
+
+export async function GET() {
+ const timestamp = new Date().toISOString();
+
+ try {
+ await prisma.$queryRaw`SELECT 1`;
+
+ return NextResponse.json(
+ {
+ status: "ok",
+ timestamp,
+ checks: {
+ database: "up",
+ },
+ },
+ { status: 200 },
+ );
+ } catch {
+ return NextResponse.json(
+ {
+ status: "degraded",
+ timestamp,
+ checks: {
+ database: "down",
+ },
+ },
+ { status: 503 },
+ );
+ }
+}
diff --git a/app/api/maintenance/route.ts b/app/api/maintenance/route.ts
new file mode 100644
index 0000000..cfb15db
--- /dev/null
+++ b/app/api/maintenance/route.ts
@@ -0,0 +1,25 @@
+import { NextResponse } from "next/server";
+
+import { getMaintenanceMode } from "@/lib/app-config";
+
+export const dynamic = "force-dynamic";
+
+export async function GET() {
+ try {
+ const enabled = await getMaintenanceMode();
+
+ return NextResponse.json(
+ {
+ enabled,
+ },
+ { status: 200 },
+ );
+ } catch {
+ return NextResponse.json(
+ {
+ enabled: false,
+ },
+ { status: 200 },
+ );
+ }
+}
diff --git a/app/globals.css b/app/globals.css
index 13d40b8..f62820a 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -7,17 +7,14 @@
--foreground: #171717;
}
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
- }
+.dark {
+ --background: #0a0a0a;
+ --foreground: #ededed;
}
body {
color: var(--foreground);
background: var(--background);
- font-family: Arial, Helvetica, sans-serif;
}
@layer utilities {
diff --git a/app/layout.tsx b/app/layout.tsx
index a36cde0..9351006 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,21 +1,9 @@
import type { Metadata } from "next";
-import localFont from "next/font/local";
import "./globals.css";
-const geistSans = localFont({
- src: "./fonts/GeistVF.woff",
- variable: "--font-geist-sans",
- weight: "100 900",
-});
-const geistMono = localFont({
- src: "./fonts/GeistMonoVF.woff",
- variable: "--font-geist-mono",
- weight: "100 900",
-});
-
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ title: "moh-sass",
+ description: "Multilingual Next.js base project",
};
export default function RootLayout({
@@ -24,12 +12,8 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
-
-
- {children}
-
+
+ {children}
);
}
diff --git a/app/page.tsx b/app/page.tsx
index 433c8aa..07049e0 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,101 +1,5 @@
-import Image from "next/image";
+import { redirect } from "next/navigation";
-export default function Home() {
- return (
-
-
-
-
- -
- Get started by editing{" "}
-
- app/page.tsx
-
- .
-
- - Save and see your changes instantly.
-
-
-
-
-
-
- );
+export default function RootPage() {
+ redirect("/de");
}
diff --git a/components/footer.tsx b/components/footer.tsx
new file mode 100644
index 0000000..df4929c
--- /dev/null
+++ b/components/footer.tsx
@@ -0,0 +1,38 @@
+import Link from "next/link";
+import { useLocale, useTranslations } from "next-intl";
+
+const navItems = [
+ { key: "home", path: "" },
+ { key: "portfolio", path: "/portfolio" },
+ { key: "products", path: "/products" },
+ { key: "about", path: "/about" },
+ { key: "contact", path: "/contact" },
+ { key: "root", path: "/root" },
+];
+
+export function Footer() {
+ const locale = useLocale();
+ const tNav = useTranslations("navigation");
+ const tFooter = useTranslations("footer");
+
+ return (
+
+ );
+}
diff --git a/components/motion-fade.tsx b/components/motion-fade.tsx
new file mode 100644
index 0000000..4492269
--- /dev/null
+++ b/components/motion-fade.tsx
@@ -0,0 +1,26 @@
+"use client";
+
+import { motion } from "framer-motion";
+import type { ReactNode } from "react";
+
+import { cn } from "@/lib/utils";
+
+type MotionFadeProps = {
+ children: ReactNode;
+ className?: string;
+ delay?: number;
+};
+
+export function MotionFade({ children, className, delay = 0 }: MotionFadeProps) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/components/navbar.tsx b/components/navbar.tsx
new file mode 100644
index 0000000..a495bc7
--- /dev/null
+++ b/components/navbar.tsx
@@ -0,0 +1,110 @@
+"use client";
+
+import { Menu, X } from "lucide-react";
+import Image from "next/image";
+import Link from "next/link";
+import { useLocale, useTranslations } from "next-intl";
+import { useState } from "react";
+
+import { ThemeToggle } from "@/components/theme-toggle";
+
+const navItems = [
+ { key: "home", path: "" },
+ { key: "portfolio", path: "/portfolio" },
+ { key: "products", path: "/products" },
+ { key: "about", path: "/about" },
+ { key: "contact", path: "/contact" },
+ { key: "root", path: "/root" },
+];
+
+export function Navbar() {
+ const [isOpen, setIsOpen] = useState(false);
+ const locale = useLocale();
+ const t = useTranslations("navigation");
+
+ return (
+
+ );
+}
diff --git a/components/theme-provider.tsx b/components/theme-provider.tsx
new file mode 100644
index 0000000..e478fc4
--- /dev/null
+++ b/components/theme-provider.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ThemeProvider as NextThemesProvider, type ThemeProviderProps } from "next-themes";
+
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+ return {children};
+}
diff --git a/components/theme-toggle.tsx b/components/theme-toggle.tsx
new file mode 100644
index 0000000..89f2bf9
--- /dev/null
+++ b/components/theme-toggle.tsx
@@ -0,0 +1,40 @@
+"use client";
+
+import { Moon, Sun } from "lucide-react";
+import { useTheme } from "next-themes";
+import { useEffect, useState } from "react";
+
+export function ThemeToggle() {
+ const { setTheme, theme, resolvedTheme } = useTheme();
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ if (!mounted) {
+ return (
+
+ );
+ }
+
+ const activeTheme = theme === "system" ? resolvedTheme : theme;
+ const isDark = activeTheme === "dark";
+
+ return (
+
+ );
+}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..88e80cc
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,44 @@
+services:
+ app:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ container_name: moh-sass-app
+ command: sh -c "npm install && npm run dev -- --hostname 0.0.0.0 --port 3000"
+ environment:
+ NEXT_TELEMETRY_DISABLED: "1"
+ WATCHPACK_POLLING: "true"
+ DATABASE_URL: postgresql://postgres:postgres@db:5432/moh_sass?schema=public
+ ADMIN_PASSWORD: ${ADMIN_PASSWORD:-change-me}
+ ADMIN_AUTH_SECRET: ${ADMIN_AUTH_SECRET:-change-me-long-secret}
+ ROOT_BASIC_AUTH_USER: ${ROOT_BASIC_AUTH_USER:-root}
+ ROOT_BASIC_AUTH_PASS: ${ROOT_BASIC_AUTH_PASS:-change-me-root}
+ volumes:
+ - .:/app
+ - app_node_modules:/app/node_modules
+ - app_next:/app/.next
+ ports:
+ - "3000:3000"
+ depends_on:
+ db:
+ condition: service_healthy
+
+ db:
+ image: postgres:16-alpine
+ container_name: moh-sass-db
+ environment:
+ POSTGRES_DB: moh_sass
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: postgres
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U postgres -d moh_sass"]
+ interval: 5s
+ timeout: 5s
+ retries: 20
+
+volumes:
+ app_node_modules:
+ app_next:
+ postgres_data:
diff --git a/i18n/request.ts b/i18n/request.ts
new file mode 100644
index 0000000..6d16c64
--- /dev/null
+++ b/i18n/request.ts
@@ -0,0 +1,17 @@
+import { getRequestConfig } from "next-intl/server";
+
+import { routing } from "./routing";
+
+export default getRequestConfig(async ({ requestLocale }) => {
+ const requestedLocale = await requestLocale;
+ const locale =
+ requestedLocale &&
+ routing.locales.includes(requestedLocale as (typeof routing.locales)[number])
+ ? requestedLocale
+ : routing.defaultLocale;
+
+ return {
+ locale,
+ messages: (await import(`../messages/${locale}.json`)).default,
+ };
+});
diff --git a/i18n/routing.ts b/i18n/routing.ts
new file mode 100644
index 0000000..084f028
--- /dev/null
+++ b/i18n/routing.ts
@@ -0,0 +1,6 @@
+import { defineRouting } from "next-intl/routing";
+
+export const routing = defineRouting({
+ locales: ["en", "de"],
+ defaultLocale: "de",
+});
diff --git a/lib/admin-auth.ts b/lib/admin-auth.ts
new file mode 100644
index 0000000..f1c9166
--- /dev/null
+++ b/lib/admin-auth.ts
@@ -0,0 +1,172 @@
+import { createHmac, timingSafeEqual } from "crypto";
+import { cookies } from "next/headers";
+
+export const ADMIN_SESSION_COOKIE = "moh_admin_session";
+const ADMIN_FAIL_COOKIE = "moh_admin_fail";
+const ADMIN_SESSION_VALUE = "superadmin";
+const MAX_FAILED_ATTEMPTS = 5;
+const LOCKOUT_SECONDS = 15 * 60;
+
+function getSecret(): string {
+ return process.env.ADMIN_AUTH_SECRET ?? "";
+}
+
+function getPassword(): string {
+ return process.env.ADMIN_PASSWORD ?? "";
+}
+
+function signValue(value: string): string {
+ return createHmac("sha256", getSecret()).update(value).digest("hex");
+}
+
+function buildToken(): string {
+ return `${ADMIN_SESSION_VALUE}.${signValue(ADMIN_SESSION_VALUE)}`;
+}
+
+function verifyToken(token: string): boolean {
+ const parts = token.split(".");
+ if (parts.length !== 2) {
+ return false;
+ }
+
+ const [value, signature] = parts;
+ if (value !== ADMIN_SESSION_VALUE) {
+ return false;
+ }
+
+ const expected = signValue(value);
+ const left = Buffer.from(signature);
+ const right = Buffer.from(expected);
+
+ if (left.length !== right.length) {
+ return false;
+ }
+
+ return timingSafeEqual(left, right);
+}
+
+export function isAdminAuthConfigured(): boolean {
+ return getPassword().length > 0 && getSecret().length > 0;
+}
+
+export function isPasswordValid(password: string): boolean {
+ if (!isAdminAuthConfigured()) {
+ return false;
+ }
+
+ const provided = Buffer.from(password);
+ const expected = Buffer.from(getPassword());
+
+ if (provided.length !== expected.length) {
+ return false;
+ }
+
+ return timingSafeEqual(provided, expected);
+}
+
+export function setAdminSessionCookie(): void {
+ const store = cookies();
+ store.set(ADMIN_SESSION_COOKIE, buildToken(), {
+ httpOnly: true,
+ sameSite: "lax",
+ secure: process.env.NODE_ENV === "production",
+ path: "/",
+ maxAge: 60 * 60 * 8,
+ });
+}
+
+export function clearAdminSessionCookie(): void {
+ const store = cookies();
+ store.set(ADMIN_SESSION_COOKIE, "", {
+ httpOnly: true,
+ sameSite: "lax",
+ secure: process.env.NODE_ENV === "production",
+ path: "/",
+ maxAge: 0,
+ });
+}
+
+type FailState = {
+ attempts: number;
+ lockUntil: number;
+};
+
+function parseFailState(rawValue: string | undefined): FailState {
+ if (!rawValue) {
+ return { attempts: 0, lockUntil: 0 };
+ }
+
+ try {
+ const parsed = JSON.parse(rawValue) as Partial;
+
+ return {
+ attempts: Number(parsed.attempts ?? 0),
+ lockUntil: Number(parsed.lockUntil ?? 0),
+ };
+ } catch {
+ return { attempts: 0, lockUntil: 0 };
+ }
+}
+
+export function getAdminLockState(): { locked: boolean; remainingSeconds: number } {
+ const store = cookies();
+ const state = parseFailState(store.get(ADMIN_FAIL_COOKIE)?.value);
+ const now = Date.now();
+
+ if (state.lockUntil > now) {
+ return {
+ locked: true,
+ remainingSeconds: Math.ceil((state.lockUntil - now) / 1000),
+ };
+ }
+
+ return { locked: false, remainingSeconds: 0 };
+}
+
+export function registerFailedAdminAttempt(): { locked: boolean; remainingSeconds: number } {
+ const store = cookies();
+ const now = Date.now();
+ const current = parseFailState(store.get(ADMIN_FAIL_COOKIE)?.value);
+ const attempts = current.lockUntil > now ? current.attempts : current.attempts + 1;
+ const locked = attempts >= MAX_FAILED_ATTEMPTS;
+ const lockUntil = locked ? now + LOCKOUT_SECONDS * 1000 : 0;
+
+ store.set(ADMIN_FAIL_COOKIE, JSON.stringify({ attempts, lockUntil }), {
+ httpOnly: true,
+ sameSite: "lax",
+ secure: process.env.NODE_ENV === "production",
+ path: "/",
+ maxAge: LOCKOUT_SECONDS,
+ });
+
+ return {
+ locked,
+ remainingSeconds: locked ? LOCKOUT_SECONDS : 0,
+ };
+}
+
+export function resetAdminFailedAttempts(): void {
+ const store = cookies();
+ store.set(ADMIN_FAIL_COOKIE, "", {
+ httpOnly: true,
+ sameSite: "lax",
+ secure: process.env.NODE_ENV === "production",
+ path: "/",
+ maxAge: 0,
+ });
+}
+
+export function isAdminAuthenticated(): boolean {
+ if (!isAdminAuthConfigured()) {
+ return false;
+ }
+
+ const store = cookies();
+ const token = store.get(ADMIN_SESSION_COOKIE)?.value;
+
+ if (!token) {
+ return false;
+ }
+
+ return verifyToken(token);
+}
diff --git a/lib/app-config.ts b/lib/app-config.ts
new file mode 100644
index 0000000..ca67864
--- /dev/null
+++ b/lib/app-config.ts
@@ -0,0 +1,28 @@
+import { prisma } from "@/lib/prisma";
+
+export const MAINTENANCE_MODE_KEY = "maintenance_mode";
+
+export async function getMaintenanceMode(): Promise {
+ try {
+ const config = await prisma.appConfig.findUnique({
+ where: { key: MAINTENANCE_MODE_KEY },
+ });
+
+ return config?.value === "true";
+ } catch {
+ return false;
+ }
+}
+
+export async function setMaintenanceMode(enabled: boolean): Promise {
+ await prisma.appConfig.upsert({
+ where: { key: MAINTENANCE_MODE_KEY },
+ update: {
+ value: enabled ? "true" : "false",
+ },
+ create: {
+ key: MAINTENANCE_MODE_KEY,
+ value: enabled ? "true" : "false",
+ },
+ });
+}
diff --git a/lib/prisma.ts b/lib/prisma.ts
new file mode 100644
index 0000000..5c4d2bf
--- /dev/null
+++ b/lib/prisma.ts
@@ -0,0 +1,32 @@
+import { PrismaPg } from "@prisma/adapter-pg";
+import { PrismaClient } from "@prisma/client";
+import { Pool } from "pg";
+
+const globalForPrisma = globalThis as unknown as {
+ prisma: PrismaClient | undefined;
+ prismaPool: Pool | undefined;
+};
+
+const connectionString =
+ process.env.DATABASE_URL ??
+ "postgresql://postgres:postgres@localhost:5432/moh_sass?schema=public";
+
+const pool =
+ globalForPrisma.prismaPool ??
+ new Pool({
+ connectionString,
+ });
+
+const adapter = new PrismaPg(pool);
+
+export const prisma =
+ globalForPrisma.prisma ??
+ new PrismaClient({
+ adapter,
+ log: ["warn", "error"],
+ });
+
+if (process.env.NODE_ENV !== "production") {
+ globalForPrisma.prismaPool = pool;
+ globalForPrisma.prisma = prisma;
+}
diff --git a/lib/site-data.ts b/lib/site-data.ts
new file mode 100644
index 0000000..9c99872
--- /dev/null
+++ b/lib/site-data.ts
@@ -0,0 +1,162 @@
+export type AppLocale = "de" | "en";
+
+type LocalizedText = Record;
+
+export type PortfolioItem = {
+ slug: string;
+ title: LocalizedText;
+ summary: LocalizedText;
+ category: LocalizedText;
+ year: string;
+};
+
+export type ProductItem = {
+ slug: string;
+ name: LocalizedText;
+ summary: LocalizedText;
+ segment: LocalizedText;
+ price: LocalizedText;
+};
+
+export const portfolioItems: PortfolioItem[] = [
+ {
+ slug: "brand-redesign",
+ title: {
+ de: "Brand Redesign",
+ en: "Brand Redesign",
+ },
+ summary: {
+ de: "Modernes Redesign fuer eine digitale Marke mit klarer Struktur.",
+ en: "Modern redesign for a digital brand with a clear system.",
+ },
+ category: {
+ de: "Branding",
+ en: "Branding",
+ },
+ year: "2025",
+ },
+ {
+ slug: "commerce-relaunch",
+ title: {
+ de: "Commerce Relaunch",
+ en: "Commerce Relaunch",
+ },
+ summary: {
+ de: "Relaunch eines Shops mit Fokus auf Performance und Conversion.",
+ en: "Store relaunch focused on performance and conversion.",
+ },
+ category: {
+ de: "E-Commerce",
+ en: "E-Commerce",
+ },
+ year: "2024",
+ },
+ {
+ slug: "saas-dashboard",
+ title: {
+ de: "SaaS Dashboard",
+ en: "SaaS Dashboard",
+ },
+ summary: {
+ de: "Admin Dashboard fuer Teams mit klaren KPIs und Reports.",
+ en: "Admin dashboard for teams with clear KPIs and reports.",
+ },
+ category: {
+ de: "Web App",
+ en: "Web App",
+ },
+ year: "2024",
+ },
+ {
+ slug: "campaign-site",
+ title: {
+ de: "Campaign Site",
+ en: "Campaign Site",
+ },
+ summary: {
+ de: "Landing Seite fuer Produktkampagnen mit schneller Iteration.",
+ en: "Landing experience for product campaigns and quick iteration.",
+ },
+ category: {
+ de: "Marketing",
+ en: "Marketing",
+ },
+ year: "2023",
+ },
+];
+
+export const productItems: ProductItem[] = [
+ {
+ slug: "starter-kit",
+ name: {
+ de: "Starter Kit",
+ en: "Starter Kit",
+ },
+ summary: {
+ de: "Basis Paket fuer den schnellen Start von neuen Projekten.",
+ en: "Base package for launching new projects quickly.",
+ },
+ segment: {
+ de: "Small Teams",
+ en: "Small Teams",
+ },
+ price: {
+ de: "ab 990 EUR",
+ en: "from 990 EUR",
+ },
+ },
+ {
+ slug: "growth-kit",
+ name: {
+ de: "Growth Kit",
+ en: "Growth Kit",
+ },
+ summary: {
+ de: "Skalierbares Paket fuer wachsende Produkte und Prozesse.",
+ en: "Scalable package for growing products and processes.",
+ },
+ segment: {
+ de: "Scaleups",
+ en: "Scaleups",
+ },
+ price: {
+ de: "ab 2490 EUR",
+ en: "from 2490 EUR",
+ },
+ },
+ {
+ slug: "enterprise-kit",
+ name: {
+ de: "Enterprise Kit",
+ en: "Enterprise Kit",
+ },
+ summary: {
+ de: "Massgeschneiderte Loesung fuer grosse Teams und komplexe Systeme.",
+ en: "Tailored solution for large teams and complex systems.",
+ },
+ segment: {
+ de: "Enterprise",
+ en: "Enterprise",
+ },
+ price: {
+ de: "auf Anfrage",
+ en: "on request",
+ },
+ },
+];
+
+export function resolveLocale(locale: string): AppLocale {
+ return locale === "en" ? "en" : "de";
+}
+
+export function pickText(text: LocalizedText, locale: AppLocale): string {
+ return text[locale];
+}
+
+export function getPortfolioItem(slug: string) {
+ return portfolioItems.find((item) => item.slug === slug);
+}
+
+export function getProductItem(slug: string) {
+ return productItems.find((item) => item.slug === slug);
+}
diff --git a/lib/utils.ts b/lib/utils.ts
new file mode 100644
index 0000000..a5ef193
--- /dev/null
+++ b/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/messages/de.json b/messages/de.json
new file mode 100644
index 0000000..c1c5c92
--- /dev/null
+++ b/messages/de.json
@@ -0,0 +1,22 @@
+{
+ "navigation": {
+ "home": "Start",
+ "portfolio": "Portfolio",
+ "products": "Produkte",
+ "about": "Ueber uns",
+ "contact": "Kontakt",
+ "root": "Root",
+ "openMenu": "Menue oeffnen",
+ "closeMenu": "Menue schliessen",
+ "languagePlaceholder": "DE | EN"
+ },
+ "footer": {
+ "copyright": "© {year} moh-sass. Alle Rechte vorbehalten."
+ },
+ "homepage": {
+ "kicker": "Willkommen",
+ "title": "Mehrsprachiger Next.js Start",
+ "description": "Diese Startseite nutzt next-intl und ist fuer weitere Sprachen vorbereitet.",
+ "sectionPlaceholder": "Diese Seite ist bereit fuer deinen Inhalt."
+ }
+}
diff --git a/messages/en.json b/messages/en.json
new file mode 100644
index 0000000..2a3a775
--- /dev/null
+++ b/messages/en.json
@@ -0,0 +1,22 @@
+{
+ "navigation": {
+ "home": "Home",
+ "portfolio": "Portfolio",
+ "products": "Products",
+ "about": "About",
+ "contact": "Contact",
+ "root": "Root",
+ "openMenu": "Open menu",
+ "closeMenu": "Close menu",
+ "languagePlaceholder": "DE | EN"
+ },
+ "footer": {
+ "copyright": "© {year} moh-sass. All rights reserved."
+ },
+ "homepage": {
+ "kicker": "Welcome",
+ "title": "Modern multilingual starter",
+ "description": "This homepage is wired with next-intl and ready for additional locales.",
+ "sectionPlaceholder": "This page is ready for your content."
+ }
+}
diff --git a/middleware.ts b/middleware.ts
new file mode 100644
index 0000000..0a9eee1
--- /dev/null
+++ b/middleware.ts
@@ -0,0 +1,147 @@
+import createMiddleware from "next-intl/middleware";
+import { NextResponse } from "next/server";
+import type { NextRequest } from "next/server";
+
+import { routing } from "./i18n/routing";
+
+const intlMiddleware = createMiddleware(routing);
+const ADMIN_SESSION_COOKIE = "moh_admin_session";
+
+async function isMaintenanceModeEnabled(request: NextRequest): Promise {
+ try {
+ const response = await fetch(`${request.nextUrl.origin}/api/maintenance`, {
+ cache: "no-store",
+ headers: {
+ "x-middleware-cache": "bypass",
+ },
+ });
+
+ if (!response.ok) {
+ return false;
+ }
+
+ const data = (await response.json()) as { enabled?: boolean };
+ return data.enabled === true;
+ } catch {
+ return false;
+ }
+}
+
+function getLocaleFromPath(pathname: string): (typeof routing.locales)[number] {
+ const firstSegment = pathname.split("/").filter(Boolean)[0];
+
+ if (routing.locales.includes(firstSegment as (typeof routing.locales)[number])) {
+ return firstSegment as (typeof routing.locales)[number];
+ }
+
+ return routing.defaultLocale;
+}
+
+function isRootBasicAuthConfigured(): boolean {
+ return Boolean(process.env.ROOT_BASIC_AUTH_USER && process.env.ROOT_BASIC_AUTH_PASS);
+}
+
+function isRootBasicAuthValid(request: NextRequest): boolean {
+ if (!isRootBasicAuthConfigured()) {
+ return false;
+ }
+
+ const header = request.headers.get("authorization");
+ if (!header || !header.startsWith("Basic ")) {
+ return false;
+ }
+
+ try {
+ const decoded = atob(header.slice(6));
+ const index = decoded.indexOf(":");
+ if (index === -1) {
+ return false;
+ }
+
+ const user = decoded.slice(0, index);
+ const pass = decoded.slice(index + 1);
+
+ return (
+ user === process.env.ROOT_BASIC_AUTH_USER &&
+ pass === process.env.ROOT_BASIC_AUTH_PASS
+ );
+ } catch {
+ return false;
+ }
+}
+
+async function isSuperAdminSessionValid(request: NextRequest): Promise {
+ const token = request.cookies.get(ADMIN_SESSION_COOKIE)?.value;
+ const secret = process.env.ADMIN_AUTH_SECRET;
+
+ if (!token || !secret) {
+ return false;
+ }
+
+ const parts = token.split(".");
+ if (parts.length !== 2) {
+ return false;
+ }
+
+ const [value, signature] = parts;
+ if (!value || !signature) {
+ return false;
+ }
+
+ try {
+ const key = await crypto.subtle.importKey(
+ "raw",
+ new TextEncoder().encode(secret),
+ { name: "HMAC", hash: "SHA-256" },
+ false,
+ ["sign"],
+ );
+ const signed = await crypto.subtle.sign(
+ "HMAC",
+ key,
+ new TextEncoder().encode(value),
+ );
+ const expected = Array.from(new Uint8Array(signed))
+ .map((byte) => byte.toString(16).padStart(2, "0"))
+ .join("");
+
+ return signature === expected;
+ } catch {
+ return false;
+ }
+}
+
+export default async function middleware(request: NextRequest) {
+ const { pathname } = request.nextUrl;
+ const locale = getLocaleFromPath(pathname);
+ const isSuperAdmin = await isSuperAdminSessionValid(request);
+
+ const isRootRoute =
+ pathname === `/${locale}/root` || pathname.startsWith(`/${locale}/root/`);
+ const isComingSoonRoute =
+ pathname === `/${locale}/coming-soon` ||
+ pathname.startsWith(`/${locale}/coming-soon/`);
+
+ if (isRootRoute && isRootBasicAuthConfigured() && !isRootBasicAuthValid(request)) {
+ return new NextResponse("Authentication required", {
+ status: 401,
+ headers: {
+ "WWW-Authenticate": 'Basic realm="Root Area", charset="UTF-8"',
+ },
+ });
+ }
+
+ if (!isRootRoute && !isComingSoonRoute && !isSuperAdmin) {
+ const maintenanceModeEnabled = await isMaintenanceModeEnabled(request);
+
+ if (maintenanceModeEnabled) {
+ return NextResponse.redirect(new URL(`/${locale}/coming-soon`, request.url));
+ }
+ }
+
+ return intlMiddleware(request);
+}
+
+export const config = {
+ matcher: ["/((?!api|trpc|_next|_vercel|.*\\..*).*)"],
+};
diff --git a/next.config.mjs b/next.config.mjs
index 4678774..5e0e2a0 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -1,4 +1,19 @@
-/** @type {import('next').NextConfig} */
-const nextConfig = {};
+import createNextIntlPlugin from "next-intl/plugin";
-export default nextConfig;
+const withNextIntl = createNextIntlPlugin();
+
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ webpack: (config) => {
+ config.ignoreWarnings = [
+ ...(config.ignoreWarnings ?? []),
+ {
+ message: /Build dependencies behind this expression are ignored/,
+ },
+ ];
+
+ return config;
+ },
+};
+
+export default withNextIntl(nextConfig);
diff --git a/package-lock.json b/package-lock.json
index f559d9e..01418b7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,24 +1,37 @@
{
- "name": "mohfarawati",
+ "name": "moh-sass",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "mohfarawati",
+ "name": "moh-sass",
"version": "0.1.0",
"dependencies": {
+ "@prisma/adapter-pg": "^7.4.2",
+ "@prisma/client": "^7.4.2",
+ "clsx": "^2.1.1",
+ "framer-motion": "^12.35.0",
+ "lucide-react": "^0.577.0",
"next": "14.2.35",
+ "next-intl": "^4.8.3",
+ "next-themes": "^0.4.6",
+ "pg": "^8.20.0",
"react": "^18",
- "react-dom": "^18"
+ "react-dom": "^18",
+ "react-hook-form": "^7.71.2",
+ "tailwind-merge": "^3.5.0",
+ "zod": "^4.3.6"
},
"devDependencies": {
"@types/node": "^20",
+ "@types/pg": "^8.18.0",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.35",
"postcss": "^8",
+ "prisma": "^7.4.2",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
@@ -36,6 +49,73 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@chevrotain/cst-dts-gen": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz",
+ "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/gast": "10.5.0",
+ "@chevrotain/types": "10.5.0",
+ "lodash": "4.17.21"
+ }
+ },
+ "node_modules/@chevrotain/gast": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz",
+ "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/types": "10.5.0",
+ "lodash": "4.17.21"
+ }
+ },
+ "node_modules/@chevrotain/types": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz",
+ "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@chevrotain/utils": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz",
+ "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@electric-sql/pglite": {
+ "version": "0.3.15",
+ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz",
+ "integrity": "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@electric-sql/pglite-socket": {
+ "version": "0.0.20",
+ "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.0.20.tgz",
+ "integrity": "sha512-J5nLGsicnD9wJHnno9r+DGxfcZWh+YJMCe0q/aCgtG6XOm9Z7fKeite8IZSNXgZeGltSigM9U/vAWZQWdgcSFg==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "pglite-server": "dist/scripts/server.js"
+ },
+ "peerDependencies": {
+ "@electric-sql/pglite": "0.3.15"
+ }
+ },
+ "node_modules/@electric-sql/pglite-tools": {
+ "version": "0.2.20",
+ "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.2.20.tgz",
+ "integrity": "sha512-BK50ZnYa3IG7ztXhtgYf0Q7zijV32Iw1cYS8C+ThdQlwx12V5VZ9KRJ42y82Hyb4PkTxZQklVQA9JHyUlex33A==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "@electric-sql/pglite": "0.3.15"
+ }
+ },
"node_modules/@emnapi/core": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
@@ -133,6 +213,71 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@formatjs/ecma402-abstract": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-3.1.1.tgz",
+ "integrity": "sha512-jhZbTwda+2tcNrs4kKvxrPLPjx8QsBCLCUgrrJ/S+G9YrGHWLhAyFMMBHJBnBoOwuLHd7L14FgYudviKaxkO2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/fast-memoize": "3.1.0",
+ "@formatjs/intl-localematcher": "0.8.1",
+ "decimal.js": "^10.6.0",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@formatjs/fast-memoize": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.0.tgz",
+ "integrity": "sha512-b5mvSWCI+XVKiz5WhnBCY3RJ4ZwfjAidU0yVlKa3d3MSgKmH1hC3tBGEAtYyN5mqL7N0G5x0BOUYyO8CEupWgg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@formatjs/icu-messageformat-parser": {
+ "version": "3.5.1",
+ "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.1.tgz",
+ "integrity": "sha512-sSDmSvmmoVQ92XqWb499KrIhv/vLisJU8ITFrx7T7NZHUmMY7EL9xgRowAosaljhqnj/5iufG24QrdzB6X3ItA==",
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/ecma402-abstract": "3.1.1",
+ "@formatjs/icu-skeleton-parser": "2.1.1",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@formatjs/icu-skeleton-parser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.1.tgz",
+ "integrity": "sha512-PSFABlcNefjI6yyk8f7nyX1DC7NHmq6WaCHZLySEXBrXuLOB2f935YsnzuPjlz+ibhb9yWTdPeVX1OVcj24w2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/ecma402-abstract": "3.1.1",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@formatjs/intl-localematcher": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.1.tgz",
+ "integrity": "sha512-xwEuwQFdtSq1UKtQnyTZWC+eHdv7Uygoa+H2k/9uzBVQjDyp9r20LNDNKedWXll7FssT3GRHvqsdJGYSUWqYFA==",
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/fast-memoize": "3.1.0",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@hono/node-server": {
+ "version": "1.19.9",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz",
+ "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.14.1"
+ },
+ "peerDependencies": {
+ "hono": "^4"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
@@ -257,6 +402,30 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@mrleebo/prisma-ast": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/@mrleebo/prisma-ast/-/prisma-ast-0.13.1.tgz",
+ "integrity": "sha512-XyroGQXcHrZdvmrGJvsA9KNeOOgGMg1Vg9OlheUsBOSKznLMDl+YChxbkboRHvtFYJEMRYmlV3uoo/njCw05iw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "chevrotain": "^10.5.0",
+ "lilconfig": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@mrleebo/prisma-ast/node_modules/lilconfig": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
@@ -478,6 +647,313 @@
"node": ">=12.4.0"
}
},
+ "node_modules/@parcel/watcher": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
+ "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-libc": "^2.0.3",
+ "is-glob": "^4.0.3",
+ "node-addon-api": "^7.0.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "@parcel/watcher-android-arm64": "2.5.6",
+ "@parcel/watcher-darwin-arm64": "2.5.6",
+ "@parcel/watcher-darwin-x64": "2.5.6",
+ "@parcel/watcher-freebsd-x64": "2.5.6",
+ "@parcel/watcher-linux-arm-glibc": "2.5.6",
+ "@parcel/watcher-linux-arm-musl": "2.5.6",
+ "@parcel/watcher-linux-arm64-glibc": "2.5.6",
+ "@parcel/watcher-linux-arm64-musl": "2.5.6",
+ "@parcel/watcher-linux-x64-glibc": "2.5.6",
+ "@parcel/watcher-linux-x64-musl": "2.5.6",
+ "@parcel/watcher-win32-arm64": "2.5.6",
+ "@parcel/watcher-win32-ia32": "2.5.6",
+ "@parcel/watcher-win32-x64": "2.5.6"
+ }
+ },
+ "node_modules/@parcel/watcher-android-arm64": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
+ "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-arm64": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
+ "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-darwin-x64": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
+ "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-freebsd-x64": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
+ "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-glibc": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
+ "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm-musl": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
+ "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-glibc": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
+ "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-arm64-musl": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
+ "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-glibc": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
+ "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-linux-x64-musl": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
+ "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-arm64": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
+ "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-ia32": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
+ "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-x64": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
+ "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@parcel/watcher/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -489,6 +965,190 @@
"node": ">=14"
}
},
+ "node_modules/@prisma/adapter-pg": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.4.2.tgz",
+ "integrity": "sha512-oUo2Zhe9Tf6YwVL8kLPuOLTK1Z2pwi/Ua77t2PuGyBan2w7shRKqHvYK+3XXmRH9RWhPJ4SMtHZKpNo6Ax/4bQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/driver-adapter-utils": "7.4.2",
+ "pg": "^8.16.3",
+ "postgres-array": "3.0.4"
+ }
+ },
+ "node_modules/@prisma/client": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.4.2.tgz",
+ "integrity": "sha512-ts2mu+cQHriAhSxngO3StcYubBGTWDtu/4juZhXCUKOwgh26l+s4KD3vT2kMUzFyrYnll9u/3qWrtzRv9CGWzA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/client-runtime-utils": "7.4.2"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24.0"
+ },
+ "peerDependencies": {
+ "prisma": "*",
+ "typescript": ">=5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "prisma": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@prisma/client-runtime-utils": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.4.2.tgz",
+ "integrity": "sha512-cID+rzOEb38VyMsx5LwJMEY4NGIrWCNpKu/0ImbeooQ2Px7TI+kOt7cm0NelxUzF2V41UVVXAmYjANZQtCu1/Q==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/config": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.4.2.tgz",
+ "integrity": "sha512-CftBjWxav99lzY1Z4oDgomdb1gh9BJFAOmWF6P2v1xRfXqQb56DfBub+QKcERRdNoAzCb3HXy3Zii8Vb4AsXhg==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "c12": "3.1.0",
+ "deepmerge-ts": "7.1.5",
+ "effect": "3.18.4",
+ "empathic": "2.0.0"
+ }
+ },
+ "node_modules/@prisma/debug": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.4.2.tgz",
+ "integrity": "sha512-aP7qzu+g/JnbF6U69LMwHoUkELiserKmWsE2shYuEpNUJ4GrtxBCvZwCyCBHFSH2kLTF2l1goBlBh4wuvRq62w==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/dev": {
+ "version": "0.20.0",
+ "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.20.0.tgz",
+ "integrity": "sha512-ovlBYwWor0OzG+yH4J3Ot+AneD818BttLA+Ii7wjbcLHUrnC4tbUPVGyNd3c/+71KETPKZfjhkTSpdS15dmXNQ==",
+ "devOptional": true,
+ "license": "ISC",
+ "dependencies": {
+ "@electric-sql/pglite": "0.3.15",
+ "@electric-sql/pglite-socket": "0.0.20",
+ "@electric-sql/pglite-tools": "0.2.20",
+ "@hono/node-server": "1.19.9",
+ "@mrleebo/prisma-ast": "0.13.1",
+ "@prisma/get-platform": "7.2.0",
+ "@prisma/query-plan-executor": "7.2.0",
+ "foreground-child": "3.3.1",
+ "get-port-please": "3.2.0",
+ "hono": "4.11.4",
+ "http-status-codes": "2.3.0",
+ "pathe": "2.0.3",
+ "proper-lockfile": "4.1.2",
+ "remeda": "2.33.4",
+ "std-env": "3.10.0",
+ "valibot": "1.2.0",
+ "zeptomatch": "2.1.0"
+ }
+ },
+ "node_modules/@prisma/driver-adapter-utils": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.4.2.tgz",
+ "integrity": "sha512-REdjFpT/ye9KdDs+CXAXPIbMQkVLhne9G5Pe97sNY4Ovx4r2DAbWM9hOFvvB1Oq8H8bOCdu0Ri3AoGALquQqVw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.4.2"
+ }
+ },
+ "node_modules/@prisma/engines": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.4.2.tgz",
+ "integrity": "sha512-B+ZZhI4rXlzjVqRw/93AothEKOU5/x4oVyJFGo9RpHPnBwaPwk4Pi0Q4iGXipKxeXPs/dqljgNBjK0m8nocOJA==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.4.2",
+ "@prisma/engines-version": "7.5.0-10.94a226be1cf2967af2541cca5529f0f7ba866919",
+ "@prisma/fetch-engine": "7.4.2",
+ "@prisma/get-platform": "7.4.2"
+ }
+ },
+ "node_modules/@prisma/engines-version": {
+ "version": "7.5.0-10.94a226be1cf2967af2541cca5529f0f7ba866919",
+ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.5.0-10.94a226be1cf2967af2541cca5529f0f7ba866919.tgz",
+ "integrity": "sha512-5FIKY3KoYQlBuZC2yc16EXfVRQ8HY+fLqgxkYfWCtKhRb3ajCRzP/rPeoSx11+NueJDANdh4hjY36mdmrTcGSg==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/engines/node_modules/@prisma/get-platform": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.4.2.tgz",
+ "integrity": "sha512-UTnChXRwiauzl/8wT4hhe7Xmixja9WE28oCnGpBtRejaHhvekx5kudr3R4Y9mLSA0kqGnAMeyTiKwDVMjaEVsw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.4.2"
+ }
+ },
+ "node_modules/@prisma/fetch-engine": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.4.2.tgz",
+ "integrity": "sha512-f/c/MwYpdJO7taLETU8rahEstLeXfYgQGlz5fycG7Fbmva3iPdzGmjiSWHeSWIgNnlXnelUdCJqyZnFocurZuA==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.4.2",
+ "@prisma/engines-version": "7.5.0-10.94a226be1cf2967af2541cca5529f0f7ba866919",
+ "@prisma/get-platform": "7.4.2"
+ }
+ },
+ "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.4.2.tgz",
+ "integrity": "sha512-UTnChXRwiauzl/8wT4hhe7Xmixja9WE28oCnGpBtRejaHhvekx5kudr3R4Y9mLSA0kqGnAMeyTiKwDVMjaEVsw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.4.2"
+ }
+ },
+ "node_modules/@prisma/get-platform": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz",
+ "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.2.0"
+ }
+ },
+ "node_modules/@prisma/get-platform/node_modules/@prisma/debug": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz",
+ "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/query-plan-executor": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz",
+ "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/studio-core": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.13.1.tgz",
+ "integrity": "sha512-agdqaPEePRHcQ7CexEfkX1RvSH9uWDb6pXrZnhCRykhDFAV0/0P3d07WtfiY8hZWb7oRU4v+NkT4cGFHkQJIPg==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -503,6 +1163,179 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@schummar/icu-type-parser": {
+ "version": "1.21.5",
+ "resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz",
+ "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==",
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@swc/core-darwin-arm64": {
+ "version": "1.15.18",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.18.tgz",
+ "integrity": "sha512-+mIv7uBuSaywN3C9LNuWaX1jJJ3SKfiJuE6Lr3bd+/1Iv8oMU7oLBjYMluX1UrEPzwN2qCdY6Io0yVicABoCwQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-darwin-x64": {
+ "version": "1.15.18",
+ "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.18.tgz",
+ "integrity": "sha512-wZle0eaQhnzxWX5V/2kEOI6Z9vl/lTFEC6V4EWcn+5pDjhemCpQv9e/TDJ0GIoiClX8EDWRvuZwh+Z3dhL1NAg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm-gnueabihf": {
+ "version": "1.15.18",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.18.tgz",
+ "integrity": "sha512-ao61HGXVqrJFHAcPtF4/DegmwEkVCo4HApnotLU8ognfmU8x589z7+tcf3hU+qBiU1WOXV5fQX6W9Nzs6hjxDw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm64-gnu": {
+ "version": "1.15.18",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.18.tgz",
+ "integrity": "sha512-3xnctOBLIq3kj8PxOCgPrGjBLP/kNOddr6f5gukYt/1IZxsITQaU9TDyjeX6jG+FiCIHjCuWuffsyQDL5Ew1bg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-arm64-musl": {
+ "version": "1.15.18",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.18.tgz",
+ "integrity": "sha512-0a+Lix+FSSHBSBOA0XznCcHo5/1nA6oLLjcnocvzXeqtdjnPb+SvchItHI+lfeiuj1sClYPDvPMLSLyXFaiIKw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-x64-gnu": {
+ "version": "1.15.18",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.18.tgz",
+ "integrity": "sha512-wG9J8vReUlpaHz4KOD/5UE1AUgirimU4UFT9oZmupUDEofxJKYb1mTA/DrMj0s78bkBiNI+7Fo2EgPuvOJfuAA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-linux-x64-musl": {
+ "version": "1.15.18",
+ "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.18.tgz",
+ "integrity": "sha512-4nwbVvCphKzicwNWRmvD5iBaZj8JYsRGa4xOxJmOyHlMDpsvvJ2OR2cODlvWyGFH6BYL1MfIAK3qph3hp0Az6g==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-arm64-msvc": {
+ "version": "1.15.18",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.18.tgz",
+ "integrity": "sha512-zk0RYO+LjiBCat2RTMHzAWaMky0cra9loH4oRrLKLLNuL+jarxKLFDA8xTZWEkCPLjUTwlRN7d28eDLLMgtUcQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-ia32-msvc": {
+ "version": "1.15.18",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.18.tgz",
+ "integrity": "sha512-yVuTrZ0RccD5+PEkpcLOBAuPbYBXS6rslENvIXfvJGXSdX5QGi1ehC4BjAMl5FkKLiam4kJECUI0l7Hq7T1vwg==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@swc/core-win32-x64-msvc": {
+ "version": "1.15.18",
+ "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.18.tgz",
+ "integrity": "sha512-7NRmE4hmUQNCbYU3Hn9Tz57mK9Qq4c97ZS+YlamlK6qG9Fb5g/BB3gPDe0iLlJkns/sYv2VWSkm8c3NmbEGjbg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@@ -519,6 +1352,15 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@swc/types": {
+ "version": "0.1.25",
+ "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz",
+ "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/counter": "^0.1.3"
+ }
+ },
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
@@ -547,18 +1389,30 @@
"undici-types": "~6.21.0"
}
},
+ "node_modules/@types/pg": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.18.0.tgz",
+ "integrity": "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "pg-protocol": "*",
+ "pg-types": "^2.2.0"
+ }
+ },
"node_modules/@types/prop-types": {
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.28",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@@ -1437,6 +2291,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/aws-ssl-profiles": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
+ "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
"node_modules/axe-core": {
"version": "4.11.1",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz",
@@ -1512,6 +2376,75 @@
"node": ">=10.16.0"
}
},
+ "node_modules/c12": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz",
+ "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^4.0.3",
+ "confbox": "^0.2.2",
+ "defu": "^6.1.4",
+ "dotenv": "^16.6.1",
+ "exsolve": "^1.0.7",
+ "giget": "^2.0.0",
+ "jiti": "^2.4.2",
+ "ohash": "^2.0.11",
+ "pathe": "^2.0.3",
+ "perfect-debounce": "^1.0.0",
+ "pkg-types": "^2.2.0",
+ "rc9": "^2.1.2"
+ },
+ "peerDependencies": {
+ "magicast": "^0.3.5"
+ },
+ "peerDependenciesMeta": {
+ "magicast": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/c12/node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/c12/node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/c12/node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/call-bind": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -1619,6 +2552,21 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/chevrotain": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz",
+ "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/cst-dts-gen": "10.5.0",
+ "@chevrotain/gast": "10.5.0",
+ "@chevrotain/types": "10.5.0",
+ "@chevrotain/utils": "10.5.0",
+ "lodash": "4.17.21",
+ "regexp-to-ast": "0.5.0"
+ }
+ },
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -1657,12 +2605,31 @@
"node": ">= 6"
}
},
+ "node_modules/citty": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz",
+ "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "consola": "^3.2.3"
+ }
+ },
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1700,11 +2667,28 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/confbox": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
+ "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/consola": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
+ "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^14.18.0 || >=16.10.0"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@@ -1732,7 +2716,7 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/damerau-levenshtein": {
@@ -1814,6 +2798,12 @@
}
}
},
+ "node_modules/decimal.js": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
+ "license": "MIT"
+ },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -1821,6 +2811,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/deepmerge-ts": {
+ "version": "7.1.5",
+ "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz",
+ "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==",
+ "devOptional": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
@@ -1857,6 +2857,39 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/defu": {
+ "version": "6.1.4",
+ "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
+ "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/denque": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/destr": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
+ "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -1884,6 +2917,19 @@
"node": ">=6.0.0"
}
},
+ "node_modules/dotenv": {
+ "version": "16.6.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+ "devOptional": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -1906,6 +2952,17 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/effect": {
+ "version": "3.18.4",
+ "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz",
+ "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "fast-check": "^3.23.1"
+ }
+ },
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -1913,6 +2970,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/empathic": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz",
+ "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/es-abstract": {
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz",
@@ -2557,6 +3624,36 @@
"node": ">=0.10.0"
}
},
+ "node_modules/exsolve": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
+ "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-check": {
+ "version": "3.23.2",
+ "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz",
+ "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "pure-rand": "^6.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -2703,7 +3800,7 @@
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
- "dev": true,
+ "devOptional": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
@@ -2716,6 +3813,33 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/framer-motion": {
+ "version": "12.35.0",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.35.0.tgz",
+ "integrity": "sha512-w8hghCMQ4oq10j6aZh3U2yeEQv5K69O/seDI/41PK4HtgkLrcBovUNc0ayBC3UyyU7V1mrY2yLzvYdWJX9pGZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^12.35.0",
+ "motion-utils": "^12.29.2",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -2779,6 +3903,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/generate-function": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
+ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-property": "^1.0.2"
+ }
+ },
"node_modules/generator-function": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
@@ -2814,6 +3948,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-port-please": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz",
+ "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
@@ -2859,6 +4000,24 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
+ "node_modules/giget": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz",
+ "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "citty": "^0.1.6",
+ "consola": "^3.4.0",
+ "defu": "^6.1.4",
+ "node-fetch-native": "^1.6.6",
+ "nypm": "^0.6.0",
+ "pathe": "^2.0.3"
+ },
+ "bin": {
+ "giget": "dist/cli.mjs"
+ }
+ },
"node_modules/glob": {
"version": "10.3.10",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
@@ -2974,6 +4133,13 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
+ "node_modules/grammex": {
+ "version": "3.1.12",
+ "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.12.tgz",
+ "integrity": "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -2981,6 +4147,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/graphmatch": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz",
+ "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/has-bigints": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
@@ -3075,6 +4248,55 @@
"node": ">= 0.4"
}
},
+ "node_modules/hono": {
+ "version": "4.11.4",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz",
+ "integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.9.0"
+ }
+ },
+ "node_modules/http-status-codes": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz",
+ "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/icu-minify": {
+ "version": "4.8.3",
+ "resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.8.3.tgz",
+ "integrity": "sha512-65Av7FLosNk7bPbmQx5z5XG2Y3T2GFppcjiXh4z1idHeVgQxlDpAmkGoYI0eFzAvrOnjpWTL5FmPDhsdfRMPEA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/amannn"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/icu-messageformat-parser": "^3.4.0"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -3146,6 +4368,18 @@
"node": ">= 0.4"
}
},
+ "node_modules/intl-messageformat": {
+ "version": "11.1.2",
+ "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.1.2.tgz",
+ "integrity": "sha512-ucSrQmZGAxfiBHfBRXW/k7UC8MaGFlEj4Ry1tKiDcmgwQm1y3EDl40u+4VNHYomxJQMJi9NEI3riDRlth96jKg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@formatjs/ecma402-abstract": "3.1.1",
+ "@formatjs/fast-memoize": "3.1.0",
+ "@formatjs/icu-messageformat-parser": "3.5.1",
+ "tslib": "^2.8.1"
+ }
+ },
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -3308,7 +4542,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -3364,7 +4597,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
@@ -3436,6 +4668,13 @@
"node": ">=8"
}
},
+ "node_modules/is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
@@ -3592,7 +4831,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
+ "devOptional": true,
"license": "ISC"
},
"node_modules/iterator.prototype": {
@@ -3791,6 +5030,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -3798,6 +5044,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "devOptional": true,
+ "license": "Apache-2.0"
+ },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -3817,6 +5070,31 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/lru.min": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
+ "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "bun": ">=1.0.0",
+ "deno": ">=1.30.0",
+ "node": ">=8.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wellwelwel"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.577.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.577.0.tgz",
+ "integrity": "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -3884,6 +5162,21 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/motion-dom": {
+ "version": "12.35.0",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.35.0.tgz",
+ "integrity": "sha512-FFMLEnIejK/zDABn+vqGVAUN4T0+3fw+cVAY8MMT65yR+j5uMuvWdd4npACWhh94OVWQs79CrBBuwOwGRZAQiA==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^12.29.2"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "12.29.2",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz",
+ "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==",
+ "license": "MIT"
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -3891,6 +5184,27 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/mysql2": {
+ "version": "3.15.3",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz",
+ "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "aws-ssl-profiles": "^1.1.1",
+ "denque": "^2.1.0",
+ "generate-function": "^2.3.1",
+ "iconv-lite": "^0.7.0",
+ "long": "^5.2.1",
+ "lru.min": "^1.0.0",
+ "named-placeholders": "^1.1.3",
+ "seq-queue": "^0.0.5",
+ "sqlstring": "^2.3.2"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ }
+ },
"node_modules/mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
@@ -3903,6 +5217,19 @@
"thenify-all": "^1.0.0"
}
},
+ "node_modules/named-placeholders": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
+ "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "lru.min": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -3944,6 +5271,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/next": {
"version": "14.2.35",
"resolved": "https://registry.npmjs.org/next/-/next-14.2.35.tgz",
@@ -3994,6 +5330,103 @@
}
}
},
+ "node_modules/next-intl": {
+ "version": "4.8.3",
+ "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.8.3.tgz",
+ "integrity": "sha512-PvdBDWg+Leh7BR7GJUQbCDVVaBRn37GwDBWc9sv0rVQOJDQ5JU1rVzx9EEGuOGYo0DHAl70++9LQ7HxTawdL7w==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/amannn"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/intl-localematcher": "^0.8.1",
+ "@parcel/watcher": "^2.4.1",
+ "@swc/core": "^1.15.2",
+ "icu-minify": "^4.8.3",
+ "negotiator": "^1.0.0",
+ "next-intl-swc-plugin-extractor": "^4.8.3",
+ "po-parser": "^2.1.1",
+ "use-intl": "^4.8.3"
+ },
+ "peerDependencies": {
+ "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0",
+ "typescript": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/next-intl-swc-plugin-extractor": {
+ "version": "4.8.3",
+ "resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.8.3.tgz",
+ "integrity": "sha512-YcaT+R9z69XkGhpDarVFWUprrCMbxgIQYPUaXoE6LGVnLjGdo8hu3gL6bramDVjNKViYY8a/pXPy7Bna0mXORg==",
+ "license": "MIT"
+ },
+ "node_modules/next-intl/node_modules/@swc/core": {
+ "version": "1.15.18",
+ "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.18.tgz",
+ "integrity": "sha512-z87aF9GphWp//fnkRsqvtY+inMVPgYW3zSlXH1kJFvRT5H/wiAn+G32qW5l3oEk63KSF1x3Ov0BfHCObAmT8RA==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/counter": "^0.1.3",
+ "@swc/types": "^0.1.25"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/swc"
+ },
+ "optionalDependencies": {
+ "@swc/core-darwin-arm64": "1.15.18",
+ "@swc/core-darwin-x64": "1.15.18",
+ "@swc/core-linux-arm-gnueabihf": "1.15.18",
+ "@swc/core-linux-arm64-gnu": "1.15.18",
+ "@swc/core-linux-arm64-musl": "1.15.18",
+ "@swc/core-linux-x64-gnu": "1.15.18",
+ "@swc/core-linux-x64-musl": "1.15.18",
+ "@swc/core-win32-arm64-msvc": "1.15.18",
+ "@swc/core-win32-ia32-msvc": "1.15.18",
+ "@swc/core-win32-x64-msvc": "1.15.18"
+ },
+ "peerDependencies": {
+ "@swc/helpers": ">=0.5.17"
+ },
+ "peerDependenciesMeta": {
+ "@swc/helpers": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/next-intl/node_modules/@swc/helpers": {
+ "version": "0.5.19",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz",
+ "integrity": "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/next-themes": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
+ "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
+ }
+ },
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -4022,6 +5455,12 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/node-addon-api": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
+ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+ "license": "MIT"
+ },
"node_modules/node-exports-info": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
@@ -4051,6 +5490,13 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/node-fetch-native": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz",
+ "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -4061,6 +5507,31 @@
"node": ">=0.10.0"
}
},
+ "node_modules/nypm": {
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz",
+ "integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "citty": "^0.2.0",
+ "pathe": "^2.0.3",
+ "tinyexec": "^1.0.2"
+ },
+ "bin": {
+ "nypm": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/nypm/node_modules/citty": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.1.tgz",
+ "integrity": "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -4194,6 +5665,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/ohash": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
+ "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -4309,7 +5787,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -4339,6 +5817,118 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/perfect-debounce": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
+ "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/pg": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz",
+ "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
+ "license": "MIT",
+ "dependencies": {
+ "pg-connection-string": "^2.12.0",
+ "pg-pool": "^3.13.0",
+ "pg-protocol": "^1.13.0",
+ "pg-types": "2.2.0",
+ "pgpass": "1.0.5"
+ },
+ "engines": {
+ "node": ">= 16.0.0"
+ },
+ "optionalDependencies": {
+ "pg-cloudflare": "^1.3.0"
+ },
+ "peerDependencies": {
+ "pg-native": ">=3.0.1"
+ },
+ "peerDependenciesMeta": {
+ "pg-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pg-cloudflare": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz",
+ "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/pg-connection-string": {
+ "version": "2.12.0",
+ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz",
+ "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==",
+ "license": "MIT"
+ },
+ "node_modules/pg-int8": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/pg-pool": {
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz",
+ "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "pg": ">=8.0"
+ }
+ },
+ "node_modules/pg-protocol": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz",
+ "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==",
+ "license": "MIT"
+ },
+ "node_modules/pg-types": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+ "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+ "license": "MIT",
+ "dependencies": {
+ "pg-int8": "1.0.1",
+ "postgres-array": "~2.0.0",
+ "postgres-bytea": "~1.0.0",
+ "postgres-date": "~1.0.4",
+ "postgres-interval": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pg-types/node_modules/postgres-array": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+ "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pgpass": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+ "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+ "license": "MIT",
+ "dependencies": {
+ "split2": "^4.1.0"
+ }
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -4378,6 +5968,24 @@
"node": ">= 6"
}
},
+ "node_modules/pkg-types": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
+ "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.2.2",
+ "exsolve": "^1.0.7",
+ "pathe": "^2.0.3"
+ }
+ },
+ "node_modules/po-parser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.1.1.tgz",
+ "integrity": "sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==",
+ "license": "MIT"
+ },
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
@@ -4551,6 +6159,59 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/postgres": {
+ "version": "3.4.7",
+ "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz",
+ "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==",
+ "devOptional": true,
+ "license": "Unlicense",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/porsager"
+ }
+ },
+ "node_modules/postgres-array": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz",
+ "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/postgres-bytea": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
+ "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-date": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+ "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-interval": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "xtend": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -4561,6 +6222,40 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prisma": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.4.2.tgz",
+ "integrity": "sha512-2bP8Ruww3Q95Z2eH4Yqh4KAENRsj/SxbdknIVBfd6DmjPwmpsC4OVFMLOeHt6tM3Amh8ebjvstrUz3V/hOe1dA==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/config": "7.4.2",
+ "@prisma/dev": "0.20.0",
+ "@prisma/engines": "7.4.2",
+ "@prisma/studio-core": "0.13.1",
+ "mysql2": "3.15.3",
+ "postgres": "3.4.7"
+ },
+ "bin": {
+ "prisma": "build/index.js"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24.0"
+ },
+ "peerDependencies": {
+ "better-sqlite3": ">=9.0.0",
+ "typescript": ">=5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "better-sqlite3": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -4573,6 +6268,25 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/proper-lockfile": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
+ "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "retry": "^0.12.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "node_modules/proper-lockfile/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "devOptional": true,
+ "license": "ISC"
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -4583,6 +6297,23 @@
"node": ">=6"
}
},
+ "node_modules/pure-rand": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
+ "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
+ "devOptional": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -4604,6 +6335,17 @@
],
"license": "MIT"
},
+ "node_modules/rc9": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz",
+ "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "defu": "^6.1.4",
+ "destr": "^2.0.3"
+ }
+ },
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
@@ -4629,6 +6371,22 @@
"react": "^18.3.1"
}
},
+ "node_modules/react-hook-form": {
+ "version": "7.71.2",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.2.tgz",
+ "integrity": "sha512-1CHvcDYzuRUNOflt4MOq3ZM46AronNJtQ1S7tnX6YN4y72qhgiUItpacZUAQ0TyWYci3yz1X+rXaSxiuEm86PA==",
+ "license": "MIT",
+ "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",
@@ -4682,6 +6440,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/regexp-to-ast": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz",
+ "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/regexp.prototype.flags": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
@@ -4703,6 +6468,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/remeda": {
+ "version": "2.33.4",
+ "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz",
+ "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/remeda"
+ }
+ },
"node_modules/resolve": {
"version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
@@ -4744,6 +6519,16 @@
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
+ "node_modules/retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
"node_modules/reusify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
@@ -4873,6 +6658,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
@@ -4895,6 +6687,12 @@
"node": ">=10"
}
},
+ "node_modules/seq-queue": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
+ "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==",
+ "devOptional": true
+ },
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -4948,7 +6746,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
@@ -4961,7 +6759,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -5047,7 +6845,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
+ "devOptional": true,
"license": "ISC",
"engines": {
"node": ">=14"
@@ -5065,6 +6863,25 @@
"node": ">=0.10.0"
}
},
+ "node_modules/split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
+ "node_modules/sqlstring": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
+ "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/stable-hash": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
@@ -5072,6 +6889,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"node_modules/stop-iteration-iterator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
@@ -5399,6 +7223,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/tailwind-merge": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz",
+ "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
"node_modules/tailwindcss": {
"version": "3.4.19",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
@@ -5467,6 +7301,16 @@
"node": ">=0.8"
}
},
+ "node_modules/tinyexec": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
+ "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -5675,7 +7519,7 @@
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "dev": true,
+ "devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -5756,6 +7600,27 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-intl": {
+ "version": "4.8.3",
+ "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.8.3.tgz",
+ "integrity": "sha512-nLxlC/RH+le6g3amA508Itnn/00mE+J22ui21QhOWo5V9hCEC43+WtnRAITbJW0ztVZphev5X9gvOf2/Dk9PLA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/amannn"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/fast-memoize": "^3.1.0",
+ "@schummar/icu-type-parser": "1.21.5",
+ "icu-minify": "^4.8.3",
+ "intl-messageformat": "^11.1.0"
+ },
+ "peerDependencies": {
+ "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -5763,11 +7628,26 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/valibot": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz",
+ "integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "typescript": ">=5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
+ "devOptional": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
@@ -5986,6 +7866,15 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -5998,6 +7887,26 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zeptomatch": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz",
+ "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "grammex": "^3.1.11",
+ "graphmatch": "^1.1.0"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
}
}
}
diff --git a/package.json b/package.json
index 401a9a5..d01a7ed 100644
--- a/package.json
+++ b/package.json
@@ -1,26 +1,46 @@
{
- "name": "mohfarawati",
+ "name": "moh-sass",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
- "lint": "next lint"
+ "lint": "next lint",
+ "prisma:generate": "prisma generate",
+ "db:migrate": "prisma migrate deploy",
+ "db:migrate:dev": "prisma migrate dev",
+ "db:seed": "prisma db seed"
+ },
+ "prisma": {
+ "seed": "node prisma/seed.js"
},
"dependencies": {
+ "@prisma/adapter-pg": "^7.4.2",
+ "@prisma/client": "^7.4.2",
+ "clsx": "^2.1.1",
+ "framer-motion": "^12.35.0",
+ "lucide-react": "^0.577.0",
+ "next": "14.2.35",
+ "next-intl": "^4.8.3",
+ "next-themes": "^0.4.6",
+ "pg": "^8.20.0",
"react": "^18",
"react-dom": "^18",
- "next": "14.2.35"
+ "react-hook-form": "^7.71.2",
+ "tailwind-merge": "^3.5.0",
+ "zod": "^4.3.6"
},
"devDependencies": {
- "typescript": "^5",
"@types/node": "^20",
+ "@types/pg": "^8.18.0",
"@types/react": "^18",
"@types/react-dom": "^18",
- "postcss": "^8",
- "tailwindcss": "^3.4.1",
"eslint": "^8",
- "eslint-config-next": "14.2.35"
+ "eslint-config-next": "14.2.35",
+ "postcss": "^8",
+ "prisma": "^7.4.2",
+ "tailwindcss": "^3.4.1",
+ "typescript": "^5"
}
}
diff --git a/prisma.config.ts b/prisma.config.ts
new file mode 100644
index 0000000..970093f
--- /dev/null
+++ b/prisma.config.ts
@@ -0,0 +1,13 @@
+import "dotenv/config";
+import { defineConfig } from "prisma/config";
+
+export default defineConfig({
+ schema: "prisma/schema.prisma",
+ migrations: {
+ path: "prisma/migrations",
+ seed: "node prisma/seed.js",
+ },
+ datasource: {
+ url: process.env["DATABASE_URL"] ?? "",
+ },
+});
diff --git a/prisma/migrations/20260306090522_init_app_config/migration.sql b/prisma/migrations/20260306090522_init_app_config/migration.sql
new file mode 100644
index 0000000..31f0801
--- /dev/null
+++ b/prisma/migrations/20260306090522_init_app_config/migration.sql
@@ -0,0 +1,13 @@
+-- CreateTable
+CREATE TABLE "AppConfig" (
+ "id" TEXT NOT NULL,
+ "key" TEXT NOT NULL,
+ "value" TEXT NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" TIMESTAMP(3) NOT NULL,
+
+ CONSTRAINT "AppConfig_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "AppConfig_key_key" ON "AppConfig"("key");
diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml
new file mode 100644
index 0000000..044d57c
--- /dev/null
+++ b/prisma/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (e.g., Git)
+provider = "postgresql"
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
new file mode 100644
index 0000000..eb6c3fe
--- /dev/null
+++ b/prisma/schema.prisma
@@ -0,0 +1,15 @@
+generator client {
+ provider = "prisma-client-js"
+}
+
+datasource db {
+ provider = "postgresql"
+}
+
+model AppConfig {
+ id String @id @default(cuid())
+ key String @unique
+ value String
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+}
diff --git a/prisma/seed.js b/prisma/seed.js
new file mode 100644
index 0000000..d50ee5a
--- /dev/null
+++ b/prisma/seed.js
@@ -0,0 +1,28 @@
+const { PrismaPg } = require("@prisma/adapter-pg");
+const { PrismaClient } = require("@prisma/client");
+const { Pool } = require("pg");
+
+const connectionString =
+ process.env.DATABASE_URL ||
+ "postgresql://postgres:postgres@localhost:5432/moh_sass?schema=public";
+
+const pool = new Pool({ connectionString });
+const prisma = new PrismaClient({ adapter: new PrismaPg(pool) });
+
+async function main() {
+ await prisma.appConfig.upsert({
+ where: { key: "siteName" },
+ update: { value: "moh-sass" },
+ create: { key: "siteName", value: "moh-sass" },
+ });
+}
+
+main()
+ .catch((error) => {
+ console.error("Seed failed:", error);
+ process.exit(1);
+ })
+ .finally(async () => {
+ await prisma.$disconnect();
+ await pool.end();
+ });
diff --git a/public/logos/dark-primary.svg b/public/logos/dark-primary.svg
new file mode 100755
index 0000000..5db18b8
--- /dev/null
+++ b/public/logos/dark-primary.svg
@@ -0,0 +1,25 @@
+
+
\ No newline at end of file
diff --git a/public/logos/light-primary.svg b/public/logos/light-primary.svg
new file mode 100755
index 0000000..8bcce57
--- /dev/null
+++ b/public/logos/light-primary.svg
@@ -0,0 +1,25 @@
+
+
\ No newline at end of file