create-mithaq 0.0.0-stage → 0.1.1

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 (357) hide show
  1. package/LICENSE +22 -0
  2. package/LICENSES/payload.txt +22 -0
  3. package/LICENSES/ponytail.txt +21 -0
  4. package/index.js +127 -0
  5. package/package.json +19 -4
  6. package/template/apps/asas-platform/components.json +25 -0
  7. package/template/apps/asas-platform/gitignore +3 -0
  8. package/template/apps/asas-platform/package.json +55 -0
  9. package/template/apps/asas-platform/public/favicon.svg +1 -0
  10. package/template/apps/asas-platform/src/api.ts +147 -0
  11. package/template/apps/asas-platform/src/auth-frame.tsx +150 -0
  12. package/template/apps/asas-platform/src/components/mithaq/cells.tsx +112 -0
  13. package/template/apps/asas-platform/src/components/mithaq/controls.tsx +650 -0
  14. package/template/apps/asas-platform/src/components/mithaq/field-shell.tsx +53 -0
  15. package/template/apps/asas-platform/src/components/mithaq/filter-operators.ts +108 -0
  16. package/template/apps/asas-platform/src/components/mithaq/form.tsx +1467 -0
  17. package/template/apps/asas-platform/src/components/mithaq/list-columns.tsx +108 -0
  18. package/template/apps/asas-platform/src/components/mithaq/list-filters.tsx +368 -0
  19. package/template/apps/asas-platform/src/components/mithaq/list-pagination.tsx +153 -0
  20. package/template/apps/asas-platform/src/components/mithaq/list-query.ts +432 -0
  21. package/template/apps/asas-platform/src/components/mithaq/list-selection.tsx +74 -0
  22. package/template/apps/asas-platform/src/components/mithaq/list.tsx +865 -0
  23. package/template/apps/asas-platform/src/components/mithaq/nav.tsx +871 -0
  24. package/template/apps/asas-platform/src/components/mithaq/nodes.tsx +1024 -0
  25. package/template/apps/asas-platform/src/components/mithaq/relationship-cell.tsx +127 -0
  26. package/template/apps/asas-platform/src/components/mithaq/relationship-control.tsx +400 -0
  27. package/template/apps/asas-platform/src/components/mithaq/rich-text-nodes.ts +6 -0
  28. package/template/apps/asas-platform/src/components/mithaq/rich-text.tsx +1083 -0
  29. package/template/apps/asas-platform/src/components/mithaq/upload.tsx +686 -0
  30. package/template/apps/asas-platform/src/components/ui/accordion.tsx +74 -0
  31. package/template/apps/asas-platform/src/components/ui/alert-dialog.tsx +185 -0
  32. package/template/apps/asas-platform/src/components/ui/avatar.tsx +106 -0
  33. package/template/apps/asas-platform/src/components/ui/badge.tsx +48 -0
  34. package/template/apps/asas-platform/src/components/ui/breadcrumb.tsx +124 -0
  35. package/template/apps/asas-platform/src/components/ui/button-group.tsx +87 -0
  36. package/template/apps/asas-platform/src/components/ui/button.tsx +55 -0
  37. package/template/apps/asas-platform/src/components/ui/calendar.tsx +223 -0
  38. package/template/apps/asas-platform/src/components/ui/card.tsx +102 -0
  39. package/template/apps/asas-platform/src/components/ui/chart.tsx +371 -0
  40. package/template/apps/asas-platform/src/components/ui/checkbox.tsx +28 -0
  41. package/template/apps/asas-platform/src/components/ui/collapsible.tsx +19 -0
  42. package/template/apps/asas-platform/src/components/ui/combobox.tsx +308 -0
  43. package/template/apps/asas-platform/src/components/ui/dialog.tsx +158 -0
  44. package/template/apps/asas-platform/src/components/ui/direction.tsx +6 -0
  45. package/template/apps/asas-platform/src/components/ui/directional-icon.tsx +20 -0
  46. package/template/apps/asas-platform/src/components/ui/drawer.tsx +216 -0
  47. package/template/apps/asas-platform/src/components/ui/dropdown-menu.tsx +267 -0
  48. package/template/apps/asas-platform/src/components/ui/field.tsx +236 -0
  49. package/template/apps/asas-platform/src/components/ui/input-group.tsx +155 -0
  50. package/template/apps/asas-platform/src/components/ui/input-otp.tsx +84 -0
  51. package/template/apps/asas-platform/src/components/ui/input.tsx +19 -0
  52. package/template/apps/asas-platform/src/components/ui/label.tsx +19 -0
  53. package/template/apps/asas-platform/src/components/ui/popover.tsx +90 -0
  54. package/template/apps/asas-platform/src/components/ui/radio-group.tsx +37 -0
  55. package/template/apps/asas-platform/src/components/ui/resizable.tsx +47 -0
  56. package/template/apps/asas-platform/src/components/ui/scroll-area.tsx +54 -0
  57. package/template/apps/asas-platform/src/components/ui/select.tsx +204 -0
  58. package/template/apps/asas-platform/src/components/ui/separator.tsx +24 -0
  59. package/template/apps/asas-platform/src/components/ui/sheet.tsx +139 -0
  60. package/template/apps/asas-platform/src/components/ui/sidebar.tsx +714 -0
  61. package/template/apps/asas-platform/src/components/ui/skeleton.tsx +13 -0
  62. package/template/apps/asas-platform/src/components/ui/slider.tsx +55 -0
  63. package/template/apps/asas-platform/src/components/ui/spinner.tsx +10 -0
  64. package/template/apps/asas-platform/src/components/ui/switch.tsx +29 -0
  65. package/template/apps/asas-platform/src/components/ui/table.tsx +115 -0
  66. package/template/apps/asas-platform/src/components/ui/tabs.tsx +79 -0
  67. package/template/apps/asas-platform/src/components/ui/textarea.tsx +17 -0
  68. package/template/apps/asas-platform/src/components/ui/toggle-group.tsx +89 -0
  69. package/template/apps/asas-platform/src/components/ui/toggle.tsx +44 -0
  70. package/template/apps/asas-platform/src/components/ui/tooltip.tsx +63 -0
  71. package/template/apps/asas-platform/src/copy.ts +1 -0
  72. package/template/apps/asas-platform/src/hooks/use-mobile.ts +19 -0
  73. package/template/apps/asas-platform/src/lib/utils.ts +1 -0
  74. package/template/apps/asas-platform/src/links.ts +38 -0
  75. package/template/apps/asas-platform/src/locale-switch.tsx +26 -0
  76. package/template/apps/asas-platform/src/redirect.ts +1 -0
  77. package/template/apps/asas-platform/src/request-headers.ts +11 -0
  78. package/template/apps/asas-platform/src/routeTree.gen.ts +181 -0
  79. package/template/apps/asas-platform/src/router.tsx +7 -0
  80. package/template/apps/asas-platform/src/routes/(screens)/platform/files.ts +4 -0
  81. package/template/apps/asas-platform/src/routes/(screens)/platform/screen.json +3609 -0
  82. package/template/apps/asas-platform/src/routes/(screens)/platform/types.ts +145 -0
  83. package/template/apps/asas-platform/src/routes/(screens)/platform.$.tsx +17 -0
  84. package/template/apps/asas-platform/src/routes/(screens)/platform.index.tsx +10 -0
  85. package/template/apps/asas-platform/src/routes/(screens)/platform.tsx +13 -0
  86. package/template/apps/asas-platform/src/routes/__root.tsx +95 -0
  87. package/template/apps/asas-platform/src/routes/api/$.ts +30 -0
  88. package/template/apps/asas-platform/src/routes/auth.sign-in.tsx +72 -0
  89. package/template/apps/asas-platform/src/routes/unauthorized.tsx +122 -0
  90. package/template/apps/asas-platform/src/screen-form.tsx +34 -0
  91. package/template/apps/asas-platform/src/screen-list.tsx +139 -0
  92. package/template/apps/asas-platform/src/screen-names.ts +10 -0
  93. package/template/apps/asas-platform/src/screens.tsx +868 -0
  94. package/template/apps/asas-platform/src/server.ts +20 -0
  95. package/template/apps/asas-platform/src/slide.ts +22 -0
  96. package/template/apps/asas-platform/src/start.ts +42 -0
  97. package/template/apps/asas-platform/src/styles.css +200 -0
  98. package/template/apps/asas-platform/src/theme.ts +2 -0
  99. package/template/apps/asas-platform/tsconfig.json +19 -0
  100. package/template/apps/asas-platform/vite.config.ts +26 -0
  101. package/template/apps/asas-tanstack/components.json +25 -0
  102. package/template/apps/asas-tanstack/gitignore +3 -0
  103. package/template/apps/asas-tanstack/package.json +56 -0
  104. package/template/apps/asas-tanstack/public/favicon.svg +1 -0
  105. package/template/apps/asas-tanstack/src/api.ts +147 -0
  106. package/template/apps/asas-tanstack/src/auth-frame.tsx +150 -0
  107. package/template/apps/asas-tanstack/src/components/mithaq/cells.tsx +112 -0
  108. package/template/apps/asas-tanstack/src/components/mithaq/controls.tsx +650 -0
  109. package/template/apps/asas-tanstack/src/components/mithaq/field-shell.tsx +53 -0
  110. package/template/apps/asas-tanstack/src/components/mithaq/filter-operators.ts +108 -0
  111. package/template/apps/asas-tanstack/src/components/mithaq/form.tsx +1467 -0
  112. package/template/apps/asas-tanstack/src/components/mithaq/list-columns.tsx +108 -0
  113. package/template/apps/asas-tanstack/src/components/mithaq/list-filters.tsx +368 -0
  114. package/template/apps/asas-tanstack/src/components/mithaq/list-pagination.tsx +153 -0
  115. package/template/apps/asas-tanstack/src/components/mithaq/list-query.ts +432 -0
  116. package/template/apps/asas-tanstack/src/components/mithaq/list-selection.tsx +74 -0
  117. package/template/apps/asas-tanstack/src/components/mithaq/list.tsx +865 -0
  118. package/template/apps/asas-tanstack/src/components/mithaq/nav.tsx +871 -0
  119. package/template/apps/asas-tanstack/src/components/mithaq/nodes.tsx +1024 -0
  120. package/template/apps/asas-tanstack/src/components/mithaq/relationship-cell.tsx +127 -0
  121. package/template/apps/asas-tanstack/src/components/mithaq/relationship-control.tsx +400 -0
  122. package/template/apps/asas-tanstack/src/components/mithaq/rich-text-nodes.ts +6 -0
  123. package/template/apps/asas-tanstack/src/components/mithaq/rich-text.tsx +1083 -0
  124. package/template/apps/asas-tanstack/src/components/mithaq/upload.tsx +686 -0
  125. package/template/apps/asas-tanstack/src/components/ui/accordion.tsx +10 -0
  126. package/template/apps/asas-tanstack/src/components/ui/alert-dialog.tsx +173 -0
  127. package/template/apps/asas-tanstack/src/components/ui/avatar.tsx +104 -0
  128. package/template/apps/asas-tanstack/src/components/ui/badge.tsx +48 -0
  129. package/template/apps/asas-tanstack/src/components/ui/breadcrumb.tsx +125 -0
  130. package/template/apps/asas-tanstack/src/components/ui/button-group.tsx +87 -0
  131. package/template/apps/asas-tanstack/src/components/ui/button.tsx +56 -0
  132. package/template/apps/asas-tanstack/src/components/ui/calendar.tsx +223 -0
  133. package/template/apps/asas-tanstack/src/components/ui/card.tsx +94 -0
  134. package/template/apps/asas-tanstack/src/components/ui/chart.tsx +369 -0
  135. package/template/apps/asas-tanstack/src/components/ui/checkbox.tsx +27 -0
  136. package/template/apps/asas-tanstack/src/components/ui/collapsible.tsx +19 -0
  137. package/template/apps/asas-tanstack/src/components/ui/combobox.tsx +300 -0
  138. package/template/apps/asas-tanstack/src/components/ui/dialog.tsx +149 -0
  139. package/template/apps/asas-tanstack/src/components/ui/direction.tsx +5 -0
  140. package/template/apps/asas-tanstack/src/components/ui/directional-icon.tsx +20 -0
  141. package/template/apps/asas-tanstack/src/components/ui/drawer.tsx +215 -0
  142. package/template/apps/asas-tanstack/src/components/ui/dropdown-menu.tsx +267 -0
  143. package/template/apps/asas-tanstack/src/components/ui/field.tsx +225 -0
  144. package/template/apps/asas-tanstack/src/components/ui/input-group.tsx +147 -0
  145. package/template/apps/asas-tanstack/src/components/ui/input-otp.tsx +20 -0
  146. package/template/apps/asas-tanstack/src/components/ui/input.tsx +20 -0
  147. package/template/apps/asas-tanstack/src/components/ui/label.tsx +18 -0
  148. package/template/apps/asas-tanstack/src/components/ui/popover.tsx +50 -0
  149. package/template/apps/asas-tanstack/src/components/ui/radio-group.tsx +10 -0
  150. package/template/apps/asas-tanstack/src/components/ui/resizable.tsx +47 -0
  151. package/template/apps/asas-tanstack/src/components/ui/scroll-area.tsx +52 -0
  152. package/template/apps/asas-tanstack/src/components/ui/select.tsx +190 -0
  153. package/template/apps/asas-tanstack/src/components/ui/separator.tsx +23 -0
  154. package/template/apps/asas-tanstack/src/components/ui/sheet.tsx +127 -0
  155. package/template/apps/asas-tanstack/src/components/ui/sidebar.tsx +716 -0
  156. package/template/apps/asas-tanstack/src/components/ui/skeleton.tsx +13 -0
  157. package/template/apps/asas-tanstack/src/components/ui/slider.tsx +13 -0
  158. package/template/apps/asas-tanstack/src/components/ui/spinner.tsx +11 -0
  159. package/template/apps/asas-tanstack/src/components/ui/switch.tsx +8 -0
  160. package/template/apps/asas-tanstack/src/components/ui/table.tsx +119 -0
  161. package/template/apps/asas-tanstack/src/components/ui/tabs.tsx +80 -0
  162. package/template/apps/asas-tanstack/src/components/ui/textarea.tsx +18 -0
  163. package/template/apps/asas-tanstack/src/components/ui/toggle-group.tsx +8 -0
  164. package/template/apps/asas-tanstack/src/components/ui/toggle.tsx +44 -0
  165. package/template/apps/asas-tanstack/src/components/ui/tooltip.tsx +68 -0
  166. package/template/apps/asas-tanstack/src/copy.ts +870 -0
  167. package/template/apps/asas-tanstack/src/hooks/use-mobile.ts +19 -0
  168. package/template/apps/asas-tanstack/src/lib/utils.ts +6 -0
  169. package/template/apps/asas-tanstack/src/links.ts +38 -0
  170. package/template/apps/asas-tanstack/src/locale-switch.tsx +26 -0
  171. package/template/apps/asas-tanstack/src/quiet-abort.ts +25 -0
  172. package/template/apps/asas-tanstack/src/redirect.ts +20 -0
  173. package/template/apps/asas-tanstack/src/request-headers.ts +11 -0
  174. package/template/apps/asas-tanstack/src/routeTree.gen.ts +382 -0
  175. package/template/apps/asas-tanstack/src/router.tsx +7 -0
  176. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/files.ts +4 -0
  177. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/screen.json +3883 -0
  178. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/types.ts +158 -0
  179. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard.$.tsx +17 -0
  180. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard.index.tsx +10 -0
  181. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard.tsx +13 -0
  182. package/template/apps/asas-tanstack/src/routes/(screens)/portal/files.ts +4 -0
  183. package/template/apps/asas-tanstack/src/routes/(screens)/portal/screen.json +1800 -0
  184. package/template/apps/asas-tanstack/src/routes/(screens)/portal/types.ts +92 -0
  185. package/template/apps/asas-tanstack/src/routes/(screens)/portal.$.tsx +17 -0
  186. package/template/apps/asas-tanstack/src/routes/(screens)/portal.index.tsx +10 -0
  187. package/template/apps/asas-tanstack/src/routes/(screens)/portal.tsx +13 -0
  188. package/template/apps/asas-tanstack/src/routes/__root.tsx +99 -0
  189. package/template/apps/asas-tanstack/src/routes/api/$.ts +11 -0
  190. package/template/apps/asas-tanstack/src/routes/auth.accept-invitation.tsx +101 -0
  191. package/template/apps/asas-tanstack/src/routes/auth.forgot-password.tsx +94 -0
  192. package/template/apps/asas-tanstack/src/routes/auth.handoff.ts +39 -0
  193. package/template/apps/asas-tanstack/src/routes/auth.reset-password.tsx +109 -0
  194. package/template/apps/asas-tanstack/src/routes/auth.sign-in.tsx +86 -0
  195. package/template/apps/asas-tanstack/src/routes/auth.sign-up.tsx +122 -0
  196. package/template/apps/asas-tanstack/src/routes/index.tsx +142 -0
  197. package/template/apps/asas-tanstack/src/routes/unauthorized.tsx +122 -0
  198. package/template/apps/asas-tanstack/src/screen-form.tsx +34 -0
  199. package/template/apps/asas-tanstack/src/screen-list.tsx +139 -0
  200. package/template/apps/asas-tanstack/src/screen-names.ts +10 -0
  201. package/template/apps/asas-tanstack/src/screens.tsx +868 -0
  202. package/template/apps/asas-tanstack/src/server.ts +16 -0
  203. package/template/apps/asas-tanstack/src/slide.ts +22 -0
  204. package/template/apps/asas-tanstack/src/start.ts +57 -0
  205. package/template/apps/asas-tanstack/src/styles.css +159 -0
  206. package/template/apps/asas-tanstack/src/theme.ts +5 -0
  207. package/template/apps/asas-tanstack/tsconfig.json +19 -0
  208. package/template/apps/asas-tanstack/vite.config.ts +28 -0
  209. package/template/package.json +16 -0
  210. package/template/templates/asas/.env.example +16 -0
  211. package/template/templates/asas/components.json +22 -0
  212. package/template/templates/asas/mithaq.hints.json +25 -0
  213. package/template/templates/asas/package.json +38 -0
  214. package/template/templates/asas/src/asas.ts +93 -0
  215. package/template/templates/asas/src/audit-log/audit-log.ts +187 -0
  216. package/template/templates/asas/src/audit-log/sink.ts +89 -0
  217. package/template/templates/asas/src/audit-log/ui/activity.tsx +243 -0
  218. package/template/templates/asas/src/courses/courses.ts +612 -0
  219. package/template/templates/asas/src/courses/ui/archive-button.tsx +25 -0
  220. package/template/templates/asas/src/courses/ui/fee-cell.tsx +19 -0
  221. package/template/templates/asas/src/courses/ui/no-courses-on-offer.tsx +11 -0
  222. package/template/templates/asas/src/courses/ui/no-courses.tsx +13 -0
  223. package/template/templates/asas/src/courses/ui/on-offer.ts +6 -0
  224. package/template/templates/asas/src/courses/ui/status-cell.tsx +31 -0
  225. package/template/templates/asas/src/enrollments/enrollments.ts +137 -0
  226. package/template/templates/asas/src/enrollments/ui/session-cell.tsx +65 -0
  227. package/template/templates/asas/src/kayan/auth/user/account.access.ts +8 -0
  228. package/template/templates/asas/src/kayan/auth/user/account.ts +31 -0
  229. package/template/templates/asas/src/kayan/auth/user/index.ts +18 -0
  230. package/template/templates/asas/src/kayan/auth/user/invitation.access.ts +4 -0
  231. package/template/templates/asas/src/kayan/auth/user/invitation.ts +27 -0
  232. package/template/templates/asas/src/kayan/auth/user/member.access.ts +10 -0
  233. package/template/templates/asas/src/kayan/auth/user/member.screens.ts +37 -0
  234. package/template/templates/asas/src/kayan/auth/user/member.ts +25 -0
  235. package/template/templates/asas/src/kayan/auth/user/organization.access.ts +20 -0
  236. package/template/templates/asas/src/kayan/auth/user/organization.screens.ts +37 -0
  237. package/template/templates/asas/src/kayan/auth/user/organization.ts +24 -0
  238. package/template/templates/asas/src/kayan/auth/user/organizationRole.access.ts +8 -0
  239. package/template/templates/asas/src/kayan/auth/user/organizationRole.ts +25 -0
  240. package/template/templates/asas/src/kayan/auth/user/session.access.ts +8 -0
  241. package/template/templates/asas/src/kayan/auth/user/session.ts +28 -0
  242. package/template/templates/asas/src/kayan/auth/user/user.access.ts +29 -0
  243. package/template/templates/asas/src/kayan/auth/user/user.screens.ts +45 -0
  244. package/template/templates/asas/src/kayan/auth/user/user.ts +31 -0
  245. package/template/templates/asas/src/kayan/auth/user/verification.access.ts +8 -0
  246. package/template/templates/asas/src/kayan/auth/user/verification.ts +23 -0
  247. package/template/templates/asas/src/local-users/local-users.ts +143 -0
  248. package/template/templates/asas/src/media/media.ts +137 -0
  249. package/template/templates/asas/src/media/ui/preview.tsx +143 -0
  250. package/template/templates/asas/src/member-roles/member-roles.ts +207 -0
  251. package/template/templates/asas/src/messages/account.json +106 -0
  252. package/template/templates/asas/src/messages/auditLog.json +186 -0
  253. package/template/templates/asas/src/messages/courses.json +270 -0
  254. package/template/templates/asas/src/messages/enrollments.json +42 -0
  255. package/template/templates/asas/src/messages/invitation.json +66 -0
  256. package/template/templates/asas/src/messages/localUsers.json +42 -0
  257. package/template/templates/asas/src/messages/media.json +66 -0
  258. package/template/templates/asas/src/messages/member.json +42 -0
  259. package/template/templates/asas/src/messages/memberRoles.json +42 -0
  260. package/template/templates/asas/src/messages/mithaq.json +170 -0
  261. package/template/templates/asas/src/messages/organization.json +50 -0
  262. package/template/templates/asas/src/messages/organizationRole.json +50 -0
  263. package/template/templates/asas/src/messages/roles.json +58 -0
  264. package/template/templates/asas/src/messages/session.json +82 -0
  265. package/template/templates/asas/src/messages/sessions.json +106 -0
  266. package/template/templates/asas/src/messages/settings.json +66 -0
  267. package/template/templates/asas/src/messages/tags.json +38 -0
  268. package/template/templates/asas/src/messages/user.json +90 -0
  269. package/template/templates/asas/src/messages/verification.json +50 -0
  270. package/template/templates/asas/src/migrations/20260920080701_parched_tombstone/migration.sql +177 -0
  271. package/template/templates/asas/src/migrations/20260920080701_parched_tombstone/snapshot.json +2169 -0
  272. package/template/templates/asas/src/migrations/20260920230505_zippy_ben_urich/migration.sql +1 -0
  273. package/template/templates/asas/src/migrations/20260920230505_zippy_ben_urich/snapshot.json +2182 -0
  274. package/template/templates/asas/src/migrations/20260920231219_slim_retro_girl/migration.sql +20 -0
  275. package/template/templates/asas/src/migrations/20260920231219_slim_retro_girl/snapshot.json +2409 -0
  276. package/template/templates/asas/src/migrations/20260922082945_military_ironclad/migration.sql +15 -0
  277. package/template/templates/asas/src/migrations/20260922082945_military_ironclad/snapshot.json +2627 -0
  278. package/template/templates/asas/src/migrations/20260922112630_bitter_chat/migration.sql +5 -0
  279. package/template/templates/asas/src/migrations/20260922112630_bitter_chat/snapshot.json +2691 -0
  280. package/template/templates/asas/src/migrations/20260923235915_foamy_rachel_grey/migration.sql +8 -0
  281. package/template/templates/asas/src/migrations/20260923235915_foamy_rachel_grey/snapshot.json +2795 -0
  282. package/template/templates/asas/src/migrations/20260926145709_clean_the_initiative/migration.sql +3 -0
  283. package/template/templates/asas/src/migrations/20260926145709_clean_the_initiative/snapshot.json +2834 -0
  284. package/template/templates/asas/src/migrations/20260927212525_bent_prowler/migration.sql +9 -0
  285. package/template/templates/asas/src/migrations/20260927212525_bent_prowler/snapshot.json +2870 -0
  286. package/template/templates/asas/src/migrations/20260927223441_tranquil_bromley/migration.sql +1 -0
  287. package/template/templates/asas/src/migrations/20260927223441_tranquil_bromley/snapshot.json +2881 -0
  288. package/template/templates/asas/src/migrations/20260928030047_outgoing_wraith/migration.sql +15 -0
  289. package/template/templates/asas/src/migrations/20260928030047_outgoing_wraith/snapshot.json +3073 -0
  290. package/template/templates/asas/src/migrations/20260929024407_grey_morlun/migration.sql +21 -0
  291. package/template/templates/asas/src/migrations/20260929024407_grey_morlun/snapshot.json +3098 -0
  292. package/template/templates/asas/src/migrations/20260929130530_bored_jackal/migration.sql +21 -0
  293. package/template/templates/asas/src/migrations/20260929130530_bored_jackal/snapshot.json +3124 -0
  294. package/template/templates/asas/src/migrations/20260930102924_material_newton_destine/migration.sql +32 -0
  295. package/template/templates/asas/src/migrations/20260930102924_material_newton_destine/snapshot.json +3324 -0
  296. package/template/templates/asas/src/migrations/20261003022353_clumsy_taskmaster/migration.sql +2 -0
  297. package/template/templates/asas/src/migrations/20261003022353_clumsy_taskmaster/snapshot.json +3350 -0
  298. package/template/templates/asas/src/migrations/20261003143249_premium_red_shift/migration.sql +21 -0
  299. package/template/templates/asas/src/migrations/20261003143249_premium_red_shift/snapshot.json +3635 -0
  300. package/template/templates/asas/src/migrations/20261008215349_square_norrin_radd/migration.sql +8 -0
  301. package/template/templates/asas/src/migrations/20261008215349_square_norrin_radd/snapshot.json +3635 -0
  302. package/template/templates/asas/src/migrations/20261008230204_many_scarecrow/migration.sql +18 -0
  303. package/template/templates/asas/src/migrations/20261008230204_many_scarecrow/snapshot.json +3635 -0
  304. package/template/templates/asas/src/migrations/20261010005521_elite_giant_girl/migration.sql +18 -0
  305. package/template/templates/asas/src/migrations/20261010005521_elite_giant_girl/snapshot.json +3635 -0
  306. package/template/templates/asas/src/migrations/20261010120822_needy_thunderbolt/migration.sql +11 -0
  307. package/template/templates/asas/src/migrations/20261010120822_needy_thunderbolt/snapshot.json +3667 -0
  308. package/template/templates/asas/src/migrations/20261012000752_organization_ownership/migration.sql +48 -0
  309. package/template/templates/asas/src/migrations/20261012000752_organization_ownership/snapshot.json +3891 -0
  310. package/template/templates/asas/src/migrations/drizzle.config.ts +11 -0
  311. package/template/templates/asas/src/migrations/schema.ts +29 -0
  312. package/template/templates/asas/src/mithaq.ts +406 -0
  313. package/template/templates/asas/src/pages.ts +74 -0
  314. package/template/templates/asas/src/platform-http.ts +96 -0
  315. package/template/templates/asas/src/rich-text-nodes.ts +11 -0
  316. package/template/templates/asas/src/roles/grants.ts +47 -0
  317. package/template/templates/asas/src/roles/roles.ts +184 -0
  318. package/template/templates/asas/src/roles/ui/role-form.tsx +226 -0
  319. package/template/templates/asas/src/roles/ui/roles.tsx +166 -0
  320. package/template/templates/asas/src/roles.ts +16 -0
  321. package/template/templates/asas/src/seed.ts +325 -0
  322. package/template/templates/asas/src/sessions/sessions.ts +155 -0
  323. package/template/templates/asas/src/settings/settings.ts +69 -0
  324. package/template/templates/asas/src/statements.ts +11 -0
  325. package/template/templates/asas/src/steps/expected-updated-at.ts +41 -0
  326. package/template/templates/asas/src/storage/defaults.ts +156 -0
  327. package/template/templates/asas/src/storage/index.ts +95 -0
  328. package/template/templates/asas/src/storage/local.ts +143 -0
  329. package/template/templates/asas/src/storage/r2.ts +5 -0
  330. package/template/templates/asas/src/storage/s3.ts +91 -0
  331. package/template/templates/asas/src/storage/uploadthing.ts +59 -0
  332. package/template/templates/asas/src/tags/tags.ts +80 -0
  333. package/template/templates/asas/src/tenant-access.ts +221 -0
  334. package/template/templates/asas/src/tenant-roles.ts +262 -0
  335. package/template/templates/asas/src/ui/app-shell.tsx +260 -0
  336. package/template/templates/asas/src/ui/kpi-card.tsx +25 -0
  337. package/template/templates/asas/src/ui/record-page.tsx +50 -0
  338. package/template/templates/asas/src/ui/rich-text-nodes.tsx +15 -0
  339. package/template/templates/asas/src/ui/screen-nav.tsx +28 -0
  340. package/template/templates/asas/src/ui/session-labels.tsx +36 -0
  341. package/template/templates/asas/src/ui/table-page.tsx +9 -0
  342. package/template/templates/asas/src/ui/top-shell.tsx +60 -0
  343. package/template/templates/asas/src/ui-dashboard/dashboard.tsx +294 -0
  344. package/template/templates/asas/src/ui-dashboard/members.tsx +630 -0
  345. package/template/templates/asas/src/ui-dashboard/theme-provider.tsx +10 -0
  346. package/template/templates/asas/src/ui-dashboard/versions.tsx +432 -0
  347. package/template/templates/asas/src/ui-platform/home.tsx +63 -0
  348. package/template/templates/asas/src/ui-platform/organization-actions.tsx +114 -0
  349. package/template/templates/asas/src/ui-platform/record-page.tsx +30 -0
  350. package/template/templates/asas/src/ui-platform/role-cells.tsx +52 -0
  351. package/template/templates/asas/src/ui-platform/user-actions.tsx +251 -0
  352. package/template/templates/asas/src/ui-portal/home.tsx +77 -0
  353. package/template/templates/asas/src/ui-portal/page.tsx +11 -0
  354. package/template/templates/auth/package.json +14 -0
  355. package/template/templates/auth/src/index.ts +690 -0
  356. package/template/templates/auth/src/platform-roles.ts +87 -0
  357. package/README.md +0 -3
@@ -0,0 +1,294 @@
1
+ // The figures are read by the page's own `loader`, which the screen host runs
2
+ // where the page is loaded, as the viewer: on the server for a page load, so the
3
+ // numbers and the series are in the first HTML, and in the browser for a client
4
+ // navigation. A figure over a kayan the report does not let the actor read is not
5
+ // requested and not drawn.
6
+ import { useEffect, useState } from 'react'
7
+ import { Banknote, BookOpen, ClipboardList, TrendingUp } from 'lucide-react'
8
+ import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'
9
+ import type { LoaderTools, PageProps, Row } from 'mithaq/admin'
10
+ import { useLocale, useScreen } from 'mithaq/admin/react'
11
+ import { Button } from '@/components/ui/button'
12
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
13
+ import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from '@/components/ui/chart'
14
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
15
+ import { counted, formatDate } from '@/components/mithaq/controls.tsx'
16
+ import { AppShell } from '../ui/app-shell.tsx'
17
+ import { KpiCard } from '../ui/kpi-card.tsx'
18
+
19
+ const ranges = [7, 30, 90] as const
20
+ type Range = (typeof ranges)[number]
21
+
22
+ /** The range `?range=` names, and 30 for anything else, since the URL is the reader's to write. */
23
+ const rangeOf = (search: string): Range => {
24
+ const asked = Number(new URLSearchParams(search).get('range'))
25
+ return ranges.find((one) => one === asked) ?? 30
26
+ }
27
+
28
+ const day = 24 * 60 * 60 * 1000
29
+
30
+ /** The `range` UTC days ending today, oldest first, as `YYYY-MM-DD`. */
31
+ const daysOf = (range: Range) => Array.from({ length: range }, (_, at) => new Date(Date.now() - (range - 1 - at) * day).toISOString().slice(0, 10))
32
+
33
+ /** Every row a read matches, page by page, since a served read answers at most the build's `maxLimit` at once. */
34
+ const readAll = async ({ tools: { client, maxLimit }, kayan, where, draft }: { tools: LoaderTools; kayan: string; where?: Row; draft?: boolean }) => {
35
+ const rows: Row[] = []
36
+ for (let page = 1, pages = 1; page <= pages; page++) {
37
+ const found = (await client({ kayan, operation: 'find', input: { limit: maxLimit, page, depth: 0, ...(where && { where }) }, ...(draft && { draft }) })) as { docs: Row[]; totalPages: number }
38
+ rows.push(...found.docs)
39
+ pages = found.totalPages
40
+ }
41
+ return rows
42
+ }
43
+
44
+ /** What the page draws: the counts, the fees per currency, and the enrollments of each of the widest range's days. */
45
+ export type Figures = {
46
+ courses: number
47
+ open: number
48
+ /** One sum per currency, where the actor reads both a course's fee and its currency. */
49
+ fees?: [currency: string, sum: number][]
50
+ enrollments?: number
51
+ perDay?: Record<string, number>
52
+ /** The next sessions to start, each beside its course's title, where the actor reads sessions. */
53
+ upcoming?: { id: string; course: string; startsAt: string; venue?: string; capacity?: number }[]
54
+ }
55
+
56
+ /** How many sessions the home's table lists, which is a card's worth. */
57
+ const upcomingLimit = 8
58
+
59
+ /**
60
+ * The figures, read as the viewer. The two totals are the store's `count`.
61
+ * The enrollments of the widest range are read once, so choosing a narrower
62
+ * one asks the server nothing.
63
+ */
64
+ export const loader = async (tools: LoaderTools): Promise<Figures> => {
65
+ const { client, access } = tools
66
+ const reads = access.collections?.enrollments?.read === true
67
+ // The table filters and orders on a session's start and names its course,
68
+ // so it is read only where the report lets the actor read both fields: a
69
+ // role holding `sessions.find` and neither field is refused the filter.
70
+ const sessionFields = access.collections?.sessions?.fields
71
+ const readsSessions = access.collections?.sessions?.read === true && sessionFields?.startsAt?.read === true && sessionFields.course?.read === true
72
+ // A fee is summed only beside its currency: a reader of the fee alone would
73
+ // be handed riyals and dollars added into one number.
74
+ const courseFields = access.collections?.courses?.fields
75
+ const readsFees = courseFields?.fee?.read === true && courseFields.currency?.read === true
76
+ const since = `${daysOf(90)[0]}T00:00:00.000Z`
77
+ // The courses the way the Courses list reads them: the latest draft of every
78
+ // row where the actor holds `findVersions`, so the card and the list count one
79
+ // set, a course nobody has published yet among it.
80
+ const draft = access.collections?.courses?.operations?.findVersions === true
81
+ const [rows, enrollments, recent, next] = await Promise.all([
82
+ readAll({ tools, kayan: 'courses', draft }),
83
+ reads ? (client({ kayan: 'enrollments', operation: 'count', input: {} }) as Promise<{ totalDocs: number }>) : undefined,
84
+ reads ? readAll({ tools, kayan: 'enrollments', where: { createdAt: { greater_than_equal: since } } }) : undefined,
85
+ readsSessions
86
+ ? (client({
87
+ kayan: 'sessions',
88
+ operation: 'find',
89
+ input: { where: { startsAt: { greater_than_equal: new Date().toISOString() } }, sort: 'startsAt', limit: Math.min(upcomingLimit, tools.maxLimit), depth: 0 },
90
+ }) as Promise<{ docs: Row[] }>)
91
+ : undefined,
92
+ ])
93
+ const titles = new Map(rows.map((course) => [String(course.id), String(course.title ?? course.id)]))
94
+ const fees: Record<string, number> = {}
95
+ // One sum per currency, because a riyal and a dollar do not add up, and
96
+ // none for a fee with no currency, which is no amount of any of them.
97
+ // Of the courses not archived, the ones the card's second line counts.
98
+ const open = rows.filter((course) => !course.archived)
99
+ for (const course of open) if (course.currency) fees[String(course.currency)] = (fees[String(course.currency)] ?? 0) + Number(course.fee ?? 0)
100
+ const perDay: Record<string, number> = {}
101
+ for (const row of recent ?? []) {
102
+ // A `Date` in process on the server, an ISO string over HTTP in the browser.
103
+ const key = new Date(row.createdAt as string | Date).toISOString().slice(0, 10)
104
+ perDay[key] = (perDay[key] ?? 0) + 1
105
+ }
106
+ return {
107
+ courses: rows.length,
108
+ open: open.length,
109
+ ...(readsFees && { fees: Object.entries(fees) }),
110
+ ...(reads && { enrollments: enrollments?.totalDocs, perDay }),
111
+ ...(next && {
112
+ upcoming: next.docs.map((session) => ({
113
+ id: String(session.id),
114
+ course: titles.get(String(session.course)) ?? String(session.course ?? ''),
115
+ startsAt: new Date(session.startsAt as string | Date).toISOString(),
116
+ ...(typeof session.venue === 'string' && { venue: session.venue }),
117
+ ...(typeof session.capacity === 'number' && { capacity: session.capacity }),
118
+ })),
119
+ }),
120
+ }
121
+ }
122
+
123
+ export function Dashboard({ loaderData: figures }: PageProps<Figures>) {
124
+ const { dir, locale, t } = useLocale()
125
+ const screen = useScreen()
126
+ const reads = figures?.perDay !== undefined
127
+ const sessions = screen.kayan.sessions
128
+ const sessionHref = (id: string) => `${screen.path}${sessions?.pages.edit?.path.replace(':id', encodeURIComponent(id)) ?? ''}`
129
+ const venueOf = (value: string) => sessions?.fields.find((field) => field.name === 'venue')?.options?.find((one) => one.value === value)?.label[locale] ?? value
130
+ const [range, setRange] = useState<Range>(30)
131
+
132
+ // The range is in the URL, so a reload or a link keeps it. The server has no
133
+ // location, so the first paint draws the default and this reads the URL once
134
+ // the page is in the browser.
135
+ useEffect(() => setRange(rangeOf(window.location.search)), [])
136
+ const choose = (next: Range) => {
137
+ const url = new URL(window.location.href)
138
+ url.searchParams.set('range', String(next))
139
+ window.history.replaceState(window.history.state, '', url)
140
+ setRange(next)
141
+ }
142
+
143
+ const count = (value: number | undefined) => (value === undefined ? '…' : new Intl.NumberFormat(locale).format(value))
144
+ const money = (value: number, currency: string) => {
145
+ try {
146
+ return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(value)
147
+ } catch {
148
+ return `${count(value)} ${currency}`
149
+ }
150
+ }
151
+ const days = daysOf(range)
152
+ const points = days.map((key) => ({
153
+ day: new Date(key).toLocaleDateString(locale, { month: 'short', day: 'numeric', timeZone: 'UTC' }),
154
+ enrollments: figures?.perDay?.[key] ?? 0,
155
+ }))
156
+ const chartConfig = { enrollments: { label: t('dashboard.kpiEnrollments', 'Enrollments'), color: 'var(--chart-1)' } } satisfies ChartConfig
157
+ // Counted in each locale's own plural forms, so Arabic says "7 أيام" and "30 يومًا".
158
+ const inRange = counted({ t, locale, key: 'dashboard.kpiInRange', count: range, fallback: 'In the last {count} days' })
159
+
160
+ return (
161
+ <AppShell
162
+ regions={{
163
+ header: <h1>{t('dashboard.dashboard', 'Dashboard')}</h1>,
164
+ main: (
165
+ <section data-dashboard="dashboard" className="@container flex flex-col gap-4 md:gap-6">
166
+ {/* Says enrollments only to a reader of them, who is drawn their figures. */}
167
+ <p className="text-muted-foreground">
168
+ {reads ? t('dashboard.dashboardAbout', 'What this organization holds, and how many have enrolled lately.') : t('dashboard.dashboardAboutCatalogue', 'What this organization holds.')}
169
+ </p>
170
+ {/* Two to a row on a phone, so four figures fit on one screen. Four to a
171
+ row once the section itself is 672 pixels wide, which is what the
172
+ main area holds beside the sidebar at `lg`, so a narrower frame
173
+ keeps two and cuts no figure. */}
174
+ <div className="grid grid-cols-2 gap-4 @2xl:grid-cols-4">
175
+ <KpiCard
176
+ kpi="courses"
177
+ icon={<BookOpen className="size-4" />}
178
+ title={t('dashboard.kpiCourses', 'Courses')}
179
+ value={count(figures?.courses)}
180
+ footer={t('dashboard.kpiCoursesOpen', '{count} not archived')!.replace('{count}', count(figures?.open))}
181
+ />
182
+ {/* One sum per line, each a left-to-right isolate, so Arabic
183
+ neither reorders "US$" into "$US" nor strands a separator at
184
+ a line's end. */}
185
+ {!figures || figures.fees ? (
186
+ <KpiCard
187
+ kpi="fees"
188
+ icon={<Banknote className="size-4" />}
189
+ title={t('dashboard.kpiFees', 'Total fees')}
190
+ // The placeholder every card draws until the figures arrive, so the grid does not shift.
191
+ value={!figures?.fees ? '…' : figures.fees.length ? figures.fees.map(([currency, sum]) => <bdi key={currency} dir="ltr" className="block w-fit">{money(sum, currency)}</bdi>) : '—'}
192
+ footer={t('dashboard.kpiFeesFooter', 'The fee of every course not archived, summed per currency')!}
193
+ />
194
+ ) : null}
195
+ {reads ? (
196
+ <>
197
+ <KpiCard
198
+ kpi="enrollments"
199
+ icon={<ClipboardList className="size-4" />}
200
+ title={t('dashboard.kpiEnrollments', 'Enrollments')}
201
+ value={count(figures?.enrollments)}
202
+ footer={t('dashboard.kpiAllTime', 'Since the first one')!}
203
+ />
204
+ <KpiCard
205
+ kpi="recent"
206
+ icon={<TrendingUp className="size-4" />}
207
+ title={t('dashboard.kpiRecent', 'New enrollments')}
208
+ value={count(figures && points.reduce((sum, point) => sum + point.enrollments, 0))}
209
+ footer={inRange}
210
+ />
211
+ </>
212
+ ) : null}
213
+ </div>
214
+ {reads ? (
215
+ <Card data-chart-card="enrollments">
216
+ <CardHeader>
217
+ <CardTitle>{t('dashboard.chartEnrollments', 'Enrollments per day')}</CardTitle>
218
+ <CardDescription>{inRange}</CardDescription>
219
+ <div className="flex items-center gap-1 pt-2">
220
+ {ranges.map((one) => (
221
+ <Button key={one} size="sm" variant={one === range ? 'secondary' : 'ghost'} className="max-sm:h-11" aria-pressed={one === range} onClick={() => choose(one)}>
222
+ {counted({ t, locale, key: 'dashboard.chartDays', count: one, fallback: '{count} days' })}
223
+ </Button>
224
+ ))}
225
+ </div>
226
+ </CardHeader>
227
+ <CardContent>
228
+ <ChartContainer config={chartConfig} className="aspect-auto h-[280px] w-full">
229
+ <AreaChart data={points}>
230
+ <CartesianGrid vertical={false} />
231
+ {/* Time runs from the edge the text starts at: in Arabic
232
+ the oldest day is on the right and the figures too. */}
233
+ <XAxis dataKey="day" reversed={dir === 'rtl'} tickLine={false} axisLine={false} tickMargin={8} minTickGap={24} />
234
+ <YAxis orientation={dir === 'rtl' ? 'right' : 'left'} allowDecimals={false} tickLine={false} axisLine={false} width={40} tickFormatter={(value: number) => count(value)} />
235
+ <ChartTooltip content={<ChartTooltipContent indicator="dot" />} />
236
+ <Area dataKey="enrollments" type="monotone" fill="var(--color-enrollments)" fillOpacity={0.4} stroke="var(--color-enrollments)" />
237
+ </AreaChart>
238
+ </ChartContainer>
239
+ </CardContent>
240
+ </Card>
241
+ ) : null}
242
+ {figures?.upcoming ? (
243
+ <Card data-home-table="sessions">
244
+ <CardHeader>
245
+ <CardTitle>{t('dashboard.upcoming', 'Upcoming sessions')}</CardTitle>
246
+ <CardDescription>{t('dashboard.upcomingAbout', 'The next sessions to start, soonest first.')}</CardDescription>
247
+ </CardHeader>
248
+ <CardContent>
249
+ {figures.upcoming.length === 0 ? (
250
+ <p className="text-muted-foreground text-sm">{t('dashboard.upcomingNone', 'No session is about to start.')}</p>
251
+ ) : (
252
+ <div className="overflow-hidden rounded-lg border">
253
+ <Table>
254
+ <TableHeader className="bg-muted/50">
255
+ <TableRow>
256
+ <TableHead>{t('sessions.course', 'Course')}</TableHead>
257
+ <TableHead>{t('sessions.startsAt', 'Starts at')}</TableHead>
258
+ <TableHead className="max-sm:hidden">{t('sessions.venue', 'Venue')}</TableHead>
259
+ <TableHead className="text-end max-sm:hidden">{t('sessions.capacity', 'Capacity')}</TableHead>
260
+ </TableRow>
261
+ </TableHeader>
262
+ <TableBody>
263
+ {figures.upcoming.map((session) => (
264
+ <TableRow key={session.id}>
265
+ <TableCell className="font-medium">
266
+ <a href={sessionHref(session.id)} className="hover:underline">{session.course}</a>
267
+ {/* Below `sm` the venue and the capacity fold under the course's name, so the table
268
+ fits a phone's width with no column cut at its edge. */}
269
+ {session.venue || session.capacity !== undefined ? (
270
+ <span className="text-muted-foreground block text-xs font-normal sm:hidden">
271
+ {[...(session.venue ? [venueOf(session.venue)] : []), ...(session.capacity === undefined ? [] : [`${t('sessions.capacity', 'Capacity')}: ${count(session.capacity)}`])].join(' · ')}
272
+ </span>
273
+ ) : null}
274
+ </TableCell>
275
+ {/* The viewer's own clock, which the server's may not be, so the browser's reading replaces the server's without a warning.
276
+ One line, so a phone does not break the date and the time over five. */}
277
+ <TableCell className="whitespace-nowrap tabular-nums" suppressHydrationWarning>{formatDate({ value: session.startsAt, locale })}</TableCell>
278
+ <TableCell className="max-sm:hidden">{session.venue ? venueOf(session.venue) : '—'}</TableCell>
279
+ <TableCell className="text-end tabular-nums max-sm:hidden">{session.capacity === undefined ? '—' : count(session.capacity)}</TableCell>
280
+ </TableRow>
281
+ ))}
282
+ </TableBody>
283
+ </Table>
284
+ </div>
285
+ )}
286
+ </CardContent>
287
+ </Card>
288
+ ) : null}
289
+ </section>
290
+ ),
291
+ }}
292
+ />
293
+ )
294
+ }