create-nexst-monorepo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (303) hide show
  1. package/dist/index.js +1727 -0
  2. package/package.json +37 -0
  3. package/template/.agents/plugins/marketplace.json +21 -0
  4. package/template/.claude/settings.json +6 -0
  5. package/template/.claude/skills/frontend-design/LICENSE.txt +177 -0
  6. package/template/.claude/skills/frontend-design/SKILL.md +154 -0
  7. package/template/.claude/skills/theme-factory/SKILL.md +95 -0
  8. package/template/.claude/skills/theme-factory/references/schema.md +104 -0
  9. package/template/.claude/skills/theme-factory/scripts/theme.mjs +261 -0
  10. package/template/.dockerignore +43 -0
  11. package/template/.editorconfig +10 -0
  12. package/template/.github/workflows/ci.yaml +289 -0
  13. package/template/.github/workflows/publish-app.yaml +66 -0
  14. package/template/.github/workflows/publish.yaml +148 -0
  15. package/template/.github/workflows/snyk.yaml +34 -0
  16. package/template/.vscode/settings.json +8 -0
  17. package/template/AGENTS.md +42 -0
  18. package/template/Dockerfile.base +9 -0
  19. package/template/README.md +54 -0
  20. package/template/_gitignore +10 -0
  21. package/template/apps/api/.env.example +32 -0
  22. package/template/apps/api/AGENTS.md +44 -0
  23. package/template/apps/api/Dockerfile +69 -0
  24. package/template/apps/api/_gitignore +8 -0
  25. package/template/apps/api/knip.config.ts +22 -0
  26. package/template/apps/api/kustomize/base/application.properties +16 -0
  27. package/template/apps/api/kustomize/base/deployment.yaml +94 -0
  28. package/template/apps/api/kustomize/base/kustomization.yaml +17 -0
  29. package/template/apps/api/kustomize/base/service.yaml +10 -0
  30. package/template/apps/api/kustomize/overlays/prod/kustomization.yaml +17 -0
  31. package/template/apps/api/kustomize/overlays/staging/application.properties +1 -0
  32. package/template/apps/api/kustomize/overlays/staging/kustomization.yaml +21 -0
  33. package/template/apps/api/kustomize/secret.keys +14 -0
  34. package/template/apps/api/oxfmt.config.ts +8 -0
  35. package/template/apps/api/oxlint.config.ts +107 -0
  36. package/template/apps/api/package.json +79 -0
  37. package/template/apps/api/prisma/migrations/20260927000000_init/migration.sql +113 -0
  38. package/template/apps/api/prisma/migrations/migration_lock.toml +3 -0
  39. package/template/apps/api/prisma/schema.prisma +113 -0
  40. package/template/apps/api/prisma.config.ts +7 -0
  41. package/template/apps/api/scripts/auth.cli.ts +19 -0
  42. package/template/apps/api/scripts/seal-secret.sh +171 -0
  43. package/template/apps/api/sonar-project.properties +9 -0
  44. package/template/apps/api/src/app-info/app-info.dto.ts +10 -0
  45. package/template/apps/api/src/app-info/app-info.module.ts +7 -0
  46. package/template/apps/api/src/app-info/app-info.resolver.spec.ts +14 -0
  47. package/template/apps/api/src/app-info/app-info.resolver.ts +21 -0
  48. package/template/apps/api/src/app.module.spec.ts +93 -0
  49. package/template/apps/api/src/app.module.ts +77 -0
  50. package/template/apps/api/src/auth/auth.guard.spec.ts +90 -0
  51. package/template/apps/api/src/auth/auth.guard.ts +97 -0
  52. package/template/apps/api/src/auth/auth.ts +129 -0
  53. package/template/apps/api/src/auth/decorator/allow-anonymous.decorator.ts +17 -0
  54. package/template/apps/api/src/auth/decorator/is-email-verified.decorator.spec.ts +41 -0
  55. package/template/apps/api/src/auth/decorator/is-email-verified.decorator.ts +20 -0
  56. package/template/apps/api/src/auth/decorator/roles.decorator.ts +27 -0
  57. package/template/apps/api/src/auth/testing/make-session.ts +31 -0
  58. package/template/apps/api/src/auth/types/auth.types.ts +27 -0
  59. package/template/apps/api/src/bootstrap/register-graphql-enums.ts +12 -0
  60. package/template/apps/api/src/bootstrap/security.ts +24 -0
  61. package/template/apps/api/src/bootstrap/swagger.ts +28 -0
  62. package/template/apps/api/src/config/app.config.spec.ts +22 -0
  63. package/template/apps/api/src/config/app.config.ts +122 -0
  64. package/template/apps/api/src/config/graphql-depth-limit.ts +103 -0
  65. package/template/apps/api/src/config/graphql.config.ts +77 -0
  66. package/template/apps/api/src/config/resources/app.yaml +29 -0
  67. package/template/apps/api/src/errors/app-parse-uuid.pipe.ts +27 -0
  68. package/template/apps/api/src/errors/app.exception.ts +47 -0
  69. package/template/apps/api/src/errors/error-code.enum.ts +54 -0
  70. package/template/apps/api/src/errors/http-status-fallback-code.util.ts +27 -0
  71. package/template/apps/api/src/errors/validation-exception.factory.ts +43 -0
  72. package/template/apps/api/src/health/health.controller.spec.ts +82 -0
  73. package/template/apps/api/src/health/health.controller.ts +57 -0
  74. package/template/apps/api/src/health/health.module.ts +12 -0
  75. package/template/apps/api/src/logging/context-logger.ts +18 -0
  76. package/template/apps/api/src/logging/logger.config.ts +23 -0
  77. package/template/apps/api/src/logging/logging.module.ts +22 -0
  78. package/template/apps/api/src/mail/mail.module.ts +12 -0
  79. package/template/apps/api/src/mail/mail.service.spec.ts +47 -0
  80. package/template/apps/api/src/mail/mail.service.ts +123 -0
  81. package/template/apps/api/src/main.ts +41 -0
  82. package/template/apps/api/src/prisma/prisma.module.ts +12 -0
  83. package/template/apps/api/src/prisma/prisma.service.spec.ts +136 -0
  84. package/template/apps/api/src/prisma/prisma.service.ts +138 -0
  85. package/template/apps/api/src/queue/dto/enqueue-example.input.ts +11 -0
  86. package/template/apps/api/src/queue/example-queue.resolver.ts +14 -0
  87. package/template/apps/api/src/queue/example-queue.service.spec.ts +21 -0
  88. package/template/apps/api/src/queue/example-queue.service.ts +32 -0
  89. package/template/apps/api/src/queue/queue.module.ts +27 -0
  90. package/template/apps/api/src/r2/r2.module.ts +12 -0
  91. package/template/apps/api/src/r2/r2.service.spec.ts +117 -0
  92. package/template/apps/api/src/r2/r2.service.ts +131 -0
  93. package/template/apps/api/src/user/dto/user.dto.ts +26 -0
  94. package/template/apps/api/src/user/user.module.ts +10 -0
  95. package/template/apps/api/src/user/user.resolver.spec.ts +18 -0
  96. package/template/apps/api/src/user/user.resolver.ts +24 -0
  97. package/template/apps/api/src/utils/catch-everything-exception.filter.spec.ts +292 -0
  98. package/template/apps/api/src/utils/catch-everything-exception.filter.ts +70 -0
  99. package/template/apps/api/src/utils/error-handler.util.spec.ts +20 -0
  100. package/template/apps/api/src/utils/error-handler.util.ts +12 -0
  101. package/template/apps/api/src/utils/escape-like-pattern.util.spec.ts +29 -0
  102. package/template/apps/api/src/utils/escape-like-pattern.util.ts +11 -0
  103. package/template/apps/api/src/utils/pagination.input.ts +20 -0
  104. package/template/apps/api/src/utils/prisma-exception.filter.spec.ts +186 -0
  105. package/template/apps/api/src/utils/prisma-exception.filter.ts +45 -0
  106. package/template/apps/api/src/utils/prisma-graphql-exception.filter.spec.ts +216 -0
  107. package/template/apps/api/src/utils/prisma-graphql-exception.filter.ts +43 -0
  108. package/template/apps/api/tests/app.e2e-spec.ts +35 -0
  109. package/template/apps/api/tests/auth.e2e-spec.ts +38 -0
  110. package/template/apps/api/tests/helpers/api.ts +27 -0
  111. package/template/apps/api/tsconfig.json +11 -0
  112. package/template/apps/api/vitest.config.e2e.ts +16 -0
  113. package/template/apps/api/vitest.config.ts +40 -0
  114. package/template/apps/web/.env.example +7 -0
  115. package/template/apps/web/.storybook/decorators.tsx +38 -0
  116. package/template/apps/web/.storybook/main.ts +17 -0
  117. package/template/apps/web/.storybook/preview.ts +38 -0
  118. package/template/apps/web/.storybook/vitest.setup.ts +5 -0
  119. package/template/apps/web/AGENTS.md +29 -0
  120. package/template/apps/web/Dockerfile +49 -0
  121. package/template/apps/web/_gitignore +16 -0
  122. package/template/apps/web/knip.config.ts +15 -0
  123. package/template/apps/web/kustomize/base/application.properties +5 -0
  124. package/template/apps/web/kustomize/base/deployment.yaml +60 -0
  125. package/template/apps/web/kustomize/base/kustomization.yaml +17 -0
  126. package/template/apps/web/kustomize/base/service.yaml +10 -0
  127. package/template/apps/web/kustomize/overlays/prod/kustomization.yaml +12 -0
  128. package/template/apps/web/kustomize/overlays/staging/application.properties +2 -0
  129. package/template/apps/web/kustomize/overlays/staging/kustomization.yaml +18 -0
  130. package/template/apps/web/messages/de.json +124 -0
  131. package/template/apps/web/messages/en.json +124 -0
  132. package/template/apps/web/next-env.d.ts +7 -0
  133. package/template/apps/web/next.config.ts +39 -0
  134. package/template/apps/web/oxfmt.config.ts +9 -0
  135. package/template/apps/web/oxlint.config.ts +36 -0
  136. package/template/apps/web/package.json +80 -0
  137. package/template/apps/web/playwright.config.ts +34 -0
  138. package/template/apps/web/postcss.config.mjs +7 -0
  139. package/template/apps/web/sonar-project.properties +9 -0
  140. package/template/apps/web/src/app/[locale]/(auth)/recovery/page.tsx +22 -0
  141. package/template/apps/web/src/app/[locale]/(auth)/reset-password/page.tsx +29 -0
  142. package/template/apps/web/src/app/[locale]/(auth)/sign-in/page.tsx +24 -0
  143. package/template/apps/web/src/app/[locale]/(auth)/sign-up/page.tsx +24 -0
  144. package/template/apps/web/src/app/[locale]/(auth)/verify/page.tsx +24 -0
  145. package/template/apps/web/src/app/[locale]/dashboard/page.tsx +34 -0
  146. package/template/apps/web/src/app/[locale]/layout.tsx +60 -0
  147. package/template/apps/web/src/app/[locale]/page.tsx +23 -0
  148. package/template/apps/web/src/app/api/health/route.ts +3 -0
  149. package/template/apps/web/src/app/global-error.tsx +19 -0
  150. package/template/apps/web/src/app/globals.css +98 -0
  151. package/template/apps/web/src/app/icon.svg +1 -0
  152. package/template/apps/web/src/app/robots.ts +15 -0
  153. package/template/apps/web/src/app/sitemap.ts +24 -0
  154. package/template/apps/web/src/components/alert/alert.stories.tsx +25 -0
  155. package/template/apps/web/src/components/alert/alert.tsx +50 -0
  156. package/template/apps/web/src/components/avatar/avatar.stories.tsx +116 -0
  157. package/template/apps/web/src/components/avatar/avatar.tsx +88 -0
  158. package/template/apps/web/src/components/button/button-variants.ts +28 -0
  159. package/template/apps/web/src/components/button/button.stories.tsx +33 -0
  160. package/template/apps/web/src/components/button/button.tsx +32 -0
  161. package/template/apps/web/src/components/checkbox/checkbox.stories.tsx +312 -0
  162. package/template/apps/web/src/components/checkbox/checkbox.tsx +38 -0
  163. package/template/apps/web/src/components/dialog/danger-panel.stories.tsx +41 -0
  164. package/template/apps/web/src/components/dialog/danger-panel.tsx +32 -0
  165. package/template/apps/web/src/components/dialog/dialog.stories.tsx +239 -0
  166. package/template/apps/web/src/components/dialog/dialog.tsx +136 -0
  167. package/template/apps/web/src/components/dialog-buttons.stories.tsx +55 -0
  168. package/template/apps/web/src/components/dialog-buttons.tsx +48 -0
  169. package/template/apps/web/src/components/drawer/drawer.stories.tsx +251 -0
  170. package/template/apps/web/src/components/drawer/drawer.tsx +120 -0
  171. package/template/apps/web/src/components/dropdown-menu/dropdown-menu.stories.tsx +329 -0
  172. package/template/apps/web/src/components/dropdown-menu/dropdown-menu.tsx +263 -0
  173. package/template/apps/web/src/components/empty-state.stories.tsx +49 -0
  174. package/template/apps/web/src/components/empty-state.tsx +33 -0
  175. package/template/apps/web/src/components/field-group.stories.tsx +47 -0
  176. package/template/apps/web/src/components/field-group.tsx +20 -0
  177. package/template/apps/web/src/components/field-label.stories.tsx +34 -0
  178. package/template/apps/web/src/components/field-label.tsx +28 -0
  179. package/template/apps/web/src/components/icons.tsx +25 -0
  180. package/template/apps/web/src/components/input/input-otp.stories.tsx +86 -0
  181. package/template/apps/web/src/components/input/input-otp.tsx +184 -0
  182. package/template/apps/web/src/components/input/input.stories.tsx +35 -0
  183. package/template/apps/web/src/components/input/input.tsx +180 -0
  184. package/template/apps/web/src/components/popover/popover.stories.tsx +479 -0
  185. package/template/apps/web/src/components/popover/popover.tsx +45 -0
  186. package/template/apps/web/src/components/responsive-dialog/responsive-dialog.stories.tsx +307 -0
  187. package/template/apps/web/src/components/responsive-dialog/responsive-dialog.tsx +96 -0
  188. package/template/apps/web/src/components/searchbar/searchbar.stories.tsx +59 -0
  189. package/template/apps/web/src/components/searchbar/searchbar.tsx +64 -0
  190. package/template/apps/web/src/components/select/select.stories.tsx +293 -0
  191. package/template/apps/web/src/components/select/select.tsx +445 -0
  192. package/template/apps/web/src/components/skeleton/skeleton.stories.tsx +76 -0
  193. package/template/apps/web/src/components/skeleton/skeleton.tsx +13 -0
  194. package/template/apps/web/src/components/spinner/spinner.tsx +18 -0
  195. package/template/apps/web/src/components/switch/switch.stories.tsx +71 -0
  196. package/template/apps/web/src/components/switch/switch.tsx +35 -0
  197. package/template/apps/web/src/components/tabs/tabs.stories.tsx +209 -0
  198. package/template/apps/web/src/components/tabs/tabs.tsx +69 -0
  199. package/template/apps/web/src/components/tabs/url-tabs.stories.tsx +68 -0
  200. package/template/apps/web/src/components/tabs/url-tabs.tsx +42 -0
  201. package/template/apps/web/src/components/textarea/textarea.stories.tsx +144 -0
  202. package/template/apps/web/src/components/textarea/textarea.tsx +115 -0
  203. package/template/apps/web/src/components/toast/toaster.tsx +38 -0
  204. package/template/apps/web/src/components/tooltip/tooltip.stories.tsx +237 -0
  205. package/template/apps/web/src/components/tooltip/tooltip.tsx +49 -0
  206. package/template/apps/web/src/config/csp.ts +59 -0
  207. package/template/apps/web/src/config/routes.ts +14 -0
  208. package/template/apps/web/src/config/site.ts +5 -0
  209. package/template/apps/web/src/env.ts +15 -0
  210. package/template/apps/web/src/features/auth/auth-card.tsx +20 -0
  211. package/template/apps/web/src/features/auth/auth-divider.tsx +14 -0
  212. package/template/apps/web/src/features/auth/google-auth-button.tsx +49 -0
  213. package/template/apps/web/src/features/auth/google-one-tap.tsx +23 -0
  214. package/template/apps/web/src/features/auth/lib/auth-error.test.ts +22 -0
  215. package/template/apps/web/src/features/auth/lib/auth-error.ts +16 -0
  216. package/template/apps/web/src/features/auth/lib/return-to.test.ts +41 -0
  217. package/template/apps/web/src/features/auth/lib/return-to.ts +19 -0
  218. package/template/apps/web/src/features/auth/lib/validator.test.ts +45 -0
  219. package/template/apps/web/src/features/auth/lib/validator.ts +49 -0
  220. package/template/apps/web/src/features/auth/passkey-settings.tsx +106 -0
  221. package/template/apps/web/src/features/auth/recovery-form.tsx +61 -0
  222. package/template/apps/web/src/features/auth/resend-verification-button.tsx +36 -0
  223. package/template/apps/web/src/features/auth/reset-password-form.tsx +62 -0
  224. package/template/apps/web/src/features/auth/sign-in-form.stories.tsx +22 -0
  225. package/template/apps/web/src/features/auth/sign-in-form.tsx +116 -0
  226. package/template/apps/web/src/features/auth/sign-out-button.tsx +26 -0
  227. package/template/apps/web/src/features/auth/sign-up-form.tsx +107 -0
  228. package/template/apps/web/src/features/shared/provider.tsx +16 -0
  229. package/template/apps/web/src/features/shared/site-header.tsx +48 -0
  230. package/template/apps/web/src/features/shared/theme-toggle.tsx +24 -0
  231. package/template/apps/web/src/i18n/navigation.ts +5 -0
  232. package/template/apps/web/src/i18n/request.ts +21 -0
  233. package/template/apps/web/src/i18n/routing.ts +6 -0
  234. package/template/apps/web/src/lib/auth-client.ts +19 -0
  235. package/template/apps/web/src/lib/auth-server.ts +46 -0
  236. package/template/apps/web/src/lib/auth.ts +8 -0
  237. package/template/apps/web/src/lib/codegen.ts +30 -0
  238. package/template/apps/web/src/lib/graphql-client.ts +29 -0
  239. package/template/apps/web/src/lib/graphql-server.ts +28 -0
  240. package/template/apps/web/src/lib/graphql-utils.test.ts +32 -0
  241. package/template/apps/web/src/lib/graphql-utils.ts +76 -0
  242. package/template/apps/web/src/lib/hooks/use-media-query.ts +21 -0
  243. package/template/apps/web/src/lib/metadata.ts +17 -0
  244. package/template/apps/web/src/lib/network-error.test.ts +30 -0
  245. package/template/apps/web/src/lib/network-error.ts +17 -0
  246. package/template/apps/web/src/lib/proxy-utils.ts +42 -0
  247. package/template/apps/web/src/lib/query-client.ts +29 -0
  248. package/template/apps/web/src/lib/utils.ts +5 -0
  249. package/template/apps/web/src/proxy.ts +57 -0
  250. package/template/apps/web/test-results/.last-run.json +4 -0
  251. package/template/apps/web/tests/auth.spec.ts +17 -0
  252. package/template/apps/web/tests/csp-theme-script.spec.ts +37 -0
  253. package/template/apps/web/tests/home.spec.ts +25 -0
  254. package/template/apps/web/tsconfig.build.json +4 -0
  255. package/template/apps/web/tsconfig.json +28 -0
  256. package/template/apps/web/vitest.config.ts +40 -0
  257. package/template/apps/web/vitest.shims.d.ts +1 -0
  258. package/template/apps/worker/.env.example +4 -0
  259. package/template/apps/worker/AGENTS.md +11 -0
  260. package/template/apps/worker/Dockerfile +35 -0
  261. package/template/apps/worker/knip.config.ts +3 -0
  262. package/template/apps/worker/kustomize/base/application.properties +4 -0
  263. package/template/apps/worker/kustomize/base/deployment.yaml +62 -0
  264. package/template/apps/worker/kustomize/base/kustomization.yaml +16 -0
  265. package/template/apps/worker/kustomize/overlays/prod/kustomization.yaml +12 -0
  266. package/template/apps/worker/kustomize/overlays/staging/application.properties +1 -0
  267. package/template/apps/worker/kustomize/overlays/staging/kustomization.yaml +18 -0
  268. package/template/apps/worker/oxfmt.config.ts +8 -0
  269. package/template/apps/worker/oxlint.config.ts +15 -0
  270. package/template/apps/worker/package.json +33 -0
  271. package/template/apps/worker/sonar-project.properties +8 -0
  272. package/template/apps/worker/src/env.ts +13 -0
  273. package/template/apps/worker/src/lib/graceful-shutdown.ts +27 -0
  274. package/template/apps/worker/src/lib/logger.ts +20 -0
  275. package/template/apps/worker/src/main.ts +17 -0
  276. package/template/apps/worker/src/processors/example-processor.ts +15 -0
  277. package/template/apps/worker/src/worker/redis.ts +19 -0
  278. package/template/apps/worker/src/worker/worker.ts +37 -0
  279. package/template/apps/worker/tests/example-processor.test.ts +13 -0
  280. package/template/apps/worker/tests/graceful-shutdown.test.ts +34 -0
  281. package/template/apps/worker/tsconfig.json +4 -0
  282. package/template/apps/worker/vitest.config.ts +13 -0
  283. package/template/bun.lock +3053 -0
  284. package/template/bunfig.toml +2 -0
  285. package/template/compose.yaml +53 -0
  286. package/template/docker-bake.hcl +86 -0
  287. package/template/package.json +47 -0
  288. package/template/packages/lint/knip.ts +40 -0
  289. package/template/packages/lint/oxfmt.ts +8 -0
  290. package/template/packages/lint/oxlint.ts +136 -0
  291. package/template/packages/lint/package.json +13 -0
  292. package/template/packages/queues/oxfmt.config.ts +8 -0
  293. package/template/packages/queues/oxlint.config.ts +7 -0
  294. package/template/packages/queues/package.json +15 -0
  295. package/template/packages/queues/src/example-job.ts +11 -0
  296. package/template/packages/queues/src/index.ts +3 -0
  297. package/template/packages/queues/src/queue-names.ts +2 -0
  298. package/template/packages/queues/src/redis-connection.ts +27 -0
  299. package/template/packages/queues/tsconfig.json +4 -0
  300. package/template/packages/tsconfig/base.json +23 -0
  301. package/template/packages/tsconfig/node.json +11 -0
  302. package/template/packages/tsconfig/package.json +4 -0
  303. package/template/turbo.json +50 -0
@@ -0,0 +1,60 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: nexst-web
5
+ spec:
6
+ replicas: 1
7
+ selector:
8
+ matchLabels: {}
9
+ template:
10
+ spec:
11
+ serviceAccountName: ghcr-puller
12
+ securityContext:
13
+ seccompProfile:
14
+ type: RuntimeDefault
15
+ containers:
16
+ - name: nexst-web
17
+ image: nexst-web
18
+ imagePullPolicy: IfNotPresent
19
+ ports:
20
+ - name: http
21
+ containerPort: 3000
22
+ protocol: TCP
23
+ envFrom:
24
+ - configMapRef:
25
+ name: nexst-web-config
26
+ volumeMounts:
27
+ - name: tmp
28
+ mountPath: /tmp
29
+ livenessProbe:
30
+ httpGet:
31
+ path: /api/health
32
+ port: http
33
+ initialDelaySeconds: 10
34
+ periodSeconds: 30
35
+ readinessProbe:
36
+ httpGet:
37
+ path: /api/health
38
+ port: http
39
+ initialDelaySeconds: 5
40
+ periodSeconds: 10
41
+ resources:
42
+ requests:
43
+ memory: 256Mi
44
+ cpu: 250m
45
+ limits:
46
+ memory: 512Mi
47
+ cpu: 1000m
48
+ securityContext:
49
+ allowPrivilegeEscalation: false
50
+ runAsNonRoot: true
51
+ runAsUser: 10001
52
+ runAsGroup: 10001
53
+ readOnlyRootFilesystem: true
54
+ capabilities:
55
+ drop: [ALL]
56
+ seccompProfile:
57
+ type: RuntimeDefault
58
+ volumes:
59
+ - name: tmp
60
+ emptyDir: {}
@@ -0,0 +1,17 @@
1
+ labels:
2
+ - pairs:
3
+ app.kubernetes.io/name: nexst-web
4
+ app.kubernetes.io/part-of: nexst
5
+ app.kubernetes.io/managed-by: kustomize
6
+ app.kubernetes.io/created-by: your-org
7
+ app.kubernetes.io/component: web
8
+ includeSelectors: true
9
+
10
+ resources:
11
+ - deployment.yaml
12
+ - service.yaml
13
+
14
+ configMapGenerator:
15
+ - name: nexst-web-config
16
+ envs:
17
+ - application.properties
@@ -0,0 +1,10 @@
1
+ apiVersion: v1
2
+ kind: Service
3
+ metadata:
4
+ name: nexst-web
5
+ spec:
6
+ ports:
7
+ - name: http
8
+ port: 80
9
+ targetPort: 3000
10
+ protocol: TCP
@@ -0,0 +1,12 @@
1
+ apiVersion: kustomize.config.k8s.io/v1beta1
2
+ kind: Kustomization
3
+
4
+ resources:
5
+ - ../../base
6
+
7
+ namespace: nexst
8
+
9
+ images:
10
+ - name: nexst-web
11
+ newName: ghcr.io/your-org/nexst-web
12
+ newTag: latest
@@ -0,0 +1,2 @@
1
+ NEXT_PUBLIC_API_URL=https://api.staging.example.com
2
+ NEXT_PUBLIC_HOST_URL=https://staging.example.com
@@ -0,0 +1,18 @@
1
+ apiVersion: kustomize.config.k8s.io/v1beta1
2
+ kind: Kustomization
3
+
4
+ resources:
5
+ - ../../base
6
+
7
+ namespace: nexst-staging
8
+
9
+ images:
10
+ - name: nexst-web
11
+ newName: ghcr.io/your-org/nexst-web
12
+ newTag: test
13
+
14
+ configMapGenerator:
15
+ - name: nexst-web-config
16
+ behavior: merge
17
+ envs:
18
+ - application.properties
@@ -0,0 +1,124 @@
1
+ {
2
+ "Common": {
3
+ "a11y": {
4
+ "hidePassword": "Passwort verbergen",
5
+ "showPassword": "Passwort anzeigen",
6
+ "close": "Schließen",
7
+ "scrollUp": "Nach oben scrollen",
8
+ "scrollDown": "Nach unten scrollen",
9
+ "clearSearch": "Suche leeren"
10
+ },
11
+ "cancel": "Abbrechen"
12
+ },
13
+ "Header": {
14
+ "navigation": "Hauptnavigation",
15
+ "toggleTheme": "Dunkelmodus umschalten",
16
+ "dashboard": "Dashboard",
17
+ "signIn": "Anmelden",
18
+ "signOut": "Abmelden"
19
+ },
20
+ "Home": {
21
+ "title": "Deine nächste Idee auf solidem Fundament",
22
+ "description": "Next.js, NestJS und Prisma – verdrahtet mit Linting, Tests, Docker-Images und CI.",
23
+ "cta": "Loslegen"
24
+ },
25
+ "Dashboard": {
26
+ "title": "Dashboard",
27
+ "signedInAs": "Angemeldet als {email}.",
28
+ "unverified": "Deine E-Mail-Adresse ist noch nicht bestätigt. Sieh in deinem Postfach nach dem Bestätigungslink."
29
+ },
30
+ "Auth": {
31
+ "SignIn": {
32
+ "title": "Anmelden",
33
+ "emailLabel": "E-Mail",
34
+ "emailPlaceholder": "du@example.com",
35
+ "passwordLabel": "Passwort",
36
+ "forgotPassword": "Passwort vergessen?",
37
+ "submitButton": "Anmelden",
38
+ "noAccount": "Noch kein Konto?",
39
+ "signUpLink": "Jetzt erstellen"
40
+ },
41
+ "SignUp": {
42
+ "title": "Konto erstellen",
43
+ "nameLabel": "Name",
44
+ "emailLabel": "E-Mail",
45
+ "emailPlaceholder": "du@example.com",
46
+ "passwordLabel": "Passwort",
47
+ "confirmPasswordLabel": "Passwort bestätigen",
48
+ "submitButton": "Konto erstellen",
49
+ "haveAccount": "Schon ein Konto?",
50
+ "signInLink": "Anmelden"
51
+ },
52
+ "Google": {
53
+ "continueButton": "Mit Google fortfahren"
54
+ },
55
+ "Passkey": {
56
+ "signIn": "Mit Passkey anmelden",
57
+ "signingIn": "Warte auf Passkey…",
58
+ "title": "Passkeys",
59
+ "description": "Melde dich per Fingerabdruck, Gesicht, Geräte-PIN oder Sicherheitsschlüssel statt mit Passwort an.",
60
+ "defaultName": "Mein Passkey",
61
+ "add": "Passkey hinzufügen",
62
+ "loading": "Passkeys werden geladen…",
63
+ "empty": "Noch keine Passkeys.",
64
+ "fallbackName": "Passkey",
65
+ "createdAt": "Hinzugefügt am {date}",
66
+ "deleteLabel": "{name} löschen",
67
+ "added": "Passkey hinzugefügt",
68
+ "addError": "Der Passkey konnte nicht hinzugefügt werden. Bitte versuche es erneut.",
69
+ "deleted": "Passkey gelöscht",
70
+ "deleteError": "Der Passkey konnte nicht gelöscht werden. Bitte versuche es erneut."
71
+ },
72
+ "Recovery": {
73
+ "title": "Passwort zurücksetzen",
74
+ "description": "Gib deine E-Mail ein, wir schicken dir einen Link für ein neues Passwort.",
75
+ "emailLabel": "E-Mail",
76
+ "emailPlaceholder": "du@example.com",
77
+ "submitButton": "Link senden",
78
+ "emailSent": "Falls ein Konto mit dieser E-Mail existiert, ist der Link unterwegs.",
79
+ "backToSignIn": "Zurück zur Anmeldung"
80
+ },
81
+ "ResetPassword": {
82
+ "title": "Neues Passwort wählen",
83
+ "passwordLabel": "Neues Passwort",
84
+ "confirmPasswordLabel": "Neues Passwort bestätigen",
85
+ "submitButton": "Passwort speichern",
86
+ "invalidLink": "Dieser Link ist ungültig oder abgelaufen. Fordere einen neuen an."
87
+ },
88
+ "Verify": {
89
+ "title": "Sieh in dein Postfach",
90
+ "description": "Wir haben dir einen Link zur Bestätigung deiner E-Mail-Adresse geschickt.",
91
+ "resendButton": "E-Mail erneut senden",
92
+ "resent": "E-Mail gesendet"
93
+ },
94
+ "Errors": {
95
+ "emailInvalid": "Gib eine gültige E-Mail-Adresse ein.",
96
+ "nameRequired": "Gib deinen Namen ein.",
97
+ "passwordRequired": "Gib dein Passwort ein.",
98
+ "passwordTooShort": "Verwende mindestens 8 Zeichen.",
99
+ "confirmPasswordRequired": "Bestätige dein Passwort.",
100
+ "passwordsMustMatch": "Die Passwörter stimmen nicht überein.",
101
+ "invalidCredentials": "E-Mail oder Passwort ist falsch.",
102
+ "emailAlreadyExists": "Mit dieser E-Mail gibt es bereits ein Konto.",
103
+ "networkError": "Der Server ist nicht erreichbar. Prüfe deine Verbindung und versuche es erneut.",
104
+ "defaultError": "Etwas ist schiefgelaufen. Bitte versuche es erneut."
105
+ },
106
+ "divider": "oder"
107
+ },
108
+ "ApiErrors": {
109
+ "BAD_REQUEST": "Die Anfrage war ungültig.",
110
+ "CONFLICT": "Das kollidiert mit vorhandenen Daten.",
111
+ "EMAIL_NOT_VERIFIED": "Bestätige zuerst deine E-Mail-Adresse.",
112
+ "FORBIDDEN": "Das darfst du nicht.",
113
+ "INSUFFICIENT_ROLE": "Dein Konto hat darauf keinen Zugriff.",
114
+ "INTERNAL_SERVER_ERROR": "Bei uns ist etwas schiefgelaufen. Bitte versuche es erneut.",
115
+ "NETWORK_ERROR": "Der Server ist nicht erreichbar. Prüfe deine Verbindung und versuche es erneut.",
116
+ "NOT_FOUND": "Nicht gefunden.",
117
+ "SERVICE_UNAVAILABLE": "Der Dienst ist vorübergehend nicht verfügbar.",
118
+ "TOO_MANY_REQUESTS": "Zu viele Anfragen. Warte kurz und versuche es erneut.",
119
+ "UNAUTHORIZED": "Melde dich an, um fortzufahren.",
120
+ "UNPROCESSABLE_ENTITY": "Das konnte nicht verarbeitet werden.",
121
+ "VALIDATION_FAILED": "Einige Felder sind ungültig.",
122
+ "defaultError": "Etwas ist schiefgelaufen. Bitte versuche es erneut."
123
+ }
124
+ }
@@ -0,0 +1,124 @@
1
+ {
2
+ "Common": {
3
+ "a11y": {
4
+ "hidePassword": "Hide password",
5
+ "showPassword": "Show password",
6
+ "close": "Close",
7
+ "scrollUp": "Scroll up",
8
+ "scrollDown": "Scroll down",
9
+ "clearSearch": "Clear search"
10
+ },
11
+ "cancel": "Cancel"
12
+ },
13
+ "Header": {
14
+ "navigation": "Main",
15
+ "toggleTheme": "Toggle dark mode",
16
+ "dashboard": "Dashboard",
17
+ "signIn": "Sign in",
18
+ "signOut": "Sign out"
19
+ },
20
+ "Home": {
21
+ "title": "Ship your next idea on a solid foundation",
22
+ "description": "Next.js, NestJS and Prisma, wired together with linting, tests, Docker images and CI.",
23
+ "cta": "Get started"
24
+ },
25
+ "Dashboard": {
26
+ "title": "Dashboard",
27
+ "signedInAs": "Signed in as {email}.",
28
+ "unverified": "Your email address is not verified yet. Check your inbox for the confirmation link."
29
+ },
30
+ "Auth": {
31
+ "SignIn": {
32
+ "title": "Sign in",
33
+ "emailLabel": "Email",
34
+ "emailPlaceholder": "you@example.com",
35
+ "passwordLabel": "Password",
36
+ "forgotPassword": "Forgot password?",
37
+ "submitButton": "Sign in",
38
+ "noAccount": "No account yet?",
39
+ "signUpLink": "Create one"
40
+ },
41
+ "SignUp": {
42
+ "title": "Create an account",
43
+ "nameLabel": "Name",
44
+ "emailLabel": "Email",
45
+ "emailPlaceholder": "you@example.com",
46
+ "passwordLabel": "Password",
47
+ "confirmPasswordLabel": "Confirm password",
48
+ "submitButton": "Create account",
49
+ "haveAccount": "Already have an account?",
50
+ "signInLink": "Sign in"
51
+ },
52
+ "Google": {
53
+ "continueButton": "Continue with Google"
54
+ },
55
+ "Passkey": {
56
+ "signIn": "Sign in with a passkey",
57
+ "signingIn": "Waiting for passkey…",
58
+ "title": "Passkeys",
59
+ "description": "Sign in with your fingerprint, face, device PIN or a security key instead of a password.",
60
+ "defaultName": "My passkey",
61
+ "add": "Add passkey",
62
+ "loading": "Loading passkeys…",
63
+ "empty": "No passkeys yet.",
64
+ "fallbackName": "Passkey",
65
+ "createdAt": "Added {date}",
66
+ "deleteLabel": "Delete {name}",
67
+ "added": "Passkey added",
68
+ "addError": "The passkey could not be added. Please try again.",
69
+ "deleted": "Passkey deleted",
70
+ "deleteError": "The passkey could not be deleted. Please try again."
71
+ },
72
+ "Recovery": {
73
+ "title": "Reset your password",
74
+ "description": "Enter your email and we will send you a link to choose a new password.",
75
+ "emailLabel": "Email",
76
+ "emailPlaceholder": "you@example.com",
77
+ "submitButton": "Send reset link",
78
+ "emailSent": "If an account exists for this email, a reset link is on its way.",
79
+ "backToSignIn": "Back to sign in"
80
+ },
81
+ "ResetPassword": {
82
+ "title": "Choose a new password",
83
+ "passwordLabel": "New password",
84
+ "confirmPasswordLabel": "Confirm new password",
85
+ "submitButton": "Save password",
86
+ "invalidLink": "This reset link is invalid or has expired. Request a new one."
87
+ },
88
+ "Verify": {
89
+ "title": "Check your inbox",
90
+ "description": "We sent you a link to confirm your email address.",
91
+ "resendButton": "Resend email",
92
+ "resent": "Email sent"
93
+ },
94
+ "Errors": {
95
+ "emailInvalid": "Enter a valid email address.",
96
+ "nameRequired": "Enter your name.",
97
+ "passwordRequired": "Enter your password.",
98
+ "passwordTooShort": "Use at least 8 characters.",
99
+ "confirmPasswordRequired": "Confirm your password.",
100
+ "passwordsMustMatch": "The passwords do not match.",
101
+ "invalidCredentials": "Invalid email or password.",
102
+ "emailAlreadyExists": "An account with this email already exists.",
103
+ "networkError": "Could not reach the server. Check your connection and try again.",
104
+ "defaultError": "Something went wrong. Please try again."
105
+ },
106
+ "divider": "or"
107
+ },
108
+ "ApiErrors": {
109
+ "BAD_REQUEST": "The request was invalid.",
110
+ "CONFLICT": "This conflicts with existing data.",
111
+ "EMAIL_NOT_VERIFIED": "Verify your email address first.",
112
+ "FORBIDDEN": "You are not allowed to do this.",
113
+ "INSUFFICIENT_ROLE": "Your account does not have access to this.",
114
+ "INTERNAL_SERVER_ERROR": "Something went wrong on our side. Please try again.",
115
+ "NETWORK_ERROR": "Could not reach the server. Check your connection and try again.",
116
+ "NOT_FOUND": "Not found.",
117
+ "SERVICE_UNAVAILABLE": "The service is temporarily unavailable.",
118
+ "TOO_MANY_REQUESTS": "Too many requests. Wait a moment and try again.",
119
+ "UNAUTHORIZED": "Sign in to continue.",
120
+ "UNPROCESSABLE_ENTITY": "This could not be processed.",
121
+ "VALIDATION_FAILED": "Some fields are invalid.",
122
+ "defaultError": "Something went wrong. Please try again."
123
+ }
124
+ }
@@ -0,0 +1,7 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+ import "./.next/types/routes.d.ts";
4
+ import "./.next/types/root-params.d.ts";
5
+
6
+ // NOTE: This file should not be edited
7
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -0,0 +1,39 @@
1
+ import bundleAnalyzer from "@next/bundle-analyzer";
2
+ import type { NextConfig } from "next";
3
+ import createNextIntlPlugin from "next-intl/plugin";
4
+
5
+ const withBundleAnalyzer = bundleAnalyzer({
6
+ enabled: process.env.ANALYZE === "true",
7
+ });
8
+
9
+ const isProductionBuild = process.env.NODE_ENV === "production";
10
+
11
+ const nextConfig: NextConfig = {
12
+ cacheComponents: true,
13
+ experimental: {
14
+ // The pod's root filesystem is read-only: keep ISR renders in memory instead of
15
+ // writing them to .next/server.
16
+ isrFlushToDisk: false,
17
+ optimizePackageImports: ["lucide-react"],
18
+ turbopackFileSystemCacheForDev: true,
19
+ useTypeScriptCli: true,
20
+ },
21
+ headers: async () => [
22
+ {
23
+ headers: [
24
+ { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
25
+ { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
26
+ { key: "X-Content-Type-Options", value: "nosniff" },
27
+ { key: "X-Frame-Options", value: "DENY" },
28
+ ],
29
+ source: "/(.*)",
30
+ },
31
+ ],
32
+ output: "standalone",
33
+ typescript: {
34
+ tsconfigPath: isProductionBuild ? "tsconfig.build.json" : "tsconfig.json",
35
+ },
36
+ };
37
+
38
+ const withNextIntl = createNextIntlPlugin();
39
+ export default withBundleAnalyzer(withNextIntl(nextConfig));
@@ -0,0 +1,9 @@
1
+ import { baseOxfmtConfig } from "@nexst/lint/oxfmt";
2
+ import { baseIgnorePatterns } from "@nexst/lint/oxlint";
3
+ import { defineConfig } from "oxfmt";
4
+
5
+ export default defineConfig({
6
+ ...baseOxfmtConfig,
7
+ ignorePatterns: [...baseIgnorePatterns, ".next/**", "src/generated/**"],
8
+ sortTailwindcss: true,
9
+ });
@@ -0,0 +1,36 @@
1
+ import base, { baseIgnorePatterns } from "@nexst/lint/oxlint";
2
+ import { defineConfig } from "oxlint";
3
+
4
+ export default defineConfig({
5
+ extends: [base],
6
+ ignorePatterns: [...baseIgnorePatterns, ".next/**", "src/generated/**"],
7
+ jsPlugins: ["eslint-plugin-storybook"],
8
+ overrides: [
9
+ {
10
+ files: ["**/config/**"],
11
+ rules: {
12
+ "sonarjs/no-duplicate-string": "off",
13
+ },
14
+ },
15
+ {
16
+ files: ["*.stories.tsx"],
17
+ rules: {
18
+ "storybook/await-interactions": "error",
19
+ "storybook/context-in-play-function": "error",
20
+ "storybook/default-exports": "warn",
21
+ "storybook/hierarchy-separator": "warn",
22
+ "storybook/no-title-property-in-meta": "warn",
23
+ "storybook/no-uninstalled-addons": "error",
24
+ "storybook/prefer-pascal-case": "warn",
25
+ "storybook/use-storybook-expect": "warn",
26
+ "storybook/use-storybook-testing-library": "warn",
27
+ },
28
+ },
29
+ ],
30
+ plugins: ["react", "nextjs", "jsx-a11y", "react-perf"],
31
+ rules: {
32
+ "typescript/no-floating-promises": "off",
33
+ "typescript/no-unnecessary-type-assertion": "warn",
34
+ "typescript/switch-exhaustiveness-check": "warn",
35
+ },
36
+ });
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "@nexst/web",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "next dev",
8
+ "build": "next build",
9
+ "start": "next start",
10
+ "lint": "oxlint",
11
+ "lint:fix": "oxlint --fix",
12
+ "fmt": "oxfmt",
13
+ "fmt:check": "oxfmt --check",
14
+ "lint:ts": "tsc --noEmit",
15
+ "unused": "knip --no-config-hints",
16
+ "storybook": "storybook dev -p 6006",
17
+ "build-storybook": "storybook build",
18
+ "test:unit": "vitest run",
19
+ "test": "playwright test",
20
+ "test:ui": "playwright test --ui",
21
+ "test:report": "playwright show-report",
22
+ "codegen": "graphql-codegen -c src/lib/codegen.ts"
23
+ },
24
+ "dependencies": {
25
+ "@better-auth/passkey": "^1.7.6",
26
+ "@hookform/resolvers": "^5.9.1",
27
+ "@radix-ui/react-checkbox": "^1.3.11",
28
+ "@radix-ui/react-dialog": "^1.1.23",
29
+ "@radix-ui/react-dropdown-menu": "^2.1.24",
30
+ "@radix-ui/react-popover": "^1.1.23",
31
+ "@radix-ui/react-select": "^2.3.7",
32
+ "@radix-ui/react-tabs": "^1.1.21",
33
+ "@radix-ui/react-tooltip": "^1.2.16",
34
+ "@t3-oss/env-nextjs": "^0.13.11",
35
+ "@tanstack/react-query": "^5.104.0",
36
+ "better-auth": "^1.7.6",
37
+ "class-variance-authority": "^0.7.1",
38
+ "clsx": "^2.1.1",
39
+ "graphql": "^17.0.2",
40
+ "graphql-request": "^7.4.0",
41
+ "lucide-react": "^1.48.0",
42
+ "next": "^16.3.6",
43
+ "next-intl": "^4.14.7",
44
+ "next-themes": "^0.4.6",
45
+ "react": "^19.3.0",
46
+ "react-dom": "^19.3.0",
47
+ "react-hook-form": "^7.89.0",
48
+ "server-only": "^0.0.1",
49
+ "sonner": "^2.0.8",
50
+ "tailwind-merge": "^3.7.0",
51
+ "vaul": "^1.1.2",
52
+ "zod": "^4.6.5"
53
+ },
54
+ "devDependencies": {
55
+ "@axe-core/playwright": "^4.13.0",
56
+ "@graphql-codegen/cli": "^7.4.3",
57
+ "@graphql-codegen/client-preset": "^6.2.0",
58
+ "@graphql-codegen/typescript": "^6.1.0",
59
+ "@graphql-typed-document-node/core": "^3.2.0",
60
+ "@next/bundle-analyzer": "^16.3.6",
61
+ "@playwright/test": "^1.63.0",
62
+ "@storybook/addon-a11y": "^10.6.0",
63
+ "@storybook/addon-docs": "^10.6.0",
64
+ "@storybook/addon-themes": "^10.6.0",
65
+ "@storybook/addon-vitest": "^10.6.0",
66
+ "@storybook/nextjs-vite": "^10.6.0",
67
+ "@tailwindcss/postcss": "^4.3.3",
68
+ "@types/node": "^26.6.3",
69
+ "@types/react": "^19.3.0",
70
+ "@types/react-dom": "^19.3.0",
71
+ "@vitest/browser": "^5.0.2",
72
+ "@vitest/browser-playwright": "^5.0.2",
73
+ "eslint-plugin-storybook": "^10.6.0",
74
+ "playwright": "^1.63.0",
75
+ "postcss": "^8.5.28",
76
+ "storybook": "10.6.0",
77
+ "tailwindcss": "^4.3.3",
78
+ "tailwindcss-animate": "^1.0.7"
79
+ }
80
+ }
@@ -0,0 +1,34 @@
1
+ import { defineConfig, devices } from "@playwright/test";
2
+
3
+ export default defineConfig({
4
+ forbidOnly: !!process.env.CI,
5
+ fullyParallel: true,
6
+ projects: [
7
+ {
8
+ name: "chromium",
9
+ use: { ...devices["Desktop Chrome"] },
10
+ },
11
+ ],
12
+ reporter: process.env.CI ? [["github"], ["html"]] : [["html"]],
13
+ retries: process.env.CI ? 2 : 0,
14
+ testDir: "tests",
15
+ use: {
16
+ baseURL: "http://localhost:3000",
17
+ screenshot: "only-on-failure",
18
+ trace: "retain-on-failure",
19
+ },
20
+
21
+ webServer: {
22
+ command: "bun run start",
23
+ env: {
24
+ NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001",
25
+ NEXT_PUBLIC_HOST_URL: process.env.NEXT_PUBLIC_HOST_URL || "http://localhost:3000",
26
+ },
27
+ reuseExistingServer: !process.env.CI,
28
+ stderr: "pipe",
29
+ stdout: "ignore",
30
+ url: "http://localhost:3000",
31
+ },
32
+
33
+ workers: process.env.CI ? 2 : 1,
34
+ });
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
@@ -0,0 +1,9 @@
1
+ sonar.projectKey=nexst-web
2
+ sonar.projectVersion=1.0
3
+
4
+ sonar.sources=src
5
+ sonar.exclusions=src/generated/**,**/*.test.ts,**/*.stories.tsx
6
+ sonar.tests=src,tests
7
+ sonar.test.inclusions=**/*.test.ts,**/*.spec.ts,**/*.stories.tsx
8
+ sonar.javascript.lcov.reportPaths=coverage/lcov.info
9
+ sonar.typescript.tsconfigPath=tsconfig.json
@@ -0,0 +1,22 @@
1
+ import { getTranslations } from "next-intl/server";
2
+ import { AuthCard } from "@/features/auth/auth-card";
3
+ import { RecoveryForm } from "@/features/auth/recovery-form";
4
+ import { Link } from "@/i18n/navigation";
5
+
6
+ export default async function RecoveryPage() {
7
+ const t = await getTranslations("Auth.Recovery");
8
+
9
+ return (
10
+ <AuthCard
11
+ description={t("description")}
12
+ footer={
13
+ <Link className="text-link font-medium hover:underline" href="/sign-in">
14
+ {t("backToSignIn")}
15
+ </Link>
16
+ }
17
+ title={t("title")}
18
+ >
19
+ <RecoveryForm />
20
+ </AuthCard>
21
+ );
22
+ }
@@ -0,0 +1,29 @@
1
+ import { getTranslations } from "next-intl/server";
2
+ import { Suspense } from "react";
3
+ import { Alert } from "@/components/alert/alert";
4
+ import { AuthCard } from "@/features/auth/auth-card";
5
+ import { ResetPasswordForm } from "@/features/auth/reset-password-form";
6
+
7
+ type Props = { searchParams: Promise<{ token?: string }> };
8
+
9
+ export default async function ResetPasswordPage({ searchParams }: Props) {
10
+ const t = await getTranslations("Auth.ResetPassword");
11
+
12
+ return (
13
+ <AuthCard title={t("title")}>
14
+ <Suspense>
15
+ <ResetPasswordContent searchParams={searchParams} />
16
+ </Suspense>
17
+ </AuthCard>
18
+ );
19
+ }
20
+
21
+ async function ResetPasswordContent({ searchParams }: Props) {
22
+ const [{ token }, t] = await Promise.all([searchParams, getTranslations("Auth.ResetPassword")]);
23
+
24
+ return token ? (
25
+ <ResetPasswordForm token={token} />
26
+ ) : (
27
+ <Alert message={t("invalidLink")} variant="error" />
28
+ );
29
+ }