@aurostack/stacks 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 (601) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +209 -0
  3. package/cli/src/extract.mjs +165 -0
  4. package/cli/src/fsx.mjs +197 -0
  5. package/cli/src/generate.mjs +235 -0
  6. package/cli/src/hooks.mjs +55 -0
  7. package/cli/src/manifest.mjs +163 -0
  8. package/cli/src/strip.mjs +208 -0
  9. package/cli/src/tokens.mjs +102 -0
  10. package/cli/stack.mjs +672 -0
  11. package/package.json +37 -0
  12. package/templates/nest-api/files/.bin/db.ts +123 -0
  13. package/templates/nest-api/files/.bin/dc.ts +74 -0
  14. package/templates/nest-api/files/.bin/helpers.ts +49 -0
  15. package/templates/nest-api/files/.bin/secrets.ts +68 -0
  16. package/templates/nest-api/files/.bin/start.ts +47 -0
  17. package/templates/nest-api/files/.dockerignore +7 -0
  18. package/templates/nest-api/files/.env.example +91 -0
  19. package/templates/nest-api/files/.env.test.example +75 -0
  20. package/templates/nest-api/files/.forgejo/workflows/build-and-push.yml +41 -0
  21. package/templates/nest-api/files/.forgejo/workflows/test-e2e.yml +83 -0
  22. package/templates/nest-api/files/.forgejo/workflows/test.yml +53 -0
  23. package/templates/nest-api/files/.npmrc.example +1 -0
  24. package/templates/nest-api/files/.nvmrc +1 -0
  25. package/templates/nest-api/files/.prettierrc +8 -0
  26. package/templates/nest-api/files/.swcrc +15 -0
  27. package/templates/nest-api/files/.watchmanconfig +3 -0
  28. package/templates/nest-api/files/.yarnrc.yml +1 -0
  29. package/templates/nest-api/files/Dockerfile +40 -0
  30. package/templates/nest-api/files/__mocks__/activation-twofa.js +7 -0
  31. package/templates/nest-api/files/__mocks__/activation.js +7 -0
  32. package/templates/nest-api/files/__mocks__/forgot-password.js +7 -0
  33. package/templates/nest-api/files/__mocks__/main-layout.js +7 -0
  34. package/templates/nest-api/files/__mocks__/password-changed.js +7 -0
  35. package/templates/nest-api/files/__mocks__/reset-password.js +7 -0
  36. package/templates/nest-api/files/__mocks__/set-password.js +7 -0
  37. package/templates/nest-api/files/__mocks__/welcome.js +7 -0
  38. package/templates/nest-api/files/_gitignore +62 -0
  39. package/templates/nest-api/files/compose.test.yml +33 -0
  40. package/templates/nest-api/files/compose.yml +39 -0
  41. package/templates/nest-api/files/emails/activation-twofa.tsx +58 -0
  42. package/templates/nest-api/files/emails/activation.tsx +36 -0
  43. package/templates/nest-api/files/emails/forgot-password.tsx +29 -0
  44. package/templates/nest-api/files/emails/main-layout.tsx +134 -0
  45. package/templates/nest-api/files/emails/password-changed.tsx +24 -0
  46. package/templates/nest-api/files/emails/reset-password.tsx +24 -0
  47. package/templates/nest-api/files/emails/set-password.tsx +25 -0
  48. package/templates/nest-api/files/emails/welcome.tsx +39 -0
  49. package/templates/nest-api/files/eslint.config.mjs +28 -0
  50. package/templates/nest-api/files/global.d.ts +5 -0
  51. package/templates/nest-api/files/nest-cli.json +8 -0
  52. package/templates/nest-api/files/package.json +150 -0
  53. package/templates/nest-api/files/prisma/schema/auth.prisma +75 -0
  54. package/templates/nest-api/files/prisma/schema/base.prisma +17 -0
  55. package/templates/nest-api/files/prisma/schema/enum.prisma +17 -0
  56. package/templates/nest-api/files/prisma/schema/notification.prisma +14 -0
  57. package/templates/nest-api/files/prisma.config.ts +13 -0
  58. package/templates/nest-api/files/public/.gitkeep +0 -0
  59. package/templates/nest-api/files/seeders/index.ts +27 -0
  60. package/templates/nest-api/files/seeders/util.ts +88 -0
  61. package/templates/nest-api/files/src/__tests__/app.controller.spec.ts +29 -0
  62. package/templates/nest-api/files/src/app.controller.ts +40 -0
  63. package/templates/nest-api/files/src/app.module.ts +268 -0
  64. package/templates/nest-api/files/src/app.resolver.ts +32 -0
  65. package/templates/nest-api/files/src/app.setup.ts +178 -0
  66. package/templates/nest-api/files/src/common/__tests__/auth.service.spec.ts +31 -0
  67. package/templates/nest-api/files/src/common/__tests__/cache.service.spec.ts +214 -0
  68. package/templates/nest-api/files/src/common/__tests__/config.service.spec.ts +56 -0
  69. package/templates/nest-api/files/src/common/__tests__/generator.service.spec.ts +114 -0
  70. package/templates/nest-api/files/src/common/__tests__/health.controller.spec.ts +72 -0
  71. package/templates/nest-api/files/src/common/__tests__/helpers.spec.ts +124 -0
  72. package/templates/nest-api/files/src/common/__tests__/mail.service.spec.ts +75 -0
  73. package/templates/nest-api/files/src/common/__tests__/prisma.indicator.spec.ts +39 -0
  74. package/templates/nest-api/files/src/common/__tests__/prisma.service.spec.ts +15 -0
  75. package/templates/nest-api/files/src/common/__tests__/records.spec.ts +87 -0
  76. package/templates/nest-api/files/src/common/__tests__/schemas.spec.ts +142 -0
  77. package/templates/nest-api/files/src/common/__tests__/throttler-storage.service.spec.ts +80 -0
  78. package/templates/nest-api/files/src/common/common.module.ts +72 -0
  79. package/templates/nest-api/files/src/common/constants/index.ts +2 -0
  80. package/templates/nest-api/files/src/common/constants/options.ts +52 -0
  81. package/templates/nest-api/files/src/common/constants/pino.ts +24 -0
  82. package/templates/nest-api/files/src/common/controllers/health.controller.ts +54 -0
  83. package/templates/nest-api/files/src/common/controllers/index.ts +2 -0
  84. package/templates/nest-api/files/src/common/controllers/metrics.controller.ts +21 -0
  85. package/templates/nest-api/files/src/common/decorators/index.ts +2 -0
  86. package/templates/nest-api/files/src/common/decorators/misc.decorator.ts +49 -0
  87. package/templates/nest-api/files/src/common/decorators/multi-part.decorator.ts +6 -0
  88. package/templates/nest-api/files/src/common/dto/filters.dto.ts +25 -0
  89. package/templates/nest-api/files/src/common/dto/index.ts +1 -0
  90. package/templates/nest-api/files/src/common/entity/index.ts +2 -0
  91. package/templates/nest-api/files/src/common/entity/ok.entity.ts +11 -0
  92. package/templates/nest-api/files/src/common/entity/pagination.entity.ts +24 -0
  93. package/templates/nest-api/files/src/common/exceptions/index.ts +1 -0
  94. package/templates/nest-api/files/src/common/exceptions/prisma.exception.ts +57 -0
  95. package/templates/nest-api/files/src/common/guards/feature-flag.guard.ts +51 -0
  96. package/templates/nest-api/files/src/common/guards/index.ts +1 -0
  97. package/templates/nest-api/files/src/common/interceptors/index.ts +1 -0
  98. package/templates/nest-api/files/src/common/interceptors/metrics.interceptor.ts +37 -0
  99. package/templates/nest-api/files/src/common/misc/helpers.ts +26 -0
  100. package/templates/nest-api/files/src/common/misc/index.ts +3 -0
  101. package/templates/nest-api/files/src/common/misc/origin-builder.ts +56 -0
  102. package/templates/nest-api/files/src/common/misc/prisma.indicator.ts +24 -0
  103. package/templates/nest-api/files/src/common/modules/index.ts +3 -0
  104. package/templates/nest-api/files/src/common/modules/logger.module.ts +104 -0
  105. package/templates/nest-api/files/src/common/modules/metrics.module.ts +28 -0
  106. package/templates/nest-api/files/src/common/modules/queue.module.ts +23 -0
  107. package/templates/nest-api/files/src/common/processors/index.ts +1 -0
  108. package/templates/nest-api/files/src/common/processors/mail/index.ts +47 -0
  109. package/templates/nest-api/files/src/common/processors/mail/process.ts +225 -0
  110. package/templates/nest-api/files/src/common/schemas/fields.ts +76 -0
  111. package/templates/nest-api/files/src/common/schemas/index.ts +1 -0
  112. package/templates/nest-api/files/src/common/services/auth-rate-limit.storage.ts +79 -0
  113. package/templates/nest-api/files/src/common/services/auth.service.ts +16 -0
  114. package/templates/nest-api/files/src/common/services/cache.service.ts +118 -0
  115. package/templates/nest-api/files/src/common/services/config.service.ts +388 -0
  116. package/templates/nest-api/files/src/common/services/feature-flag.service.ts +117 -0
  117. package/templates/nest-api/files/src/common/services/generator.service.ts +54 -0
  118. package/templates/nest-api/files/src/common/services/index.ts +10 -0
  119. package/templates/nest-api/files/src/common/services/logger.service.ts +14 -0
  120. package/templates/nest-api/files/src/common/services/mail.service.ts +44 -0
  121. package/templates/nest-api/files/src/common/services/prisma.service.ts +106 -0
  122. package/templates/nest-api/files/src/common/services/throttler-storage.service.ts +112 -0
  123. package/templates/nest-api/files/src/common/types/email.ts +55 -0
  124. package/templates/nest-api/files/src/common/types/index.ts +3 -0
  125. package/templates/nest-api/files/src/common/types/misc.ts +20 -0
  126. package/templates/nest-api/files/src/common/types/prisma.ts +18 -0
  127. package/templates/nest-api/files/src/common/utils/enum.ts +39 -0
  128. package/templates/nest-api/files/src/common/utils/helpers.ts +139 -0
  129. package/templates/nest-api/files/src/common/utils/index.ts +5 -0
  130. package/templates/nest-api/files/src/common/utils/ip.ts +22 -0
  131. package/templates/nest-api/files/src/common/utils/records.ts +66 -0
  132. package/templates/nest-api/files/src/common/utils/redis.keys.ts +31 -0
  133. package/templates/nest-api/files/src/lib/__tests__/access.spec.ts +33 -0
  134. package/templates/nest-api/files/src/lib/access.ts +114 -0
  135. package/templates/nest-api/files/src/lib/auth.ts +76 -0
  136. package/templates/nest-api/files/src/lib/rate-limit.ts +20 -0
  137. package/templates/nest-api/files/src/main.ts +28 -0
  138. package/templates/nest-api/files/src/media/__tests__/media.service.spec.ts +60 -0
  139. package/templates/nest-api/files/src/media/__tests__/s3.service.spec.ts +159 -0
  140. package/templates/nest-api/files/src/media/entity/file.entity.ts +7 -0
  141. package/templates/nest-api/files/src/media/entity/index.ts +1 -0
  142. package/templates/nest-api/files/src/media/media.module.ts +20 -0
  143. package/templates/nest-api/files/src/media/processors/index.ts +44 -0
  144. package/templates/nest-api/files/src/media/services/index.ts +2 -0
  145. package/templates/nest-api/files/src/media/services/media.service.ts +29 -0
  146. package/templates/nest-api/files/src/media/services/s3.service.ts +168 -0
  147. package/templates/nest-api/files/src/notifications/controllers/index.ts +1 -0
  148. package/templates/nest-api/files/src/notifications/controllers/notifications.controller.ts +72 -0
  149. package/templates/nest-api/files/src/notifications/dto/index.ts +1 -0
  150. package/templates/nest-api/files/src/notifications/dto/notification.dto.ts +11 -0
  151. package/templates/nest-api/files/src/notifications/entity/index.ts +1 -0
  152. package/templates/nest-api/files/src/notifications/entity/notification.entity.ts +37 -0
  153. package/templates/nest-api/files/src/notifications/notifications.module.ts +11 -0
  154. package/templates/nest-api/files/src/notifications/services/index.ts +1 -0
  155. package/templates/nest-api/files/src/notifications/services/notifications.service.ts +98 -0
  156. package/templates/nest-api/files/src/realtime/__tests__/events.gateway.spec.ts +88 -0
  157. package/templates/nest-api/files/src/realtime/__tests__/rooms.spec.ts +29 -0
  158. package/templates/nest-api/files/src/realtime/__tests__/ws-permissions.guard.spec.ts +54 -0
  159. package/templates/nest-api/files/src/realtime/adapters/redis-io.adapter.ts +57 -0
  160. package/templates/nest-api/files/src/realtime/constants/index.ts +68 -0
  161. package/templates/nest-api/files/src/realtime/decorators/index.ts +1 -0
  162. package/templates/nest-api/files/src/realtime/decorators/ws-permissions.decorator.ts +14 -0
  163. package/templates/nest-api/files/src/realtime/gateways/events.gateway.ts +187 -0
  164. package/templates/nest-api/files/src/realtime/guards/index.ts +2 -0
  165. package/templates/nest-api/files/src/realtime/guards/ws-auth.guard.ts +20 -0
  166. package/templates/nest-api/files/src/realtime/guards/ws-permissions.guard.ts +35 -0
  167. package/templates/nest-api/files/src/realtime/misc/session-from-socket.ts +54 -0
  168. package/templates/nest-api/files/src/realtime/realtime.module.ts +26 -0
  169. package/templates/nest-api/files/src/realtime/services/presence.service.ts +56 -0
  170. package/templates/nest-api/files/src/realtime/services/realtime.service.ts +72 -0
  171. package/templates/nest-api/files/src/users/__tests__/current-user.controller.spec.ts +77 -0
  172. package/templates/nest-api/files/src/users/__tests__/current-user.service.spec.ts +159 -0
  173. package/templates/nest-api/files/src/users/__tests__/user.entity.spec.ts +175 -0
  174. package/templates/nest-api/files/src/users/__tests__/users.controller.spec.ts +89 -0
  175. package/templates/nest-api/files/src/users/__tests__/users.service.spec.ts +398 -0
  176. package/templates/nest-api/files/src/users/controllers/current-user.controller.ts +82 -0
  177. package/templates/nest-api/files/src/users/controllers/index.ts +2 -0
  178. package/templates/nest-api/files/src/users/controllers/users.controller.ts +40 -0
  179. package/templates/nest-api/files/src/users/decorators/index.ts +1 -0
  180. package/templates/nest-api/files/src/users/decorators/permissions.decorator.ts +24 -0
  181. package/templates/nest-api/files/src/users/dto/avatar.dto.ts +13 -0
  182. package/templates/nest-api/files/src/users/dto/filters.dto.ts +8 -0
  183. package/templates/nest-api/files/src/users/dto/index.ts +2 -0
  184. package/templates/nest-api/files/src/users/entity/index.ts +1 -0
  185. package/templates/nest-api/files/src/users/entity/user.entity.ts +53 -0
  186. package/templates/nest-api/files/src/users/services/current-user.service.ts +46 -0
  187. package/templates/nest-api/files/src/users/services/index.ts +2 -0
  188. package/templates/nest-api/files/src/users/services/users.service.ts +88 -0
  189. package/templates/nest-api/files/src/users/users.module.ts +10 -0
  190. package/templates/nest-api/files/test/app.spec.ts +102 -0
  191. package/templates/nest-api/files/test/auth.spec.ts +108 -0
  192. package/templates/nest-api/files/test/current-user.spec.ts +154 -0
  193. package/templates/nest-api/files/test/factory/app.ts +172 -0
  194. package/templates/nest-api/files/test/factory/constants.ts +22 -0
  195. package/templates/nest-api/files/test/factory/dto.ts +27 -0
  196. package/templates/nest-api/files/test/factory/entity.ts +70 -0
  197. package/templates/nest-api/files/test/factory/gen.ts +75 -0
  198. package/templates/nest-api/files/test/factory/util.ts +21 -0
  199. package/templates/nest-api/files/test/notifications.spec.ts +176 -0
  200. package/templates/nest-api/files/test/rate-limit.spec.ts +180 -0
  201. package/templates/nest-api/files/test/setup.ts +1 -0
  202. package/templates/nest-api/files/test/users.spec.ts +437 -0
  203. package/templates/nest-api/files/tsconfig.build.json +13 -0
  204. package/templates/nest-api/files/tsconfig.json +35 -0
  205. package/templates/nest-api/files/vitest.config.ts +53 -0
  206. package/templates/nest-api/template.json +452 -0
  207. package/templates/node-worker/files/.bin/db.ts +123 -0
  208. package/templates/node-worker/files/.bin/helpers.ts +49 -0
  209. package/templates/node-worker/files/.bin/secrets.ts +68 -0
  210. package/templates/node-worker/files/.dockerignore +5 -0
  211. package/templates/node-worker/files/.env.example +21 -0
  212. package/templates/node-worker/files/.forgejo/workflows/build-and-push.yml +41 -0
  213. package/templates/node-worker/files/.nvmrc +1 -0
  214. package/templates/node-worker/files/.prettierignore +4 -0
  215. package/templates/node-worker/files/.prettierrc +8 -0
  216. package/templates/node-worker/files/.yarnrc.yml +5 -0
  217. package/templates/node-worker/files/Dockerfile +63 -0
  218. package/templates/node-worker/files/Dockerfile.dev +59 -0
  219. package/templates/node-worker/files/_gitignore +48 -0
  220. package/templates/node-worker/files/ecosystem.config.cjs +10 -0
  221. package/templates/node-worker/files/eslint.config.js +37 -0
  222. package/templates/node-worker/files/package.json +67 -0
  223. package/templates/node-worker/files/prisma/_gitignore +1 -0
  224. package/templates/node-worker/files/prisma/schema/base.prisma +36 -0
  225. package/templates/node-worker/files/prisma.config.ts +13 -0
  226. package/templates/node-worker/files/src/config.ts +55 -0
  227. package/templates/node-worker/files/src/db.ts +118 -0
  228. package/templates/node-worker/files/src/index.ts +7 -0
  229. package/templates/node-worker/files/src/logger.ts +45 -0
  230. package/templates/node-worker/files/src/types.ts +23 -0
  231. package/templates/node-worker/files/src/utils/browser.ts +16 -0
  232. package/templates/node-worker/files/src/utils/date.ts +68 -0
  233. package/templates/node-worker/files/src/utils/helpers.ts +17 -0
  234. package/templates/node-worker/files/src/utils/number.ts +47 -0
  235. package/templates/node-worker/files/src/utils/worker.ts +29 -0
  236. package/templates/node-worker/files/src/workers/constants.ts +6 -0
  237. package/templates/node-worker/files/src/workers/example/index.ts +27 -0
  238. package/templates/node-worker/files/src/workers/index.ts +1 -0
  239. package/templates/node-worker/files/tsconfig.json +30 -0
  240. package/templates/node-worker/template.json +135 -0
  241. package/templates/py-worker/files/.dockerignore +176 -0
  242. package/templates/py-worker/files/.env.example +20 -0
  243. package/templates/py-worker/files/.forgejo/workflows/build-amd-push.yml +39 -0
  244. package/templates/py-worker/files/Dockerfile +10 -0
  245. package/templates/py-worker/files/README.md +3 -0
  246. package/templates/py-worker/files/_gitignore +176 -0
  247. package/templates/py-worker/files/config.py +89 -0
  248. package/templates/py-worker/files/models/decorators.py +42 -0
  249. package/templates/py-worker/files/models/enums.py +32 -0
  250. package/templates/py-worker/files/models/mixins.py +61 -0
  251. package/templates/py-worker/files/requirements.txt +18 -0
  252. package/templates/py-worker/files/utils/db.py +67 -0
  253. package/templates/py-worker/files/utils/logger.py +97 -0
  254. package/templates/py-worker/files/utils/misc.py +45 -0
  255. package/templates/py-worker/files/utils/worker.py +80 -0
  256. package/templates/py-worker/files/worker.py +41 -0
  257. package/templates/py-worker/files/workers/hello/process.py +22 -0
  258. package/templates/py-worker/template.json +87 -0
  259. package/templates/react-app/README.md +78 -0
  260. package/templates/react-app/derive.sh +128 -0
  261. package/templates/react-app/files/.bin/openapi.js +32 -0
  262. package/templates/react-app/files/.bin/secrets.js +47 -0
  263. package/templates/react-app/files/.dockerignore +16 -0
  264. package/templates/react-app/files/.editorconfig +12 -0
  265. package/templates/react-app/files/.env.example +34 -0
  266. package/templates/react-app/files/.forgejo/workflows/build-and-push.yml +55 -0
  267. package/templates/react-app/files/.forgejo/workflows/ci.yml +39 -0
  268. package/templates/react-app/files/.husky/pre-commit +1 -0
  269. package/templates/react-app/files/.nvmrc +1 -0
  270. package/templates/react-app/files/.prettierignore +6 -0
  271. package/templates/react-app/files/.prettierrc +9 -0
  272. package/templates/react-app/files/.yarnrc.yml +2 -0
  273. package/templates/react-app/files/Dockerfile +52 -0
  274. package/templates/react-app/files/_gitignore +33 -0
  275. package/templates/react-app/files/build.sh +53 -0
  276. package/templates/react-app/files/docker/nginx.conf +134 -0
  277. package/templates/react-app/files/eslint.config.mjs +75 -0
  278. package/templates/react-app/files/index.html +62 -0
  279. package/templates/react-app/files/openapi-ts.config.ts +22 -0
  280. package/templates/react-app/files/package.json +80 -0
  281. package/templates/react-app/files/src/app/nav.tsx +32 -0
  282. package/templates/react-app/files/src/app/providers.tsx +39 -0
  283. package/templates/react-app/files/src/app/router.tsx +142 -0
  284. package/templates/react-app/files/src/app/store.ts +20 -0
  285. package/templates/react-app/files/src/features/auth/components/auth-card.tsx +23 -0
  286. package/templates/react-app/files/src/features/auth/components/checkbox-field.tsx +32 -0
  287. package/templates/react-app/files/src/features/auth/components/google-button.tsx +41 -0
  288. package/templates/react-app/files/src/features/auth/components/or-divider.tsx +9 -0
  289. package/templates/react-app/files/src/features/auth/components/password-field.tsx +70 -0
  290. package/templates/react-app/files/src/features/auth/components/submit-button.tsx +16 -0
  291. package/templates/react-app/files/src/features/auth/lib/auth-actions.ts +68 -0
  292. package/templates/react-app/files/src/features/auth/lib/password-strength.ts +20 -0
  293. package/templates/react-app/files/src/features/auth/lib/redirect.ts +6 -0
  294. package/templates/react-app/files/src/features/auth/lib/schemas.ts +37 -0
  295. package/templates/react-app/files/src/features/seo/routes.ts +65 -0
  296. package/templates/react-app/files/src/lib/env.ts +28 -0
  297. package/templates/react-app/files/src/lib/paths.ts +58 -0
  298. package/templates/react-app/files/src/main.tsx +19 -0
  299. package/templates/react-app/files/src/routes/admin/home.tsx +24 -0
  300. package/templates/react-app/files/src/routes/auth/forgot-password.tsx +93 -0
  301. package/templates/react-app/files/src/routes/auth/login.tsx +87 -0
  302. package/templates/react-app/files/src/routes/auth/reset-password.tsx +83 -0
  303. package/templates/react-app/files/src/routes/auth/signup.tsx +117 -0
  304. package/templates/react-app/files/src/routes/auth/verified.tsx +31 -0
  305. package/templates/react-app/files/src/routes/auth/verify-email.tsx +97 -0
  306. package/templates/react-app/files/src/routes/home.tsx +17 -0
  307. package/templates/react-app/files/src/routes/legal/privacy.tsx +26 -0
  308. package/templates/react-app/files/src/routes/legal/terms.tsx +28 -0
  309. package/templates/react-app/files/src/routes/marketing/home.tsx +18 -0
  310. package/templates/react-app/files/src/routes/settings.tsx +10 -0
  311. package/templates/react-app/files/src/shared/api/base-api.ts +24 -0
  312. package/templates/react-app/files/src/shared/api/base-query.ts +29 -0
  313. package/templates/react-app/files/src/shared/api/config.ts +21 -0
  314. package/templates/react-app/files/src/shared/api/index.ts +15 -0
  315. package/templates/react-app/files/src/shared/api/realtime-events.ts +34 -0
  316. package/templates/react-app/files/src/shared/api/socket.ts +41 -0
  317. package/templates/react-app/files/src/shared/api/store.ts +39 -0
  318. package/templates/react-app/files/src/shared/api/upload-query.ts +115 -0
  319. package/templates/react-app/files/src/shared/auth/client.ts +27 -0
  320. package/templates/react-app/files/src/shared/auth/config.ts +28 -0
  321. package/templates/react-app/files/src/shared/auth/guards.tsx +75 -0
  322. package/templates/react-app/files/src/shared/auth/hooks.ts +13 -0
  323. package/templates/react-app/files/src/shared/auth/index.ts +20 -0
  324. package/templates/react-app/files/src/shared/auth/logout.ts +23 -0
  325. package/templates/react-app/files/src/shared/auth/provider.tsx +52 -0
  326. package/templates/react-app/files/src/shared/auth/redirect.ts +65 -0
  327. package/templates/react-app/files/src/shared/auth/slice.ts +54 -0
  328. package/templates/react-app/files/src/shared/hooks/index.ts +6 -0
  329. package/templates/react-app/files/src/shared/layouts/app-shell/app-shell.tsx +225 -0
  330. package/templates/react-app/files/src/shared/layouts/app-shell/user-menu.tsx +74 -0
  331. package/templates/react-app/files/src/shared/layouts/auth-layout.tsx +137 -0
  332. package/templates/react-app/files/src/shared/layouts/error-boundary.tsx +38 -0
  333. package/templates/react-app/files/src/shared/layouts/index.ts +23 -0
  334. package/templates/react-app/files/src/shared/layouts/marketing/marketing-layout.tsx +29 -0
  335. package/templates/react-app/files/src/shared/layouts/marketing/section-heading.tsx +38 -0
  336. package/templates/react-app/files/src/shared/layouts/marketing/section.tsx +30 -0
  337. package/templates/react-app/files/src/shared/layouts/states/app-shell-skeleton.tsx +47 -0
  338. package/templates/react-app/files/src/shared/layouts/states/empty.tsx +22 -0
  339. package/templates/react-app/files/src/shared/layouts/states/forbidden.tsx +14 -0
  340. package/templates/react-app/files/src/shared/layouts/states/loading.tsx +11 -0
  341. package/templates/react-app/files/src/shared/layouts/states/not-found.tsx +21 -0
  342. package/templates/react-app/files/src/shared/layouts/states/page-skeleton.tsx +39 -0
  343. package/templates/react-app/files/src/shared/layouts/states/route-fallback.tsx +26 -0
  344. package/templates/react-app/files/src/shared/layouts/states/state-screen.tsx +22 -0
  345. package/templates/react-app/files/src/shared/types/README.md +28 -0
  346. package/templates/react-app/files/src/shared/types/api/index.ts +9 -0
  347. package/templates/react-app/files/src/shared/types/api/zod.gen.ts +5 -0
  348. package/templates/react-app/files/src/shared/types/index.ts +4 -0
  349. package/templates/react-app/files/src/shared/ui/components/brand/logo.tsx +51 -0
  350. package/templates/react-app/files/src/shared/ui/components/brand/social-icons.tsx +37 -0
  351. package/templates/react-app/files/src/shared/ui/components/form/form.tsx +120 -0
  352. package/templates/react-app/files/src/shared/ui/components/ui/alert.tsx +25 -0
  353. package/templates/react-app/files/src/shared/ui/components/ui/button.tsx +43 -0
  354. package/templates/react-app/files/src/shared/ui/components/ui/calendar.tsx +78 -0
  355. package/templates/react-app/files/src/shared/ui/components/ui/card.tsx +44 -0
  356. package/templates/react-app/files/src/shared/ui/components/ui/checkbox.tsx +16 -0
  357. package/templates/react-app/files/src/shared/ui/components/ui/date-picker.tsx +76 -0
  358. package/templates/react-app/files/src/shared/ui/components/ui/input.tsx +22 -0
  359. package/templates/react-app/files/src/shared/ui/components/ui/label.tsx +19 -0
  360. package/templates/react-app/files/src/shared/ui/components/ui/modal.tsx +94 -0
  361. package/templates/react-app/files/src/shared/ui/components/ui/popover.tsx +31 -0
  362. package/templates/react-app/files/src/shared/ui/components/ui/progress.tsx +33 -0
  363. package/templates/react-app/files/src/shared/ui/components/ui/search-input.tsx +69 -0
  364. package/templates/react-app/files/src/shared/ui/components/ui/select.tsx +80 -0
  365. package/templates/react-app/files/src/shared/ui/components/ui/skeleton.tsx +12 -0
  366. package/templates/react-app/files/src/shared/ui/components/ui/sonner.tsx +23 -0
  367. package/templates/react-app/files/src/shared/ui/components/ui/spinner.tsx +8 -0
  368. package/templates/react-app/files/src/shared/ui/components/ui/textarea.tsx +22 -0
  369. package/templates/react-app/files/src/shared/ui/index.ts +45 -0
  370. package/templates/react-app/files/src/shared/ui/lib/utils.ts +7 -0
  371. package/templates/react-app/files/src/shared/ui/lib/zod-setup.ts +28 -0
  372. package/templates/react-app/files/src/shared/ui/meta/brand.ts +18 -0
  373. package/templates/react-app/files/src/shared/ui/meta/meta-context.ts +30 -0
  374. package/templates/react-app/files/src/shared/ui/meta/meta-provider.tsx +24 -0
  375. package/templates/react-app/files/src/shared/ui/meta/page-meta.tsx +69 -0
  376. package/templates/react-app/files/src/shared/ui/meta/use-meta-config.ts +11 -0
  377. package/templates/react-app/files/src/shared/ui/styles/globals.css +285 -0
  378. package/templates/react-app/files/src/shared/ui/styles/theme.css +48 -0
  379. package/templates/react-app/files/src/shared/ui/theme/init-script.ts +23 -0
  380. package/templates/react-app/files/src/shared/ui/theme/theme-colors.ts +14 -0
  381. package/templates/react-app/files/src/shared/ui/theme/theme-context.ts +60 -0
  382. package/templates/react-app/files/src/shared/ui/theme/theme-provider.tsx +101 -0
  383. package/templates/react-app/files/src/shared/ui/theme/theme-toggle.tsx +40 -0
  384. package/templates/react-app/files/src/shared/ui/theme/use-theme.ts +11 -0
  385. package/templates/react-app/files/src/vite-env.d.ts +13 -0
  386. package/templates/react-app/files/tsconfig.json +26 -0
  387. package/templates/react-app/files/tsconfig.node.json +25 -0
  388. package/templates/react-app/files/vite.config.ts +344 -0
  389. package/templates/react-app/overrides/src/routes/auth/forgot-password.tsx +93 -0
  390. package/templates/react-app/overrides/src/routes/auth/login.tsx +87 -0
  391. package/templates/react-app/overrides/src/routes/auth/signup.tsx +117 -0
  392. package/templates/react-app/overrides/src/routes/auth/verified.tsx +31 -0
  393. package/templates/react-app/overrides/src/routes/auth/verify-email.tsx +97 -0
  394. package/templates/react-app/overrides/src/shared/auth/config.ts +28 -0
  395. package/templates/react-app/overrides/src/shared/auth/guards.tsx +75 -0
  396. package/templates/react-app/overrides/src/shared/auth/index.ts +20 -0
  397. package/templates/react-app/overrides/src/shared/auth/redirect.ts +65 -0
  398. package/templates/react-app/overrides/src/shared/hooks/index.ts +6 -0
  399. package/templates/react-app/overrides/src/shared/types/README.md +28 -0
  400. package/templates/react-app/template.json +222 -0
  401. package/templates/react-monorepo/files/.dockerignore +19 -0
  402. package/templates/react-monorepo/files/.editorconfig +12 -0
  403. package/templates/react-monorepo/files/.forgejo/workflows/build-and-push.yml +98 -0
  404. package/templates/react-monorepo/files/.forgejo/workflows/ci.yml +35 -0
  405. package/templates/react-monorepo/files/.husky/pre-commit +1 -0
  406. package/templates/react-monorepo/files/.nvmrc +1 -0
  407. package/templates/react-monorepo/files/.prettierignore +6 -0
  408. package/templates/react-monorepo/files/.prettierrc +9 -0
  409. package/templates/react-monorepo/files/.yarnrc.yml +2 -0
  410. package/templates/react-monorepo/files/Dockerfile +90 -0
  411. package/templates/react-monorepo/files/_gitignore +35 -0
  412. package/templates/react-monorepo/files/apps/admin/.bin/secrets.js +57 -0
  413. package/templates/react-monorepo/files/apps/admin/.env.example +16 -0
  414. package/templates/react-monorepo/files/apps/admin/eslint.config.mjs +3 -0
  415. package/templates/react-monorepo/files/apps/admin/index.html +57 -0
  416. package/templates/react-monorepo/files/apps/admin/package.json +33 -0
  417. package/templates/react-monorepo/files/apps/admin/src/App.tsx +3 -0
  418. package/templates/react-monorepo/files/apps/admin/src/app/nav.tsx +14 -0
  419. package/templates/react-monorepo/files/apps/admin/src/app/providers.tsx +42 -0
  420. package/templates/react-monorepo/files/apps/admin/src/app/router.tsx +50 -0
  421. package/templates/react-monorepo/files/apps/admin/src/app/store.ts +15 -0
  422. package/templates/react-monorepo/files/apps/admin/src/lib/env.ts +12 -0
  423. package/templates/react-monorepo/files/apps/admin/src/main.tsx +14 -0
  424. package/templates/react-monorepo/files/apps/admin/src/routes/home.tsx +17 -0
  425. package/templates/react-monorepo/files/apps/admin/src/routes/settings.tsx +10 -0
  426. package/templates/react-monorepo/files/apps/admin/src/vite-env.d.ts +14 -0
  427. package/templates/react-monorepo/files/apps/admin/tsconfig.json +10 -0
  428. package/templates/react-monorepo/files/apps/admin/vite.config.ts +13 -0
  429. package/templates/react-monorepo/files/apps/auth/.bin/secrets.js +57 -0
  430. package/templates/react-monorepo/files/apps/auth/.env.example +16 -0
  431. package/templates/react-monorepo/files/apps/auth/eslint.config.mjs +3 -0
  432. package/templates/react-monorepo/files/apps/auth/index.html +57 -0
  433. package/templates/react-monorepo/files/apps/auth/package.json +32 -0
  434. package/templates/react-monorepo/files/apps/auth/src/App.tsx +3 -0
  435. package/templates/react-monorepo/files/apps/auth/src/app/providers.tsx +42 -0
  436. package/templates/react-monorepo/files/apps/auth/src/app/router.tsx +19 -0
  437. package/templates/react-monorepo/files/apps/auth/src/app/store.ts +15 -0
  438. package/templates/react-monorepo/files/apps/auth/src/components/auth-card.tsx +23 -0
  439. package/templates/react-monorepo/files/apps/auth/src/components/checkbox-field.tsx +32 -0
  440. package/templates/react-monorepo/files/apps/auth/src/components/google-button.tsx +41 -0
  441. package/templates/react-monorepo/files/apps/auth/src/components/or-divider.tsx +9 -0
  442. package/templates/react-monorepo/files/apps/auth/src/components/password-field.tsx +70 -0
  443. package/templates/react-monorepo/files/apps/auth/src/components/submit-button.tsx +16 -0
  444. package/templates/react-monorepo/files/apps/auth/src/lib/auth-actions.ts +68 -0
  445. package/templates/react-monorepo/files/apps/auth/src/lib/env.ts +8 -0
  446. package/templates/react-monorepo/files/apps/auth/src/lib/password-strength.ts +20 -0
  447. package/templates/react-monorepo/files/apps/auth/src/lib/redirect.ts +6 -0
  448. package/templates/react-monorepo/files/apps/auth/src/lib/schemas.ts +37 -0
  449. package/templates/react-monorepo/files/apps/auth/src/main.tsx +14 -0
  450. package/templates/react-monorepo/files/apps/auth/src/routes/forgot-password.tsx +91 -0
  451. package/templates/react-monorepo/files/apps/auth/src/routes/login.tsx +82 -0
  452. package/templates/react-monorepo/files/apps/auth/src/routes/reset-password.tsx +83 -0
  453. package/templates/react-monorepo/files/apps/auth/src/routes/signup.tsx +117 -0
  454. package/templates/react-monorepo/files/apps/auth/src/routes/verified.tsx +30 -0
  455. package/templates/react-monorepo/files/apps/auth/src/routes/verify-email.tsx +96 -0
  456. package/templates/react-monorepo/files/apps/auth/src/vite-env.d.ts +14 -0
  457. package/templates/react-monorepo/files/apps/auth/tsconfig.json +10 -0
  458. package/templates/react-monorepo/files/apps/auth/vite.config.ts +13 -0
  459. package/templates/react-monorepo/files/apps/client/.bin/secrets.js +57 -0
  460. package/templates/react-monorepo/files/apps/client/.env.example +16 -0
  461. package/templates/react-monorepo/files/apps/client/eslint.config.mjs +3 -0
  462. package/templates/react-monorepo/files/apps/client/index.html +57 -0
  463. package/templates/react-monorepo/files/apps/client/package.json +35 -0
  464. package/templates/react-monorepo/files/apps/client/src/App.tsx +3 -0
  465. package/templates/react-monorepo/files/apps/client/src/app/nav.tsx +14 -0
  466. package/templates/react-monorepo/files/apps/client/src/app/providers.tsx +42 -0
  467. package/templates/react-monorepo/files/apps/client/src/app/router.tsx +50 -0
  468. package/templates/react-monorepo/files/apps/client/src/app/store.ts +15 -0
  469. package/templates/react-monorepo/files/apps/client/src/lib/env.ts +12 -0
  470. package/templates/react-monorepo/files/apps/client/src/main.tsx +19 -0
  471. package/templates/react-monorepo/files/apps/client/src/routes/home.tsx +17 -0
  472. package/templates/react-monorepo/files/apps/client/src/routes/settings.tsx +10 -0
  473. package/templates/react-monorepo/files/apps/client/src/vite-env.d.ts +15 -0
  474. package/templates/react-monorepo/files/apps/client/tsconfig.json +10 -0
  475. package/templates/react-monorepo/files/apps/client/vite.config.ts +120 -0
  476. package/templates/react-monorepo/files/apps/landing/.bin/secrets.js +57 -0
  477. package/templates/react-monorepo/files/apps/landing/.env.example +20 -0
  478. package/templates/react-monorepo/files/apps/landing/eslint.config.mjs +3 -0
  479. package/templates/react-monorepo/files/apps/landing/index.html +51 -0
  480. package/templates/react-monorepo/files/apps/landing/package.json +33 -0
  481. package/templates/react-monorepo/files/apps/landing/src/App.tsx +3 -0
  482. package/templates/react-monorepo/files/apps/landing/src/app/providers.tsx +42 -0
  483. package/templates/react-monorepo/files/apps/landing/src/app/router.tsx +21 -0
  484. package/templates/react-monorepo/files/apps/landing/src/app/store.ts +18 -0
  485. package/templates/react-monorepo/files/apps/landing/src/features/seo/routes.ts +65 -0
  486. package/templates/react-monorepo/files/apps/landing/src/lib/env.ts +10 -0
  487. package/templates/react-monorepo/files/apps/landing/src/main.tsx +14 -0
  488. package/templates/react-monorepo/files/apps/landing/src/routes/home.tsx +18 -0
  489. package/templates/react-monorepo/files/apps/landing/src/vite-env.d.ts +15 -0
  490. package/templates/react-monorepo/files/apps/landing/tsconfig.json +10 -0
  491. package/templates/react-monorepo/files/apps/landing/vite.config.ts +169 -0
  492. package/templates/react-monorepo/files/build.sh +61 -0
  493. package/templates/react-monorepo/files/docker/nginx.conf +136 -0
  494. package/templates/react-monorepo/files/package.json +37 -0
  495. package/templates/react-monorepo/files/packages/.gitkeep +0 -0
  496. package/templates/react-monorepo/files/packages/api/eslint.config.mjs +3 -0
  497. package/templates/react-monorepo/files/packages/api/package.json +30 -0
  498. package/templates/react-monorepo/files/packages/api/src/base-api.ts +24 -0
  499. package/templates/react-monorepo/files/packages/api/src/base-query.ts +29 -0
  500. package/templates/react-monorepo/files/packages/api/src/config.ts +21 -0
  501. package/templates/react-monorepo/files/packages/api/src/index.ts +15 -0
  502. package/templates/react-monorepo/files/packages/api/src/realtime-events.ts +34 -0
  503. package/templates/react-monorepo/files/packages/api/src/socket.ts +41 -0
  504. package/templates/react-monorepo/files/packages/api/src/store.ts +39 -0
  505. package/templates/react-monorepo/files/packages/api/src/upload-query.ts +115 -0
  506. package/templates/react-monorepo/files/packages/api/tsconfig.json +4 -0
  507. package/templates/react-monorepo/files/packages/auth/eslint.config.mjs +3 -0
  508. package/templates/react-monorepo/files/packages/auth/package.json +33 -0
  509. package/templates/react-monorepo/files/packages/auth/src/client.ts +27 -0
  510. package/templates/react-monorepo/files/packages/auth/src/config.ts +22 -0
  511. package/templates/react-monorepo/files/packages/auth/src/guards.tsx +61 -0
  512. package/templates/react-monorepo/files/packages/auth/src/hooks.ts +13 -0
  513. package/templates/react-monorepo/files/packages/auth/src/index.ts +20 -0
  514. package/templates/react-monorepo/files/packages/auth/src/logout.ts +23 -0
  515. package/templates/react-monorepo/files/packages/auth/src/provider.tsx +52 -0
  516. package/templates/react-monorepo/files/packages/auth/src/redirect.ts +37 -0
  517. package/templates/react-monorepo/files/packages/auth/src/slice.ts +54 -0
  518. package/templates/react-monorepo/files/packages/auth/tsconfig.json +4 -0
  519. package/templates/react-monorepo/files/packages/config/README.md +47 -0
  520. package/templates/react-monorepo/files/packages/config/eslint/base.mjs +83 -0
  521. package/templates/react-monorepo/files/packages/config/package.json +37 -0
  522. package/templates/react-monorepo/files/packages/config/tailwind/theme.css +48 -0
  523. package/templates/react-monorepo/files/packages/config/tsconfig/base.json +22 -0
  524. package/templates/react-monorepo/files/packages/config/tsconfig/node.json +9 -0
  525. package/templates/react-monorepo/files/packages/config/tsconfig/react.json +9 -0
  526. package/templates/react-monorepo/files/packages/config/vite/index.d.ts +18 -0
  527. package/templates/react-monorepo/files/packages/config/vite/index.mjs +129 -0
  528. package/templates/react-monorepo/files/packages/hooks/eslint.config.mjs +3 -0
  529. package/templates/react-monorepo/files/packages/hooks/package.json +16 -0
  530. package/templates/react-monorepo/files/packages/hooks/src/index.ts +3 -0
  531. package/templates/react-monorepo/files/packages/hooks/tsconfig.json +4 -0
  532. package/templates/react-monorepo/files/packages/layouts/eslint.config.mjs +3 -0
  533. package/templates/react-monorepo/files/packages/layouts/package.json +30 -0
  534. package/templates/react-monorepo/files/packages/layouts/src/app-shell/app-shell.tsx +225 -0
  535. package/templates/react-monorepo/files/packages/layouts/src/app-shell/user-menu.tsx +74 -0
  536. package/templates/react-monorepo/files/packages/layouts/src/auth-layout.tsx +137 -0
  537. package/templates/react-monorepo/files/packages/layouts/src/error-boundary.tsx +38 -0
  538. package/templates/react-monorepo/files/packages/layouts/src/index.ts +23 -0
  539. package/templates/react-monorepo/files/packages/layouts/src/marketing/marketing-layout.tsx +29 -0
  540. package/templates/react-monorepo/files/packages/layouts/src/marketing/section-heading.tsx +38 -0
  541. package/templates/react-monorepo/files/packages/layouts/src/marketing/section.tsx +30 -0
  542. package/templates/react-monorepo/files/packages/layouts/src/states/app-shell-skeleton.tsx +47 -0
  543. package/templates/react-monorepo/files/packages/layouts/src/states/empty.tsx +22 -0
  544. package/templates/react-monorepo/files/packages/layouts/src/states/forbidden.tsx +14 -0
  545. package/templates/react-monorepo/files/packages/layouts/src/states/loading.tsx +11 -0
  546. package/templates/react-monorepo/files/packages/layouts/src/states/not-found.tsx +21 -0
  547. package/templates/react-monorepo/files/packages/layouts/src/states/page-skeleton.tsx +39 -0
  548. package/templates/react-monorepo/files/packages/layouts/src/states/route-fallback.tsx +26 -0
  549. package/templates/react-monorepo/files/packages/layouts/src/states/state-screen.tsx +22 -0
  550. package/templates/react-monorepo/files/packages/layouts/tsconfig.json +4 -0
  551. package/templates/react-monorepo/files/packages/types/.bin/openapi.js +32 -0
  552. package/templates/react-monorepo/files/packages/types/.bin/secrets.js +57 -0
  553. package/templates/react-monorepo/files/packages/types/README.md +28 -0
  554. package/templates/react-monorepo/files/packages/types/eslint.config.mjs +3 -0
  555. package/templates/react-monorepo/files/packages/types/openapi-ts.config.ts +22 -0
  556. package/templates/react-monorepo/files/packages/types/package.json +29 -0
  557. package/templates/react-monorepo/files/packages/types/src/index.ts +4 -0
  558. package/templates/react-monorepo/files/packages/types/src/types/api/index.ts +9 -0
  559. package/templates/react-monorepo/files/packages/types/src/types/api/zod.gen.ts +5 -0
  560. package/templates/react-monorepo/files/packages/types/tsconfig.json +7 -0
  561. package/templates/react-monorepo/files/packages/ui/components.json +21 -0
  562. package/templates/react-monorepo/files/packages/ui/eslint.config.mjs +13 -0
  563. package/templates/react-monorepo/files/packages/ui/package.json +44 -0
  564. package/templates/react-monorepo/files/packages/ui/src/components/brand/logo.tsx +51 -0
  565. package/templates/react-monorepo/files/packages/ui/src/components/brand/social-icons.tsx +37 -0
  566. package/templates/react-monorepo/files/packages/ui/src/components/form/form.tsx +120 -0
  567. package/templates/react-monorepo/files/packages/ui/src/components/ui/alert.tsx +25 -0
  568. package/templates/react-monorepo/files/packages/ui/src/components/ui/button.tsx +43 -0
  569. package/templates/react-monorepo/files/packages/ui/src/components/ui/calendar.tsx +78 -0
  570. package/templates/react-monorepo/files/packages/ui/src/components/ui/card.tsx +44 -0
  571. package/templates/react-monorepo/files/packages/ui/src/components/ui/checkbox.tsx +16 -0
  572. package/templates/react-monorepo/files/packages/ui/src/components/ui/date-picker.tsx +76 -0
  573. package/templates/react-monorepo/files/packages/ui/src/components/ui/input.tsx +22 -0
  574. package/templates/react-monorepo/files/packages/ui/src/components/ui/label.tsx +19 -0
  575. package/templates/react-monorepo/files/packages/ui/src/components/ui/modal.tsx +94 -0
  576. package/templates/react-monorepo/files/packages/ui/src/components/ui/popover.tsx +31 -0
  577. package/templates/react-monorepo/files/packages/ui/src/components/ui/progress.tsx +33 -0
  578. package/templates/react-monorepo/files/packages/ui/src/components/ui/search-input.tsx +69 -0
  579. package/templates/react-monorepo/files/packages/ui/src/components/ui/select.tsx +80 -0
  580. package/templates/react-monorepo/files/packages/ui/src/components/ui/skeleton.tsx +12 -0
  581. package/templates/react-monorepo/files/packages/ui/src/components/ui/sonner.tsx +23 -0
  582. package/templates/react-monorepo/files/packages/ui/src/components/ui/spinner.tsx +8 -0
  583. package/templates/react-monorepo/files/packages/ui/src/components/ui/textarea.tsx +22 -0
  584. package/templates/react-monorepo/files/packages/ui/src/index.ts +45 -0
  585. package/templates/react-monorepo/files/packages/ui/src/lib/utils.ts +7 -0
  586. package/templates/react-monorepo/files/packages/ui/src/lib/zod-setup.ts +28 -0
  587. package/templates/react-monorepo/files/packages/ui/src/meta/brand.ts +18 -0
  588. package/templates/react-monorepo/files/packages/ui/src/meta/meta-context.ts +30 -0
  589. package/templates/react-monorepo/files/packages/ui/src/meta/meta-provider.tsx +24 -0
  590. package/templates/react-monorepo/files/packages/ui/src/meta/page-meta.tsx +69 -0
  591. package/templates/react-monorepo/files/packages/ui/src/meta/use-meta-config.ts +11 -0
  592. package/templates/react-monorepo/files/packages/ui/src/styles/globals.css +285 -0
  593. package/templates/react-monorepo/files/packages/ui/src/theme/init-script.ts +23 -0
  594. package/templates/react-monorepo/files/packages/ui/src/theme/theme-colors.ts +14 -0
  595. package/templates/react-monorepo/files/packages/ui/src/theme/theme-context.ts +60 -0
  596. package/templates/react-monorepo/files/packages/ui/src/theme/theme-provider.tsx +101 -0
  597. package/templates/react-monorepo/files/packages/ui/src/theme/theme-toggle.tsx +40 -0
  598. package/templates/react-monorepo/files/packages/ui/src/theme/use-theme.ts +11 -0
  599. package/templates/react-monorepo/files/packages/ui/tsconfig.json +9 -0
  600. package/templates/react-monorepo/files/turbo.json +24 -0
  601. package/templates/react-monorepo/template.json +230 -0
@@ -0,0 +1,29 @@
1
+ import { fetchBaseQuery, type BaseQueryFn, type FetchArgs, type FetchBaseQueryError } from '@reduxjs/toolkit/query';
2
+ import { getApiBaseUrl, notifyUnauthorized } from './config';
3
+
4
+ const rawBaseQuery = fetchBaseQuery({
5
+ baseUrl: '',
6
+ credentials: 'include'
7
+ });
8
+
9
+ /**
10
+ * Shared base query: sends the Better Auth session cookie (credentials:'include'),
11
+ * prefixes the configured API base URL, and routes 401s to the app's re-auth
12
+ * handler (registered via configureApi) — the single chokepoint for session expiry.
13
+ */
14
+ export const baseQueryWithReauth: BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryError> = async (
15
+ args,
16
+ api,
17
+ extraOptions
18
+ ) => {
19
+ const base = getApiBaseUrl();
20
+ const withBase = typeof args === 'string' ? `${base}${args}` : { ...args, url: `${base}${args.url}` };
21
+
22
+ const result = await rawBaseQuery(withBase, api, extraOptions);
23
+
24
+ if (result.error && result.error.status === 401) {
25
+ notifyUnauthorized();
26
+ }
27
+
28
+ return result;
29
+ };
@@ -0,0 +1,21 @@
1
+ // Runtime configuration for @/shared/api. The consuming app calls configureApi()
2
+ // once at startup (before creating the store or issuing requests) to inject the
3
+ // backend base URL and the 401 -> re-auth redirect handler (from @/shared/auth).
4
+
5
+ let apiBaseUrl = '';
6
+ let onUnauthorized: () => void = () => {};
7
+
8
+ export function configureApi(options: { baseUrl: string; onUnauthorized?: () => void }): void {
9
+ apiBaseUrl = options.baseUrl.replace(/\/$/, '');
10
+ if (options.onUnauthorized) {
11
+ onUnauthorized = options.onUnauthorized;
12
+ }
13
+ }
14
+
15
+ export function getApiBaseUrl(): string {
16
+ return apiBaseUrl;
17
+ }
18
+
19
+ export function notifyUnauthorized(): void {
20
+ onUnauthorized();
21
+ }
@@ -0,0 +1,15 @@
1
+ // @/shared/api — shared Redux store + RTK Query base + Socket.IO client.
2
+ export { baseApi } from './base-api';
3
+ export { baseQueryWithReauth } from './base-query';
4
+ export { uploadBaseQuery } from './upload-query';
5
+ export type { UploadArgs } from './upload-query';
6
+ export { configureApi, getApiBaseUrl, notifyUnauthorized } from './config';
7
+ export { makeStore } from './store';
8
+ export type { AppStore } from './store';
9
+ // @feature:start realtime
10
+ export { connectSocket, getSocket, disconnectSocket, joinRoom, leaveRoom } from './socket';
11
+ export { ServerEvent, ClientEvent, FEED_ROOM, channelRoom } from './realtime-events';
12
+ // @feature:end
13
+
14
+ // Convenience re-exports so apps get their Redux bindings from one place.
15
+ export { Provider, useDispatch, useSelector, useStore } from 'react-redux';
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Frontend mirror of the backend realtime wire contract
3
+ * (backend: `src/realtime/constants`). Keep these names in lockstep with the
4
+ * server — they are the single source of truth for what the socket speaks, and
5
+ * a rename on one side that is not mirrored on the other fails silently: the
6
+ * listener simply never fires.
7
+ */
8
+
9
+ /** Server → client event names. */
10
+ export const ServerEvent = {
11
+ /** Presence roster changed. */
12
+ PRESENCE_UPDATE: 'presence:update',
13
+ /** Someone started/stopped typing in a room. */
14
+ TYPING: 'activity:typing',
15
+ /** Someone is viewing a resource. */
16
+ VIEWING: 'activity:viewing',
17
+ /** A user-directed notification. Scoped to `user:<id>`. */
18
+ NOTIFICATION: 'notification',
19
+ /** Something in a channel changed; payload is app-defined. */
20
+ CHANNEL_UPDATE: 'channel:update'
21
+ } as const;
22
+
23
+ /** Client → server event names. */
24
+ export const ClientEvent = {
25
+ JOIN_ROOM: 'room:join',
26
+ LEAVE_ROOM: 'room:leave',
27
+ TYPING: 'activity:typing',
28
+ VIEWING: 'activity:viewing'
29
+ } as const;
30
+
31
+ /** Firehose room — members see every broadcast on the default channel. */
32
+ export const FEED_ROOM = 'feed';
33
+ /** Room scoped to a single resource. */
34
+ export const channelRoom = (channelId: string) => `channel:${channelId}`;
@@ -0,0 +1,41 @@
1
+ import { io, type Socket } from 'socket.io-client';
2
+ import { ClientEvent } from './realtime-events';
3
+
4
+ let socket: Socket | null = null;
5
+
6
+ /**
7
+ * Connect the realtime Socket.IO client. The Better Auth session cookie is
8
+ * carried on the handshake via withCredentials. Feature event wiring + the RTK
9
+ * Query cache bridge land in Phase 2+.
10
+ */
11
+ export function connectSocket(url: string): Socket {
12
+ if (socket) {
13
+ if (!socket.connected) socket.connect();
14
+ return socket;
15
+ }
16
+ socket = io(url, {
17
+ withCredentials: true,
18
+ path: '/realtime',
19
+ autoConnect: true
20
+ });
21
+ return socket;
22
+ }
23
+
24
+ export function getSocket(): Socket | null {
25
+ return socket;
26
+ }
27
+
28
+ /** Join a server-side room (forum feed / thread). No-op until connected. */
29
+ export function joinRoom(room: string): void {
30
+ socket?.emit(ClientEvent.JOIN_ROOM, { room });
31
+ }
32
+
33
+ /** Leave a previously joined room. */
34
+ export function leaveRoom(room: string): void {
35
+ socket?.emit(ClientEvent.LEAVE_ROOM, { room });
36
+ }
37
+
38
+ export function disconnectSocket(): void {
39
+ socket?.disconnect();
40
+ socket = null;
41
+ }
@@ -0,0 +1,39 @@
1
+ import { configureStore, type Middleware, type Reducer } from '@reduxjs/toolkit';
2
+ import { setupListeners } from '@reduxjs/toolkit/query';
3
+ import { baseApi } from './base-api';
4
+
5
+ type MakeStoreOptions = {
6
+ /** App-specific slice reducers (e.g. authSlice, feature UI slices). */
7
+ reducer?: Record<string, Reducer>;
8
+ /** Extra middleware (e.g. the socket middleware). */
9
+ middleware?: Middleware[];
10
+ };
11
+
12
+ /**
13
+ * Create an app store wired with the shared RTK Query API + any app slices.
14
+ * Each app derives its own typed hooks from the returned store, e.g.:
15
+ * export const useAppDispatch = useDispatch.withTypes<typeof store.dispatch>();
16
+ * export const useAppSelector = useSelector.withTypes<ReturnType<typeof store.getState>>();
17
+ */
18
+ export function makeStore(options: MakeStoreOptions = {}) {
19
+ const store = configureStore({
20
+ reducer: {
21
+ [baseApi.reducerPath]: baseApi.reducer,
22
+ ...options.reducer
23
+ },
24
+ middleware: (getDefaultMiddleware) =>
25
+ getDefaultMiddleware({
26
+ serializableCheck: {
27
+ // Upload endpoints (see `uploadBaseQuery`) carry a FormData body and an
28
+ // `onProgress` callback in their args. Neither is serializable, and
29
+ // neither is ever persisted or replayed — only read once, in flight.
30
+ ignoredActionPaths: ['meta.arg.originalArgs.body', 'meta.arg.originalArgs.onProgress']
31
+ }
32
+ }).concat(baseApi.middleware, ...(options.middleware ?? []))
33
+ });
34
+
35
+ setupListeners(store.dispatch);
36
+ return store;
37
+ }
38
+
39
+ export type AppStore = ReturnType<typeof makeStore>;
@@ -0,0 +1,115 @@
1
+ import type { BaseQueryFn, FetchBaseQueryError } from '@reduxjs/toolkit/query';
2
+ import { getApiBaseUrl, notifyUnauthorized } from './config';
3
+
4
+ /** How long an upload may stall (no bytes acknowledged) before we give up. */
5
+ const DEFAULT_STALL_TIMEOUT_MS = 45_000;
6
+
7
+ export type UploadArgs = {
8
+ url: string;
9
+ body: FormData;
10
+ method?: 'POST' | 'PUT' | 'PATCH';
11
+ /** Called with 0-100 as the request body is written to the socket. */
12
+ onProgress?: (percent: number) => void;
13
+ /**
14
+ * Abort if no upload progress is observed for this many ms. Guards against a
15
+ * connection that opens, stalls mid-body and never errors on its own — the
16
+ * common failure on slow mobile uplinks.
17
+ */
18
+ stallTimeoutMs?: number;
19
+ };
20
+
21
+ /** RTK Query's own marker for an aborted-by-timeout request. */
22
+ const TIMEOUT_ERROR: FetchBaseQueryError = {
23
+ status: 'TIMEOUT_ERROR',
24
+ error: 'The upload stalled. Check your connection and try again.'
25
+ };
26
+
27
+ function parseBody(xhr: XMLHttpRequest): unknown {
28
+ const text = xhr.responseText;
29
+ if (!text) return undefined;
30
+ try {
31
+ return JSON.parse(text);
32
+ } catch {
33
+ return text;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Multipart uploads over XMLHttpRequest instead of `fetch`.
39
+ *
40
+ * `fetchBaseQuery` cannot report request-body progress (the Fetch API exposes no
41
+ * upload stream events), so a large file on a slow connection is indistinguishable
42
+ * from a hung request — the UI can only show an indefinite spinner. XHR still
43
+ * emits `upload.onprogress`, which lets callers render real progress and lets us
44
+ * abort a stalled transfer rather than waiting on the OS socket timeout.
45
+ *
46
+ * Mirrors `baseQueryWithReauth`: prefixes the configured API base URL, sends the
47
+ * session cookie, and routes 401s to the shared re-auth handler.
48
+ */
49
+ export const uploadBaseQuery: BaseQueryFn<UploadArgs, unknown, FetchBaseQueryError> = (args) =>
50
+ new Promise((resolve) => {
51
+ const { url, body, method = 'POST', onProgress, stallTimeoutMs = DEFAULT_STALL_TIMEOUT_MS } = args;
52
+ const xhr = new XMLHttpRequest();
53
+
54
+ let stallTimer: ReturnType<typeof setTimeout> | undefined;
55
+ let settled = false;
56
+
57
+ const cleanup = () => {
58
+ if (stallTimer) clearTimeout(stallTimer);
59
+ };
60
+
61
+ const settle = (result: { data: unknown } | { error: FetchBaseQueryError }) => {
62
+ if (settled) return;
63
+ settled = true;
64
+ cleanup();
65
+ resolve(result);
66
+ };
67
+
68
+ // Restarted on every progress event; only fires when the socket goes quiet.
69
+ const armStallTimer = () => {
70
+ if (stallTimer) clearTimeout(stallTimer);
71
+ stallTimer = setTimeout(() => {
72
+ xhr.abort();
73
+ settle({ error: TIMEOUT_ERROR });
74
+ }, stallTimeoutMs);
75
+ };
76
+
77
+ xhr.open(method, `${getApiBaseUrl()}${url}`, true);
78
+ // Matches fetchBaseQuery's `credentials: 'include'` — Better Auth session cookie.
79
+ xhr.withCredentials = true;
80
+
81
+ xhr.upload.onprogress = (event) => {
82
+ armStallTimer();
83
+ if (!event.lengthComputable || !onProgress) return;
84
+ onProgress(Math.round((event.loaded / event.total) * 100));
85
+ };
86
+
87
+ // Body is fully written; the server is now working. Keep the stall guard
88
+ // running so a server that accepts the bytes and then hangs still fails.
89
+ xhr.upload.onload = () => {
90
+ onProgress?.(100);
91
+ armStallTimer();
92
+ };
93
+
94
+ xhr.onload = () => {
95
+ const data = parseBody(xhr);
96
+ if (xhr.status >= 200 && xhr.status < 300) {
97
+ settle({ data });
98
+ return;
99
+ }
100
+ if (xhr.status === 401) notifyUnauthorized();
101
+ settle({ error: { status: xhr.status, data } as FetchBaseQueryError });
102
+ };
103
+
104
+ xhr.onerror = () => {
105
+ settle({
106
+ error: { status: 'FETCH_ERROR', error: 'The upload failed. Check your connection and try again.' }
107
+ });
108
+ };
109
+
110
+ // Fires when the transfer is cut mid-body — the truncated-upload case.
111
+ xhr.onabort = () => settle({ error: TIMEOUT_ERROR });
112
+
113
+ armStallTimer();
114
+ xhr.send(body);
115
+ });
@@ -0,0 +1,27 @@
1
+ import { createAuthClient } from 'better-auth/react';
2
+ import { adminClient } from 'better-auth/client/plugins';
3
+
4
+ function makeClient(baseURL: string) {
5
+ // The admin plugin adds `authClient.admin.*` (list/ban/unban/setRole). The
6
+ // methods are always present but the backend enforces the caller's role, so
7
+ // including them here is harmless for non-admin apps.
8
+ return createAuthClient({ baseURL, plugins: [adminClient()] });
9
+ }
10
+
11
+ let client: ReturnType<typeof makeClient> | null = null;
12
+
13
+ /** Initialize the Better Auth client singleton (idempotent). */
14
+ export function initAuthClient(baseURL: string) {
15
+ client ??= makeClient(baseURL);
16
+ return client;
17
+ }
18
+
19
+ /** The Better Auth client. Throws if not initialized (call configureAuth first). */
20
+ export function getAuthClient() {
21
+ if (!client) {
22
+ throw new Error('Auth client not initialized. Call configureAuth({ authApiBaseUrl, ... }) at app startup.');
23
+ }
24
+ return client;
25
+ }
26
+
27
+ export type AuthClient = ReturnType<typeof makeClient>;
@@ -0,0 +1,28 @@
1
+ import { initAuthClient } from './client';
2
+ import { configureRedirects } from './redirect';
3
+
4
+ /**
5
+ * One-shot auth wiring, called once at startup (before rendering).
6
+ *
7
+ * There is no `authAppUrl` here, unlike the multi-app build of this stack: the
8
+ * auth screens are routes in this bundle, so login is a path within the same
9
+ * origin. `trustedOrigins` therefore defaults to this app's own origin — the
10
+ * complete answer unless you deliberately hand off to another host.
11
+ */
12
+ export function configureAuth(options: {
13
+ /** Backend Better Auth base URL (e.g. https://api.example.com/auth). */
14
+ authApiBaseUrl: string;
15
+ /** Route the login screen is mounted at. Defaults to `/login`. */
16
+ loginPath?: string;
17
+ /** Origins allowed as a post-login redirect target. Defaults to this origin. */
18
+ trustedOrigins?: string[];
19
+ /** Where a login with no `?redirect=` lands. */
20
+ defaultRedirect?: string;
21
+ }): void {
22
+ initAuthClient(options.authApiBaseUrl);
23
+ configureRedirects({
24
+ loginPath: options.loginPath,
25
+ trustedOrigins: options.trustedOrigins,
26
+ defaultRedirect: options.defaultRedirect
27
+ });
28
+ }
@@ -0,0 +1,75 @@
1
+ import type { ReactNode } from 'react';
2
+ import { Navigate, Outlet, useLocation } from 'react-router';
3
+ import { useAuth } from './hooks';
4
+ import { loginRedirect } from './redirect';
5
+
6
+ type GuardProps = {
7
+ children?: ReactNode;
8
+ /**
9
+ * Shown while the session is resolving.
10
+ *
11
+ * Required on purpose. This used to default to `null`, which meant the app
12
+ * rendered *nothing* for the entire session round trip — ~880 ms measured
13
+ * against a deployed API, and worse on mobile. Pass the shell skeleton so the
14
+ * frame is on screen while the session resolves; there is no correct case for
15
+ * gating a whole app behind a blank screen.
16
+ */
17
+ fallback: ReactNode;
18
+ };
19
+
20
+ /**
21
+ * Requires any authenticated session; otherwise sends the visitor to `/login`
22
+ * with a `?redirect=` back to where they were.
23
+ *
24
+ * `<Navigate>` rather than `window.location.assign`: the login screen is a route
25
+ * in this same bundle, so a full page load would re-download and re-boot the app
26
+ * to render a screen it already has. `replace` keeps the guarded URL out of
27
+ * history — without it, Back from the login screen bounces straight through the
28
+ * guard again.
29
+ */
30
+ export function ProtectedRoute({ children, fallback }: GuardProps) {
31
+ const { status } = useAuth();
32
+ const location = useLocation();
33
+
34
+ if (status === 'loading') return <>{fallback}</>;
35
+ if (status === 'unauthenticated') {
36
+ return <Navigate to={loginRedirect(location.pathname + location.search)} replace />;
37
+ }
38
+ return <>{children ?? <Outlet />}</>;
39
+ }
40
+
41
+ type RoleRouteProps = GuardProps & {
42
+ /** Roles allowed to view (any match). Compared against session.user.role. */
43
+ allow: string[];
44
+ /**
45
+ * Rendered when authenticated but lacking a required role (403). Required for
46
+ * the same reason as `fallback` — a silent blank 403 looks like a broken app.
47
+ */
48
+ forbidden: ReactNode;
49
+ };
50
+
51
+ /**
52
+ * Requires an authenticated session AND one of `allow` roles.
53
+ * 401 (no session) -> the login screen; 403 (wrong role) -> `forbidden`.
54
+ *
55
+ * This is a UI affordance, not a security boundary: the role it reads comes from
56
+ * the session the browser holds, so it decides what to *render*. Every endpoint
57
+ * behind it must enforce the same role server-side.
58
+ */
59
+ export function RoleRoute({ allow, children, fallback, forbidden }: RoleRouteProps) {
60
+ const { status, user } = useAuth();
61
+ const location = useLocation();
62
+
63
+ if (status === 'loading') return <>{fallback}</>;
64
+ if (status === 'unauthenticated') {
65
+ return <Navigate to={loginRedirect(location.pathname + location.search)} replace />;
66
+ }
67
+
68
+ const roles = (user?.role ?? '')
69
+ .split(',')
70
+ .map((role) => role.trim())
71
+ .filter(Boolean);
72
+ if (!allow.some((role) => roles.includes(role))) return <>{forbidden}</>;
73
+
74
+ return <>{children ?? <Outlet />}</>;
75
+ }
@@ -0,0 +1,13 @@
1
+ import { useSelector } from 'react-redux';
2
+ import { selectAuth } from './slice';
3
+
4
+ /** Synchronous auth state for rendering/gating. Hydrated from Better Auth. */
5
+ export function useAuth() {
6
+ const { user, status } = useSelector(selectAuth);
7
+ return {
8
+ user,
9
+ status,
10
+ isAuthenticated: status === 'authenticated',
11
+ isLoading: status === 'loading'
12
+ };
13
+ }
@@ -0,0 +1,20 @@
1
+ // @/shared/auth — authentication on Better Auth.
2
+ export { configureAuth } from './config';
3
+ export { initAuthClient, getAuthClient } from './client';
4
+ export type { AuthClient } from './client';
5
+ export { AuthProvider } from './provider';
6
+ export { useAuth } from './hooks';
7
+ export { ProtectedRoute, RoleRoute } from './guards';
8
+ export { redirectToLogin, loginRedirect, getSafeRedirect, configureRedirects } from './redirect';
9
+ export { signOut, broadcastAuthEvent, AUTH_BROADCAST_CHANNEL } from './logout';
10
+ export {
11
+ authReducer,
12
+ sessionLoading,
13
+ sessionLoaded,
14
+ sessionCleared,
15
+ selectAuth,
16
+ selectAuthUser,
17
+ selectAuthStatus,
18
+ selectIsAuthenticated
19
+ } from './slice';
20
+ export type { AuthUser, AuthStatus } from './slice';
@@ -0,0 +1,23 @@
1
+ import { getAuthClient } from './client';
2
+
3
+ export const AUTH_BROADCAST_CHANNEL = 'acme-auth';
4
+
5
+ /** Notify other tabs/apps of an auth event (e.g. logout) so stale UI can react. */
6
+ export function broadcastAuthEvent(type: 'login' | 'logout'): void {
7
+ try {
8
+ const channel = new BroadcastChannel(AUTH_BROADCAST_CHANNEL);
9
+ channel.postMessage({ type });
10
+ channel.close();
11
+ } catch {
12
+ /* BroadcastChannel unsupported — no-op */
13
+ }
14
+ }
15
+
16
+ /** Sign out (clears the shared-domain cookie), broadcast, then optionally redirect. */
17
+ export async function signOut(options?: { redirectTo?: string }): Promise<void> {
18
+ await getAuthClient().signOut();
19
+ broadcastAuthEvent('logout');
20
+ if (options?.redirectTo) {
21
+ window.location.assign(options.redirectTo);
22
+ }
23
+ }
@@ -0,0 +1,52 @@
1
+ import { type ReactNode, useEffect } from 'react';
2
+ import { useDispatch } from 'react-redux';
3
+ import { getAuthClient } from './client';
4
+ import { sessionCleared, sessionLoaded, sessionLoading, type AuthUser } from './slice';
5
+ import { AUTH_BROADCAST_CHANNEL } from './logout';
6
+
7
+ function toAuthUser(user: Record<string, unknown> | null | undefined): AuthUser | null {
8
+ if (!user) return null;
9
+ return {
10
+ id: String(user.id),
11
+ email: String(user.email ?? ''),
12
+ name: (user.name as string | undefined) ?? null,
13
+ image: (user.image as string | undefined) ?? null,
14
+ role: (user.role as string | undefined) ?? null,
15
+ emailVerified: Boolean(user.emailVerified),
16
+ onboardingCompletedAt: (user.onboardingCompletedAt as string | undefined) ?? null
17
+ };
18
+ }
19
+
20
+ /**
21
+ * Bootstraps the Better Auth session and hydrates the authSlice. Must render
22
+ * inside the Redux <Provider>. Guards read the resulting status for gating.
23
+ * Also syncs logout across tabs via BroadcastChannel.
24
+ */
25
+ export function AuthProvider({ children }: { children: ReactNode }) {
26
+ const dispatch = useDispatch();
27
+ const session = getAuthClient().useSession();
28
+
29
+ useEffect(() => {
30
+ if (session.isPending) {
31
+ dispatch(sessionLoading());
32
+ return;
33
+ }
34
+ const user = (session.data?.user as Record<string, unknown> | undefined) ?? null;
35
+ dispatch(sessionLoaded(toAuthUser(user)));
36
+ }, [session.isPending, session.data, dispatch]);
37
+
38
+ useEffect(() => {
39
+ let channel: BroadcastChannel | null = null;
40
+ try {
41
+ channel = new BroadcastChannel(AUTH_BROADCAST_CHANNEL);
42
+ channel.onmessage = (event: MessageEvent<{ type?: string }>) => {
43
+ if (event.data?.type === 'logout') dispatch(sessionCleared());
44
+ };
45
+ } catch {
46
+ /* BroadcastChannel unsupported — no-op */
47
+ }
48
+ return () => channel?.close();
49
+ }, [dispatch]);
50
+
51
+ return <>{children}</>;
52
+ }
@@ -0,0 +1,65 @@
1
+ // Redirect helpers with open-redirect protection.
2
+ //
3
+ // The multi-app build of this stack puts the auth screens on their own origin,
4
+ // so an unauthenticated user is bounced across hosts and back. Here they are
5
+ // routes in this same bundle, so login is a *path*, not a host — but the
6
+ // allowlist stays exactly as it was: `?redirect=` is still attacker-controllable
7
+ // and a monolith is no less vulnerable to being used as an open redirect than
8
+ // four apps were.
9
+
10
+ let loginPath = '/login';
11
+ let trustedOrigins: string[] = [];
12
+ let defaultRedirect = '/';
13
+
14
+ export function configureRedirects(options: {
15
+ /** Route the auth screens are mounted at. */
16
+ loginPath?: string;
17
+ /**
18
+ * Origins allowed as a post-login redirect target. Defaults to this app's
19
+ * own origin, which is the whole answer for a single deployment; add to it
20
+ * only if you deliberately hand off to another host.
21
+ */
22
+ trustedOrigins?: string[];
23
+ /** Where a login with no `?redirect=` lands. */
24
+ defaultRedirect?: string;
25
+ }): void {
26
+ if (options.loginPath) loginPath = options.loginPath;
27
+ trustedOrigins = (options.trustedOrigins ?? [window.location.origin]).map((origin) => origin.replace(/\/$/, ''));
28
+ if (options.defaultRedirect) defaultRedirect = options.defaultRedirect;
29
+ }
30
+
31
+ /** Validate a redirect target against the trusted-origin allowlist. */
32
+ export function getSafeRedirect(target: string | null | undefined, fallback: string = defaultRedirect): string {
33
+ if (!target) return fallback;
34
+ try {
35
+ const url = new URL(target, window.location.origin);
36
+ if (trustedOrigins.includes(url.origin)) return url.toString();
37
+ } catch {
38
+ /* not a valid URL — fall through to fallback */
39
+ }
40
+ return fallback;
41
+ }
42
+
43
+ /**
44
+ * The login route, carrying where the user was heading.
45
+ *
46
+ * Returned as a path rather than navigated to, so the caller can decide: the
47
+ * route guard renders `<Navigate>` with it (a client-side transition), while the
48
+ * 401 handler below does a hard load.
49
+ */
50
+ export function loginRedirect(returnTo: string = window.location.pathname + window.location.search): string {
51
+ const params = new URLSearchParams({ redirect: returnTo });
52
+ return `${loginPath}?${params.toString()}`;
53
+ }
54
+
55
+ /**
56
+ * Hard navigation to the login screen.
57
+ *
58
+ * This is the 401 chokepoint (`configureApi({ onUnauthorized })`), which fires
59
+ * from outside React — there is no router context to navigate with. A full load
60
+ * is also the right behaviour here: a 401 means the session died mid-session, so
61
+ * discarding in-memory state is the point, not a cost.
62
+ */
63
+ export function redirectToLogin(returnTo?: string): void {
64
+ window.location.assign(loginRedirect(returnTo));
65
+ }
@@ -0,0 +1,54 @@
1
+ import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
2
+
3
+ export type AuthUser = {
4
+ id: string;
5
+ email: string;
6
+ name?: string | null;
7
+ image?: string | null;
8
+ role?: string | null;
9
+ emailVerified: boolean;
10
+ /** ISO timestamp set when onboarding is finished/skipped; null = not yet onboarded. */
11
+ onboardingCompletedAt?: string | null;
12
+ };
13
+
14
+ export type AuthStatus = 'loading' | 'authenticated' | 'unauthenticated';
15
+
16
+ type AuthState = {
17
+ user: AuthUser | null;
18
+ status: AuthStatus;
19
+ };
20
+
21
+ const initialState: AuthState = {
22
+ user: null,
23
+ status: 'loading'
24
+ };
25
+
26
+ // Minimal projection of the Better Auth session for synchronous, app-wide reads.
27
+ // Better Auth remains the source of truth; this slice is hydrated FROM it (AuthProvider).
28
+ const authSlice = createSlice({
29
+ name: 'auth',
30
+ initialState,
31
+ reducers: {
32
+ sessionLoading(state) {
33
+ state.status = 'loading';
34
+ },
35
+ sessionLoaded(state, action: PayloadAction<AuthUser | null>) {
36
+ state.user = action.payload;
37
+ state.status = action.payload ? 'authenticated' : 'unauthenticated';
38
+ },
39
+ sessionCleared(state) {
40
+ state.user = null;
41
+ state.status = 'unauthenticated';
42
+ }
43
+ }
44
+ });
45
+
46
+ export const { sessionLoading, sessionLoaded, sessionCleared } = authSlice.actions;
47
+ export const authReducer = authSlice.reducer;
48
+
49
+ type WithAuth = { auth: AuthState };
50
+
51
+ export const selectAuth = (state: WithAuth) => state.auth;
52
+ export const selectAuthUser = (state: WithAuth) => state.auth.user;
53
+ export const selectAuthStatus = (state: WithAuth) => state.auth.status;
54
+ export const selectIsAuthenticated = (state: WithAuth) => state.auth.status === 'authenticated';
@@ -0,0 +1,6 @@
1
+ // @/shared/hooks — cross-cutting React hooks.
2
+ //
3
+ // Deliberately empty. A hook that belongs to one feature belongs *in* that
4
+ // feature; this is for the ones two features would otherwise each grow their own
5
+ // copy of — a media query, a debounce, a BroadcastChannel wrapper.
6
+ export {};