diff --git a/apps/admin/.gitignore b/apps/admin/.gitignore index 55afcc4..1bb80a1 100644 --- a/apps/admin/.gitignore +++ b/apps/admin/.gitignore @@ -42,4 +42,6 @@ android/app/build/generated/ */fastlane/test_output # Bundle artifact -*.jsbundle \ No newline at end of file +*.jsbundle +# clerk configuration (can include secrets) +/.clerk/ diff --git a/apps/admin/data/fitai.db b/apps/admin/data/fitai.db index 0b26a5e..3540a1a 100644 Binary files a/apps/admin/data/fitai.db and b/apps/admin/data/fitai.db differ diff --git a/apps/admin/middleware.ts b/apps/admin/middleware.ts new file mode 100644 index 0000000..01d401d --- /dev/null +++ b/apps/admin/middleware.ts @@ -0,0 +1,12 @@ +import { clerkMiddleware } from '@clerk/nextjs/server' + +export default clerkMiddleware() + +export const config = { + matcher: [ + // Skip Next.js internals and all static files, unless found in search params + '/((?!_next|[^?]*\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', + // Always run for API routes + '/(api|trpc)(.*)', + ], +} diff --git a/apps/admin/package-lock.json b/apps/admin/package-lock.json index 48defc4..d2166c1 100644 --- a/apps/admin/package-lock.json +++ b/apps/admin/package-lock.json @@ -8,6 +8,7 @@ "name": "@fitai/admin", "version": "1.0.0", "dependencies": { + "@clerk/nextjs": "^6.34.5", "@fitai/shared": "file:../../packages/shared", "@hookform/resolvers": "^5.2.2", "@tailwindcss/postcss": "^4.1.17", @@ -600,6 +601,103 @@ "dev": true, "license": "MIT" }, + "node_modules/@clerk/backend": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/@clerk/backend/-/backend-2.20.0.tgz", + "integrity": "sha512-RcZN7CAxGkkLydGtWpxCyq4C0pSo/1ch0LJMDQnckrt10Jx8mAjwce2nZQa2xRykxsOla4+boF9a5kDw3nUvVg==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "^3.31.1", + "@clerk/types": "^4.97.2", + "cookie": "1.0.2", + "standardwebhooks": "^1.0.0", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=18.17.0" + } + }, + "node_modules/@clerk/clerk-react": { + "version": "5.53.8", + "resolved": "https://registry.npmjs.org/@clerk/clerk-react/-/clerk-react-5.53.8.tgz", + "integrity": "sha512-TOiYk31rQUL9JOKZr/fhajf+fQCHicy1J4Rxq7vqtjHseJsnIBjzTigjOap/w8PrDAF28O6dbPC5CA0Tp7Md8w==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "^3.31.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" + } + }, + "node_modules/@clerk/nextjs": { + "version": "6.34.5", + "resolved": "https://registry.npmjs.org/@clerk/nextjs/-/nextjs-6.34.5.tgz", + "integrity": "sha512-f1OyucHc5HHBZovzEtJrPR0MUePZxEH2mqu3dt24iGTWTmV2UPnHMB5uSi4XVSWcungnzHWKgTKnHKTVF3vxUA==", + "license": "MIT", + "dependencies": { + "@clerk/backend": "^2.20.0", + "@clerk/clerk-react": "^5.53.8", + "@clerk/shared": "^3.31.1", + "@clerk/types": "^4.97.2", + "server-only": "0.0.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "next": "^13.5.7 || ^14.2.25 || ^15.2.3 || ^16", + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" + } + }, + "node_modules/@clerk/shared": { + "version": "3.31.1", + "resolved": "https://registry.npmjs.org/@clerk/shared/-/shared-3.31.1.tgz", + "integrity": "sha512-mqxZqlzLJYJxA+ryLzhwFR0eO73teAvRd+wvA8bLUZLYvCRFvaiHsB9dEvbo9Z5bMYdq3NPwnx2uljMuu/tiQw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "csstype": "3.1.3", + "dequal": "2.0.3", + "glob-to-regexp": "0.4.1", + "js-cookie": "3.0.5", + "std-env": "^3.9.0", + "swr": "2.3.4" + }, + "engines": { + "node": ">=18.17.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@clerk/types": { + "version": "4.97.2", + "resolved": "https://registry.npmjs.org/@clerk/types/-/types-4.97.2.tgz", + "integrity": "sha512-xnJq3xzpmuuDnNnWuUMKJLPPkaEaLDM0kiv2Hm0gKIcL1+1P3VaGf2vL9roIhmhLswB2PUwtVvZKBmGjT5yOVw==", + "license": "MIT", + "dependencies": { + "@clerk/shared": "^3.31.1" + }, + "engines": { + "node": ">=18.17.0" + } + }, "node_modules/@emnapi/core": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.0.tgz", @@ -2203,6 +2301,12 @@ "@sinonjs/commons": "^3.0.1" } }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, "node_modules/@standard-schema/spec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", @@ -4607,6 +4711,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4633,7 +4746,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true, "license": "MIT" }, "node_modules/d3-array": { @@ -4954,9 +5066,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -5933,6 +6043,12 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense" + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -6414,6 +6530,12 @@ "node": ">=10.13.0" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, "node_modules/glob/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -8333,6 +8455,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -10652,6 +10783,12 @@ "node": ">=10" } }, + "node_modules/server-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", + "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", + "license": "MIT" + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -11091,6 +11228,22 @@ "node": ">=8" } }, + "node_modules/standardwebhooks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", + "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -11433,6 +11586,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.4.tgz", + "integrity": "sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/synckit": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", diff --git a/apps/admin/package.json b/apps/admin/package.json index 6e9b87b..a6df47f 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -11,6 +11,7 @@ "test": "jest" }, "dependencies": { + "@clerk/nextjs": "^6.34.5", "@fitai/shared": "file:../../packages/shared", "@hookform/resolvers": "^5.2.2", "@tailwindcss/postcss": "^4.1.17", diff --git a/apps/admin/src/app/layout.tsx b/apps/admin/src/app/layout.tsx index 945adca..5e349b0 100644 --- a/apps/admin/src/app/layout.tsx +++ b/apps/admin/src/app/layout.tsx @@ -1,22 +1,27 @@ -import type { Metadata } from 'next' -import { Inter } from 'next/font/google' -import './globals.css' +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; +import { ClerkProvider } from "@clerk/nextjs"; -const inter = Inter({ subsets: ['latin'] }) +const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { - title: 'FitAI Admin', - description: 'Fitness management admin dashboard', -} + title: "FitAI Admin", + description: "Fitness management admin dashboard", +}; export default function RootLayout({ children, }: { - children: React.ReactNode + children: React.ReactNode; }) { return ( - - {children} - - ) -} \ No newline at end of file + + + {children} + + + ); +} diff --git a/apps/admin/src/app/page.tsx b/apps/admin/src/app/page.tsx index e01b86c..aa98df5 100644 --- a/apps/admin/src/app/page.tsx +++ b/apps/admin/src/app/page.tsx @@ -3,6 +3,7 @@ import Link from "next/link"; import { UserManagement } from "@/components/users/UserManagement"; import { AnalyticsDashboard } from "@/components/analytics/AnalyticsDashboard"; +import { SignedIn, SignedOut, SignInButton, UserButton } from "@clerk/nextjs"; export default function Home() { return ( @@ -12,56 +13,65 @@ export default function Home() {

FitAI Admin Dashboard

- +
+ + + + + + +
-
-
-

Client Management

-

- Manage fitness clients and their profiles + +

+
+

Client Management

+

+ Manage fitness clients and their profiles +

+
+
+

Payment Tracking

+

+ Monitor payments and subscriptions +

+
+
+

Attendance

+

+ Track client attendance and habits +

+
+
+ +
+
+

+ Recent User Activity +

+
+ +
+
+ +
+

Quick Analytics

+
+ +
+
+
+ + + +
+

+ Please sign in to access the admin dashboard.

+
-
-

Payment Tracking

-

Monitor payments and subscriptions

-
-
-

Attendance

-

Track client attendance and habits

-
-
- -
-
-

- Recent User Activity -

-
- -
-
- -
-

Quick Analytics

-
- -
-
-
+
); diff --git a/apps/admin/src/components/charts/RevenueChart.tsx b/apps/admin/src/components/charts/RevenueChart.tsx index d583944..dcc1f28 100644 --- a/apps/admin/src/components/charts/RevenueChart.tsx +++ b/apps/admin/src/components/charts/RevenueChart.tsx @@ -5,7 +5,7 @@ import { AgCharts } from "ag-charts-react"; import { AgChartOptions } from "ag-charts-community"; interface BarData { - category: string; + label: string; value: number; color?: string; } @@ -30,7 +30,7 @@ export function RevenueChart({ series: [ { type: "bar", - xKey: "category", + xKey: "label", yKey: "value", fills: data.map((item) => item.color || "#10b981"), strokes: ["#ffffff"], @@ -55,7 +55,7 @@ export function RevenueChart({ enabled: true, renderer: (params: any) => { return `
-
${params.datum.category}
+
${params.datum.label}
Revenue: $${params.datum.value.toLocaleString()}
`; }, diff --git a/apps/admin/src/components/users/UserGrid.tsx b/apps/admin/src/components/users/UserGrid.tsx index 969f284..b089377 100644 --- a/apps/admin/src/components/users/UserGrid.tsx +++ b/apps/admin/src/components/users/UserGrid.tsx @@ -163,14 +163,17 @@ export function UserGrid({ const gridRef = React.useRef>(null); const gridOptions = { - theme: "legacy", + theme: "legacy" as const, columnDefs, defaultColDef, rowData: users, - rowSelection: "multiple", + rowSelection: { mode: "multiRow" as const }, onSelectionChanged: () => { const selectedNodes = gridRef.current?.api.getSelectedNodes(); - const selectedData = selectedNodes?.map((node) => node.data) || []; + const selectedData = + selectedNodes + ?.map((node) => node.data) + .filter((data): data is User => data !== undefined) || []; setSelectedUsers(selectedData); if (selectedData.length === 1 && onUserSelect) { onUserSelect(selectedData[0]); diff --git a/apps/admin/src/components/users/UserManagement.tsx b/apps/admin/src/components/users/UserManagement.tsx index 73ca79c..32c7739 100644 --- a/apps/admin/src/components/users/UserManagement.tsx +++ b/apps/admin/src/components/users/UserManagement.tsx @@ -191,14 +191,14 @@ export function UserManagement() {