Wire frontend to the backend: auth, organizations, real patient API

Connects the UI-only app to the new backend over Better Auth + a small
patient API client, replacing the in-memory fixture and placeholder identity.

- Better Auth React client (lib/auth-client.ts) + shared access-control
  roles; API client (lib/api-client.ts) sending credentials cross-origin.
- Designed (auth) route group: login, signup, verify-email, forgot/reset
  password, clinic onboarding, and accept-invite.
- proxy.ts (this Next's renamed middleware) optimistic redirect + an
  authoritative client AppAuthGuard requiring a session and active clinic.
- Real identity in nav-user (useSession + sign out); the unused team
  switcher repurposed as an organization (clinic) switcher; Care-team
  settings tab manages members and invitations.
- lib/patients.ts now calls the org-scoped API (types unchanged); patients
  table and create/edit dialog updated for async create/update.
- next.config: standalone output + Dockerfile for containerized runs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-06-02 21:28:02 +03:00
parent dec150c77d
commit 1ecbd16404
31 changed files with 1853 additions and 286 deletions
+339
View File
@@ -18,6 +18,7 @@
"@xyflow/react": "^12.10.2",
"ai": "^6.0.193",
"ansi-to-react": "^6.2.6",
"better-auth": "^1.6.13",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
@@ -596,6 +597,150 @@
}
}
},
"node_modules/@better-auth/core": {
"version": "1.6.13",
"resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.6.13.tgz",
"integrity": "sha512-3YNjiLUmlNt5T9qQ/weu0tZgGgXDSYax4EE/uLUBIBBGtQI9Q3KdEnO6tfPgDedborcSE1bIspuAIaHpaHwxZQ==",
"license": "MIT",
"dependencies": {
"@opentelemetry/semantic-conventions": "^1.39.0",
"@standard-schema/spec": "^1.1.0",
"zod": "^4.3.6"
},
"peerDependencies": {
"@better-auth/utils": "0.4.1",
"@better-fetch/fetch": "1.1.21",
"@cloudflare/workers-types": ">=4",
"@opentelemetry/api": "^1.9.0",
"better-call": "1.3.5",
"jose": "^6.1.0",
"kysely": "^0.28.5 || ^0.29.0",
"nanostores": "^1.0.1"
},
"peerDependenciesMeta": {
"@cloudflare/workers-types": {
"optional": true
},
"@opentelemetry/api": {
"optional": true
}
}
},
"node_modules/@better-auth/drizzle-adapter": {
"version": "1.6.13",
"resolved": "https://registry.npmjs.org/@better-auth/drizzle-adapter/-/drizzle-adapter-1.6.13.tgz",
"integrity": "sha512-0V6e+e7TnIZZDjhQP/tvAberSrdrf5yfbDSx5oDFsfI5MCh2ATvbuTPNxGWbLdbGnUYfbX4K9FZwzKMj8RpLmg==",
"license": "MIT",
"peerDependencies": {
"@better-auth/core": "^1.6.13",
"@better-auth/utils": "0.4.1",
"drizzle-orm": "^0.45.2"
},
"peerDependenciesMeta": {
"drizzle-orm": {
"optional": true
}
}
},
"node_modules/@better-auth/kysely-adapter": {
"version": "1.6.13",
"resolved": "https://registry.npmjs.org/@better-auth/kysely-adapter/-/kysely-adapter-1.6.13.tgz",
"integrity": "sha512-r+TeBL9dJecuCaSMqL3106qwaXYL3GAkoJDfmtbZ2eZ/Ejr9xVj5msJnSULb0ZqyQ1g5SCbnM39WZaCOFirziQ==",
"license": "MIT",
"peerDependencies": {
"@better-auth/core": "^1.6.13",
"@better-auth/utils": "0.4.1",
"kysely": "^0.28.17 || ^0.29.0"
},
"peerDependenciesMeta": {
"kysely": {
"optional": true
}
}
},
"node_modules/@better-auth/memory-adapter": {
"version": "1.6.13",
"resolved": "https://registry.npmjs.org/@better-auth/memory-adapter/-/memory-adapter-1.6.13.tgz",
"integrity": "sha512-upmNncEwm9Q0MpWLVOdx9Pe3fU/aqobO80zwI+WVCavxmL59SufW5Ud7194/J5ushw4Dd52XNn0XWPJT1ZUThg==",
"license": "MIT",
"peerDependencies": {
"@better-auth/core": "^1.6.13",
"@better-auth/utils": "0.4.1"
}
},
"node_modules/@better-auth/mongo-adapter": {
"version": "1.6.13",
"resolved": "https://registry.npmjs.org/@better-auth/mongo-adapter/-/mongo-adapter-1.6.13.tgz",
"integrity": "sha512-u0g5KThZQInx4QxsaXDJ+Yg5A9z/ia/3EBwi+gI7+kSTKkeT9PZZ6J+erwJ5Sh4d0JUQsEX2DX2YRsg/mYnXWQ==",
"license": "MIT",
"peerDependencies": {
"@better-auth/core": "^1.6.13",
"@better-auth/utils": "0.4.1",
"mongodb": "^6.0.0 || ^7.0.0"
},
"peerDependenciesMeta": {
"mongodb": {
"optional": true
}
}
},
"node_modules/@better-auth/prisma-adapter": {
"version": "1.6.13",
"resolved": "https://registry.npmjs.org/@better-auth/prisma-adapter/-/prisma-adapter-1.6.13.tgz",
"integrity": "sha512-gjmUIdqmxWb4WoNEN5rTQYQli6A9fPopAaVDiLh/gwO3ET10/PuOEwfESePEwUbArlKLLK3hPEWWe0RBojyxgQ==",
"license": "MIT",
"peerDependencies": {
"@better-auth/core": "^1.6.13",
"@better-auth/utils": "0.4.1",
"@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0",
"prisma": "^5.0.0 || ^6.0.0 || ^7.0.0"
},
"peerDependenciesMeta": {
"@prisma/client": {
"optional": true
},
"prisma": {
"optional": true
}
}
},
"node_modules/@better-auth/telemetry": {
"version": "1.6.13",
"resolved": "https://registry.npmjs.org/@better-auth/telemetry/-/telemetry-1.6.13.tgz",
"integrity": "sha512-CXfPPL55mZrGH1FUhZOw9REp2WRJoVjCh9egn+cIx3ReB/OnPz+eHSRft/IVLD2PQyP1FNr1Au89SXd2oPBUPg==",
"license": "MIT",
"peerDependencies": {
"@better-auth/core": "^1.6.13",
"@better-auth/utils": "0.4.1",
"@better-fetch/fetch": "1.1.21"
}
},
"node_modules/@better-auth/utils": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.4.1.tgz",
"integrity": "sha512-SZBPRPF3z0nBvE5ygOkxae35wnnXPRShmqFo78S+qslLeFoPu/pMgnXAuNKFMMybac3tiLaVg1e3MQW5MC+1iA==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "^2.0.1"
}
},
"node_modules/@better-auth/utils/node_modules/@noble/hashes": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz",
"integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==",
"license": "MIT",
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@better-fetch/fetch": {
"version": "1.1.21",
"resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.21.tgz",
"integrity": "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="
},
"node_modules/@braintree/sanitize-url": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz",
@@ -2082,6 +2227,15 @@
"node": ">=8.0.0"
}
},
"node_modules/@opentelemetry/semantic-conventions": {
"version": "1.41.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz",
"integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==",
"license": "Apache-2.0",
"engines": {
"node": ">=14"
}
},
"node_modules/@radix-ui/primitive": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
@@ -4716,6 +4870,155 @@
"node": ">=6.0.0"
}
},
"node_modules/better-auth": {
"version": "1.6.13",
"resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.6.13.tgz",
"integrity": "sha512-jn8ATnGWDzMwpO4a/3iyW1/RayOF/aoPQOfAeqyCVnQCdqkaONVas9CjbY6PovMsTMa/MG+GRABySfzqtj5J/g==",
"license": "MIT",
"dependencies": {
"@better-auth/core": "1.6.13",
"@better-auth/drizzle-adapter": "1.6.13",
"@better-auth/kysely-adapter": "1.6.13",
"@better-auth/memory-adapter": "1.6.13",
"@better-auth/mongo-adapter": "1.6.13",
"@better-auth/prisma-adapter": "1.6.13",
"@better-auth/telemetry": "1.6.13",
"@better-auth/utils": "0.4.1",
"@better-fetch/fetch": "1.1.21",
"@noble/ciphers": "^2.1.1",
"@noble/hashes": "^2.0.1",
"better-call": "1.3.5",
"defu": "^6.1.4",
"jose": "^6.1.3",
"kysely": "^0.28.17 || ^0.29.0",
"nanostores": "^1.1.1",
"zod": "^4.3.6"
},
"peerDependencies": {
"@lynx-js/react": "*",
"@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0",
"@sveltejs/kit": "^2.0.0",
"@tanstack/react-start": "^1.0.0",
"@tanstack/solid-start": "^1.0.0",
"better-sqlite3": "^12.0.0",
"drizzle-kit": ">=0.31.4",
"drizzle-orm": "^0.45.2",
"mongodb": "^6.0.0 || ^7.0.0",
"mysql2": "^3.0.0",
"next": "^14.0.0 || ^15.0.0 || ^16.0.0",
"pg": "^8.0.0",
"prisma": "^5.0.0 || ^6.0.0 || ^7.0.0",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0",
"solid-js": "^1.0.0",
"svelte": "^4.0.0 || ^5.0.0",
"vitest": "^2.0.0 || ^3.0.0 || ^4.0.0",
"vue": "^3.0.0"
},
"peerDependenciesMeta": {
"@lynx-js/react": {
"optional": true
},
"@prisma/client": {
"optional": true
},
"@sveltejs/kit": {
"optional": true
},
"@tanstack/react-start": {
"optional": true
},
"@tanstack/solid-start": {
"optional": true
},
"better-sqlite3": {
"optional": true
},
"drizzle-kit": {
"optional": true
},
"drizzle-orm": {
"optional": true
},
"mongodb": {
"optional": true
},
"mysql2": {
"optional": true
},
"next": {
"optional": true
},
"pg": {
"optional": true
},
"prisma": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
},
"solid-js": {
"optional": true
},
"svelte": {
"optional": true
},
"vitest": {
"optional": true
},
"vue": {
"optional": true
}
}
},
"node_modules/better-auth/node_modules/@noble/ciphers": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.2.0.tgz",
"integrity": "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==",
"license": "MIT",
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/better-auth/node_modules/@noble/hashes": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz",
"integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==",
"license": "MIT",
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/better-call": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/better-call/-/better-call-1.3.5.tgz",
"integrity": "sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA==",
"license": "MIT",
"dependencies": {
"@better-auth/utils": "^0.4.0",
"@better-fetch/fetch": "^1.1.21",
"rou3": "^0.7.12",
"set-cookie-parser": "^3.0.1"
},
"peerDependencies": {
"zod": "^4.0.0"
},
"peerDependenciesMeta": {
"zod": {
"optional": true
}
}
},
"node_modules/body-parser": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
@@ -6005,6 +6308,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/defu": {
"version": "6.1.7",
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz",
"integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==",
"license": "MIT"
},
"node_modules/delaunator": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz",
@@ -8971,6 +9280,15 @@
"node": ">=6"
}
},
"node_modules/kysely": {
"version": "0.29.2",
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.29.2.tgz",
"integrity": "sha512-s6WVJyEZrbm6jhBpiKHsGHyePMrVQKJ85wZCFCr9W4QHv6WTjWIrdvTmO9hDEA3bNK0xkrE2DqrHsXMLWuZpQg==",
"license": "MIT",
"engines": {
"node": ">=22.0.0"
}
},
"node_modules/language-subtag-registry": {
"version": "0.3.23",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
@@ -10678,6 +10996,21 @@
"node": "^18 || >=20"
}
},
"node_modules/nanostores": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/nanostores/-/nanostores-1.3.0.tgz",
"integrity": "sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"engines": {
"node": "^20.0.0 || >=22.0.0"
}
},
"node_modules/napi-postinstall": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
@@ -12170,6 +12503,12 @@
"integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==",
"license": "Unlicense"
},
"node_modules/rou3": {
"version": "0.7.12",
"resolved": "https://registry.npmjs.org/rou3/-/rou3-0.7.12.tgz",
"integrity": "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==",
"license": "MIT"
},
"node_modules/roughjs": {
"version": "4.6.6",
"resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz",