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.
- 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 +1094 -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
|
@@ -22,6 +22,7 @@ export type OrganizationValues = {
|
|
|
22
22
|
slug: string
|
|
23
23
|
logo: string | null
|
|
24
24
|
metadata: string | null
|
|
25
|
+
names: string | null
|
|
25
26
|
}
|
|
26
27
|
export type MemberValues = {
|
|
27
28
|
id: string
|
|
@@ -30,6 +31,7 @@ export type MemberValues = {
|
|
|
30
31
|
organizationId: string
|
|
31
32
|
userId: string
|
|
32
33
|
role: string
|
|
34
|
+
lastSeenAt: Date | null
|
|
33
35
|
}
|
|
34
36
|
export type CoursesValues = {
|
|
35
37
|
id: string
|
|
@@ -54,6 +56,7 @@ export type CoursesValues = {
|
|
|
54
56
|
_status: 'draft' | 'published' | null
|
|
55
57
|
details: { root: Record<string, unknown> } | null
|
|
56
58
|
cover: unknown
|
|
59
|
+
selfEnroll: boolean | null
|
|
57
60
|
}
|
|
58
61
|
export type SessionsValues = {
|
|
59
62
|
id: string
|
|
@@ -68,6 +71,7 @@ export type SessionsValues = {
|
|
|
68
71
|
archived: boolean | null
|
|
69
72
|
details: { root: Record<string, unknown> } | null
|
|
70
73
|
enrollments: Values['enrollments'][]
|
|
74
|
+
archiveReason: string | null
|
|
71
75
|
}
|
|
72
76
|
export type EnrollmentsValues = {
|
|
73
77
|
id: string
|
|
@@ -118,6 +122,18 @@ export type LocalUsersValues = {
|
|
|
118
122
|
_verified: boolean | null
|
|
119
123
|
_verificationToken: string | null
|
|
120
124
|
}
|
|
125
|
+
export type BackofficeLogValues = {
|
|
126
|
+
id: string
|
|
127
|
+
createdAt: Date
|
|
128
|
+
action: 'impersonate' | 'stopImpersonating' | 'setRole' | 'ban' | 'unban'
|
|
129
|
+
actor: string | null
|
|
130
|
+
actorEmail: string | null
|
|
131
|
+
target: string | null
|
|
132
|
+
targetEmail: string | null
|
|
133
|
+
previousRole: string | null
|
|
134
|
+
role: string | null
|
|
135
|
+
banReason: string | null
|
|
136
|
+
}
|
|
121
137
|
export type Values = {
|
|
122
138
|
user: UserValues
|
|
123
139
|
organization: OrganizationValues
|
|
@@ -129,6 +145,7 @@ export type Values = {
|
|
|
129
145
|
roles: RolesValues
|
|
130
146
|
memberRoles: MemberRolesValues
|
|
131
147
|
localUsers: LocalUsersValues
|
|
148
|
+
backofficeLog: BackofficeLogValues
|
|
132
149
|
}
|
|
133
150
|
export type Pages = {
|
|
134
151
|
user: 'list' | 'view'
|
|
@@ -141,5 +158,6 @@ export type Pages = {
|
|
|
141
158
|
roles: 'list' | 'view'
|
|
142
159
|
memberRoles: 'list' | 'view'
|
|
143
160
|
localUsers: 'list' | 'edit'
|
|
161
|
+
backofficeLog: 'list'
|
|
144
162
|
}
|
|
145
163
|
export type ScreenClient = Client<Pages>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// Written by `mithaq generate screens`. Do not edit.
|
|
2
2
|
import { createFileRoute } from '@tanstack/react-router'
|
|
3
3
|
import { ScreenNotFound, ScreenPageView, askedFor, pageHead, pageLoader } from '@/screens.tsx'
|
|
4
|
-
import { files } from './
|
|
4
|
+
import { files } from './backoffice/files.ts'
|
|
5
5
|
|
|
6
|
-
export const Route = createFileRoute('/(screens)/
|
|
6
|
+
export const Route = createFileRoute('/(screens)/backoffice/$')({
|
|
7
7
|
validateSearch: askedFor,
|
|
8
|
-
loader: ({ location, params }) => pageLoader({ screen: '
|
|
8
|
+
loader: ({ location, params }) => pageLoader({ screen: 'backoffice', files, path: params._splat ?? '', query: location.search, href: location.href }),
|
|
9
9
|
head: ({ loaderData, match }) => pageHead(loaderData, match.context),
|
|
10
|
-
notFoundComponent: () => <ScreenNotFound files={files} screen={'
|
|
10
|
+
notFoundComponent: () => <ScreenNotFound files={files} screen={'backoffice'} />,
|
|
11
11
|
component: Page,
|
|
12
12
|
})
|
|
13
13
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Written by `mithaq generate screens`. Do not edit.
|
|
2
2
|
import { createFileRoute } from '@tanstack/react-router'
|
|
3
3
|
import { ScreenPageView, askedFor, pageHead, pageLoader } from '@/screens.tsx'
|
|
4
|
-
import { files } from './
|
|
4
|
+
import { files } from './backoffice/files.ts'
|
|
5
5
|
|
|
6
|
-
export const Route = createFileRoute('/(screens)/
|
|
7
|
-
loader: ({ location }) => pageLoader({ screen: '
|
|
6
|
+
export const Route = createFileRoute('/(screens)/backoffice/')({
|
|
7
|
+
loader: ({ location }) => pageLoader({ screen: 'backoffice', files, path: '', query: {}, href: location.href }),
|
|
8
8
|
head: ({ loaderData, match }) => pageHead(loaderData, match.context),
|
|
9
9
|
component: () => <ScreenPageView files={files} data={Route.useLoaderData()} query={askedFor({})} onQuery={() => {}} />,
|
|
10
10
|
})
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Written by `mithaq generate screens`. Do not edit.
|
|
2
2
|
import { Outlet, createFileRoute } from '@tanstack/react-router'
|
|
3
3
|
import { ScreenFrame, frameLoader } from '@/screens.tsx'
|
|
4
|
-
import { files } from './
|
|
4
|
+
import { files } from './backoffice/files.ts'
|
|
5
5
|
|
|
6
|
-
export const Route = createFileRoute('/(screens)/
|
|
7
|
-
loader: ({ location }) => frameLoader({ screen: '
|
|
6
|
+
export const Route = createFileRoute('/(screens)/backoffice')({
|
|
7
|
+
loader: ({ location }) => frameLoader({ screen: 'backoffice', files, href: location.href }),
|
|
8
8
|
component: () => (
|
|
9
9
|
<ScreenFrame files={files} data={Route.useLoaderData()}>
|
|
10
10
|
<Outlet />
|
|
@@ -2,7 +2,7 @@ import { HeadContent, Outlet, Scripts, createRootRoute } from '@tanstack/react-r
|
|
|
2
2
|
import { DirectionProvider } from '@/components/ui/direction'
|
|
3
3
|
export { locales } from '../copy.ts'
|
|
4
4
|
import { dirOf, documentHead, localeOf, translator } from '../copy.ts'
|
|
5
|
-
import { ThemeProvider } from '
|
|
5
|
+
import { ThemeProvider } from 'mithaq-admin/theme.tsx'
|
|
6
6
|
import { themeStorageKey } from '../theme.ts'
|
|
7
7
|
import appCss from '../styles.css?url'
|
|
8
8
|
|
|
@@ -37,7 +37,7 @@ export const Route = createRootRoute({
|
|
|
37
37
|
typeof document === 'undefined'
|
|
38
38
|
? localeOf((ctx as unknown as FromRequest).serverContext?.locale as string | undefined)
|
|
39
39
|
: localeOf(document.documentElement.lang)
|
|
40
|
-
return { locale, dir: dirOf(locale), app: '
|
|
40
|
+
return { locale, dir: dirOf(locale), app: 'backoffice.brand' }
|
|
41
41
|
},
|
|
42
42
|
// The favicon stops the browser asking for a `/favicon.ico` nobody serves.
|
|
43
43
|
head: ({ match }) => ({
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
// Loads the types of the `server` route option; erased at build.
|
|
3
|
+
import type {} from '@tanstack/start-client-core'
|
|
4
|
+
import { asas, backofficeURL } from 'asas'
|
|
5
|
+
|
|
6
|
+
// The backoffice reads every organization: a read of a kayan asks `tenant: 'all'`, which the runner
|
|
7
|
+
// grants backoffice staff in this app alone and refuses anyone else. A global is one organization's row
|
|
8
|
+
// and asks for none.
|
|
9
|
+
const reads = new Set(asas.operations.filter((one) => one.effect === 'read' && !one.single).map((one) => `/api/${one.kayan}/${one.name}`))
|
|
10
|
+
|
|
11
|
+
// The body is handed on unread, so the plug that answers bounds it before any session is looked at.
|
|
12
|
+
const acrossTenants = (request: Request) => {
|
|
13
|
+
const url = new URL(request.url)
|
|
14
|
+
if (!reads.has(url.pathname)) return request
|
|
15
|
+
url.searchParams.set('tenant', 'all')
|
|
16
|
+
return new Request(url, { method: request.method, headers: request.headers, body: request.body, signal: request.signal, duplex: 'half' } as RequestInit)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// A write whose `Origin` is not BACKOFFICE_URL's is refused: the data plug checks no origin and leans on
|
|
20
|
+
// the session cookie's `SameSite=Lax`, which the customer app's sibling subdomain passes, so this check
|
|
21
|
+
// is the barrier between the two. Never the URL the request arrived on: behind a TLS proxy it is
|
|
22
|
+
// http://, and its host is whatever the Host header said.
|
|
23
|
+
const origin = new URL(backofficeURL).origin
|
|
24
|
+
|
|
25
|
+
export const Route = createFileRoute('/api/$')({
|
|
26
|
+
server: {
|
|
27
|
+
handlers: {
|
|
28
|
+
ANY: async ({ request }) =>
|
|
29
|
+
request.method === 'GET' || request.method === 'HEAD' || request.headers.get('origin') === origin
|
|
30
|
+
? asas.handler(acrossTenants(request))
|
|
31
|
+
: new Response('Forbidden', { status: 403 }),
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
})
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { useState, type ComponentProps } from 'react'
|
|
2
|
+
import { createFileRoute, redirect, useNavigate } from '@tanstack/react-router'
|
|
3
|
+
import { Button } from '@/components/ui/button'
|
|
4
|
+
import { authArm } from '../api.ts'
|
|
5
|
+
import { AuthBox, AuthFrame, authGate, notAnEmail, useRefusal } from '../auth-frame.tsx'
|
|
6
|
+
import { documentHead } from '../copy.ts'
|
|
7
|
+
import { firstUser } from '../first-user.ts'
|
|
8
|
+
import { useCopy } from './__root.tsx'
|
|
9
|
+
|
|
10
|
+
export const Route = createFileRoute('/auth/create-first-user')({
|
|
11
|
+
head: ({ match }) => documentHead(match.context, (t) => t('auth.createFirstUser')),
|
|
12
|
+
beforeLoad: async () => {
|
|
13
|
+
const state = await firstUser()
|
|
14
|
+
if (state.initialized) throw redirect({ to: '/auth/sign-in' })
|
|
15
|
+
return { code: state.code, password: (await authGate()).password }
|
|
16
|
+
},
|
|
17
|
+
component: CreateFirstUser,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
function CreateFirstUser() {
|
|
21
|
+
const navigate = useNavigate()
|
|
22
|
+
const { t } = useCopy()
|
|
23
|
+
const refused = useRefusal()
|
|
24
|
+
const { code: asked, password: bounds } = Route.useRouteContext()
|
|
25
|
+
const [name, setName] = useState('')
|
|
26
|
+
const [email, setEmail] = useState('')
|
|
27
|
+
const [password, setPassword] = useState('')
|
|
28
|
+
const [confirmation, setConfirmation] = useState('')
|
|
29
|
+
const [code, setCode] = useState('')
|
|
30
|
+
const [pending, setPending] = useState(false)
|
|
31
|
+
const [refusal, setRefusal] = useState<string | null>(null)
|
|
32
|
+
const [left, setLeft] = useState<ReadonlySet<string>>(() => new Set())
|
|
33
|
+
const required = t('auth.required', 'This field is required.')
|
|
34
|
+
const wrong = {
|
|
35
|
+
name: () => (name.trim() ? undefined : required),
|
|
36
|
+
email: () => (!email ? required : notAnEmail(email) ? t('auth.invalidEmail', 'Invalid email') : undefined),
|
|
37
|
+
password: () =>
|
|
38
|
+
!password ? required
|
|
39
|
+
: password.length < bounds.min ? t('auth.passwordTooShort', 'Password too short')
|
|
40
|
+
: password.length > bounds.max ? t('auth.passwordTooLong', 'Password too long')
|
|
41
|
+
: undefined,
|
|
42
|
+
confirmation: () => (!confirmation ? required : confirmation === password ? undefined : t('auth.passwordsDiffer', 'The two passwords are not the same.')),
|
|
43
|
+
code: () => (asked !== 'required' || code ? undefined : required),
|
|
44
|
+
}
|
|
45
|
+
const errorOf = (box: keyof typeof wrong) => (left.has(box) ? wrong[box]() : undefined)
|
|
46
|
+
const box = (id: keyof typeof wrong, label: string | undefined, props: ComponentProps<'input'> & { description?: string }) => (
|
|
47
|
+
<AuthBox id={id} label={label} error={errorOf(id)} onLeave={() => setLeft((already) => new Set(already).add(id))} {...props} />
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
const submit = async (event: React.FormEvent) => {
|
|
51
|
+
event.preventDefault()
|
|
52
|
+
setLeft(new Set(Object.keys(wrong)))
|
|
53
|
+
if (Object.values(wrong).some((check) => check())) return
|
|
54
|
+
setPending(true)
|
|
55
|
+
setRefusal(null)
|
|
56
|
+
const made = await authArm('first-register', { name, email, password, ...(asked === 'required' && { code }) })
|
|
57
|
+
setPending(false)
|
|
58
|
+
if (!made.ok) return setRefusal(refused(made, t('auth.createFirstUserFailed', 'That administrator could not be created.')))
|
|
59
|
+
await navigate({ to: '/auth/sign-in' })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (asked === 'unset') {
|
|
63
|
+
return (
|
|
64
|
+
<AuthFrame title={t('auth.createFirstUser', 'Create the first administrator')}>
|
|
65
|
+
<p className="text-sm" role="alert">
|
|
66
|
+
{t('auth.setupCodeUnset')}
|
|
67
|
+
</p>
|
|
68
|
+
</AuthFrame>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
return (
|
|
72
|
+
<AuthFrame
|
|
73
|
+
title={t('auth.createFirstUser', 'Create the first administrator')}
|
|
74
|
+
description={t('auth.createFirstUserDescription', 'No one administers this backoffice yet. The account made here becomes its first administrator.')}
|
|
75
|
+
>
|
|
76
|
+
<form className="grid gap-4" noValidate onSubmit={submit}>
|
|
77
|
+
{box('name', t('auth.name', 'Name'), { autoComplete: 'name', onChange: (event) => setName(event.target.value), type: 'text', value: name })}
|
|
78
|
+
{box('email', t('auth.email', 'Email'), { autoComplete: 'email', onChange: (event) => setEmail(event.target.value), type: 'email', value: email })}
|
|
79
|
+
{box('password', t('auth.password', 'Password'), {
|
|
80
|
+
autoComplete: 'new-password',
|
|
81
|
+
description: t('auth.passwordRule', 'At least {count} characters.')!.replace('{count}', String(bounds.min)),
|
|
82
|
+
onChange: (event) => setPassword(event.target.value),
|
|
83
|
+
type: 'password',
|
|
84
|
+
value: password,
|
|
85
|
+
})}
|
|
86
|
+
{box('confirmation', t('auth.confirmPassword', 'Confirm the password'), {
|
|
87
|
+
autoComplete: 'new-password',
|
|
88
|
+
onChange: (event) => setConfirmation(event.target.value),
|
|
89
|
+
type: 'password',
|
|
90
|
+
value: confirmation,
|
|
91
|
+
})}
|
|
92
|
+
{asked === 'required'
|
|
93
|
+
? box('code', t('auth.setupCode', 'Setup code'), {
|
|
94
|
+
autoComplete: 'off',
|
|
95
|
+
description: t('auth.setupCodeDescription', 'The SETUP_CODE this server was started with.'),
|
|
96
|
+
onChange: (event) => setCode(event.target.value),
|
|
97
|
+
type: 'password',
|
|
98
|
+
value: code,
|
|
99
|
+
})
|
|
100
|
+
: null}
|
|
101
|
+
{/* `role="alert"` so a refusal is spoken when it appears; nothing moves the focus to it. */}
|
|
102
|
+
{refusal && (
|
|
103
|
+
<p className="text-destructive text-sm" role="alert">
|
|
104
|
+
{refusal}
|
|
105
|
+
</p>
|
|
106
|
+
)}
|
|
107
|
+
<Button disabled={pending} type="submit">
|
|
108
|
+
{pending ? t('auth.createFirstUserPending', 'Creating…') : t('auth.createFirstUserSubmit', 'Create the administrator')}
|
|
109
|
+
</Button>
|
|
110
|
+
</form>
|
|
111
|
+
</AuthFrame>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { useState, type ComponentProps } from 'react'
|
|
2
|
-
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
|
2
|
+
import { createFileRoute, redirect, useNavigate } from '@tanstack/react-router'
|
|
3
3
|
import { Button } from '@/components/ui/button'
|
|
4
4
|
import { authArm } from '../api.ts'
|
|
5
5
|
import { AuthBox, AuthFrame, authGate, notAnEmail, redirectSearch, useRefusal } from '../auth-frame.tsx'
|
|
6
6
|
import { documentHead } from '../copy.ts'
|
|
7
|
+
import { firstUser } from '../first-user.ts'
|
|
7
8
|
import { useCopy } from './__root.tsx'
|
|
8
|
-
import {
|
|
9
|
+
import { landing } from '../redirect.ts'
|
|
9
10
|
|
|
10
11
|
export const Route = createFileRoute('/auth/sign-in')({
|
|
11
12
|
head: ({ match }) => documentHead(match.context, (t) => t('auth.signIn')),
|
|
12
13
|
validateSearch: redirectSearch,
|
|
14
|
+
// Nobody can sign in to a backoffice no one administers yet.
|
|
15
|
+
beforeLoad: async () => {
|
|
16
|
+
if (!(await firstUser()).initialized) throw redirect({ href: '/auth/create-first-user' })
|
|
17
|
+
},
|
|
13
18
|
component: SignIn,
|
|
14
19
|
})
|
|
15
20
|
|
|
@@ -49,11 +54,11 @@ function SignIn() {
|
|
|
49
54
|
setPending(false)
|
|
50
55
|
|
|
51
56
|
// A navigation, not a reload, so the screen's loader runs with the cookie just set.
|
|
52
|
-
await navigate({
|
|
57
|
+
await navigate({ href: landing({ asked: search.redirect, screens: (await authGate()).screens }) })
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
return (
|
|
56
|
-
<AuthFrame title={t('auth.signIn', 'Sign in')} description={t('auth.
|
|
61
|
+
<AuthFrame title={t('auth.signIn', 'Sign in')} description={t('auth.signInBackofficeDescription', 'Sign in to reach the backoffice.')}>
|
|
57
62
|
<form className="grid gap-4" noValidate onSubmit={submit}>
|
|
58
63
|
{box('email', t('auth.email', 'Email'), { autoComplete: 'email', onChange: (event) => setEmail(event.target.value), type: 'email', value: email })}
|
|
59
64
|
{box('password', t('auth.password', 'Password'), { autoComplete: 'current-password', onChange: (event) => setPassword(event.target.value), type: 'password', value: password })}
|
|
@@ -4,9 +4,17 @@ import { Button } from '@/components/ui/button'
|
|
|
4
4
|
import { authArm } from '../api.ts'
|
|
5
5
|
import { AuthFrame, useRefusal } from '../auth-frame.tsx'
|
|
6
6
|
import { documentHead } from '../copy.ts'
|
|
7
|
+
import { NoOrganization } from '../no-organization.tsx'
|
|
7
8
|
import { ImpersonationBar, loadEnterable } from '../screens.tsx'
|
|
8
9
|
import { useCopy } from './__root.tsx'
|
|
9
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Where someone sent here from a screen their role does not open goes without being asked: the one screen it does
|
|
13
|
+
* open. A page refused inside that screen is said instead, and so is an impersonation, whose bar is the way out.
|
|
14
|
+
*/
|
|
15
|
+
export const onwardOf = ({ screens, refused, impersonating }: { screens: readonly { path: string }[]; refused?: unknown; impersonating?: unknown }) =>
|
|
16
|
+
!refused && !impersonating && screens.length === 1 ? screens[0]!.path : undefined
|
|
17
|
+
|
|
10
18
|
export const Route = createFileRoute('/unauthorized')({
|
|
11
19
|
// The path a screen refused; the server only matches it against the build's pages.
|
|
12
20
|
validateSearch: (search: Record<string, unknown>): { from?: string } => (typeof search.from === 'string' ? { from: search.from } : {}),
|
|
@@ -15,6 +23,8 @@ export const Route = createFileRoute('/unauthorized')({
|
|
|
15
23
|
const entered = await loadEnterable({ data: { from: deps.from, locale: context.locale } })
|
|
16
24
|
// Carries this page's own path so a visitor whose session expired lands back here after signing in.
|
|
17
25
|
if (!entered) throw redirect({ to: '/auth/sign-in', search: { redirect: '/unauthorized' } })
|
|
26
|
+
const onward = onwardOf(entered)
|
|
27
|
+
if (onward) throw redirect({ href: onward })
|
|
18
28
|
return entered
|
|
19
29
|
},
|
|
20
30
|
// After the loader, so its data's type is inferred before this reads it.
|
|
@@ -49,19 +59,21 @@ function Unauthorized() {
|
|
|
49
59
|
: part === '{page}' ? <bdi key={at}>{refused?.page}</bdi>
|
|
50
60
|
: part)
|
|
51
61
|
const link = 'text-foreground underline underline-offset-4'
|
|
62
|
+
// Someone who belongs to no organization is shown the ways into one, which the app draws.
|
|
63
|
+
const unplaced = !refused && !screens.length && !organizations.length && !others.length && !impersonating
|
|
52
64
|
const screenName = (name: string) => t(`screens.${name}`, name) ?? name
|
|
53
65
|
return (
|
|
54
66
|
<AuthFrame
|
|
55
|
-
title={screens.length ? t('auth.unauthorized', '
|
|
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
|