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
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Please sign in to access the admin dashboard.
+
-
-
Payment Tracking
-
Monitor payments and subscriptions
-
-
-
Attendance
-
Track client attendance and habits
-
-
-
-
-
-
- Recent User Activity
-
-
-
-
-
-
-
-
+
);
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() {