refactor(web): wire @bytelyst/react-auth into auth-context, clean platform-sync auth plumbing

This commit is contained in:
saravanakumardb1 2026-02-28 11:28:14 -08:00
parent bde5cb792d
commit 8a5a40676a
4 changed files with 99 additions and 358 deletions

172
web/package-lock.json generated
View File

@ -8,7 +8,9 @@
"name": "web",
"version": "0.1.0",
"dependencies": {
"@bytelyst/api-client": "file:../../learning_ai_common_plat/packages/api-client",
"@bytelyst/auth-client": "file:../../learning_ai_common_plat/packages/auth-client",
"@bytelyst/react-auth": "file:../../learning_ai_common_plat/packages/react-auth",
"@bytelyst/telemetry-client": "file:../../learning_ai_common_plat/packages/telemetry-client",
"@serwist/next": "^9.5.6",
"date-fns": "^4.1.0",
@ -40,10 +42,32 @@
"vitest": "^4.0.18"
}
},
"../../learning_ai_common_plat/packages/api-client": {
"name": "@bytelyst/api-client",
"version": "0.1.0"
},
"../../learning_ai_common_plat/packages/auth-client": {
"name": "@bytelyst/auth-client",
"version": "0.1.0"
},
"../../learning_ai_common_plat/packages/react-auth": {
"name": "@bytelyst/react-auth",
"version": "0.1.0",
"dependencies": {
"@bytelyst/api-client": "workspace:*"
},
"devDependencies": {
"@testing-library/react": "^16.3.2",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"happy-dom": "^18.0.1",
"react": "^19.2.4",
"react-dom": "^19.2.4"
},
"peerDependencies": {
"react": ">=18.0.0"
}
},
"../../learning_ai_common_plat/packages/telemetry-client": {
"name": "@bytelyst/telemetry-client",
"version": "0.1.0"
@ -164,7 +188,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@ -397,10 +420,18 @@
"specificity": "bin/cli.js"
}
},
"node_modules/@bytelyst/api-client": {
"resolved": "../../learning_ai_common_plat/packages/api-client",
"link": true
},
"node_modules/@bytelyst/auth-client": {
"resolved": "../../learning_ai_common_plat/packages/auth-client",
"link": true
},
"node_modules/@bytelyst/react-auth": {
"resolved": "../../learning_ai_common_plat/packages/react-auth",
"link": true
},
"node_modules/@bytelyst/telemetry-client": {
"resolved": "../../learning_ai_common_plat/packages/telemetry-client",
"link": true
@ -493,7 +524,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=20.19.0"
},
@ -534,7 +564,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=20.19.0"
}
@ -1980,9 +2009,8 @@
"version": "1.58.2",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/@playwright/test/-/test-1.58.2.tgz",
"integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
"devOptional": true,
"dev": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"playwright": "1.58.2"
},
@ -2809,36 +2837,6 @@
"tailwindcss": "4.2.1"
}
},
"node_modules/@testing-library/dom": {
"version": "10.4.1",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/@testing-library/dom/-/dom-10.4.1.tgz",
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
"@types/aria-query": "^5.0.1",
"aria-query": "5.3.0",
"dom-accessibility-api": "^0.5.9",
"lz-string": "^1.5.0",
"picocolors": "1.1.1",
"pretty-format": "^27.0.2"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@testing-library/dom/node_modules/aria-query": {
"version": "5.3.0",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/aria-query/-/aria-query-5.3.0.tgz",
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"dequal": "^2.0.3"
}
},
"node_modules/@testing-library/jest-dom": {
"version": "6.9.1",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz",
@ -2905,13 +2903,6 @@
"tslib": "^2.4.0"
}
},
"node_modules/@types/aria-query": {
"version": "5.0.4",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/@types/aria-query/-/aria-query-5.0.4.tgz",
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/chai": {
"version": "5.2.3",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/@types/chai/-/chai-5.2.3.tgz",
@ -3020,7 +3011,6 @@
"integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
@ -3029,9 +3019,8 @@
"version": "19.2.14",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"devOptional": true,
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@ -3042,7 +3031,6 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"dev": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@ -3111,7 +3099,6 @@
"integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.56.1",
"@typescript-eslint/types": "8.56.1",
@ -3748,7 +3735,6 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@ -4128,7 +4114,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@ -4371,7 +4356,7 @@
"version": "3.2.3",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"devOptional": true,
"dev": true,
"license": "MIT"
},
"node_modules/d3-array": {
@ -4654,16 +4639,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/detect-libc/-/detect-libc-2.1.2.tgz",
@ -4687,13 +4662,6 @@
"node": ">=0.10.0"
}
},
"node_modules/dom-accessibility-api": {
"version": "0.5.16",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"dev": true,
"license": "MIT"
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/dunder-proto/-/dunder-proto-1.0.1.tgz",
@ -5018,7 +4986,6 @@
"integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@ -5204,7 +5171,6 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@ -6633,7 +6599,6 @@
"integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@acemir/cssom": "^0.9.31",
"@asamuzakjp/dom-selector": "^6.8.1",
@ -7104,16 +7069,6 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/lz-string": {
"version": "1.5.0",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/lz-string/-/lz-string-1.5.0.tgz",
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"dev": true,
"license": "MIT",
"bin": {
"lz-string": "bin/bin.js"
}
},
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/magic-string/-/magic-string-0.30.21.tgz",
@ -7260,7 +7215,6 @@
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/next/-/next-16.1.6.tgz",
"integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@next/env": "16.1.6",
"@swc/helpers": "0.5.15",
@ -7674,7 +7628,7 @@
"version": "1.58.2",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/playwright/-/playwright-1.58.2.tgz",
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
"devOptional": true,
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.58.2"
@ -7693,7 +7647,7 @@
"version": "1.58.2",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/playwright-core/-/playwright-core-1.58.2.tgz",
"integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
"devOptional": true,
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
@ -7778,41 +7732,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pretty-format": {
"version": "27.5.1",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/pretty-format/-/pretty-format-27.5.1.tgz",
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
"react-is": "^17.0.1"
},
"engines": {
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
}
},
"node_modules/pretty-format/node_modules/ansi-styles": {
"version": "5.2.0",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/pretty-format/node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true,
"license": "MIT"
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/prop-types/-/prop-types-15.8.1.tgz",
@ -7860,7 +7779,6 @@
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/react/-/react-19.2.3.tgz",
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@ -7870,7 +7788,6 @@
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@ -7882,15 +7799,14 @@
"version": "16.13.1",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT",
"peer": true
"dev": true,
"license": "MIT"
},
"node_modules/react-redux": {
"version": "9.2.0",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
@ -7957,8 +7873,7 @@
"version": "5.0.1",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/redux-thunk": {
"version": "3.1.0",
@ -8938,7 +8853,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@ -9155,9 +9069,8 @@
"version": "5.9.3",
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -9351,7 +9264,6 @@
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@ -9445,7 +9357,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@ -9844,7 +9755,6 @@
"resolved": "https://jfrog-pkg-proxy.it.att.com/artifactory/api/npm/att-npm-proxy-group/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}

View File

@ -14,7 +14,9 @@
"test:e2e:ui": "playwright test --ui"
},
"dependencies": {
"@bytelyst/api-client": "file:../../learning_ai_common_plat/packages/api-client",
"@bytelyst/auth-client": "file:../../learning_ai_common_plat/packages/auth-client",
"@bytelyst/react-auth": "file:../../learning_ai_common_plat/packages/react-auth",
"@bytelyst/telemetry-client": "file:../../learning_ai_common_plat/packages/telemetry-client",
"@serwist/next": "^9.5.6",
"date-fns": "^4.1.0",

View File

@ -1,211 +1,73 @@
// ── Auth Context ──────────────────────────────────────────────
// Provides authentication state and actions for ChronoMind web.
// Calls platform-service /auth/* endpoints for login/register/me.
// Delegates to @bytelyst/react-auth shared package.
'use client';
import { createContext, useContext, useCallback, useEffect, useState, type ReactNode } from 'react';
import {
loginUser,
registerUser,
getMe,
setAuthToken,
setRefreshToken,
setSyncEnabled,
isAuthenticated as checkAuth,
refreshAccessToken,
forgotPassword as apiForgotPassword,
resetPassword as apiResetPassword,
changePassword as apiChangePassword,
deleteAccount as apiDeleteAccount,
type AuthUser,
} from './platform-sync';
import { createAuthProvider } from '@bytelyst/react-auth';
import { setSyncEnabled } from './platform-sync';
import { PRODUCT_ID, getAuthClient } from './auth-api';
interface AuthState {
user: AuthUser | null;
isLoading: boolean;
isAuthenticated: boolean;
error: string | null;
interface ChronoMindUser {
id: string;
email: string;
name: string;
displayName: string;
role: string;
plan: string;
[key: string]: unknown;
}
interface AuthActions {
login: (email: string, password: string) => Promise<boolean>;
register: (email: string, password: string, displayName: string) => Promise<boolean>;
logout: () => void;
clearError: () => void;
forgotPassword: (email: string) => Promise<boolean>;
resetPassword: (token: string, newPassword: string) => Promise<boolean>;
changePassword: (currentPassword: string, newPassword: string) => Promise<boolean>;
deleteAccount: (password: string) => Promise<boolean>;
successMessage: string | null;
function getBaseUrl(): string {
if (typeof window !== 'undefined' && (window as unknown as Record<string, unknown>).__PLATFORM_URL__) {
return (window as unknown as Record<string, unknown>).__PLATFORM_URL__ as string;
}
return process.env.NEXT_PUBLIC_PLATFORM_SERVICE_URL ?? 'https://api.chronomind.app';
}
type AuthContextValue = AuthState & AuthActions;
const AuthContext = createContext<AuthContextValue | null>(null);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<AuthUser | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
// Hydrate user from stored token on mount
useEffect(() => {
if (!checkAuth()) {
setIsLoading(false);
return;
}
getMe()
.then((u) => setUser(u))
.catch(() => {
setAuthToken(null);
})
.finally(() => setIsLoading(false));
}, []);
// Auto-refresh token every 45 minutes
useEffect(() => {
if (!user) return;
const REFRESH_INTERVAL = 45 * 60 * 1000;
const timer = setInterval(async () => {
const refreshed = await refreshAccessToken();
if (!refreshed) {
setUser(null);
}
}, REFRESH_INTERVAL);
return () => clearInterval(timer);
}, [user]);
const login = useCallback(async (email: string, password: string): Promise<boolean> => {
setError(null);
try {
const result = await loginUser(email, password);
setAuthToken(result.accessToken);
setRefreshToken(result.refreshToken);
const { AuthProvider: _AuthProvider, useAuth: _useAuth } = createAuthProvider<ChronoMindUser>({
baseUrl: getBaseUrl(),
storagePrefix: 'chronomind',
loginEndpoint: '/auth/login',
registerEndpoint: '/auth/register',
forgotPasswordEndpoint: '/auth/forgot-password',
changePasswordEndpoint: '/auth/change-password',
deleteAccountEndpoint: '/auth/account',
refreshEndpoint: '/auth/refresh',
mapLoginResponse: (data: unknown) => {
const d = data as { user: { id: string; email: string; displayName: string; role: string; plan: string }; accessToken: string; refreshToken: string };
setSyncEnabled(true);
setUser(result.user);
return true;
} catch (err) {
setError(err instanceof Error ? err.message : 'Login failed');
return false;
}
}, []);
return {
user: { id: d.user.id, email: d.user.email, name: d.user.displayName, displayName: d.user.displayName, role: d.user.role, plan: d.user.plan },
accessToken: d.accessToken,
refreshToken: d.refreshToken,
};
},
onLogout: () => setSyncEnabled(false),
});
const register = useCallback(
async (email: string, password: string, displayName: string): Promise<boolean> => {
setError(null);
export const AuthProvider = _AuthProvider;
/**
* Wrapper around shared useAuth that adapts naming for backward compat:
* - success successMessage
* - clearMessages clearError
*/
export function useAuth() {
const ctx = _useAuth();
return {
...ctx,
successMessage: ctx.success,
clearError: ctx.clearMessages,
resetPassword: async (token: string, newPassword: string): Promise<boolean> => {
try {
const result = await registerUser(email, password, displayName);
setAuthToken(result.accessToken);
setRefreshToken(result.refreshToken);
setSyncEnabled(true);
setUser(result.user);
await getAuthClient().resetPassword(token, newPassword);
return true;
} catch (err) {
setError(err instanceof Error ? err.message : 'Registration failed');
} catch {
return false;
}
},
[]
);
const logout = useCallback(() => {
setAuthToken(null);
setSyncEnabled(false);
setUser(null);
}, []);
const clearError = useCallback(() => {
setError(null);
setSuccessMessage(null);
}, []);
const forgotPassword = useCallback(async (email: string): Promise<boolean> => {
setError(null);
setSuccessMessage(null);
try {
const result = await apiForgotPassword(email);
setSuccessMessage(result.message);
return true;
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to send reset email');
return false;
}
}, []);
const resetPassword = useCallback(async (token: string, newPassword: string): Promise<boolean> => {
setError(null);
setSuccessMessage(null);
try {
const result = await apiResetPassword(token, newPassword);
setSuccessMessage(result.message);
return true;
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to reset password');
return false;
}
}, []);
const changePassword = useCallback(
async (currentPassword: string, newPassword: string): Promise<boolean> => {
setError(null);
setSuccessMessage(null);
try {
const result = await apiChangePassword(currentPassword, newPassword);
setSuccessMessage(result.message);
return true;
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to change password');
return false;
}
},
[]
);
const deleteAccount = useCallback(
async (password: string): Promise<boolean> => {
setError(null);
try {
await apiDeleteAccount(password);
setAuthToken(null);
setSyncEnabled(false);
setUser(null);
setSuccessMessage('Account deleted successfully.');
return true;
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to delete account');
return false;
}
},
[]
);
return (
<AuthContext.Provider
value={{
user,
isLoading,
isAuthenticated: user !== null,
error,
successMessage,
login,
register,
logout,
clearError,
forgotPassword,
resetPassword,
changePassword,
deleteAccount,
}}
>
{children}
</AuthContext.Provider>
);
};
}
export function useAuth(): AuthContextValue {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
return ctx;
}
export type { ChronoMindUser as AuthUser };

View File

@ -4,7 +4,6 @@
import type { Timer } from './timer-engine';
import { getAuthClient, PRODUCT_ID as _PRODUCT_ID } from './auth-api';
import type { AuthUser, AuthResult } from '@bytelyst/auth-client';
// ── DTOs ──────────────────────────────────────────────────────
@ -223,45 +222,13 @@ function setLastSyncDate(date: string): void {
}
// ── Auth Operations (delegated to @bytelyst/auth-client) ─────
export type { AuthUser, AuthResult };
export async function loginUser(email: string, password: string): Promise<AuthResult> {
return getAuthClient().login(email, password);
}
export async function registerUser(email: string, password: string, displayName: string): Promise<AuthResult> {
return getAuthClient().register(email, password, displayName);
}
export async function getMe(): Promise<AuthUser> {
return getAuthClient().getMe();
}
export async function forgotPassword(email: string): Promise<{ message: string }> {
return getAuthClient().forgotPassword(email);
}
export async function resetPassword(token: string, newPassword: string): Promise<{ message: string }> {
return getAuthClient().resetPassword(token, newPassword);
}
export async function changePassword(currentPassword: string, newPassword: string): Promise<{ message: string }> {
return getAuthClient().changePassword(currentPassword, newPassword);
}
// Most auth operations are now handled by @bytelyst/react-auth in auth-context.tsx.
// Only verifyEmail is still used directly by the verify-email page.
export async function verifyEmail(token: string): Promise<{ message: string }> {
return getAuthClient().verifyEmail(token);
}
export async function resendVerification(email: string): Promise<{ message: string }> {
return getAuthClient().resendVerification(email);
}
export async function deleteAccount(password: string): Promise<{ message: string }> {
return getAuthClient().deleteAccount(password);
}
// ── Sync Operations ───────────────────────────────────────────
export async function pullDelta(since?: string): Promise<SyncTimerDTO[]> {