@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,76 @@
1
+ import * as React from 'react';
2
+ import { CalendarIcon } from 'lucide-react';
3
+ import { format, isValid, parseISO } from 'date-fns';
4
+ import { Popover, PopoverContent, PopoverTrigger } from './popover';
5
+ import { cn } from '../../lib/utils';
6
+
7
+ // Calendar (react-day-picker) is heavy — load it only when a date field opens.
8
+ const LazyCalendar = React.lazy(() => import('./calendar').then((module) => ({ default: module.Calendar })));
9
+
10
+ type DatePickerProps = {
11
+ /** `yyyy-MM-dd`, or '' for empty — the same wire shape as a native `<input type="date">`. */
12
+ value: string;
13
+ onChange: (value: string) => void;
14
+ id?: string;
15
+ placeholder?: string;
16
+ invalid?: boolean;
17
+ /** Called after a date is picked — `FormDateField` uses it to mark the field touched. */
18
+ onCommit?: () => void;
19
+ };
20
+
21
+ /**
22
+ * Controlled date field: an input-styled trigger opening a Popover calendar.
23
+ *
24
+ * Unbound to any form library, so it works in `admin` and `landing` (neither
25
+ * has Formik). `FormDateField` wraps this for the Formik apps — keep the
26
+ * composition here only, so the two can't drift.
27
+ */
28
+ function DatePicker({ value, onChange, id, placeholder = 'Pick a date', invalid, onCommit }: DatePickerProps) {
29
+ const [open, setOpen] = React.useState(false);
30
+
31
+ const parsed = value ? parseISO(value) : undefined;
32
+ const selected = parsed && isValid(parsed) ? parsed : undefined;
33
+
34
+ return (
35
+ <Popover open={open} onOpenChange={setOpen}>
36
+ <PopoverTrigger asChild>
37
+ <button
38
+ type="button"
39
+ id={id}
40
+ aria-invalid={invalid || undefined}
41
+ className={cn(
42
+ 'flex h-11 w-full items-center justify-between gap-2 rounded-xl border border-input bg-card px-3.5 text-sm outline-none transition-colors',
43
+ 'focus-visible:border-foreground focus-visible:ring-2 focus-visible:ring-ring',
44
+ 'aria-invalid:border-negative'
45
+ )}
46
+ >
47
+ {selected ? (
48
+ <span className="text-foreground">{format(selected, 'd MMM yyyy')}</span>
49
+ ) : (
50
+ <span className="text-mute">{placeholder}</span>
51
+ )}
52
+ <CalendarIcon className="size-4 shrink-0 text-mute" />
53
+ </button>
54
+ </PopoverTrigger>
55
+ <PopoverContent align="start">
56
+ <React.Suspense
57
+ fallback={<div className="flex h-72 w-64 items-center justify-center text-sm text-mute">Loading…</div>}
58
+ >
59
+ <LazyCalendar
60
+ mode="single"
61
+ autoFocus
62
+ selected={selected}
63
+ onSelect={(date) => {
64
+ onChange(date ? format(date, 'yyyy-MM-dd') : '');
65
+ onCommit?.();
66
+ setOpen(false);
67
+ }}
68
+ />
69
+ </React.Suspense>
70
+ </PopoverContent>
71
+ </Popover>
72
+ );
73
+ }
74
+
75
+ export { DatePicker };
76
+ export type { DatePickerProps };
@@ -0,0 +1,22 @@
1
+ import * as React from 'react';
2
+ import { cn } from '../../lib/utils';
3
+
4
+ function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
5
+ return (
6
+ <input
7
+ type={type}
8
+ data-slot="input"
9
+ className={cn(
10
+ 'flex h-11 w-full min-w-0 rounded-xl border border-input bg-card px-3.5 py-2 text-base text-foreground transition-colors',
11
+ 'placeholder:text-mute selection:bg-primary selection:text-primary-foreground',
12
+ 'outline-none focus-visible:border-foreground focus-visible:ring-2 focus-visible:ring-ring',
13
+ 'aria-invalid:border-negative aria-invalid:focus-visible:ring-negative/30',
14
+ 'disabled:cursor-not-allowed disabled:opacity-50',
15
+ className
16
+ )}
17
+ {...props}
18
+ />
19
+ );
20
+ }
21
+
22
+ export { Input };
@@ -0,0 +1,19 @@
1
+ import * as React from 'react';
2
+ import * as LabelPrimitive from '@radix-ui/react-label';
3
+ import { cn } from '../../lib/utils';
4
+
5
+ function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
6
+ return (
7
+ <LabelPrimitive.Root
8
+ data-slot="label"
9
+ className={cn(
10
+ 'text-sm font-semibold leading-none text-foreground',
11
+ 'peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
12
+ className
13
+ )}
14
+ {...props}
15
+ />
16
+ );
17
+ }
18
+
19
+ export { Label };
@@ -0,0 +1,94 @@
1
+ import { useEffect, useRef, useState, type ReactNode } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { cn } from '../../lib/utils';
4
+
5
+ // Open modals, oldest first — so Escape only closes the topmost when nested.
6
+ const modalStack: symbol[] = [];
7
+
8
+ type ModalProps = {
9
+ /** Called on overlay click, Escape, and by the dialog's own close controls. */
10
+ onClose: () => void;
11
+ children: ReactNode;
12
+ /** Accessible label for the dialog. */
13
+ label?: string;
14
+ /** Extra classes for the panel — e.g. a wider `sm:max-w-lg`. */
15
+ className?: string;
16
+ /** Show the mobile drag-handle grabber (default true). */
17
+ handle?: boolean;
18
+ /** Allow closing via overlay click / Escape (default true). */
19
+ dismissible?: boolean;
20
+ /** ARIA role — use "alertdialog" for destructive confirms. */
21
+ role?: 'dialog' | 'alertdialog';
22
+ };
23
+
24
+ /**
25
+ * App modal: a **bottom sheet on mobile** (slides up, rounded top, safe-area
26
+ * aware) and a **centered card on ≥sm**. Rendered in a portal on `document.body`;
27
+ * closes on overlay click + Escape, locks background scroll while open, and caps
28
+ * its height with internal scroll. Enter animation only (unmounts instantly on
29
+ * close, matching the conditional-render pattern callers use).
30
+ */
31
+ export function Modal({
32
+ onClose,
33
+ children,
34
+ label,
35
+ className,
36
+ handle = true,
37
+ dismissible = true,
38
+ role = 'dialog'
39
+ }: ModalProps) {
40
+ const [shown, setShown] = useState(false);
41
+ const panelRef = useRef<HTMLDivElement>(null);
42
+
43
+ // Animate in on mount.
44
+ useEffect(() => {
45
+ const raf = requestAnimationFrame(() => setShown(true));
46
+ return () => cancelAnimationFrame(raf);
47
+ }, []);
48
+
49
+ // Escape to close (topmost only) + lock background scroll while open.
50
+ useEffect(() => {
51
+ const id = Symbol('modal');
52
+ modalStack.push(id);
53
+ const onKey = (event: KeyboardEvent) => {
54
+ if (event.key === 'Escape' && dismissible && modalStack[modalStack.length - 1] === id) onClose();
55
+ };
56
+ document.addEventListener('keydown', onKey);
57
+ const previousOverflow = document.body.style.overflow;
58
+ document.body.style.overflow = 'hidden';
59
+ return () => {
60
+ document.removeEventListener('keydown', onKey);
61
+ const index = modalStack.indexOf(id);
62
+ if (index !== -1) modalStack.splice(index, 1);
63
+ // Restore the overflow value this modal saw (keeps the lock while a parent stays open).
64
+ document.body.style.overflow = previousOverflow;
65
+ };
66
+ }, [onClose, dismissible]);
67
+
68
+ return createPortal(
69
+ <div className="fixed inset-0 z-60 flex items-end justify-center sm:items-center sm:p-4">
70
+ <div
71
+ aria-hidden="true"
72
+ onClick={dismissible ? onClose : undefined}
73
+ className={cn('fixed inset-0 bg-scrim transition-opacity duration-200', shown ? 'opacity-100' : 'opacity-0')}
74
+ />
75
+ <div
76
+ ref={panelRef}
77
+ role={role}
78
+ aria-modal="true"
79
+ aria-label={label}
80
+ className={cn(
81
+ 'relative flex max-h-[92dvh] w-full flex-col overflow-y-auto bg-card p-6',
82
+ 'rounded-t-3xl pb-[calc(1.5rem+env(safe-area-inset-bottom))] sm:max-w-md sm:rounded-3xl sm:pb-6',
83
+ 'transition duration-200 ease-out',
84
+ shown ? 'translate-y-0 opacity-100 sm:scale-100' : 'translate-y-full opacity-0 sm:translate-y-0 sm:scale-95',
85
+ className
86
+ )}
87
+ >
88
+ {handle ? <div className="mx-auto mb-3 h-1 w-9 shrink-0 rounded-full bg-border sm:hidden" /> : null}
89
+ {children}
90
+ </div>
91
+ </div>,
92
+ document.body
93
+ );
94
+ }
@@ -0,0 +1,31 @@
1
+ import * as React from 'react';
2
+ import * as PopoverPrimitive from '@radix-ui/react-popover';
3
+ import { cn } from '../../lib/utils';
4
+
5
+ const Popover = PopoverPrimitive.Root;
6
+ const PopoverTrigger = PopoverPrimitive.Trigger;
7
+ const PopoverAnchor = PopoverPrimitive.Anchor;
8
+
9
+ function PopoverContent({
10
+ className,
11
+ align = 'start',
12
+ sideOffset = 6,
13
+ ...props
14
+ }: React.ComponentProps<typeof PopoverPrimitive.Content>) {
15
+ return (
16
+ <PopoverPrimitive.Portal>
17
+ <PopoverPrimitive.Content
18
+ data-slot="popover-content"
19
+ align={align}
20
+ sideOffset={sideOffset}
21
+ className={cn(
22
+ 'z-80 w-auto rounded-2xl border border-border bg-card p-3 text-foreground shadow-[0_12px_32px_rgba(14,15,12,0.18)] outline-none',
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ </PopoverPrimitive.Portal>
28
+ );
29
+ }
30
+
31
+ export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
@@ -0,0 +1,33 @@
1
+ import * as React from 'react';
2
+ import { cn } from '../../lib/utils';
3
+
4
+ type ProgressProps = React.ComponentProps<'div'> & {
5
+ /** 0-100. Values outside the range are clamped. */
6
+ value: number;
7
+ /** Accessible name, e.g. "Upload progress". */
8
+ label?: string;
9
+ };
10
+
11
+ /** A determinate progress bar — pair with a percentage or byte count in text. */
12
+ function Progress({ value, label, className, ...props }: ProgressProps) {
13
+ const percent = Math.min(100, Math.max(0, Math.round(value)));
14
+ return (
15
+ <div
16
+ role="progressbar"
17
+ aria-valuenow={percent}
18
+ aria-valuemin={0}
19
+ aria-valuemax={100}
20
+ aria-label={label}
21
+ className={cn('h-2 w-full overflow-hidden rounded-full bg-secondary', className)}
22
+ {...props}
23
+ >
24
+ <div
25
+ className="h-full rounded-full bg-primary transition-[width] duration-200 ease-out"
26
+ style={{ width: `${percent}%` }}
27
+ />
28
+ </div>
29
+ );
30
+ }
31
+
32
+ export { Progress };
33
+ export type { ProgressProps };
@@ -0,0 +1,69 @@
1
+ import * as React from 'react';
2
+ import { Search } from 'lucide-react';
3
+ import { cn } from '../../lib/utils';
4
+
5
+ /**
6
+ * `text-base` below `sm` is load-bearing, not a style preference.
7
+ *
8
+ * iOS auto-zooms a focused text input whose computed font-size is under 16px,
9
+ * and it never restores the zoom on blur — so one tap leaves the user zoomed in
10
+ * for the rest of the session, in the installed PWA too. `text-sm` is 14px.
11
+ * 14px from `sm` up keeps the intended desktop density, where nothing auto-zooms.
12
+ *
13
+ * Never drop the `text-base` half, and never "fix" a zoom by adding
14
+ * `maximum-scale`/`user-scalable=no` to the viewport — that disables pinch-zoom
15
+ * and fails WCAG 2.1 SC 1.4.4.
16
+ */
17
+ const NO_IOS_ZOOM = 'text-base sm:text-sm';
18
+
19
+ /**
20
+ * Bare input for a search field. Renders only the input, so callers keep their
21
+ * own wrapper and icon placement (the market search needs a container ref for its
22
+ * results dropdown, and the shapes differ — pill vs card).
23
+ *
24
+ * Pass shape, padding and tone through `className`.
25
+ */
26
+ function SearchInput({ className, ...props }: React.ComponentProps<'input'>) {
27
+ return (
28
+ <input
29
+ data-slot="search-input"
30
+ className={cn(
31
+ 'w-full text-foreground outline-none placeholder:text-mute',
32
+ 'focus-visible:ring-2 focus-visible:ring-ring',
33
+ NO_IOS_ZOOM,
34
+ className
35
+ )}
36
+ {...props}
37
+ />
38
+ );
39
+ }
40
+
41
+ /**
42
+ * The filter row at the top of a combobox dropdown panel: leading icon, a
43
+ * transparent auto-focused input, and a hairline under both.
44
+ *
45
+ * Defaults `aria-label` to the placeholder — a placeholder is not a label.
46
+ */
47
+ function ComboboxFilter({ className, placeholder, 'aria-label': ariaLabel, ...props }: React.ComponentProps<'input'>) {
48
+ return (
49
+ <div className="flex items-center gap-2 border-b border-border px-3 py-2">
50
+ <Search aria-hidden="true" className="size-4 shrink-0 text-mute" />
51
+ <input
52
+ // The panel only exists because the user just opened it to type, so
53
+ // focus belongs here.
54
+ autoFocus
55
+ aria-label={ariaLabel ?? placeholder}
56
+ placeholder={placeholder}
57
+ data-slot="combobox-filter"
58
+ className={cn(
59
+ 'w-full bg-transparent text-foreground outline-none placeholder:text-mute',
60
+ NO_IOS_ZOOM,
61
+ className
62
+ )}
63
+ {...props}
64
+ />
65
+ </div>
66
+ );
67
+ }
68
+
69
+ export { SearchInput, ComboboxFilter };
@@ -0,0 +1,80 @@
1
+ import * as React from 'react';
2
+ import * as SelectPrimitive from '@radix-ui/react-select';
3
+ import { Check, ChevronDown } from 'lucide-react';
4
+ import { cn } from '../../lib/utils';
5
+
6
+ const Select = SelectPrimitive.Root;
7
+ const SelectGroup = SelectPrimitive.Group;
8
+ const SelectValue = SelectPrimitive.Value;
9
+
10
+ function SelectTrigger({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Trigger>) {
11
+ return (
12
+ <SelectPrimitive.Trigger
13
+ data-slot="select-trigger"
14
+ className={cn(
15
+ 'flex h-11 w-full items-center justify-between gap-2 rounded-xl border border-input bg-card px-3.5 py-2 text-sm text-foreground outline-none transition-colors',
16
+ 'data-[placeholder]:text-mute focus-visible:border-foreground focus-visible:ring-2 focus-visible:ring-ring',
17
+ 'disabled:cursor-not-allowed disabled:opacity-50 [&>span]:truncate',
18
+ className
19
+ )}
20
+ {...props}
21
+ >
22
+ {children}
23
+ <SelectPrimitive.Icon asChild>
24
+ <ChevronDown className="size-4 shrink-0 text-mute" />
25
+ </SelectPrimitive.Icon>
26
+ </SelectPrimitive.Trigger>
27
+ );
28
+ }
29
+
30
+ function SelectContent({
31
+ className,
32
+ children,
33
+ position = 'popper',
34
+ ...props
35
+ }: React.ComponentProps<typeof SelectPrimitive.Content>) {
36
+ return (
37
+ <SelectPrimitive.Portal>
38
+ <SelectPrimitive.Content
39
+ data-slot="select-content"
40
+ position={position}
41
+ className={cn(
42
+ 'relative z-[70] max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-hidden rounded-2xl border border-border bg-card p-1 text-foreground shadow-[0_12px_32px_rgba(14,15,12,0.18)]',
43
+ position === 'popper' && 'data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1',
44
+ className
45
+ )}
46
+ {...props}
47
+ >
48
+ <SelectPrimitive.Viewport
49
+ className={cn('p-1', position === 'popper' && 'w-full min-w-[var(--radix-select-trigger-width)]')}
50
+ >
51
+ {children}
52
+ </SelectPrimitive.Viewport>
53
+ </SelectPrimitive.Content>
54
+ </SelectPrimitive.Portal>
55
+ );
56
+ }
57
+
58
+ function SelectItem({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Item>) {
59
+ return (
60
+ <SelectPrimitive.Item
61
+ data-slot="select-item"
62
+ className={cn(
63
+ 'relative flex w-full cursor-pointer select-none items-center rounded-lg py-2 pl-3 pr-8 text-sm outline-none',
64
+ 'focus:bg-secondary data-[state=checked]:font-semibold',
65
+ 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
66
+ className
67
+ )}
68
+ {...props}
69
+ >
70
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
71
+ <span className="absolute right-2.5 flex size-4 items-center justify-center">
72
+ <SelectPrimitive.ItemIndicator>
73
+ <Check className="size-4 text-ink-deep" />
74
+ </SelectPrimitive.ItemIndicator>
75
+ </span>
76
+ </SelectPrimitive.Item>
77
+ );
78
+ }
79
+
80
+ export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectItem };
@@ -0,0 +1,12 @@
1
+ import * as React from 'react';
2
+ import { cn } from '../../lib/utils';
3
+
4
+ /**
5
+ * Placeholder block for loading states. Fill is `foreground/10` so it reads on both
6
+ * white cards and the muted page background, and adapts to dark mode automatically.
7
+ */
8
+ function Skeleton({ className, ...props }: React.ComponentProps<'div'>) {
9
+ return <div aria-hidden="true" className={cn('animate-pulse rounded-md bg-foreground/10', className)} {...props} />;
10
+ }
11
+
12
+ export { Skeleton };
@@ -0,0 +1,23 @@
1
+ import type { ComponentProps } from 'react';
2
+ import { Toaster as SonnerToaster } from 'sonner';
3
+
4
+ type ToasterProps = ComponentProps<typeof SonnerToaster>;
5
+
6
+ function Toaster(props: ToasterProps) {
7
+ return (
8
+ <SonnerToaster
9
+ position="top-center"
10
+ toastOptions={{
11
+ classNames: {
12
+ toast: 'rounded-2xl border border-border bg-card text-card-foreground',
13
+ description: 'text-mute',
14
+ actionButton: 'bg-primary text-primary-foreground',
15
+ cancelButton: 'bg-secondary text-secondary-foreground'
16
+ }
17
+ }}
18
+ {...props}
19
+ />
20
+ );
21
+ }
22
+
23
+ export { Toaster };
@@ -0,0 +1,8 @@
1
+ import { LoaderCircle } from 'lucide-react';
2
+ import { cn } from '../../lib/utils';
3
+
4
+ function Spinner({ className }: { className?: string }) {
5
+ return <LoaderCircle className={cn('size-4 animate-spin', className)} aria-hidden="true" />;
6
+ }
7
+
8
+ export { Spinner };
@@ -0,0 +1,22 @@
1
+ import * as React from 'react';
2
+ import { cn } from '../../lib/utils';
3
+
4
+ /** Multiline text input, styled to match Input. */
5
+ function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {
6
+ return (
7
+ <textarea
8
+ data-slot="textarea"
9
+ className={cn(
10
+ 'flex min-h-20 w-full min-w-0 rounded-xl border border-input bg-card px-3.5 py-2.5 text-base text-foreground transition-colors',
11
+ 'placeholder:text-mute selection:bg-primary selection:text-primary-foreground',
12
+ 'outline-none focus-visible:border-foreground focus-visible:ring-2 focus-visible:ring-ring',
13
+ 'aria-invalid:border-negative aria-invalid:focus-visible:ring-negative/30',
14
+ 'disabled:cursor-not-allowed disabled:opacity-50',
15
+ className
16
+ )}
17
+ {...props}
18
+ />
19
+ );
20
+ }
21
+
22
+ export { Textarea };
@@ -0,0 +1,45 @@
1
+ // @/shared/ui — shared shadcn/ui components (Wise design system), theme, and primitives.
2
+ export * from './lib/utils';
3
+ export * from './lib/zod-setup';
4
+
5
+ // Primitives
6
+ export * from './components/ui/button';
7
+ export * from './components/ui/input';
8
+ export * from './components/ui/search-input';
9
+ export * from './components/ui/label';
10
+ export * from './components/ui/card';
11
+ export * from './components/ui/alert';
12
+ export * from './components/ui/checkbox';
13
+ export * from './components/ui/select';
14
+ export * from './components/ui/modal';
15
+ export * from './components/ui/textarea';
16
+ export * from './components/ui/popover';
17
+ // Calendar is intentionally NOT re-exported here — it pulls in react-day-picker, and
18
+ // DatePicker lazy-loads it so it stays out of the main bundle.
19
+ export * from './components/ui/date-picker';
20
+ export * from './components/ui/progress';
21
+ export * from './components/ui/skeleton';
22
+ export * from './components/ui/spinner';
23
+ export * from './components/ui/sonner';
24
+
25
+ // Form primitives (Formik + Zod)
26
+ export * from './components/form/form';
27
+
28
+ // Theme (light/dark/system) — shared by every app
29
+ export { ThemeProvider } from './theme/theme-provider';
30
+ export { ThemeToggle } from './theme/theme-toggle';
31
+ export { useTheme } from './theme/use-theme';
32
+ export { themeInitScript } from './theme/init-script';
33
+ export { THEME_STORAGE_KEY, THEME_COLORS } from './theme/theme-context';
34
+ export type { ThemeMode, ResolvedTheme } from './theme/theme-context';
35
+
36
+ // Document metadata (per-route titles, description, robots, canonical)
37
+ export { PageMeta } from './meta/page-meta';
38
+ export type { PageMetaProps } from './meta/page-meta';
39
+ export { MetaProvider } from './meta/meta-provider';
40
+ export { useMetaConfig } from './meta/use-meta-config';
41
+ export type { MetaConfig } from './meta/meta-context';
42
+
43
+ // Brand
44
+ export * from './components/brand/logo';
45
+ export * from './components/brand/social-icons';
@@ -0,0 +1,7 @@
1
+ import { clsx, type ClassValue } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ /** Merge Tailwind class names, de-duplicating conflicts. */
5
+ export function cn(...inputs: ClassValue[]): string {
6
+ return twMerge(clsx(inputs));
7
+ }
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Install once per app at bootstrap. Friendly defaults for missing/empty
5
+ * required fields; explicit per-field messages (.email(), .min(1, '...'),
6
+ * .positive('...'), etc.) always take precedence over these.
7
+ */
8
+ export function installZodErrorMap() {
9
+ z.config({
10
+ customError: (issue) => {
11
+ if (issue.code === 'invalid_type' && (issue.input === undefined || issue.input === null)) {
12
+ return 'This field is required';
13
+ }
14
+ if (
15
+ issue.code === 'invalid_type' &&
16
+ issue.expected === 'number' &&
17
+ typeof issue.input === 'number' &&
18
+ Number.isNaN(issue.input)
19
+ ) {
20
+ return 'This field is required';
21
+ }
22
+ if (issue.code === 'too_small' && issue.origin === 'string' && issue.minimum === 1) {
23
+ return 'This field is required';
24
+ }
25
+ return undefined;
26
+ }
27
+ });
28
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Product name, in one place.
3
+ *
4
+ * Consumed twice over, which is the whole reason it lives here:
5
+ * - at runtime by `MetaProvider`, as the per-route title suffix;
6
+ * - at build time by each app's `vite.config.ts`, which passes it to
7
+ * `defineAppConfig({ appTitle })` to substitute `%APP_TITLE%` into
8
+ * `index.html` — HTML cannot import, and the static `<title>` is what a
9
+ * visitor sees before React mounts.
10
+ *
11
+ * Keep this file free of React imports: `vite.config.ts` imports it directly
12
+ * via the `@/shared/ui/meta/brand` subpath, exactly as it does `./theme-colors`.
13
+ */
14
+ export const BRAND = {
15
+ name: 'Acme Corp',
16
+ /** The console needs its own suffix so an admin can tell two tabs apart. */
17
+ adminName: 'Acme Corp Admin'
18
+ } as const;
@@ -0,0 +1,30 @@
1
+ import { createContext } from 'react';
2
+ import { BRAND } from './brand';
3
+
4
+ /**
5
+ * Per-app title suffix, declared once by `MetaProvider` so routes supply only
6
+ * their own segment. Admin uses a different brand string, which is exactly why
7
+ * this is context and not a constant.
8
+ */
9
+ export type MetaConfig = {
10
+ /** Appended after a separator, e.g. `Acme Corp`. */
11
+ suffix: string;
12
+ /** Separator between the page segment and the suffix. */
13
+ separator: string;
14
+ };
15
+
16
+ export const DEFAULT_META: MetaConfig = {
17
+ suffix: BRAND.name,
18
+ separator: ' · '
19
+ };
20
+
21
+ export const MetaContext = createContext<MetaConfig>(DEFAULT_META);
22
+
23
+ /** `About` + `Acme Corp` -> `About · Acme Corp`. */
24
+ export function formatTitle(segment: string, config: MetaConfig): string {
25
+ const trimmed = segment.trim();
26
+ if (!trimmed) return config.suffix;
27
+ // Don't double up when a segment already carries the brand.
28
+ if (trimmed === config.suffix) return trimmed;
29
+ return `${trimmed}${config.separator}${config.suffix}`;
30
+ }
@@ -0,0 +1,24 @@
1
+ import { useMemo, type ReactNode } from 'react';
2
+ import { DEFAULT_META, MetaContext, type MetaConfig } from './meta-context';
3
+
4
+ /**
5
+ * Declares the app's title template once, so routes supply only their own
6
+ * segment. Wrap the app root, above the router.
7
+ *
8
+ * ```tsx
9
+ * <MetaProvider suffix="Acme Corp Admin">
10
+ * ```
11
+ */
12
+ export function MetaProvider({
13
+ children,
14
+ suffix = DEFAULT_META.suffix,
15
+ separator = DEFAULT_META.separator
16
+ }: {
17
+ children: ReactNode;
18
+ suffix?: string;
19
+ separator?: string;
20
+ }) {
21
+ const value = useMemo<MetaConfig>(() => ({ suffix, separator }), [suffix, separator]);
22
+
23
+ return <MetaContext.Provider value={value}>{children}</MetaContext.Provider>;
24
+ }