create-mithaq 0.1.1 → 0.2.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 (348) hide show
  1. package/LICENSES/openstatus-time-picker.txt +21 -0
  2. package/index.js +457 -63
  3. package/package.json +1 -1
  4. package/template/apps/{asas-platform → asas-backoffice}/package.json +5 -3
  5. package/template/apps/{asas-platform → asas-backoffice}/src/api.ts +1 -1
  6. package/template/apps/{asas-platform → asas-backoffice}/src/auth-frame.tsx +12 -12
  7. package/template/apps/asas-backoffice/src/components.ts +35 -0
  8. package/template/apps/asas-backoffice/src/first-user.ts +14 -0
  9. package/template/apps/{asas-platform → asas-backoffice}/src/links.ts +1 -1
  10. package/template/apps/{asas-platform → asas-backoffice}/src/locale-switch.tsx +7 -5
  11. package/template/apps/asas-backoffice/src/no-organization.tsx +5 -0
  12. package/template/apps/asas-backoffice/src/redirect.ts +1 -0
  13. package/template/apps/{asas-platform → asas-backoffice}/src/request-headers.ts +1 -1
  14. package/template/apps/asas-backoffice/src/routeTree.gen.ts +206 -0
  15. package/template/apps/asas-backoffice/src/router.tsx +18 -0
  16. package/template/apps/asas-backoffice/src/routes/(screens)/backoffice/files.ts +10 -0
  17. package/template/apps/{asas-platform/src/routes/(screens)/platform → asas-backoffice/src/routes/(screens)/backoffice}/screen.json +426 -64
  18. package/template/apps/{asas-platform/src/routes/(screens)/platform → asas-backoffice/src/routes/(screens)/backoffice}/types.ts +18 -0
  19. package/template/apps/{asas-platform/src/routes/(screens)/platform.$.tsx → asas-backoffice/src/routes/(screens)/backoffice.$.tsx} +4 -4
  20. package/template/apps/{asas-platform/src/routes/(screens)/platform.index.tsx → asas-backoffice/src/routes/(screens)/backoffice.index.tsx} +3 -3
  21. package/template/apps/{asas-platform/src/routes/(screens)/platform.tsx → asas-backoffice/src/routes/(screens)/backoffice.tsx} +3 -3
  22. package/template/apps/{asas-platform → asas-backoffice}/src/routes/__root.tsx +2 -2
  23. package/template/apps/asas-backoffice/src/routes/api/$.ts +34 -0
  24. package/template/apps/asas-backoffice/src/routes/auth.create-first-user.tsx +113 -0
  25. package/template/apps/{asas-platform → asas-backoffice}/src/routes/auth.sign-in.tsx +9 -4
  26. package/template/apps/{asas-platform → asas-backoffice}/src/routes/unauthorized.tsx +24 -12
  27. package/template/apps/{asas-platform → asas-backoffice}/src/screen-form.tsx +5 -3
  28. package/template/apps/{asas-platform → asas-backoffice}/src/screen-list.tsx +6 -5
  29. package/template/apps/{asas-platform → asas-backoffice}/src/screen-names.ts +1 -1
  30. package/template/apps/{asas-platform → asas-backoffice}/src/screens.tsx +46 -26
  31. package/template/apps/asas-backoffice/src/server.ts +30 -0
  32. package/template/apps/{asas-platform → asas-backoffice}/src/slide.ts +1 -1
  33. package/template/apps/{asas-platform → asas-backoffice}/src/start.ts +1 -1
  34. package/template/apps/{asas-platform → asas-backoffice}/src/styles.css +9 -7
  35. package/template/apps/{asas-platform → asas-backoffice}/tsconfig.json +2 -2
  36. package/template/apps/{asas-platform → asas-backoffice}/vite.config.ts +7 -1
  37. package/template/apps/asas-tanstack/package.json +4 -2
  38. package/template/apps/asas-tanstack/src/api.ts +1 -1
  39. package/template/apps/asas-tanstack/src/auth-frame.tsx +12 -12
  40. package/template/apps/asas-tanstack/src/choose-password.tsx +150 -0
  41. package/template/apps/asas-tanstack/src/components.ts +35 -0
  42. package/template/apps/asas-tanstack/src/copy.ts +416 -79
  43. package/template/apps/asas-tanstack/src/first-run.ts +16 -0
  44. package/template/apps/asas-tanstack/src/links.ts +1 -1
  45. package/template/apps/asas-tanstack/src/locale-switch.tsx +7 -5
  46. package/template/apps/asas-tanstack/src/no-organization.tsx +108 -0
  47. package/template/apps/asas-tanstack/src/redirect.ts +20 -1
  48. package/template/apps/asas-tanstack/src/request-headers.ts +1 -1
  49. package/template/apps/asas-tanstack/src/routeTree.gen.ts +42 -0
  50. package/template/apps/asas-tanstack/src/router.tsx +12 -1
  51. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/files.ts +9 -2
  52. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/screen.json +475 -116
  53. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/types.ts +4 -2
  54. package/template/apps/asas-tanstack/src/routes/(screens)/portal/files.ts +7 -2
  55. package/template/apps/asas-tanstack/src/routes/(screens)/portal/screen.json +83 -319
  56. package/template/apps/asas-tanstack/src/routes/(screens)/portal/types.ts +3 -1
  57. package/template/apps/asas-tanstack/src/routes/__root.tsx +1 -1
  58. package/template/apps/asas-tanstack/src/routes/auth.accept-invitation.tsx +14 -7
  59. package/template/apps/asas-tanstack/src/routes/auth.first-run.tsx +114 -0
  60. package/template/apps/asas-tanstack/src/routes/auth.forgot-password.tsx +2 -2
  61. package/template/apps/asas-tanstack/src/routes/auth.handoff.ts +5 -5
  62. package/template/apps/asas-tanstack/src/routes/auth.reset-password.tsx +7 -100
  63. package/template/apps/asas-tanstack/src/routes/auth.set-password.tsx +16 -0
  64. package/template/apps/asas-tanstack/src/routes/auth.sign-in.tsx +27 -15
  65. package/template/apps/asas-tanstack/src/routes/auth.sign-up.tsx +59 -38
  66. package/template/apps/asas-tanstack/src/routes/index.tsx +18 -14
  67. package/template/apps/asas-tanstack/src/routes/unauthorized.tsx +24 -12
  68. package/template/apps/asas-tanstack/src/screen-form.tsx +5 -3
  69. package/template/apps/asas-tanstack/src/screen-list.tsx +6 -5
  70. package/template/apps/asas-tanstack/src/screen-names.ts +1 -1
  71. package/template/apps/asas-tanstack/src/screens.tsx +46 -26
  72. package/template/apps/asas-tanstack/src/server.ts +10 -0
  73. package/template/apps/asas-tanstack/src/slide.ts +1 -1
  74. package/template/apps/asas-tanstack/src/styles.css +7 -3
  75. package/template/apps/asas-tanstack/tsconfig.json +2 -2
  76. package/template/apps/asas-tanstack/vite.config.ts +7 -1
  77. package/template/package.json +3 -3
  78. package/template/templates/asas/.claude/skills/add-field/SKILL.md +27 -0
  79. package/template/templates/asas/.claude/skills/add-kayan/SKILL.md +26 -0
  80. package/template/templates/asas/.claude/skills/eject-admin-piece/SKILL.md +28 -0
  81. package/template/templates/asas/.claude/skills/upgrade-mithaq/SKILL.md +35 -0
  82. package/template/templates/asas/.env.example +22 -5
  83. package/template/templates/asas/AGENTS.md +101 -0
  84. package/template/templates/asas/CLAUDE.md +1 -0
  85. package/template/templates/asas/README.md +1094 -0
  86. package/template/templates/asas/package.json +19 -4
  87. package/template/templates/asas/src/asas.ts +81 -56
  88. package/template/templates/asas/src/{platform-http.ts → backoffice-http.ts} +48 -27
  89. package/template/templates/asas/src/dev-setup.ts +112 -0
  90. package/template/templates/asas/src/local-database.ts +22 -0
  91. package/template/templates/asas/src/messages/account.json +129 -103
  92. package/template/templates/asas/src/messages/auditLog.json +223 -105
  93. package/template/templates/asas/src/messages/backofficeLog.json +132 -0
  94. package/template/templates/asas/src/messages/courses.json +256 -214
  95. package/template/templates/asas/src/messages/enrollments.json +45 -27
  96. package/template/templates/asas/src/messages/invitation.json +72 -56
  97. package/template/templates/asas/src/messages/localUsers.json +34 -28
  98. package/template/templates/asas/src/messages/media.json +31 -21
  99. package/template/templates/asas/src/messages/member.json +59 -39
  100. package/template/templates/asas/src/messages/memberRoles.json +37 -31
  101. package/template/templates/asas/src/messages/mithaq.json +206 -162
  102. package/template/templates/asas/src/messages/organization.json +63 -41
  103. package/template/templates/asas/src/messages/organizationRole.json +59 -47
  104. package/template/templates/asas/src/messages/roles.json +59 -45
  105. package/template/templates/asas/src/messages/session.json +99 -79
  106. package/template/templates/asas/src/messages/sessions.json +110 -80
  107. package/template/templates/asas/src/messages/settings.json +51 -53
  108. package/template/templates/asas/src/messages/tags.json +31 -21
  109. package/template/templates/asas/src/messages/user.json +109 -87
  110. package/template/templates/asas/src/messages/verification.json +59 -47
  111. package/template/templates/asas/src/migrations/20261014221355_tags_restrict/migration.sql +1 -0
  112. package/template/templates/asas/src/migrations/20261014221355_tags_restrict/snapshot.json +3881 -0
  113. package/template/templates/asas/src/migrations/20261014222218_one_seat_per_session/migration.sql +1 -0
  114. package/template/templates/asas/src/migrations/20261014222218_one_seat_per_session/snapshot.json +3894 -0
  115. package/template/templates/asas/src/migrations/20261014223522_session_archive_reason/migration.sql +1 -0
  116. package/template/templates/asas/src/migrations/20261014223522_session_archive_reason/snapshot.json +3907 -0
  117. package/template/templates/asas/src/migrations/20261014225728_course_archive_by_operation/migration.sql +1 -0
  118. package/template/templates/asas/src/migrations/20261014225728_course_archive_by_operation/snapshot.json +3907 -0
  119. package/template/templates/asas/src/migrations/20261015042444_organization_names/migration.sql +1 -0
  120. package/template/templates/asas/src/migrations/20261015042444_organization_names/snapshot.json +3920 -0
  121. package/template/templates/asas/src/migrations/20261015053212_member_last_seen/migration.sql +1 -0
  122. package/template/templates/asas/src/migrations/20261015053212_member_last_seen/snapshot.json +3933 -0
  123. package/template/templates/asas/src/migrations/20261015093203_activity_and_staff_history/migration.sql +15 -0
  124. package/template/templates/asas/src/migrations/20261015093203_activity_and_staff_history/snapshot.json +4113 -0
  125. package/template/templates/asas/src/migrations/20261015095752_portal/migration.sql +2 -0
  126. package/template/templates/asas/src/migrations/20261015095752_portal/snapshot.json +4113 -0
  127. package/template/templates/asas/src/migrations/20261015135529_better_auth_indexes/migration.sql +9 -0
  128. package/template/templates/asas/src/migrations/20261015135529_better_auth_indexes/snapshot.json +4302 -0
  129. package/template/templates/asas/src/migrations/20261015162438_audit_changes_json/migration.sql +27 -0
  130. package/template/templates/asas/src/migrations/20261015162438_audit_changes_json/snapshot.json +4289 -0
  131. package/template/templates/asas/src/migrations/schema.ts +1 -0
  132. package/template/templates/asas/src/mithaq.ts +4 -406
  133. package/template/templates/asas/src/organizations.ts +3 -0
  134. package/template/templates/asas/src/seed.ts +147 -45
  135. package/template/templates/asas/src/{ui-platform → ui-backoffice}/home.tsx +9 -9
  136. package/template/templates/asas/src/ui-backoffice/organization-actions.tsx +121 -0
  137. package/template/templates/asas/src/{ui-platform → ui-backoffice}/record-page.tsx +4 -4
  138. package/template/templates/asas/src/{ui-platform → ui-backoffice}/role-cells.tsx +13 -6
  139. package/template/templates/asas/src/{ui-platform → ui-backoffice}/user-actions.tsx +46 -48
  140. package/template/templates/asas/src/ui-dashboard/dashboard.tsx +10 -4
  141. package/template/templates/asas/src/ui-dashboard/home.tsx +73 -0
  142. package/template/templates/asas/src/ui-dashboard/members-card.tsx +147 -0
  143. package/template/templates/asas/src/ui-dashboard/members.tsx +637 -438
  144. package/template/templates/asas/src/ui-dashboard/versions.tsx +22 -8
  145. package/template/templates/asas/src/ui-portal/courses-home.tsx +96 -0
  146. package/template/templates/asas/src/ui-portal/home.tsx +9 -13
  147. package/template/templates/features/audit-log/audit-log/audit-log.ts +284 -0
  148. package/template/templates/features/audit-log/backoffice-log/backoffice-log.ts +67 -0
  149. package/template/templates/features/audit-log/index.ts +4 -0
  150. package/template/templates/features/audit-log/package.json +13 -0
  151. package/template/templates/features/auth/index.ts +11 -0
  152. package/template/templates/{asas/src → features/auth}/local-users/local-users.ts +7 -7
  153. package/template/templates/{auth → features/auth}/package.json +2 -3
  154. package/template/templates/{asas/src/kayan → features}/auth/user/account.access.ts +2 -2
  155. package/template/templates/{asas/src/kayan → features}/auth/user/account.ts +2 -2
  156. package/template/templates/{asas/src/kayan → features}/auth/user/invitation.access.ts +1 -1
  157. package/template/templates/{asas/src/kayan → features}/auth/user/invitation.ts +3 -3
  158. package/template/templates/{asas/src/kayan → features}/auth/user/member.access.ts +4 -4
  159. package/template/templates/{asas/src/kayan → features}/auth/user/member.screens.ts +5 -5
  160. package/template/templates/{asas/src/kayan → features}/auth/user/member.ts +4 -3
  161. package/template/templates/{asas/src/kayan → features}/auth/user/organization.access.ts +6 -6
  162. package/template/templates/{asas/src/kayan → features}/auth/user/organization.screens.ts +6 -6
  163. package/template/templates/{asas/src/kayan → features}/auth/user/organization.ts +2 -1
  164. package/template/templates/{asas/src/kayan → features}/auth/user/organizationRole.access.ts +2 -2
  165. package/template/templates/{asas/src/kayan → features}/auth/user/organizationRole.ts +3 -3
  166. package/template/templates/{asas/src/kayan → features}/auth/user/session.access.ts +2 -2
  167. package/template/templates/{asas/src/kayan → features}/auth/user/session.ts +2 -2
  168. package/template/templates/{asas/src/kayan → features}/auth/user/user.access.ts +8 -7
  169. package/template/templates/{asas/src/kayan → features}/auth/user/user.screens.ts +9 -9
  170. package/template/templates/{asas/src/kayan → features}/auth/user/user.ts +2 -2
  171. package/template/templates/{asas/src/kayan → features}/auth/user/verification.access.ts +2 -2
  172. package/template/templates/{asas/src/kayan → features}/auth/user/verification.ts +2 -2
  173. package/template/templates/{asas/src → features/courses}/courses/courses.ts +122 -133
  174. package/template/templates/features/courses/courses/cover-reads.ts +33 -0
  175. package/template/templates/features/courses/courses/ui/archived-badge.tsx +9 -0
  176. package/template/templates/{asas/src → features/courses}/courses/ui/fee-cell.tsx +1 -1
  177. package/template/templates/{asas/src → features/courses}/courses/ui/status-cell.tsx +5 -3
  178. package/template/templates/features/courses/courses/ui/unpublish-button.tsx +15 -0
  179. package/template/templates/features/courses/domain/archive.ts +39 -0
  180. package/template/templates/features/courses/domain/on-offer.ts +6 -0
  181. package/template/templates/features/courses/enrollments/enrollments.ts +309 -0
  182. package/template/templates/{asas/src → features/courses}/enrollments/ui/session-cell.tsx +17 -7
  183. package/template/templates/{asas/src → features/courses/enrollments}/ui/session-labels.tsx +3 -3
  184. package/template/templates/features/courses/index.ts +8 -0
  185. package/template/templates/features/courses/package.json +24 -0
  186. package/template/templates/features/courses/portal/ui/catalogue.tsx +86 -0
  187. package/template/templates/features/courses/portal/ui/course.tsx +176 -0
  188. package/template/templates/features/courses/portal/ui/enrollments.tsx +170 -0
  189. package/template/templates/features/courses/portal/ui/parts.tsx +95 -0
  190. package/template/templates/features/courses/portal/ui/reads.ts +76 -0
  191. package/template/templates/features/courses/portal/ui/rules.ts +87 -0
  192. package/template/templates/features/courses/portal/ui/shell.tsx +194 -0
  193. package/template/templates/features/courses/sessions/seats.ts +42 -0
  194. package/template/templates/{asas/src → features/courses}/sessions/sessions.ts +45 -14
  195. package/template/templates/features/media/index.ts +10 -0
  196. package/template/templates/{asas/src → features/media}/media/media.ts +34 -40
  197. package/template/templates/{asas/src → features/media}/media/ui/preview.tsx +2 -2
  198. package/template/templates/features/media/package.json +19 -0
  199. package/template/templates/features/roles/index.ts +4 -0
  200. package/template/templates/{asas/src → features/roles}/member-roles/member-roles.ts +15 -15
  201. package/template/templates/features/roles/package.json +21 -0
  202. package/template/templates/{asas/src → features/roles}/roles/roles.ts +23 -23
  203. package/template/templates/{asas/src → features/roles}/roles/ui/role-form.tsx +65 -24
  204. package/template/templates/{asas/src → features/roles}/roles/ui/roles.tsx +49 -25
  205. package/template/templates/features/settings/index.ts +3 -0
  206. package/template/templates/features/settings/package.json +13 -0
  207. package/template/templates/{asas/src → features/settings}/settings/settings.ts +12 -17
  208. package/template/templates/features/tags/index.ts +3 -0
  209. package/template/templates/features/tags/package.json +14 -0
  210. package/template/templates/{asas/src → features/tags}/tags/tags.ts +15 -15
  211. package/template/templates/mithaq/package.json +33 -0
  212. package/template/templates/mithaq/src/mail/index.ts +32 -0
  213. package/template/templates/mithaq/src/mail/mailers.ts +36 -0
  214. package/template/templates/{asas/src/audit-log/sink.ts → mithaq/src/mithaq/audit-sink.ts} +31 -9
  215. package/template/templates/{auth/src/platform-roles.ts → mithaq/src/mithaq/auth/backoffice-roles.ts} +44 -23
  216. package/template/templates/mithaq/src/mithaq/auth/index.ts +1859 -0
  217. package/template/templates/mithaq/src/mithaq/auth/organizations.ts +80 -0
  218. package/template/templates/{asas/src → mithaq/src/mithaq}/pages.ts +16 -7
  219. package/template/templates/mithaq/src/mithaq/rich-text-nodes.ts +19 -0
  220. package/template/templates/mithaq/src/mithaq.ts +593 -0
  221. package/template/templates/mithaq/src/roles/default-member-roles.ts +29 -0
  222. package/template/templates/{asas → mithaq}/src/roles/grants.ts +1 -1
  223. package/template/templates/mithaq/src/roles/member-access.ts +11 -0
  224. package/template/templates/{asas/src → mithaq/src/roles}/statements.ts +6 -4
  225. package/template/templates/mithaq/src/roles/system-roles.ts +16 -0
  226. package/template/templates/{asas/src → mithaq/src/roles}/tenant-access.ts +96 -57
  227. package/template/templates/{asas/src → mithaq/src/roles}/tenant-roles.ts +84 -25
  228. package/template/templates/{asas → mithaq}/src/steps/expected-updated-at.ts +2 -2
  229. package/template/templates/{asas → mithaq}/src/storage/index.ts +3 -1
  230. package/template/templates/mithaq/src/ui/activity-page.tsx +33 -0
  231. package/template/templates/mithaq/src/ui/activity.tsx +527 -0
  232. package/template/templates/{asas → mithaq}/src/ui/app-shell.tsx +27 -19
  233. package/template/templates/mithaq/src/ui/rich-text-nodes.tsx +33 -0
  234. package/template/templates/{asas → mithaq}/src/ui/screen-nav.tsx +1 -1
  235. package/template/templates/{asas → mithaq}/src/ui/top-shell.tsx +8 -8
  236. package/template/apps/asas-platform/src/components/mithaq/cells.tsx +0 -112
  237. package/template/apps/asas-platform/src/components/mithaq/controls.tsx +0 -650
  238. package/template/apps/asas-platform/src/components/mithaq/field-shell.tsx +0 -53
  239. package/template/apps/asas-platform/src/components/mithaq/filter-operators.ts +0 -108
  240. package/template/apps/asas-platform/src/components/mithaq/form.tsx +0 -1467
  241. package/template/apps/asas-platform/src/components/mithaq/list-columns.tsx +0 -108
  242. package/template/apps/asas-platform/src/components/mithaq/list-filters.tsx +0 -368
  243. package/template/apps/asas-platform/src/components/mithaq/list-pagination.tsx +0 -153
  244. package/template/apps/asas-platform/src/components/mithaq/list-query.ts +0 -432
  245. package/template/apps/asas-platform/src/components/mithaq/list-selection.tsx +0 -74
  246. package/template/apps/asas-platform/src/components/mithaq/list.tsx +0 -865
  247. package/template/apps/asas-platform/src/components/mithaq/nav.tsx +0 -871
  248. package/template/apps/asas-platform/src/components/mithaq/nodes.tsx +0 -1024
  249. package/template/apps/asas-platform/src/components/mithaq/relationship-cell.tsx +0 -127
  250. package/template/apps/asas-platform/src/components/mithaq/relationship-control.tsx +0 -400
  251. package/template/apps/asas-platform/src/components/mithaq/rich-text-nodes.ts +0 -6
  252. package/template/apps/asas-platform/src/components/mithaq/rich-text.tsx +0 -1083
  253. package/template/apps/asas-platform/src/components/mithaq/upload.tsx +0 -686
  254. package/template/apps/asas-platform/src/components/ui/drawer.tsx +0 -216
  255. package/template/apps/asas-platform/src/redirect.ts +0 -1
  256. package/template/apps/asas-platform/src/routeTree.gen.ts +0 -181
  257. package/template/apps/asas-platform/src/router.tsx +0 -7
  258. package/template/apps/asas-platform/src/routes/(screens)/platform/files.ts +0 -4
  259. package/template/apps/asas-platform/src/routes/api/$.ts +0 -30
  260. package/template/apps/asas-platform/src/server.ts +0 -20
  261. package/template/apps/asas-tanstack/src/components/mithaq/cells.tsx +0 -112
  262. package/template/apps/asas-tanstack/src/components/mithaq/controls.tsx +0 -650
  263. package/template/apps/asas-tanstack/src/components/mithaq/field-shell.tsx +0 -53
  264. package/template/apps/asas-tanstack/src/components/mithaq/filter-operators.ts +0 -108
  265. package/template/apps/asas-tanstack/src/components/mithaq/form.tsx +0 -1467
  266. package/template/apps/asas-tanstack/src/components/mithaq/list-columns.tsx +0 -108
  267. package/template/apps/asas-tanstack/src/components/mithaq/list-filters.tsx +0 -368
  268. package/template/apps/asas-tanstack/src/components/mithaq/list-pagination.tsx +0 -153
  269. package/template/apps/asas-tanstack/src/components/mithaq/list-query.ts +0 -432
  270. package/template/apps/asas-tanstack/src/components/mithaq/list-selection.tsx +0 -74
  271. package/template/apps/asas-tanstack/src/components/mithaq/list.tsx +0 -865
  272. package/template/apps/asas-tanstack/src/components/mithaq/nav.tsx +0 -871
  273. package/template/apps/asas-tanstack/src/components/mithaq/nodes.tsx +0 -1024
  274. package/template/apps/asas-tanstack/src/components/mithaq/relationship-cell.tsx +0 -127
  275. package/template/apps/asas-tanstack/src/components/mithaq/relationship-control.tsx +0 -400
  276. package/template/apps/asas-tanstack/src/components/mithaq/rich-text-nodes.ts +0 -6
  277. package/template/apps/asas-tanstack/src/components/mithaq/rich-text.tsx +0 -1083
  278. package/template/apps/asas-tanstack/src/components/mithaq/upload.tsx +0 -686
  279. package/template/apps/asas-tanstack/src/components/ui/drawer.tsx +0 -215
  280. package/template/templates/asas/src/audit-log/audit-log.ts +0 -187
  281. package/template/templates/asas/src/audit-log/ui/activity.tsx +0 -243
  282. package/template/templates/asas/src/courses/ui/archive-button.tsx +0 -25
  283. package/template/templates/asas/src/courses/ui/no-courses-on-offer.tsx +0 -11
  284. package/template/templates/asas/src/courses/ui/on-offer.ts +0 -6
  285. package/template/templates/asas/src/enrollments/enrollments.ts +0 -137
  286. package/template/templates/asas/src/rich-text-nodes.ts +0 -11
  287. package/template/templates/asas/src/roles.ts +0 -16
  288. package/template/templates/asas/src/ui/rich-text-nodes.tsx +0 -15
  289. package/template/templates/asas/src/ui-platform/organization-actions.tsx +0 -114
  290. package/template/templates/auth/src/index.ts +0 -690
  291. /package/template/apps/{asas-platform → asas-backoffice}/components.json +0 -0
  292. /package/template/apps/{asas-platform → asas-backoffice}/gitignore +0 -0
  293. /package/template/apps/{asas-platform → asas-backoffice}/public/favicon.svg +0 -0
  294. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/accordion.tsx +0 -0
  295. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/alert-dialog.tsx +0 -0
  296. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/avatar.tsx +0 -0
  297. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/badge.tsx +0 -0
  298. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/breadcrumb.tsx +0 -0
  299. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/button-group.tsx +0 -0
  300. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/button.tsx +0 -0
  301. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/calendar.tsx +0 -0
  302. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/card.tsx +0 -0
  303. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/chart.tsx +0 -0
  304. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/checkbox.tsx +0 -0
  305. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/collapsible.tsx +0 -0
  306. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/combobox.tsx +0 -0
  307. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/dialog.tsx +0 -0
  308. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/direction.tsx +0 -0
  309. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/directional-icon.tsx +0 -0
  310. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/dropdown-menu.tsx +0 -0
  311. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/field.tsx +0 -0
  312. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/input-group.tsx +0 -0
  313. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/input-otp.tsx +0 -0
  314. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/input.tsx +0 -0
  315. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/label.tsx +0 -0
  316. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/popover.tsx +0 -0
  317. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/radio-group.tsx +0 -0
  318. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/resizable.tsx +0 -0
  319. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/scroll-area.tsx +0 -0
  320. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/select.tsx +0 -0
  321. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/separator.tsx +0 -0
  322. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/sheet.tsx +0 -0
  323. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/sidebar.tsx +0 -0
  324. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/skeleton.tsx +0 -0
  325. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/slider.tsx +0 -0
  326. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/spinner.tsx +0 -0
  327. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/switch.tsx +0 -0
  328. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/table.tsx +0 -0
  329. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/tabs.tsx +0 -0
  330. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/textarea.tsx +0 -0
  331. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/toggle-group.tsx +0 -0
  332. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/toggle.tsx +0 -0
  333. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/tooltip.tsx +0 -0
  334. /package/template/apps/{asas-platform → asas-backoffice}/src/copy.ts +0 -0
  335. /package/template/apps/{asas-platform → asas-backoffice}/src/hooks/use-mobile.ts +0 -0
  336. /package/template/apps/{asas-platform → asas-backoffice}/src/lib/utils.ts +0 -0
  337. /package/template/apps/{asas-platform → asas-backoffice}/src/theme.ts +0 -0
  338. /package/template/templates/{asas/src/kayan → features}/auth/user/index.ts +0 -0
  339. /package/template/templates/{asas/src → features/courses}/courses/ui/no-courses.tsx +0 -0
  340. /package/template/templates/{asas → mithaq}/components.json +0 -0
  341. /package/template/templates/{asas → mithaq}/src/storage/defaults.ts +0 -0
  342. /package/template/templates/{asas → mithaq}/src/storage/local.ts +0 -0
  343. /package/template/templates/{asas → mithaq}/src/storage/r2.ts +0 -0
  344. /package/template/templates/{asas → mithaq}/src/storage/s3.ts +0 -0
  345. /package/template/templates/{asas → mithaq}/src/storage/uploadthing.ts +0 -0
  346. /package/template/templates/{asas → mithaq}/src/ui/kpi-card.tsx +0 -0
  347. /package/template/templates/{asas → mithaq}/src/ui/record-page.tsx +0 -0
  348. /package/template/templates/{asas → mithaq}/src/ui/table-page.tsx +0 -0
@@ -22,6 +22,7 @@ export type OrganizationValues = {
22
22
  slug: string
23
23
  logo: string | null
24
24
  metadata: string | null
25
+ names: string | null
25
26
  }
26
27
  export type MemberValues = {
27
28
  id: string
@@ -30,6 +31,7 @@ export type MemberValues = {
30
31
  organizationId: string
31
32
  userId: string
32
33
  role: string
34
+ lastSeenAt: Date | null
33
35
  }
34
36
  export type CoursesValues = {
35
37
  id: string
@@ -54,6 +56,7 @@ export type CoursesValues = {
54
56
  _status: 'draft' | 'published' | null
55
57
  details: { root: Record<string, unknown> } | null
56
58
  cover: unknown
59
+ selfEnroll: boolean | null
57
60
  }
58
61
  export type SessionsValues = {
59
62
  id: string
@@ -68,6 +71,7 @@ export type SessionsValues = {
68
71
  archived: boolean | null
69
72
  details: { root: Record<string, unknown> } | null
70
73
  enrollments: Values['enrollments'][]
74
+ archiveReason: string | null
71
75
  }
72
76
  export type EnrollmentsValues = {
73
77
  id: string
@@ -118,6 +122,18 @@ export type LocalUsersValues = {
118
122
  _verified: boolean | null
119
123
  _verificationToken: string | null
120
124
  }
125
+ export type BackofficeLogValues = {
126
+ id: string
127
+ createdAt: Date
128
+ action: 'impersonate' | 'stopImpersonating' | 'setRole' | 'ban' | 'unban'
129
+ actor: string | null
130
+ actorEmail: string | null
131
+ target: string | null
132
+ targetEmail: string | null
133
+ previousRole: string | null
134
+ role: string | null
135
+ banReason: string | null
136
+ }
121
137
  export type Values = {
122
138
  user: UserValues
123
139
  organization: OrganizationValues
@@ -129,6 +145,7 @@ export type Values = {
129
145
  roles: RolesValues
130
146
  memberRoles: MemberRolesValues
131
147
  localUsers: LocalUsersValues
148
+ backofficeLog: BackofficeLogValues
132
149
  }
133
150
  export type Pages = {
134
151
  user: 'list' | 'view'
@@ -141,5 +158,6 @@ export type Pages = {
141
158
  roles: 'list' | 'view'
142
159
  memberRoles: 'list' | 'view'
143
160
  localUsers: 'list' | 'edit'
161
+ backofficeLog: 'list'
144
162
  }
145
163
  export type ScreenClient = Client<Pages>
@@ -1,13 +1,13 @@
1
1
  // Written by `mithaq generate screens`. Do not edit.
2
2
  import { createFileRoute } from '@tanstack/react-router'
3
3
  import { ScreenNotFound, ScreenPageView, askedFor, pageHead, pageLoader } from '@/screens.tsx'
4
- import { files } from './platform/files.ts'
4
+ import { files } from './backoffice/files.ts'
5
5
 
6
- export const Route = createFileRoute('/(screens)/platform/$')({
6
+ export const Route = createFileRoute('/(screens)/backoffice/$')({
7
7
  validateSearch: askedFor,
8
- loader: ({ location, params }) => pageLoader({ screen: 'platform', files, path: params._splat ?? '', query: location.search, href: location.href }),
8
+ loader: ({ location, params }) => pageLoader({ screen: 'backoffice', files, path: params._splat ?? '', query: location.search, href: location.href }),
9
9
  head: ({ loaderData, match }) => pageHead(loaderData, match.context),
10
- notFoundComponent: () => <ScreenNotFound files={files} screen={'platform'} />,
10
+ notFoundComponent: () => <ScreenNotFound files={files} screen={'backoffice'} />,
11
11
  component: Page,
12
12
  })
13
13
 
@@ -1,10 +1,10 @@
1
1
  // Written by `mithaq generate screens`. Do not edit.
2
2
  import { createFileRoute } from '@tanstack/react-router'
3
3
  import { ScreenPageView, askedFor, pageHead, pageLoader } from '@/screens.tsx'
4
- import { files } from './platform/files.ts'
4
+ import { files } from './backoffice/files.ts'
5
5
 
6
- export const Route = createFileRoute('/(screens)/platform/')({
7
- loader: ({ location }) => pageLoader({ screen: 'platform', files, path: '', query: {}, href: location.href }),
6
+ export const Route = createFileRoute('/(screens)/backoffice/')({
7
+ loader: ({ location }) => pageLoader({ screen: 'backoffice', files, path: '', query: {}, href: location.href }),
8
8
  head: ({ loaderData, match }) => pageHead(loaderData, match.context),
9
9
  component: () => <ScreenPageView files={files} data={Route.useLoaderData()} query={askedFor({})} onQuery={() => {}} />,
10
10
  })
@@ -1,10 +1,10 @@
1
1
  // Written by `mithaq generate screens`. Do not edit.
2
2
  import { Outlet, createFileRoute } from '@tanstack/react-router'
3
3
  import { ScreenFrame, frameLoader } from '@/screens.tsx'
4
- import { files } from './platform/files.ts'
4
+ import { files } from './backoffice/files.ts'
5
5
 
6
- export const Route = createFileRoute('/(screens)/platform')({
7
- loader: ({ location }) => frameLoader({ screen: 'platform', files, href: location.href }),
6
+ export const Route = createFileRoute('/(screens)/backoffice')({
7
+ loader: ({ location }) => frameLoader({ screen: 'backoffice', files, href: location.href }),
8
8
  component: () => (
9
9
  <ScreenFrame files={files} data={Route.useLoaderData()}>
10
10
  <Outlet />
@@ -2,7 +2,7 @@ import { HeadContent, Outlet, Scripts, createRootRoute } from '@tanstack/react-r
2
2
  import { DirectionProvider } from '@/components/ui/direction'
3
3
  export { locales } from '../copy.ts'
4
4
  import { dirOf, documentHead, localeOf, translator } from '../copy.ts'
5
- import { ThemeProvider } from '../components/mithaq/nav.tsx'
5
+ import { ThemeProvider } from 'mithaq-admin/theme.tsx'
6
6
  import { themeStorageKey } from '../theme.ts'
7
7
  import appCss from '../styles.css?url'
8
8
 
@@ -37,7 +37,7 @@ export const Route = createRootRoute({
37
37
  typeof document === 'undefined'
38
38
  ? localeOf((ctx as unknown as FromRequest).serverContext?.locale as string | undefined)
39
39
  : localeOf(document.documentElement.lang)
40
- return { locale, dir: dirOf(locale), app: 'platform.brand' }
40
+ return { locale, dir: dirOf(locale), app: 'backoffice.brand' }
41
41
  },
42
42
  // The favicon stops the browser asking for a `/favicon.ico` nobody serves.
43
43
  head: ({ match }) => ({
@@ -0,0 +1,34 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+ // Loads the types of the `server` route option; erased at build.
3
+ import type {} from '@tanstack/start-client-core'
4
+ import { asas, backofficeURL } from 'asas'
5
+
6
+ // The backoffice reads every organization: a read of a kayan asks `tenant: 'all'`, which the runner
7
+ // grants backoffice staff in this app alone and refuses anyone else. A global is one organization's row
8
+ // and asks for none.
9
+ const reads = new Set(asas.operations.filter((one) => one.effect === 'read' && !one.single).map((one) => `/api/${one.kayan}/${one.name}`))
10
+
11
+ // The body is handed on unread, so the plug that answers bounds it before any session is looked at.
12
+ const acrossTenants = (request: Request) => {
13
+ const url = new URL(request.url)
14
+ if (!reads.has(url.pathname)) return request
15
+ url.searchParams.set('tenant', 'all')
16
+ return new Request(url, { method: request.method, headers: request.headers, body: request.body, signal: request.signal, duplex: 'half' } as RequestInit)
17
+ }
18
+
19
+ // A write whose `Origin` is not BACKOFFICE_URL's is refused: the data plug checks no origin and leans on
20
+ // the session cookie's `SameSite=Lax`, which the customer app's sibling subdomain passes, so this check
21
+ // is the barrier between the two. Never the URL the request arrived on: behind a TLS proxy it is
22
+ // http://, and its host is whatever the Host header said.
23
+ const origin = new URL(backofficeURL).origin
24
+
25
+ export const Route = createFileRoute('/api/$')({
26
+ server: {
27
+ handlers: {
28
+ ANY: async ({ request }) =>
29
+ request.method === 'GET' || request.method === 'HEAD' || request.headers.get('origin') === origin
30
+ ? asas.handler(acrossTenants(request))
31
+ : new Response('Forbidden', { status: 403 }),
32
+ },
33
+ },
34
+ })
@@ -0,0 +1,113 @@
1
+ import { useState, type ComponentProps } from 'react'
2
+ import { createFileRoute, redirect, useNavigate } from '@tanstack/react-router'
3
+ import { Button } from '@/components/ui/button'
4
+ import { authArm } from '../api.ts'
5
+ import { AuthBox, AuthFrame, authGate, notAnEmail, useRefusal } from '../auth-frame.tsx'
6
+ import { documentHead } from '../copy.ts'
7
+ import { firstUser } from '../first-user.ts'
8
+ import { useCopy } from './__root.tsx'
9
+
10
+ export const Route = createFileRoute('/auth/create-first-user')({
11
+ head: ({ match }) => documentHead(match.context, (t) => t('auth.createFirstUser')),
12
+ beforeLoad: async () => {
13
+ const state = await firstUser()
14
+ if (state.initialized) throw redirect({ to: '/auth/sign-in' })
15
+ return { code: state.code, password: (await authGate()).password }
16
+ },
17
+ component: CreateFirstUser,
18
+ })
19
+
20
+ function CreateFirstUser() {
21
+ const navigate = useNavigate()
22
+ const { t } = useCopy()
23
+ const refused = useRefusal()
24
+ const { code: asked, password: bounds } = Route.useRouteContext()
25
+ const [name, setName] = useState('')
26
+ const [email, setEmail] = useState('')
27
+ const [password, setPassword] = useState('')
28
+ const [confirmation, setConfirmation] = useState('')
29
+ const [code, setCode] = useState('')
30
+ const [pending, setPending] = useState(false)
31
+ const [refusal, setRefusal] = useState<string | null>(null)
32
+ const [left, setLeft] = useState<ReadonlySet<string>>(() => new Set())
33
+ const required = t('auth.required', 'This field is required.')
34
+ const wrong = {
35
+ name: () => (name.trim() ? undefined : required),
36
+ email: () => (!email ? required : notAnEmail(email) ? t('auth.invalidEmail', 'Invalid email') : undefined),
37
+ password: () =>
38
+ !password ? required
39
+ : password.length < bounds.min ? t('auth.passwordTooShort', 'Password too short')
40
+ : password.length > bounds.max ? t('auth.passwordTooLong', 'Password too long')
41
+ : undefined,
42
+ confirmation: () => (!confirmation ? required : confirmation === password ? undefined : t('auth.passwordsDiffer', 'The two passwords are not the same.')),
43
+ code: () => (asked !== 'required' || code ? undefined : required),
44
+ }
45
+ const errorOf = (box: keyof typeof wrong) => (left.has(box) ? wrong[box]() : undefined)
46
+ const box = (id: keyof typeof wrong, label: string | undefined, props: ComponentProps<'input'> & { description?: string }) => (
47
+ <AuthBox id={id} label={label} error={errorOf(id)} onLeave={() => setLeft((already) => new Set(already).add(id))} {...props} />
48
+ )
49
+
50
+ const submit = async (event: React.FormEvent) => {
51
+ event.preventDefault()
52
+ setLeft(new Set(Object.keys(wrong)))
53
+ if (Object.values(wrong).some((check) => check())) return
54
+ setPending(true)
55
+ setRefusal(null)
56
+ const made = await authArm('first-register', { name, email, password, ...(asked === 'required' && { code }) })
57
+ setPending(false)
58
+ if (!made.ok) return setRefusal(refused(made, t('auth.createFirstUserFailed', 'That administrator could not be created.')))
59
+ await navigate({ to: '/auth/sign-in' })
60
+ }
61
+
62
+ if (asked === 'unset') {
63
+ return (
64
+ <AuthFrame title={t('auth.createFirstUser', 'Create the first administrator')}>
65
+ <p className="text-sm" role="alert">
66
+ {t('auth.setupCodeUnset')}
67
+ </p>
68
+ </AuthFrame>
69
+ )
70
+ }
71
+ return (
72
+ <AuthFrame
73
+ title={t('auth.createFirstUser', 'Create the first administrator')}
74
+ description={t('auth.createFirstUserDescription', 'No one administers this backoffice yet. The account made here becomes its first administrator.')}
75
+ >
76
+ <form className="grid gap-4" noValidate onSubmit={submit}>
77
+ {box('name', t('auth.name', 'Name'), { autoComplete: 'name', onChange: (event) => setName(event.target.value), type: 'text', value: name })}
78
+ {box('email', t('auth.email', 'Email'), { autoComplete: 'email', onChange: (event) => setEmail(event.target.value), type: 'email', value: email })}
79
+ {box('password', t('auth.password', 'Password'), {
80
+ autoComplete: 'new-password',
81
+ description: t('auth.passwordRule', 'At least {count} characters.')!.replace('{count}', String(bounds.min)),
82
+ onChange: (event) => setPassword(event.target.value),
83
+ type: 'password',
84
+ value: password,
85
+ })}
86
+ {box('confirmation', t('auth.confirmPassword', 'Confirm the password'), {
87
+ autoComplete: 'new-password',
88
+ onChange: (event) => setConfirmation(event.target.value),
89
+ type: 'password',
90
+ value: confirmation,
91
+ })}
92
+ {asked === 'required'
93
+ ? box('code', t('auth.setupCode', 'Setup code'), {
94
+ autoComplete: 'off',
95
+ description: t('auth.setupCodeDescription', 'The SETUP_CODE this server was started with.'),
96
+ onChange: (event) => setCode(event.target.value),
97
+ type: 'password',
98
+ value: code,
99
+ })
100
+ : null}
101
+ {/* `role="alert"` so a refusal is spoken when it appears; nothing moves the focus to it. */}
102
+ {refusal && (
103
+ <p className="text-destructive text-sm" role="alert">
104
+ {refusal}
105
+ </p>
106
+ )}
107
+ <Button disabled={pending} type="submit">
108
+ {pending ? t('auth.createFirstUserPending', 'Creating…') : t('auth.createFirstUserSubmit', 'Create the administrator')}
109
+ </Button>
110
+ </form>
111
+ </AuthFrame>
112
+ )
113
+ }
@@ -1,15 +1,20 @@
1
1
  import { useState, type ComponentProps } from 'react'
2
- import { createFileRoute, useNavigate } from '@tanstack/react-router'
2
+ import { createFileRoute, redirect, useNavigate } from '@tanstack/react-router'
3
3
  import { Button } from '@/components/ui/button'
4
4
  import { authArm } from '../api.ts'
5
5
  import { AuthBox, AuthFrame, authGate, notAnEmail, redirectSearch, useRefusal } from '../auth-frame.tsx'
6
6
  import { documentHead } from '../copy.ts'
7
+ import { firstUser } from '../first-user.ts'
7
8
  import { useCopy } from './__root.tsx'
8
- import { wanted } from '../redirect.ts'
9
+ import { landing } from '../redirect.ts'
9
10
 
10
11
  export const Route = createFileRoute('/auth/sign-in')({
11
12
  head: ({ match }) => documentHead(match.context, (t) => t('auth.signIn')),
12
13
  validateSearch: redirectSearch,
14
+ // Nobody can sign in to a backoffice no one administers yet.
15
+ beforeLoad: async () => {
16
+ if (!(await firstUser()).initialized) throw redirect({ href: '/auth/create-first-user' })
17
+ },
13
18
  component: SignIn,
14
19
  })
15
20
 
@@ -49,11 +54,11 @@ function SignIn() {
49
54
  setPending(false)
50
55
 
51
56
  // A navigation, not a reload, so the screen's loader runs with the cookie just set.
52
- await navigate({ to: wanted({ asked: search.redirect, home: (await authGate()).home.path }) })
57
+ await navigate({ href: landing({ asked: search.redirect, screens: (await authGate()).screens }) })
53
58
  }
54
59
 
55
60
  return (
56
- <AuthFrame title={t('auth.signIn', 'Sign in')} description={t('auth.signInPlatformDescription', 'Sign in to reach the platform.')}>
61
+ <AuthFrame title={t('auth.signIn', 'Sign in')} description={t('auth.signInBackofficeDescription', 'Sign in to reach the backoffice.')}>
57
62
  <form className="grid gap-4" noValidate onSubmit={submit}>
58
63
  {box('email', t('auth.email', 'Email'), { autoComplete: 'email', onChange: (event) => setEmail(event.target.value), type: 'email', value: email })}
59
64
  {box('password', t('auth.password', 'Password'), { autoComplete: 'current-password', onChange: (event) => setPassword(event.target.value), type: 'password', value: password })}
@@ -4,9 +4,17 @@ import { Button } from '@/components/ui/button'
4
4
  import { authArm } from '../api.ts'
5
5
  import { AuthFrame, useRefusal } from '../auth-frame.tsx'
6
6
  import { documentHead } from '../copy.ts'
7
+ import { NoOrganization } from '../no-organization.tsx'
7
8
  import { ImpersonationBar, loadEnterable } from '../screens.tsx'
8
9
  import { useCopy } from './__root.tsx'
9
10
 
11
+ /**
12
+ * Where someone sent here from a screen their role does not open goes without being asked: the one screen it does
13
+ * open. A page refused inside that screen is said instead, and so is an impersonation, whose bar is the way out.
14
+ */
15
+ export const onwardOf = ({ screens, refused, impersonating }: { screens: readonly { path: string }[]; refused?: unknown; impersonating?: unknown }) =>
16
+ !refused && !impersonating && screens.length === 1 ? screens[0]!.path : undefined
17
+
10
18
  export const Route = createFileRoute('/unauthorized')({
11
19
  // The path a screen refused; the server only matches it against the build's pages.
12
20
  validateSearch: (search: Record<string, unknown>): { from?: string } => (typeof search.from === 'string' ? { from: search.from } : {}),
@@ -15,6 +23,8 @@ export const Route = createFileRoute('/unauthorized')({
15
23
  const entered = await loadEnterable({ data: { from: deps.from, locale: context.locale } })
16
24
  // Carries this page's own path so a visitor whose session expired lands back here after signing in.
17
25
  if (!entered) throw redirect({ to: '/auth/sign-in', search: { redirect: '/unauthorized' } })
26
+ const onward = onwardOf(entered)
27
+ if (onward) throw redirect({ href: onward })
18
28
  return entered
19
29
  },
20
30
  // After the loader, so its data's type is inferred before this reads it.
@@ -49,19 +59,21 @@ function Unauthorized() {
49
59
  : part === '{page}' ? <bdi key={at}>{refused?.page}</bdi>
50
60
  : part)
51
61
  const link = 'text-foreground underline underline-offset-4'
62
+ // Someone who belongs to no organization is shown the ways into one, which the app draws.
63
+ const unplaced = !refused && !screens.length && !organizations.length && !others.length && !impersonating
52
64
  const screenName = (name: string) => t(`screens.${name}`, name) ?? name
53
65
  return (
54
66
  <AuthFrame
55
- title={screens.length ? t('auth.unauthorized', 'Not for your role') : t('auth.noAccess', 'Your account has no access yet')}
67
+ title={screens.length ? t('auth.unauthorized', 'Choose where to go') : t('auth.noAccess', 'Your account has no access yet')}
56
68
  description={
57
69
  <span data-no-access={screens.length ? undefined : ''}>
58
70
  {refused
59
71
  ? said(t('auth.unauthorizedPage', 'Your role does not open that page: {page}.'))
60
72
  : screens.length
61
- ? t('auth.unauthorizedDescription', 'Your role does not open that screen. These are the screens it opens:')
73
+ ? t('auth.unauthorizedDescription', 'This page isn\'t part of your account. Where would you like to go?')
62
74
  : organizations.length
63
75
  ? said(t('auth.noAccessDescription', '{email} is a member of {organization} and holds no role that opens a screen here. Ask an administrator of {organization} to give you one.'))
64
- : said(t('auth.noOrganization', '{email} belongs to no organization yet. Ask an administrator to invite you.'))}
76
+ : said(t('auth.noOrganization', '{email} belongs to no organization yet.'))}
65
77
  </span>
66
78
  }
67
79
  >
@@ -73,16 +85,16 @@ function Unauthorized() {
73
85
  {(t('auth.backTo', 'Back to {screen}') ?? 'Back to {screen}').replace('{screen}', () => screenName(refused.screen.name))}
74
86
  </a>
75
87
  ) : screens.length ? (
76
- <ul className="grid gap-2">
88
+ <div className="grid gap-2">
77
89
  {screens.map((screen) => (
78
- <li key={screen.name}>
79
- {/* An anchor: the path is the screen's own data, not a route the router knows. */}
80
- <a className={link} href={screen.path}>
81
- {screenName(screen.name)}
82
- </a>
83
- </li>
90
+ // An anchor: the path is the screen's own data, not a route the router knows.
91
+ <Button key={screen.name} nativeButton={false} className="max-sm:h-11" render={<a href={screen.path} />}>
92
+ {screenName(screen.name)}
93
+ </Button>
84
94
  ))}
85
- </ul>
95
+ </div>
96
+ ) : unplaced ? (
97
+ <NoOrganization email={email} />
86
98
  ) : null}
87
99
  {/* So a switch into an organization where the actor holds no role is not a dead end. */}
88
100
  {others.map((one) => (
@@ -119,4 +131,4 @@ function Unauthorized() {
119
131
  </AuthFrame>
120
132
  )
121
133
  }
122
- // mithaq-admin b06d8d335d306c07556f8f2b81fba20ba7ded198 3c5c304b3892c56759bf780f155ef3aa154ce3cc954e3d240b3537cdd97da735
134
+ // mithaq-admin 0.0.0 81d25978cdacbb38254854b0f64b738946ee27baca06a952aed51d35e6fb10af
@@ -1,9 +1,11 @@
1
1
  import { useBlocker, useRouter } from '@tanstack/react-router'
2
2
  import { useState, type ReactElement } from 'react'
3
- import { Form, type FormProps } from './components/mithaq/form.tsx'
3
+ import { Form } from 'mithaq-admin/form.tsx'
4
+ import type { FormProps } from 'mithaq-admin/form-types.tsx'
4
5
 
5
6
  /**
6
- * A kayan's form on a screen's page. A create moves to the new row's edit page, a delete to the list.
7
+ * A kayan's form on a screen's page. A create moves to where `edit` sends it, the new row's edit page or, for a page
8
+ * opened over its list, the list; a delete moves to the list.
7
9
  * While the form holds a change the router blocks navigation and the form asks its discard question.
8
10
  */
9
11
  export function ScreenForm({ edit, list, ...props }: Omit<FormProps, 'onDone' | 'onDirty' | 'onCancel' | 'blocked'> & {
@@ -31,4 +33,4 @@ export function ScreenForm({ edit, list, ...props }: Omit<FormProps, 'onDone' |
31
33
  />
32
34
  )
33
35
  }
34
- // mithaq-admin b06d8d335d306c07556f8f2b81fba20ba7ded198 7284b5e97770fd0c395fafa0b7459cab4ceb458e4bc982dc8233894126116170
36
+ // mithaq-admin 0.0.0 508fa95987312292d3a5737dd1fd6aba03fbd5a7dbbbf5bb3bea2812b2adfde4
@@ -2,10 +2,11 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'
2
2
  import { useState, type ReactElement } from 'react'
3
3
  import { ListContext, useScreen } from 'mithaq/admin/react'
4
4
  import { Button } from '@/components/ui/button'
5
- import { List, type ListProps, type Page } from './components/mithaq/list.tsx'
6
- import { QueryRefused, findOf, readQuery, readRaw, rulesOf, scopedTo, writeQuery, type ListQuery, type RawQuery } from './components/mithaq/list-query.ts'
7
- import { counted } from './components/mithaq/controls.tsx'
8
- import { listOf } from './components/mithaq/nodes.tsx'
5
+ import { List } from 'mithaq-admin/list.tsx'
6
+ import type { ListProps, Page } from 'mithaq-admin/list-types.tsx'
7
+ import { QueryRefused, findOf, readQuery, readRaw, rulesOf, scopedTo, writeQuery, type ListQuery, type RawQuery } from 'mithaq-admin/list-query.ts'
8
+ import { counted } from 'mithaq-admin/format.tsx'
9
+ import { listOf } from 'mithaq-admin/node-tree.ts'
9
10
 
10
11
  /**
11
12
  * The list's search as the router carries it: the URL's pairs as strings, each
@@ -136,4 +137,4 @@ export function ScreenList({
136
137
  </ListContext>
137
138
  )
138
139
  }
139
- // mithaq-admin b06d8d335d306c07556f8f2b81fba20ba7ded198 3c37825f9f645fd3f9c95a3d1ec554e863e85767e8329a1b2e16a40e5f5ea381
140
+ // mithaq-admin 2d3ab739c92c6c2b397041b76bf4b23b2902b7a2 900f9707948cf18e36aec6776da8f9e77e4ed92438073443319e54e7a33a9ecf
@@ -7,4 +7,4 @@ export const screenNamed = <Known extends object>(known: Known, name: unknown):
7
7
  if (typeof name !== 'string' || !Object.hasOwn(known, name)) throw new Error(`"${String(name)}" is no screen of this app.`)
8
8
  return name as keyof Known & string
9
9
  }
10
- // mithaq-admin b06d8d335d306c07556f8f2b81fba20ba7ded198 878b7180f2aa169546feb42bcdfcb6002841d08e46788c94ddd6edfc4e3502ca
10
+ // mithaq-admin 2d3ab739c92c6c2b397041b76bf4b23b2902b7a2 878b7180f2aa169546feb42bcdfcb6002841d08e46788c94ddd6edfc4e3502ca