diff --git a/apps/api/package.json b/apps/api/package.json
index d1b337f..50024a1 100644
--- a/apps/api/package.json
+++ b/apps/api/package.json
@@ -12,6 +12,7 @@
"dependencies": {
"@hono/node-server": "^1.19.9",
"@hono/zod-openapi": "^0.19.2",
+ "@repo/auth": "workspace:*",
"@repo/db": "workspace:*",
"@repo/env": "workspace:*",
"dotenv": "^17.2.4",
diff --git a/apps/api/src/app.ts b/apps/api/src/app.ts
index 113e848..9bd30b2 100644
--- a/apps/api/src/app.ts
+++ b/apps/api/src/app.ts
@@ -1,3 +1,5 @@
+import { auth } from "@repo/auth"
+import { env } from "@repo/env/server"
import { cors } from "hono/cors"
import createApp from "@/lib/helpers/app/create-app"
import configureOpenAPI from "@/lib/helpers/openapi/configure-openapi"
@@ -6,7 +8,18 @@ import waitlistRouter from "@/routes/waitlist/index"
const app = createApp()
-app.use("*", cors())
+app.use(
+ "*",
+ cors({
+ origin:
+ env.NODE_ENV === "development"
+ ? "http://localhost:3000"
+ : env.NEXT_PUBLIC_API_URL,
+ credentials: true,
+ })
+)
+
+app.on(["GET", "POST"], "/api/auth/**", (c) => auth.handler(c.req.raw))
configureOpenAPI(app)
diff --git a/apps/web/.gitignore b/apps/web/.gitignore
index 5ef6a52..8c3bd87 100644
--- a/apps/web/.gitignore
+++ b/apps/web/.gitignore
@@ -1,24 +1,8 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
# dependencies
/node_modules
-/.pnp
-.pnp.*
-.yarn/*
-!.yarn/patches
-!.yarn/plugins
-!.yarn/releases
-!.yarn/versions
-
-# testing
-/coverage
-
-# next.js
-/.next/
-/out/
# production
-/build
+/dist
# misc
.DS_Store
@@ -30,12 +14,11 @@ yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
-# env files (can opt-in for committing if needed)
+# env files
.env*
-# vercel
-.vercel
-
# typescript
*.tsbuildinfo
-next-env.d.ts
+
+# tanstack router (auto-generated)
+routeTree.gen.ts
diff --git a/apps/web/README.md b/apps/web/README.md
deleted file mode 100644
index e215bc4..0000000
--- a/apps/web/README.md
+++ /dev/null
@@ -1,36 +0,0 @@
-This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
-
-## Getting Started
-
-First, run the development server:
-
-```bash
-npm run dev
-# or
-yarn dev
-# or
-pnpm dev
-# or
-bun dev
-```
-
-Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
-
-You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
-
-This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
-
-## Learn More
-
-To learn more about Next.js, take a look at the following resources:
-
-- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
-- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
-
-You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
-
-## Deploy on Vercel
-
-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.
diff --git a/apps/web/app/components/providers.tsx b/apps/web/app/components/providers.tsx
deleted file mode 100644
index 14484bd..0000000
--- a/apps/web/app/components/providers.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-"use client"
-
-import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
-import { ThemeProvider } from "next-themes"
-import { useState } from "react"
-
-export function Providers({ children }: { children: React.ReactNode }) {
- const [queryClient] = useState(
- () =>
- new QueryClient({
- defaultOptions: {
- queries: {
- staleTime: 60 * 1000,
- },
- },
- })
- )
-
- return (
-
-
- {children}
-
-
- )
-}
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
deleted file mode 100644
index fb9443e..0000000
--- a/apps/web/app/layout.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import type { Metadata } from "next"
-import localFont from "next/font/local"
-import { Providers } from "./components/providers"
-import "@repo/ui/globals.css"
-
-const geistSans = localFont({
- src: "./fonts/GeistVF.woff",
- variable: "--font-geist-sans",
-})
-const geistMono = localFont({
- src: "./fonts/GeistMonoVF.woff",
- variable: "--font-geist-mono",
-})
-
-export const metadata: Metadata = {
- title: "Townhall",
- description: "Community chat. Nothing else.",
-}
-
-export default function RootLayout({
- children,
-}: Readonly<{
- children: React.ReactNode
-}>) {
- return (
-
-
- {children}
-
-
- )
-}
diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx
deleted file mode 100644
index 7d23791..0000000
--- a/apps/web/app/page.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function Home() {
- return (
-
-
Townhall
-
- )
-}
diff --git a/apps/web/index.html b/apps/web/index.html
new file mode 100644
index 0000000..10e83f5
--- /dev/null
+++ b/apps/web/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+ Townhall
+
+
+
+
+
+
+
+
diff --git a/apps/web/next.config.js b/apps/web/next.config.js
deleted file mode 100644
index 7ff7441..0000000
--- a/apps/web/next.config.js
+++ /dev/null
@@ -1,8 +0,0 @@
-const { resolve } = await import("node:path")
-const dotenv = await import("dotenv")
-dotenv.config({ path: resolve(import.meta.dirname, "../../.env") })
-
-/** @type {import('next').NextConfig} */
-const nextConfig = {}
-
-export default nextConfig
diff --git a/apps/web/package.json b/apps/web/package.json
index 4492862..6d58a8f 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -4,10 +4,10 @@
"type": "module",
"private": true,
"scripts": {
- "dev": "next dev --port 3000",
- "build": "next build",
- "start": "next start -p ${PORT:-3000}",
- "check-types": "next typegen && tsc --noEmit"
+ "dev": "vite --port 3000",
+ "build": "vite build",
+ "start": "vite preview --port 3000",
+ "check-types": "tsc --noEmit"
},
"dependencies": {
"@repo/api-client": "workspace:*",
@@ -16,11 +16,10 @@
"@repo/ui": "workspace:*",
"@tailwindcss/postcss": "^4.1.18",
"@tanstack/react-query": "^5.90.21",
+ "@tanstack/react-router": "^1.120.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
- "dotenv": "^17.2.4",
"lucide-react": "^0.563.0",
- "next": "16.1.6",
"next-themes": "^0.4.6",
"postcss": "^8.5.6",
"radix-ui": "^1.4.3",
@@ -32,8 +31,13 @@
},
"devDependencies": {
"@repo/typescript-config": "workspace:*",
+ "@tanstack/react-query-devtools": "^5.91.3",
+ "@tanstack/react-router-devtools": "^1.120.3",
+ "@tanstack/router-plugin": "^1.120.3",
"@types/react": "19.2.13",
"@types/react-dom": "19.2.3",
- "tw-animate-css": "^1.4.0"
+ "@vitejs/plugin-react": "^4.5.2",
+ "tw-animate-css": "^1.4.0",
+ "vite": "^6.3.5"
}
}
diff --git a/apps/web/pnpm-workspace.yaml b/apps/web/pnpm-workspace.yaml
deleted file mode 100644
index 581a9d5..0000000
--- a/apps/web/pnpm-workspace.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-ignoredBuiltDependencies:
- - sharp
- - unrs-resolver
diff --git a/apps/web/app/favicon.ico b/apps/web/public/favicon.ico
similarity index 100%
rename from apps/web/app/favicon.ico
rename to apps/web/public/favicon.ico
diff --git a/apps/web/app/fonts/GeistMonoVF.woff b/apps/web/src/assets/fonts/GeistMonoVF.woff
similarity index 100%
rename from apps/web/app/fonts/GeistMonoVF.woff
rename to apps/web/src/assets/fonts/GeistMonoVF.woff
diff --git a/apps/web/app/fonts/GeistVF.woff b/apps/web/src/assets/fonts/GeistVF.woff
similarity index 100%
rename from apps/web/app/fonts/GeistVF.woff
rename to apps/web/src/assets/fonts/GeistVF.woff
diff --git a/apps/web/lib/api-client.ts b/apps/web/src/lib/api-client.ts
similarity index 100%
rename from apps/web/lib/api-client.ts
rename to apps/web/src/lib/api-client.ts
diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx
new file mode 100644
index 0000000..6aead06
--- /dev/null
+++ b/apps/web/src/main.tsx
@@ -0,0 +1,44 @@
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
+import { ReactQueryDevtools } from "@tanstack/react-query-devtools"
+import { createRouter, RouterProvider } from "@tanstack/react-router"
+import { ThemeProvider } from "next-themes"
+import { StrictMode } from "react"
+import { createRoot } from "react-dom/client"
+import "@repo/ui/globals.css"
+import "./styles/fonts.css"
+import { routeTree } from "./routeTree.gen"
+
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: 60 * 1000,
+ },
+ },
+})
+
+const router = createRouter({ routeTree })
+
+declare module "@tanstack/react-router" {
+ interface Register {
+ router: typeof router
+ }
+}
+
+const rootElement = document.getElementById("root")
+if (!rootElement) throw new Error("Root element not found")
+
+createRoot(rootElement).render(
+
+
+
+
+
+
+
+
+)
diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx
new file mode 100644
index 0000000..2d0b90d
--- /dev/null
+++ b/apps/web/src/routes/__root.tsx
@@ -0,0 +1,21 @@
+import { createRootRoute, Outlet } from "@tanstack/react-router"
+import { lazy, Suspense } from "react"
+
+const TanStackRouterDevtools = import.meta.env.DEV
+ ? lazy(() =>
+ import("@tanstack/react-router-devtools").then((mod) => ({
+ default: mod.TanStackRouterDevtools,
+ }))
+ )
+ : () => null
+
+export const Route = createRootRoute({
+ component: () => (
+ <>
+
+
+
+
+ >
+ ),
+})
diff --git a/apps/web/src/routes/_authenticated.tsx b/apps/web/src/routes/_authenticated.tsx
new file mode 100644
index 0000000..4d076ba
--- /dev/null
+++ b/apps/web/src/routes/_authenticated.tsx
@@ -0,0 +1,32 @@
+import { authClient } from "@repo/auth/client"
+import { createFileRoute, Outlet, useNavigate } from "@tanstack/react-router"
+import { useEffect } from "react"
+
+export const Route = createFileRoute("/_authenticated")({
+ component: AuthenticatedLayout,
+})
+
+function AuthenticatedLayout() {
+ const navigate = useNavigate()
+ const { data: session, isPending } = authClient.useSession()
+
+ useEffect(() => {
+ if (!isPending && !session) {
+ navigate({ to: "/login" })
+ }
+ }, [isPending, session, navigate])
+
+ if (isPending) {
+ return (
+
+ )
+ }
+
+ if (!session) {
+ return null
+ }
+
+ return
+}
diff --git a/apps/web/src/routes/_authenticated/index.tsx b/apps/web/src/routes/_authenticated/index.tsx
new file mode 100644
index 0000000..af70582
--- /dev/null
+++ b/apps/web/src/routes/_authenticated/index.tsx
@@ -0,0 +1,25 @@
+import { authClient } from "@repo/auth/client"
+import { Button } from "@repo/ui/components/button"
+import { createFileRoute } from "@tanstack/react-router"
+
+export const Route = createFileRoute("/_authenticated/")({
+ component: Home,
+})
+
+function Home() {
+ const { data: session } = authClient.useSession()
+
+ return (
+
+
Townhall
+ {session && (
+
+ Welcome, {session.user.name}
+
+ )}
+
+
+ )
+}
diff --git a/apps/web/src/routes/login.tsx b/apps/web/src/routes/login.tsx
new file mode 100644
index 0000000..743062c
--- /dev/null
+++ b/apps/web/src/routes/login.tsx
@@ -0,0 +1,94 @@
+import { authClient } from "@repo/auth/client"
+import { Button } from "@repo/ui/components/button"
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@repo/ui/components/card"
+import { Input } from "@repo/ui/components/input"
+import { Label } from "@repo/ui/components/label"
+import { useMutation } from "@tanstack/react-query"
+import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"
+import { type FormEvent, useState } from "react"
+
+export const Route = createFileRoute("/login")({
+ component: LoginPage,
+})
+
+function LoginPage() {
+ const navigate = useNavigate()
+ const [email, setEmail] = useState("")
+ const [password, setPassword] = useState("")
+
+ const {
+ mutate: signIn,
+ isPending,
+ error,
+ } = useMutation({
+ mutationFn: async () => {
+ const { error } = await authClient.signIn.email({ email, password })
+ if (error) throw new Error(error.message ?? "Failed to sign in")
+ },
+ onSuccess: () => navigate({ to: "/" }),
+ })
+
+ return (
+
+
+
+ Login
+
+ Enter your credentials to access your account
+
+
+
+
+
+ )
+}
diff --git a/apps/web/src/routes/signup.tsx b/apps/web/src/routes/signup.tsx
new file mode 100644
index 0000000..bc3daaa
--- /dev/null
+++ b/apps/web/src/routes/signup.tsx
@@ -0,0 +1,123 @@
+import { authClient } from "@repo/auth/client"
+import { Button } from "@repo/ui/components/button"
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@repo/ui/components/card"
+import { Input } from "@repo/ui/components/input"
+import { Label } from "@repo/ui/components/label"
+import { useMutation } from "@tanstack/react-query"
+import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"
+import { type FormEvent, useState } from "react"
+
+export const Route = createFileRoute("/signup")({
+ component: SignUpPage,
+})
+
+function SignUpPage() {
+ const navigate = useNavigate()
+ const [name, setName] = useState("")
+ const [username, setUsername] = useState("")
+ const [email, setEmail] = useState("")
+ const [password, setPassword] = useState("")
+
+ const {
+ mutate: signUp,
+ isPending,
+ error,
+ } = useMutation({
+ mutationFn: async () => {
+ const { error } = await authClient.signUp.email({
+ name,
+ username,
+ email,
+ password,
+ })
+ if (error) throw new Error(error.message ?? "Failed to create account")
+ },
+ onSuccess: () => navigate({ to: "/" }),
+ })
+
+ return (
+
+
+
+ Sign up
+ Create your Townhall account
+
+
+
+
+ )
+}
diff --git a/apps/web/src/styles/fonts.css b/apps/web/src/styles/fonts.css
new file mode 100644
index 0000000..ea4db11
--- /dev/null
+++ b/apps/web/src/styles/fonts.css
@@ -0,0 +1,24 @@
+@font-face {
+ font-family: "Geist Sans";
+ src: url("../assets/fonts/GeistVF.woff") format("woff");
+ font-weight: 100 900;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "Geist Mono";
+ src: url("../assets/fonts/GeistMonoVF.woff") format("woff");
+ font-weight: 100 900;
+ font-display: swap;
+}
+
+:root {
+ --font-geist-sans: "Geist Sans", ui-sans-serif, system-ui, sans-serif;
+ --font-geist-mono: "Geist Mono", ui-monospace, monospace;
+}
+
+body {
+ font-family: var(--font-geist-sans);
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
diff --git a/apps/web/src/vite-env.d.ts b/apps/web/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/apps/web/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json
index 84e20cd..9f6b5f4 100644
--- a/apps/web/tsconfig.json
+++ b/apps/web/tsconfig.json
@@ -1,21 +1,10 @@
{
- "extends": "@repo/typescript-config/nextjs.json",
+ "extends": "@repo/typescript-config/vite-react.json",
"compilerOptions": {
- "plugins": [
- {
- "name": "next"
- }
- ],
"paths": {
- "@/*": ["./*"]
+ "@/*": ["./src/*"]
}
},
- "include": [
- "**/*.ts",
- "**/*.tsx",
- "next-env.d.ts",
- "next.config.js",
- ".next/types/**/*.ts"
- ],
- "exclude": ["node_modules"]
+ "include": ["src", "vite.config.ts"],
+ "exclude": ["node_modules", "dist"]
}
diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts
new file mode 100644
index 0000000..604aa9e
--- /dev/null
+++ b/apps/web/vite.config.ts
@@ -0,0 +1,29 @@
+import { resolve } from "node:path"
+import { tanstackRouter } from "@tanstack/router-plugin/vite"
+import react from "@vitejs/plugin-react"
+import { defineConfig, loadEnv } from "vite"
+
+export default defineConfig(({ mode }) => {
+ const monorepoRoot = resolve(__dirname, "../..")
+ const env = {
+ ...loadEnv(mode, monorepoRoot, "NEXT_PUBLIC_"),
+ ...loadEnv(mode, __dirname, "NEXT_PUBLIC_"),
+ }
+
+ return {
+ plugins: [tanstackRouter(), react()],
+ resolve: {
+ alias: {
+ "@": resolve(__dirname, "./src"),
+ },
+ },
+ define: {
+ "process.env.NEXT_PUBLIC_API_URL": JSON.stringify(
+ env.NEXT_PUBLIC_API_URL
+ ),
+ "process.env.NEXT_PUBLIC_MAX_FILE_UPLOAD_SIZE": JSON.stringify(
+ env.NEXT_PUBLIC_MAX_FILE_UPLOAD_SIZE
+ ),
+ },
+ }
+})
diff --git a/biome.json b/biome.json
index 6635208..e59811a 100644
--- a/biome.json
+++ b/biome.json
@@ -7,7 +7,8 @@
"defaultBranch": "main"
},
"files": {
- "ignoreUnknown": true
+ "ignoreUnknown": true,
+ "includes": ["**", "!**/routeTree.gen.ts"]
},
"formatter": {
"enabled": true,
diff --git a/packages/auth/src/lib/auth-client.ts b/packages/auth/src/lib/auth-client.ts
index 5d0c03c..f8f74a5 100644
--- a/packages/auth/src/lib/auth-client.ts
+++ b/packages/auth/src/lib/auth-client.ts
@@ -1,3 +1,4 @@
+import { env } from "@repo/env/client"
import {
adminClient,
inferAdditionalFields,
@@ -10,6 +11,7 @@ import { createAuthClient } from "better-auth/react"
import type { auth } from "./auth.js"
export const authClient = createAuthClient({
+ baseURL: env.NEXT_PUBLIC_API_URL,
plugins: [
organizationClient({
schema: inferOrgAdditionalFields(),
diff --git a/packages/auth/src/lib/auth.ts b/packages/auth/src/lib/auth.ts
index 9d97d3f..019ed99 100644
--- a/packages/auth/src/lib/auth.ts
+++ b/packages/auth/src/lib/auth.ts
@@ -1,12 +1,15 @@
-import { db } from "@repo/db"
+import { db, schema } from "@repo/db"
import { env } from "@repo/env/server"
import { drizzleAdapter } from "better-auth/adapters/drizzle"
import { betterAuth } from "better-auth/minimal"
import { admin, organization, twoFactor, username } from "better-auth/plugins"
export const auth = betterAuth({
- database: drizzleAdapter(db, { provider: "pg" }),
+ baseURL: env.NEXT_PUBLIC_API_URL,
+ database: drizzleAdapter(db, { provider: "pg", schema }),
secret: env.BETTER_AUTH_SECRET,
+ trustedOrigins:
+ env.NODE_ENV === "development" ? ["http://localhost:3000"] : [],
emailAndPassword: {
enabled: true,
},
@@ -21,6 +24,20 @@ export const auth = betterAuth({
schema: {
organization: {
modelName: "guild",
+ additionalFields: {
+ ownerId: {
+ type: "string",
+ fieldName: "ownerId",
+ references: {
+ field: "id",
+ table: "user",
+ model: "user",
+ onDelete: "restrict",
+ },
+ required: true,
+ returned: true,
+ },
+ },
},
member: {
modelName: "guildMember",
@@ -38,6 +55,15 @@ export const auth = betterAuth({
activeOrganizationId: "activeGuildId",
},
},
+ organizationRole: {
+ modelName: "guildRole",
+ fields: {
+ organizationId: "guildId",
+ },
+ },
+ },
+ dynamicAccessControl: {
+ enabled: true,
},
}),
admin(),
diff --git a/packages/auth/tsconfig.json b/packages/auth/tsconfig.json
index 8e03c53..cf25c1c 100644
--- a/packages/auth/tsconfig.json
+++ b/packages/auth/tsconfig.json
@@ -1,6 +1,8 @@
{
"extends": "@repo/typescript-config/base.json",
"compilerOptions": {
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
"declaration": false,
"declarationMap": false
},
diff --git a/packages/db/src/generated-schema.ts b/packages/db/src/generated-schema.ts
index e69de29..ea67dfe 100644
--- a/packages/db/src/generated-schema.ts
+++ b/packages/db/src/generated-schema.ts
@@ -0,0 +1,252 @@
+import { relations } from "drizzle-orm"
+import {
+ boolean,
+ index,
+ pgTable,
+ text,
+ timestamp,
+ uniqueIndex,
+} from "drizzle-orm/pg-core"
+
+export const user = pgTable("user", {
+ id: text("id").primaryKey(),
+ name: text("name").notNull(),
+ email: text("email").notNull().unique(),
+ emailVerified: boolean("email_verified").default(false).notNull(),
+ image: text("image"),
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at")
+ .defaultNow()
+ .$onUpdate(() => /* @__PURE__ */ new Date())
+ .notNull(),
+ role: text("role"),
+ banned: boolean("banned").default(false),
+ banReason: text("ban_reason"),
+ banExpires: timestamp("ban_expires"),
+ username: text("username").unique(),
+ displayUsername: text("display_username"),
+ twoFactorEnabled: boolean("two_factor_enabled").default(false),
+})
+
+export const session = pgTable(
+ "session",
+ {
+ id: text("id").primaryKey(),
+ expiresAt: timestamp("expires_at").notNull(),
+ token: text("token").notNull().unique(),
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at")
+ .$onUpdate(() => /* @__PURE__ */ new Date())
+ .notNull(),
+ ipAddress: text("ip_address"),
+ userAgent: text("user_agent"),
+ userId: text("user_id")
+ .notNull()
+ .references(() => user.id, { onDelete: "cascade" }),
+ activeGuildId: text("active_guild_id"),
+ impersonatedBy: text("impersonated_by"),
+ },
+ (table) => [index("session_userId_idx").on(table.userId)]
+)
+
+export const account = pgTable(
+ "account",
+ {
+ id: text("id").primaryKey(),
+ accountId: text("account_id").notNull(),
+ providerId: text("provider_id").notNull(),
+ userId: text("user_id")
+ .notNull()
+ .references(() => user.id, { onDelete: "cascade" }),
+ accessToken: text("access_token"),
+ refreshToken: text("refresh_token"),
+ idToken: text("id_token"),
+ accessTokenExpiresAt: timestamp("access_token_expires_at"),
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
+ scope: text("scope"),
+ password: text("password"),
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at")
+ .$onUpdate(() => /* @__PURE__ */ new Date())
+ .notNull(),
+ },
+ (table) => [index("account_userId_idx").on(table.userId)]
+)
+
+export const verification = pgTable(
+ "verification",
+ {
+ id: text("id").primaryKey(),
+ identifier: text("identifier").notNull(),
+ value: text("value").notNull(),
+ expiresAt: timestamp("expires_at").notNull(),
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at")
+ .defaultNow()
+ .$onUpdate(() => /* @__PURE__ */ new Date())
+ .notNull(),
+ },
+ (table) => [index("verification_identifier_idx").on(table.identifier)]
+)
+
+export const guild = pgTable(
+ "guild",
+ {
+ id: text("id").primaryKey(),
+ name: text("name").notNull(),
+ slug: text("slug").notNull().unique(),
+ logo: text("logo"),
+ createdAt: timestamp("created_at").notNull(),
+ metadata: text("metadata"),
+ ownerId: text("owner_id")
+ .notNull()
+ .references(() => user.id, { onDelete: "restrict" }),
+ },
+ (table) => [uniqueIndex("guild_slug_uidx").on(table.slug)]
+)
+
+export const guildRole = pgTable(
+ "guild_role",
+ {
+ id: text("id").primaryKey(),
+ guildId: text("guild_id")
+ .notNull()
+ .references(() => guild.id, { onDelete: "cascade" }),
+ role: text("role").notNull(),
+ permission: text("permission").notNull(),
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at").$onUpdate(
+ () => /* @__PURE__ */ new Date()
+ ),
+ },
+ (table) => [
+ index("guildRole_guildId_idx").on(table.guildId),
+ index("guildRole_role_idx").on(table.role),
+ ]
+)
+
+export const guildMember = pgTable(
+ "guild_member",
+ {
+ id: text("id").primaryKey(),
+ guildId: text("guild_id")
+ .notNull()
+ .references(() => guild.id, { onDelete: "cascade" }),
+ userId: text("user_id")
+ .notNull()
+ .references(() => user.id, { onDelete: "cascade" }),
+ role: text("role").default("member").notNull(),
+ createdAt: timestamp("created_at").notNull(),
+ },
+ (table) => [
+ index("guildMember_guildId_idx").on(table.guildId),
+ index("guildMember_userId_idx").on(table.userId),
+ ]
+)
+
+export const invitation = pgTable(
+ "invitation",
+ {
+ id: text("id").primaryKey(),
+ guildId: text("guild_id")
+ .notNull()
+ .references(() => guild.id, { onDelete: "cascade" }),
+ email: text("email").notNull(),
+ role: text("role"),
+ status: text("status").default("pending").notNull(),
+ expiresAt: timestamp("expires_at").notNull(),
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ inviterId: text("inviter_id")
+ .notNull()
+ .references(() => user.id, { onDelete: "cascade" }),
+ },
+ (table) => [
+ index("invitation_guildId_idx").on(table.guildId),
+ index("invitation_email_idx").on(table.email),
+ ]
+)
+
+export const twoFactor = pgTable(
+ "two_factor",
+ {
+ id: text("id").primaryKey(),
+ secret: text("secret").notNull(),
+ backupCodes: text("backup_codes").notNull(),
+ userId: text("user_id")
+ .notNull()
+ .references(() => user.id, { onDelete: "cascade" }),
+ },
+ (table) => [
+ index("twoFactor_secret_idx").on(table.secret),
+ index("twoFactor_userId_idx").on(table.userId),
+ ]
+)
+
+export const userRelations = relations(user, ({ many }) => ({
+ sessions: many(session),
+ accounts: many(account),
+ guilds: many(guild),
+ guildMembers: many(guildMember),
+ invitations: many(invitation),
+ twoFactors: many(twoFactor),
+}))
+
+export const sessionRelations = relations(session, ({ one }) => ({
+ user: one(user, {
+ fields: [session.userId],
+ references: [user.id],
+ }),
+}))
+
+export const accountRelations = relations(account, ({ one }) => ({
+ user: one(user, {
+ fields: [account.userId],
+ references: [user.id],
+ }),
+}))
+
+export const guildRelations = relations(guild, ({ one, many }) => ({
+ user: one(user, {
+ fields: [guild.ownerId],
+ references: [user.id],
+ }),
+ guildRoles: many(guildRole),
+ guildMembers: many(guildMember),
+ invitations: many(invitation),
+}))
+
+export const guildRoleRelations = relations(guildRole, ({ one }) => ({
+ guild: one(guild, {
+ fields: [guildRole.guildId],
+ references: [guild.id],
+ }),
+}))
+
+export const guildMemberRelations = relations(guildMember, ({ one }) => ({
+ guild: one(guild, {
+ fields: [guildMember.guildId],
+ references: [guild.id],
+ }),
+ user: one(user, {
+ fields: [guildMember.userId],
+ references: [user.id],
+ }),
+}))
+
+export const invitationRelations = relations(invitation, ({ one }) => ({
+ guild: one(guild, {
+ fields: [invitation.guildId],
+ references: [guild.id],
+ }),
+ user: one(user, {
+ fields: [invitation.inviterId],
+ references: [user.id],
+ }),
+}))
+
+export const twoFactorRelations = relations(twoFactor, ({ one }) => ({
+ user: one(user, {
+ fields: [twoFactor.userId],
+ references: [user.id],
+ }),
+}))
diff --git a/packages/db/src/schemas/accounts.ts b/packages/db/src/schemas/accounts.ts
index c4db6d3..f731645 100644
--- a/packages/db/src/schemas/accounts.ts
+++ b/packages/db/src/schemas/accounts.ts
@@ -8,7 +8,7 @@ export const account = pgTable(
id: uuid("id").defaultRandom().primaryKey(),
accountId: text("account_id").notNull(),
providerId: text("provider_id").notNull(),
- userId: text("user_id")
+ userId: uuid("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
accessToken: text("access_token"),
diff --git a/packages/db/src/schemas/channels.ts b/packages/db/src/schemas/channels.ts
index 6ccfbb6..a32ae44 100644
--- a/packages/db/src/schemas/channels.ts
+++ b/packages/db/src/schemas/channels.ts
@@ -38,12 +38,12 @@ export const channel = pgTable(
type: channelTypeEnum("type").notNull().default("text"),
// null for DMs/group DMs
- guildId: text("guild_id").references(() => guild.id, {
+ guildId: uuid("guild_id").references(() => guild.id, {
onDelete: "cascade",
}),
// points to a category channel
- parentId: text("parent_id").references((): AnyPgColumn => channel.id, {
+ parentId: uuid("parent_id").references((): AnyPgColumn => channel.id, {
onDelete: "set null",
}),
@@ -51,7 +51,7 @@ export const channel = pgTable(
position: integer("position").default(0).notNull(),
// group DM owner — null for guild channels (use roles/permissions instead)
- ownerId: text("owner_id").references(() => user.id, {
+ ownerId: uuid("owner_id").references(() => user.id, {
onDelete: "set null",
}),
@@ -91,10 +91,10 @@ export const channelMember = pgTable(
"channel_member",
{
id: uuid("id").defaultRandom().primaryKey(),
- channelId: text("channel_id")
+ channelId: uuid("channel_id")
.notNull()
.references(() => channel.id, { onDelete: "cascade" }),
- userId: text("user_id")
+ userId: uuid("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
createdAt: timestamp("created_at").defaultNow().notNull(),
diff --git a/packages/db/src/schemas/guild-members.ts b/packages/db/src/schemas/guild-members.ts
index 69fb9cf..e8805f3 100644
--- a/packages/db/src/schemas/guild-members.ts
+++ b/packages/db/src/schemas/guild-members.ts
@@ -7,10 +7,10 @@ export const guildMember = pgTable(
"guild_member",
{
id: uuid("id").defaultRandom().primaryKey(),
- guildId: text("guild_id")
+ guildId: uuid("guild_id")
.notNull()
.references(() => guild.id, { onDelete: "cascade" }),
- userId: text("user_id")
+ userId: uuid("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
role: text("role").default("member").notNull(),
diff --git a/packages/db/src/schemas/guild-roles.ts b/packages/db/src/schemas/guild-roles.ts
new file mode 100644
index 0000000..0bd37c0
--- /dev/null
+++ b/packages/db/src/schemas/guild-roles.ts
@@ -0,0 +1,30 @@
+import { relations } from "drizzle-orm"
+import { index, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
+import { guild } from "./guilds"
+
+export const guildRole = pgTable(
+ "guild_role",
+ {
+ id: uuid("id").primaryKey().defaultRandom(),
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at").$onUpdate(
+ () => /* @__PURE__ */ new Date()
+ ),
+ guildId: uuid("guild_id")
+ .notNull()
+ .references(() => guild.id, { onDelete: "cascade" }),
+ role: text("role").notNull(),
+ permission: text("permission").notNull(),
+ },
+ (table) => [
+ index("guildRole_guildId_idx").on(table.guildId),
+ index("guildRole_role_idx").on(table.role),
+ ]
+)
+
+export const guildRoleRelations = relations(guildRole, ({ one }) => ({
+ guild: one(guild, {
+ fields: [guildRole.guildId],
+ references: [guild.id],
+ }),
+}))
diff --git a/packages/db/src/schemas/guilds.ts b/packages/db/src/schemas/guilds.ts
index a7a85e2..8572ac8 100644
--- a/packages/db/src/schemas/guilds.ts
+++ b/packages/db/src/schemas/guilds.ts
@@ -7,7 +7,9 @@ import {
uuid,
} from "drizzle-orm/pg-core"
import { guildMember } from "./guild-members"
+import { guildRole } from "./guild-roles"
import { invitation } from "./invitations"
+import { user } from "./users"
export const guild = pgTable(
"guild",
@@ -17,12 +19,20 @@ export const guild = pgTable(
slug: text("slug").notNull().unique(),
logo: text("logo"),
createdAt: timestamp("created_at").notNull(),
+ ownerId: uuid("owner_id") // this is the source of truth for the owner of the guild, the guildMember who owns this guild will also have role === "owner" so we will need to keep these in sync
+ .notNull()
+ .references(() => user.id, { onDelete: "restrict" }), // don't delete guild if owner deletes account
metadata: text("metadata"),
},
(table) => [uniqueIndex("guild_slug_uidx").on(table.slug)]
)
-export const guildRelations = relations(guild, ({ many }) => ({
+export const guildRelations = relations(guild, ({ one, many }) => ({
+ user: one(user, {
+ fields: [guild.ownerId],
+ references: [user.id],
+ }),
+ guildRoles: many(guildRole),
guildMembers: many(guildMember),
invitations: many(invitation),
}))
diff --git a/packages/db/src/schemas/index.ts b/packages/db/src/schemas/index.ts
index 7965cd2..eaf99bc 100644
--- a/packages/db/src/schemas/index.ts
+++ b/packages/db/src/schemas/index.ts
@@ -1,5 +1,6 @@
export * from "./accounts"
export * from "./guild-members"
+export * from "./guild-roles"
export * from "./guilds"
export * from "./invitations"
export * from "./sessions"
diff --git a/packages/db/src/schemas/invitations.ts b/packages/db/src/schemas/invitations.ts
index d806fe9..b0b92c8 100644
--- a/packages/db/src/schemas/invitations.ts
+++ b/packages/db/src/schemas/invitations.ts
@@ -7,7 +7,7 @@ export const invitation = pgTable(
"invitation",
{
id: uuid("id").defaultRandom().primaryKey(),
- guildId: text("guild_id")
+ guildId: uuid("guild_id")
.notNull()
.references(() => guild.id, { onDelete: "cascade" }),
email: text("email").notNull(),
@@ -15,7 +15,7 @@ export const invitation = pgTable(
status: text("status").default("pending").notNull(),
expiresAt: timestamp("expires_at").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
- inviterId: text("inviter_id")
+ inviterId: uuid("inviter_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
},
diff --git a/packages/db/src/schemas/messages.ts b/packages/db/src/schemas/messages.ts
index 5af0f8a..820f7d0 100644
--- a/packages/db/src/schemas/messages.ts
+++ b/packages/db/src/schemas/messages.ts
@@ -26,10 +26,10 @@ export const message = pgTable(
"message",
{
id: uuid("id").defaultRandom().primaryKey(),
- channelId: text("channel_id")
+ channelId: uuid("channel_id")
.notNull()
.references(() => channel.id, { onDelete: "cascade" }),
- authorId: text("author_id") // all messages must have an author
+ authorId: uuid("author_id") // all messages must have an author
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
@@ -37,7 +37,7 @@ export const message = pgTable(
type: messageTypeEnum("type").notNull().default("default"),
// for replies — points to the message being replied to
- referencedMessageId: text("referenced_message_id").references(
+ referencedMessageId: uuid("referenced_message_id").references(
(): AnyPgColumn => message.id,
{ onDelete: "set null" }
),
diff --git a/packages/db/src/schemas/sessions.ts b/packages/db/src/schemas/sessions.ts
index 1b2da6a..7958f7a 100644
--- a/packages/db/src/schemas/sessions.ts
+++ b/packages/db/src/schemas/sessions.ts
@@ -14,10 +14,10 @@ export const session = pgTable(
.notNull(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
- userId: text("user_id")
+ userId: uuid("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
- activeGuildId: text("active_guild_id"),
+ activeGuildId: uuid("active_guild_id"),
impersonatedBy: text("impersonated_by"),
},
(table) => [index("session_userId_idx").on(table.userId)]
diff --git a/packages/db/src/schemas/two-factors.ts b/packages/db/src/schemas/two-factors.ts
index ab22d59..11494f9 100644
--- a/packages/db/src/schemas/two-factors.ts
+++ b/packages/db/src/schemas/two-factors.ts
@@ -8,7 +8,7 @@ export const twoFactor = pgTable(
id: uuid("id").defaultRandom().primaryKey(),
secret: text("secret").notNull(),
backupCodes: text("backup_codes").notNull(),
- userId: text("user_id")
+ userId: uuid("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
},
diff --git a/packages/db/src/schemas/users.ts b/packages/db/src/schemas/users.ts
index bc89528..3a0b66a 100644
--- a/packages/db/src/schemas/users.ts
+++ b/packages/db/src/schemas/users.ts
@@ -2,6 +2,7 @@ import { relations } from "drizzle-orm"
import { boolean, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
import { account } from "./accounts"
import { guildMember } from "./guild-members"
+import { guild } from "./guilds"
import { invitation } from "./invitations"
import { session } from "./sessions"
import { twoFactor } from "./two-factors"
@@ -29,6 +30,7 @@ export const user = pgTable("user", {
export const userRelations = relations(user, ({ many }) => ({
sessions: many(session),
accounts: many(account),
+ guilds: many(guild), // can be owners of many guilds
guildMembers: many(guildMember),
invitations: many(invitation),
twoFactors: many(twoFactor),
diff --git a/packages/env/src/client.ts b/packages/env/src/client.ts
index 7cfddf9..fec7a20 100644
--- a/packages/env/src/client.ts
+++ b/packages/env/src/client.ts
@@ -3,12 +3,20 @@ import { z } from "zod"
/** 20 MB default — keep in sync with server.ts */
const DEFAULT_MAX_FILE_UPLOAD_SIZE = 20 * 1024 * 1024
-const addProtocol = (url: string) =>
- url.startsWith("http://") || url.startsWith("https://")
- ? url
- : `https://${url}`
+/** Adds a protocol to a URL if missing. Defaults to http:// for localhost/loopback, https:// otherwise. */
+const addProtocol = (url: string) => {
+ const trimmed = url.trim()
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://"))
+ return trimmed
+ const isLocal =
+ trimmed.startsWith("localhost") || trimmed.startsWith("127.0.0.1")
+ return isLocal ? `http://${trimmed}` : `https://${trimmed}`
+}
const clientSchema = z.object({
+ NODE_ENV: z
+ .enum(["development", "staging", "production", "test"])
+ .default("production"),
NEXT_PUBLIC_API_URL: z.string().min(1).transform(addProtocol),
NEXT_PUBLIC_MAX_FILE_UPLOAD_SIZE: z.coerce
.number()
@@ -16,6 +24,7 @@ const clientSchema = z.object({
})
export const env = clientSchema.parse({
+ NODE_ENV: process.env.NODE_ENV,
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
NEXT_PUBLIC_MAX_FILE_UPLOAD_SIZE:
process.env.NEXT_PUBLIC_MAX_FILE_UPLOAD_SIZE,
diff --git a/packages/env/src/server.ts b/packages/env/src/server.ts
index c3d633e..1670735 100644
--- a/packages/env/src/server.ts
+++ b/packages/env/src/server.ts
@@ -8,15 +8,29 @@ if (process.env.NODE_ENV !== "production") {
dotenvConfig({ path: resolve(process.cwd(), "../../.env") })
}
+/** Adds a protocol to a URL if missing. Defaults to http:// for localhost/loopback, https:// otherwise. */
+const addProtocol = (url: string) => {
+ const trimmed = url.trim()
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://"))
+ return trimmed
+ const isLocal =
+ trimmed.startsWith("localhost") || trimmed.startsWith("127.0.0.1")
+ return isLocal ? `http://${trimmed}` : `https://${trimmed}`
+}
+
/** 20 MB default — keep in sync with client.ts */
const DEFAULT_MAX_FILE_UPLOAD_SIZE = 20 * 1024 * 1024
const serverSchema = z.object({
+ NODE_ENV: z
+ .enum(["development", "staging", "production", "test"])
+ .default("production"),
DATABASE_URL: z.string().url(),
PORT: z.coerce.number().default(8080),
BETTER_AUTH_SECRET: z.string().min(1),
SELF_HOSTED: z.coerce.boolean().default(true),
MAX_FILE_UPLOAD_SIZE: z.coerce.number().default(DEFAULT_MAX_FILE_UPLOAD_SIZE),
+ NEXT_PUBLIC_API_URL: z.string().min(1).transform(addProtocol),
})
export const env = serverSchema.parse(process.env)
diff --git a/packages/typescript-config/vite-react.json b/packages/typescript-config/vite-react.json
new file mode 100644
index 0000000..197b5b4
--- /dev/null
+++ b/packages/typescript-config/vite-react.json
@@ -0,0 +1,10 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "extends": "./base.json",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "noEmit": true
+ }
+}
diff --git a/packages/ui/src/components/label.tsx b/packages/ui/src/components/label.tsx
new file mode 100644
index 0000000..2b7831f
--- /dev/null
+++ b/packages/ui/src/components/label.tsx
@@ -0,0 +1,23 @@
+"use client"
+
+import { cn } from "@repo/ui/lib/utils"
+import { Label as LabelPrimitive } from "radix-ui"
+import type * as React from "react"
+
+function Label({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Label }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6e1159e..062f7e4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -32,6 +32,9 @@ importers:
'@hono/zod-openapi':
specifier: ^0.19.2
version: 0.19.10(hono@4.11.9)(zod@3.25.76)
+ '@repo/auth':
+ specifier: workspace:*
+ version: link:../../packages/auth
'@repo/db':
specifier: workspace:*
version: link:../../packages/db
@@ -87,21 +90,18 @@ importers:
'@tanstack/react-query':
specifier: ^5.90.21
version: 5.90.21(react@19.2.4)
+ '@tanstack/react-router':
+ specifier: ^1.120.3
+ version: 1.159.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
clsx:
specifier: ^2.1.1
version: 2.1.1
- dotenv:
- specifier: ^17.2.4
- version: 17.2.4
lucide-react:
specifier: ^0.563.0
version: 0.563.0(react@19.2.4)
- next:
- specifier: 16.1.6
- version: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -130,15 +130,30 @@ importers:
'@repo/typescript-config':
specifier: workspace:*
version: link:../../packages/typescript-config
+ '@tanstack/react-query-devtools':
+ specifier: ^5.91.3
+ version: 5.91.3(@tanstack/react-query@5.90.21(react@19.2.4))(react@19.2.4)
+ '@tanstack/react-router-devtools':
+ specifier: ^1.120.3
+ version: 1.159.10(@tanstack/react-router@1.159.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.159.9)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@tanstack/router-plugin':
+ specifier: ^1.120.3
+ version: 1.159.11(@tanstack/react-router@1.159.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@6.4.1(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))
'@types/react':
specifier: 19.2.13
version: 19.2.13
'@types/react-dom':
specifier: 19.2.3
version: 19.2.3(@types/react@19.2.13)
+ '@vitejs/plugin-react':
+ specifier: ^4.5.2
+ version: 4.7.0(vite@6.4.1(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))
tw-animate-css:
specifier: ^1.4.0
version: 1.4.0
+ vite:
+ specifier: ^6.3.5
+ version: 6.4.1(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
apps/www:
dependencies:
@@ -442,6 +457,18 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
+ '@babel/plugin-transform-react-jsx-self@7.27.1':
+ resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1':
+ resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
'@babel/plugin-transform-typescript@7.28.6':
resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==}
engines: {node: '>=6.9.0'}
@@ -2042,6 +2069,9 @@ packages:
'@radix-ui/rect@1.1.1':
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
+ '@rolldown/pluginutils@1.0.0-beta.27':
+ resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
+
'@rollup/rollup-android-arm-eabi@4.57.1':
resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==}
cpu: [arm]
@@ -2271,17 +2301,117 @@ packages:
'@tailwindcss/postcss@4.1.18':
resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==}
+ '@tanstack/history@1.154.14':
+ resolution: {integrity: sha512-xyIfof8eHBuub1CkBnbKNKQXeRZC4dClhmzePHVOEel4G7lk/dW+TQ16da7CFdeNLv6u6Owf5VoBQxoo6DFTSA==}
+ engines: {node: '>=12'}
+
'@tanstack/query-core@5.90.20':
resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==}
+ '@tanstack/query-devtools@5.93.0':
+ resolution: {integrity: sha512-+kpsx1NQnOFTZsw6HAFCW3HkKg0+2cepGtAWXjiiSOJJ1CtQpt72EE2nyZb+AjAbLRPoeRmPJ8MtQd8r8gsPdg==}
+
+ '@tanstack/react-query-devtools@5.91.3':
+ resolution: {integrity: sha512-nlahjMtd/J1h7IzOOfqeyDh5LNfG0eULwlltPEonYy0QL+nqrBB+nyzJfULV+moL7sZyxc2sHdNJki+vLA9BSA==}
+ peerDependencies:
+ '@tanstack/react-query': ^5.90.20
+ react: ^18 || ^19
+
'@tanstack/react-query@5.90.21':
resolution: {integrity: sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==}
peerDependencies:
react: ^18 || ^19
+ '@tanstack/react-router-devtools@1.159.10':
+ resolution: {integrity: sha512-dfaXh7WBz1HJ639oMix5hJUJWCxrpcINPVXiN/3CBPYuGB2wYsBG2Iw61yufp+KkuFatAy95VTTnyeqGOq8ysw==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ '@tanstack/react-router': ^1.159.10
+ '@tanstack/router-core': ^1.159.9
+ react: '>=18.0.0 || >=19.0.0'
+ react-dom: '>=18.0.0 || >=19.0.0'
+ peerDependenciesMeta:
+ '@tanstack/router-core':
+ optional: true
+
+ '@tanstack/react-router@1.159.10':
+ resolution: {integrity: sha512-PQO6hpnqNALmotXasfCafVBWWKpxChmYbXRjwPZQQq8au7m71z4WtAHsmUA2v/uqqhsvE9ySyWVx/Ece/Uq2ZQ==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ react: '>=18.0.0 || >=19.0.0'
+ react-dom: '>=18.0.0 || >=19.0.0'
+
+ '@tanstack/react-store@0.8.0':
+ resolution: {integrity: sha512-1vG9beLIuB7q69skxK9r5xiLN3ztzIPfSQSs0GfeqWGO2tGIyInZx0x1COhpx97RKaONSoAb8C3dxacWksm1ow==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ '@tanstack/router-core@1.159.9':
+ resolution: {integrity: sha512-A9B8gvklvMCjSAFG8nDAhfmROI8kjcij8wzznQaw4RfGIOrYXyNe5fCAcbHXGpgNeTE2JnK75b6AjidDPQfrmw==}
+ engines: {node: '>=12'}
+
+ '@tanstack/router-devtools-core@1.159.9':
+ resolution: {integrity: sha512-2b1zmN12qOhuxAYq5EEtecDmj1ekA8i7yKKDXc2WYCwc6W2sqz+JMoKDwGzAIrC8rHpe4n0+eU3r1re5VnIPcg==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ '@tanstack/router-core': ^1.159.9
+ csstype: ^3.0.10
+ peerDependenciesMeta:
+ csstype:
+ optional: true
+
+ '@tanstack/router-generator@1.159.9':
+ resolution: {integrity: sha512-WDn17uYP/Mk//7OP5ZnlYK228ezQ/N+pVA8BrwoF69g3Scq5CkfZUD633UI1+oXIl8Fb1pCt4CU0LkN7niMTmQ==}
+ engines: {node: '>=12'}
+
+ '@tanstack/router-plugin@1.159.11':
+ resolution: {integrity: sha512-QrnwUX9XtfOqiNsD/AYmqTvvezuUwv4W7ewWwUgSTe0CEkuyjEa8aiZMLrofB613lRmoHSmjT6ciaV3z2vHdWw==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ '@rsbuild/core': '>=1.0.2'
+ '@tanstack/react-router': ^1.159.10
+ vite: '>=5.0.0 || >=6.0.0 || >=7.0.0'
+ vite-plugin-solid: ^2.11.10
+ webpack: '>=5.92.0'
+ peerDependenciesMeta:
+ '@rsbuild/core':
+ optional: true
+ '@tanstack/react-router':
+ optional: true
+ vite:
+ optional: true
+ vite-plugin-solid:
+ optional: true
+ webpack:
+ optional: true
+
+ '@tanstack/router-utils@1.158.0':
+ resolution: {integrity: sha512-qZ76eaLKU6Ae9iI/mc5zizBX149DXXZkBVVO3/QRIll79uKLJZHQlMKR++2ba7JsciBWz1pgpIBcCJPE9S0LVg==}
+ engines: {node: '>=12'}
+
+ '@tanstack/store@0.8.0':
+ resolution: {integrity: sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ==}
+
+ '@tanstack/virtual-file-routes@1.154.7':
+ resolution: {integrity: sha512-cHHDnewHozgjpI+MIVp9tcib6lYEQK5MyUr0ChHpHFGBl8Xei55rohFK0I0ve/GKoHeioaK42Smd8OixPp6CTg==}
+ engines: {node: '>=12'}
+
'@ts-morph/common@0.27.0':
resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==}
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.27.0':
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.28.0':
+ resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
@@ -2302,6 +2432,12 @@ packages:
'@types/validate-npm-package-name@4.0.2':
resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==}
+ '@vitejs/plugin-react@4.7.0':
+ resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+
accepts@2.0.0:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
engines: {node: '>= 0.6'}
@@ -2345,6 +2481,10 @@ packages:
any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
+ anymatch@3.1.3:
+ resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+ engines: {node: '>= 8'}
+
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
@@ -2360,6 +2500,9 @@ packages:
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
engines: {node: '>=8.0.0'}
+ babel-dead-code-elimination@1.0.12:
+ resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==}
+
baseline-browser-mapping@2.9.19:
resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==}
hasBin: true
@@ -2434,6 +2577,10 @@ packages:
zod:
optional: true
+ binary-extensions@2.3.0:
+ resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
+ engines: {node: '>=8'}
+
body-parser@2.2.2:
resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
engines: {node: '>=18'}
@@ -2487,6 +2634,10 @@ packages:
resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+ chokidar@3.6.0:
+ resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
+ engines: {node: '>= 8.10.0'}
+
chokidar@4.0.3:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
@@ -2560,6 +2711,9 @@ packages:
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+ cookie-es@2.0.0:
+ resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==}
+
cookie-signature@1.2.2:
resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
engines: {node: '>=6.6.0'}
@@ -2987,6 +3141,11 @@ packages:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
+ goober@2.1.18:
+ resolution: {integrity: sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==}
+ peerDependencies:
+ csstype: ^3.0.10
+
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
@@ -3070,6 +3229,10 @@ packages:
is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+ is-binary-path@2.1.0:
+ resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+ engines: {node: '>=8'}
+
is-docker@3.0.0:
resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -3142,6 +3305,10 @@ packages:
resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
engines: {node: '>=16'}
+ isbot@5.1.35:
+ resolution: {integrity: sha512-waFfC72ZNfwLLuJ2iLaoVaqcNo+CAaLR7xCpAn0Y5WfGzkNHv7ZN39Vbi1y+kb+Zs46XHOX3tZNExroFUPX+Kg==}
+ engines: {node: '>=18'}
+
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
@@ -3418,6 +3585,10 @@ packages:
node-releases@2.0.27:
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
+ normalize-path@3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+
npm-run-path@4.0.1:
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
engines: {node: '>=8'}
@@ -3587,6 +3758,11 @@ packages:
resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==}
engines: {node: '>=20'}
+ prettier@3.8.1:
+ resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==}
+ engines: {node: '>=14'}
+ hasBin: true
+
pretty-ms@9.3.0:
resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==}
engines: {node: '>=18'}
@@ -3647,6 +3823,10 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19
+ react-refresh@0.17.0:
+ resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
+ engines: {node: '>=0.10.0'}
+
react-remove-scroll-bar@2.3.8:
resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
engines: {node: '>=10'}
@@ -3681,6 +3861,10 @@ packages:
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
engines: {node: '>=0.10.0'}
+ readdirp@3.6.0:
+ resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+ engines: {node: '>=8.10.0'}
+
readdirp@4.1.2:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
@@ -3768,6 +3952,16 @@ packages:
resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
engines: {node: '>= 18'}
+ seroval-plugins@1.5.0:
+ resolution: {integrity: sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ seroval: ^1.0
+
+ seroval@1.5.0:
+ resolution: {integrity: sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw==}
+ engines: {node: '>=10'}
+
serve-static@2.2.1:
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
engines: {node: '>= 18'}
@@ -3934,6 +4128,9 @@ packages:
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
+ tiny-warning@1.0.3:
+ resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
+
tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
@@ -4073,6 +4270,10 @@ packages:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
+ unplugin@2.3.11:
+ resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==}
+ engines: {node: '>=18.12.0'}
+
until-async@3.0.2:
resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==}
@@ -4118,10 +4319,53 @@ packages:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
+ vite@6.4.1:
+ resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ jiti: '>=1.21.0'
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
web-streams-polyfill@3.3.3:
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
engines: {node: '>= 8'}
+ webpack-virtual-modules@0.6.2:
+ resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
+
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
@@ -4343,6 +4587,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/helper-plugin-utils': 7.28.6
+
'@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.29.0
@@ -5727,6 +5981,8 @@ snapshots:
'@radix-ui/rect@1.1.1': {}
+ '@rolldown/pluginutils@1.0.0-beta.27': {}
+
'@rollup/rollup-android-arm-eabi@4.57.1':
optional: true
@@ -5883,19 +6139,150 @@ snapshots:
postcss: 8.5.6
tailwindcss: 4.1.18
+ '@tanstack/history@1.154.14': {}
+
'@tanstack/query-core@5.90.20': {}
+ '@tanstack/query-devtools@5.93.0': {}
+
+ '@tanstack/react-query-devtools@5.91.3(@tanstack/react-query@5.90.21(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@tanstack/query-devtools': 5.93.0
+ '@tanstack/react-query': 5.90.21(react@19.2.4)
+ react: 19.2.4
+
'@tanstack/react-query@5.90.21(react@19.2.4)':
dependencies:
'@tanstack/query-core': 5.90.20
react: 19.2.4
+ '@tanstack/react-router-devtools@1.159.10(@tanstack/react-router@1.159.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.159.9)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@tanstack/react-router': 1.159.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@tanstack/router-devtools-core': 1.159.9(@tanstack/router-core@1.159.9)(csstype@3.2.3)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@tanstack/router-core': 1.159.9
+ transitivePeerDependencies:
+ - csstype
+
+ '@tanstack/react-router@1.159.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@tanstack/history': 1.154.14
+ '@tanstack/react-store': 0.8.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@tanstack/router-core': 1.159.9
+ isbot: 5.1.35
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ tiny-invariant: 1.3.3
+ tiny-warning: 1.0.3
+
+ '@tanstack/react-store@0.8.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@tanstack/store': 0.8.0
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ use-sync-external-store: 1.6.0(react@19.2.4)
+
+ '@tanstack/router-core@1.159.9':
+ dependencies:
+ '@tanstack/history': 1.154.14
+ '@tanstack/store': 0.8.0
+ cookie-es: 2.0.0
+ seroval: 1.5.0
+ seroval-plugins: 1.5.0(seroval@1.5.0)
+ tiny-invariant: 1.3.3
+ tiny-warning: 1.0.3
+
+ '@tanstack/router-devtools-core@1.159.9(@tanstack/router-core@1.159.9)(csstype@3.2.3)':
+ dependencies:
+ '@tanstack/router-core': 1.159.9
+ clsx: 2.1.1
+ goober: 2.1.18(csstype@3.2.3)
+ tiny-invariant: 1.3.3
+ optionalDependencies:
+ csstype: 3.2.3
+
+ '@tanstack/router-generator@1.159.9':
+ dependencies:
+ '@tanstack/router-core': 1.159.9
+ '@tanstack/router-utils': 1.158.0
+ '@tanstack/virtual-file-routes': 1.154.7
+ prettier: 3.8.1
+ recast: 0.23.11
+ source-map: 0.7.6
+ tsx: 4.21.0
+ zod: 3.25.76
+ transitivePeerDependencies:
+ - supports-color
+
+ '@tanstack/router-plugin@1.159.11(@tanstack/react-router@1.159.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@6.4.1(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0)
+ '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0)
+ '@babel/template': 7.28.6
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
+ '@tanstack/router-core': 1.159.9
+ '@tanstack/router-generator': 1.159.9
+ '@tanstack/router-utils': 1.158.0
+ '@tanstack/virtual-file-routes': 1.154.7
+ chokidar: 3.6.0
+ unplugin: 2.3.11
+ zod: 3.25.76
+ optionalDependencies:
+ '@tanstack/react-router': 1.159.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ vite: 6.4.1(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@tanstack/router-utils@1.158.0':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/generator': 7.29.1
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+ ansis: 4.2.0
+ babel-dead-code-elimination: 1.0.12
+ diff: 8.0.3
+ pathe: 2.0.3
+ tinyglobby: 0.2.15
+ transitivePeerDependencies:
+ - supports-color
+
+ '@tanstack/store@0.8.0': {}
+
+ '@tanstack/virtual-file-routes@1.154.7': {}
+
'@ts-morph/common@0.27.0':
dependencies:
fast-glob: 3.3.3
minimatch: 10.1.2
path-browserify: 1.0.1
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.29.0
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+
+ '@types/babel__traverse@7.28.0':
+ dependencies:
+ '@babel/types': 7.29.0
+
'@types/estree@1.0.8': {}
'@types/node@25.2.2':
@@ -5914,6 +6301,18 @@ snapshots:
'@types/validate-npm-package-name@4.0.2': {}
+ '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0)
+ '@rolldown/pluginutils': 1.0.0-beta.27
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.17.0
+ vite: 6.4.1(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
+ transitivePeerDependencies:
+ - supports-color
+
accepts@2.0.0:
dependencies:
mime-types: 3.0.2
@@ -5946,6 +6345,11 @@ snapshots:
any-promise@1.3.0: {}
+ anymatch@3.1.3:
+ dependencies:
+ normalize-path: 3.0.0
+ picomatch: 2.3.1
+
argparse@2.0.1: {}
aria-hidden@1.2.6:
@@ -5958,6 +6362,15 @@ snapshots:
atomic-sleep@1.0.0: {}
+ babel-dead-code-elimination@1.0.12:
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/parser': 7.29.0
+ '@babel/traverse': 7.29.0
+ '@babel/types': 7.29.0
+ transitivePeerDependencies:
+ - supports-color
+
baseline-browser-mapping@2.9.19: {}
better-auth@1.4.18(drizzle-kit@0.31.9)(drizzle-orm@0.45.1(kysely@0.28.11)(postgres@3.4.8))(next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
@@ -5990,6 +6403,8 @@ snapshots:
optionalDependencies:
zod: 4.3.6
+ binary-extensions@2.3.0: {}
+
body-parser@2.2.2:
dependencies:
bytes: 3.1.2
@@ -6047,6 +6462,18 @@ snapshots:
chalk@5.6.2: {}
+ chokidar@3.6.0:
+ dependencies:
+ anymatch: 3.1.3
+ braces: 3.0.3
+ glob-parent: 5.1.2
+ is-binary-path: 2.1.0
+ is-glob: 4.0.3
+ normalize-path: 3.0.0
+ readdirp: 3.6.0
+ optionalDependencies:
+ fsevents: 2.3.3
+
chokidar@4.0.3:
dependencies:
readdirp: 4.1.2
@@ -6099,6 +6526,8 @@ snapshots:
convert-source-map@2.0.0: {}
+ cookie-es@2.0.0: {}
+
cookie-signature@1.2.2: {}
cookie@0.7.2: {}
@@ -6511,6 +6940,10 @@ snapshots:
dependencies:
is-glob: 4.0.3
+ goober@2.1.18(csstype@3.2.3):
+ dependencies:
+ csstype: 3.2.3
+
gopd@1.2.0: {}
graceful-fs@4.2.11: {}
@@ -6575,6 +7008,10 @@ snapshots:
is-arrayish@0.2.1: {}
+ is-binary-path@2.1.0:
+ dependencies:
+ binary-extensions: 2.3.0
+
is-docker@3.0.0: {}
is-extglob@2.1.1: {}
@@ -6617,6 +7054,8 @@ snapshots:
dependencies:
is-inside-container: 1.0.0
+ isbot@5.1.35: {}
+
isexe@2.0.0: {}
isexe@3.1.5: {}
@@ -6845,6 +7284,8 @@ snapshots:
node-releases@2.0.27: {}
+ normalize-path@3.0.0: {}
+
npm-run-path@4.0.1:
dependencies:
path-key: 3.1.1
@@ -7020,6 +7461,8 @@ snapshots:
powershell-utils@0.1.0: {}
+ prettier@3.8.1: {}
+
pretty-ms@9.3.0:
dependencies:
parse-ms: 4.0.0
@@ -7130,6 +7573,8 @@ snapshots:
dependencies:
react: 19.2.4
+ react-refresh@0.17.0: {}
+
react-remove-scroll-bar@2.3.8(@types/react@19.2.13)(react@19.2.4):
dependencies:
react: 19.2.4
@@ -7159,6 +7604,10 @@ snapshots:
react@19.2.4: {}
+ readdirp@3.6.0:
+ dependencies:
+ picomatch: 2.3.1
+
readdirp@4.1.2: {}
real-require@0.2.0: {}
@@ -7268,6 +7717,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ seroval-plugins@1.5.0(seroval@1.5.0):
+ dependencies:
+ seroval: 1.5.0
+
+ seroval@1.5.0: {}
+
serve-static@2.2.1:
dependencies:
encodeurl: 2.0.0
@@ -7493,6 +7948,8 @@ snapshots:
tiny-invariant@1.3.3: {}
+ tiny-warning@1.0.3: {}
+
tinyexec@0.3.2: {}
tinyexec@1.0.2: {}
@@ -7621,6 +8078,13 @@ snapshots:
unpipe@1.0.0: {}
+ unplugin@2.3.11:
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ acorn: 8.15.0
+ picomatch: 4.0.3
+ webpack-virtual-modules: 0.6.2
+
until-async@3.0.2: {}
update-browserslist-db@1.2.3(browserslist@4.28.1):
@@ -7654,8 +8118,26 @@ snapshots:
vary@1.1.2: {}
+ vite@6.4.1(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2):
+ dependencies:
+ esbuild: 0.25.12
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+ postcss: 8.5.6
+ rollup: 4.57.1
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ '@types/node': 25.2.2
+ fsevents: 2.3.3
+ jiti: 2.6.1
+ lightningcss: 1.30.2
+ tsx: 4.21.0
+ yaml: 2.8.2
+
web-streams-polyfill@3.3.3: {}
+ webpack-virtual-modules@0.6.2: {}
+
which@2.0.2:
dependencies:
isexe: 2.0.0