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
@@ -1,871 +0,0 @@
1
- import { createContext, useContext, useEffect, useMemo, useState, type ComponentProps, type CSSProperties, type ReactNode } from 'react'
2
- import {
3
- BookOpen,
4
- Building2,
5
- CalendarDays,
6
- Check,
7
- ChevronsUpDown,
8
- ClipboardList,
9
- Folder,
10
- GalleryVerticalEnd,
11
- HelpCircle,
12
- Languages,
13
- LayoutGrid,
14
- LogOut,
15
- Mail,
16
- Monitor,
17
- Moon,
18
- MoreHorizontal,
19
- PlusCircle,
20
- Search,
21
- Settings2,
22
- Share2,
23
- ShieldCheck,
24
- Sun,
25
- Table2,
26
- Trash2,
27
- Users,
28
- type LucideIcon,
29
- } from 'lucide-react'
30
- import { Moon02Icon, Sun03Icon } from '@hugeicons/core-free-icons'
31
- import { HugeiconsIcon } from '@hugeicons/react'
32
- import type { ClientKayan } from 'mithaq/admin'
33
- import { Avatar, AvatarFallback } from '@/components/ui/avatar'
34
- import { Button, buttonVariants } from '@/components/ui/button'
35
- import {
36
- DropdownMenu,
37
- DropdownMenuContent,
38
- DropdownMenuGroup,
39
- DropdownMenuItem,
40
- DropdownMenuLabel,
41
- DropdownMenuRadioGroup,
42
- DropdownMenuRadioItem,
43
- DropdownMenuSeparator,
44
- DropdownMenuTrigger,
45
- } from '@/components/ui/dropdown-menu'
46
- import { Separator } from '@/components/ui/separator'
47
- import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '@/components/ui/sheet'
48
- import {
49
- Sidebar,
50
- SidebarContent,
51
- SidebarFooter,
52
- SidebarGroup,
53
- SidebarGroupContent,
54
- SidebarGroupLabel,
55
- SidebarHeader,
56
- SidebarInset,
57
- SidebarMenu,
58
- SidebarMenuAction,
59
- SidebarMenuButton,
60
- SidebarMenuItem,
61
- SidebarProvider,
62
- SidebarTrigger,
63
- useSidebar,
64
- } from '@/components/ui/sidebar'
65
- import { titleOf, type Translate } from './controls.tsx'
66
-
67
- /**
68
- * As much of the access report as this screen reads: one `read` per kayan, keyed by its name.
69
- * `operations` is what a screen gates a control on: an effect is allowed where any one of its
70
- * operations is, so gate a button on `operations.archive`, never on the effect.
71
- */
72
- export type AccessReport = {
73
- readonly collections?: Readonly<
74
- Record<
75
- string,
76
- { readonly read?: boolean; readonly operations?: Readonly<Record<string, boolean>> } | undefined
77
- >
78
- >
79
- }
80
-
81
- /**
82
- * The two keys of a kayan this screen reads: its name, and whether it is a global, which picks the
83
- * singular over the plural. A shell handed only these server-renders no field names or labels.
84
- */
85
- export type NavKayan = Pick<ClientKayan, 'name' | 'single'>
86
-
87
- /**
88
- * One lucide icon per common kayan name, `Table2` for every other kayan. An app gives its own
89
- * kayan an icon by editing this map.
90
- */
91
- export const navIcons: Record<string, LucideIcon> = {
92
- courses: BookOpen,
93
- sessions: CalendarDays,
94
- enrollments: ClipboardList,
95
- settings: Settings2,
96
- user: Users,
97
- localUsers: Users,
98
- member: Users,
99
- organization: Building2,
100
- invitation: Mail,
101
- roles: ShieldCheck,
102
- }
103
-
104
- /** The icon a kayan is listed under. `Object.hasOwn` so a kayan named for a key of `Object.prototype` reads nothing off it. */
105
- export const iconOf = (name: string): LucideIcon => (Object.hasOwn(navIcons, name) ? navIcons[name]! : Table2)
106
-
107
- /** One entry of the kayan index: what it is called, where it goes and what it is drawn with. */
108
- export type NavEntry = { kayan: NavKayan; label: string; href: string; Icon: LucideIcon }
109
-
110
- /**
111
- * What the header's `<h1>` says: the kayan's plural on its list, its singular on a row of it, the
112
- * shell's own heading anywhere else. A row is matched by the rule `NavMain` marks an entry active by.
113
- */
114
- export const titleFor = ({
115
- entries,
116
- copy,
117
- pathname,
118
- }: {
119
- entries: readonly NavEntry[]
120
- copy: { t: Translate; locale: string }
121
- pathname?: string
122
- }): string | undefined => {
123
- const row = pathname === undefined ? undefined : entries.find((one) => pathname.startsWith(`${one.href}/`))
124
- if (row) return titleOf({ kayan: row.kayan, copy })
125
- const listed = entries.find((one) => one.href === pathname)
126
- return listed ? listed.label : copy.t('admin.nav', 'Admin')
127
- }
128
-
129
- /** The kayan index: the kayans this request's access report lets the viewer read. */
130
- export const navEntries = ({
131
- client,
132
- access,
133
- copy,
134
- href,
135
- }: {
136
- client: readonly NavKayan[]
137
- access: AccessReport
138
- copy: { t: Translate; locale: string }
139
- href: (kayan: NavKayan) => string
140
- }): NavEntry[] =>
141
- client
142
- // `=== true` so a kayan the report has no entry for is denied, including one named for a key
143
- // of `Object.prototype`.
144
- .filter((kayan) => access.collections?.[kayan.name]?.read === true)
145
- .map((kayan) => ({
146
- kayan,
147
- label: titleOf({ kayan, copy, many: !kayan.single }),
148
- href: href(kayan),
149
- Icon: iconOf(kayan.name),
150
- }))
151
-
152
- /** The values the theme toggle offers. */
153
- export type Theme = 'dark' | 'light' | 'system'
154
-
155
- /** The themes the user menu offers unless the project passes its own to `NavUserMenu`. */
156
- export const defaultThemes: readonly Theme[] = ['light', 'dark', 'system']
157
-
158
- type ThemeProviderProps = {
159
- children: ReactNode
160
- defaultTheme?: Theme
161
- storageKey?: string
162
- }
163
-
164
- type ThemeProviderState = {
165
- theme: Theme
166
- setTheme: (theme: Theme) => void
167
- }
168
-
169
- const initialState: ThemeProviderState = {
170
- theme: 'system',
171
- setTheme: () => null,
172
- }
173
-
174
- /** `null` outside a provider, so a menu that offers the choice draws nothing where nothing would keep it. */
175
- const ThemeProviderContext = createContext<ThemeProviderState | null>(null)
176
-
177
- /**
178
- * Keeps the theme in `localStorage` and puts `light` or `dark` on the document element. The stored
179
- * theme is read in the first browser render and not in an effect: a change after mount re-renders
180
- * consumers, and inside an unhydrated Suspense boundary that makes React drop the server's HTML.
181
- */
182
- export function ThemeProvider({
183
- children,
184
- defaultTheme = 'system',
185
- storageKey = 'vite-ui-theme',
186
- }: ThemeProviderProps) {
187
- const [theme, setTheme] = useState<Theme>(() => {
188
- if (typeof window === 'undefined') return defaultTheme
189
- const stored = localStorage.getItem(storageKey)
190
- // A stray value under this key would hydrate mismatched against the server's default.
191
- return stored === 'dark' || stored === 'light' || stored === 'system' ? stored : defaultTheme
192
- })
193
-
194
- useEffect(() => {
195
- const root = window.document.documentElement
196
- root.classList.remove('light', 'dark')
197
-
198
- if (theme === 'system') {
199
- const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
200
- root.classList.add(systemTheme)
201
- return
202
- }
203
-
204
- root.classList.add(theme)
205
- }, [theme])
206
-
207
- const value = useMemo(
208
- () => ({
209
- theme,
210
- setTheme: (newTheme: Theme) => {
211
- localStorage.setItem(storageKey, newTheme)
212
- setTheme(newTheme)
213
- },
214
- }),
215
- [theme, storageKey],
216
- )
217
-
218
- return <ThemeProviderContext.Provider value={value}>{children}</ThemeProviderContext.Provider>
219
- }
220
-
221
- /** The theme and its setter, or `null` where no `ThemeProvider` is above. */
222
- export const useTheme = () => useContext(ThemeProviderContext)
223
-
224
- /** A theme toggle button whose menu offers light, dark and system. */
225
- export function ModeToggle({ t }: { t: Translate }) {
226
- const { setTheme } = useTheme() ?? initialState
227
-
228
- return (
229
- <DropdownMenu>
230
- <DropdownMenuTrigger className={buttonVariants({ variant: 'outline', size: 'icon' })}>
231
- <HugeiconsIcon
232
- icon={Sun03Icon}
233
- className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"
234
- />
235
- <HugeiconsIcon
236
- icon={Moon02Icon}
237
- className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"
238
- />
239
- <span className="sr-only">{t('admin.theme', 'Toggle theme')}</span>
240
- </DropdownMenuTrigger>
241
- <DropdownMenuContent align="end" className="max-sm:**:data-[slot$=item]:min-h-11">
242
- <DropdownMenuItem onClick={() => setTheme('light')}>{t('admin.themeLight', 'Light')}</DropdownMenuItem>
243
- <DropdownMenuItem onClick={() => setTheme('dark')}>{t('admin.themeDark', 'Dark')}</DropdownMenuItem>
244
- <DropdownMenuItem onClick={() => setTheme('system')}>
245
- {t('admin.themeSystem', 'System')}
246
- </DropdownMenuItem>
247
- </DropdownMenuContent>
248
- </DropdownMenu>
249
- )
250
- }
251
-
252
- /**
253
- * A language's own name for itself (`ar` reads `العربية`), whatever the viewer reads in. It is the
254
- * same in every locale, so it carries no copy key. `of` throws a `RangeError` on a malformed tag,
255
- * so the tag itself is the fallback.
256
- */
257
- export const endonym = (locale: string): string => {
258
- try {
259
- return new Intl.DisplayNames([locale], { type: 'language' }).of(locale) ?? locale
260
- } catch {
261
- return locale
262
- }
263
- }
264
-
265
- /** A radio row: a 44 pixel target on a phone, and the tick on the inline end in right-to-left pages, where the primitive draws it at the right. */
266
- const radioRow = 'max-sm:min-h-11 rtl:pr-3 rtl:pl-8 rtl:[&>[data-slot=dropdown-menu-radio-item-indicator]]:right-auto rtl:[&>[data-slot=dropdown-menu-radio-item-indicator]]:left-2'
267
-
268
- /** One row of the user menu's locale group: the locale, what it calls itself, and whether it is the one being read in. */
269
- export const localeEntries = (
270
- locales: readonly string[],
271
- current: string,
272
- ): readonly { locale: string; label: string; active: boolean }[] =>
273
- locales.map((one) => ({ locale: one, label: endonym(one), active: one === current }))
274
-
275
- /** What the shell is told about the viewer, which is the two strings the user menu draws. */
276
- export type NavUser = {
277
- name: string
278
- email: string
279
- /** The other screens the host links for this viewer, each a whole address; the user menu draws a link to each. */
280
- screens?: readonly { name: string; path: string }[]
281
- }
282
-
283
- /**
284
- * One organization the switcher lists. Two may share a name, so each carries a slug, which the
285
- * switcher shows beside a name that repeats.
286
- */
287
- export type NavOrganization = { id: string; name: string; slug?: string }
288
-
289
- export type ShellProps = {
290
- /** Every kayan and global the app chose to hand down, as little of each as the two keys of `NavKayan`. */
291
- client: readonly NavKayan[]
292
- access: AccessReport
293
- t: Translate
294
- locale: string
295
- /** Where an entry goes. A prop so the shell imports no router. */
296
- href: (kayan: NavKayan) => string
297
- /** The path the viewer is on, so the entry for it is marked current. A prop for the same reason `href` is one. */
298
- pathname?: string
299
- /** Which side the sidebar is on. */
300
- side?: 'left' | 'right'
301
- user: NavUser
302
- organizations: readonly NavOrganization[]
303
- activeOrganizationId?: string | null
304
- /** What switching the active organization means, which is the app's business because it is the auth arm's. */
305
- onSwitchOrganization: (id: string) => void
306
- onSignOut: () => void
307
- /** Where the host's permissions screen is. No row is drawn without it. */
308
- permissionsHref?: string
309
- /** Which locales the host reads in, so the user menu can offer them. A prop because mithaq resolves no locale. */
310
- locales?: readonly string[]
311
- /** What choosing a locale means. Without it the menu has no locale group. */
312
- onLocale?: (locale: string) => void
313
- children?: ReactNode
314
- }
315
-
316
- export function Shell({
317
- access,
318
- activeOrganizationId,
319
- children,
320
- client,
321
- href,
322
- locale,
323
- locales,
324
- onLocale,
325
- onSignOut,
326
- onSwitchOrganization,
327
- organizations,
328
- pathname,
329
- permissionsHref,
330
- side = 'left',
331
- t,
332
- user,
333
- }: ShellProps) {
334
- const entries = navEntries({ client, access, copy: { t, locale }, href })
335
- return (
336
- <SidebarProvider
337
- style={
338
- {
339
- '--sidebar-width': 'calc(var(--spacing) * 72)',
340
- '--header-height': 'calc(var(--spacing) * 12)',
341
- } as CSSProperties
342
- }
343
- >
344
- <AppSidebar
345
- activeOrganizationId={activeOrganizationId}
346
- entries={entries}
347
- locale={locale}
348
- locales={locales}
349
- onLocale={onLocale}
350
- onSignOut={onSignOut}
351
- onSwitchOrganization={onSwitchOrganization}
352
- organizations={organizations}
353
- pathname={pathname}
354
- permissionsHref={permissionsHref}
355
- side={side}
356
- t={t}
357
- user={user}
358
- variant="inset"
359
- />
360
- {/* `min-w-0`: the inset is a flex item beside the sidebar, and a table wider than a phone
361
- would widen the whole document. */}
362
- <SidebarInset className="min-w-0">
363
- <SiteHeader t={t} title={titleFor({ entries, copy: { t, locale }, pathname })} />
364
- <div className="flex flex-1 flex-col px-4 pb-4 lg:px-6 lg:pb-6">
365
- {/* `min-w-0`: a flex child whose minimum is its content pushes the sidebar off the page
366
- instead of scrolling a wide table. */}
367
- <div className="@container/main flex min-w-0 flex-1 flex-col gap-2">{children}</div>
368
- </div>
369
- </SidebarInset>
370
- </SidebarProvider>
371
- )
372
- }
373
-
374
- /**
375
- * The sidebar: organization switcher, kayan index and user menu. It takes `side` and no `dir`,
376
- * since the direction is already on the document.
377
- */
378
- export function AppSidebar({
379
- activeOrganizationId,
380
- entries,
381
- locale,
382
- locales,
383
- onLocale,
384
- onSignOut,
385
- onSwitchOrganization,
386
- organizations,
387
- pathname,
388
- permissionsHref,
389
- t,
390
- user,
391
- ...props
392
- }: {
393
- activeOrganizationId?: string | null
394
- entries: readonly NavEntry[]
395
- locale?: string
396
- locales?: readonly string[]
397
- onLocale?: (locale: string) => void
398
- onSignOut: () => void
399
- onSwitchOrganization: (id: string) => void
400
- organizations: readonly NavOrganization[]
401
- pathname?: string
402
- permissionsHref?: string
403
- t: Translate
404
- user: NavUser
405
- } & ComponentProps<typeof Sidebar>) {
406
- return (
407
- <NavSidebar {...props} t={t}>
408
- {/* One navigation landmark around the whole sidebar. A box of its own: the sidebar's props
409
- reach no element on a phone, where it is a sheet. */}
410
- <nav aria-label={t('admin.navigation', 'Navigation')} className="flex min-h-0 flex-1 flex-col">
411
- <SidebarHeader>
412
- <OrganizationSwitcher
413
- activeOrganizationId={activeOrganizationId}
414
- onSwitch={onSwitchOrganization}
415
- organizations={organizations}
416
- t={t}
417
- />
418
- </SidebarHeader>
419
- <SidebarContent>
420
- <NavMain entries={entries} pathname={pathname} t={t} />
421
- <NavDocuments t={t} />
422
- <NavSecondary className="mt-auto" permissionsHref={permissionsHref} t={t} />
423
- </SidebarContent>
424
- <SidebarFooter>
425
- <NavUserMenu locale={locale} locales={locales} onLocale={onLocale} onSignOut={onSignOut} t={t} user={user} />
426
- </SidebarFooter>
427
- </nav>
428
- </NavSidebar>
429
- )
430
- }
431
-
432
- /**
433
- * The kayan index as one sidebar group. An entry is current when the path is its own or a segment
434
- * under it, so no two entries are current at once. `render` and not `asChild`: base-ui composes a
435
- * button into an anchor through `useRender`.
436
- */
437
- export function NavMain({ entries, pathname, t }: { entries: readonly NavEntry[]; pathname?: string; t: Translate }) {
438
- return (
439
- <SidebarGroup>
440
- <SidebarGroupContent className="flex flex-col gap-2">
441
- {/* Quick Create and Inbox are placeholders: this admin has no destination for either. */}
442
- <SidebarMenu>
443
- <SidebarMenuItem className="flex items-center gap-2">
444
- <SidebarMenuButton
445
- tooltip={t('admin.quickCreate', 'Quick Create')}
446
- className="bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground min-w-8 duration-200 ease-linear"
447
- >
448
- <PlusCircle />
449
- <span>{t('admin.quickCreate', 'Quick Create')}</span>
450
- </SidebarMenuButton>
451
- <Button size="icon" className="size-8 group-data-[collapsible=icon]:opacity-0" variant="outline">
452
- <Mail />
453
- <span className="sr-only">{t('admin.inbox', 'Inbox')}</span>
454
- </Button>
455
- </SidebarMenuItem>
456
- </SidebarMenu>
457
- <SidebarMenu>
458
- {entries.map((entry) => {
459
- const isActive = pathname !== undefined && (pathname === entry.href || pathname.startsWith(`${entry.href}/`))
460
- return (
461
- <SidebarMenuItem key={entry.kayan.name}>
462
- <SidebarMenuButton
463
- isActive={isActive}
464
- render={
465
- <a href={entry.href}>
466
- <entry.Icon />
467
- <span>{entry.label}</span>
468
- </a>
469
- }
470
- />
471
- </SidebarMenuItem>
472
- )
473
- })}
474
- </SidebarMenu>
475
- </SidebarGroupContent>
476
- </SidebarGroup>
477
- )
478
- }
479
-
480
- /**
481
- * The documents group. `items` defaults to none, so only the label and the "More" row draw; the
482
- * row markup stays for an app that passes its own documents.
483
- */
484
- export function NavDocuments({
485
- items = [],
486
- t,
487
- }: {
488
- items?: readonly { name: string; url: string; icon: LucideIcon }[]
489
- t: Translate
490
- }) {
491
- const { isMobile } = useSidebar()
492
- return (
493
- <SidebarGroup className="group-data-[collapsible=icon]:hidden">
494
- <SidebarGroupLabel>{t('admin.documents', 'Documents')}</SidebarGroupLabel>
495
- <SidebarMenu>
496
- {items.map((item) => (
497
- <SidebarMenuItem key={item.name}>
498
- <SidebarMenuButton render={<a href={item.url} />}>
499
- <item.icon />
500
- <span>{item.name}</span>
501
- </SidebarMenuButton>
502
- <DropdownMenu>
503
- <DropdownMenuTrigger
504
- render={(props) => (
505
- <SidebarMenuAction showOnHover className="data-[state=open]:bg-accent rounded-sm" {...props}>
506
- <MoreHorizontal />
507
- <span className="sr-only">{t('admin.more', 'More')}</span>
508
- </SidebarMenuAction>
509
- )}
510
- />
511
- <DropdownMenuContent
512
- className="w-24 rounded-lg"
513
- side={isMobile ? 'bottom' : 'right'}
514
- align={isMobile ? 'end' : 'start'}
515
- >
516
- <DropdownMenuItem>
517
- <Folder className="me-2 h-4 w-4" />
518
- <span>{t('admin.open', 'Open')}</span>
519
- </DropdownMenuItem>
520
- <DropdownMenuItem>
521
- <Share2 className="me-2 h-4 w-4" />
522
- <span>{t('admin.share', 'Share')}</span>
523
- </DropdownMenuItem>
524
- <DropdownMenuSeparator />
525
- <DropdownMenuItem variant="destructive">
526
- <Trash2 className="me-2 h-4 w-4" />
527
- <span>{t('admin.trash', 'Move to Trash')}</span>
528
- </DropdownMenuItem>
529
- </DropdownMenuContent>
530
- </DropdownMenu>
531
- </SidebarMenuItem>
532
- ))}
533
- <SidebarMenuItem>
534
- <SidebarMenuButton className="text-sidebar-foreground/70">
535
- <MoreHorizontal className="text-sidebar-foreground/70" />
536
- <span>{t('admin.more', 'More')}</span>
537
- </SidebarMenuButton>
538
- </SidebarMenuItem>
539
- </SidebarMenu>
540
- </SidebarGroup>
541
- )
542
- }
543
-
544
- /**
545
- * The rows at the foot of the sidebar: a permissions row where the host gives `permissionsHref`,
546
- * then Get Help and Search, which point at `#`. Their copy keys are quoted literals at the call
547
- * so the copy sweep can see them.
548
- */
549
- export function NavSecondary({
550
- className,
551
- permissionsHref,
552
- t,
553
- }: {
554
- className?: string
555
- permissionsHref?: string
556
- t: Translate
557
- }) {
558
- return (
559
- <SidebarGroup className={className}>
560
- <SidebarGroupContent>
561
- <SidebarMenu>
562
- {permissionsHref ? (
563
- <SidebarMenuItem>
564
- <SidebarMenuButton render={<a href={permissionsHref} />}>
565
- <ShieldCheck />
566
- <span>{t('admin.permissions', 'Permissions')}</span>
567
- </SidebarMenuButton>
568
- </SidebarMenuItem>
569
- ) : null}
570
- <SidebarMenuItem>
571
- <SidebarMenuButton render={<a href="#" />}>
572
- <HelpCircle />
573
- <span>{t('admin.getHelp', 'Get Help')}</span>
574
- </SidebarMenuButton>
575
- </SidebarMenuItem>
576
- <SidebarMenuItem>
577
- <SidebarMenuButton render={<a href="#" />}>
578
- <Search />
579
- <span>{t('admin.search', 'Search')}</span>
580
- </SidebarMenuButton>
581
- </SidebarMenuItem>
582
- </SidebarMenu>
583
- </SidebarGroupContent>
584
- </SidebarGroup>
585
- )
586
- }
587
-
588
- const initialsOf = (name: string) =>
589
- name
590
- .split(/\s+/)
591
- .filter(Boolean)
592
- .slice(0, 2)
593
- .map((word) => word[0]!.toUpperCase())
594
- .join('') || '?'
595
-
596
- /**
597
- * The organization switcher: a dropdown listing every organization as a radio item, the active
598
- * one checked.
599
- */
600
- export function OrganizationSwitcher({
601
- activeOrganizationId,
602
- onSwitch,
603
- organizations,
604
- t,
605
- }: {
606
- activeOrganizationId?: string | null
607
- onSwitch: (id: string) => void
608
- organizations: readonly NavOrganization[]
609
- t: Translate
610
- }) {
611
- const { isMobile } = useSidebar()
612
- const active = organizations.find((one) => one.id === activeOrganizationId) ?? organizations[0]
613
- /** An organization's name, with its slug beside it where another listed shares the name. */
614
- const named = (one: NavOrganization) =>
615
- one.slug && organizations.some((other) => other.id !== one.id && other.name === one.name) ? (
616
- <>
617
- {one.name} <span dir="ltr" data-slug="" className="text-muted-foreground text-xs font-normal">{one.slug}</span>
618
- </>
619
- ) : (
620
- one.name
621
- )
622
- const badge = (
623
- <>
624
- <div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
625
- <GalleryVerticalEnd className="size-4" />
626
- </div>
627
- <div className="grid flex-1 text-start text-sm leading-tight">
628
- <span className="truncate font-semibold text-foreground">
629
- {active ? named(active) : t('admin.noOrganization', 'No organization')}
630
- </span>
631
- <span className="truncate text-xs">
632
- {active ? t('admin.organization', 'Organization') : ''}
633
- </span>
634
- </div>
635
- </>
636
- )
637
- // One organization or none is nothing to switch between, so it is drawn as a plain name.
638
- if (organizations.length < 2) {
639
- return (
640
- <SidebarMenu>
641
- <SidebarMenuItem data-organization="">
642
- <div className="flex h-12 items-center gap-2 p-2">{badge}</div>
643
- </SidebarMenuItem>
644
- </SidebarMenu>
645
- )
646
- }
647
- return (
648
- <SidebarMenu>
649
- <SidebarMenuItem>
650
- <DropdownMenu>
651
- <DropdownMenuTrigger
652
- render={(props, { open }) => (
653
- <SidebarMenuButton
654
- size="lg"
655
- data-state={open ? 'open' : 'closed'}
656
- className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
657
- {...props}
658
- >
659
- {badge}
660
- <ChevronsUpDown className="ms-auto" />
661
- </SidebarMenuButton>
662
- )}
663
- />
664
- <DropdownMenuContent
665
- className="min-w-56 rounded-lg max-sm:**:data-[slot$=item]:min-h-11"
666
- align="start"
667
- side={isMobile ? 'bottom' : 'right'}
668
- sideOffset={4}
669
- >
670
- <DropdownMenuGroup>
671
- <DropdownMenuLabel className="text-xs text-muted-foreground">
672
- {t('admin.organizations', 'Organizations')}
673
- </DropdownMenuLabel>
674
- {/* Radio items, so the current organization is `aria-checked`; `closeOnClick`
675
- because a radio item does not close the menu by itself. */}
676
- <DropdownMenuRadioGroup value={active?.id} onValueChange={onSwitch}>
677
- {organizations.map((one) => (
678
- <DropdownMenuRadioItem key={one.id} value={one.id} closeOnClick className={radioRow}>
679
- {named(one)}
680
- </DropdownMenuRadioItem>
681
- ))}
682
- </DropdownMenuRadioGroup>
683
- </DropdownMenuGroup>
684
- </DropdownMenuContent>
685
- </DropdownMenu>
686
- </SidebarMenuItem>
687
- </SidebarMenu>
688
- )
689
- }
690
-
691
- /**
692
- * The user menu: avatar, name and email, a link to each of the viewer's other screens, a locale
693
- * group, a theme group and sign-out. The locale and theme groups draw only where they can work.
694
- */
695
- export function NavUserMenu({
696
- locale,
697
- locales,
698
- onLocale,
699
- onSignOut,
700
- t,
701
- themes = defaultThemes,
702
- user,
703
- }: {
704
- locale?: string
705
- locales?: readonly string[]
706
- onLocale?: (locale: string) => void
707
- onSignOut: () => void
708
- t: Translate
709
- /**
710
- * Which themes the menu offers: `defaultThemes` unless given, none for `[]`. Drawn only where a
711
- * `ThemeProvider` is above. Rows keep the order light, dark, system.
712
- */
713
- themes?: readonly Theme[]
714
- user: NavUser
715
- }) {
716
- const { isMobile } = useSidebar()
717
- const provided = useTheme()
718
- const screens = user.screens
719
- return (
720
- <SidebarMenu>
721
- <SidebarMenuItem>
722
- <DropdownMenu>
723
- <DropdownMenuTrigger
724
- render={(props, { open }) => (
725
- <SidebarMenuButton
726
- data-state={open ? 'open' : 'closed'}
727
- size="lg"
728
- className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
729
- {...props}
730
- >
731
- <Avatar className="h-8 w-8 rounded-lg grayscale">
732
- <AvatarFallback className="rounded-lg">{initialsOf(user.name)}</AvatarFallback>
733
- </Avatar>
734
- <div className="grid flex-1 text-start text-sm leading-tight">
735
- <span className="truncate font-medium">{user.name}</span>
736
- <span className="text-muted-foreground truncate text-xs">{user.email}</span>
737
- </div>
738
- <ChevronsUpDown className="ms-auto size-4" />
739
- </SidebarMenuButton>
740
- )}
741
- />
742
- <DropdownMenuContent
743
- className="min-w-56 rounded-lg w-56 max-sm:**:data-[slot$=item]:min-h-11"
744
- side={isMobile ? 'bottom' : 'top'}
745
- align="end"
746
- sideOffset={4}
747
- >
748
- <DropdownMenuGroup>
749
- <DropdownMenuLabel className="p-0 font-normal">
750
- <div className="flex items-center gap-2 px-1 py-1.5 text-start text-sm">
751
- <Avatar className="h-8 w-8 rounded-lg">
752
- <AvatarFallback className="rounded-lg">{initialsOf(user.name)}</AvatarFallback>
753
- </Avatar>
754
- <div className="grid flex-1 text-start text-sm leading-tight">
755
- <span className="truncate font-medium">{user.name}</span>
756
- <span className="text-muted-foreground truncate text-xs">{user.email}</span>
757
- </div>
758
- </div>
759
- </DropdownMenuLabel>
760
- </DropdownMenuGroup>
761
- {screens && screens.length > 0 ? (
762
- <>
763
- <DropdownMenuSeparator />
764
- <DropdownMenuGroup>
765
- {screens.map((one) => (
766
- <DropdownMenuItem key={one.name} className="max-sm:min-h-11" render={<a href={one.path} />}>
767
- <LayoutGrid className="me-2 h-4 w-4" />
768
- {t(`screens.${one.name}`, one.name) ?? one.name}
769
- </DropdownMenuItem>
770
- ))}
771
- </DropdownMenuGroup>
772
- </>
773
- ) : null}
774
- {locales && locales.length > 0 && onLocale ? (
775
- <>
776
- <DropdownMenuSeparator />
777
- <DropdownMenuGroup>
778
- {localeEntries(locales, locale ?? '').map((entry) => (
779
- <DropdownMenuItem key={entry.locale} className="max-sm:min-h-11" onClick={() => onLocale(entry.locale)}>
780
- <Languages className="me-2 h-4 w-4" />
781
- {entry.label}
782
- {entry.active ? <Check className="ms-auto h-4 w-4" /> : null}
783
- </DropdownMenuItem>
784
- ))}
785
- </DropdownMenuGroup>
786
- </>
787
- ) : null}
788
- {provided && themes.length > 0 ? (
789
- <>
790
- <DropdownMenuSeparator />
791
- <DropdownMenuGroup>
792
- <DropdownMenuLabel className="text-xs text-muted-foreground">{t('admin.theme', 'Toggle theme')}</DropdownMenuLabel>
793
- <DropdownMenuRadioGroup value={provided.theme} onValueChange={(next) => provided.setTheme(next as Theme)}>
794
- {(
795
- [
796
- ['light', Sun, t('admin.themeLight', 'Light')],
797
- ['dark', Moon, t('admin.themeDark', 'Dark')],
798
- ['system', Monitor, t('admin.themeSystem', 'System')],
799
- ] as const
800
- )
801
- .filter(([value]) => themes.includes(value))
802
- .map(([value, Icon, label]) => (
803
- <DropdownMenuRadioItem key={value} value={value} closeOnClick className={radioRow}>
804
- <Icon />
805
- {label}
806
- </DropdownMenuRadioItem>
807
- ))}
808
- </DropdownMenuRadioGroup>
809
- </DropdownMenuGroup>
810
- </>
811
- ) : null}
812
- <DropdownMenuSeparator />
813
- <DropdownMenuItem className="max-sm:min-h-11" onClick={onSignOut}>
814
- <LogOut className="me-2 h-4 w-4" />
815
- {t('admin.signOut', 'Sign out')}
816
- </DropdownMenuItem>
817
- </DropdownMenuContent>
818
- </DropdownMenu>
819
- </SidebarMenuItem>
820
- </SidebarMenu>
821
- )
822
- }
823
-
824
- /**
825
- * `Sidebar`, except that on a phone its sheet is titled and described in the page's language;
826
- * the primitive's sheet is English only, which a screen reader announces on an Arabic page.
827
- * It has no close button: the overlay and Escape close it.
828
- */
829
- export function NavSidebar({ children, side = 'left', t, ...props }: ComponentProps<typeof Sidebar> & { t: Translate }) {
830
- const { isMobile, openMobile, setOpenMobile } = useSidebar()
831
- if (!isMobile) return <Sidebar {...props} side={side} collapsible="offcanvas">{children}</Sidebar>
832
- return (
833
- <Sheet open={openMobile} onOpenChange={setOpenMobile}>
834
- <SheetContent
835
- side={side}
836
- showCloseButton={false}
837
- data-sidebar="sidebar"
838
- data-slot="sidebar"
839
- data-mobile="true"
840
- className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0"
841
- style={{ '--sidebar-width': '18rem' } as CSSProperties}
842
- >
843
- <SheetHeader className="sr-only">
844
- <SheetTitle>{t('admin.navigation', 'Navigation')}</SheetTitle>
845
- <SheetDescription>{t('admin.navigationAbout', 'The pages you can open.')}</SheetDescription>
846
- </SheetHeader>
847
- <div className="flex h-full w-full flex-col">{children}</div>
848
- </SheetContent>
849
- </Sheet>
850
- )
851
- }
852
-
853
- /**
854
- * The header: sidebar trigger, the screen's title and the theme toggle. The trigger's
855
- * `aria-label` is its only name, so an Arabic page carries no English word for it.
856
- */
857
- export function SiteHeader({ t, title }: { t: Translate; title?: string }) {
858
- return (
859
- <header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
860
- <div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
861
- <SidebarTrigger aria-label={t('admin.toggleSidebar', 'Toggle Sidebar')} className="-ms-1" />
862
- <Separator orientation="vertical" className="mx-2 data-[orientation=vertical]:h-4" />
863
- <h1 className="text-base font-medium">{title ?? t('admin.nav', 'Admin')}</h1>
864
- <div className="ms-auto flex items-center gap-2">
865
- <ModeToggle t={t} />
866
- </div>
867
- </div>
868
- </header>
869
- )
870
- }
871
- // mithaq-admin b06d8d335d306c07556f8f2b81fba20ba7ded198 b1ad44964867b0d65635652733a4821b190a720b1e283fc2f4aab3f82993effa