@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,129 @@
1
+ import { defineConfig, loadEnv } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import tailwindcss from '@tailwindcss/vite';
4
+ import path from 'node:path';
5
+
6
+ /**
7
+ * Opens the connection to the API origin while the HTML is still parsing.
8
+ *
9
+ * Every authenticated app's first act is a Better Auth session request, and the
10
+ * API is a different origin, so that request pays DNS + TCP + TLS before it can
11
+ * even be sent. Measured against the deployed API: ~420 ms of an ~880 ms
12
+ * `get-session` was connection setup. Preconnecting overlaps it with the bundle
13
+ * download instead of paying it afterwards.
14
+ *
15
+ * `crossorigin` is load-bearing, not decoration. Better Auth and RTK Query both
16
+ * send the session cookie (`credentials: 'include'`), and a credentialed request
17
+ * will not reuse an anonymous socket — omit it and the browser opens a second
18
+ * connection, making the preconnect pure waste. It must also stay a *preconnect*
19
+ * and not become a `preload`: the session response is user-specific and must not
20
+ * be speculatively fetched.
21
+ *
22
+ * Injected here rather than in each `index.html` because `VITE_APP_API_URL` is
23
+ * only known to the build, and HTML can't read env.
24
+ */
25
+ function apiPreconnect(apiUrl) {
26
+ let origin = '';
27
+ try {
28
+ origin = apiUrl ? new URL(apiUrl).origin : '';
29
+ } catch {
30
+ /* malformed VITE_APP_API_URL — skip rather than emit a broken tag */
31
+ }
32
+ return {
33
+ name: 'acme:api-preconnect',
34
+ transformIndexHtml: {
35
+ order: 'pre',
36
+ handler: () =>
37
+ origin
38
+ ? [{ tag: 'link', attrs: { rel: 'preconnect', href: origin, crossorigin: true }, injectTo: 'head-prepend' }]
39
+ : []
40
+ }
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Substitutes `%THEME_LIGHT%` / `%THEME_DARK%` in `index.html`.
46
+ *
47
+ * Every app's `<head>` needs the theme background twice over — the pre-paint
48
+ * script that sets the `dark` class before first paint, and the `html`
49
+ * background that stops a white flash on a dark load — and HTML cannot import.
50
+ * Doing it here keeps `THEME_COLORS` the single source of truth instead of four
51
+ * hardcoded copies drifting apart; that drift is exactly how the client's PWA
52
+ * manifest once ended up `#ffffff` in both themes.
53
+ *
54
+ * The colours are passed in rather than imported: `@acme/config` is the lowest
55
+ * layer and must not depend on `@acme/ui`, which already depends on it.
56
+ *
57
+ * Runs for `vite dev` as well as the build.
58
+ */
59
+ function injectThemeColors(colors) {
60
+ return {
61
+ name: 'acme:inject-theme-colors',
62
+ transformIndexHtml: (html) => html.replaceAll('%THEME_LIGHT%', colors.light).replaceAll('%THEME_DARK%', colors.dark)
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Substitutes `%APP_TITLE%` in `index.html`.
68
+ *
69
+ * The static `<title>` is the fallback a visitor sees before React mounts, and
70
+ * on any route that renders no `PageMeta`. It has to match the suffix that
71
+ * `MetaProvider` appends at runtime, and the brand string is passed in from
72
+ * `@acme/ui/brand` so there is one source rather than a literal per app — four
73
+ * copies of "Acme" drifting from the real product name is precisely what
74
+ * MAI-101 was filed about.
75
+ *
76
+ * Passed in rather than imported for the same reason as the theme colours:
77
+ * `@acme/config` is the lowest layer and must not depend on `@acme/ui`.
78
+ */
79
+ function injectAppTitle(appTitle) {
80
+ return {
81
+ name: 'acme:inject-app-title',
82
+ transformIndexHtml: (html) => html.replaceAll('%APP_TITLE%', appTitle)
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Shared Vite config factory for Acme apps.
88
+ *
89
+ * Usage in an app's `vite.config.ts`:
90
+ * import { defineAppConfig } from '@acme/config/vite';
91
+ * export default defineAppConfig({ rootDir: import.meta.dirname });
92
+ *
93
+ * Reads `PORT` and comma-separated `VITE_APP_ALLOWED_HOSTS` from the app's env,
94
+ * wires the `@` -> `src` alias, and registers the React + Tailwind v4 plugins.
95
+ * Extra app-specific plugins (e.g. the PWA plugin on the client) are appended
96
+ * after the base ones via `plugins`.
97
+ *
98
+ * @param {{ rootDir: string, themeColors?: { light: string, dark: string }, appTitle?: string, plugins?: import('vite').PluginOption[], overrides?: import('vite').UserConfig }} options
99
+ * @returns {import('vite').UserConfigExport}
100
+ */
101
+ export function defineAppConfig({ rootDir, themeColors, appTitle, plugins = [], overrides = {} }) {
102
+ return defineConfig(({ mode }) => {
103
+ const env = loadEnv(mode, rootDir, '');
104
+ const hosts = env.VITE_APP_ALLOWED_HOSTS;
105
+ const allowedHosts = hosts ? hosts.split(',').map((h) => h.trim()) : [];
106
+ return {
107
+ plugins: [
108
+ react(),
109
+ tailwindcss(),
110
+ apiPreconnect(env.VITE_APP_API_URL),
111
+ ...(themeColors ? [injectThemeColors(themeColors)] : []),
112
+ ...(appTitle ? [injectAppTitle(appTitle)] : []),
113
+ ...plugins
114
+ ],
115
+ server: {
116
+ host: true,
117
+ port: env.PORT ? Number(env.PORT) : undefined,
118
+ strictPort: true,
119
+ allowedHosts
120
+ },
121
+ resolve: {
122
+ alias: {
123
+ '@': path.resolve(rootDir, './src')
124
+ }
125
+ },
126
+ ...overrides
127
+ };
128
+ });
129
+ }
@@ -0,0 +1,3 @@
1
+ import config from '@acme/config/eslint';
2
+
3
+ export default config;
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@acme/hooks",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./src/index.ts"
8
+ },
9
+ "scripts": {
10
+ "lint": "eslint .",
11
+ "typecheck": "tsc --noEmit"
12
+ },
13
+ "devDependencies": {
14
+ "@acme/config": "workspace:*"
15
+ }
16
+ }
@@ -0,0 +1,3 @@
1
+ // @acme/hooks — cross-app React hooks.
2
+ // Phase 1 adds: useMediaQuery, useDebounce, useBroadcastChannel('auth'), etc.
3
+ export const HOOKS_PACKAGE = '@acme/hooks';
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "@acme/config/tsconfig/react.json",
3
+ "include": ["src"]
4
+ }
@@ -0,0 +1,3 @@
1
+ import config from '@acme/config/eslint';
2
+
3
+ export default config;
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@acme/layouts",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./src/index.ts"
8
+ },
9
+ "scripts": {
10
+ "lint": "eslint .",
11
+ "typecheck": "tsc --noEmit"
12
+ },
13
+ "dependencies": {
14
+ "@acme/ui": "workspace:*",
15
+ "lucide-react": "^1.17.0"
16
+ },
17
+ "peerDependencies": {
18
+ "react": ">=19",
19
+ "react-dom": ">=19",
20
+ "react-router": ">=7"
21
+ },
22
+ "devDependencies": {
23
+ "@acme/config": "workspace:*",
24
+ "@types/react": "^19.2.14",
25
+ "@types/react-dom": "^19.2.3",
26
+ "react": "^19.2.6",
27
+ "react-dom": "^19.2.6",
28
+ "react-router": "^8.2.0"
29
+ }
30
+ }
@@ -0,0 +1,225 @@
1
+ import { type ReactNode, useState } from 'react';
2
+ import { NavLink, Outlet } from 'react-router';
3
+ import { Menu, X } from 'lucide-react';
4
+ import { Logo, cn } from '@acme/ui';
5
+ import { UserMenu, type ShellUser } from './user-menu';
6
+
7
+ export type NavItem = {
8
+ label: string;
9
+ to: string;
10
+ icon: ReactNode;
11
+ /** Match the route exactly (use for the index/dashboard route). */
12
+ end?: boolean;
13
+ };
14
+
15
+ /**
16
+ * Warm a route's chunk before the click, given its `to`. Wired to hover, keyboard
17
+ * focus and touch-start, so it covers pointer, keyboard and mobile — a phone has
18
+ * no hover, but `touchstart` still lands a few hundred ms before the click.
19
+ * Optional: apps without code-split routes just leave it off.
20
+ */
21
+ export type PreloadRoute = (to: string) => void;
22
+
23
+ export type ComingSoonItem = {
24
+ label: string;
25
+ icon: ReactNode;
26
+ };
27
+
28
+ type AppShellProps = {
29
+ nav: NavItem[];
30
+ user: ShellUser;
31
+ onSignOut: () => void;
32
+ /** Brand lockup at the top of the sidebar. Defaults to the Acme Corp mark. */
33
+ brand?: ReactNode;
34
+ /** Locked "Coming soon" nav entries shown below the main nav. */
35
+ comingSoon?: ComingSoonItem[];
36
+ /** Slot pinned to the bottom of the sidebar (e.g. the market ticker). */
37
+ sidebarFooter?: ReactNode;
38
+ /** Topbar search slot. */
39
+ search?: ReactNode;
40
+ /** Topbar action slot to the left of the user menu (e.g. the notification bell). */
41
+ topbarActions?: ReactNode;
42
+ /** Page content. Defaults to the router <Outlet /> when omitted. */
43
+ children?: ReactNode;
44
+ /** Warms a route's code-split chunk on nav hover/focus/touch. */
45
+ preloadRoute?: PreloadRoute;
46
+ };
47
+
48
+ function DefaultBrand() {
49
+ return (
50
+ <div className="px-2">
51
+ <Logo />
52
+ </div>
53
+ );
54
+ }
55
+
56
+ function Sidebar({
57
+ nav,
58
+ comingSoon,
59
+ sidebarFooter,
60
+ brand,
61
+ onNavigate,
62
+ preloadRoute
63
+ }: Pick<AppShellProps, 'nav' | 'comingSoon' | 'sidebarFooter' | 'brand' | 'preloadRoute'> & {
64
+ onNavigate?: () => void;
65
+ }) {
66
+ return (
67
+ <div className="flex h-full flex-col p-4">
68
+ <div className="py-2">{brand ?? <DefaultBrand />}</div>
69
+ {/*
70
+ * `min-h-0` is load-bearing: a flex child defaults to `min-height: auto`,
71
+ * which lets it grow past its flex-basis to fit its content instead of
72
+ * scrolling — on a short viewport that pushed `sidebarFooter` off the
73
+ * bottom of the fixed-height sidebar with no way to reach it. Scrollbar
74
+ * is hidden (still scrollable via wheel/touch/keyboard) to match the
75
+ * flat, chrome-free sidebar surface — nav is short enough that a visible
76
+ * track would only appear briefly during scroll anyway.
77
+ */}
78
+ <div className="flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
79
+ <nav className="flex flex-col gap-0.5">
80
+ {nav.map((item) => (
81
+ <NavLink
82
+ key={item.to}
83
+ to={item.to}
84
+ end={item.end}
85
+ onClick={onNavigate}
86
+ onPointerEnter={() => preloadRoute?.(item.to)}
87
+ onFocus={() => preloadRoute?.(item.to)}
88
+ onTouchStart={() => preloadRoute?.(item.to)}
89
+ className={({ isActive }) =>
90
+ cn(
91
+ 'flex items-center gap-3 rounded-xl px-3 py-2.5 text-sm font-semibold transition-colors [&_svg]:size-[18px]',
92
+ isActive ? 'bg-accent text-accent-foreground' : 'text-body hover:bg-secondary'
93
+ )
94
+ }
95
+ >
96
+ {item.icon}
97
+ {item.label}
98
+ </NavLink>
99
+ ))}
100
+ </nav>
101
+ {comingSoon && comingSoon.length > 0 ? (
102
+ <>
103
+ <div className="mx-2 my-3 h-px bg-border" />
104
+ <p className="px-3 pb-1 text-[10px] font-bold uppercase tracking-[0.09em] text-mute">Coming soon</p>
105
+ <div className="flex flex-col gap-0.5">
106
+ {comingSoon.map((item) => (
107
+ <div
108
+ key={item.label}
109
+ className="flex cursor-default items-center justify-between gap-2 rounded-xl px-3 py-2 text-sm font-semibold text-mute [&_svg]:size-4"
110
+ >
111
+ <span className="flex items-center gap-3">
112
+ {item.icon}
113
+ {item.label}
114
+ </span>
115
+ <span className="rounded-full bg-accent px-1.5 py-0.5 text-[8.5px] font-bold uppercase tracking-[0.08em] text-accent-foreground">
116
+ Soon
117
+ </span>
118
+ </div>
119
+ ))}
120
+ </div>
121
+ </>
122
+ ) : null}
123
+ </div>
124
+ {sidebarFooter ? <div className="pt-3">{sidebarFooter}</div> : null}
125
+ </div>
126
+ );
127
+ }
128
+
129
+ /**
130
+ * Authenticated app shell (shared by Client + Admin): fixed sidebar at desktop,
131
+ * a slide-in drawer on mobile, and a sticky topbar with search / actions / user
132
+ * menu. Presentational only — nav and slots are passed in, and sign-out is
133
+ * delegated via `onSignOut`, so it stays decoupled from the auth package.
134
+ */
135
+ export function AppShell({
136
+ nav,
137
+ user,
138
+ onSignOut,
139
+ brand,
140
+ comingSoon,
141
+ sidebarFooter,
142
+ search,
143
+ topbarActions,
144
+ children,
145
+ preloadRoute
146
+ }: AppShellProps) {
147
+ const [mobileOpen, setMobileOpen] = useState(false);
148
+
149
+ return (
150
+ <div className="min-h-screen bg-background">
151
+ {/* Desktop sidebar */}
152
+ <aside className="fixed inset-y-0 left-0 hidden w-64 border-r border-border bg-card lg:block">
153
+ <Sidebar
154
+ nav={nav}
155
+ comingSoon={comingSoon}
156
+ sidebarFooter={sidebarFooter}
157
+ brand={brand}
158
+ preloadRoute={preloadRoute}
159
+ />
160
+ </aside>
161
+
162
+ {/* Mobile drawer — always mounted so it animates on both enter and exit. */}
163
+ <div
164
+ className={cn('fixed inset-0 z-50 lg:hidden', !mobileOpen && 'pointer-events-none')}
165
+ aria-hidden={!mobileOpen}
166
+ inert={!mobileOpen}
167
+ >
168
+ <div
169
+ onClick={() => setMobileOpen(false)}
170
+ aria-hidden="true"
171
+ className={cn(
172
+ 'absolute inset-0 bg-scrim transition-opacity duration-300 ease-out motion-reduce:transition-none',
173
+ mobileOpen ? 'opacity-100' : 'opacity-0'
174
+ )}
175
+ />
176
+ <aside
177
+ className={cn(
178
+ 'absolute inset-y-0 left-0 w-64 border-r border-border bg-card',
179
+ 'transition-transform duration-300 ease-out motion-reduce:transition-none',
180
+ mobileOpen ? 'translate-x-0' : '-translate-x-full'
181
+ )}
182
+ >
183
+ <button
184
+ type="button"
185
+ onClick={() => setMobileOpen(false)}
186
+ aria-label="Close menu"
187
+ className="absolute right-3 top-4 flex size-8 items-center justify-center rounded-full text-mute hover:bg-secondary"
188
+ >
189
+ <X className="size-5" />
190
+ </button>
191
+ <Sidebar
192
+ nav={nav}
193
+ comingSoon={comingSoon}
194
+ sidebarFooter={sidebarFooter}
195
+ brand={brand}
196
+ preloadRoute={preloadRoute}
197
+ onNavigate={() => setMobileOpen(false)}
198
+ />
199
+ </aside>
200
+ </div>
201
+
202
+ {/* Main column */}
203
+ <div className="flex min-h-screen flex-col lg:pl-64">
204
+ <header className="sticky top-0 z-40 flex items-center gap-2.5 border-b border-border bg-card px-4 py-2.5">
205
+ <button
206
+ type="button"
207
+ onClick={() => setMobileOpen(true)}
208
+ aria-label="Open menu"
209
+ className="flex size-9 items-center justify-center rounded-full text-foreground hover:bg-secondary lg:hidden"
210
+ >
211
+ <Menu className="size-5" />
212
+ </button>
213
+ {search ?? <div className="flex-1" />}
214
+ <div className="ml-auto flex items-center gap-2">
215
+ {topbarActions}
216
+ <UserMenu user={user} onSignOut={onSignOut} />
217
+ </div>
218
+ </header>
219
+ <main className="flex-1">
220
+ <div className="mx-auto w-full max-w-6xl p-4 sm:p-6 lg:p-8">{children ?? <Outlet />}</div>
221
+ </main>
222
+ </div>
223
+ </div>
224
+ );
225
+ }
@@ -0,0 +1,74 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { LogOut } from 'lucide-react';
3
+
4
+ export type ShellUser = {
5
+ name?: string | null;
6
+ email?: string | null;
7
+ image?: string | null;
8
+ };
9
+
10
+ function initials(user: ShellUser): string {
11
+ const name = (user.name ?? '').trim();
12
+ if (name) {
13
+ const parts = name.split(/\s+/);
14
+ const first = parts[0]?.[0] ?? '';
15
+ const second = parts[1]?.[0] ?? '';
16
+ return (first + second || first).toUpperCase();
17
+ }
18
+ const email = (user.email ?? '').trim();
19
+ return email ? email[0].toUpperCase() : '?';
20
+ }
21
+
22
+ /** Avatar button + dropdown (name/email + sign out). Sign-out is delegated via `onSignOut`. */
23
+ export function UserMenu({ user, onSignOut }: { user: ShellUser; onSignOut: () => void }) {
24
+ const [open, setOpen] = useState(false);
25
+ const ref = useRef<HTMLDivElement>(null);
26
+
27
+ useEffect(() => {
28
+ if (!open) return;
29
+ const onDocClick = (event: MouseEvent) => {
30
+ if (ref.current && !ref.current.contains(event.target as Node)) setOpen(false);
31
+ };
32
+ document.addEventListener('mousedown', onDocClick);
33
+ return () => document.removeEventListener('mousedown', onDocClick);
34
+ }, [open]);
35
+
36
+ return (
37
+ <div className="relative" ref={ref}>
38
+ <button
39
+ type="button"
40
+ onClick={() => setOpen((value) => !value)}
41
+ aria-haspopup="menu"
42
+ aria-expanded={open}
43
+ aria-label="Account menu"
44
+ className="flex size-9 items-center justify-center overflow-hidden rounded-full bg-primary text-sm font-bold text-primary-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
45
+ >
46
+ {user.image ? <img src={user.image} alt="" className="size-9 rounded-full object-cover" /> : initials(user)}
47
+ </button>
48
+ {open ? (
49
+ <div
50
+ role="menu"
51
+ className="absolute right-0 top-11 z-50 w-56 rounded-2xl border border-border bg-card p-2 shadow-[0_12px_32px_rgba(14,15,12,0.18)]"
52
+ >
53
+ <div className="px-3 py-2">
54
+ <p className="truncate text-sm font-semibold text-foreground">{user.name ?? 'Your account'}</p>
55
+ {user.email ? <p className="truncate text-xs text-mute">{user.email}</p> : null}
56
+ </div>
57
+ <div className="my-1 h-px bg-border" />
58
+ <button
59
+ type="button"
60
+ role="menuitem"
61
+ onClick={() => {
62
+ setOpen(false);
63
+ onSignOut();
64
+ }}
65
+ className="flex w-full items-center gap-2.5 rounded-xl px-3 py-2 text-left text-sm font-semibold text-foreground transition-colors hover:bg-secondary"
66
+ >
67
+ <LogOut className="size-4" />
68
+ Sign out
69
+ </button>
70
+ </div>
71
+ ) : null}
72
+ </div>
73
+ );
74
+ }
@@ -0,0 +1,137 @@
1
+ import type { ReactNode } from 'react';
2
+ import { Logo, LogoMark, ThemeToggle } from '@acme/ui';
3
+
4
+ /**
5
+ * Brand-panel watermark, per the design system's Watermark guideline: one large
6
+ * mark, offset, clear of all text, never tiled — "felt, not read".
7
+ *
8
+ * Inline SVG (`LogoMark` fills with `currentColor`), so it costs no request and
9
+ * inverts with the theme for free: ink on the sage panel, near-white on the ink
10
+ * one. The DS quotes 6–8% ink, but that assumes a light surface — the dark panel
11
+ * is far darker, so the same alpha reads ~45% stronger against it. Measured in
12
+ * CIELAB against each panel colour and matched: 0.07 light gives ΔE 5.4, 0.05
13
+ * dark gives ΔE 5.6. Don't "simplify" these to one value.
14
+ *
15
+ * `-z-10` under the panel's `isolate` puts it behind the copy while staying above
16
+ * the panel's own background; without `isolate` the negative z-index would sink
17
+ * below it and disappear.
18
+ */
19
+ function BrandWatermark() {
20
+ return (
21
+ <div className="pointer-events-none absolute inset-0 -z-10 overflow-hidden" aria-hidden="true">
22
+ {/*
23
+ * Size and offset are load-bearing, and every value here came from looking
24
+ * at it rather than reasoning about it:
25
+ * - 460px left only a corner fragment on-panel, so it read as stray grey
26
+ * rectangles rather than the monogram.
27
+ * - 300px read correctly at 1280 but the dot poked out past the teaser
28
+ * card's right edge; the guideline is explicit that it stays clear.
29
+ * - 240px clears the card at 1280 — but not at 1024, where the panel is
30
+ * only 512px wide. The card is 340px inside 48px padding, so it owns the
31
+ * left 388px and *any* legible mark overlaps it horizontally there. The
32
+ * only way to clear is vertically, hence the smaller `lg` size that fits
33
+ * in the ~155px below the card, going full size from `xl`.
34
+ *
35
+ * Sized in CSS, not the `width` prop, so it can be responsive — the classes
36
+ * override the SVG's width/height attributes and the viewBox keeps the ratio.
37
+ * Re-check both breakpoints if the card's width or the panel padding changes.
38
+ */}
39
+ <LogoMark
40
+ width={240}
41
+ className="absolute -bottom-4 -right-4 h-auto w-40 text-foreground opacity-[0.07] dark:opacity-[0.05] xl:w-60"
42
+ />
43
+ </div>
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Product teaser — the brand panel's signature element. Shows a prospective user
49
+ * what the product does instead of decorating around them, and costs nothing:
50
+ * the sparkline is inline SVG, no image request.
51
+ *
52
+ * Replace the copy and the figure with your own; keep the shape. **The numbers
53
+ * are illustrative, not anyone's data**, which is why the visually-hidden
54
+ * "Example" prefix is here — without it a screen reader announces a fabricated
55
+ * figure as fact.
56
+ *
57
+ * Brand green on the line doesn't conflict with "green is for CTAs": `--primary`
58
+ * is the same hex in both themes, so the card needs no per-theme handling, and
59
+ * the surrounding surfaces do.
60
+ */
61
+ const SPARK = 'M0,44 C24,40 40,46 62,38 C86,29 102,34 124,27 C148,19 166,24 190,17 C214,11 236,14 256,9 L280,6';
62
+
63
+ function ProductTeaser() {
64
+ return (
65
+ <div className="mt-7 max-w-[340px] rounded-[20px] bg-card px-5 py-[18px]">
66
+ <div className="flex items-baseline justify-between gap-2.5">
67
+ <span className="text-[11px] font-semibold uppercase leading-[15px] tracking-[0.07em] text-mute">
68
+ <span className="sr-only">Example: </span>This month
69
+ </span>
70
+ <span className="text-[11.5px] font-semibold leading-4 text-positive">▲ +18.4%</span>
71
+ </div>
72
+ <div className="mt-1.5 text-[26px] font-extrabold leading-8 tracking-[-0.02em] tabular-nums text-foreground">
73
+ 42,830.16
74
+ </div>
75
+ <svg viewBox="0 0 280 56" className="mt-2.5 h-14 w-full" aria-hidden="true">
76
+ {/* Area first so the stroke sits on top of it. */}
77
+ <path d={`${SPARK} L280,56 L0,56 Z`} className="fill-primary opacity-12" />
78
+ <path d={SPARK} fill="none" strokeWidth="2.5" className="stroke-primary" />
79
+ </svg>
80
+ <p className="mt-1.5 text-[10.5px] leading-[15px] text-mute">Illustrative figures · replace with your own</p>
81
+ </div>
82
+ );
83
+ }
84
+
85
+ type AuthLayoutProps = {
86
+ children: ReactNode;
87
+ /** Optional brand-panel content (headline / illustration). */
88
+ aside?: ReactNode;
89
+ };
90
+
91
+ /**
92
+ * Split auth shell (Wise pattern): sage brand panel on the left at desktop,
93
+ * form panel on the right; stacked to a single centered column on mobile.
94
+ * No shadows — the form sits on the page/card surface via contrast.
95
+ */
96
+ export function AuthLayout({ children, aside }: AuthLayoutProps) {
97
+ return (
98
+ <div className="min-h-screen w-full bg-background lg:grid lg:grid-cols-2">
99
+ {/*
100
+ * `isolate` so the watermark's -z-10 stays above the panel background.
101
+ *
102
+ * `my-auto` on the content block rather than `justify-between` on the panel:
103
+ * the footer line used to anchor the bottom, so with it gone space-between
104
+ * would drop the headline to the floor.
105
+ */}
106
+ <aside className="relative isolate hidden flex-col gap-10 bg-brand-panel p-12 lg:flex">
107
+ <BrandWatermark />
108
+ <Logo />
109
+ <div className="my-auto max-w-md">
110
+ {aside ?? (
111
+ <>
112
+ <h1 className="font-display text-4xl font-black leading-[1.06] tracking-tight text-foreground">
113
+ One line on why you exist.
114
+ </h1>
115
+ <p className="mt-4 max-w-sm text-body">
116
+ A sentence of supporting copy, written for someone who has never heard of this product.
117
+ </p>
118
+ <ProductTeaser />
119
+ </>
120
+ )}
121
+ </div>
122
+ </aside>
123
+ {/*
124
+ * The auth app has no topbar, so the toggle rides the form panel. Placed
125
+ * here rather than per-route because every auth screen goes through this
126
+ * layout. `relative` on the panel keeps it inside the right-hand column at
127
+ * desktop instead of over the brand panel.
128
+ */}
129
+ <main className="relative flex min-h-screen items-center justify-center p-6">
130
+ <div className="absolute right-4 top-4">
131
+ <ThemeToggle />
132
+ </div>
133
+ <div className="w-full max-w-md">{children}</div>
134
+ </main>
135
+ </div>
136
+ );
137
+ }
@@ -0,0 +1,38 @@
1
+ import { Component, type ErrorInfo, type ReactNode } from 'react';
2
+ import { Button } from '@acme/ui';
3
+ import { StateScreen } from './states/state-screen';
4
+
5
+ type ErrorBoundaryProps = {
6
+ children: ReactNode;
7
+ fallback?: ReactNode;
8
+ };
9
+
10
+ type ErrorBoundaryState = {
11
+ hasError: boolean;
12
+ };
13
+
14
+ /** Catches render errors in its subtree and shows a recoverable fallback. */
15
+ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
16
+ state: ErrorBoundaryState = { hasError: false };
17
+
18
+ static getDerivedStateFromError(): ErrorBoundaryState {
19
+ return { hasError: true };
20
+ }
21
+
22
+ componentDidCatch(error: Error, info: ErrorInfo): void {
23
+ console.error('ErrorBoundary caught an error', error, info);
24
+ }
25
+
26
+ render(): ReactNode {
27
+ if (this.state.hasError) {
28
+ return (
29
+ this.props.fallback ?? (
30
+ <StateScreen title="Something went wrong" description="An unexpected error occurred. Try reloading the page.">
31
+ <Button onClick={() => window.location.reload()}>Reload</Button>
32
+ </StateScreen>
33
+ )
34
+ );
35
+ }
36
+ return this.props.children;
37
+ }
38
+ }