create-mithaq 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSES/openstatus-time-picker.txt +21 -0
- package/index.js +457 -63
- package/package.json +1 -1
- package/template/apps/{asas-platform → asas-backoffice}/package.json +5 -3
- package/template/apps/{asas-platform → asas-backoffice}/src/api.ts +1 -1
- package/template/apps/{asas-platform → asas-backoffice}/src/auth-frame.tsx +12 -12
- package/template/apps/asas-backoffice/src/components.ts +35 -0
- package/template/apps/asas-backoffice/src/first-user.ts +14 -0
- package/template/apps/{asas-platform → asas-backoffice}/src/links.ts +1 -1
- package/template/apps/{asas-platform → asas-backoffice}/src/locale-switch.tsx +7 -5
- package/template/apps/asas-backoffice/src/no-organization.tsx +5 -0
- package/template/apps/asas-backoffice/src/redirect.ts +1 -0
- package/template/apps/{asas-platform → asas-backoffice}/src/request-headers.ts +1 -1
- package/template/apps/asas-backoffice/src/routeTree.gen.ts +206 -0
- package/template/apps/asas-backoffice/src/router.tsx +18 -0
- package/template/apps/asas-backoffice/src/routes/(screens)/backoffice/files.ts +10 -0
- package/template/apps/{asas-platform/src/routes/(screens)/platform → asas-backoffice/src/routes/(screens)/backoffice}/screen.json +426 -64
- package/template/apps/{asas-platform/src/routes/(screens)/platform → asas-backoffice/src/routes/(screens)/backoffice}/types.ts +18 -0
- package/template/apps/{asas-platform/src/routes/(screens)/platform.$.tsx → asas-backoffice/src/routes/(screens)/backoffice.$.tsx} +4 -4
- package/template/apps/{asas-platform/src/routes/(screens)/platform.index.tsx → asas-backoffice/src/routes/(screens)/backoffice.index.tsx} +3 -3
- package/template/apps/{asas-platform/src/routes/(screens)/platform.tsx → asas-backoffice/src/routes/(screens)/backoffice.tsx} +3 -3
- package/template/apps/{asas-platform → asas-backoffice}/src/routes/__root.tsx +2 -2
- package/template/apps/asas-backoffice/src/routes/api/$.ts +34 -0
- package/template/apps/asas-backoffice/src/routes/auth.create-first-user.tsx +113 -0
- package/template/apps/{asas-platform → asas-backoffice}/src/routes/auth.sign-in.tsx +9 -4
- package/template/apps/{asas-platform → asas-backoffice}/src/routes/unauthorized.tsx +24 -12
- package/template/apps/{asas-platform → asas-backoffice}/src/screen-form.tsx +5 -3
- package/template/apps/{asas-platform → asas-backoffice}/src/screen-list.tsx +6 -5
- package/template/apps/{asas-platform → asas-backoffice}/src/screen-names.ts +1 -1
- package/template/apps/{asas-platform → asas-backoffice}/src/screens.tsx +46 -26
- package/template/apps/asas-backoffice/src/server.ts +30 -0
- package/template/apps/{asas-platform → asas-backoffice}/src/slide.ts +1 -1
- package/template/apps/{asas-platform → asas-backoffice}/src/start.ts +1 -1
- package/template/apps/{asas-platform → asas-backoffice}/src/styles.css +9 -7
- package/template/apps/{asas-platform → asas-backoffice}/tsconfig.json +2 -2
- package/template/apps/{asas-platform → asas-backoffice}/vite.config.ts +7 -1
- package/template/apps/asas-tanstack/package.json +4 -2
- package/template/apps/asas-tanstack/src/api.ts +1 -1
- package/template/apps/asas-tanstack/src/auth-frame.tsx +12 -12
- package/template/apps/asas-tanstack/src/choose-password.tsx +150 -0
- package/template/apps/asas-tanstack/src/components.ts +35 -0
- package/template/apps/asas-tanstack/src/copy.ts +416 -79
- package/template/apps/asas-tanstack/src/first-run.ts +16 -0
- package/template/apps/asas-tanstack/src/links.ts +1 -1
- package/template/apps/asas-tanstack/src/locale-switch.tsx +7 -5
- package/template/apps/asas-tanstack/src/no-organization.tsx +108 -0
- package/template/apps/asas-tanstack/src/redirect.ts +20 -1
- package/template/apps/asas-tanstack/src/request-headers.ts +1 -1
- package/template/apps/asas-tanstack/src/routeTree.gen.ts +42 -0
- package/template/apps/asas-tanstack/src/router.tsx +12 -1
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/files.ts +9 -2
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/screen.json +475 -116
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/types.ts +4 -2
- package/template/apps/asas-tanstack/src/routes/(screens)/portal/files.ts +7 -2
- package/template/apps/asas-tanstack/src/routes/(screens)/portal/screen.json +83 -319
- package/template/apps/asas-tanstack/src/routes/(screens)/portal/types.ts +3 -1
- package/template/apps/asas-tanstack/src/routes/__root.tsx +1 -1
- package/template/apps/asas-tanstack/src/routes/auth.accept-invitation.tsx +14 -7
- package/template/apps/asas-tanstack/src/routes/auth.first-run.tsx +114 -0
- package/template/apps/asas-tanstack/src/routes/auth.forgot-password.tsx +2 -2
- package/template/apps/asas-tanstack/src/routes/auth.handoff.ts +5 -5
- package/template/apps/asas-tanstack/src/routes/auth.reset-password.tsx +7 -100
- package/template/apps/asas-tanstack/src/routes/auth.set-password.tsx +16 -0
- package/template/apps/asas-tanstack/src/routes/auth.sign-in.tsx +27 -15
- package/template/apps/asas-tanstack/src/routes/auth.sign-up.tsx +59 -38
- package/template/apps/asas-tanstack/src/routes/index.tsx +18 -14
- package/template/apps/asas-tanstack/src/routes/unauthorized.tsx +24 -12
- package/template/apps/asas-tanstack/src/screen-form.tsx +5 -3
- package/template/apps/asas-tanstack/src/screen-list.tsx +6 -5
- package/template/apps/asas-tanstack/src/screen-names.ts +1 -1
- package/template/apps/asas-tanstack/src/screens.tsx +46 -26
- package/template/apps/asas-tanstack/src/server.ts +10 -0
- package/template/apps/asas-tanstack/src/slide.ts +1 -1
- package/template/apps/asas-tanstack/src/styles.css +7 -3
- package/template/apps/asas-tanstack/tsconfig.json +2 -2
- package/template/apps/asas-tanstack/vite.config.ts +7 -1
- package/template/package.json +3 -3
- package/template/templates/asas/.claude/skills/add-field/SKILL.md +27 -0
- package/template/templates/asas/.claude/skills/add-kayan/SKILL.md +26 -0
- package/template/templates/asas/.claude/skills/eject-admin-piece/SKILL.md +28 -0
- package/template/templates/asas/.claude/skills/upgrade-mithaq/SKILL.md +35 -0
- package/template/templates/asas/.env.example +22 -5
- package/template/templates/asas/AGENTS.md +101 -0
- package/template/templates/asas/CLAUDE.md +1 -0
- package/template/templates/asas/README.md +1056 -0
- package/template/templates/asas/package.json +19 -4
- package/template/templates/asas/src/asas.ts +81 -56
- package/template/templates/asas/src/{platform-http.ts → backoffice-http.ts} +48 -27
- package/template/templates/asas/src/dev-setup.ts +112 -0
- package/template/templates/asas/src/local-database.ts +22 -0
- package/template/templates/asas/src/messages/account.json +129 -103
- package/template/templates/asas/src/messages/auditLog.json +223 -105
- package/template/templates/asas/src/messages/backofficeLog.json +132 -0
- package/template/templates/asas/src/messages/courses.json +256 -214
- package/template/templates/asas/src/messages/enrollments.json +45 -27
- package/template/templates/asas/src/messages/invitation.json +72 -56
- package/template/templates/asas/src/messages/localUsers.json +34 -28
- package/template/templates/asas/src/messages/media.json +31 -21
- package/template/templates/asas/src/messages/member.json +59 -39
- package/template/templates/asas/src/messages/memberRoles.json +37 -31
- package/template/templates/asas/src/messages/mithaq.json +206 -162
- package/template/templates/asas/src/messages/organization.json +63 -41
- package/template/templates/asas/src/messages/organizationRole.json +59 -47
- package/template/templates/asas/src/messages/roles.json +59 -45
- package/template/templates/asas/src/messages/session.json +99 -79
- package/template/templates/asas/src/messages/sessions.json +110 -80
- package/template/templates/asas/src/messages/settings.json +51 -53
- package/template/templates/asas/src/messages/tags.json +31 -21
- package/template/templates/asas/src/messages/user.json +109 -87
- package/template/templates/asas/src/messages/verification.json +59 -47
- package/template/templates/asas/src/migrations/20261014221355_tags_restrict/migration.sql +1 -0
- package/template/templates/asas/src/migrations/20261014221355_tags_restrict/snapshot.json +3881 -0
- package/template/templates/asas/src/migrations/20261014222218_one_seat_per_session/migration.sql +1 -0
- package/template/templates/asas/src/migrations/20261014222218_one_seat_per_session/snapshot.json +3894 -0
- package/template/templates/asas/src/migrations/20261014223522_session_archive_reason/migration.sql +1 -0
- package/template/templates/asas/src/migrations/20261014223522_session_archive_reason/snapshot.json +3907 -0
- package/template/templates/asas/src/migrations/20261014225728_course_archive_by_operation/migration.sql +1 -0
- package/template/templates/asas/src/migrations/20261014225728_course_archive_by_operation/snapshot.json +3907 -0
- package/template/templates/asas/src/migrations/20261015042444_organization_names/migration.sql +1 -0
- package/template/templates/asas/src/migrations/20261015042444_organization_names/snapshot.json +3920 -0
- package/template/templates/asas/src/migrations/20261015053212_member_last_seen/migration.sql +1 -0
- package/template/templates/asas/src/migrations/20261015053212_member_last_seen/snapshot.json +3933 -0
- package/template/templates/asas/src/migrations/20261015093203_activity_and_staff_history/migration.sql +15 -0
- package/template/templates/asas/src/migrations/20261015093203_activity_and_staff_history/snapshot.json +4113 -0
- package/template/templates/asas/src/migrations/20261015095752_portal/migration.sql +2 -0
- package/template/templates/asas/src/migrations/20261015095752_portal/snapshot.json +4113 -0
- package/template/templates/asas/src/migrations/20261015135529_better_auth_indexes/migration.sql +9 -0
- package/template/templates/asas/src/migrations/20261015135529_better_auth_indexes/snapshot.json +4302 -0
- package/template/templates/asas/src/migrations/20261015162438_audit_changes_json/migration.sql +27 -0
- package/template/templates/asas/src/migrations/20261015162438_audit_changes_json/snapshot.json +4289 -0
- package/template/templates/asas/src/migrations/schema.ts +1 -0
- package/template/templates/asas/src/mithaq.ts +4 -406
- package/template/templates/asas/src/organizations.ts +3 -0
- package/template/templates/asas/src/seed.ts +147 -45
- package/template/templates/asas/src/{ui-platform → ui-backoffice}/home.tsx +9 -9
- package/template/templates/asas/src/ui-backoffice/organization-actions.tsx +121 -0
- package/template/templates/asas/src/{ui-platform → ui-backoffice}/record-page.tsx +4 -4
- package/template/templates/asas/src/{ui-platform → ui-backoffice}/role-cells.tsx +13 -6
- package/template/templates/asas/src/{ui-platform → ui-backoffice}/user-actions.tsx +46 -48
- package/template/templates/asas/src/ui-dashboard/dashboard.tsx +10 -4
- package/template/templates/asas/src/ui-dashboard/home.tsx +73 -0
- package/template/templates/asas/src/ui-dashboard/members-card.tsx +147 -0
- package/template/templates/asas/src/ui-dashboard/members.tsx +637 -438
- package/template/templates/asas/src/ui-dashboard/versions.tsx +22 -8
- package/template/templates/asas/src/ui-portal/courses-home.tsx +96 -0
- package/template/templates/asas/src/ui-portal/home.tsx +9 -13
- package/template/templates/features/audit-log/audit-log/audit-log.ts +284 -0
- package/template/templates/features/audit-log/backoffice-log/backoffice-log.ts +67 -0
- package/template/templates/features/audit-log/index.ts +4 -0
- package/template/templates/features/audit-log/package.json +13 -0
- package/template/templates/features/auth/index.ts +11 -0
- package/template/templates/{asas/src → features/auth}/local-users/local-users.ts +7 -7
- package/template/templates/{auth → features/auth}/package.json +2 -3
- package/template/templates/{asas/src/kayan → features}/auth/user/account.access.ts +2 -2
- package/template/templates/{asas/src/kayan → features}/auth/user/account.ts +2 -2
- package/template/templates/{asas/src/kayan → features}/auth/user/invitation.access.ts +1 -1
- package/template/templates/{asas/src/kayan → features}/auth/user/invitation.ts +3 -3
- package/template/templates/{asas/src/kayan → features}/auth/user/member.access.ts +4 -4
- package/template/templates/{asas/src/kayan → features}/auth/user/member.screens.ts +5 -5
- package/template/templates/{asas/src/kayan → features}/auth/user/member.ts +4 -3
- package/template/templates/{asas/src/kayan → features}/auth/user/organization.access.ts +6 -6
- package/template/templates/{asas/src/kayan → features}/auth/user/organization.screens.ts +6 -6
- package/template/templates/{asas/src/kayan → features}/auth/user/organization.ts +2 -1
- package/template/templates/{asas/src/kayan → features}/auth/user/organizationRole.access.ts +2 -2
- package/template/templates/{asas/src/kayan → features}/auth/user/organizationRole.ts +3 -3
- package/template/templates/{asas/src/kayan → features}/auth/user/session.access.ts +2 -2
- package/template/templates/{asas/src/kayan → features}/auth/user/session.ts +2 -2
- package/template/templates/{asas/src/kayan → features}/auth/user/user.access.ts +8 -7
- package/template/templates/{asas/src/kayan → features}/auth/user/user.screens.ts +9 -9
- package/template/templates/{asas/src/kayan → features}/auth/user/user.ts +2 -2
- package/template/templates/{asas/src/kayan → features}/auth/user/verification.access.ts +2 -2
- package/template/templates/{asas/src/kayan → features}/auth/user/verification.ts +2 -2
- package/template/templates/{asas/src → features/courses}/courses/courses.ts +122 -133
- package/template/templates/features/courses/courses/cover-reads.ts +33 -0
- package/template/templates/features/courses/courses/ui/archived-badge.tsx +9 -0
- package/template/templates/{asas/src → features/courses}/courses/ui/fee-cell.tsx +1 -1
- package/template/templates/{asas/src → features/courses}/courses/ui/status-cell.tsx +5 -3
- package/template/templates/features/courses/courses/ui/unpublish-button.tsx +15 -0
- package/template/templates/features/courses/domain/archive.ts +39 -0
- package/template/templates/features/courses/domain/on-offer.ts +6 -0
- package/template/templates/features/courses/enrollments/enrollments.ts +309 -0
- package/template/templates/{asas/src → features/courses}/enrollments/ui/session-cell.tsx +17 -7
- package/template/templates/{asas/src → features/courses/enrollments}/ui/session-labels.tsx +3 -3
- package/template/templates/features/courses/index.ts +8 -0
- package/template/templates/features/courses/package.json +24 -0
- package/template/templates/features/courses/portal/ui/catalogue.tsx +86 -0
- package/template/templates/features/courses/portal/ui/course.tsx +176 -0
- package/template/templates/features/courses/portal/ui/enrollments.tsx +170 -0
- package/template/templates/features/courses/portal/ui/parts.tsx +95 -0
- package/template/templates/features/courses/portal/ui/reads.ts +76 -0
- package/template/templates/features/courses/portal/ui/rules.ts +87 -0
- package/template/templates/features/courses/portal/ui/shell.tsx +194 -0
- package/template/templates/features/courses/sessions/seats.ts +42 -0
- package/template/templates/{asas/src → features/courses}/sessions/sessions.ts +45 -14
- package/template/templates/features/media/index.ts +10 -0
- package/template/templates/{asas/src → features/media}/media/media.ts +34 -40
- package/template/templates/{asas/src → features/media}/media/ui/preview.tsx +2 -2
- package/template/templates/features/media/package.json +19 -0
- package/template/templates/features/roles/index.ts +4 -0
- package/template/templates/{asas/src → features/roles}/member-roles/member-roles.ts +15 -15
- package/template/templates/features/roles/package.json +21 -0
- package/template/templates/{asas/src → features/roles}/roles/roles.ts +23 -23
- package/template/templates/{asas/src → features/roles}/roles/ui/role-form.tsx +65 -24
- package/template/templates/{asas/src → features/roles}/roles/ui/roles.tsx +49 -25
- package/template/templates/features/settings/index.ts +3 -0
- package/template/templates/features/settings/package.json +13 -0
- package/template/templates/{asas/src → features/settings}/settings/settings.ts +12 -17
- package/template/templates/features/tags/index.ts +3 -0
- package/template/templates/features/tags/package.json +14 -0
- package/template/templates/{asas/src → features/tags}/tags/tags.ts +15 -15
- package/template/templates/mithaq/package.json +33 -0
- package/template/templates/mithaq/src/mail/index.ts +32 -0
- package/template/templates/mithaq/src/mail/mailers.ts +36 -0
- package/template/templates/{asas/src/audit-log/sink.ts → mithaq/src/mithaq/audit-sink.ts} +31 -9
- package/template/templates/{auth/src/platform-roles.ts → mithaq/src/mithaq/auth/backoffice-roles.ts} +44 -23
- package/template/templates/mithaq/src/mithaq/auth/index.ts +1859 -0
- package/template/templates/mithaq/src/mithaq/auth/organizations.ts +80 -0
- package/template/templates/{asas/src → mithaq/src/mithaq}/pages.ts +16 -7
- package/template/templates/mithaq/src/mithaq/rich-text-nodes.ts +19 -0
- package/template/templates/mithaq/src/mithaq.ts +593 -0
- package/template/templates/mithaq/src/roles/default-member-roles.ts +29 -0
- package/template/templates/{asas → mithaq}/src/roles/grants.ts +1 -1
- package/template/templates/mithaq/src/roles/member-access.ts +11 -0
- package/template/templates/{asas/src → mithaq/src/roles}/statements.ts +6 -4
- package/template/templates/mithaq/src/roles/system-roles.ts +16 -0
- package/template/templates/{asas/src → mithaq/src/roles}/tenant-access.ts +96 -57
- package/template/templates/{asas/src → mithaq/src/roles}/tenant-roles.ts +84 -25
- package/template/templates/{asas → mithaq}/src/steps/expected-updated-at.ts +2 -2
- package/template/templates/{asas → mithaq}/src/storage/index.ts +3 -1
- package/template/templates/mithaq/src/ui/activity-page.tsx +33 -0
- package/template/templates/mithaq/src/ui/activity.tsx +527 -0
- package/template/templates/{asas → mithaq}/src/ui/app-shell.tsx +27 -19
- package/template/templates/mithaq/src/ui/rich-text-nodes.tsx +33 -0
- package/template/templates/{asas → mithaq}/src/ui/screen-nav.tsx +1 -1
- package/template/templates/{asas → mithaq}/src/ui/top-shell.tsx +8 -8
- package/template/apps/asas-platform/src/components/mithaq/cells.tsx +0 -112
- package/template/apps/asas-platform/src/components/mithaq/controls.tsx +0 -650
- package/template/apps/asas-platform/src/components/mithaq/field-shell.tsx +0 -53
- package/template/apps/asas-platform/src/components/mithaq/filter-operators.ts +0 -108
- package/template/apps/asas-platform/src/components/mithaq/form.tsx +0 -1467
- package/template/apps/asas-platform/src/components/mithaq/list-columns.tsx +0 -108
- package/template/apps/asas-platform/src/components/mithaq/list-filters.tsx +0 -368
- package/template/apps/asas-platform/src/components/mithaq/list-pagination.tsx +0 -153
- package/template/apps/asas-platform/src/components/mithaq/list-query.ts +0 -432
- package/template/apps/asas-platform/src/components/mithaq/list-selection.tsx +0 -74
- package/template/apps/asas-platform/src/components/mithaq/list.tsx +0 -865
- package/template/apps/asas-platform/src/components/mithaq/nav.tsx +0 -871
- package/template/apps/asas-platform/src/components/mithaq/nodes.tsx +0 -1024
- package/template/apps/asas-platform/src/components/mithaq/relationship-cell.tsx +0 -127
- package/template/apps/asas-platform/src/components/mithaq/relationship-control.tsx +0 -400
- package/template/apps/asas-platform/src/components/mithaq/rich-text-nodes.ts +0 -6
- package/template/apps/asas-platform/src/components/mithaq/rich-text.tsx +0 -1083
- package/template/apps/asas-platform/src/components/mithaq/upload.tsx +0 -686
- package/template/apps/asas-platform/src/components/ui/drawer.tsx +0 -216
- package/template/apps/asas-platform/src/redirect.ts +0 -1
- package/template/apps/asas-platform/src/routeTree.gen.ts +0 -181
- package/template/apps/asas-platform/src/router.tsx +0 -7
- package/template/apps/asas-platform/src/routes/(screens)/platform/files.ts +0 -4
- package/template/apps/asas-platform/src/routes/api/$.ts +0 -30
- package/template/apps/asas-platform/src/server.ts +0 -20
- package/template/apps/asas-tanstack/src/components/mithaq/cells.tsx +0 -112
- package/template/apps/asas-tanstack/src/components/mithaq/controls.tsx +0 -650
- package/template/apps/asas-tanstack/src/components/mithaq/field-shell.tsx +0 -53
- package/template/apps/asas-tanstack/src/components/mithaq/filter-operators.ts +0 -108
- package/template/apps/asas-tanstack/src/components/mithaq/form.tsx +0 -1467
- package/template/apps/asas-tanstack/src/components/mithaq/list-columns.tsx +0 -108
- package/template/apps/asas-tanstack/src/components/mithaq/list-filters.tsx +0 -368
- package/template/apps/asas-tanstack/src/components/mithaq/list-pagination.tsx +0 -153
- package/template/apps/asas-tanstack/src/components/mithaq/list-query.ts +0 -432
- package/template/apps/asas-tanstack/src/components/mithaq/list-selection.tsx +0 -74
- package/template/apps/asas-tanstack/src/components/mithaq/list.tsx +0 -865
- package/template/apps/asas-tanstack/src/components/mithaq/nav.tsx +0 -871
- package/template/apps/asas-tanstack/src/components/mithaq/nodes.tsx +0 -1024
- package/template/apps/asas-tanstack/src/components/mithaq/relationship-cell.tsx +0 -127
- package/template/apps/asas-tanstack/src/components/mithaq/relationship-control.tsx +0 -400
- package/template/apps/asas-tanstack/src/components/mithaq/rich-text-nodes.ts +0 -6
- package/template/apps/asas-tanstack/src/components/mithaq/rich-text.tsx +0 -1083
- package/template/apps/asas-tanstack/src/components/mithaq/upload.tsx +0 -686
- package/template/apps/asas-tanstack/src/components/ui/drawer.tsx +0 -215
- package/template/templates/asas/src/audit-log/audit-log.ts +0 -187
- package/template/templates/asas/src/audit-log/ui/activity.tsx +0 -243
- package/template/templates/asas/src/courses/ui/archive-button.tsx +0 -25
- package/template/templates/asas/src/courses/ui/no-courses-on-offer.tsx +0 -11
- package/template/templates/asas/src/courses/ui/on-offer.ts +0 -6
- package/template/templates/asas/src/enrollments/enrollments.ts +0 -137
- package/template/templates/asas/src/rich-text-nodes.ts +0 -11
- package/template/templates/asas/src/roles.ts +0 -16
- package/template/templates/asas/src/ui/rich-text-nodes.tsx +0 -15
- package/template/templates/asas/src/ui-platform/organization-actions.tsx +0 -114
- package/template/templates/auth/src/index.ts +0 -690
- /package/template/apps/{asas-platform → asas-backoffice}/components.json +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/gitignore +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/public/favicon.svg +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/accordion.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/alert-dialog.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/avatar.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/badge.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/breadcrumb.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/button-group.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/button.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/calendar.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/card.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/chart.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/checkbox.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/collapsible.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/combobox.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/dialog.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/direction.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/directional-icon.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/dropdown-menu.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/field.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/input-group.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/input-otp.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/input.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/label.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/popover.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/radio-group.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/resizable.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/scroll-area.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/select.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/separator.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/sheet.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/sidebar.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/skeleton.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/slider.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/spinner.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/switch.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/table.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/tabs.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/textarea.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/toggle-group.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/toggle.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/tooltip.tsx +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/copy.ts +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/hooks/use-mobile.ts +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/lib/utils.ts +0 -0
- /package/template/apps/{asas-platform → asas-backoffice}/src/theme.ts +0 -0
- /package/template/templates/{asas/src/kayan → features}/auth/user/index.ts +0 -0
- /package/template/templates/{asas/src → features/courses}/courses/ui/no-courses.tsx +0 -0
- /package/template/templates/{asas → mithaq}/components.json +0 -0
- /package/template/templates/{asas → mithaq}/src/storage/defaults.ts +0 -0
- /package/template/templates/{asas → mithaq}/src/storage/local.ts +0 -0
- /package/template/templates/{asas → mithaq}/src/storage/r2.ts +0 -0
- /package/template/templates/{asas → mithaq}/src/storage/s3.ts +0 -0
- /package/template/templates/{asas → mithaq}/src/storage/uploadthing.ts +0 -0
- /package/template/templates/{asas → mithaq}/src/ui/kpi-card.tsx +0 -0
- /package/template/templates/{asas → mithaq}/src/ui/record-page.tsx +0 -0
- /package/template/templates/{asas → mithaq}/src/ui/table-page.tsx +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Link, createFileRoute } from '@tanstack/react-router'
|
|
2
|
-
import {
|
|
2
|
+
import { organizations, signUp } from 'asas/organizations'
|
|
3
|
+
import { LayoutDashboard, Languages, ShieldCheck } from 'lucide-react'
|
|
3
4
|
import { Button } from '@/components/ui/button'
|
|
4
5
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
5
6
|
import { useHomeLabel } from '../auth-frame.tsx'
|
|
@@ -13,17 +14,22 @@ export const Route = createFileRoute('/')({
|
|
|
13
14
|
component: Landing,
|
|
14
15
|
})
|
|
15
16
|
|
|
17
|
+
/** Whether a visitor with no invitation may make an account, so the page offers one, as sign-in does. */
|
|
18
|
+
const offersSignUp = organizations === 'many' || signUp === 'open'
|
|
19
|
+
|
|
16
20
|
function EntryActions({ screens, size }: { screens: readonly { name: string; path: string }[] | null; size: 'sm' | 'lg' }) {
|
|
17
21
|
const { t } = useCopy()
|
|
18
22
|
const label = useHomeLabel()
|
|
19
23
|
if (screens === null) {
|
|
20
24
|
return (
|
|
21
25
|
<>
|
|
22
|
-
|
|
23
|
-
<
|
|
24
|
-
|
|
26
|
+
{offersSignUp ? (
|
|
27
|
+
<Link to="/auth/sign-up">
|
|
28
|
+
<Button size={size}>{t('marketing.getStarted', 'Get started')}</Button>
|
|
29
|
+
</Link>
|
|
30
|
+
) : null}
|
|
25
31
|
<Link to="/auth/sign-in">
|
|
26
|
-
<Button size={size} variant=
|
|
32
|
+
<Button size={size} variant={offersSignUp ? 'outline' : 'default'}>
|
|
27
33
|
{t('auth.signIn', 'Sign in')}
|
|
28
34
|
</Button>
|
|
29
35
|
</Link>
|
|
@@ -39,7 +45,7 @@ function EntryActions({ screens, size }: { screens: readonly { name: string; pat
|
|
|
39
45
|
)
|
|
40
46
|
}
|
|
41
47
|
|
|
42
|
-
function Feature({ icon: Icon, title, description }: { icon: typeof
|
|
48
|
+
function Feature({ icon: Icon, title, description }: { icon: typeof ShieldCheck; title: string; description: string }) {
|
|
43
49
|
return (
|
|
44
50
|
<Card>
|
|
45
51
|
<CardHeader>
|
|
@@ -69,15 +75,13 @@ function Landing() {
|
|
|
69
75
|
// `t` answers `string | undefined` for a key no locale carries; the fallback only satisfies that type.
|
|
70
76
|
const tt = (key: string, fallback: string) => t(key, fallback) ?? fallback
|
|
71
77
|
const features = [
|
|
72
|
-
{ icon: BookOpen, title: tt('marketing.featureCoursesTitle', 'Courses and sessions'), description: tt('marketing.featureCoursesDescription', '') },
|
|
73
|
-
{ icon: Users, title: tt('marketing.featureEnrollmentsTitle', 'Enrollments'), description: tt('marketing.featureEnrollmentsDescription', '') },
|
|
74
78
|
{ icon: ShieldCheck, title: tt('marketing.featureRolesTitle', 'Roles and permissions'), description: tt('marketing.featureRolesDescription', '') },
|
|
75
79
|
{ icon: Languages, title: tt('marketing.featureBilingualTitle', 'Arabic and English, either direction'), description: tt('marketing.featureBilingualDescription', '') },
|
|
76
80
|
{ icon: LayoutDashboard, title: tt('marketing.featurePortalTitle', 'A dashboard and a customer portal'), description: tt('marketing.featurePortalDescription', '') },
|
|
77
81
|
]
|
|
78
82
|
const steps = [
|
|
79
83
|
{ title: tt('marketing.howStep1Title', 'Set up your organization'), description: tt('marketing.howStep1Description', '') },
|
|
80
|
-
{ title: tt('marketing.howStep2Title', 'Add
|
|
84
|
+
{ title: tt('marketing.howStep2Title', 'Add your records and roles'), description: tt('marketing.howStep2Description', '') },
|
|
81
85
|
{ title: tt('marketing.howStep3Title', 'Open the portal to your customers'), description: tt('marketing.howStep3Description', '') },
|
|
82
86
|
]
|
|
83
87
|
return (
|
|
@@ -94,7 +98,7 @@ function Landing() {
|
|
|
94
98
|
|
|
95
99
|
<main className="flex-1">
|
|
96
100
|
<section className="mx-auto flex max-w-4xl flex-col items-center gap-6 px-4 py-20 text-center sm:px-6 sm:py-28">
|
|
97
|
-
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl">{t('marketing.heroTitle', 'Run
|
|
101
|
+
<h1 className="text-4xl font-bold tracking-tight sm:text-5xl">{t('marketing.heroTitle', 'Run your organization without the busywork')}</h1>
|
|
98
102
|
<p className="max-w-2xl text-lg text-muted-foreground">{t('marketing.heroSubtitle', '')}</p>
|
|
99
103
|
<div className="flex flex-col gap-3 sm:flex-row">
|
|
100
104
|
<EntryActions screens={screens} size="lg" />
|
|
@@ -103,7 +107,7 @@ function Landing() {
|
|
|
103
107
|
|
|
104
108
|
<section className="mx-auto max-w-6xl px-4 py-16 sm:px-6">
|
|
105
109
|
<div className="mx-auto mb-12 max-w-2xl text-center">
|
|
106
|
-
<h2 className="text-3xl font-bold tracking-tight">{t('marketing.featuresTitle', 'Everything
|
|
110
|
+
<h2 className="text-3xl font-bold tracking-tight">{t('marketing.featuresTitle', 'Everything an organization needs')}</h2>
|
|
107
111
|
<p className="mt-3 text-muted-foreground">{t('marketing.featuresSubtitle', '')}</p>
|
|
108
112
|
</div>
|
|
109
113
|
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
@@ -125,7 +129,7 @@ function Landing() {
|
|
|
125
129
|
</section>
|
|
126
130
|
|
|
127
131
|
<section className="mx-auto flex max-w-3xl flex-col items-center gap-6 px-4 py-20 text-center sm:px-6">
|
|
128
|
-
<h2 className="text-3xl font-bold tracking-tight">{t('marketing.ctaTitle', 'Start running your
|
|
132
|
+
<h2 className="text-3xl font-bold tracking-tight">{t('marketing.ctaTitle', 'Start running your organization on Asas')}</h2>
|
|
129
133
|
<p className="text-muted-foreground">{t('marketing.ctaSubtitle', '')}</p>
|
|
130
134
|
<EntryActions screens={screens} size="lg" />
|
|
131
135
|
</section>
|
|
@@ -133,10 +137,10 @@ function Landing() {
|
|
|
133
137
|
|
|
134
138
|
<footer className="border-t py-8">
|
|
135
139
|
<div className="mx-auto max-w-6xl px-4 text-center text-sm text-muted-foreground sm:px-6">
|
|
136
|
-
{t('marketing.footer', 'Asas —
|
|
140
|
+
{t('marketing.footer', 'Asas — one place for an organization\'s records.')}
|
|
137
141
|
</div>
|
|
138
142
|
</footer>
|
|
139
143
|
</div>
|
|
140
144
|
)
|
|
141
145
|
}
|
|
142
|
-
// mithaq-admin
|
|
146
|
+
// mithaq-admin 0.0.0 4ea8d722cd59ec1a91778142281c6f7db6ab9abe005dfc5f8c0192ee4e1095aa
|
|
@@ -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', '
|
|
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', '
|
|
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.
|
|
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
|
-
<
|
|
88
|
+
<div className="grid gap-2">
|
|
77
89
|
{screens.map((screen) => (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
</
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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
|
|
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
|
|
10
|
+
// mithaq-admin 2d3ab739c92c6c2b397041b76bf4b23b2902b7a2 878b7180f2aa169546feb42bcdfcb6002841d08e46788c94ddd6edfc4e3502ca
|
|
@@ -7,17 +7,27 @@ import { ScreenContext, useScreen, type ScreenState } from 'mithaq/admin/react'
|
|
|
7
7
|
import { actingAs, arm, authArm, caused, clientOf, readsOf, takesKey } from './api.ts'
|
|
8
8
|
import { clampedHref, routedHref } from './links.ts'
|
|
9
9
|
import { mergedHeaders } from './request-headers.ts'
|
|
10
|
+
import { nameIn } from 'asas/organizations'
|
|
10
11
|
import { documentHead, localeOf, locales, translator, type Locale } from './copy.ts'
|
|
11
|
-
import { FieldValue
|
|
12
|
-
import type {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
12
|
+
import { FieldValue } from 'mithaq-admin/field-value.tsx'
|
|
13
|
+
import type { FormClient, FormProps } from 'mithaq-admin/form-types.tsx'
|
|
14
|
+
import type { Page } from 'mithaq-admin/list-types.tsx'
|
|
15
|
+
import { QueryRefused, findOf, listRaw, prefixOf, readQuery, rulesOf, scopedTo, withList, type RawQuery } from 'mithaq-admin/list-query.ts'
|
|
16
|
+
import { newTitleOf, titleOf } from 'mithaq-admin/copy.ts'
|
|
17
|
+
import { createsIn } from 'mithaq-admin/field-values.ts'
|
|
18
|
+
import { NavLinks } from 'mithaq-admin/nav-links.tsx'
|
|
19
|
+
import { NodeButton, OwnButton, pageButtonOf } from 'mithaq-admin/node-buttons.tsx'
|
|
20
|
+
import { formIn, listIn, nodesOf, readsAsValues, relatedIn, scopeOf, type Draw } from 'mithaq-admin/node-tree.ts'
|
|
21
|
+
import { frameOf, pageOf } from 'mithaq-admin/nodes.tsx'
|
|
22
|
+
import { RecordFrame, holdFocus } from 'mithaq-admin/record-frame.tsx'
|
|
23
|
+
import { rowTitle } from 'mithaq-admin/row-title.tsx'
|
|
24
|
+
import { refOf, type Refs, type TreePage } from 'mithaq-admin/screen-refs.ts'
|
|
25
|
+
import { idsOf } from 'mithaq-admin/relationship-control.tsx'
|
|
17
26
|
import { ScreenForm } from './screen-form.tsx'
|
|
18
27
|
import { ScreenList, askedFor, readsDrafts, rowReadsDrafts, rowsKey } from './screen-list.tsx'
|
|
19
28
|
import { screenNamed } from './screen-names.ts'
|
|
20
29
|
import { slide } from './slide.ts'
|
|
30
|
+
import { Toaster } from 'mithaq-admin/toast.tsx'
|
|
21
31
|
import { useCopy } from './routes/__root.tsx'
|
|
22
32
|
|
|
23
33
|
/**
|
|
@@ -29,13 +39,13 @@ export const getAsas = createServerOnlyFn(async () => (await import('asas')).asa
|
|
|
29
39
|
type InProcess = Record<string, { operations: Record<string, (input: object, options: object) => Promise<unknown>> }>
|
|
30
40
|
|
|
31
41
|
/**
|
|
32
|
-
* Each kayan's operations in process. In the
|
|
33
|
-
* which the runner grants
|
|
42
|
+
* Each kayan's operations in process. In the backoffice app a read of a kayan asks `tenant: 'all'`,
|
|
43
|
+
* which the runner grants backoffice staff there alone, so staff read every organization's rows; a
|
|
34
44
|
* global is one organization's row and asks for none, and a write never asks.
|
|
35
45
|
*/
|
|
36
46
|
const inProcess = async (asas: Awaited<ReturnType<typeof getAsas>>): Promise<InProcess> => {
|
|
37
47
|
const kayan = asas.kayan as unknown as InProcess
|
|
38
|
-
if (!(await import('asas')).
|
|
48
|
+
if (!(await import('asas')).onBackoffice()) return kayan
|
|
39
49
|
const reads = new Set(asas.operations.filter((one) => one.effect === 'read' && !one.single).map((one) => `${one.kayan}/${one.name}`))
|
|
40
50
|
return Object.fromEntries(Object.entries(kayan).map(([name, one]) => [name, {
|
|
41
51
|
operations: Object.fromEntries(Object.entries(one.operations).map(([operation, run]) =>
|
|
@@ -117,7 +127,7 @@ type Actor = {
|
|
|
117
127
|
readonly email?: string | null
|
|
118
128
|
readonly impersonatedBy?: unknown
|
|
119
129
|
readonly activeOrganizationId?: string | null
|
|
120
|
-
readonly organizations?: readonly { readonly id: string; readonly name: string; readonly slug?: string }[]
|
|
130
|
+
readonly organizations?: readonly { readonly id: string; readonly name: string; readonly slug?: string; readonly names?: unknown }[]
|
|
121
131
|
}
|
|
122
132
|
|
|
123
133
|
// `'unauthorized'` is answered before the projection is read, so a refused screen sends no kayan, field, row or page;
|
|
@@ -140,15 +150,15 @@ const resolveScreen = async (asked: unknown) => {
|
|
|
140
150
|
.filter(([one]) => report.collections?.[one]?.read === true)
|
|
141
151
|
.map(([one, value]) => [one, { ...value, fields: value.fields.filter((field) => report.collections?.[one]?.fields?.[field.name]?.read === true) }]),
|
|
142
152
|
)
|
|
143
|
-
const {
|
|
144
|
-
const
|
|
145
|
-
// The other screens of this app the actor may enter. The
|
|
146
|
-
// is left out of a tenant app's list, so the one link to it is `
|
|
153
|
+
const { onBackoffice, backofficeEntry, BACKOFFICE_SCREEN } = await import('asas')
|
|
154
|
+
const backoffice = backofficeEntry(actor)
|
|
155
|
+
// The other screens of this app the actor may enter. The backoffice's own name
|
|
156
|
+
// is left out of a tenant app's list, so the one link to it is `backofficeEntry`'s alone.
|
|
147
157
|
const served = screensOf(asas.targets) as Record<string, { client: ScreenClient }>
|
|
148
158
|
const siblings = (report.screens ?? [])
|
|
149
|
-
.filter((one) => one !== name && Object.hasOwn(served, one) && (one ===
|
|
159
|
+
.filter((one) => one !== name && Object.hasOwn(served, one) && (one === BACKOFFICE_SCREEN) === onBackoffice())
|
|
150
160
|
.map((one) => ({ name: one, path: served[one]!.client.path }))
|
|
151
|
-
const others = [...siblings, ...(
|
|
161
|
+
const others = [...siblings, ...(backoffice ? [{ name: BACKOFFICE_SCREEN, path: backoffice }] : [])]
|
|
152
162
|
return {
|
|
153
163
|
asas,
|
|
154
164
|
request,
|
|
@@ -164,7 +174,8 @@ const resolveScreen = async (asked: unknown) => {
|
|
|
164
174
|
...(actor.impersonatedBy ? { impersonating: true } : {}),
|
|
165
175
|
...(others.length ? { screens: others } : {}),
|
|
166
176
|
...(actor.organizations && {
|
|
167
|
-
|
|
177
|
+
// Every locale's, because the frame is read before the viewer's locale is known here; the shell picks one.
|
|
178
|
+
organizations: actor.organizations.map(({ id, name, slug, names }) => ({ id, name, slug, ...(typeof names === 'string' && { names }) })),
|
|
168
179
|
activeOrganizationId: actor.activeOrganizationId ?? null,
|
|
169
180
|
}),
|
|
170
181
|
},
|
|
@@ -206,14 +217,14 @@ export const loadEnterable = createServerFn({ method: 'GET' })
|
|
|
206
217
|
others?: { id: string; name: string; slug?: string }[]
|
|
207
218
|
impersonating?: string
|
|
208
219
|
} | null> => {
|
|
209
|
-
const [{ getRequest }, asas, {
|
|
220
|
+
const [{ getRequest }, asas, { onBackoffice, BACKOFFICE_SCREEN }] = await Promise.all([import('@tanstack/react-start/server'), getAsas(), import('asas')])
|
|
210
221
|
const request = getRequest()
|
|
211
222
|
const actor = (await asas.auth.actor(request.headers)) as Actor | null
|
|
212
223
|
if (!actor?.email) return null
|
|
213
224
|
const report = (await (await asas.handler(new Request(new URL('/api/access', request.url), { headers: request.headers }))).json()) as AccessReport
|
|
214
225
|
const screens = screensOf(asas.targets) as Record<string, { client: ScreenClient }>
|
|
215
226
|
const entered = (report.screens ?? [])
|
|
216
|
-
.filter((name) => Object.hasOwn(screens, name) && (name ===
|
|
227
|
+
.filter((name) => Object.hasOwn(screens, name) && (name === BACKOFFICE_SCREEN) === onBackoffice())
|
|
217
228
|
.map((name) => ({ name, path: screens[name]!.client.path }))
|
|
218
229
|
const impersonating = actor.impersonatedBy ? { impersonating: actor.name ?? actor.email } : {}
|
|
219
230
|
if (entered.length) {
|
|
@@ -226,11 +237,14 @@ export const loadEnterable = createServerFn({ method: 'GET' })
|
|
|
226
237
|
}
|
|
227
238
|
const memberships = actor.organizations ?? []
|
|
228
239
|
const active = memberships.find((one) => one.id === actor.activeOrganizationId)
|
|
240
|
+
// Named here, where the locale is known: the page draws what it is handed, and a name the viewer cannot read
|
|
241
|
+
// in the default locale would be no name to them.
|
|
242
|
+
const named = ({ id, name, slug, names }: NonNullable<Actor['organizations']>[number]) => ({ id, name: nameIn({ name, names }, data.locale), slug })
|
|
229
243
|
return {
|
|
230
244
|
screens: entered,
|
|
231
245
|
email: actor.email,
|
|
232
|
-
organizations: (active ? [active] : memberships).map(
|
|
233
|
-
others:
|
|
246
|
+
organizations: (active ? [active] : memberships).map(named),
|
|
247
|
+
others: onBackoffice() ? [] : memberships.filter((one) => one !== active).map(named),
|
|
234
248
|
...impersonating,
|
|
235
249
|
}
|
|
236
250
|
})
|
|
@@ -473,7 +487,8 @@ const nameOfPage = ({ screen, name, match, row, titledBy, copy }: {
|
|
|
473
487
|
if (match.kayan === undefined) return screen.nav.find((entry) => !entry.kayan && entry.page === match.page)?.label?.[copy.locale] ?? copy.t(`screens.${name}`, name) ?? name
|
|
474
488
|
const kayan = screen.kayan[match.kayan]!
|
|
475
489
|
const page = kayan.pages[match.page]
|
|
476
|
-
|
|
490
|
+
// A list page draws every row, whether a tree's list node or a component drawing it whole.
|
|
491
|
+
const many = match.page === 'list' || (!!page && 'page' in page && listIn(nodesOf(page)) !== undefined)
|
|
477
492
|
if (match.page === 'create') return newTitleOf({ kayan, copy })
|
|
478
493
|
const field = kayan.fields.find((one) => one.name === kayan.useAsTitle)
|
|
479
494
|
const target = field?.kind === 'relationship' ? screen.kayan[field.relationTo ?? ''] : undefined
|
|
@@ -500,7 +515,7 @@ const useScreenState = (data: ScreenData): ScreenState => {
|
|
|
500
515
|
|
|
501
516
|
/**
|
|
502
517
|
* The bar that says the session is an impersonation, with the way out. Stopping
|
|
503
|
-
* is a sign-out: the staff member's own session lives in the
|
|
518
|
+
* is a sign-out: the staff member's own session lives in the backoffice app under
|
|
504
519
|
* that app's cookie, and the sign-out answers where they go next.
|
|
505
520
|
*/
|
|
506
521
|
export function ImpersonationBar({ name }: { name: string }): ReactElement {
|
|
@@ -544,6 +559,7 @@ function ScreenBar({ user }: { user: ScreenData['user'] }): ReactElement | null
|
|
|
544
559
|
|
|
545
560
|
/** The screen's own frame: its providers, outermost first, around the frame's bar and the page. */
|
|
546
561
|
export function ScreenFrame({ files, data, children }: { files: Files; data: ScreenData; children: ReactNode }): ReactElement {
|
|
562
|
+
const { t } = useCopy()
|
|
547
563
|
const refs = refsOf(files, data.screen)
|
|
548
564
|
// One per frame: made per request on the server, so no row one actor read
|
|
549
565
|
// reaches another's render; held for the frame's life in the browser.
|
|
@@ -590,6 +606,8 @@ export function ScreenFrame({ files, data, children }: { files: Files; data: Scr
|
|
|
590
606
|
<>
|
|
591
607
|
<ScreenBar user={data.user} />
|
|
592
608
|
{children}
|
|
609
|
+
{/* The one place every write's toast is drawn, kept across the screen's pages. */}
|
|
610
|
+
<Toaster closeLabel={t('admin.close', 'Close') ?? 'Close'} />
|
|
593
611
|
</>,
|
|
594
612
|
)}
|
|
595
613
|
</Suspense>
|
|
@@ -666,7 +684,7 @@ function PageBody({ files, data, query, onQuery }: PageViewProps): ReactElement
|
|
|
666
684
|
return path ? `${base}${id ? path.replace(':id', encodeURIComponent(id)) : path}` : undefined
|
|
667
685
|
}
|
|
668
686
|
// A row opens its kayan's edit page, else the one page whose path draws a
|
|
669
|
-
// row (how the
|
|
687
|
+
// row (how the backoffice opens a record it only reads).
|
|
670
688
|
const rowPage = (target: ClientKayan) => (target.pages.edit ? 'edit' : Object.keys(target.pages).find((one) => target.pages[one]!.path.includes(':id')))
|
|
671
689
|
const related = relatedOf(data.screen, kayan)
|
|
672
690
|
const go = (href: string | undefined) => void (href ? router.navigate({ href }) : router.invalidate())
|
|
@@ -844,7 +862,9 @@ function PageBody({ files, data, query, onQuery }: PageViewProps): ReactElement
|
|
|
844
862
|
api={readsOf(related)}
|
|
845
863
|
operations={data.operations}
|
|
846
864
|
related={draw(page).list}
|
|
847
|
-
|
|
865
|
+
// A create opened over its list closes back to it once it lands, the new row in the list, as Payload's drawer does;
|
|
866
|
+
// one whose kayan autosaves its drafts moves to the row's page instead, since its first autosave is a create too.
|
|
867
|
+
edit={(id) => (page.mode && listHref && !data.drafts?.autosave ? listHref : hrefOf(kayan, 'edit', id))}
|
|
848
868
|
list={listHref}
|
|
849
869
|
drafts={data.drafts}
|
|
850
870
|
expectedUpdatedAt={data.expectedUpdatedAt}
|
|
@@ -865,4 +885,4 @@ function PageBody({ files, data, query, onQuery }: PageViewProps): ReactElement
|
|
|
865
885
|
}
|
|
866
886
|
return form
|
|
867
887
|
}
|
|
868
|
-
// mithaq-admin
|
|
888
|
+
// mithaq-admin 0.0.0 da2e742262e16a7f9f8dab050c2a4201517478795101af68736907464a0d52ce
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import handler, { createServerEntry } from '@tanstack/react-start/server-entry'
|
|
2
|
+
import type { ScreenClient } from 'mithaq/admin'
|
|
3
|
+
import { checkComponents } from 'mithaq-admin/components.tsx'
|
|
4
|
+
import { components } from './components.ts'
|
|
2
5
|
import { withQuietAbort } from './quiet-abort.ts'
|
|
3
6
|
|
|
7
|
+
// Every screen this app serves, checked against its components map as it starts, so a field whose kind has no
|
|
8
|
+
// control fails the start, naming the kind and the field, and never reaches a page. Read on the server alone,
|
|
9
|
+
// since a screen.json names every field of the screen.
|
|
10
|
+
for (const client of Object.values(import.meta.glob<ScreenClient>('./routes/(screens)/*/screen.json', { eager: true, import: 'default' }))) {
|
|
11
|
+
checkComponents({ client, components })
|
|
12
|
+
}
|
|
13
|
+
|
|
4
14
|
// A production start connects once before handing on any request, and `connect()` refuses to
|
|
5
15
|
// start while a migration is unapplied; a refused start exits 1. NODE_ENV is read off `process`
|
|
6
16
|
// so Vite cannot replace it at build time, and a built server started without
|
|
@@ -19,4 +19,4 @@ export const slide = async ({
|
|
|
19
19
|
// Set once with every strategy's cookies: a second set replaces the first.
|
|
20
20
|
if (cookies.length) setResponseHeader('set-cookie', cookies)
|
|
21
21
|
}
|
|
22
|
-
// mithaq-admin
|
|
22
|
+
// mithaq-admin 2d3ab739c92c6c2b397041b76bf4b23b2902b7a2 bc0221e1088103f8638e0333b84aafb6b326e3991a187ced198bce2d090ecbe9
|
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
imports, custom-variant, :root and .dark variables, the @theme inline
|
|
3
3
|
block and the @layer base rules, unchanged. Only the @source line below
|
|
4
4
|
differs, pointing at this project's template instead of TB's monorepo.
|
|
5
|
-
Tailwind finds this app's own files,
|
|
5
|
+
Tailwind finds this app's own files, an ejected `src/components/mithaq`
|
|
6
6
|
among them, without one. */
|
|
7
7
|
@import 'tailwindcss';
|
|
8
8
|
@import 'tw-animate-css';
|
|
9
9
|
@import 'shadcn/tailwind.css';
|
|
10
|
+
/* The admin's pieces in node_modules, which Tailwind never scans on its own. */
|
|
11
|
+
@import 'mithaq-admin/source.css';
|
|
10
12
|
@import '@fontsource-variable/raleway';
|
|
11
13
|
@import '@fontsource/ibm-plex-sans-arabic';
|
|
12
14
|
|
|
13
15
|
/* The project's own shells, pages and components, which the screens load
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
(the language package's `src/ui/app-shell.tsx`), in every package of the project.
|
|
17
|
+
Named by their folder, which every package manager leaves where it is, since
|
|
18
|
+
each links the package elsewhere. */
|
|
19
|
+
@source '../../../templates';
|
|
16
20
|
|
|
17
21
|
@custom-variant dark (&:is(.dark *));
|
|
18
22
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"allowImportingTsExtensions": true,
|
|
14
14
|
"resolveJsonModule": true,
|
|
15
15
|
"types": ["node", "vite/client"],
|
|
16
|
-
"paths": { "@/*": ["./src/*"] }
|
|
16
|
+
"paths": { "@/*": ["./src/*"], "mithaq-admin/*": ["./src/components/mithaq/*", "./node_modules/mithaq-admin/registry/*"] }
|
|
17
17
|
},
|
|
18
|
-
"include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts", "../../templates/
|
|
18
|
+
"include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts", "../../templates/mithaq/src/ui/*.tsx", "../../templates/asas/src/ui-*/*.tsx", "../../templates/features/*/*/ui/*.tsx"]
|
|
19
19
|
}
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import tailwindcss from '@tailwindcss/vite'
|
|
2
2
|
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
|
|
3
3
|
import viteReact from '@vitejs/plugin-react'
|
|
4
|
+
import { mithaqAdmin } from 'mithaq/vite'
|
|
4
5
|
import { defineConfig } from 'vite'
|
|
5
6
|
import { fileURLToPath } from 'node:url'
|
|
6
7
|
|
|
7
8
|
export default defineConfig({
|
|
8
9
|
// An alias rather than `resolve.tsconfigPaths`, which applies only to files the nearest
|
|
9
|
-
// tsconfig claims: the project's shells in `
|
|
10
|
+
// tsconfig claims: the project's shells in the language package's `src/ui/` import `@/` too.
|
|
10
11
|
// `^@/` and not `@`, so `@tanstack/*` is left alone.
|
|
11
12
|
resolve: { alias: [{ find: /^@\//, replacement: fileURLToPath(new URL('./src/', import.meta.url)) }] },
|
|
12
13
|
plugins: [
|
|
13
14
|
tailwindcss(),
|
|
15
|
+
// A file under this folder takes the place of mithaq-admin's file of the same name, for every importer.
|
|
16
|
+
mithaqAdmin(fileURLToPath(new URL('./src/components/mithaq/', import.meta.url))),
|
|
14
17
|
// `mithaq generate screens` writes `types.ts` and `files.ts` beside the route files, and neither is a route.
|
|
15
18
|
tanstackStart({ router: { routeFileIgnorePattern: '^(files|types)\\.ts$' } }),
|
|
16
19
|
viteReact(),
|
|
17
20
|
],
|
|
21
|
+
// Behind the proxy that routes by Host: bound to the loopback address it targets, and answering
|
|
22
|
+
// the public name in BETTER_AUTH_URL, which Vite refuses in a Host header unless named here.
|
|
23
|
+
preview: { host: '127.0.0.1', allowedHosts: process.env.BETTER_AUTH_URL ? [new URL(process.env.BETTER_AUTH_URL).hostname] : [] },
|
|
18
24
|
environments: {
|
|
19
25
|
ssr: {
|
|
20
26
|
// `asas` and `mithaq` stay external: the drizzle adapter imports `drizzle-kit/api-postgres`
|
package/template/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"private": true,
|
|
3
3
|
"type": "module",
|
|
4
|
-
"packageManager": "bun@1.3.14",
|
|
5
4
|
"engines": {
|
|
6
5
|
"node": ">=24"
|
|
7
6
|
},
|
|
8
7
|
"workspaces": [
|
|
9
|
-
"
|
|
8
|
+
"packages/mithaq",
|
|
9
|
+
"packages/features/*",
|
|
10
|
+
"packages/asas",
|
|
10
11
|
"apps/*"
|
|
11
12
|
],
|
|
12
13
|
"devDependencies": {
|
|
13
|
-
"typescript": "^7.0.2",
|
|
14
14
|
"@types/node": "^24.12.3"
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-field
|
|
3
|
+
description: Add, change or remove a field of an existing kayan in this mithaq project and write the migration for it. Use when asked to add a column, change a field's kind or rules, or write a migration.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Add a field and its migration
|
|
7
|
+
|
|
8
|
+
The guide is "A field" under "Add a feature to an existing kayan" in `README.md`, and "Migrations" in `AGENTS.md`. The installed mithaq is `packages/asas/node_modules/mithaq/`, or `node_modules/mithaq/` where the package manager hoists it to the root: every kind and rule is in its `docs/packages/mithaq/README.md`, and `mithaq migrate` in its `docs/packages/mithaq/src/asas/cli/README.md`.
|
|
9
|
+
|
|
10
|
+
1. Edit `fields` in the kayan's file. A kind or rule the language does not hold changes the vocabulary: stop and ask the developer.
|
|
11
|
+
2. A new `required` field on a table with rows needs a literal `defaultValue`, or goes in without `required` and becomes required in a later migration.
|
|
12
|
+
3. Add the field to the pages that should draw it, and to the roles' grants when they list fields one by one (`<kayan>.fields.<field>`).
|
|
13
|
+
4. From `packages/asas/`, with `DATABASE_URL` pointing at a database built from the migrations (never a pushed one; `AGENTS.md` "Migrations" says how to build one):
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
bunx mithaq generate screens # when a page draws it
|
|
17
|
+
bunx mithaq generate messages # then fill every empty translation
|
|
18
|
+
bunx mithaq migrate --name <slug>
|
|
19
|
+
bunx mithaq generate --check
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
5. When `mithaq migrate` stops and asks for an answer in `mithaq.hints.json`, it is a rename or a drop. A drop deletes rows: ask the developer before you answer it.
|
|
23
|
+
6. From the project root, `bunx --package typescript tsc -p .` and the same with `-p` naming each app.
|
|
24
|
+
|
|
25
|
+
Commit the migration folder.
|
|
26
|
+
|
|
27
|
+
In a project made with `--layout single`, read each path as `AGENTS.md` maps it; the installed mithaq is `node_modules/mithaq/`.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-kayan
|
|
3
|
+
description: Add a kayan, one data type with its table, operations, access and admin pages, to this mithaq project. Use when asked for a new entity, table, resource or list in the dashboard.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Add a kayan
|
|
7
|
+
|
|
8
|
+
The steps are "Adding a kayan" in `AGENTS.md`, and the guide's worked example is "Add a kayan" in `README.md`. The installed mithaq is `packages/asas/node_modules/mithaq/`, or `node_modules/mithaq/` where the package manager hoists it to the root; its `docs/packages/mithaq/README.md` lists every kind and rule a field can take.
|
|
9
|
+
|
|
10
|
+
1. Ask, unless the request says: does each row belong to one organization (place `organization`, the usual answer) or does every organization share it?
|
|
11
|
+
2. Follow `AGENTS.md` "Adding a kayan", copying `packages/features/tags/`. Use only the kinds and rules the language holds; a new one changes the vocabulary, so stop and ask the developer.
|
|
12
|
+
3. Grant its keys to the roles that use it, as step 4 there says. A kayan no role is granted answers no one.
|
|
13
|
+
4. From `packages/asas/`, with `DATABASE_URL` pointing at a database built from the migrations (never a pushed one; `AGENTS.md` "Migrations" says how to build one):
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
bunx mithaq generate screens # when a screen draws it
|
|
17
|
+
bunx mithaq generate messages # then fill every empty translation
|
|
18
|
+
bunx mithaq migrate --name add_<name>
|
|
19
|
+
bunx mithaq generate --check
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
5. From the project root, `bunx --package typescript tsc -p .` and `bunx --package typescript tsc -p apps/asas-tanstack` (and `apps/asas-backoffice` when the project has it).
|
|
23
|
+
|
|
24
|
+
Commit the migration folder. Never edit a file whose first line reads ``// Written by `mithaq <command>` ``; run the command again.
|
|
25
|
+
|
|
26
|
+
In a project made with `--layout single`, read each path as `AGENTS.md` maps it; the installed mithaq is `node_modules/mithaq/`.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: eject-admin-piece
|
|
3
|
+
description: Change how the admin draws something in this mithaq project, by replacing a control, cell or node in the components map or by ejecting a mithaq-admin piece. Use when asked to restyle or change an admin control, list, form, shell or page piece.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Change an admin piece
|
|
7
|
+
|
|
8
|
+
The installed mithaq is `packages/asas/node_modules/mithaq/`, or `node_modules/mithaq/` where the package manager hoists it to the root. The guide is the admin's, its `docs/packages/mithaq-admin/README.md` ("Add or replace a control", "Replace a node", "Eject a piece"), and `eject` in its `docs/packages/mithaq/src/asas/cli/README.md`.
|
|
9
|
+
|
|
10
|
+
1. Reach for the components map first: a control, a cell or a node is replaced in the app's `src/components.ts` without ejecting anything.
|
|
11
|
+
2. Otherwise eject the piece, in the app that draws it (`apps/asas-tanstack` or `apps/asas-backoffice`):
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
bunx mithaq eject <item> # an unknown name lists every item
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
It writes the piece into `src/components/mithaq/`, drawn in place of the package's, and names any shadcn primitive or npm package to add. Restart the dev server.
|
|
18
|
+
3. Edit the ejected file in place; it is the project's now. Keep its last line, the `// mithaq-admin` marker, which `upgrade --check` reads.
|
|
19
|
+
4. Check, in the app:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
bunx mithaq upgrade --check # the held files against the installed mithaq-admin
|
|
23
|
+
bunx --package typescript tsc -p .
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
To go back to the package's piece, delete the file, or run `bunx mithaq eject --prune`, which deletes every ejected file nobody edited.
|
|
27
|
+
|
|
28
|
+
In a project made with `--layout single`, read each path as `AGENTS.md` maps it; the installed mithaq is `node_modules/mithaq/`.
|