create-mithaq 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/LICENSES/payload.txt +22 -0
- package/LICENSES/ponytail.txt +21 -0
- package/index.js +127 -0
- package/package.json +19 -4
- package/template/apps/asas-platform/components.json +25 -0
- package/template/apps/asas-platform/gitignore +3 -0
- package/template/apps/asas-platform/package.json +55 -0
- package/template/apps/asas-platform/public/favicon.svg +1 -0
- package/template/apps/asas-platform/src/api.ts +147 -0
- package/template/apps/asas-platform/src/auth-frame.tsx +150 -0
- package/template/apps/asas-platform/src/components/mithaq/cells.tsx +112 -0
- package/template/apps/asas-platform/src/components/mithaq/controls.tsx +650 -0
- package/template/apps/asas-platform/src/components/mithaq/field-shell.tsx +53 -0
- package/template/apps/asas-platform/src/components/mithaq/filter-operators.ts +108 -0
- package/template/apps/asas-platform/src/components/mithaq/form.tsx +1467 -0
- package/template/apps/asas-platform/src/components/mithaq/list-columns.tsx +108 -0
- package/template/apps/asas-platform/src/components/mithaq/list-filters.tsx +368 -0
- package/template/apps/asas-platform/src/components/mithaq/list-pagination.tsx +153 -0
- package/template/apps/asas-platform/src/components/mithaq/list-query.ts +432 -0
- package/template/apps/asas-platform/src/components/mithaq/list-selection.tsx +74 -0
- package/template/apps/asas-platform/src/components/mithaq/list.tsx +865 -0
- package/template/apps/asas-platform/src/components/mithaq/nav.tsx +871 -0
- package/template/apps/asas-platform/src/components/mithaq/nodes.tsx +1024 -0
- package/template/apps/asas-platform/src/components/mithaq/relationship-cell.tsx +127 -0
- package/template/apps/asas-platform/src/components/mithaq/relationship-control.tsx +400 -0
- package/template/apps/asas-platform/src/components/mithaq/rich-text-nodes.ts +6 -0
- package/template/apps/asas-platform/src/components/mithaq/rich-text.tsx +1083 -0
- package/template/apps/asas-platform/src/components/mithaq/upload.tsx +686 -0
- package/template/apps/asas-platform/src/components/ui/accordion.tsx +74 -0
- package/template/apps/asas-platform/src/components/ui/alert-dialog.tsx +185 -0
- package/template/apps/asas-platform/src/components/ui/avatar.tsx +106 -0
- package/template/apps/asas-platform/src/components/ui/badge.tsx +48 -0
- package/template/apps/asas-platform/src/components/ui/breadcrumb.tsx +124 -0
- package/template/apps/asas-platform/src/components/ui/button-group.tsx +87 -0
- package/template/apps/asas-platform/src/components/ui/button.tsx +55 -0
- package/template/apps/asas-platform/src/components/ui/calendar.tsx +223 -0
- package/template/apps/asas-platform/src/components/ui/card.tsx +102 -0
- package/template/apps/asas-platform/src/components/ui/chart.tsx +371 -0
- package/template/apps/asas-platform/src/components/ui/checkbox.tsx +28 -0
- package/template/apps/asas-platform/src/components/ui/collapsible.tsx +19 -0
- package/template/apps/asas-platform/src/components/ui/combobox.tsx +308 -0
- package/template/apps/asas-platform/src/components/ui/dialog.tsx +158 -0
- package/template/apps/asas-platform/src/components/ui/direction.tsx +6 -0
- package/template/apps/asas-platform/src/components/ui/directional-icon.tsx +20 -0
- package/template/apps/asas-platform/src/components/ui/drawer.tsx +216 -0
- package/template/apps/asas-platform/src/components/ui/dropdown-menu.tsx +267 -0
- package/template/apps/asas-platform/src/components/ui/field.tsx +236 -0
- package/template/apps/asas-platform/src/components/ui/input-group.tsx +155 -0
- package/template/apps/asas-platform/src/components/ui/input-otp.tsx +84 -0
- package/template/apps/asas-platform/src/components/ui/input.tsx +19 -0
- package/template/apps/asas-platform/src/components/ui/label.tsx +19 -0
- package/template/apps/asas-platform/src/components/ui/popover.tsx +90 -0
- package/template/apps/asas-platform/src/components/ui/radio-group.tsx +37 -0
- package/template/apps/asas-platform/src/components/ui/resizable.tsx +47 -0
- package/template/apps/asas-platform/src/components/ui/scroll-area.tsx +54 -0
- package/template/apps/asas-platform/src/components/ui/select.tsx +204 -0
- package/template/apps/asas-platform/src/components/ui/separator.tsx +24 -0
- package/template/apps/asas-platform/src/components/ui/sheet.tsx +139 -0
- package/template/apps/asas-platform/src/components/ui/sidebar.tsx +714 -0
- package/template/apps/asas-platform/src/components/ui/skeleton.tsx +13 -0
- package/template/apps/asas-platform/src/components/ui/slider.tsx +55 -0
- package/template/apps/asas-platform/src/components/ui/spinner.tsx +10 -0
- package/template/apps/asas-platform/src/components/ui/switch.tsx +29 -0
- package/template/apps/asas-platform/src/components/ui/table.tsx +115 -0
- package/template/apps/asas-platform/src/components/ui/tabs.tsx +79 -0
- package/template/apps/asas-platform/src/components/ui/textarea.tsx +17 -0
- package/template/apps/asas-platform/src/components/ui/toggle-group.tsx +89 -0
- package/template/apps/asas-platform/src/components/ui/toggle.tsx +44 -0
- package/template/apps/asas-platform/src/components/ui/tooltip.tsx +63 -0
- package/template/apps/asas-platform/src/copy.ts +1 -0
- package/template/apps/asas-platform/src/hooks/use-mobile.ts +19 -0
- package/template/apps/asas-platform/src/lib/utils.ts +1 -0
- package/template/apps/asas-platform/src/links.ts +38 -0
- package/template/apps/asas-platform/src/locale-switch.tsx +26 -0
- package/template/apps/asas-platform/src/redirect.ts +1 -0
- package/template/apps/asas-platform/src/request-headers.ts +11 -0
- package/template/apps/asas-platform/src/routeTree.gen.ts +181 -0
- package/template/apps/asas-platform/src/router.tsx +7 -0
- package/template/apps/asas-platform/src/routes/(screens)/platform/files.ts +4 -0
- package/template/apps/asas-platform/src/routes/(screens)/platform/screen.json +3609 -0
- package/template/apps/asas-platform/src/routes/(screens)/platform/types.ts +145 -0
- package/template/apps/asas-platform/src/routes/(screens)/platform.$.tsx +17 -0
- package/template/apps/asas-platform/src/routes/(screens)/platform.index.tsx +10 -0
- package/template/apps/asas-platform/src/routes/(screens)/platform.tsx +13 -0
- package/template/apps/asas-platform/src/routes/__root.tsx +95 -0
- package/template/apps/asas-platform/src/routes/api/$.ts +30 -0
- package/template/apps/asas-platform/src/routes/auth.sign-in.tsx +72 -0
- package/template/apps/asas-platform/src/routes/unauthorized.tsx +122 -0
- package/template/apps/asas-platform/src/screen-form.tsx +34 -0
- package/template/apps/asas-platform/src/screen-list.tsx +139 -0
- package/template/apps/asas-platform/src/screen-names.ts +10 -0
- package/template/apps/asas-platform/src/screens.tsx +868 -0
- package/template/apps/asas-platform/src/server.ts +20 -0
- package/template/apps/asas-platform/src/slide.ts +22 -0
- package/template/apps/asas-platform/src/start.ts +42 -0
- package/template/apps/asas-platform/src/styles.css +200 -0
- package/template/apps/asas-platform/src/theme.ts +2 -0
- package/template/apps/asas-platform/tsconfig.json +19 -0
- package/template/apps/asas-platform/vite.config.ts +26 -0
- package/template/apps/asas-tanstack/components.json +25 -0
- package/template/apps/asas-tanstack/gitignore +3 -0
- package/template/apps/asas-tanstack/package.json +56 -0
- package/template/apps/asas-tanstack/public/favicon.svg +1 -0
- package/template/apps/asas-tanstack/src/api.ts +147 -0
- package/template/apps/asas-tanstack/src/auth-frame.tsx +150 -0
- package/template/apps/asas-tanstack/src/components/mithaq/cells.tsx +112 -0
- package/template/apps/asas-tanstack/src/components/mithaq/controls.tsx +650 -0
- package/template/apps/asas-tanstack/src/components/mithaq/field-shell.tsx +53 -0
- package/template/apps/asas-tanstack/src/components/mithaq/filter-operators.ts +108 -0
- package/template/apps/asas-tanstack/src/components/mithaq/form.tsx +1467 -0
- package/template/apps/asas-tanstack/src/components/mithaq/list-columns.tsx +108 -0
- package/template/apps/asas-tanstack/src/components/mithaq/list-filters.tsx +368 -0
- package/template/apps/asas-tanstack/src/components/mithaq/list-pagination.tsx +153 -0
- package/template/apps/asas-tanstack/src/components/mithaq/list-query.ts +432 -0
- package/template/apps/asas-tanstack/src/components/mithaq/list-selection.tsx +74 -0
- package/template/apps/asas-tanstack/src/components/mithaq/list.tsx +865 -0
- package/template/apps/asas-tanstack/src/components/mithaq/nav.tsx +871 -0
- package/template/apps/asas-tanstack/src/components/mithaq/nodes.tsx +1024 -0
- package/template/apps/asas-tanstack/src/components/mithaq/relationship-cell.tsx +127 -0
- package/template/apps/asas-tanstack/src/components/mithaq/relationship-control.tsx +400 -0
- package/template/apps/asas-tanstack/src/components/mithaq/rich-text-nodes.ts +6 -0
- package/template/apps/asas-tanstack/src/components/mithaq/rich-text.tsx +1083 -0
- package/template/apps/asas-tanstack/src/components/mithaq/upload.tsx +686 -0
- package/template/apps/asas-tanstack/src/components/ui/accordion.tsx +10 -0
- package/template/apps/asas-tanstack/src/components/ui/alert-dialog.tsx +173 -0
- package/template/apps/asas-tanstack/src/components/ui/avatar.tsx +104 -0
- package/template/apps/asas-tanstack/src/components/ui/badge.tsx +48 -0
- package/template/apps/asas-tanstack/src/components/ui/breadcrumb.tsx +125 -0
- package/template/apps/asas-tanstack/src/components/ui/button-group.tsx +87 -0
- package/template/apps/asas-tanstack/src/components/ui/button.tsx +56 -0
- package/template/apps/asas-tanstack/src/components/ui/calendar.tsx +223 -0
- package/template/apps/asas-tanstack/src/components/ui/card.tsx +94 -0
- package/template/apps/asas-tanstack/src/components/ui/chart.tsx +369 -0
- package/template/apps/asas-tanstack/src/components/ui/checkbox.tsx +27 -0
- package/template/apps/asas-tanstack/src/components/ui/collapsible.tsx +19 -0
- package/template/apps/asas-tanstack/src/components/ui/combobox.tsx +300 -0
- package/template/apps/asas-tanstack/src/components/ui/dialog.tsx +149 -0
- package/template/apps/asas-tanstack/src/components/ui/direction.tsx +5 -0
- package/template/apps/asas-tanstack/src/components/ui/directional-icon.tsx +20 -0
- package/template/apps/asas-tanstack/src/components/ui/drawer.tsx +215 -0
- package/template/apps/asas-tanstack/src/components/ui/dropdown-menu.tsx +267 -0
- package/template/apps/asas-tanstack/src/components/ui/field.tsx +225 -0
- package/template/apps/asas-tanstack/src/components/ui/input-group.tsx +147 -0
- package/template/apps/asas-tanstack/src/components/ui/input-otp.tsx +20 -0
- package/template/apps/asas-tanstack/src/components/ui/input.tsx +20 -0
- package/template/apps/asas-tanstack/src/components/ui/label.tsx +18 -0
- package/template/apps/asas-tanstack/src/components/ui/popover.tsx +50 -0
- package/template/apps/asas-tanstack/src/components/ui/radio-group.tsx +10 -0
- package/template/apps/asas-tanstack/src/components/ui/resizable.tsx +47 -0
- package/template/apps/asas-tanstack/src/components/ui/scroll-area.tsx +52 -0
- package/template/apps/asas-tanstack/src/components/ui/select.tsx +190 -0
- package/template/apps/asas-tanstack/src/components/ui/separator.tsx +23 -0
- package/template/apps/asas-tanstack/src/components/ui/sheet.tsx +127 -0
- package/template/apps/asas-tanstack/src/components/ui/sidebar.tsx +716 -0
- package/template/apps/asas-tanstack/src/components/ui/skeleton.tsx +13 -0
- package/template/apps/asas-tanstack/src/components/ui/slider.tsx +13 -0
- package/template/apps/asas-tanstack/src/components/ui/spinner.tsx +11 -0
- package/template/apps/asas-tanstack/src/components/ui/switch.tsx +8 -0
- package/template/apps/asas-tanstack/src/components/ui/table.tsx +119 -0
- package/template/apps/asas-tanstack/src/components/ui/tabs.tsx +80 -0
- package/template/apps/asas-tanstack/src/components/ui/textarea.tsx +18 -0
- package/template/apps/asas-tanstack/src/components/ui/toggle-group.tsx +8 -0
- package/template/apps/asas-tanstack/src/components/ui/toggle.tsx +44 -0
- package/template/apps/asas-tanstack/src/components/ui/tooltip.tsx +68 -0
- package/template/apps/asas-tanstack/src/copy.ts +870 -0
- package/template/apps/asas-tanstack/src/hooks/use-mobile.ts +19 -0
- package/template/apps/asas-tanstack/src/lib/utils.ts +6 -0
- package/template/apps/asas-tanstack/src/links.ts +38 -0
- package/template/apps/asas-tanstack/src/locale-switch.tsx +26 -0
- package/template/apps/asas-tanstack/src/quiet-abort.ts +25 -0
- package/template/apps/asas-tanstack/src/redirect.ts +20 -0
- package/template/apps/asas-tanstack/src/request-headers.ts +11 -0
- package/template/apps/asas-tanstack/src/routeTree.gen.ts +382 -0
- package/template/apps/asas-tanstack/src/router.tsx +7 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/files.ts +4 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/screen.json +3883 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/types.ts +158 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard.$.tsx +17 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard.index.tsx +10 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/dashboard.tsx +13 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/portal/files.ts +4 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/portal/screen.json +1800 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/portal/types.ts +92 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/portal.$.tsx +17 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/portal.index.tsx +10 -0
- package/template/apps/asas-tanstack/src/routes/(screens)/portal.tsx +13 -0
- package/template/apps/asas-tanstack/src/routes/__root.tsx +99 -0
- package/template/apps/asas-tanstack/src/routes/api/$.ts +11 -0
- package/template/apps/asas-tanstack/src/routes/auth.accept-invitation.tsx +101 -0
- package/template/apps/asas-tanstack/src/routes/auth.forgot-password.tsx +94 -0
- package/template/apps/asas-tanstack/src/routes/auth.handoff.ts +39 -0
- package/template/apps/asas-tanstack/src/routes/auth.reset-password.tsx +109 -0
- package/template/apps/asas-tanstack/src/routes/auth.sign-in.tsx +86 -0
- package/template/apps/asas-tanstack/src/routes/auth.sign-up.tsx +122 -0
- package/template/apps/asas-tanstack/src/routes/index.tsx +142 -0
- package/template/apps/asas-tanstack/src/routes/unauthorized.tsx +122 -0
- package/template/apps/asas-tanstack/src/screen-form.tsx +34 -0
- package/template/apps/asas-tanstack/src/screen-list.tsx +139 -0
- package/template/apps/asas-tanstack/src/screen-names.ts +10 -0
- package/template/apps/asas-tanstack/src/screens.tsx +868 -0
- package/template/apps/asas-tanstack/src/server.ts +16 -0
- package/template/apps/asas-tanstack/src/slide.ts +22 -0
- package/template/apps/asas-tanstack/src/start.ts +57 -0
- package/template/apps/asas-tanstack/src/styles.css +159 -0
- package/template/apps/asas-tanstack/src/theme.ts +5 -0
- package/template/apps/asas-tanstack/tsconfig.json +19 -0
- package/template/apps/asas-tanstack/vite.config.ts +28 -0
- package/template/package.json +16 -0
- package/template/templates/asas/.env.example +16 -0
- package/template/templates/asas/components.json +22 -0
- package/template/templates/asas/mithaq.hints.json +25 -0
- package/template/templates/asas/package.json +38 -0
- package/template/templates/asas/src/asas.ts +93 -0
- package/template/templates/asas/src/audit-log/audit-log.ts +187 -0
- package/template/templates/asas/src/audit-log/sink.ts +89 -0
- package/template/templates/asas/src/audit-log/ui/activity.tsx +243 -0
- package/template/templates/asas/src/courses/courses.ts +612 -0
- package/template/templates/asas/src/courses/ui/archive-button.tsx +25 -0
- package/template/templates/asas/src/courses/ui/fee-cell.tsx +19 -0
- package/template/templates/asas/src/courses/ui/no-courses-on-offer.tsx +11 -0
- package/template/templates/asas/src/courses/ui/no-courses.tsx +13 -0
- package/template/templates/asas/src/courses/ui/on-offer.ts +6 -0
- package/template/templates/asas/src/courses/ui/status-cell.tsx +31 -0
- package/template/templates/asas/src/enrollments/enrollments.ts +137 -0
- package/template/templates/asas/src/enrollments/ui/session-cell.tsx +65 -0
- package/template/templates/asas/src/kayan/auth/user/account.access.ts +8 -0
- package/template/templates/asas/src/kayan/auth/user/account.ts +31 -0
- package/template/templates/asas/src/kayan/auth/user/index.ts +18 -0
- package/template/templates/asas/src/kayan/auth/user/invitation.access.ts +4 -0
- package/template/templates/asas/src/kayan/auth/user/invitation.ts +27 -0
- package/template/templates/asas/src/kayan/auth/user/member.access.ts +10 -0
- package/template/templates/asas/src/kayan/auth/user/member.screens.ts +37 -0
- package/template/templates/asas/src/kayan/auth/user/member.ts +25 -0
- package/template/templates/asas/src/kayan/auth/user/organization.access.ts +20 -0
- package/template/templates/asas/src/kayan/auth/user/organization.screens.ts +37 -0
- package/template/templates/asas/src/kayan/auth/user/organization.ts +24 -0
- package/template/templates/asas/src/kayan/auth/user/organizationRole.access.ts +8 -0
- package/template/templates/asas/src/kayan/auth/user/organizationRole.ts +25 -0
- package/template/templates/asas/src/kayan/auth/user/session.access.ts +8 -0
- package/template/templates/asas/src/kayan/auth/user/session.ts +28 -0
- package/template/templates/asas/src/kayan/auth/user/user.access.ts +29 -0
- package/template/templates/asas/src/kayan/auth/user/user.screens.ts +45 -0
- package/template/templates/asas/src/kayan/auth/user/user.ts +31 -0
- package/template/templates/asas/src/kayan/auth/user/verification.access.ts +8 -0
- package/template/templates/asas/src/kayan/auth/user/verification.ts +23 -0
- package/template/templates/asas/src/local-users/local-users.ts +143 -0
- package/template/templates/asas/src/media/media.ts +137 -0
- package/template/templates/asas/src/media/ui/preview.tsx +143 -0
- package/template/templates/asas/src/member-roles/member-roles.ts +207 -0
- package/template/templates/asas/src/messages/account.json +106 -0
- package/template/templates/asas/src/messages/auditLog.json +186 -0
- package/template/templates/asas/src/messages/courses.json +270 -0
- package/template/templates/asas/src/messages/enrollments.json +42 -0
- package/template/templates/asas/src/messages/invitation.json +66 -0
- package/template/templates/asas/src/messages/localUsers.json +42 -0
- package/template/templates/asas/src/messages/media.json +66 -0
- package/template/templates/asas/src/messages/member.json +42 -0
- package/template/templates/asas/src/messages/memberRoles.json +42 -0
- package/template/templates/asas/src/messages/mithaq.json +170 -0
- package/template/templates/asas/src/messages/organization.json +50 -0
- package/template/templates/asas/src/messages/organizationRole.json +50 -0
- package/template/templates/asas/src/messages/roles.json +58 -0
- package/template/templates/asas/src/messages/session.json +82 -0
- package/template/templates/asas/src/messages/sessions.json +106 -0
- package/template/templates/asas/src/messages/settings.json +66 -0
- package/template/templates/asas/src/messages/tags.json +38 -0
- package/template/templates/asas/src/messages/user.json +90 -0
- package/template/templates/asas/src/messages/verification.json +50 -0
- package/template/templates/asas/src/migrations/20260920080701_parched_tombstone/migration.sql +177 -0
- package/template/templates/asas/src/migrations/20260920080701_parched_tombstone/snapshot.json +2169 -0
- package/template/templates/asas/src/migrations/20260920230505_zippy_ben_urich/migration.sql +1 -0
- package/template/templates/asas/src/migrations/20260920230505_zippy_ben_urich/snapshot.json +2182 -0
- package/template/templates/asas/src/migrations/20260920231219_slim_retro_girl/migration.sql +20 -0
- package/template/templates/asas/src/migrations/20260920231219_slim_retro_girl/snapshot.json +2409 -0
- package/template/templates/asas/src/migrations/20260922082945_military_ironclad/migration.sql +15 -0
- package/template/templates/asas/src/migrations/20260922082945_military_ironclad/snapshot.json +2627 -0
- package/template/templates/asas/src/migrations/20260922112630_bitter_chat/migration.sql +5 -0
- package/template/templates/asas/src/migrations/20260922112630_bitter_chat/snapshot.json +2691 -0
- package/template/templates/asas/src/migrations/20260923235915_foamy_rachel_grey/migration.sql +8 -0
- package/template/templates/asas/src/migrations/20260923235915_foamy_rachel_grey/snapshot.json +2795 -0
- package/template/templates/asas/src/migrations/20260926145709_clean_the_initiative/migration.sql +3 -0
- package/template/templates/asas/src/migrations/20260926145709_clean_the_initiative/snapshot.json +2834 -0
- package/template/templates/asas/src/migrations/20260927212525_bent_prowler/migration.sql +9 -0
- package/template/templates/asas/src/migrations/20260927212525_bent_prowler/snapshot.json +2870 -0
- package/template/templates/asas/src/migrations/20260927223441_tranquil_bromley/migration.sql +1 -0
- package/template/templates/asas/src/migrations/20260927223441_tranquil_bromley/snapshot.json +2881 -0
- package/template/templates/asas/src/migrations/20260928030047_outgoing_wraith/migration.sql +15 -0
- package/template/templates/asas/src/migrations/20260928030047_outgoing_wraith/snapshot.json +3073 -0
- package/template/templates/asas/src/migrations/20260929024407_grey_morlun/migration.sql +21 -0
- package/template/templates/asas/src/migrations/20260929024407_grey_morlun/snapshot.json +3098 -0
- package/template/templates/asas/src/migrations/20260929130530_bored_jackal/migration.sql +21 -0
- package/template/templates/asas/src/migrations/20260929130530_bored_jackal/snapshot.json +3124 -0
- package/template/templates/asas/src/migrations/20260930102924_material_newton_destine/migration.sql +32 -0
- package/template/templates/asas/src/migrations/20260930102924_material_newton_destine/snapshot.json +3324 -0
- package/template/templates/asas/src/migrations/20261003022353_clumsy_taskmaster/migration.sql +2 -0
- package/template/templates/asas/src/migrations/20261003022353_clumsy_taskmaster/snapshot.json +3350 -0
- package/template/templates/asas/src/migrations/20261003143249_premium_red_shift/migration.sql +21 -0
- package/template/templates/asas/src/migrations/20261003143249_premium_red_shift/snapshot.json +3635 -0
- package/template/templates/asas/src/migrations/20261008215349_square_norrin_radd/migration.sql +8 -0
- package/template/templates/asas/src/migrations/20261008215349_square_norrin_radd/snapshot.json +3635 -0
- package/template/templates/asas/src/migrations/20261008230204_many_scarecrow/migration.sql +18 -0
- package/template/templates/asas/src/migrations/20261008230204_many_scarecrow/snapshot.json +3635 -0
- package/template/templates/asas/src/migrations/20261010005521_elite_giant_girl/migration.sql +18 -0
- package/template/templates/asas/src/migrations/20261010005521_elite_giant_girl/snapshot.json +3635 -0
- package/template/templates/asas/src/migrations/20261010120822_needy_thunderbolt/migration.sql +11 -0
- package/template/templates/asas/src/migrations/20261010120822_needy_thunderbolt/snapshot.json +3667 -0
- package/template/templates/asas/src/migrations/20261012000752_organization_ownership/migration.sql +48 -0
- package/template/templates/asas/src/migrations/20261012000752_organization_ownership/snapshot.json +3891 -0
- package/template/templates/asas/src/migrations/drizzle.config.ts +11 -0
- package/template/templates/asas/src/migrations/schema.ts +29 -0
- package/template/templates/asas/src/mithaq.ts +406 -0
- package/template/templates/asas/src/pages.ts +74 -0
- package/template/templates/asas/src/platform-http.ts +96 -0
- package/template/templates/asas/src/rich-text-nodes.ts +11 -0
- package/template/templates/asas/src/roles/grants.ts +47 -0
- package/template/templates/asas/src/roles/roles.ts +184 -0
- package/template/templates/asas/src/roles/ui/role-form.tsx +226 -0
- package/template/templates/asas/src/roles/ui/roles.tsx +166 -0
- package/template/templates/asas/src/roles.ts +16 -0
- package/template/templates/asas/src/seed.ts +325 -0
- package/template/templates/asas/src/sessions/sessions.ts +155 -0
- package/template/templates/asas/src/settings/settings.ts +69 -0
- package/template/templates/asas/src/statements.ts +11 -0
- package/template/templates/asas/src/steps/expected-updated-at.ts +41 -0
- package/template/templates/asas/src/storage/defaults.ts +156 -0
- package/template/templates/asas/src/storage/index.ts +95 -0
- package/template/templates/asas/src/storage/local.ts +143 -0
- package/template/templates/asas/src/storage/r2.ts +5 -0
- package/template/templates/asas/src/storage/s3.ts +91 -0
- package/template/templates/asas/src/storage/uploadthing.ts +59 -0
- package/template/templates/asas/src/tags/tags.ts +80 -0
- package/template/templates/asas/src/tenant-access.ts +221 -0
- package/template/templates/asas/src/tenant-roles.ts +262 -0
- package/template/templates/asas/src/ui/app-shell.tsx +260 -0
- package/template/templates/asas/src/ui/kpi-card.tsx +25 -0
- package/template/templates/asas/src/ui/record-page.tsx +50 -0
- package/template/templates/asas/src/ui/rich-text-nodes.tsx +15 -0
- package/template/templates/asas/src/ui/screen-nav.tsx +28 -0
- package/template/templates/asas/src/ui/session-labels.tsx +36 -0
- package/template/templates/asas/src/ui/table-page.tsx +9 -0
- package/template/templates/asas/src/ui/top-shell.tsx +60 -0
- package/template/templates/asas/src/ui-dashboard/dashboard.tsx +294 -0
- package/template/templates/asas/src/ui-dashboard/members.tsx +630 -0
- package/template/templates/asas/src/ui-dashboard/theme-provider.tsx +10 -0
- package/template/templates/asas/src/ui-dashboard/versions.tsx +432 -0
- package/template/templates/asas/src/ui-platform/home.tsx +63 -0
- package/template/templates/asas/src/ui-platform/organization-actions.tsx +114 -0
- package/template/templates/asas/src/ui-platform/record-page.tsx +30 -0
- package/template/templates/asas/src/ui-platform/role-cells.tsx +52 -0
- package/template/templates/asas/src/ui-platform/user-actions.tsx +251 -0
- package/template/templates/asas/src/ui-portal/home.tsx +77 -0
- package/template/templates/asas/src/ui-portal/page.tsx +11 -0
- package/template/templates/auth/package.json +14 -0
- package/template/templates/auth/src/index.ts +690 -0
- package/template/templates/auth/src/platform-roles.ts +87 -0
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 drhomet
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018-2026 Payload CMS, LLC <info@payloadcms.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DietrichGebert
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/index.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `bun create mithaq my-app`: lays a new project down from the template this
|
|
3
|
+
// package carries, at the version of `@drhomet/mithaq` this package was
|
|
4
|
+
// published with. Plain JavaScript on Node's standard library, so `bunx` and
|
|
5
|
+
// `npx` run it with nothing to install first.
|
|
6
|
+
import { spawnSync } from 'node:child_process'
|
|
7
|
+
import { randomBytes } from 'node:crypto'
|
|
8
|
+
import { cpSync, existsSync, readdirSync, readFileSync, realpathSync, renameSync, writeFileSync } from 'node:fs'
|
|
9
|
+
import { basename, dirname, join } from 'node:path'
|
|
10
|
+
import { createInterface } from 'node:readline/promises'
|
|
11
|
+
import { fileURLToPath } from 'node:url'
|
|
12
|
+
import { parseArgs } from 'node:util'
|
|
13
|
+
|
|
14
|
+
const readJson = (file) => JSON.parse(readFileSync(file, 'utf8'))
|
|
15
|
+
const writeJson = (file, value) => writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`)
|
|
16
|
+
|
|
17
|
+
const generateSecret = () => randomBytes(32).toString('hex')
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Pins the template's manifests to what a published project installs. It edits
|
|
21
|
+
* `devDependencies` too, where the template keeps `mithaq-admin`. `mithaq`
|
|
22
|
+
* becomes the published package under the name every import uses, and
|
|
23
|
+
* `mithaq-admin`, which only the framework's own repository has, goes, since a
|
|
24
|
+
* project reads the registry through `@mithaq`; `asas` and `@asas/auth` stay
|
|
25
|
+
* `"*"`, the project's own workspaces. Any other `workspace:`, `link:` or
|
|
26
|
+
* `file:` spec is an error rather than a pin, because nothing published stands
|
|
27
|
+
* behind it.
|
|
28
|
+
*/
|
|
29
|
+
export function updatePackageJSONDependencies({ mithaq, packageJson }) {
|
|
30
|
+
for (const dependencies of [packageJson.dependencies, packageJson.devDependencies]) {
|
|
31
|
+
if (!dependencies) continue
|
|
32
|
+
if ('mithaq' in dependencies) dependencies.mithaq = mithaq
|
|
33
|
+
delete dependencies['mithaq-admin']
|
|
34
|
+
for (const [name, spec] of Object.entries(dependencies)) {
|
|
35
|
+
if (spec !== mithaq && /^(workspace|link|file):/.test(spec)) {
|
|
36
|
+
throw new Error(`The template's dependency ${name} is ${spec}, which only resolves inside the mithaq repository.`)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Writes the project `name` under `cwd` from `template`, and answers its
|
|
44
|
+
* folder. `version` is the `@drhomet/mithaq` the project installs and whose
|
|
45
|
+
* registry it reads, pinned exactly so the two cannot drift apart and an
|
|
46
|
+
* upgrade is a change the project makes; `mithaq` replaces the dependency
|
|
47
|
+
* itself, which only the release check does.
|
|
48
|
+
*/
|
|
49
|
+
export function create({ name, cwd, template, version, mithaq = `npm:@drhomet/mithaq@${version}` }) {
|
|
50
|
+
// A package name npm accepts, so it is also one folder, inside `cwd`.
|
|
51
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/.test(name) || name.length > 214) {
|
|
52
|
+
throw new Error(`"${name}" is not a valid project name: use lowercase letters, digits, "-", "." and "_", starting with a letter or digit.`)
|
|
53
|
+
}
|
|
54
|
+
const project = join(cwd, name)
|
|
55
|
+
if (existsSync(project) && readdirSync(project).length > 0) {
|
|
56
|
+
throw new Error(`${project} already exists and is not empty. Pick another name.`)
|
|
57
|
+
}
|
|
58
|
+
cpSync(template, project, { recursive: true })
|
|
59
|
+
const files = readdirSync(project, { recursive: true })
|
|
60
|
+
// npm drops every `.gitignore` from a published package, so the stage keeps
|
|
61
|
+
// each as `gitignore`.
|
|
62
|
+
for (const file of files.filter((file) => basename(file) === 'gitignore')) {
|
|
63
|
+
renameSync(join(project, file), join(project, dirname(file), '.gitignore'))
|
|
64
|
+
}
|
|
65
|
+
for (const file of files.filter((file) => basename(file) === 'package.json')) {
|
|
66
|
+
const packageJson = readJson(join(project, file))
|
|
67
|
+
updatePackageJSONDependencies({ mithaq, packageJson })
|
|
68
|
+
writeJson(join(project, file), file === 'package.json' ? { name, ...packageJson } : packageJson)
|
|
69
|
+
}
|
|
70
|
+
// shadcn reads `@mithaq/<item>` from this URL with `{name}` replaced, at the
|
|
71
|
+
// version installed.
|
|
72
|
+
for (const file of files.filter((file) => basename(file) === 'components.json')) {
|
|
73
|
+
const components = readJson(join(project, file))
|
|
74
|
+
writeJson(join(project, file), { ...components, registries: { ...components.registries, '@mithaq': `https://cdn.jsdelivr.net/npm/@drhomet/mithaq@${version}/r/{name}.json` } })
|
|
75
|
+
}
|
|
76
|
+
// One `.env` for the template's commands and one for each app's dev server,
|
|
77
|
+
// the same secrets in each: the tenant app and the platform console sign
|
|
78
|
+
// and read each other's sessions under one `BETTER_AUTH_SECRET`.
|
|
79
|
+
const env = readFileSync(join(project, 'templates/asas/.env.example'), 'utf8').replace(/^([A-Z_]*SECRET)=$/gm, (_, key) => `${key}=${generateSecret()}`)
|
|
80
|
+
for (const folder of ['templates/asas', 'apps/asas-tanstack', 'apps/asas-platform']) writeFileSync(join(project, folder, '.env'), env, { mode: 0o600 })
|
|
81
|
+
// The screens find their files by globs under `node_modules/asas/`, which
|
|
82
|
+
// only Bun's isolated installer lays out.
|
|
83
|
+
writeFileSync(join(project, 'bunfig.toml'), '[install]\nlinker = "isolated"\n')
|
|
84
|
+
writeFileSync(join(project, '.gitignore'), ['node_modules', '.env', 'dist', '.storage/', ''].join('\n'))
|
|
85
|
+
writeJson(join(project, 'tsconfig.json'), {
|
|
86
|
+
compilerOptions: { target: 'es2024', module: 'nodenext', moduleResolution: 'nodenext', strict: true, noUncheckedIndexedAccess: true, noEmit: true, skipLibCheck: true, erasableSyntaxOnly: true, allowImportingTsExtensions: true, types: ['node'] },
|
|
87
|
+
include: ['templates/*/src/**/*.ts', 'apps/*/src/**/*.ts'],
|
|
88
|
+
})
|
|
89
|
+
return project
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const main = async () => {
|
|
93
|
+
const { values, positionals } = parseArgs({ allowPositionals: true, options: { mithaq: { type: 'string' } } })
|
|
94
|
+
let [name] = positionals
|
|
95
|
+
if (!name) {
|
|
96
|
+
const prompt = createInterface({ input: process.stdin, output: process.stdout })
|
|
97
|
+
name = (await prompt.question('Project name: ')).trim()
|
|
98
|
+
prompt.close()
|
|
99
|
+
}
|
|
100
|
+
const { version } = readJson(new URL('package.json', import.meta.url))
|
|
101
|
+
const project = create({ name, cwd: process.cwd(), template: fileURLToPath(new URL('template/', import.meta.url)), version, mithaq: values.mithaq })
|
|
102
|
+
console.log(`Created ${project}.`)
|
|
103
|
+
// `bunx` and `bun create` name Bun here (`bun/1.3.14 npm/? node/…`).
|
|
104
|
+
const installed = process.env.npm_config_user_agent?.startsWith('bun/') && spawnSync('bun', ['install'], { cwd: project, stdio: 'inherit' }).status === 0
|
|
105
|
+
if (!installed) {
|
|
106
|
+
console.warn(`\nNot installed. Install with Bun: the project is a Bun workspace whose screens need Bun's isolated installer (bunfig.toml), and npm, pnpm and yarn lay node_modules out where they find nothing.`)
|
|
107
|
+
}
|
|
108
|
+
console.log(`
|
|
109
|
+
Next, with PostgreSQL 15 or later serving the database DATABASE_URL names in templates/asas/.env:
|
|
110
|
+
|
|
111
|
+
cd ${name}${installed ? '' : '\n bun install'}
|
|
112
|
+
cd templates/asas
|
|
113
|
+
# push the schema once, development only
|
|
114
|
+
bun -e "const { asas } = await import('./src/asas.ts'); await asas.connect(); await asas.destroy()"
|
|
115
|
+
cd ../..
|
|
116
|
+
bun run --cwd apps/asas-tanstack dev # the tenant app, http://localhost:5173
|
|
117
|
+
bun run --cwd apps/asas-platform dev # in a second terminal: the platform console, http://localhost:5174
|
|
118
|
+
|
|
119
|
+
Sign up admin@admin.com with the organization "Admin org", then run bun run seed in templates/asas.`)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
123
|
+
await main().catch((error) => {
|
|
124
|
+
console.error(error.message)
|
|
125
|
+
process.exitCode = 1
|
|
126
|
+
})
|
|
127
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-mithaq",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-mithaq": "index.js"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=24"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/drhomet/mithaq.git",
|
|
15
|
+
"directory": "packages/create-mithaq"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/drhomet/mithaq#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/drhomet/mithaq/issues"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "base-sera",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/styles.css",
|
|
9
|
+
"baseColor": "mauve",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"iconLibrary": "lucide",
|
|
14
|
+
"rtl": true,
|
|
15
|
+
"aliases": {
|
|
16
|
+
"components": "@/components",
|
|
17
|
+
"utils": "@/lib/utils",
|
|
18
|
+
"ui": "@/components/ui",
|
|
19
|
+
"lib": "@/lib",
|
|
20
|
+
"hooks": "@/hooks"
|
|
21
|
+
},
|
|
22
|
+
"menuColor": "default",
|
|
23
|
+
"menuAccent": "subtle",
|
|
24
|
+
"registries": {}
|
|
25
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "asas-platform",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "(cd ../../templates/asas && mithaq lock --check) && vite build",
|
|
8
|
+
"dev": "(cd ../../templates/asas && mithaq lock --check) && vite dev --port 5174",
|
|
9
|
+
"preview": "(cd ../../templates/asas && mithaq lock --check) && vite preview --port 4174"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@base-ui/react": "^1.5.0",
|
|
13
|
+
"@fontsource-variable/figtree": "^5.3.0",
|
|
14
|
+
"@fontsource-variable/noto-sans": "^5.3.0",
|
|
15
|
+
"@fontsource/ibm-plex-sans-arabic": "^5.2.8",
|
|
16
|
+
"@hugeicons/core-free-icons": "^4.1.4",
|
|
17
|
+
"@hugeicons/react": "^1.1.6",
|
|
18
|
+
"@lexical/extension": "0.52.0",
|
|
19
|
+
"@lexical/link": "0.52.0",
|
|
20
|
+
"@lexical/list": "0.52.0",
|
|
21
|
+
"@lexical/react": "0.52.0",
|
|
22
|
+
"@lexical/rich-text": "0.52.0",
|
|
23
|
+
"@lexical/selection": "0.52.0",
|
|
24
|
+
"@lexical/utils": "0.52.0",
|
|
25
|
+
"@tanstack/react-form": "^1.33.5",
|
|
26
|
+
"@tanstack/react-query": "^5.104.0",
|
|
27
|
+
"@tanstack/react-router": "^1.170.29",
|
|
28
|
+
"@tanstack/react-start": "^1.168.56",
|
|
29
|
+
"asas": "*",
|
|
30
|
+
"class-variance-authority": "^0.7.1",
|
|
31
|
+
"cn": "^0.2.6",
|
|
32
|
+
"date-fns": "^4.4.0",
|
|
33
|
+
"input-otp": "^1.5.0",
|
|
34
|
+
"lexical": "0.52.0",
|
|
35
|
+
"lucide-react": "^1.16.0",
|
|
36
|
+
"mithaq": "*",
|
|
37
|
+
"react": "^19.3.0",
|
|
38
|
+
"react-day-picker": "^10.0.1",
|
|
39
|
+
"react-dom": "^19.3.0",
|
|
40
|
+
"react-resizable-panels": "^4.14.1",
|
|
41
|
+
"recharts": "3.8.0",
|
|
42
|
+
"tw-animate-css": "^1.4.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@tailwindcss/vite": "^4.3.0",
|
|
46
|
+
"@tanstack/start-client-core": "^1.170.1",
|
|
47
|
+
"@types/react": "^19.3.0",
|
|
48
|
+
"@types/react-dom": "^19.3.0",
|
|
49
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
50
|
+
"mithaq-admin": "*",
|
|
51
|
+
"shadcn": "4.18.0",
|
|
52
|
+
"tailwindcss": "^4.3.0",
|
|
53
|
+
"vite": "^8.0.13"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" fill="#5b21b6"/><path d="M16 5l9 4v6c0 6-4 10-9 12-5-2-9-6-9-12V9z" fill="none" stroke="#f5f3ff" stroke-width="2.5" stroke-linejoin="round"/><path d="M12 16l3 3 5-6" fill="none" stroke="#f5f3ff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { afterCommitHeader, type ClientKayan, type FetchClient } from 'mithaq/admin'
|
|
2
|
+
|
|
3
|
+
/** What a write may ask beside its input on a kayan that keeps drafts: a draft save, and an autosave. The fetch plug reads them off the query string only. */
|
|
4
|
+
export type DraftOptions = { readonly draft?: boolean; readonly autosave?: boolean }
|
|
5
|
+
|
|
6
|
+
const queryOf = ({ draft, autosave }: DraftOptions = {}) => (draft ? `?draft=true${autosave ? '&autosave=true' : ''}` : '')
|
|
7
|
+
|
|
8
|
+
const call = async ({ kayan, operation, input, ...options }: { kayan: string; operation: string; input: Record<string, unknown> } & DraftOptions) => {
|
|
9
|
+
const response = await fetch(`/api/${encodeURIComponent(kayan)}/${encodeURIComponent(operation)}${queryOf(options)}`, {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
headers: { 'content-type': 'application/json' },
|
|
12
|
+
body: JSON.stringify(input),
|
|
13
|
+
})
|
|
14
|
+
const answer = await response.json().catch(() => null)
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
const error = new Error(typeof answer?.message === 'string' ? answer.message : `HTTP ${response.status}`)
|
|
17
|
+
Object.assign(error, { status: response.status, code: answer?.code, data: answer?.data })
|
|
18
|
+
throw error
|
|
19
|
+
}
|
|
20
|
+
// A failed `afterCommit` raises the screens' banner and a committed write
|
|
21
|
+
// clears it. Only the server knows an operation's effect, so the plug sets
|
|
22
|
+
// the header on writes and never on reads.
|
|
23
|
+
const said = response.headers.get(afterCommitHeader)
|
|
24
|
+
if (typeof window !== 'undefined' && said) {
|
|
25
|
+
window.dispatchEvent(new CustomEvent('mithaq:after-commit', { detail: said === 'failed' }))
|
|
26
|
+
}
|
|
27
|
+
return answer
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Restated, not imported from the `.tsx` file that declares them: a `.ts` file
|
|
31
|
+
// must not need the JSX compiler.
|
|
32
|
+
type FoundPage = { docs: readonly Record<string, unknown>[]; hasNextPage: boolean; nextPage: number | null }
|
|
33
|
+
type FindApi = Readonly<
|
|
34
|
+
Record<
|
|
35
|
+
string,
|
|
36
|
+
| {
|
|
37
|
+
find: (input: Record<string, unknown>) => Promise<FoundPage>
|
|
38
|
+
create?: (input: Record<string, unknown>) => Promise<unknown>
|
|
39
|
+
delete?: (input: Record<string, unknown>) => Promise<unknown>
|
|
40
|
+
update?: (input: Record<string, unknown>) => Promise<unknown>
|
|
41
|
+
}
|
|
42
|
+
| undefined
|
|
43
|
+
>
|
|
44
|
+
>
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Every operation of every kayan, keyed by name strings. `arm[kayan][operation](input)`
|
|
48
|
+
* posts to `POST /api/<kayan>/<operation>`; the proxy never answers `undefined`,
|
|
49
|
+
* and a name no kayan has is the server's not-found.
|
|
50
|
+
*/
|
|
51
|
+
export type Arm = Readonly<Record<string, Readonly<Record<string, (input: Record<string, unknown>, options?: DraftOptions) => Promise<unknown>>>>>
|
|
52
|
+
export const arm = new Proxy(Object.create(null) as Arm, {
|
|
53
|
+
get: (_target, kayan: string) => new Proxy(Object.create(null) as Record<string, (input: Record<string, unknown>, options?: DraftOptions) => Promise<unknown>>, {
|
|
54
|
+
get: (_operations, operation: string) => (input: Record<string, unknown>, options?: DraftOptions) => call({ kayan, operation, input, ...options }),
|
|
55
|
+
}),
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Whether an operation's input names a key: probes the schema with a value no
|
|
60
|
+
* browser-written schema accepts and looks for an issue at that key. A schema
|
|
61
|
+
* that answers asynchronously, or throws, reads as naming nothing.
|
|
62
|
+
*/
|
|
63
|
+
export const takesKey = (input: { '~standard': { validate: (value: unknown) => unknown } }, key: string) => {
|
|
64
|
+
let answer: { issues?: readonly { path?: readonly unknown[] }[] }
|
|
65
|
+
try {
|
|
66
|
+
answer = input['~standard'].validate({ [key]: Symbol(key) }) as typeof answer
|
|
67
|
+
} catch {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
if (answer instanceof Promise) {
|
|
71
|
+
answer.catch(() => {})
|
|
72
|
+
return false
|
|
73
|
+
}
|
|
74
|
+
return answer.issues?.some(({ path }) => {
|
|
75
|
+
const segment = path?.[0]
|
|
76
|
+
return String(typeof segment === 'object' && segment ? (segment as { key: unknown }).key : segment) === key
|
|
77
|
+
}) ?? false
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** What a screen's `client` is over an arm; the draft words ride along with the input. */
|
|
81
|
+
export const clientOf = (through: Arm): FetchClient => ({ kayan, operation, input, draft, autosave }) => through[kayan]![operation]!(input, { draft, autosave })
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* What the server's `client` is: each operation in process, as `actor`. Only the
|
|
85
|
+
* two draft words are read off the caller's options and the actor goes last, so
|
|
86
|
+
* a loader cannot run as someone else.
|
|
87
|
+
*/
|
|
88
|
+
export const actingAs = (
|
|
89
|
+
through: Readonly<Record<string, { operations: Record<string, (input: object, options: { actor?: unknown } & DraftOptions) => Promise<unknown>> }>>,
|
|
90
|
+
actor: unknown,
|
|
91
|
+
): FetchClient => ({ kayan, operation, input, draft, autosave }) => through[kayan]!.operations[operation]!(input, { draft, autosave, actor })
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* What a relationship control and cell read a second kayan through. Only the
|
|
95
|
+
* kayans the screen's loader already narrowed to what this actor may read are
|
|
96
|
+
* in the record, so a picker cannot page any other.
|
|
97
|
+
*/
|
|
98
|
+
export const readsOf = (kayans: readonly ClientKayan[]): FindApi =>
|
|
99
|
+
Object.fromEntries(
|
|
100
|
+
kayans.map((kayan) => [
|
|
101
|
+
kayan.name,
|
|
102
|
+
{
|
|
103
|
+
find: (input: Record<string, unknown>) => arm[kayan.name]!.find!(input) as Promise<FoundPage>,
|
|
104
|
+
},
|
|
105
|
+
]),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
type FieldReads = {
|
|
109
|
+
readonly collections?: Readonly<
|
|
110
|
+
Record<string, { readonly fields?: Readonly<Record<string, Readonly<Record<string, boolean>> | undefined>> } | undefined>
|
|
111
|
+
>
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* One kayan of the projection with every field this actor may not read taken
|
|
116
|
+
* off it. A loader's result is serialized into the document, so an unreadable
|
|
117
|
+
* field's name, label or rule would otherwise reach the viewer.
|
|
118
|
+
*/
|
|
119
|
+
export const readable = (one: ClientKayan, report: FieldReads): ClientKayan => ({
|
|
120
|
+
...one,
|
|
121
|
+
fields: one.fields.filter((field) => report.collections?.[one.name]?.fields?.[field.name]?.read === true),
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Whether a thrown refusal is the caller's. Those carry an HTTP status; a store
|
|
126
|
+
* that is down or a hook bug carries none and must be rethrown, not reported
|
|
127
|
+
* as a missing row.
|
|
128
|
+
*/
|
|
129
|
+
export const caused = (thrown: unknown): boolean =>
|
|
130
|
+
typeof (thrown as { status?: unknown } | null | undefined)?.status === 'number'
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* One POST at the auth arm (Better Auth's router under `/api/auth/user`) with the session cookie.
|
|
134
|
+
* Returns the answer instead of throwing: a refusal is a sentence for the viewer; no reply is status 0.
|
|
135
|
+
*/
|
|
136
|
+
export const authArm = async (path: string, body: unknown = {}) => {
|
|
137
|
+
const response = await fetch(`/api/auth/user/${path}`, {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: { 'content-type': 'application/json' },
|
|
140
|
+
body: JSON.stringify(body),
|
|
141
|
+
}).catch(() => null)
|
|
142
|
+
if (!response) return { ok: false, status: 0, message: undefined, body: null }
|
|
143
|
+
const answered = (await response.json().catch(() => null)) as ({ message?: string } & Record<string, unknown>) | null
|
|
144
|
+
// The status too, because a rate limit's refusal carries no code to read.
|
|
145
|
+
return { ok: response.ok, status: response.status, message: answered?.message, body: answered }
|
|
146
|
+
}
|
|
147
|
+
// mithaq-admin b06d8d335d306c07556f8f2b81fba20ba7ded198 94db2fc49ad4391c0d1c2183cb41001dd08d62017c76af86cc5f611488c22c68
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { useState, type ComponentProps, type ReactNode } from 'react'
|
|
2
|
+
import { redirect, useRouteContext } from '@tanstack/react-router'
|
|
3
|
+
import { createServerFn } from '@tanstack/react-start'
|
|
4
|
+
import { Eye, EyeOff } from 'lucide-react'
|
|
5
|
+
import { Button } from '@/components/ui/button'
|
|
6
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
7
|
+
import { Input } from '@/components/ui/input'
|
|
8
|
+
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from '@/components/ui/input-group'
|
|
9
|
+
import { authArm } from './api.ts'
|
|
10
|
+
import { FieldShell, describedBy } from './components/mithaq/field-shell.tsx'
|
|
11
|
+
import { refusalKey } from './copy.ts'
|
|
12
|
+
import { LocaleSwitch } from './locale-switch.tsx'
|
|
13
|
+
import { wanted } from './redirect.ts'
|
|
14
|
+
import { useCopy } from './routes/__root.tsx'
|
|
15
|
+
|
|
16
|
+
/** The one search key the auth pages read, off the raw search. */
|
|
17
|
+
export const redirectSearch = (search: Record<string, unknown>): { redirect?: string } =>
|
|
18
|
+
typeof search.redirect === 'string' ? { redirect: search.redirect } : {}
|
|
19
|
+
|
|
20
|
+
/** Whether a read of the actor names someone. A read that throws, or an actor with no email, names no one. */
|
|
21
|
+
export const signedInAs = async (actor: Promise<unknown>) => Boolean(((await actor.catch(() => null)) as { email?: unknown } | null)?.email)
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whether this request carries a session, where signing in lands for it, and the provider's password bounds.
|
|
25
|
+
* No auth page sends a referrer, since the reset page's address carries its token.
|
|
26
|
+
*/
|
|
27
|
+
export const authGate = createServerFn({ method: 'GET' }).handler(async () => {
|
|
28
|
+
// Imported inside the server half so no auth page ships the screen host to the browser.
|
|
29
|
+
const [{ getRequest, setResponseHeader }, { asas }, { loadEnterable }] = await Promise.all([import('@tanstack/react-start/server'), import('asas'), import('./screens.tsx')])
|
|
30
|
+
setResponseHeader('referrer-policy', 'no-referrer')
|
|
31
|
+
type Provider = { name: string; server: { $context: Promise<{ password: { config: { minPasswordLength: number; maxPasswordLength: number } } }> } }
|
|
32
|
+
const provider = (asas.auth.strategies as unknown as Provider[]).find((one) => one.name === 'user')!
|
|
33
|
+
const [signedIn, { password }] = await Promise.all([signedInAs(asas.auth.actor(getRequest().headers)), provider.server.$context])
|
|
34
|
+
const first = signedIn ? (await loadEnterable())?.screens[0] : undefined
|
|
35
|
+
const home: { path: string; name?: string } = first ?? { path: '/unauthorized' }
|
|
36
|
+
return { signedIn, home, password: { min: password.config.minPasswordLength, max: password.config.maxPasswordLength } }
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Sign-in's and sign-up's `beforeLoad`. A signed-in person is sent where a
|
|
41
|
+
* successful sign-in sends them; everyone else gets the password bounds in the
|
|
42
|
+
* route context.
|
|
43
|
+
*/
|
|
44
|
+
export const gate = async ({ search }: { search: { redirect?: string } }) => {
|
|
45
|
+
const { signedIn, home, password } = await authGate()
|
|
46
|
+
if (signedIn) throw redirect({ href: wanted({ asked: search.redirect, home: home.path }) })
|
|
47
|
+
return { password }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** What a link to `home` says: the screen it opens, or, where it opens none, the action of seeing the account's status. */
|
|
51
|
+
export const useHomeLabel = () => {
|
|
52
|
+
const { t } = useCopy()
|
|
53
|
+
return (home: { name?: string }, sentence: 'auth.backTo' | 'marketing.openScreen') =>
|
|
54
|
+
home.name === undefined
|
|
55
|
+
? t('auth.accountStatus', 'See your account status')
|
|
56
|
+
: t(sentence, sentence === 'auth.backTo' ? 'Back to {screen}' : 'Open {screen}')!.replace('{screen}', () => t(`screens.${home.name}`, home.name)!)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** What the forgot and reset pages draw for someone already signed in: the way on, and a sign-out. */
|
|
60
|
+
export function AlreadySignedIn({ redirect: asked, home }: { redirect?: string; home: { path: string; name?: string } }) {
|
|
61
|
+
const { t } = useCopy()
|
|
62
|
+
const label = useHomeLabel()
|
|
63
|
+
const to = wanted({ asked, home: home.path })
|
|
64
|
+
return (
|
|
65
|
+
<AuthFrame title={t('auth.alreadySignedIn', 'You are already signed in')}>
|
|
66
|
+
<div className="grid gap-4">
|
|
67
|
+
{/* An anchor: the path is the visitor's own, not a route the router knows. */}
|
|
68
|
+
<a className="text-primary text-center text-sm underline underline-offset-4" href={to}>
|
|
69
|
+
{to === home.path ? label(home, 'auth.backTo') : t('auth.continue', 'Continue')}
|
|
70
|
+
</a>
|
|
71
|
+
<Button type="button" variant="outline" onClick={() => void authArm('sign-out').then(() => window.location.reload())}>
|
|
72
|
+
{t('admin.signOut', 'Sign out')}
|
|
73
|
+
</Button>
|
|
74
|
+
</div>
|
|
75
|
+
</AuthFrame>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The sentence for a refusal of the provider: this app's own words for the status or code, then `fallback`. Never the provider's `message`, which is English in any locale. */
|
|
80
|
+
export const useRefusal = () => {
|
|
81
|
+
const { t } = useCopy()
|
|
82
|
+
return (answered: { status: number; body: Record<string, unknown> | null }, fallback: string | undefined) => {
|
|
83
|
+
const key = refusalKey(answered.status, answered.body?.code)
|
|
84
|
+
return (key && t(key)) || fallback || ''
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Whether the browser's own `type="email"` refuses this text. */
|
|
89
|
+
export const notAnEmail = (value: string) => Object.assign(document.createElement('input'), { type: 'email', value }).validity.typeMismatch
|
|
90
|
+
|
|
91
|
+
/** The frame every auth page is drawn in: the app's name linking home, the language switch, and the page's card. */
|
|
92
|
+
export function AuthFrame({ title, description, children }: { title: string | undefined; description?: ReactNode; children: ReactNode }) {
|
|
93
|
+
const { t, locale } = useCopy()
|
|
94
|
+
const { app } = useRouteContext({ from: '__root__' })
|
|
95
|
+
return (
|
|
96
|
+
<div className="flex min-h-screen flex-col">
|
|
97
|
+
<header className="mx-auto flex h-16 w-full max-w-6xl items-center justify-between gap-2 px-4 sm:px-6">
|
|
98
|
+
{/* An anchor: `/` is not a router route in an app whose start entry answers its home. */}
|
|
99
|
+
<a className="text-lg font-semibold" href="/">
|
|
100
|
+
{t(app, 'Asas')}
|
|
101
|
+
</a>
|
|
102
|
+
<LocaleSwitch locale={locale} />
|
|
103
|
+
</header>
|
|
104
|
+
<main className="flex flex-1 items-center justify-center px-4 pb-16">
|
|
105
|
+
<Card className="w-full max-w-sm">
|
|
106
|
+
<CardHeader>
|
|
107
|
+
<CardTitle><h1>{title}</h1></CardTitle>
|
|
108
|
+
{description ? <CardDescription>{description}</CardDescription> : null}
|
|
109
|
+
</CardHeader>
|
|
110
|
+
{/* Descriptions follow the text direction; the shared primitive aligns them left. */}
|
|
111
|
+
<CardContent className="[&_[data-slot=field-description]]:text-start">{children}</CardContent>
|
|
112
|
+
</Card>
|
|
113
|
+
</main>
|
|
114
|
+
</div>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function PasswordInput(props: ComponentProps<'input'>) {
|
|
119
|
+
const { t } = useCopy()
|
|
120
|
+
const [shown, setShown] = useState(false)
|
|
121
|
+
return (
|
|
122
|
+
// Focus moving to the box's own toggle has not left the box.
|
|
123
|
+
<InputGroup onBlur={(event) => event.currentTarget.contains(event.relatedTarget) && event.stopPropagation()}>
|
|
124
|
+
<InputGroupInput {...props} dir="ltr" type={shown ? 'text' : 'password'} />
|
|
125
|
+
<InputGroupAddon align="inline-end">
|
|
126
|
+
<InputGroupButton aria-label={t('auth.showPassword', 'Show password')} aria-pressed={shown} size="icon-xs" onClick={() => setShown((was) => !was)}>
|
|
127
|
+
{shown ? <EyeOff /> : <Eye />}
|
|
128
|
+
</InputGroupButton>
|
|
129
|
+
</InputGroupAddon>
|
|
130
|
+
</InputGroup>
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** One labelled box, which says what is wrong with it once it has been left. An email or password reads left to right in any locale. */
|
|
135
|
+
export function AuthBox({
|
|
136
|
+
id,
|
|
137
|
+
label,
|
|
138
|
+
error,
|
|
139
|
+
description,
|
|
140
|
+
onLeave,
|
|
141
|
+
...props
|
|
142
|
+
}: { id: string; label: string | undefined; error?: string; description?: string; onLeave: () => void } & ComponentProps<'input'>) {
|
|
143
|
+
const control = { 'aria-describedby': describedBy({ id, description, error }), 'aria-invalid': error ? true : undefined, id, name: id, required: true, ...props }
|
|
144
|
+
return (
|
|
145
|
+
<FieldShell id={id} label={label ?? id} description={description} error={error} onBlur={onLeave}>
|
|
146
|
+
{props.type === 'password' ? <PasswordInput {...control} /> : <Input dir={props.type === 'email' ? 'ltr' : 'auto'} {...control} />}
|
|
147
|
+
</FieldShell>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
// mithaq-admin b06d8d335d306c07556f8f2b81fba20ba7ded198 ab822afe361dfc5f82c504570fd5d0ea13ec4572e60adb679a3f9532ccf67803
|