From 92fdd80449c4439b11aab3c123cac8265bc338db Mon Sep 17 00:00:00 2001 From: smakosh Date: Mon, 1 Dec 2025 17:50:23 +0100 Subject: [PATCH 1/8] feat(admin): introduce admin dashboard and related configurations - Added a new admin dashboard application with necessary components and configurations. - Updated environment variables to include ADMIN_PORT and ADMIN_URL. - Enhanced documentation to reflect the addition of the admin interface. - Adjusted API routes to accommodate admin functionalities. ## Test Plan - [ ] Verify that the admin dashboard loads correctly on http://localhost:3006. - [ ] Ensure all new API routes for admin are functional. - [ ] Validate that environment variables are correctly set and utilized. --- .dockerignore | 1 - .env.example | 1 + .env.unified.example | 1 + .github/start.sh | 3 +- .github/test-split-docker.sh | 1 + .github/test-unified-docker.sh | 1 + AGENTS.md | 3 +- CLAUDE.md | 4 +- README.md | 1 + apps/admin/.gitignore | 42 + apps/admin/.lintstagedrc.json | 5 + apps/admin/.prettierignore | 3 + apps/admin/README.md | 36 + apps/admin/components.json | 22 + apps/admin/eslint.config.mjs | 17 + apps/admin/middleware.ts | 81 + apps/admin/next.config.ts | 21 + apps/admin/package.json | 76 + apps/admin/postcss.config.mjs | 5 + .../public/favicon/android-chrome-192x192.png | Bin 0 -> 2024 bytes .../public/favicon/android-chrome-512x512.png | Bin 0 -> 7632 bytes .../admin/public/favicon/apple-touch-icon.png | Bin 0 -> 1989 bytes apps/admin/public/favicon/favicon-16x16.png | Bin 0 -> 265 bytes apps/admin/public/favicon/favicon-32x32.png | Bin 0 -> 416 bytes apps/admin/public/favicon/favicon.ico | Bin 0 -> 15406 bytes apps/admin/public/favicon/site.webmanifest | 19 + apps/admin/public/opengraph.png | Bin 0 -> 97338 bytes apps/admin/src/app/globals.css | 126 + apps/admin/src/app/layout.tsx | 50 + apps/admin/src/app/login/page.tsx | 171 + apps/admin/src/app/page.tsx | 127 + apps/admin/src/components/admin-shell.tsx | 116 + .../src/components/auth/user-provider.tsx | 27 + .../src/components/landing/theme-toggle.tsx | 87 + .../src/components/server-data-wrapper.tsx | 28 + apps/admin/src/components/ui/alert.tsx | 66 + apps/admin/src/components/ui/avatar.tsx | 53 + apps/admin/src/components/ui/badge.tsx | 46 + apps/admin/src/components/ui/button.tsx | 60 + apps/admin/src/components/ui/carousel.tsx | 248 ++ apps/admin/src/components/ui/checkbox.tsx | 32 + apps/admin/src/components/ui/collapsible.tsx | 33 + apps/admin/src/components/ui/command.tsx | 115 + apps/admin/src/components/ui/dialog.tsx | 143 + .../admin/src/components/ui/dropdown-menu.tsx | 257 ++ apps/admin/src/components/ui/form.tsx | 171 + apps/admin/src/components/ui/hover-card.tsx | 44 + apps/admin/src/components/ui/input-group.tsx | 172 + apps/admin/src/components/ui/input.tsx | 21 + apps/admin/src/components/ui/label.tsx | 24 + apps/admin/src/components/ui/logo.tsx | 19 + apps/admin/src/components/ui/popover.tsx | 36 + apps/admin/src/components/ui/progress.tsx | 31 + .../src/components/ui/providers-icons.tsx | 1121 ++++++ apps/admin/src/components/ui/scroll-area.tsx | 58 + apps/admin/src/components/ui/select.tsx | 187 + apps/admin/src/components/ui/separator.tsx | 28 + apps/admin/src/components/ui/sheet.tsx | 139 + apps/admin/src/components/ui/sidebar.tsx | 729 ++++ apps/admin/src/components/ui/skeleton.tsx | 13 + apps/admin/src/components/ui/sonner.tsx | 27 + apps/admin/src/components/ui/tabs.tsx | 66 + apps/admin/src/components/ui/textarea.tsx | 18 + apps/admin/src/components/ui/tooltip.tsx | 61 + apps/admin/src/hooks/use-mobile.ts | 21 + apps/admin/src/hooks/useUser.ts | 111 + apps/admin/src/lib/admin-metrics.ts | 25 + apps/admin/src/lib/api/v1.d.ts | 3475 +++++++++++++++++ apps/admin/src/lib/auth-client.ts | 31 + apps/admin/src/lib/config-server.ts | 25 + apps/admin/src/lib/config.tsx | 31 + apps/admin/src/lib/fetch-client.ts | 28 + apps/admin/src/lib/getUser.ts | 34 + apps/admin/src/lib/providers.tsx | 52 + apps/admin/src/lib/server-api.ts | 93 + apps/admin/src/lib/stripe.ts | 42 + apps/admin/src/lib/types.ts | 5 + apps/admin/src/lib/utils.ts | 54 + apps/admin/src/types/next-themes.d.ts | 7 + apps/admin/tsconfig.json | 37 + apps/api/src/auth/config.ts | 2 +- apps/api/src/index.ts | 1 + apps/api/src/routes/admin.ts | 150 + apps/api/src/routes/index.ts | 3 + apps/api/src/routes/user.ts | 24 + apps/docs/app/api/proxy/route.ts | 1 + apps/docs/content/self-host.mdx | 1 + apps/gateway/src/app.ts | 1 + apps/playground/src/lib/config-server.ts | 2 + .../blog/2025-05-01-self-host-llm-gateway.md | 3 +- apps/ui/src/lib/config-server.ts | 2 + infra/bunnyshell.yaml | 38 + infra/docker-compose.split.local.yml | 32 + infra/docker-compose.split.yml | 28 + infra/docker-compose.unified.local.yml | 2 + infra/docker-compose.unified.yml | 2 + infra/supervisord.conf | 11 + infra/unified.dockerfile | 2 +- package.json | 2 +- pnpm-lock.yaml | 265 +- turbo.json | 1 - 101 files changed, 9705 insertions(+), 32 deletions(-) create mode 100644 apps/admin/.gitignore create mode 100644 apps/admin/.lintstagedrc.json create mode 100644 apps/admin/.prettierignore create mode 100644 apps/admin/README.md create mode 100644 apps/admin/components.json create mode 100644 apps/admin/eslint.config.mjs create mode 100644 apps/admin/middleware.ts create mode 100644 apps/admin/next.config.ts create mode 100644 apps/admin/package.json create mode 100644 apps/admin/postcss.config.mjs create mode 100644 apps/admin/public/favicon/android-chrome-192x192.png create mode 100644 apps/admin/public/favicon/android-chrome-512x512.png create mode 100644 apps/admin/public/favicon/apple-touch-icon.png create mode 100644 apps/admin/public/favicon/favicon-16x16.png create mode 100644 apps/admin/public/favicon/favicon-32x32.png create mode 100644 apps/admin/public/favicon/favicon.ico create mode 100644 apps/admin/public/favicon/site.webmanifest create mode 100644 apps/admin/public/opengraph.png create mode 100644 apps/admin/src/app/globals.css create mode 100644 apps/admin/src/app/layout.tsx create mode 100644 apps/admin/src/app/login/page.tsx create mode 100644 apps/admin/src/app/page.tsx create mode 100644 apps/admin/src/components/admin-shell.tsx create mode 100644 apps/admin/src/components/auth/user-provider.tsx create mode 100644 apps/admin/src/components/landing/theme-toggle.tsx create mode 100644 apps/admin/src/components/server-data-wrapper.tsx create mode 100644 apps/admin/src/components/ui/alert.tsx create mode 100644 apps/admin/src/components/ui/avatar.tsx create mode 100644 apps/admin/src/components/ui/badge.tsx create mode 100644 apps/admin/src/components/ui/button.tsx create mode 100644 apps/admin/src/components/ui/carousel.tsx create mode 100644 apps/admin/src/components/ui/checkbox.tsx create mode 100644 apps/admin/src/components/ui/collapsible.tsx create mode 100644 apps/admin/src/components/ui/command.tsx create mode 100644 apps/admin/src/components/ui/dialog.tsx create mode 100644 apps/admin/src/components/ui/dropdown-menu.tsx create mode 100644 apps/admin/src/components/ui/form.tsx create mode 100644 apps/admin/src/components/ui/hover-card.tsx create mode 100644 apps/admin/src/components/ui/input-group.tsx create mode 100644 apps/admin/src/components/ui/input.tsx create mode 100644 apps/admin/src/components/ui/label.tsx create mode 100644 apps/admin/src/components/ui/logo.tsx create mode 100644 apps/admin/src/components/ui/popover.tsx create mode 100644 apps/admin/src/components/ui/progress.tsx create mode 100644 apps/admin/src/components/ui/providers-icons.tsx create mode 100644 apps/admin/src/components/ui/scroll-area.tsx create mode 100644 apps/admin/src/components/ui/select.tsx create mode 100644 apps/admin/src/components/ui/separator.tsx create mode 100644 apps/admin/src/components/ui/sheet.tsx create mode 100644 apps/admin/src/components/ui/sidebar.tsx create mode 100644 apps/admin/src/components/ui/skeleton.tsx create mode 100644 apps/admin/src/components/ui/sonner.tsx create mode 100644 apps/admin/src/components/ui/tabs.tsx create mode 100644 apps/admin/src/components/ui/textarea.tsx create mode 100644 apps/admin/src/components/ui/tooltip.tsx create mode 100644 apps/admin/src/hooks/use-mobile.ts create mode 100644 apps/admin/src/hooks/useUser.ts create mode 100644 apps/admin/src/lib/admin-metrics.ts create mode 100644 apps/admin/src/lib/api/v1.d.ts create mode 100644 apps/admin/src/lib/auth-client.ts create mode 100644 apps/admin/src/lib/config-server.ts create mode 100644 apps/admin/src/lib/config.tsx create mode 100644 apps/admin/src/lib/fetch-client.ts create mode 100644 apps/admin/src/lib/getUser.ts create mode 100644 apps/admin/src/lib/providers.tsx create mode 100644 apps/admin/src/lib/server-api.ts create mode 100644 apps/admin/src/lib/stripe.ts create mode 100644 apps/admin/src/lib/types.ts create mode 100644 apps/admin/src/lib/utils.ts create mode 100644 apps/admin/src/types/next-themes.d.ts create mode 100644 apps/admin/tsconfig.json create mode 100644 apps/api/src/routes/admin.ts diff --git a/.dockerignore b/.dockerignore index eb7196949c..6cd9bd56e5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,7 +7,6 @@ **/.pnpm-store **/.idea **/openapi.json -**/.vinxi **/.next **/.next-dev **/.vercel diff --git a/.env.example b/.env.example index 2c4a44bc51..1c826d6605 100644 --- a/.env.example +++ b/.env.example @@ -32,6 +32,7 @@ API_PORT=4002 UI_PORT=3002 PLAYGROUND_PORT=3003 DOCS_PORT=3005 +ADMIN_PORT=3006 # Health check timeout in milliseconds (defaults to 5000ms) TIMEOUT_MS=5000 diff --git a/.env.unified.example b/.env.unified.example index da8d1f525a..7049ca3577 100644 --- a/.env.unified.example +++ b/.env.unified.example @@ -18,6 +18,7 @@ AUTH_SECRET=your-secret-key-here UI_URL=http://localhost:3002 PLAYGROUND_URL=http://localhost:3003 DOCS_URL=http://localhost:3005 +ADMIN_URL=http://localhost:3006 API_URL=http://localhost:4002 API_BACKEND_URL=http://localhost:4002 ORIGIN_URLS=http://localhost:3002 diff --git a/.github/start.sh b/.github/start.sh index 5ed67083c9..64c350aca0 100755 --- a/.github/start.sh +++ b/.github/start.sh @@ -8,7 +8,7 @@ YELLOW='\033[0;33m' NC='\033[0m' # No Color # Array of apps to test -APPS=("api" "gateway" "playground" "ui" "docs") +APPS=("api" "gateway" "playground" "ui" "docs" "admin") # Ports for each app declare -A APP_PORTS @@ -17,6 +17,7 @@ APP_PORTS["gateway"]=4001 APP_PORTS["ui"]=3002 APP_PORTS["playground"]=3003 APP_PORTS["docs"]=3005 +APP_PORTS["admin"]=3006 # Health check routes for each app (optional) declare -A HEALTH_ROUTES diff --git a/.github/test-split-docker.sh b/.github/test-split-docker.sh index 2436643c57..1be9fc90cd 100755 --- a/.github/test-split-docker.sh +++ b/.github/test-split-docker.sh @@ -19,6 +19,7 @@ APP_ENDPOINTS["gateway"]="http://localhost:4001" APP_ENDPOINTS["ui"]="http://localhost:3002" APP_ENDPOINTS["playground"]="http://localhost:3003" APP_ENDPOINTS["docs"]="http://localhost:3005" +APP_ENDPOINTS["admin"]="http://localhost:3006" # Health check routes for each app (optional) declare -A HEALTH_ROUTES diff --git a/.github/test-unified-docker.sh b/.github/test-unified-docker.sh index 79057ad49f..84ceb36c79 100755 --- a/.github/test-unified-docker.sh +++ b/.github/test-unified-docker.sh @@ -23,6 +23,7 @@ APP_ENDPOINTS["gateway"]="http://localhost:4001" APP_ENDPOINTS["ui"]="http://localhost:3002" APP_ENDPOINTS["playground"]="http://localhost:3003" APP_ENDPOINTS["docs"]="http://localhost:3005" +APP_ENDPOINTS["admin"]="http://localhost:3006" # Health check routes for each app (optional) declare -A HEALTH_ROUTES diff --git a/AGENTS.md b/AGENTS.md index 2f40f7a69b..51ccc2199b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,7 +14,7 @@ This file provides guidance to AI agents when working with code in this reposito NOTE: these commands can only be run in the root directory of the repository, not in individual app directories. -- `pnpm dev` - Start all development servers (UI on :3002, Playground on :3003, API on :4002, Gateway on :4001, Docs on :3005) +- `pnpm dev` - Start all development servers (UI on :3002, Playground on :3003, API on :4002, Gateway on :4001, Docs on :3005, Admin on :3006) - `pnpm build` - Build all applications for production - `pnpm clean` - Clean build artifacts and cache directories @@ -135,6 +135,7 @@ NOTE: these commands can only be run in the root directory of the repository, no - UI: http://localhost:3002 - Playground: http://localhost:3003 - Docs: http://localhost:3005 +- Admin: http://localhost:3006 - API: http://localhost:4002 - Gateway: http://localhost:4001 - PostgreSQL: localhost:5432 diff --git a/CLAUDE.md b/CLAUDE.md index 972c522284..8ddbaaf8d6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,7 +14,7 @@ This file provides guidance to AI agents when working with code in this reposito NOTE: these commands can only be run in the root directory of the repository, not in individual app directories. -- `pnpm dev` - Start all development servers (UI on :3002, Playground on :3003, API on :4002, Gateway on :4001, Docs on :3005) +- `pnpm dev` - Start all development servers (UI on :3002, Playground on :3003, API on :4002, Gateway on :4001, Docs on :3005, Admin on :3006) - `pnpm build` - Build all applications for production. Always run this after finishing work on a feature. - `pnpm clean` - Clean build artifacts and cache directories @@ -145,6 +145,7 @@ NOTE: these commands can only be run in the root directory of the repository, no - API: http://localhost:4002 - Gateway: http://localhost:4001 - Docs: http://localhost:3005 +- Admin: http://localhost:3006 - PostgreSQL: localhost:5432 - Redis: localhost:6379 @@ -155,6 +156,7 @@ NOTE: these commands can only be run in the root directory of the repository, no - `apps/api`: Hono backend - `apps/gateway`: API gateway for routing LLM requests - `apps/docs`: Documentation site +- `apps/admin`: Internal Admin Dashboard - `packages/db`: Drizzle ORM schema and migrations - `packages/models`: Model and provider definitions - `packages/shared`: Shared types and utilities diff --git a/README.md b/README.md index 668cb877b7..ddbdd74c17 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ curl -X POST https://api.llmgateway.io/v1/chat/completions \ - `apps/api`: Hono backend - `apps/gateway`: API gateway for routing LLM requests - `apps/docs`: Documentation site +- `apps/admin`: Internal admin dashboard - `packages/db`: Drizzle ORM schema and migrations - `packages/models`: Model and provider definitions - `packages/shared`: Shared types and utilities diff --git a/apps/admin/.gitignore b/apps/admin/.gitignore new file mode 100644 index 0000000000..8d2e85b22b --- /dev/null +++ b/apps/admin/.gitignore @@ -0,0 +1,42 @@ +# 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-dev/ +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/apps/admin/.lintstagedrc.json b/apps/admin/.lintstagedrc.json new file mode 100644 index 0000000000..0c4499386f --- /dev/null +++ b/apps/admin/.lintstagedrc.json @@ -0,0 +1,5 @@ +{ + "package.json": "pnpm sort-package-json", + "**/*.{js,jsx,ts,tsx}": ["pnpm eslint --fix", "pnpm prettier --write"], + "**/*.{md,mdx,json,yml,yaml}": ["pnpm prettier --write"] +} diff --git a/apps/admin/.prettierignore b/apps/admin/.prettierignore new file mode 100644 index 0000000000..7048ce1062 --- /dev/null +++ b/apps/admin/.prettierignore @@ -0,0 +1,3 @@ +.content-collections +.next +v1.d.ts diff --git a/apps/admin/README.md b/apps/admin/README.md new file mode 100644 index 0000000000..e215bc4ccf --- /dev/null +++ b/apps/admin/README.md @@ -0,0 +1,36 @@ +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/admin/components.json b/apps/admin/components.json new file mode 100644 index 0000000000..580352e410 --- /dev/null +++ b/apps/admin/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/app/globals.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/apps/admin/eslint.config.mjs b/apps/admin/eslint.config.mjs new file mode 100644 index 0000000000..335f627e06 --- /dev/null +++ b/apps/admin/eslint.config.mjs @@ -0,0 +1,17 @@ +import lint from "../../eslint.config.mjs"; +import react from "@abinnovision/eslint-config-react"; + +/** @type {import("eslint").Linter.Config[]} */ +export default [ + ...lint, + ...react, + { + rules: { + "no-console": "off", + "react/no-unescaped-entities": "off", + }, + }, + { + ignores: ["**/v1.d.ts"], + }, +]; diff --git a/apps/admin/middleware.ts b/apps/admin/middleware.ts new file mode 100644 index 0000000000..638c192540 --- /dev/null +++ b/apps/admin/middleware.ts @@ -0,0 +1,81 @@ +import { NextResponse } from "next/server"; + +import type { NextRequest } from "next/server"; + +interface MeResponse { + user: { + id: string; + email: string; + isAdmin?: boolean; + }; +} + +export async function middleware(req: NextRequest) { + const { pathname } = req.nextUrl; + + // Allow auth and static routes without admin check + if ( + pathname.startsWith("/login") || + pathname.startsWith("/signup") || + pathname.startsWith("/_next") || + pathname.startsWith("/favicon") || + pathname.startsWith("/api") + ) { + return NextResponse.next(); + } + + const apiUrl = + process.env.API_BACKEND_URL || + process.env.API_URL || + "http://localhost:4002"; + + const key = "better-auth.session_token"; + const cookies = req.cookies; + const secureSession = cookies.get(`__Secure-${key}`)?.value; + const session = cookies.get(key)?.value; + + const cookieHeader = secureSession + ? `__Secure-${key}=${secureSession}` + : session + ? `${key}=${session}` + : ""; + + if (!cookieHeader) { + const loginUrl = new URL("/login", req.url); + loginUrl.searchParams.set("returnUrl", pathname || "/"); + return NextResponse.redirect(loginUrl); + } + + try { + const res = await fetch(`${apiUrl}/user/me`, { + method: "GET", + headers: { + Cookie: cookieHeader, + }, + }); + + if (!res.ok) { + const loginUrl = new URL("/login", req.url); + loginUrl.searchParams.set("returnUrl", pathname || "/"); + return NextResponse.redirect(loginUrl); + } + + const data = (await res.json()) as MeResponse; + + if (!data.user?.isAdmin) { + return new NextResponse("Forbidden: admin access required", { + status: 403, + }); + } + } catch { + return new NextResponse("Forbidden: admin access required", { + status: 403, + }); + } + + return NextResponse.next(); +} + +export const config = { + matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"], +}; diff --git a/apps/admin/next.config.ts b/apps/admin/next.config.ts new file mode 100644 index 0000000000..d240a2788c --- /dev/null +++ b/apps/admin/next.config.ts @@ -0,0 +1,21 @@ +import { join } from "path"; + +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + outputFileTracingRoot: join(__dirname, "../../"), + distDir: process.env.NODE_ENV === "development" ? ".next-dev" : ".next", + output: "standalone", + reactStrictMode: true, + productionBrowserSourceMaps: true, + reactCompiler: true, + experimental: { + // turbopackFileSystemCacheForDev: true, + // turbopackFileSystemCacheForBuild: true, + }, + typescript: { + ignoreBuildErrors: true, + }, +}; + +export default nextConfig; diff --git a/apps/admin/package.json b/apps/admin/package.json new file mode 100644 index 0000000000..76a674d4a4 --- /dev/null +++ b/apps/admin/package.json @@ -0,0 +1,76 @@ +{ + "name": "admin", + "version": "0.0.0", + "private": true, + "license": "SEE LICENSE IN ../../LICENSE", + "files": [ + "public", + ".next" + ], + "scripts": { + "build": "pnpm generate && tsc && next build --turbopack && mkdir -p .next/static && cp -r .next/static .next/standalone/apps/admin/.next/ && test -d public && cp -r public .next/standalone/apps/admin/", + "dev": "next dev --port 3006 --turbopack", + "format": "eslint --fix . && prettier --write .", + "generate": "openapi-typescript ../api/openapi.json -o ./src/lib/api/v1.d.ts", + "lint": "eslint . && prettier --check .", + "start": "next start" + }, + "dependencies": { + "@hookform/resolvers": "5.2.2", + "@llmgateway/models": "workspace:*", + "@radix-ui/react-avatar": "1.1.11", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "2.1.16", + "@radix-ui/react-hover-card": "1.1.15", + "@radix-ui/react-label": "2.1.8", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-progress": "^1.1.8", + "@radix-ui/react-scroll-area": "1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "1.2.4", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@stripe/react-stripe-js": "5.2.0", + "@stripe/stripe-js": "8.1.0", + "@tanstack/react-query": "^5.90.5", + "api": "workspace:*", + "babel-plugin-react-compiler": "1.0.0", + "better-auth": "1.3.30", + "class-variance-authority": "0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "4.1.0", + "embla-carousel-react": "8.6.0", + "lucide-react": "0.548.0", + "nanoid": "5.1.6", + "next": "16.0.1", + "next-themes": "^0.4.6", + "openapi-fetch": "0.15.0", + "openapi-react-query": "0.5.1", + "pretty-bytes": "7.1.0", + "react": "19.2.0", + "react-dom": "19.2.0", + "react-hook-form": "7.65.0", + "sonner": "^2.0.7", + "tailwind-merge": "3.3.1" + }, + "devDependencies": { + "@eslint/eslintrc": "^3.3.1", + "@tailwindcss/postcss": "^4.1.16", + "@tanstack/react-query-devtools": "5.90.2", + "@types/node": "^24.9.1", + "@types/react": "^19.2.6", + "@types/react-dom": "^19.2.3", + "eslint": "^9.38.0", + "eslint-config-next": "16.0.0", + "openapi-typescript": "7.10.1", + "tailwindcss": "^4.1.16", + "tw-animate-css": "1.4.0", + "typescript": "^5.9.3", + "zod": "3.25.76" + } +} diff --git a/apps/admin/postcss.config.mjs b/apps/admin/postcss.config.mjs new file mode 100644 index 0000000000..f50127cda3 --- /dev/null +++ b/apps/admin/postcss.config.mjs @@ -0,0 +1,5 @@ +const config = { + plugins: ["@tailwindcss/postcss"], +}; + +export default config; diff --git a/apps/admin/public/favicon/android-chrome-192x192.png b/apps/admin/public/favicon/android-chrome-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..2a8a2afc7907a9004968df5a20be7bf031a38df4 GIT binary patch literal 2024 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 JPmDZb{|BA$Wb6O{ literal 0 HcmV?d00001 diff --git a/apps/admin/public/favicon/apple-touch-icon.png b/apps/admin/public/favicon/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8d104132970d289577b5d981d1f3d607ab0a7736 GIT binary patch literal 1989 zcmV;$2RitPP)Ha1avJnzOus=FZ3N)yJQ}T_E@^NAewAR23_D7Hf)62&uPfcmh z$b)b~UQs(--mRuMWWD>?U}?Eja-<(YHi|8rf{YB*)EhQ6vWy^GQIAePgguy|Cv0!y z;-A+KIytc$>^_v++PEb03{7Ov7A874lg`Gak@wJ+`)#Llv~kzaFH1XNFs`7TwBCKxK|jPsKrVuIrlbhl1x1gM3!w|KDW4vkzUR>P|GbH9 z%`yt|6X;tOkUq0_0_|%KyuOrip|^&c4%Luxp;t9-Ix?+40&TG+4^{fizYp*_bht7b zSQfCFhHH}7;*??Y=f5_YZpa`FFkVDCe-iCn3L+(ax~!0XV!$k`tr zV=O+s53MyxXFOJs_a47zXs5`r$cn38q1Q)m7gtHpJwk&@5xaC(cLxpW5;rxWU_roL z9oiDd6G=+PL8y|rsxI`wBCm5Ab7{f)2$)vTF0_ayG=UiEMS0-AyUxEj~NBnbjf(LNuLk~G?Vsc3~&?rU#`;>Sl1DQxk`YF&>;5qk({zRQlkQk zT;tJAx|)gst#hC~tA(9>B$IK`n=-kG&?*LPS*=tps*@{CM~%nijJ1V^MFYGF6g{PB z3@wAYK5OQy56#!m6sMoMx=yd752QZ0>3h)WtC((~DNkP?y$((Pt3m5;JnO}21Dzl< zMJrRb8U`t)ue}MOlyXk!G+C@$Bmv_O`Um}k z{z3nsf6$#l4=&75!H0hYnuLJP#7ff9115bf=*``u50E`QhVZLHuS4oUKiIn`g@2)s z4}C}p@?Kka1ASd#>=$}hh<_hCueL-BMO_J6gv+n1vMB4!BLcgu>h=e{be7IHjz@Oh zmwFuP>HdP6c6|_|?NIa8dMH+RXY%yPg^Mzs7}hm*=Unu~wU#U5;Kv^1d-1gTL4O3A zeGz(mqaElev~Rqs#a#=U%-juI+%TY*p4e8+ghIt3*JQ)613&g}^f=aT*hcW;x%TPXg~&@uufbC<`gYJDNG^tX zE;9)3lDA~{1j@t9lc4oJsqWiq&Ffrx}pwah+h~UCHSqbVbdbP zZ_l8APtyO#le|61wS=C(vQ59cJSdOoTMSmWV}lkc2c_O2m+$ zO%Z@}j2(woyst%DSu3mrZM_HT7IqYx4fKr#nU5Y(g5aI4>C1rjRj!V)BhY?a2l>=m zHYRv~8#6DU&!@NbxM~n*GY<0N+Vl09x{E!O8F^V4A|`rSotfot!Vodl&yvy%3+NlA zWq(_2J$8H;leKT=ypG+jIhKhv$F%1i-m!(!A6VXwhrt$t;90u#%GQB_Q=Y zB|(0{|N0oJKP*YxYb|#>Y+e2yb)YzBfk$L90|U1(2s1Lwnj--eEcSG945_#kdp=T# zS&@U~LSE$6-}lXT_-G05dm_5FY1^vcyWdwPZe?>=aLN2hmN>(ydjbwGTD6iqezh*F zHYrecE4;SnVtOCbI~EUPZkq)Qj&|A!lvE!yIm&%y)4Dqg)Pvs7=ghpj{C`dCzv;8% eEnYv5{Lk!hgQ@6=-HRBY^$eb_elF{r5}E*7fpl2_ literal 0 HcmV?d00001 diff --git a/apps/admin/public/favicon/favicon-32x32.png b/apps/admin/public/favicon/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..fd4fdbd3cc1e92389060f729cd13997cd8307b59 GIT binary patch literal 416 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 literal 0 HcmV?d00001 diff --git a/apps/admin/public/favicon/site.webmanifest b/apps/admin/public/favicon/site.webmanifest new file mode 100644 index 0000000000..483b78d50e --- /dev/null +++ b/apps/admin/public/favicon/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/favicon/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/favicon/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/apps/admin/public/opengraph.png b/apps/admin/public/opengraph.png new file mode 100644 index 0000000000000000000000000000000000000000..83f6ba8f6005b5d42cbb407e61a5291b57b13a55 GIT binary patch literal 97338 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 literal 0 HcmV?d00001 diff --git a/apps/admin/src/app/globals.css b/apps/admin/src/app/globals.css new file mode 100644 index 0000000000..eca69747db --- /dev/null +++ b/apps/admin/src/app/globals.css @@ -0,0 +1,126 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-inter); + --font-mono: var(--font-geist-mono); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.141 0.005 285.823); + --card: oklch(1 0 0); + --card-foreground: oklch(0.141 0.005 285.823); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.141 0.005 285.823); + --primary: oklch(0.21 0.006 285.885); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.967 0.001 286.375); + --secondary-foreground: oklch(0.21 0.006 285.885); + --muted: oklch(0.967 0.001 286.375); + --muted-foreground: oklch(0.552 0.016 285.938); + --accent: oklch(0.967 0.001 286.375); + --accent-foreground: oklch(0.21 0.006 285.885); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.92 0.004 286.32); + --input: oklch(0.92 0.004 286.32); + --ring: oklch(0.705 0.015 286.067); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.141 0.005 285.823); + --sidebar-primary: oklch(0.21 0.006 285.885); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.967 0.001 286.375); + --sidebar-accent-foreground: oklch(0.21 0.006 285.885); + --sidebar-border: oklch(0.92 0.004 286.32); + --sidebar-ring: oklch(0.705 0.015 286.067); +} + +.dark { + --background: oklch(0.141 0.005 285.823); + --foreground: oklch(0.985 0 0); + --card: oklch(0.21 0.006 285.885); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.21 0.006 285.885); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.92 0.004 286.32); + --primary-foreground: oklch(0.21 0.006 285.885); + --secondary: oklch(0.274 0.006 286.033); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.274 0.006 286.033); + --muted-foreground: oklch(0.705 0.015 286.067); + --accent: oklch(0.274 0.006 286.033); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.552 0.016 285.938); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.141 0.005 285.823); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.274 0.006 286.033); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.552 0.016 285.938); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } + + code { + @apply font-mono; + } +} diff --git a/apps/admin/src/app/layout.tsx b/apps/admin/src/app/layout.tsx new file mode 100644 index 0000000000..f87599538f --- /dev/null +++ b/apps/admin/src/app/layout.tsx @@ -0,0 +1,50 @@ +import { Inter, Geist_Mono } from "next/font/google"; + +import { AdminShell } from "@/components/admin-shell"; +import { getConfig } from "@/lib/config-server"; +import { Providers } from "@/lib/providers"; + +import "./globals.css"; + +import type { Metadata } from "next"; +import type { ReactNode } from "react"; + +const inter = Inter({ + variable: "--font-inter", + subsets: ["latin"], + display: "swap", +}); + +const geistMono = Geist_Mono({ + variable: "--font-mono", + subsets: ["latin"], +}); + +export const dynamic = "force-dynamic"; + +export const metadata: Metadata = { + metadataBase: new URL("https://admin.llmgateway.io"), + title: "LLM Gateway Admin", + description: "Admin dashboard for LLM Gateway.", + icons: { + icon: "/favicon/favicon.ico?v=2", + }, + robots: { + index: false, + follow: false, + }, +}; + +export default function RootLayout({ children }: { children: ReactNode }) { + const config = getConfig(); + + return ( + + + + {children} + + + + ); +} diff --git a/apps/admin/src/app/login/page.tsx b/apps/admin/src/app/login/page.tsx new file mode 100644 index 0000000000..b37c309201 --- /dev/null +++ b/apps/admin/src/app/login/page.tsx @@ -0,0 +1,171 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { useQueryClient } from "@tanstack/react-query"; +import { Loader2 } from "lucide-react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import * as z from "zod"; + +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { useUser } from "@/hooks/useUser"; +import { useAuth } from "@/lib/auth-client"; + +const formSchema = z.object({ + email: z.string().email({ message: "Please enter a valid email address" }), + password: z + .string() + .min(8, { message: "Password must be at least 8 characters" }), +}); + +function getSafeRedirectUrl(url: string | null): string { + if (!url) { + return "/"; + } + if (url.startsWith("/") && !url.startsWith("//")) { + return url; + } + return "/"; +} + +export default function Login() { + const queryClient = useQueryClient(); + const router = useRouter(); + const searchParams = useSearchParams(); + const [isLoading, setIsLoading] = useState(false); + const { signIn } = useAuth(); + const returnUrl = getSafeRedirectUrl(searchParams.get("returnUrl")); + + useUser({ + redirectTo: returnUrl, + redirectWhen: "authenticated", + }); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + email: "", + password: "", + }, + }); + + async function onSubmit(values: z.infer) { + setIsLoading(true); + const { error } = await signIn.email( + { + email: values.email, + password: values.password, + }, + { + onSuccess: () => { + queryClient.clear(); + toast.success("Login successful"); + router.push("/"); + }, + onError: (ctx) => { + toast.error(ctx.error.message || "An unknown error occurred", { + style: { + backgroundColor: "var(--destructive)", + color: "var(--destructive-foreground)", + }, + }); + }, + }, + ); + + if (error) { + toast.error(error.message || "An unknown error occurred", { + style: { + backgroundColor: "var(--destructive)", + color: "var(--destructive-foreground)", + }, + }); + } + + setIsLoading(false); + } + + return ( +
+
+
+

+ Welcome back +

+

+ Enter your email and password to sign in to your account +

+
+
+ + ( + + Email + + + + + + )} + /> + ( + + Password + + + + + + )} + /> + + + +
+
+ +
+
+ Or +
+
+
+
+ ); +} diff --git a/apps/admin/src/app/page.tsx b/apps/admin/src/app/page.tsx new file mode 100644 index 0000000000..c5a29c31b8 --- /dev/null +++ b/apps/admin/src/app/page.tsx @@ -0,0 +1,127 @@ +import { ArrowUpRight, CircleDollarSign, Coins, Users } from "lucide-react"; + +import { getAdminDashboardMetrics } from "@/lib/admin-metrics"; +import { cn } from "@/lib/utils"; + +const currencyFormatter = new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + maximumFractionDigits: 2, +}); + +const numberFormatter = new Intl.NumberFormat("en-US", { + maximumFractionDigits: 0, +}); + +function MetricCard({ + label, + value, + subtitle, + icon, + accent, +}: { + label: string; + value: string; + subtitle?: string; + icon?: React.ReactNode; + accent?: "green" | "blue" | "purple"; +}) { + return ( +
+
+
+

+ {label} +

+

{value}

+ {subtitle ? ( +

{subtitle}

+ ) : null} +
+ {icon ? ( +
+ {icon} +
+ ) : null} +
+
+ ); +} + +export default async function Page() { + const metrics = await getAdminDashboardMetrics(); + + return ( +
+
+
+

+ Admin Dashboard +

+

+ High-level overview of global usage, revenue, and customers. +

+
+
+ + Live data +
+
+ +
+ } + accent="purple" + /> + } + accent="green" + /> + } + accent="blue" + /> + } + accent="blue" + /> + } + accent="green" + /> + } + accent="purple" + /> +
+
+ ); +} diff --git a/apps/admin/src/components/admin-shell.tsx b/apps/admin/src/components/admin-shell.tsx new file mode 100644 index 0000000000..9a54355412 --- /dev/null +++ b/apps/admin/src/components/admin-shell.tsx @@ -0,0 +1,116 @@ +"use client"; + +import { useQueryClient } from "@tanstack/react-query"; +import { LayoutDashboard, LogOut, Users } from "lucide-react"; +import Link from "next/link"; +import { usePathname, useRouter } from "next/navigation"; + +import { Button } from "@/components/ui/button"; +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarGroup, + SidebarGroupLabel, + SidebarHeader, + SidebarInset, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + SidebarProvider, + SidebarTrigger, +} from "@/components/ui/sidebar"; +import { useAuth } from "@/lib/auth-client"; + +import { Logo } from "./ui/logo"; + +import type { ReactNode } from "react"; + +interface AdminShellProps { + children: ReactNode; +} + +export function AdminShell({ children }: AdminShellProps) { + const pathname = usePathname(); + const router = useRouter(); + const { signOut } = useAuth(); + const queryClient = useQueryClient(); + + const isDashboard = pathname === "/" || pathname === ""; + + const handleSignOut = async () => { + await signOut({ + fetchOptions: { + onSuccess: () => { + queryClient.clear(); + router.push("/login"); + }, + }, + }); + }; + + return ( + + + +
+
+
+ +
+
+ + LLM Gateway + + + Admin + +
+
+ +
+
+ + + Main + + + + + + Dashboard + + + + + + + People + + + +
+ + Users (coming soon) +
+
+
+
+
+
+ + + +
+ {children} +
+ ); +} diff --git a/apps/admin/src/components/auth/user-provider.tsx b/apps/admin/src/components/auth/user-provider.tsx new file mode 100644 index 0000000000..da097bb3eb --- /dev/null +++ b/apps/admin/src/components/auth/user-provider.tsx @@ -0,0 +1,27 @@ +"use client"; + +import { useQueryClient } from "@tanstack/react-query"; +import { useRef } from "react"; + +import { useApi } from "@/lib/fetch-client"; + +import type { User } from "@/lib/types"; + +interface UserProviderProps { + children: React.ReactNode; + initialUserData?: { user: User } | null; +} + +export function UserProvider({ children, initialUserData }: UserProviderProps) { + const queryClient = useQueryClient(); + const hasSetInitialData = useRef(false); + const api = useApi(); + + if (initialUserData && !hasSetInitialData.current) { + const queryKey = api.queryOptions("get", "/user/me", {}).queryKey; + queryClient.setQueryData(queryKey, initialUserData); + hasSetInitialData.current = true; + } + + return children; +} diff --git a/apps/admin/src/components/landing/theme-toggle.tsx b/apps/admin/src/components/landing/theme-toggle.tsx new file mode 100644 index 0000000000..76c6285cb5 --- /dev/null +++ b/apps/admin/src/components/landing/theme-toggle.tsx @@ -0,0 +1,87 @@ +"use client"; + +import { Moon, Sun } from "lucide-react"; +import { useTheme } from "next-themes"; +import { useEffect, useState } from "react"; + +import { cn } from "@/lib/utils"; + +interface ThemeToggleProps { + className?: string; +} + +export function ThemeToggle({ className }: ThemeToggleProps) { + const { theme, setTheme, systemTheme } = useTheme(); + const [mounted, setMounted] = useState(false); + const currentTheme = theme === "system" ? systemTheme : theme; + const isDark = currentTheme === "dark"; + + useEffect(() => setMounted(true), []); + + if (!mounted) { + return ( +
+
+
+ +
+
+ +
+
+
+ ); + } + + return ( +
setTheme(isDark ? "light" : "dark")} + role="button" + tabIndex={0} + > +
+
+ {isDark ? ( + + ) : ( + + )} +
+
+ {isDark ? ( + + ) : ( + + )} +
+
+
+ ); +} diff --git a/apps/admin/src/components/server-data-wrapper.tsx b/apps/admin/src/components/server-data-wrapper.tsx new file mode 100644 index 0000000000..5041865bf6 --- /dev/null +++ b/apps/admin/src/components/server-data-wrapper.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { useQueryClient } from "@tanstack/react-query"; +import { useEffect } from "react"; + +interface ServerDataWrapperProps { + children: React.ReactNode; + initialData: Array<{ + queryKey: string[]; + data: any; + }>; +} + +export function ServerDataWrapper({ + children, + initialData, +}: ServerDataWrapperProps) { + const queryClient = useQueryClient(); + + useEffect(() => { + // Set initial data for all queries + initialData.forEach(({ queryKey, data }) => { + queryClient.setQueryData(queryKey, data); + }); + }, [queryClient, initialData]); + + return children; +} diff --git a/apps/admin/src/components/ui/alert.tsx b/apps/admin/src/components/ui/alert.tsx new file mode 100644 index 0000000000..534f6d8b01 --- /dev/null +++ b/apps/admin/src/components/ui/alert.tsx @@ -0,0 +1,66 @@ +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const alertVariants = cva( + "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", + { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: + "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ); +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ); +} + +export { Alert, AlertTitle, AlertDescription }; diff --git a/apps/admin/src/components/ui/avatar.tsx b/apps/admin/src/components/ui/avatar.tsx new file mode 100644 index 0000000000..9e44272963 --- /dev/null +++ b/apps/admin/src/components/ui/avatar.tsx @@ -0,0 +1,53 @@ +"use client"; + +import * as AvatarPrimitive from "@radix-ui/react-avatar"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +function Avatar({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/apps/admin/src/components/ui/badge.tsx b/apps/admin/src/components/ui/badge.tsx new file mode 100644 index 0000000000..868117c263 --- /dev/null +++ b/apps/admin/src/components/ui/badge.tsx @@ -0,0 +1,46 @@ +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const badgeVariants = cva( + "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + secondary: + "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + destructive: + "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : "span"; + + return ( + + ); +} + +export { Badge, badgeVariants }; diff --git a/apps/admin/src/components/ui/button.tsx b/apps/admin/src/components/ui/button.tsx new file mode 100644 index 0000000000..132f441df5 --- /dev/null +++ b/apps/admin/src/components/ui/button.tsx @@ -0,0 +1,60 @@ +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + "icon-sm": "size-8", + "icon-lg": "size-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +function Button({ + className, + variant, + size, + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean; + }) { + const Comp = asChild ? Slot : "button"; + + return ( + + ); +} + +export { Button, buttonVariants }; diff --git a/apps/admin/src/components/ui/carousel.tsx b/apps/admin/src/components/ui/carousel.tsx new file mode 100644 index 0000000000..eb3af8fbc4 --- /dev/null +++ b/apps/admin/src/components/ui/carousel.tsx @@ -0,0 +1,248 @@ +"use client"; + +import useEmblaCarousel, { + type UseEmblaCarouselType, +} from "embla-carousel-react"; +import { ArrowLeft, ArrowRight } from "lucide-react"; +import * as React from "react"; + +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; + +type CarouselApi = UseEmblaCarouselType[1]; +type UseCarouselParameters = Parameters; +type CarouselOptions = UseCarouselParameters[0]; +type CarouselPlugin = UseCarouselParameters[1]; + +interface CarouselProps { + opts?: CarouselOptions; + plugins?: CarouselPlugin; + orientation?: "horizontal" | "vertical"; + setApi?: (api: CarouselApi) => void; +} + +type CarouselContextProps = { + carouselRef: ReturnType[0]; + api: ReturnType[1]; + scrollPrev: () => void; + scrollNext: () => void; + canScrollPrev: boolean; + canScrollNext: boolean; +} & CarouselProps; + +const CarouselContext = React.createContext(null); + +function useCarousel() { + const context = React.useContext(CarouselContext); + + if (!context) { + throw new Error("useCarousel must be used within a "); + } + + return context; +} + +function Carousel({ + orientation = "horizontal", + opts, + setApi, + plugins, + className, + children, + ...props +}: React.ComponentProps<"div"> & CarouselProps) { + const [carouselRef, api] = useEmblaCarousel( + { + ...opts, + axis: orientation === "horizontal" ? "x" : "y", + }, + plugins, + ); + const [canScrollPrev, setCanScrollPrev] = React.useState(false); + const [canScrollNext, setCanScrollNext] = React.useState(false); + + const onSelect = React.useCallback((api: CarouselApi) => { + if (!api) { + return; + } + setCanScrollPrev(api.canScrollPrev()); + setCanScrollNext(api.canScrollNext()); + }, []); + + const scrollPrev = React.useCallback(() => { + api?.scrollPrev(); + }, [api]); + + const scrollNext = React.useCallback(() => { + api?.scrollNext(); + }, [api]); + + const handleKeyDown = React.useCallback( + (event: React.KeyboardEvent) => { + if (event.key === "ArrowLeft") { + event.preventDefault(); + scrollPrev(); + } else if (event.key === "ArrowRight") { + event.preventDefault(); + scrollNext(); + } + }, + [scrollPrev, scrollNext], + ); + + React.useEffect(() => { + if (!api || !setApi) { + return; + } + setApi(api); + }, [api, setApi]); + + React.useEffect(() => { + if (!api) { + return; + } + onSelect(api); + api.on("reInit", onSelect); + api.on("select", onSelect); + + return () => { + api?.off("select", onSelect); + }; + }, [api, onSelect]); + + return ( + +
+ {children} +
+
+ ); +} + +function CarouselContent({ className, ...props }: React.ComponentProps<"div">) { + const { carouselRef, orientation } = useCarousel(); + + return ( +
+
+
+ ); +} + +function CarouselItem({ className, ...props }: React.ComponentProps<"div">) { + const { orientation } = useCarousel(); + + return ( +
+ ); +} + +function CarouselPrevious({ + className, + variant = "outline", + size = "icon", + ...props +}: React.ComponentProps) { + const { orientation, scrollPrev, canScrollPrev } = useCarousel(); + + return ( + + ); +} + +function CarouselNext({ + className, + variant = "outline", + size = "icon", + ...props +}: React.ComponentProps) { + const { orientation, scrollNext, canScrollNext } = useCarousel(); + + return ( + + ); +} + +export { + type CarouselApi, + Carousel, + CarouselContent, + CarouselItem, + CarouselPrevious, + CarouselNext, +}; diff --git a/apps/admin/src/components/ui/checkbox.tsx b/apps/admin/src/components/ui/checkbox.tsx new file mode 100644 index 0000000000..9471c2c411 --- /dev/null +++ b/apps/admin/src/components/ui/checkbox.tsx @@ -0,0 +1,32 @@ +"use client"; + +import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import { CheckIcon } from "lucide-react"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +function Checkbox({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + + ); +} + +export { Checkbox }; diff --git a/apps/admin/src/components/ui/collapsible.tsx b/apps/admin/src/components/ui/collapsible.tsx new file mode 100644 index 0000000000..0551ffdd83 --- /dev/null +++ b/apps/admin/src/components/ui/collapsible.tsx @@ -0,0 +1,33 @@ +"use client"; + +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; + +function Collapsible({ + ...props +}: React.ComponentProps) { + return ; +} + +function CollapsibleTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function CollapsibleContent({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { Collapsible, CollapsibleTrigger, CollapsibleContent }; diff --git a/apps/admin/src/components/ui/command.tsx b/apps/admin/src/components/ui/command.tsx new file mode 100644 index 0000000000..f4394a99d2 --- /dev/null +++ b/apps/admin/src/components/ui/command.tsx @@ -0,0 +1,115 @@ +"use client"; + +import { Command as CommandPrimitive } from "cmdk"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Command.displayName = "Command"; + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + // eslint-disable-next-line react/no-unknown-property +
+ +
+)); +CommandInput.displayName = CommandPrimitive.Input.displayName; + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandList.displayName = CommandPrimitive.List.displayName; + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandEmpty.displayName = CommandPrimitive.Empty.displayName; + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandGroup.displayName = CommandPrimitive.Group.displayName; + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandItem.displayName = CommandPrimitive.Item.displayName; + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandSeparator.displayName = CommandPrimitive.Separator.displayName; + +export { + Command, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandSeparator, +}; diff --git a/apps/admin/src/components/ui/dialog.tsx b/apps/admin/src/components/ui/dialog.tsx new file mode 100644 index 0000000000..20c2d988c4 --- /dev/null +++ b/apps/admin/src/components/ui/dialog.tsx @@ -0,0 +1,143 @@ +"use client"; + +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { XIcon } from "lucide-react"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +function Dialog({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogPortal({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return ; +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean; +}) { + return ( + + + + {children} + {showCloseButton && ( + + + Close + + )} + + + ); +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +}; diff --git a/apps/admin/src/components/ui/dropdown-menu.tsx b/apps/admin/src/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000000..021f026c96 --- /dev/null +++ b/apps/admin/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,257 @@ +"use client"; + +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +function DropdownMenu({ + ...props +}: React.ComponentProps) { + return ; +} + +function DropdownMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuContent({ + className, + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ); +} + +function DropdownMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps & { + inset?: boolean; + variant?: "default" | "destructive"; +}) { + return ( + + ); +} + +function DropdownMenuCheckboxItem({ + className, + children, + checked, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ); +} + +function DropdownMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuRadioItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ); +} + +function DropdownMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean; +}) { + return ( + + ); +} + +function DropdownMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DropdownMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ); +} + +function DropdownMenuSub({ + ...props +}: React.ComponentProps) { + return ; +} + +function DropdownMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean; +}) { + return ( + + {children} + + + ); +} + +function DropdownMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { + DropdownMenu, + DropdownMenuPortal, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuSub, + DropdownMenuSubTrigger, + DropdownMenuSubContent, +}; diff --git a/apps/admin/src/components/ui/form.tsx b/apps/admin/src/components/ui/form.tsx new file mode 100644 index 0000000000..6bc4931436 --- /dev/null +++ b/apps/admin/src/components/ui/form.tsx @@ -0,0 +1,171 @@ +"use client"; + +import { Slot } from "@radix-ui/react-slot"; +import * as React from "react"; +import { + Controller, + FormProvider, + useFormContext, + useFormState, + type ControllerProps, + type FieldPath, + type FieldValues, +} from "react-hook-form"; + +import { Label } from "@/components/ui/label"; +import { cn } from "@/lib/utils"; + +import type * as LabelPrimitive from "@radix-ui/react-label"; + +const Form = FormProvider; + +interface FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +> { + name: TName; +} + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue, +); + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +>({ + ...props +}: ControllerProps) => { + return ( + // eslint-disable-next-line react/jsx-no-constructed-context-values + + + + ); +}; + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext); + const itemContext = React.useContext(FormItemContext); + const { getFieldState } = useFormContext(); + const formState = useFormState({ name: fieldContext.name }); + const fieldState = getFieldState(fieldContext.name, formState); + + if (!fieldContext) { + throw new Error("useFormField should be used within "); + } + + const { id } = itemContext; + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + }; +}; + +interface FormItemContextValue { + id: string; +} + +const FormItemContext = React.createContext( + {} as FormItemContextValue, +); + +function FormItem({ className, ...props }: React.ComponentProps<"div">) { + const id = React.useId(); + + return ( + // eslint-disable-next-line react/jsx-no-constructed-context-values + +
+ + ); +} + +function FormLabel({ + className, + ...props +}: React.ComponentProps) { + const { error, formItemId } = useFormField(); + + return ( +