create-mithaq 0.0.0-stage → 0.2.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 (396) hide show
  1. package/LICENSE +22 -0
  2. package/LICENSES/openstatus-time-picker.txt +21 -0
  3. package/LICENSES/payload.txt +22 -0
  4. package/LICENSES/ponytail.txt +21 -0
  5. package/index.js +521 -0
  6. package/package.json +19 -4
  7. package/template/apps/asas-backoffice/components.json +25 -0
  8. package/template/apps/asas-backoffice/gitignore +3 -0
  9. package/template/apps/asas-backoffice/package.json +57 -0
  10. package/template/apps/asas-backoffice/public/favicon.svg +1 -0
  11. package/template/apps/asas-backoffice/src/api.ts +147 -0
  12. package/template/apps/asas-backoffice/src/auth-frame.tsx +150 -0
  13. package/template/apps/asas-backoffice/src/components/ui/accordion.tsx +74 -0
  14. package/template/apps/asas-backoffice/src/components/ui/alert-dialog.tsx +185 -0
  15. package/template/apps/asas-backoffice/src/components/ui/avatar.tsx +106 -0
  16. package/template/apps/asas-backoffice/src/components/ui/badge.tsx +48 -0
  17. package/template/apps/asas-backoffice/src/components/ui/breadcrumb.tsx +124 -0
  18. package/template/apps/asas-backoffice/src/components/ui/button-group.tsx +87 -0
  19. package/template/apps/asas-backoffice/src/components/ui/button.tsx +55 -0
  20. package/template/apps/asas-backoffice/src/components/ui/calendar.tsx +223 -0
  21. package/template/apps/asas-backoffice/src/components/ui/card.tsx +102 -0
  22. package/template/apps/asas-backoffice/src/components/ui/chart.tsx +371 -0
  23. package/template/apps/asas-backoffice/src/components/ui/checkbox.tsx +28 -0
  24. package/template/apps/asas-backoffice/src/components/ui/collapsible.tsx +19 -0
  25. package/template/apps/asas-backoffice/src/components/ui/combobox.tsx +308 -0
  26. package/template/apps/asas-backoffice/src/components/ui/dialog.tsx +158 -0
  27. package/template/apps/asas-backoffice/src/components/ui/direction.tsx +6 -0
  28. package/template/apps/asas-backoffice/src/components/ui/directional-icon.tsx +20 -0
  29. package/template/apps/asas-backoffice/src/components/ui/dropdown-menu.tsx +267 -0
  30. package/template/apps/asas-backoffice/src/components/ui/field.tsx +236 -0
  31. package/template/apps/asas-backoffice/src/components/ui/input-group.tsx +155 -0
  32. package/template/apps/asas-backoffice/src/components/ui/input-otp.tsx +84 -0
  33. package/template/apps/asas-backoffice/src/components/ui/input.tsx +19 -0
  34. package/template/apps/asas-backoffice/src/components/ui/label.tsx +19 -0
  35. package/template/apps/asas-backoffice/src/components/ui/popover.tsx +90 -0
  36. package/template/apps/asas-backoffice/src/components/ui/radio-group.tsx +37 -0
  37. package/template/apps/asas-backoffice/src/components/ui/resizable.tsx +47 -0
  38. package/template/apps/asas-backoffice/src/components/ui/scroll-area.tsx +54 -0
  39. package/template/apps/asas-backoffice/src/components/ui/select.tsx +204 -0
  40. package/template/apps/asas-backoffice/src/components/ui/separator.tsx +24 -0
  41. package/template/apps/asas-backoffice/src/components/ui/sheet.tsx +139 -0
  42. package/template/apps/asas-backoffice/src/components/ui/sidebar.tsx +714 -0
  43. package/template/apps/asas-backoffice/src/components/ui/skeleton.tsx +13 -0
  44. package/template/apps/asas-backoffice/src/components/ui/slider.tsx +55 -0
  45. package/template/apps/asas-backoffice/src/components/ui/spinner.tsx +10 -0
  46. package/template/apps/asas-backoffice/src/components/ui/switch.tsx +29 -0
  47. package/template/apps/asas-backoffice/src/components/ui/table.tsx +115 -0
  48. package/template/apps/asas-backoffice/src/components/ui/tabs.tsx +79 -0
  49. package/template/apps/asas-backoffice/src/components/ui/textarea.tsx +17 -0
  50. package/template/apps/asas-backoffice/src/components/ui/toggle-group.tsx +89 -0
  51. package/template/apps/asas-backoffice/src/components/ui/toggle.tsx +44 -0
  52. package/template/apps/asas-backoffice/src/components/ui/tooltip.tsx +63 -0
  53. package/template/apps/asas-backoffice/src/components.ts +35 -0
  54. package/template/apps/asas-backoffice/src/copy.ts +1 -0
  55. package/template/apps/asas-backoffice/src/first-user.ts +14 -0
  56. package/template/apps/asas-backoffice/src/hooks/use-mobile.ts +19 -0
  57. package/template/apps/asas-backoffice/src/lib/utils.ts +1 -0
  58. package/template/apps/asas-backoffice/src/links.ts +38 -0
  59. package/template/apps/asas-backoffice/src/locale-switch.tsx +28 -0
  60. package/template/apps/asas-backoffice/src/no-organization.tsx +5 -0
  61. package/template/apps/asas-backoffice/src/redirect.ts +1 -0
  62. package/template/apps/asas-backoffice/src/request-headers.ts +11 -0
  63. package/template/apps/asas-backoffice/src/routeTree.gen.ts +206 -0
  64. package/template/apps/asas-backoffice/src/router.tsx +18 -0
  65. package/template/apps/asas-backoffice/src/routes/(screens)/backoffice/files.ts +10 -0
  66. package/template/apps/asas-backoffice/src/routes/(screens)/backoffice/screen.json +3971 -0
  67. package/template/apps/asas-backoffice/src/routes/(screens)/backoffice/types.ts +163 -0
  68. package/template/apps/asas-backoffice/src/routes/(screens)/backoffice.$.tsx +17 -0
  69. package/template/apps/asas-backoffice/src/routes/(screens)/backoffice.index.tsx +10 -0
  70. package/template/apps/asas-backoffice/src/routes/(screens)/backoffice.tsx +13 -0
  71. package/template/apps/asas-backoffice/src/routes/__root.tsx +95 -0
  72. package/template/apps/asas-backoffice/src/routes/api/$.ts +34 -0
  73. package/template/apps/asas-backoffice/src/routes/auth.create-first-user.tsx +113 -0
  74. package/template/apps/asas-backoffice/src/routes/auth.sign-in.tsx +77 -0
  75. package/template/apps/asas-backoffice/src/routes/unauthorized.tsx +134 -0
  76. package/template/apps/asas-backoffice/src/screen-form.tsx +36 -0
  77. package/template/apps/asas-backoffice/src/screen-list.tsx +140 -0
  78. package/template/apps/asas-backoffice/src/screen-names.ts +10 -0
  79. package/template/apps/asas-backoffice/src/screens.tsx +888 -0
  80. package/template/apps/asas-backoffice/src/server.ts +30 -0
  81. package/template/apps/asas-backoffice/src/slide.ts +22 -0
  82. package/template/apps/asas-backoffice/src/start.ts +42 -0
  83. package/template/apps/asas-backoffice/src/styles.css +202 -0
  84. package/template/apps/asas-backoffice/src/theme.ts +2 -0
  85. package/template/apps/asas-backoffice/tsconfig.json +19 -0
  86. package/template/apps/asas-backoffice/vite.config.ts +32 -0
  87. package/template/apps/asas-tanstack/components.json +25 -0
  88. package/template/apps/asas-tanstack/gitignore +3 -0
  89. package/template/apps/asas-tanstack/package.json +58 -0
  90. package/template/apps/asas-tanstack/public/favicon.svg +1 -0
  91. package/template/apps/asas-tanstack/src/api.ts +147 -0
  92. package/template/apps/asas-tanstack/src/auth-frame.tsx +150 -0
  93. package/template/apps/asas-tanstack/src/choose-password.tsx +150 -0
  94. package/template/apps/asas-tanstack/src/components/ui/accordion.tsx +10 -0
  95. package/template/apps/asas-tanstack/src/components/ui/alert-dialog.tsx +173 -0
  96. package/template/apps/asas-tanstack/src/components/ui/avatar.tsx +104 -0
  97. package/template/apps/asas-tanstack/src/components/ui/badge.tsx +48 -0
  98. package/template/apps/asas-tanstack/src/components/ui/breadcrumb.tsx +125 -0
  99. package/template/apps/asas-tanstack/src/components/ui/button-group.tsx +87 -0
  100. package/template/apps/asas-tanstack/src/components/ui/button.tsx +56 -0
  101. package/template/apps/asas-tanstack/src/components/ui/calendar.tsx +223 -0
  102. package/template/apps/asas-tanstack/src/components/ui/card.tsx +94 -0
  103. package/template/apps/asas-tanstack/src/components/ui/chart.tsx +369 -0
  104. package/template/apps/asas-tanstack/src/components/ui/checkbox.tsx +27 -0
  105. package/template/apps/asas-tanstack/src/components/ui/collapsible.tsx +19 -0
  106. package/template/apps/asas-tanstack/src/components/ui/combobox.tsx +300 -0
  107. package/template/apps/asas-tanstack/src/components/ui/dialog.tsx +149 -0
  108. package/template/apps/asas-tanstack/src/components/ui/direction.tsx +5 -0
  109. package/template/apps/asas-tanstack/src/components/ui/directional-icon.tsx +20 -0
  110. package/template/apps/asas-tanstack/src/components/ui/dropdown-menu.tsx +267 -0
  111. package/template/apps/asas-tanstack/src/components/ui/field.tsx +225 -0
  112. package/template/apps/asas-tanstack/src/components/ui/input-group.tsx +147 -0
  113. package/template/apps/asas-tanstack/src/components/ui/input-otp.tsx +20 -0
  114. package/template/apps/asas-tanstack/src/components/ui/input.tsx +20 -0
  115. package/template/apps/asas-tanstack/src/components/ui/label.tsx +18 -0
  116. package/template/apps/asas-tanstack/src/components/ui/popover.tsx +50 -0
  117. package/template/apps/asas-tanstack/src/components/ui/radio-group.tsx +10 -0
  118. package/template/apps/asas-tanstack/src/components/ui/resizable.tsx +47 -0
  119. package/template/apps/asas-tanstack/src/components/ui/scroll-area.tsx +52 -0
  120. package/template/apps/asas-tanstack/src/components/ui/select.tsx +190 -0
  121. package/template/apps/asas-tanstack/src/components/ui/separator.tsx +23 -0
  122. package/template/apps/asas-tanstack/src/components/ui/sheet.tsx +127 -0
  123. package/template/apps/asas-tanstack/src/components/ui/sidebar.tsx +716 -0
  124. package/template/apps/asas-tanstack/src/components/ui/skeleton.tsx +13 -0
  125. package/template/apps/asas-tanstack/src/components/ui/slider.tsx +13 -0
  126. package/template/apps/asas-tanstack/src/components/ui/spinner.tsx +11 -0
  127. package/template/apps/asas-tanstack/src/components/ui/switch.tsx +8 -0
  128. package/template/apps/asas-tanstack/src/components/ui/table.tsx +119 -0
  129. package/template/apps/asas-tanstack/src/components/ui/tabs.tsx +80 -0
  130. package/template/apps/asas-tanstack/src/components/ui/textarea.tsx +18 -0
  131. package/template/apps/asas-tanstack/src/components/ui/toggle-group.tsx +8 -0
  132. package/template/apps/asas-tanstack/src/components/ui/toggle.tsx +44 -0
  133. package/template/apps/asas-tanstack/src/components/ui/tooltip.tsx +68 -0
  134. package/template/apps/asas-tanstack/src/components.ts +35 -0
  135. package/template/apps/asas-tanstack/src/copy.ts +1207 -0
  136. package/template/apps/asas-tanstack/src/first-run.ts +16 -0
  137. package/template/apps/asas-tanstack/src/hooks/use-mobile.ts +19 -0
  138. package/template/apps/asas-tanstack/src/lib/utils.ts +6 -0
  139. package/template/apps/asas-tanstack/src/links.ts +38 -0
  140. package/template/apps/asas-tanstack/src/locale-switch.tsx +28 -0
  141. package/template/apps/asas-tanstack/src/no-organization.tsx +108 -0
  142. package/template/apps/asas-tanstack/src/quiet-abort.ts +25 -0
  143. package/template/apps/asas-tanstack/src/redirect.ts +39 -0
  144. package/template/apps/asas-tanstack/src/request-headers.ts +11 -0
  145. package/template/apps/asas-tanstack/src/routeTree.gen.ts +424 -0
  146. package/template/apps/asas-tanstack/src/router.tsx +18 -0
  147. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/files.ts +11 -0
  148. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/screen.json +4242 -0
  149. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/types.ts +160 -0
  150. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard.$.tsx +17 -0
  151. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard.index.tsx +10 -0
  152. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard.tsx +13 -0
  153. package/template/apps/asas-tanstack/src/routes/(screens)/portal/files.ts +9 -0
  154. package/template/apps/asas-tanstack/src/routes/(screens)/portal/screen.json +1564 -0
  155. package/template/apps/asas-tanstack/src/routes/(screens)/portal/types.ts +94 -0
  156. package/template/apps/asas-tanstack/src/routes/(screens)/portal.$.tsx +17 -0
  157. package/template/apps/asas-tanstack/src/routes/(screens)/portal.index.tsx +10 -0
  158. package/template/apps/asas-tanstack/src/routes/(screens)/portal.tsx +13 -0
  159. package/template/apps/asas-tanstack/src/routes/__root.tsx +99 -0
  160. package/template/apps/asas-tanstack/src/routes/api/$.ts +11 -0
  161. package/template/apps/asas-tanstack/src/routes/auth.accept-invitation.tsx +108 -0
  162. package/template/apps/asas-tanstack/src/routes/auth.first-run.tsx +114 -0
  163. package/template/apps/asas-tanstack/src/routes/auth.forgot-password.tsx +94 -0
  164. package/template/apps/asas-tanstack/src/routes/auth.handoff.ts +39 -0
  165. package/template/apps/asas-tanstack/src/routes/auth.reset-password.tsx +16 -0
  166. package/template/apps/asas-tanstack/src/routes/auth.set-password.tsx +16 -0
  167. package/template/apps/asas-tanstack/src/routes/auth.sign-in.tsx +98 -0
  168. package/template/apps/asas-tanstack/src/routes/auth.sign-up.tsx +143 -0
  169. package/template/apps/asas-tanstack/src/routes/index.tsx +146 -0
  170. package/template/apps/asas-tanstack/src/routes/unauthorized.tsx +134 -0
  171. package/template/apps/asas-tanstack/src/screen-form.tsx +36 -0
  172. package/template/apps/asas-tanstack/src/screen-list.tsx +140 -0
  173. package/template/apps/asas-tanstack/src/screen-names.ts +10 -0
  174. package/template/apps/asas-tanstack/src/screens.tsx +888 -0
  175. package/template/apps/asas-tanstack/src/server.ts +26 -0
  176. package/template/apps/asas-tanstack/src/slide.ts +22 -0
  177. package/template/apps/asas-tanstack/src/start.ts +57 -0
  178. package/template/apps/asas-tanstack/src/styles.css +163 -0
  179. package/template/apps/asas-tanstack/src/theme.ts +5 -0
  180. package/template/apps/asas-tanstack/tsconfig.json +19 -0
  181. package/template/apps/asas-tanstack/vite.config.ts +34 -0
  182. package/template/package.json +16 -0
  183. package/template/templates/asas/.claude/skills/add-field/SKILL.md +27 -0
  184. package/template/templates/asas/.claude/skills/add-kayan/SKILL.md +26 -0
  185. package/template/templates/asas/.claude/skills/eject-admin-piece/SKILL.md +28 -0
  186. package/template/templates/asas/.claude/skills/upgrade-mithaq/SKILL.md +35 -0
  187. package/template/templates/asas/.env.example +33 -0
  188. package/template/templates/asas/AGENTS.md +101 -0
  189. package/template/templates/asas/CLAUDE.md +1 -0
  190. package/template/templates/asas/README.md +1056 -0
  191. package/template/templates/asas/mithaq.hints.json +25 -0
  192. package/template/templates/asas/package.json +53 -0
  193. package/template/templates/asas/src/asas.ts +118 -0
  194. package/template/templates/asas/src/backoffice-http.ts +117 -0
  195. package/template/templates/asas/src/dev-setup.ts +112 -0
  196. package/template/templates/asas/src/local-database.ts +22 -0
  197. package/template/templates/asas/src/messages/account.json +132 -0
  198. package/template/templates/asas/src/messages/auditLog.json +304 -0
  199. package/template/templates/asas/src/messages/backofficeLog.json +132 -0
  200. package/template/templates/asas/src/messages/courses.json +312 -0
  201. package/template/templates/asas/src/messages/enrollments.json +60 -0
  202. package/template/templates/asas/src/messages/invitation.json +82 -0
  203. package/template/templates/asas/src/messages/localUsers.json +48 -0
  204. package/template/templates/asas/src/messages/media.json +76 -0
  205. package/template/templates/asas/src/messages/member.json +62 -0
  206. package/template/templates/asas/src/messages/memberRoles.json +48 -0
  207. package/template/templates/asas/src/messages/mithaq.json +214 -0
  208. package/template/templates/asas/src/messages/organization.json +72 -0
  209. package/template/templates/asas/src/messages/organizationRole.json +62 -0
  210. package/template/templates/asas/src/messages/roles.json +72 -0
  211. package/template/templates/asas/src/messages/session.json +102 -0
  212. package/template/templates/asas/src/messages/sessions.json +136 -0
  213. package/template/templates/asas/src/messages/settings.json +64 -0
  214. package/template/templates/asas/src/messages/tags.json +48 -0
  215. package/template/templates/asas/src/messages/user.json +112 -0
  216. package/template/templates/asas/src/messages/verification.json +62 -0
  217. package/template/templates/asas/src/migrations/20260920080701_parched_tombstone/migration.sql +177 -0
  218. package/template/templates/asas/src/migrations/20260920080701_parched_tombstone/snapshot.json +2169 -0
  219. package/template/templates/asas/src/migrations/20260920230505_zippy_ben_urich/migration.sql +1 -0
  220. package/template/templates/asas/src/migrations/20260920230505_zippy_ben_urich/snapshot.json +2182 -0
  221. package/template/templates/asas/src/migrations/20260920231219_slim_retro_girl/migration.sql +20 -0
  222. package/template/templates/asas/src/migrations/20260920231219_slim_retro_girl/snapshot.json +2409 -0
  223. package/template/templates/asas/src/migrations/20260922082945_military_ironclad/migration.sql +15 -0
  224. package/template/templates/asas/src/migrations/20260922082945_military_ironclad/snapshot.json +2627 -0
  225. package/template/templates/asas/src/migrations/20260922112630_bitter_chat/migration.sql +5 -0
  226. package/template/templates/asas/src/migrations/20260922112630_bitter_chat/snapshot.json +2691 -0
  227. package/template/templates/asas/src/migrations/20260923235915_foamy_rachel_grey/migration.sql +8 -0
  228. package/template/templates/asas/src/migrations/20260923235915_foamy_rachel_grey/snapshot.json +2795 -0
  229. package/template/templates/asas/src/migrations/20260926145709_clean_the_initiative/migration.sql +3 -0
  230. package/template/templates/asas/src/migrations/20260926145709_clean_the_initiative/snapshot.json +2834 -0
  231. package/template/templates/asas/src/migrations/20260927212525_bent_prowler/migration.sql +9 -0
  232. package/template/templates/asas/src/migrations/20260927212525_bent_prowler/snapshot.json +2870 -0
  233. package/template/templates/asas/src/migrations/20260927223441_tranquil_bromley/migration.sql +1 -0
  234. package/template/templates/asas/src/migrations/20260927223441_tranquil_bromley/snapshot.json +2881 -0
  235. package/template/templates/asas/src/migrations/20260928030047_outgoing_wraith/migration.sql +15 -0
  236. package/template/templates/asas/src/migrations/20260928030047_outgoing_wraith/snapshot.json +3073 -0
  237. package/template/templates/asas/src/migrations/20260929024407_grey_morlun/migration.sql +21 -0
  238. package/template/templates/asas/src/migrations/20260929024407_grey_morlun/snapshot.json +3098 -0
  239. package/template/templates/asas/src/migrations/20260929130530_bored_jackal/migration.sql +21 -0
  240. package/template/templates/asas/src/migrations/20260929130530_bored_jackal/snapshot.json +3124 -0
  241. package/template/templates/asas/src/migrations/20260930102924_material_newton_destine/migration.sql +32 -0
  242. package/template/templates/asas/src/migrations/20260930102924_material_newton_destine/snapshot.json +3324 -0
  243. package/template/templates/asas/src/migrations/20261003022353_clumsy_taskmaster/migration.sql +2 -0
  244. package/template/templates/asas/src/migrations/20261003022353_clumsy_taskmaster/snapshot.json +3350 -0
  245. package/template/templates/asas/src/migrations/20261003143249_premium_red_shift/migration.sql +21 -0
  246. package/template/templates/asas/src/migrations/20261003143249_premium_red_shift/snapshot.json +3635 -0
  247. package/template/templates/asas/src/migrations/20261008215349_square_norrin_radd/migration.sql +8 -0
  248. package/template/templates/asas/src/migrations/20261008215349_square_norrin_radd/snapshot.json +3635 -0
  249. package/template/templates/asas/src/migrations/20261008230204_many_scarecrow/migration.sql +18 -0
  250. package/template/templates/asas/src/migrations/20261008230204_many_scarecrow/snapshot.json +3635 -0
  251. package/template/templates/asas/src/migrations/20261010005521_elite_giant_girl/migration.sql +18 -0
  252. package/template/templates/asas/src/migrations/20261010005521_elite_giant_girl/snapshot.json +3635 -0
  253. package/template/templates/asas/src/migrations/20261010120822_needy_thunderbolt/migration.sql +11 -0
  254. package/template/templates/asas/src/migrations/20261010120822_needy_thunderbolt/snapshot.json +3667 -0
  255. package/template/templates/asas/src/migrations/20261012000752_organization_ownership/migration.sql +48 -0
  256. package/template/templates/asas/src/migrations/20261012000752_organization_ownership/snapshot.json +3891 -0
  257. package/template/templates/asas/src/migrations/20261014221355_tags_restrict/migration.sql +1 -0
  258. package/template/templates/asas/src/migrations/20261014221355_tags_restrict/snapshot.json +3881 -0
  259. package/template/templates/asas/src/migrations/20261014222218_one_seat_per_session/migration.sql +1 -0
  260. package/template/templates/asas/src/migrations/20261014222218_one_seat_per_session/snapshot.json +3894 -0
  261. package/template/templates/asas/src/migrations/20261014223522_session_archive_reason/migration.sql +1 -0
  262. package/template/templates/asas/src/migrations/20261014223522_session_archive_reason/snapshot.json +3907 -0
  263. package/template/templates/asas/src/migrations/20261014225728_course_archive_by_operation/migration.sql +1 -0
  264. package/template/templates/asas/src/migrations/20261014225728_course_archive_by_operation/snapshot.json +3907 -0
  265. package/template/templates/asas/src/migrations/20261015042444_organization_names/migration.sql +1 -0
  266. package/template/templates/asas/src/migrations/20261015042444_organization_names/snapshot.json +3920 -0
  267. package/template/templates/asas/src/migrations/20261015053212_member_last_seen/migration.sql +1 -0
  268. package/template/templates/asas/src/migrations/20261015053212_member_last_seen/snapshot.json +3933 -0
  269. package/template/templates/asas/src/migrations/20261015093203_activity_and_staff_history/migration.sql +15 -0
  270. package/template/templates/asas/src/migrations/20261015093203_activity_and_staff_history/snapshot.json +4113 -0
  271. package/template/templates/asas/src/migrations/20261015095752_portal/migration.sql +2 -0
  272. package/template/templates/asas/src/migrations/20261015095752_portal/snapshot.json +4113 -0
  273. package/template/templates/asas/src/migrations/20261015135529_better_auth_indexes/migration.sql +9 -0
  274. package/template/templates/asas/src/migrations/20261015135529_better_auth_indexes/snapshot.json +4302 -0
  275. package/template/templates/asas/src/migrations/20261015162438_audit_changes_json/migration.sql +27 -0
  276. package/template/templates/asas/src/migrations/20261015162438_audit_changes_json/snapshot.json +4289 -0
  277. package/template/templates/asas/src/migrations/drizzle.config.ts +11 -0
  278. package/template/templates/asas/src/migrations/schema.ts +30 -0
  279. package/template/templates/asas/src/mithaq.ts +4 -0
  280. package/template/templates/asas/src/organizations.ts +3 -0
  281. package/template/templates/asas/src/seed.ts +427 -0
  282. package/template/templates/asas/src/ui-backoffice/home.tsx +63 -0
  283. package/template/templates/asas/src/ui-backoffice/organization-actions.tsx +121 -0
  284. package/template/templates/asas/src/ui-backoffice/record-page.tsx +30 -0
  285. package/template/templates/asas/src/ui-backoffice/role-cells.tsx +59 -0
  286. package/template/templates/asas/src/ui-backoffice/user-actions.tsx +249 -0
  287. package/template/templates/asas/src/ui-dashboard/dashboard.tsx +300 -0
  288. package/template/templates/asas/src/ui-dashboard/home.tsx +73 -0
  289. package/template/templates/asas/src/ui-dashboard/members-card.tsx +147 -0
  290. package/template/templates/asas/src/ui-dashboard/members.tsx +829 -0
  291. package/template/templates/asas/src/ui-dashboard/theme-provider.tsx +10 -0
  292. package/template/templates/asas/src/ui-dashboard/versions.tsx +446 -0
  293. package/template/templates/asas/src/ui-portal/courses-home.tsx +96 -0
  294. package/template/templates/asas/src/ui-portal/home.tsx +73 -0
  295. package/template/templates/asas/src/ui-portal/page.tsx +11 -0
  296. package/template/templates/features/audit-log/audit-log/audit-log.ts +284 -0
  297. package/template/templates/features/audit-log/backoffice-log/backoffice-log.ts +67 -0
  298. package/template/templates/features/audit-log/index.ts +4 -0
  299. package/template/templates/features/audit-log/package.json +13 -0
  300. package/template/templates/features/auth/index.ts +11 -0
  301. package/template/templates/features/auth/local-users/local-users.ts +143 -0
  302. package/template/templates/features/auth/package.json +13 -0
  303. package/template/templates/features/auth/user/account.access.ts +8 -0
  304. package/template/templates/features/auth/user/account.ts +31 -0
  305. package/template/templates/features/auth/user/index.ts +18 -0
  306. package/template/templates/features/auth/user/invitation.access.ts +4 -0
  307. package/template/templates/features/auth/user/invitation.ts +27 -0
  308. package/template/templates/features/auth/user/member.access.ts +10 -0
  309. package/template/templates/features/auth/user/member.screens.ts +37 -0
  310. package/template/templates/features/auth/user/member.ts +26 -0
  311. package/template/templates/features/auth/user/organization.access.ts +20 -0
  312. package/template/templates/features/auth/user/organization.screens.ts +37 -0
  313. package/template/templates/features/auth/user/organization.ts +25 -0
  314. package/template/templates/features/auth/user/organizationRole.access.ts +8 -0
  315. package/template/templates/features/auth/user/organizationRole.ts +25 -0
  316. package/template/templates/features/auth/user/session.access.ts +8 -0
  317. package/template/templates/features/auth/user/session.ts +28 -0
  318. package/template/templates/features/auth/user/user.access.ts +30 -0
  319. package/template/templates/features/auth/user/user.screens.ts +45 -0
  320. package/template/templates/features/auth/user/user.ts +31 -0
  321. package/template/templates/features/auth/user/verification.access.ts +8 -0
  322. package/template/templates/features/auth/user/verification.ts +23 -0
  323. package/template/templates/features/courses/courses/courses.ts +601 -0
  324. package/template/templates/features/courses/courses/cover-reads.ts +33 -0
  325. package/template/templates/features/courses/courses/ui/archived-badge.tsx +9 -0
  326. package/template/templates/features/courses/courses/ui/fee-cell.tsx +19 -0
  327. package/template/templates/features/courses/courses/ui/no-courses.tsx +13 -0
  328. package/template/templates/features/courses/courses/ui/status-cell.tsx +33 -0
  329. package/template/templates/features/courses/courses/ui/unpublish-button.tsx +15 -0
  330. package/template/templates/features/courses/domain/archive.ts +39 -0
  331. package/template/templates/features/courses/domain/on-offer.ts +6 -0
  332. package/template/templates/features/courses/enrollments/enrollments.ts +309 -0
  333. package/template/templates/features/courses/enrollments/ui/session-cell.tsx +75 -0
  334. package/template/templates/features/courses/enrollments/ui/session-labels.tsx +36 -0
  335. package/template/templates/features/courses/index.ts +8 -0
  336. package/template/templates/features/courses/package.json +24 -0
  337. package/template/templates/features/courses/portal/ui/catalogue.tsx +86 -0
  338. package/template/templates/features/courses/portal/ui/course.tsx +176 -0
  339. package/template/templates/features/courses/portal/ui/enrollments.tsx +170 -0
  340. package/template/templates/features/courses/portal/ui/parts.tsx +95 -0
  341. package/template/templates/features/courses/portal/ui/reads.ts +76 -0
  342. package/template/templates/features/courses/portal/ui/rules.ts +87 -0
  343. package/template/templates/features/courses/portal/ui/shell.tsx +194 -0
  344. package/template/templates/features/courses/sessions/seats.ts +42 -0
  345. package/template/templates/features/courses/sessions/sessions.ts +186 -0
  346. package/template/templates/features/media/index.ts +10 -0
  347. package/template/templates/features/media/media/media.ts +131 -0
  348. package/template/templates/features/media/media/ui/preview.tsx +143 -0
  349. package/template/templates/features/media/package.json +19 -0
  350. package/template/templates/features/roles/index.ts +4 -0
  351. package/template/templates/features/roles/member-roles/member-roles.ts +207 -0
  352. package/template/templates/features/roles/package.json +21 -0
  353. package/template/templates/features/roles/roles/roles.ts +184 -0
  354. package/template/templates/features/roles/roles/ui/role-form.tsx +267 -0
  355. package/template/templates/features/roles/roles/ui/roles.tsx +190 -0
  356. package/template/templates/features/settings/index.ts +3 -0
  357. package/template/templates/features/settings/package.json +13 -0
  358. package/template/templates/features/settings/settings/settings.ts +64 -0
  359. package/template/templates/features/tags/index.ts +3 -0
  360. package/template/templates/features/tags/package.json +14 -0
  361. package/template/templates/features/tags/tags/tags.ts +80 -0
  362. package/template/templates/mithaq/components.json +22 -0
  363. package/template/templates/mithaq/package.json +33 -0
  364. package/template/templates/mithaq/src/mail/index.ts +32 -0
  365. package/template/templates/mithaq/src/mail/mailers.ts +36 -0
  366. package/template/templates/mithaq/src/mithaq/audit-sink.ts +111 -0
  367. package/template/templates/mithaq/src/mithaq/auth/backoffice-roles.ts +108 -0
  368. package/template/templates/mithaq/src/mithaq/auth/index.ts +1859 -0
  369. package/template/templates/mithaq/src/mithaq/auth/organizations.ts +80 -0
  370. package/template/templates/mithaq/src/mithaq/pages.ts +83 -0
  371. package/template/templates/mithaq/src/mithaq/rich-text-nodes.ts +19 -0
  372. package/template/templates/mithaq/src/mithaq.ts +593 -0
  373. package/template/templates/mithaq/src/roles/default-member-roles.ts +29 -0
  374. package/template/templates/mithaq/src/roles/grants.ts +47 -0
  375. package/template/templates/mithaq/src/roles/member-access.ts +11 -0
  376. package/template/templates/mithaq/src/roles/statements.ts +13 -0
  377. package/template/templates/mithaq/src/roles/system-roles.ts +16 -0
  378. package/template/templates/mithaq/src/roles/tenant-access.ts +260 -0
  379. package/template/templates/mithaq/src/roles/tenant-roles.ts +321 -0
  380. package/template/templates/mithaq/src/steps/expected-updated-at.ts +41 -0
  381. package/template/templates/mithaq/src/storage/defaults.ts +156 -0
  382. package/template/templates/mithaq/src/storage/index.ts +97 -0
  383. package/template/templates/mithaq/src/storage/local.ts +143 -0
  384. package/template/templates/mithaq/src/storage/r2.ts +5 -0
  385. package/template/templates/mithaq/src/storage/s3.ts +91 -0
  386. package/template/templates/mithaq/src/storage/uploadthing.ts +59 -0
  387. package/template/templates/mithaq/src/ui/activity-page.tsx +33 -0
  388. package/template/templates/mithaq/src/ui/activity.tsx +527 -0
  389. package/template/templates/mithaq/src/ui/app-shell.tsx +268 -0
  390. package/template/templates/mithaq/src/ui/kpi-card.tsx +25 -0
  391. package/template/templates/mithaq/src/ui/record-page.tsx +50 -0
  392. package/template/templates/mithaq/src/ui/rich-text-nodes.tsx +33 -0
  393. package/template/templates/mithaq/src/ui/screen-nav.tsx +28 -0
  394. package/template/templates/mithaq/src/ui/table-page.tsx +9 -0
  395. package/template/templates/mithaq/src/ui/top-shell.tsx +60 -0
  396. package/README.md +0 -3
@@ -0,0 +1,150 @@
1
+ import { useState, type ComponentProps, type ReactNode } from 'react'
2
+ import { redirect, useRouteContext } from '@tanstack/react-router'
3
+ import { createServerFn } from '@tanstack/react-start'
4
+ import { Eye, EyeOff } from 'lucide-react'
5
+ import { Button } from '@/components/ui/button'
6
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
7
+ import { Input } from '@/components/ui/input'
8
+ import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from '@/components/ui/input-group'
9
+ import { authArm } from './api.ts'
10
+ import { FieldShell, describedBy } from 'mithaq-admin/field-shell.tsx'
11
+ import { refusalKey } from './copy.ts'
12
+ import { LocaleSwitch } from './locale-switch.tsx'
13
+ import { landing } from './redirect.ts'
14
+ import { useCopy } from './routes/__root.tsx'
15
+
16
+ /** The one search key the auth pages read, off the raw search. */
17
+ export const redirectSearch = (search: Record<string, unknown>): { redirect?: string } =>
18
+ typeof search.redirect === 'string' ? { redirect: search.redirect } : {}
19
+
20
+ /** Whether a read of the actor names someone. A read that throws, or an actor with no email, names no one. */
21
+ export const signedInAs = async (actor: Promise<unknown>) => Boolean(((await actor.catch(() => null)) as { email?: unknown } | null)?.email)
22
+
23
+ /**
24
+ * Whether this request carries a session, the screens it opens, the first of them as `home`, which a link back names,
25
+ * and the provider's password bounds. No auth page sends a referrer, since the reset page's address carries its token.
26
+ */
27
+ export const authGate = createServerFn({ method: 'GET' }).handler(async () => {
28
+ // Imported inside the server half so no auth page ships the screen host to the browser.
29
+ const [{ getRequest, setResponseHeader }, { asas }, { loadEnterable }] = await Promise.all([import('@tanstack/react-start/server'), import('asas'), import('./screens.tsx')])
30
+ setResponseHeader('referrer-policy', 'no-referrer')
31
+ type Provider = { name: string; server: { $context: Promise<{ password: { config: { minPasswordLength: number; maxPasswordLength: number } } }> } }
32
+ const provider = (asas.auth.strategies as unknown as Provider[]).find((one) => one.name === 'user')!
33
+ const [signedIn, { password }] = await Promise.all([signedInAs(asas.auth.actor(getRequest().headers)), provider.server.$context])
34
+ const entered = signedIn ? ((await loadEnterable())?.screens ?? []) : []
35
+ const home: { path: string; name?: string } = entered[0] ?? { path: '/unauthorized' }
36
+ return { signedIn, home, screens: entered, password: { min: password.config.minPasswordLength, max: password.config.maxPasswordLength } }
37
+ })
38
+
39
+ /**
40
+ * Sign-in's and sign-up's `beforeLoad`. A signed-in person is sent where a
41
+ * successful sign-in sends them; everyone else gets the password bounds in the
42
+ * route context.
43
+ */
44
+ export const gate = async ({ search }: { search: { redirect?: string } }) => {
45
+ const { signedIn, screens, password } = await authGate()
46
+ if (signedIn) throw redirect({ href: landing({ asked: search.redirect, screens }) })
47
+ return { password }
48
+ }
49
+
50
+ /** What a link to `home` says: the screen it opens, or, where it opens none, the action of seeing the account's status. */
51
+ export const useHomeLabel = () => {
52
+ const { t } = useCopy()
53
+ return (home: { name?: string }, sentence: 'auth.backTo' | 'marketing.openScreen') =>
54
+ home.name === undefined
55
+ ? t('auth.accountStatus', 'See your account status')
56
+ : t(sentence, sentence === 'auth.backTo' ? 'Back to {screen}' : 'Open {screen}')!.replace('{screen}', () => t(`screens.${home.name}`, home.name)!)
57
+ }
58
+
59
+ /** What the forgot and reset pages draw for someone already signed in: the way on, and a sign-out. */
60
+ export function AlreadySignedIn({ redirect: asked, home, screens }: { redirect?: string; home: { path: string; name?: string }; screens: readonly { name: string; path: string }[] }) {
61
+ const { t } = useCopy()
62
+ const label = useHomeLabel()
63
+ const to = landing({ asked, screens })
64
+ return (
65
+ <AuthFrame title={t('auth.alreadySignedIn', 'You are already signed in')}>
66
+ <div className="grid gap-4">
67
+ {/* An anchor: the path is the visitor's own, not a route the router knows. */}
68
+ <a className="text-primary text-center text-sm underline underline-offset-4" href={to}>
69
+ {to === home.path ? label(home, 'auth.backTo') : t('auth.continue', 'Continue')}
70
+ </a>
71
+ <Button type="button" variant="outline" onClick={() => void authArm('sign-out').then(() => window.location.reload())}>
72
+ {t('admin.signOut', 'Sign out')}
73
+ </Button>
74
+ </div>
75
+ </AuthFrame>
76
+ )
77
+ }
78
+
79
+ /** The sentence for a refusal of the provider: this app's own words for the status or code, then `fallback`. Never the provider's `message`, which is English in any locale. */
80
+ export const useRefusal = () => {
81
+ const { t } = useCopy()
82
+ return (answered: { status: number; body: Record<string, unknown> | null }, fallback: string | undefined) => {
83
+ const key = refusalKey(answered.status, answered.body?.code)
84
+ return (key && t(key)) || fallback || ''
85
+ }
86
+ }
87
+
88
+ /** Whether the browser's own `type="email"` refuses this text. */
89
+ export const notAnEmail = (value: string) => Object.assign(document.createElement('input'), { type: 'email', value }).validity.typeMismatch
90
+
91
+ /** The frame every auth page is drawn in: the app's name linking home, the language switch, and the page's card. */
92
+ export function AuthFrame({ title, description, children }: { title: string | undefined; description?: ReactNode; children: ReactNode }) {
93
+ const { t, locale } = useCopy()
94
+ const { app } = useRouteContext({ from: '__root__' })
95
+ return (
96
+ <div className="flex min-h-screen flex-col">
97
+ <header className="mx-auto flex h-16 w-full max-w-6xl items-center justify-between gap-2 px-4 sm:px-6">
98
+ {/* An anchor: `/` is not a router route in an app whose start entry answers its home. */}
99
+ <a className="text-lg font-semibold" href="/">
100
+ {t(app, 'Asas')}
101
+ </a>
102
+ <LocaleSwitch locale={locale} />
103
+ </header>
104
+ <main className="flex flex-1 items-center justify-center px-4 pb-16">
105
+ <Card className="w-full max-w-sm">
106
+ <CardHeader>
107
+ <CardTitle><h1>{title}</h1></CardTitle>
108
+ {description ? <CardDescription>{description}</CardDescription> : null}
109
+ </CardHeader>
110
+ {/* Descriptions follow the text direction; the shared primitive aligns them left. */}
111
+ <CardContent className="[&_[data-slot=field-description]]:text-start">{children}</CardContent>
112
+ </Card>
113
+ </main>
114
+ </div>
115
+ )
116
+ }
117
+
118
+ function PasswordInput(props: ComponentProps<'input'>) {
119
+ const { t } = useCopy()
120
+ const [shown, setShown] = useState(false)
121
+ return (
122
+ // Focus moving to the box's own toggle has not left the box.
123
+ <InputGroup onBlur={(event) => event.currentTarget.contains(event.relatedTarget) && event.stopPropagation()}>
124
+ <InputGroupInput {...props} dir="ltr" type={shown ? 'text' : 'password'} />
125
+ <InputGroupAddon align="inline-end">
126
+ <InputGroupButton aria-label={t('auth.showPassword', 'Show password')} aria-pressed={shown} size="icon-xs" onClick={() => setShown((was) => !was)}>
127
+ {shown ? <EyeOff /> : <Eye />}
128
+ </InputGroupButton>
129
+ </InputGroupAddon>
130
+ </InputGroup>
131
+ )
132
+ }
133
+
134
+ /** One labelled box, which says what is wrong with it once it has been left. An email or password reads left to right in any locale. */
135
+ export function AuthBox({
136
+ id,
137
+ label,
138
+ error,
139
+ description,
140
+ onLeave,
141
+ ...props
142
+ }: { id: string; label: string | undefined; error?: string; description?: string; onLeave: () => void } & ComponentProps<'input'>) {
143
+ const control = { 'aria-describedby': describedBy({ id, description, error }), 'aria-invalid': error ? true : undefined, id, name: id, required: true, ...props }
144
+ return (
145
+ <FieldShell id={id} label={label ?? id} description={description} error={error} onBlur={onLeave}>
146
+ {props.type === 'password' ? <PasswordInput {...control} /> : <Input dir={props.type === 'email' ? 'ltr' : 'auto'} {...control} />}
147
+ </FieldShell>
148
+ )
149
+ }
150
+ // mithaq-admin 0.0.0 e498423cf4a61adb8cf6358d043d31ae92d853de4c6939a9c0fe138c848b7211
@@ -0,0 +1,150 @@
1
+ import { useState, type ComponentProps } from 'react'
2
+ import { Link, useNavigate } from '@tanstack/react-router'
3
+ import { createServerFn } from '@tanstack/react-start'
4
+ import { Button } from '@/components/ui/button'
5
+ import { authArm } from './api.ts'
6
+ import { AlreadySignedIn, AuthBox, AuthFrame, authGate, redirectSearch, useRefusal } from './auth-frame.tsx'
7
+ import { useCopy } from './routes/__root.tsx'
8
+
9
+ /** The search keys of a page a mailed link opens: its token, and where to go after. */
10
+ export const tokenSearch = (search: Record<string, unknown>): { redirect?: string; token?: string } => ({
11
+ ...redirectSearch(search),
12
+ ...(typeof search.token === 'string' && search.token ? { token: search.token } : {}),
13
+ })
14
+
15
+ /**
16
+ * The address of the account a live link chooses the password of, or `null` for a spent, expired or made-up one: the
17
+ * provider's own answer, read in process. A POST, so the token rides in no further URL.
18
+ */
19
+ const passwordLink = createServerFn({ method: 'POST' })
20
+ .validator((token: unknown) => (typeof token === 'string' ? token : ''))
21
+ .handler(async ({ data }) => {
22
+ // Imported inside the server half so no page ships the provider to the browser.
23
+ const { asas } = await import('asas')
24
+ type Provider = { name: string; server: { api: { setPasswordState(args: { query: { token: string } }): Promise<{ email: string }> } } }
25
+ const provider = (asas.auth.strategies as unknown as Provider[]).find((one) => one.name === 'user')!
26
+ return data ? provider.server.api.setPasswordState({ query: { token: data } }).then(({ email }) => email, () => null) : null
27
+ })
28
+
29
+ /** The `beforeLoad` of a page a mailed link opens: the auth gate's answer, and the account the link is for. */
30
+ export const passwordGate = async ({ token }: { token?: string }) => ({ ...(await authGate()), email: token ? await passwordLink({ data: token }) : null })
31
+
32
+ type Props = {
33
+ /** A first password, chosen from a sign-up's or an added person's link, rather than a forgotten one replaced. */
34
+ first: boolean
35
+ token?: string
36
+ redirect?: string
37
+ signedIn: boolean
38
+ home: { path: string; name?: string }
39
+ /** The screens a signed-in person opens. */
40
+ screens: readonly { name: string; path: string }[]
41
+ password: { min: number; max: number }
42
+ /** The account the link is for; `null` for a link that names none, `undefined` while unread. */
43
+ email?: string | null
44
+ }
45
+
46
+ /**
47
+ * The page a mailed link opens to choose an account's password: a first one, or one forgotten. It shows the account
48
+ * it is for and signs the person in with the password just chosen, through the ordinary sign-in, as Payload's reset
49
+ * does: every rule a sign-in keeps still holds, and nothing is known that the person did not just type.
50
+ */
51
+ export function ChoosePassword({ first, token, redirect, signedIn, home, screens, password: bounds, email }: Props) {
52
+ const { t } = useCopy()
53
+ const navigate = useNavigate()
54
+ const refused = useRefusal()
55
+ const [password, setPassword] = useState('')
56
+ const [confirmation, setConfirmation] = useState('')
57
+ const [pending, setPending] = useState(false)
58
+ const [done, setDone] = useState(false)
59
+ // The answer, not its sentence, so a language switch says it again in the language chosen.
60
+ const [refusal, setRefusal] = useState<Parameters<typeof refused>[0] | null>(null)
61
+ const [left, setLeft] = useState<ReadonlySet<string>>(() => new Set())
62
+ const title = first ? t('auth.setPassword', 'Choose your password') : t('auth.resetPassword', 'Reset password')
63
+ const wrong = {
64
+ password: () =>
65
+ !password ? t('auth.required', 'This field is required.')
66
+ : password.length < bounds.min ? t('auth.passwordTooShort', 'Password too short')
67
+ : password.length > bounds.max ? t('auth.passwordTooLong', 'Password too long')
68
+ : undefined,
69
+ confirmation: () => (!confirmation ? t('auth.required', 'This field is required.') : confirmation === password ? undefined : t('auth.passwordsDiffer', 'The two passwords are not the same.')),
70
+ }
71
+ const errorOf = (box: keyof typeof wrong) => (left.has(box) ? wrong[box]() : undefined)
72
+ const box = (id: keyof typeof wrong, label: string | undefined, props: ComponentProps<'input'> & { description?: string }) => (
73
+ <AuthBox id={id} label={label} error={errorOf(id)} onLeave={() => setLeft((already) => new Set(already).add(id))} {...props} />
74
+ )
75
+ const signIn = (
76
+ <p className="mt-4 text-center text-sm">
77
+ <Link className="text-primary underline underline-offset-4" to="/auth/sign-in">
78
+ {t('auth.signIn', 'Sign in')}
79
+ </Link>
80
+ </p>
81
+ )
82
+
83
+ const submit = async (event: React.FormEvent) => {
84
+ event.preventDefault()
85
+ setLeft(new Set(Object.keys(wrong)))
86
+ if (Object.values(wrong).some((check) => check())) return
87
+ setPending(true)
88
+ setRefusal(null)
89
+ const answered = await authArm('reset-password', { newPassword: password, token })
90
+ if (!answered.ok) {
91
+ setPending(false)
92
+ return setRefusal(answered)
93
+ }
94
+ // A sign-in this refuses, a banned account's, is left to the sign-in page, which says why.
95
+ // Signed in, the sign-in page's own gate sends the person on to `redirect` or their first screen.
96
+ if (email && (await authArm('sign-in/email', { email, password })).ok) return navigate({ to: '/auth/sign-in', search: { redirect } })
97
+ setPending(false)
98
+ setDone(true)
99
+ }
100
+
101
+ if (signedIn) return <AlreadySignedIn redirect={redirect} home={home} screens={screens} />
102
+ if (!token || email === null) {
103
+ return (
104
+ <AuthFrame title={title} description={t('auth.tokenInvalid', 'Token is either invalid or has expired.')}>
105
+ <p className="text-center text-sm">
106
+ <Link className="text-primary underline underline-offset-4" to="/auth/forgot-password">
107
+ {t('auth.forgotPassword', 'Forgot password')}
108
+ </Link>
109
+ </p>
110
+ </AuthFrame>
111
+ )
112
+ }
113
+ if (done) {
114
+ return (
115
+ <AuthFrame title={title} description={first ? t('auth.passwordChosen', 'Your address is confirmed and your password is set. Sign in with it.') : t('auth.passwordReset', 'Password reset successfully.')}>
116
+ {signIn}
117
+ </AuthFrame>
118
+ )
119
+ }
120
+ return (
121
+ <AuthFrame title={title}>
122
+ <form className="grid gap-4" noValidate onSubmit={submit}>
123
+ {/* The account the password is for, which a password manager saves it under. */}
124
+ {email ? <AuthBox id="email" label={t('auth.email', 'Email')} type="email" autoComplete="username" readOnly value={email} onLeave={() => {}} /> : null}
125
+ {box('password', t('auth.newPassword', 'New password'), {
126
+ autoComplete: 'new-password',
127
+ description: t('auth.passwordRule', 'At least {count} characters.')!.replace('{count}', String(bounds.min)),
128
+ onChange: (event) => setPassword(event.target.value),
129
+ type: 'password',
130
+ value: password,
131
+ })}
132
+ {box('confirmation', t('auth.confirmPassword', 'Confirm the password'), {
133
+ autoComplete: 'new-password',
134
+ onChange: (event) => setConfirmation(event.target.value),
135
+ type: 'password',
136
+ value: confirmation,
137
+ })}
138
+ {refusal && (
139
+ <p className="text-destructive text-sm" role="alert">
140
+ {refused(refusal, t('auth.resetFailed', 'That reset did not work.'))}
141
+ </p>
142
+ )}
143
+ <Button disabled={pending} type="submit">
144
+ {first ? t('auth.setPasswordSubmit', 'Confirm and set password') : t('auth.resetPassword', 'Reset password')}
145
+ </Button>
146
+ </form>
147
+ {signIn}
148
+ </AuthFrame>
149
+ )
150
+ }
@@ -0,0 +1,10 @@
1
+ import { Accordion as Primitive } from '@base-ui/react/accordion'
2
+ import { cn } from '@/lib/utils'
3
+
4
+ export const Accordion = Primitive.Root
5
+ export const AccordionItem = Primitive.Item
6
+ export const AccordionHeader = Primitive.Header
7
+ export const AccordionPanel = Primitive.Panel
8
+ export function AccordionTrigger({ className, ...props }: Primitive.Trigger.Props) {
9
+ return <Primitive.Trigger className={cn('flex w-full cursor-pointer items-center justify-between px-4 py-3 text-start', className)} {...props} />
10
+ }
@@ -0,0 +1,173 @@
1
+ import * as React from "react"
2
+ import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"
3
+
4
+ import { cn } from "@/lib/utils"
5
+ import { Button } from "./button"
6
+
7
+ function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
8
+ return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
9
+ }
10
+
11
+ function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
12
+ return (
13
+ <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
14
+ )
15
+ }
16
+
17
+ function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
18
+ return (
19
+ <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
20
+ )
21
+ }
22
+
23
+ function AlertDialogOverlay({
24
+ className,
25
+ ...props
26
+ }: AlertDialogPrimitive.Backdrop.Props) {
27
+ return (
28
+ <AlertDialogPrimitive.Backdrop
29
+ data-slot="alert-dialog-overlay"
30
+ className={cn(
31
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/80 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50",
32
+ className
33
+ )}
34
+ {...props}
35
+ />
36
+ )
37
+ }
38
+
39
+ function AlertDialogContent({
40
+ className,
41
+ size = "default",
42
+ ...props
43
+ }: AlertDialogPrimitive.Popup.Props & {
44
+ size?: "default" | "sm"
45
+ }) {
46
+ return (
47
+ <AlertDialogPortal>
48
+ <AlertDialogOverlay />
49
+ <AlertDialogPrimitive.Popup
50
+ data-slot="alert-dialog-content"
51
+ data-size={size}
52
+ className={cn(
53
+ "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/5 gap-6 rounded-4xl p-6 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-md group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none",
54
+ className
55
+ )}
56
+ {...props}
57
+ />
58
+ </AlertDialogPortal>
59
+ )
60
+ }
61
+
62
+ function AlertDialogHeader({
63
+ className,
64
+ ...props
65
+ }: React.ComponentProps<"div">) {
66
+ return (
67
+ <div
68
+ data-slot="alert-dialog-header"
69
+ className={cn("grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]", className)}
70
+ {...props}
71
+ />
72
+ )
73
+ }
74
+
75
+ function AlertDialogFooter({
76
+ className,
77
+ ...props
78
+ }: React.ComponentProps<"div">) {
79
+ return (
80
+ <div
81
+ data-slot="alert-dialog-footer"
82
+ className={cn(
83
+ "flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
84
+ className
85
+ )}
86
+ {...props}
87
+ />
88
+ )
89
+ }
90
+
91
+ function AlertDialogMedia({
92
+ className,
93
+ ...props
94
+ }: React.ComponentProps<"div">) {
95
+ return (
96
+ <div
97
+ data-slot="alert-dialog-media"
98
+ className={cn("bg-muted mb-2 inline-flex size-16 items-center justify-center rounded-full sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-8", className)}
99
+ {...props}
100
+ />
101
+ )
102
+ }
103
+
104
+ function AlertDialogTitle({
105
+ className,
106
+ ...props
107
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
108
+ return (
109
+ <AlertDialogPrimitive.Title
110
+ data-slot="alert-dialog-title"
111
+ className={cn("text-lg font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2", className)}
112
+ {...props}
113
+ />
114
+ )
115
+ }
116
+
117
+ function AlertDialogDescription({
118
+ className,
119
+ ...props
120
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
121
+ return (
122
+ <AlertDialogPrimitive.Description
123
+ data-slot="alert-dialog-description"
124
+ className={cn("text-muted-foreground *:[a]:hover:text-foreground text-sm text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3", className)}
125
+ {...props}
126
+ />
127
+ )
128
+ }
129
+
130
+ function AlertDialogAction({
131
+ className,
132
+ ...props
133
+ }: React.ComponentProps<typeof Button>) {
134
+ return (
135
+ <Button
136
+ data-slot="alert-dialog-action"
137
+ className={cn(className)}
138
+ {...props}
139
+ />
140
+ )
141
+ }
142
+
143
+ function AlertDialogCancel({
144
+ className,
145
+ variant = "outline",
146
+ size = "default",
147
+ ...props
148
+ }: AlertDialogPrimitive.Close.Props &
149
+ Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
150
+ return (
151
+ <AlertDialogPrimitive.Close
152
+ data-slot="alert-dialog-cancel"
153
+ className={cn(className)}
154
+ render={<Button variant={variant} size={size} />}
155
+ {...props}
156
+ />
157
+ )
158
+ }
159
+
160
+ export {
161
+ AlertDialog,
162
+ AlertDialogAction,
163
+ AlertDialogCancel,
164
+ AlertDialogContent,
165
+ AlertDialogDescription,
166
+ AlertDialogFooter,
167
+ AlertDialogHeader,
168
+ AlertDialogMedia,
169
+ AlertDialogOverlay,
170
+ AlertDialogPortal,
171
+ AlertDialogTitle,
172
+ AlertDialogTrigger,
173
+ }
@@ -0,0 +1,104 @@
1
+ import * as React from "react"
2
+ import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ function Avatar({
7
+ className,
8
+ size = "default",
9
+ ...props
10
+ }: AvatarPrimitive.Root.Props & {
11
+ size?: "default" | "sm" | "lg"
12
+ }) {
13
+ return (
14
+ <AvatarPrimitive.Root
15
+ data-slot="avatar"
16
+ data-size={size}
17
+ className={cn(
18
+ "size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 after:border-border group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten",
19
+ className
20
+ )}
21
+ {...props}
22
+ />
23
+ )
24
+ }
25
+
26
+ function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
27
+ return (
28
+ <AvatarPrimitive.Image
29
+ data-slot="avatar-image"
30
+ className={cn(
31
+ "rounded-full aspect-square size-full object-cover",
32
+ className
33
+ )}
34
+ {...props}
35
+ />
36
+ )
37
+ }
38
+
39
+ function AvatarFallback({
40
+ className,
41
+ ...props
42
+ }: AvatarPrimitive.Fallback.Props) {
43
+ return (
44
+ <AvatarPrimitive.Fallback
45
+ data-slot="avatar-fallback"
46
+ className={cn(
47
+ "bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs",
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ )
53
+ }
54
+
55
+ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
56
+ return (
57
+ <span
58
+ data-slot="avatar-badge"
59
+ className={cn(
60
+ "bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-blend-color ring-2 select-none",
61
+ "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
62
+ "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
63
+ "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
64
+ className
65
+ )}
66
+ {...props}
67
+ />
68
+ )
69
+ }
70
+
71
+ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
72
+ return (
73
+ <div
74
+ data-slot="avatar-group"
75
+ className={cn(
76
+ "*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2",
77
+ className
78
+ )}
79
+ {...props}
80
+ />
81
+ )
82
+ }
83
+
84
+ function AvatarGroupCount({
85
+ className,
86
+ ...props
87
+ }: React.ComponentProps<"div">) {
88
+ return (
89
+ <div
90
+ data-slot="avatar-group-count"
91
+ className={cn("bg-muted text-muted-foreground size-8 rounded-full text-sm group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3 ring-background relative flex shrink-0 items-center justify-center ring-2", className)}
92
+ {...props}
93
+ />
94
+ )
95
+ }
96
+
97
+ export {
98
+ Avatar,
99
+ AvatarImage,
100
+ AvatarFallback,
101
+ AvatarGroup,
102
+ AvatarGroupCount,
103
+ AvatarBadge,
104
+ }
@@ -0,0 +1,48 @@
1
+ import { mergeProps } from "@base-ui/react/merge-props"
2
+ import { useRender } from "@base-ui/react/use-render"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const badgeVariants = cva(
8
+ "h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive overflow-hidden group/badge",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
13
+ secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
14
+ destructive: "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
15
+ outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground bg-input/30",
16
+ ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
17
+ link: "text-primary underline-offset-4 hover:underline",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ }
24
+ )
25
+
26
+ function Badge({
27
+ className,
28
+ variant = "default",
29
+ render,
30
+ ...props
31
+ }: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
32
+ return useRender({
33
+ defaultTagName: "span",
34
+ props: mergeProps<"span">(
35
+ {
36
+ className: cn(badgeVariants({ className, variant })),
37
+ },
38
+ props
39
+ ),
40
+ render,
41
+ state: {
42
+ slot: "badge",
43
+ variant,
44
+ },
45
+ })
46
+ }
47
+
48
+ export { Badge, badgeVariants }