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
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 drhomet
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018-2026 Payload CMS, LLC <info@payloadcms.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DietrichGebert
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/index.js ADDED
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ // `bun create mithaq my-app`: lays a new project down from the template this
3
+ // package carries, at the version of `@drhomet/mithaq` this package was
4
+ // published with. Plain JavaScript on Node's standard library, so `bunx` and
5
+ // `npx` run it with nothing to install first.
6
+ import { spawnSync } from 'node:child_process'
7
+ import { randomBytes } from 'node:crypto'
8
+ import { cpSync, existsSync, readdirSync, readFileSync, realpathSync, renameSync, writeFileSync } from 'node:fs'
9
+ import { basename, dirname, join } from 'node:path'
10
+ import { createInterface } from 'node:readline/promises'
11
+ import { fileURLToPath } from 'node:url'
12
+ import { parseArgs } from 'node:util'
13
+
14
+ const readJson = (file) => JSON.parse(readFileSync(file, 'utf8'))
15
+ const writeJson = (file, value) => writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`)
16
+
17
+ const generateSecret = () => randomBytes(32).toString('hex')
18
+
19
+ /**
20
+ * Pins the template's manifests to what a published project installs. It edits
21
+ * `devDependencies` too, where the template keeps `mithaq-admin`. `mithaq`
22
+ * becomes the published package under the name every import uses, and
23
+ * `mithaq-admin`, which only the framework's own repository has, goes, since a
24
+ * project reads the registry through `@mithaq`; `asas` and `@asas/auth` stay
25
+ * `"*"`, the project's own workspaces. Any other `workspace:`, `link:` or
26
+ * `file:` spec is an error rather than a pin, because nothing published stands
27
+ * behind it.
28
+ */
29
+ export function updatePackageJSONDependencies({ mithaq, packageJson }) {
30
+ for (const dependencies of [packageJson.dependencies, packageJson.devDependencies]) {
31
+ if (!dependencies) continue
32
+ if ('mithaq' in dependencies) dependencies.mithaq = mithaq
33
+ delete dependencies['mithaq-admin']
34
+ for (const [name, spec] of Object.entries(dependencies)) {
35
+ if (spec !== mithaq && /^(workspace|link|file):/.test(spec)) {
36
+ throw new Error(`The template's dependency ${name} is ${spec}, which only resolves inside the mithaq repository.`)
37
+ }
38
+ }
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Writes the project `name` under `cwd` from `template`, and answers its
44
+ * folder. `version` is the `@drhomet/mithaq` the project installs and whose
45
+ * registry it reads, pinned exactly so the two cannot drift apart and an
46
+ * upgrade is a change the project makes; `mithaq` replaces the dependency
47
+ * itself, which only the release check does.
48
+ */
49
+ export function create({ name, cwd, template, version, mithaq = `npm:@drhomet/mithaq@${version}` }) {
50
+ // A package name npm accepts, so it is also one folder, inside `cwd`.
51
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(name) || name.length > 214) {
52
+ throw new Error(`"${name}" is not a valid project name: use lowercase letters, digits, "-", "." and "_", starting with a letter or digit.`)
53
+ }
54
+ const project = join(cwd, name)
55
+ if (existsSync(project) && readdirSync(project).length > 0) {
56
+ throw new Error(`${project} already exists and is not empty. Pick another name.`)
57
+ }
58
+ cpSync(template, project, { recursive: true })
59
+ const files = readdirSync(project, { recursive: true })
60
+ // npm drops every `.gitignore` from a published package, so the stage keeps
61
+ // each as `gitignore`.
62
+ for (const file of files.filter((file) => basename(file) === 'gitignore')) {
63
+ renameSync(join(project, file), join(project, dirname(file), '.gitignore'))
64
+ }
65
+ for (const file of files.filter((file) => basename(file) === 'package.json')) {
66
+ const packageJson = readJson(join(project, file))
67
+ updatePackageJSONDependencies({ mithaq, packageJson })
68
+ writeJson(join(project, file), file === 'package.json' ? { name, ...packageJson } : packageJson)
69
+ }
70
+ // shadcn reads `@mithaq/<item>` from this URL with `{name}` replaced, at the
71
+ // version installed.
72
+ for (const file of files.filter((file) => basename(file) === 'components.json')) {
73
+ const components = readJson(join(project, file))
74
+ writeJson(join(project, file), { ...components, registries: { ...components.registries, '@mithaq': `https://cdn.jsdelivr.net/npm/@drhomet/mithaq@${version}/r/{name}.json` } })
75
+ }
76
+ // One `.env` for the template's commands and one for each app's dev server,
77
+ // the same secrets in each: the tenant app and the platform console sign
78
+ // and read each other's sessions under one `BETTER_AUTH_SECRET`.
79
+ const env = readFileSync(join(project, 'templates/asas/.env.example'), 'utf8').replace(/^([A-Z_]*SECRET)=$/gm, (_, key) => `${key}=${generateSecret()}`)
80
+ for (const folder of ['templates/asas', 'apps/asas-tanstack', 'apps/asas-platform']) writeFileSync(join(project, folder, '.env'), env, { mode: 0o600 })
81
+ // The screens find their files by globs under `node_modules/asas/`, which
82
+ // only Bun's isolated installer lays out.
83
+ writeFileSync(join(project, 'bunfig.toml'), '[install]\nlinker = "isolated"\n')
84
+ writeFileSync(join(project, '.gitignore'), ['node_modules', '.env', 'dist', '.storage/', ''].join('\n'))
85
+ writeJson(join(project, 'tsconfig.json'), {
86
+ compilerOptions: { target: 'es2024', module: 'nodenext', moduleResolution: 'nodenext', strict: true, noUncheckedIndexedAccess: true, noEmit: true, skipLibCheck: true, erasableSyntaxOnly: true, allowImportingTsExtensions: true, types: ['node'] },
87
+ include: ['templates/*/src/**/*.ts', 'apps/*/src/**/*.ts'],
88
+ })
89
+ return project
90
+ }
91
+
92
+ const main = async () => {
93
+ const { values, positionals } = parseArgs({ allowPositionals: true, options: { mithaq: { type: 'string' } } })
94
+ let [name] = positionals
95
+ if (!name) {
96
+ const prompt = createInterface({ input: process.stdin, output: process.stdout })
97
+ name = (await prompt.question('Project name: ')).trim()
98
+ prompt.close()
99
+ }
100
+ const { version } = readJson(new URL('package.json', import.meta.url))
101
+ const project = create({ name, cwd: process.cwd(), template: fileURLToPath(new URL('template/', import.meta.url)), version, mithaq: values.mithaq })
102
+ console.log(`Created ${project}.`)
103
+ // `bunx` and `bun create` name Bun here (`bun/1.3.14 npm/? node/…`).
104
+ const installed = process.env.npm_config_user_agent?.startsWith('bun/') && spawnSync('bun', ['install'], { cwd: project, stdio: 'inherit' }).status === 0
105
+ if (!installed) {
106
+ console.warn(`\nNot installed. Install with Bun: the project is a Bun workspace whose screens need Bun's isolated installer (bunfig.toml), and npm, pnpm and yarn lay node_modules out where they find nothing.`)
107
+ }
108
+ console.log(`
109
+ Next, with PostgreSQL 15 or later serving the database DATABASE_URL names in templates/asas/.env:
110
+
111
+ cd ${name}${installed ? '' : '\n bun install'}
112
+ cd templates/asas
113
+ # push the schema once, development only
114
+ bun -e "const { asas } = await import('./src/asas.ts'); await asas.connect(); await asas.destroy()"
115
+ cd ../..
116
+ bun run --cwd apps/asas-tanstack dev # the tenant app, http://localhost:5173
117
+ bun run --cwd apps/asas-platform dev # in a second terminal: the platform console, http://localhost:5174
118
+
119
+ Sign up admin@admin.com with the organization "Admin org", then run bun run seed in templates/asas.`)
120
+ }
121
+
122
+ if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
123
+ await main().catch((error) => {
124
+ console.error(error.message)
125
+ process.exitCode = 1
126
+ })
127
+ }
package/package.json CHANGED
@@ -1,6 +1,21 @@
1
1
  {
2
2
  "name": "create-mithaq",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-mithaq": "index.js"
8
+ },
9
+ "engines": {
10
+ "node": ">=24"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/drhomet/mithaq.git",
15
+ "directory": "packages/create-mithaq"
16
+ },
17
+ "homepage": "https://github.com/drhomet/mithaq#readme",
18
+ "bugs": {
19
+ "url": "https://github.com/drhomet/mithaq/issues"
20
+ }
21
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "base-sera",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/styles.css",
9
+ "baseColor": "mauve",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "lucide",
14
+ "rtl": true,
15
+ "aliases": {
16
+ "components": "@/components",
17
+ "utils": "@/lib/utils",
18
+ "ui": "@/components/ui",
19
+ "lib": "@/lib",
20
+ "hooks": "@/hooks"
21
+ },
22
+ "menuColor": "default",
23
+ "menuAccent": "subtle",
24
+ "registries": {}
25
+ }
@@ -0,0 +1,3 @@
1
+ .tanstack
2
+ dist
3
+ node_modules
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "asas-platform",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "(cd ../../templates/asas && mithaq lock --check) && vite build",
8
+ "dev": "(cd ../../templates/asas && mithaq lock --check) && vite dev --port 5174",
9
+ "preview": "(cd ../../templates/asas && mithaq lock --check) && vite preview --port 4174"
10
+ },
11
+ "dependencies": {
12
+ "@base-ui/react": "^1.5.0",
13
+ "@fontsource-variable/figtree": "^5.3.0",
14
+ "@fontsource-variable/noto-sans": "^5.3.0",
15
+ "@fontsource/ibm-plex-sans-arabic": "^5.2.8",
16
+ "@hugeicons/core-free-icons": "^4.1.4",
17
+ "@hugeicons/react": "^1.1.6",
18
+ "@lexical/extension": "0.52.0",
19
+ "@lexical/link": "0.52.0",
20
+ "@lexical/list": "0.52.0",
21
+ "@lexical/react": "0.52.0",
22
+ "@lexical/rich-text": "0.52.0",
23
+ "@lexical/selection": "0.52.0",
24
+ "@lexical/utils": "0.52.0",
25
+ "@tanstack/react-form": "^1.33.5",
26
+ "@tanstack/react-query": "^5.104.0",
27
+ "@tanstack/react-router": "^1.170.29",
28
+ "@tanstack/react-start": "^1.168.56",
29
+ "asas": "*",
30
+ "class-variance-authority": "^0.7.1",
31
+ "cn": "^0.2.6",
32
+ "date-fns": "^4.4.0",
33
+ "input-otp": "^1.5.0",
34
+ "lexical": "0.52.0",
35
+ "lucide-react": "^1.16.0",
36
+ "mithaq": "*",
37
+ "react": "^19.3.0",
38
+ "react-day-picker": "^10.0.1",
39
+ "react-dom": "^19.3.0",
40
+ "react-resizable-panels": "^4.14.1",
41
+ "recharts": "3.8.0",
42
+ "tw-animate-css": "^1.4.0"
43
+ },
44
+ "devDependencies": {
45
+ "@tailwindcss/vite": "^4.3.0",
46
+ "@tanstack/start-client-core": "^1.170.1",
47
+ "@types/react": "^19.3.0",
48
+ "@types/react-dom": "^19.3.0",
49
+ "@vitejs/plugin-react": "^6.0.2",
50
+ "mithaq-admin": "*",
51
+ "shadcn": "4.18.0",
52
+ "tailwindcss": "^4.3.0",
53
+ "vite": "^8.0.13"
54
+ }
55
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" fill="#5b21b6"/><path d="M16 5l9 4v6c0 6-4 10-9 12-5-2-9-6-9-12V9z" fill="none" stroke="#f5f3ff" stroke-width="2.5" stroke-linejoin="round"/><path d="M12 16l3 3 5-6" fill="none" stroke="#f5f3ff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
@@ -0,0 +1,147 @@
1
+ import { afterCommitHeader, type ClientKayan, type FetchClient } from 'mithaq/admin'
2
+
3
+ /** What a write may ask beside its input on a kayan that keeps drafts: a draft save, and an autosave. The fetch plug reads them off the query string only. */
4
+ export type DraftOptions = { readonly draft?: boolean; readonly autosave?: boolean }
5
+
6
+ const queryOf = ({ draft, autosave }: DraftOptions = {}) => (draft ? `?draft=true${autosave ? '&autosave=true' : ''}` : '')
7
+
8
+ const call = async ({ kayan, operation, input, ...options }: { kayan: string; operation: string; input: Record<string, unknown> } & DraftOptions) => {
9
+ const response = await fetch(`/api/${encodeURIComponent(kayan)}/${encodeURIComponent(operation)}${queryOf(options)}`, {
10
+ method: 'POST',
11
+ headers: { 'content-type': 'application/json' },
12
+ body: JSON.stringify(input),
13
+ })
14
+ const answer = await response.json().catch(() => null)
15
+ if (!response.ok) {
16
+ const error = new Error(typeof answer?.message === 'string' ? answer.message : `HTTP ${response.status}`)
17
+ Object.assign(error, { status: response.status, code: answer?.code, data: answer?.data })
18
+ throw error
19
+ }
20
+ // A failed `afterCommit` raises the screens' banner and a committed write
21
+ // clears it. Only the server knows an operation's effect, so the plug sets
22
+ // the header on writes and never on reads.
23
+ const said = response.headers.get(afterCommitHeader)
24
+ if (typeof window !== 'undefined' && said) {
25
+ window.dispatchEvent(new CustomEvent('mithaq:after-commit', { detail: said === 'failed' }))
26
+ }
27
+ return answer
28
+ }
29
+
30
+ // Restated, not imported from the `.tsx` file that declares them: a `.ts` file
31
+ // must not need the JSX compiler.
32
+ type FoundPage = { docs: readonly Record<string, unknown>[]; hasNextPage: boolean; nextPage: number | null }
33
+ type FindApi = Readonly<
34
+ Record<
35
+ string,
36
+ | {
37
+ find: (input: Record<string, unknown>) => Promise<FoundPage>
38
+ create?: (input: Record<string, unknown>) => Promise<unknown>
39
+ delete?: (input: Record<string, unknown>) => Promise<unknown>
40
+ update?: (input: Record<string, unknown>) => Promise<unknown>
41
+ }
42
+ | undefined
43
+ >
44
+ >
45
+
46
+ /**
47
+ * Every operation of every kayan, keyed by name strings. `arm[kayan][operation](input)`
48
+ * posts to `POST /api/<kayan>/<operation>`; the proxy never answers `undefined`,
49
+ * and a name no kayan has is the server's not-found.
50
+ */
51
+ export type Arm = Readonly<Record<string, Readonly<Record<string, (input: Record<string, unknown>, options?: DraftOptions) => Promise<unknown>>>>>
52
+ export const arm = new Proxy(Object.create(null) as Arm, {
53
+ get: (_target, kayan: string) => new Proxy(Object.create(null) as Record<string, (input: Record<string, unknown>, options?: DraftOptions) => Promise<unknown>>, {
54
+ get: (_operations, operation: string) => (input: Record<string, unknown>, options?: DraftOptions) => call({ kayan, operation, input, ...options }),
55
+ }),
56
+ })
57
+
58
+ /**
59
+ * Whether an operation's input names a key: probes the schema with a value no
60
+ * browser-written schema accepts and looks for an issue at that key. A schema
61
+ * that answers asynchronously, or throws, reads as naming nothing.
62
+ */
63
+ export const takesKey = (input: { '~standard': { validate: (value: unknown) => unknown } }, key: string) => {
64
+ let answer: { issues?: readonly { path?: readonly unknown[] }[] }
65
+ try {
66
+ answer = input['~standard'].validate({ [key]: Symbol(key) }) as typeof answer
67
+ } catch {
68
+ return false
69
+ }
70
+ if (answer instanceof Promise) {
71
+ answer.catch(() => {})
72
+ return false
73
+ }
74
+ return answer.issues?.some(({ path }) => {
75
+ const segment = path?.[0]
76
+ return String(typeof segment === 'object' && segment ? (segment as { key: unknown }).key : segment) === key
77
+ }) ?? false
78
+ }
79
+
80
+ /** What a screen's `client` is over an arm; the draft words ride along with the input. */
81
+ export const clientOf = (through: Arm): FetchClient => ({ kayan, operation, input, draft, autosave }) => through[kayan]![operation]!(input, { draft, autosave })
82
+
83
+ /**
84
+ * What the server's `client` is: each operation in process, as `actor`. Only the
85
+ * two draft words are read off the caller's options and the actor goes last, so
86
+ * a loader cannot run as someone else.
87
+ */
88
+ export const actingAs = (
89
+ through: Readonly<Record<string, { operations: Record<string, (input: object, options: { actor?: unknown } & DraftOptions) => Promise<unknown>> }>>,
90
+ actor: unknown,
91
+ ): FetchClient => ({ kayan, operation, input, draft, autosave }) => through[kayan]!.operations[operation]!(input, { draft, autosave, actor })
92
+
93
+ /**
94
+ * What a relationship control and cell read a second kayan through. Only the
95
+ * kayans the screen's loader already narrowed to what this actor may read are
96
+ * in the record, so a picker cannot page any other.
97
+ */
98
+ export const readsOf = (kayans: readonly ClientKayan[]): FindApi =>
99
+ Object.fromEntries(
100
+ kayans.map((kayan) => [
101
+ kayan.name,
102
+ {
103
+ find: (input: Record<string, unknown>) => arm[kayan.name]!.find!(input) as Promise<FoundPage>,
104
+ },
105
+ ]),
106
+ )
107
+
108
+ type FieldReads = {
109
+ readonly collections?: Readonly<
110
+ Record<string, { readonly fields?: Readonly<Record<string, Readonly<Record<string, boolean>> | undefined>> } | undefined>
111
+ >
112
+ }
113
+
114
+ /**
115
+ * One kayan of the projection with every field this actor may not read taken
116
+ * off it. A loader's result is serialized into the document, so an unreadable
117
+ * field's name, label or rule would otherwise reach the viewer.
118
+ */
119
+ export const readable = (one: ClientKayan, report: FieldReads): ClientKayan => ({
120
+ ...one,
121
+ fields: one.fields.filter((field) => report.collections?.[one.name]?.fields?.[field.name]?.read === true),
122
+ })
123
+
124
+ /**
125
+ * Whether a thrown refusal is the caller's. Those carry an HTTP status; a store
126
+ * that is down or a hook bug carries none and must be rethrown, not reported
127
+ * as a missing row.
128
+ */
129
+ export const caused = (thrown: unknown): boolean =>
130
+ typeof (thrown as { status?: unknown } | null | undefined)?.status === 'number'
131
+
132
+ /**
133
+ * One POST at the auth arm (Better Auth's router under `/api/auth/user`) with the session cookie.
134
+ * Returns the answer instead of throwing: a refusal is a sentence for the viewer; no reply is status 0.
135
+ */
136
+ export const authArm = async (path: string, body: unknown = {}) => {
137
+ const response = await fetch(`/api/auth/user/${path}`, {
138
+ method: 'POST',
139
+ headers: { 'content-type': 'application/json' },
140
+ body: JSON.stringify(body),
141
+ }).catch(() => null)
142
+ if (!response) return { ok: false, status: 0, message: undefined, body: null }
143
+ const answered = (await response.json().catch(() => null)) as ({ message?: string } & Record<string, unknown>) | null
144
+ // The status too, because a rate limit's refusal carries no code to read.
145
+ return { ok: response.ok, status: response.status, message: answered?.message, body: answered }
146
+ }
147
+ // mithaq-admin b06d8d335d306c07556f8f2b81fba20ba7ded198 94db2fc49ad4391c0d1c2183cb41001dd08d62017c76af86cc5f611488c22c68
@@ -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 './components/mithaq/field-shell.tsx'
11
+ import { refusalKey } from './copy.ts'
12
+ import { LocaleSwitch } from './locale-switch.tsx'
13
+ import { wanted } 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, where signing in lands for it, and the provider's password bounds.
25
+ * 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 first = signedIn ? (await loadEnterable())?.screens[0] : undefined
35
+ const home: { path: string; name?: string } = first ?? { path: '/unauthorized' }
36
+ return { signedIn, home, 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, home, password } = await authGate()
46
+ if (signedIn) throw redirect({ href: wanted({ asked: search.redirect, home: home.path }) })
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 }: { redirect?: string; home: { path: string; name?: string } }) {
61
+ const { t } = useCopy()
62
+ const label = useHomeLabel()
63
+ const to = wanted({ asked, home: home.path })
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 b06d8d335d306c07556f8f2b81fba20ba7ded198 ab822afe361dfc5f82c504570fd5d0ea13ec4572e60adb679a3f9532ccf67803