@nextsparkjs/core 0.1.0-beta.188 → 0.1.0-beta.189
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/dist/components/api/keys/ApiKeyDisplay.js +19 -19
- package/dist/components/api/keys/CreateApiKeyDialog.js +21 -21
- package/dist/components/app/layouts/PublicNavbar.d.ts.map +1 -1
- package/dist/components/app/layouts/PublicNavbar.js +34 -16
- package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
- package/dist/components/app/misc/ThemeToggle.js +3 -4
- package/dist/components/auth/SessionCookieRefresher.d.ts +5 -0
- package/dist/components/auth/SessionCookieRefresher.d.ts.map +1 -1
- package/dist/components/auth/SessionCookieRefresher.js +7 -1
- package/dist/components/auth/forms/LoginForm.d.ts.map +1 -1
- package/dist/components/auth/forms/LoginForm.js +49 -12
- package/dist/components/auth/forms/SignupForm.d.ts.map +1 -1
- package/dist/components/auth/forms/SignupForm.js +4 -3
- package/dist/components/dashboard/block-editor/block-settings-panel.js +1 -1
- package/dist/components/dashboard/block-editor/floating-block-toolbar.js +1 -1
- package/dist/components/dashboard/block-editor/sortable-block.js +1 -1
- package/dist/components/dashboard/layouts/Sidebar.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/Sidebar.js +8 -6
- package/dist/components/dashboard/layouts/TopNavbar.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/TopNavbar.js +51 -54
- package/dist/components/dashboard/misc/NotificationsDropdown.d.ts.map +1 -1
- package/dist/components/dashboard/misc/NotificationsDropdown.js +17 -15
- package/dist/components/dashboard/misc/SearchDropdown.d.ts.map +1 -1
- package/dist/components/dashboard/misc/SearchDropdown.js +19 -22
- package/dist/components/dashboard/mobile/MobileBottomNav.js +1 -1
- package/dist/components/dashboard/mobile/MobileTopBar.js +2 -2
- package/dist/components/dashboard/navigation/DynamicNavigation.js +5 -5
- package/dist/components/devtools/BlockDetailViewer.js +1 -1
- package/dist/components/devtools/BlocksViewer.js +1 -1
- package/dist/components/entities/EntityChildManager.js +5 -5
- package/dist/components/entities/EntityDetailPage.d.ts.map +1 -1
- package/dist/components/entities/EntityDetailPage.js +16 -15
- package/dist/components/entities/EntityFieldRenderer.d.ts.map +1 -1
- package/dist/components/entities/EntityFieldRenderer.js +12 -7
- package/dist/components/entities/EntityPageHeader.js +5 -5
- package/dist/components/entities/ErrorBoundary.d.ts.map +1 -1
- package/dist/components/entities/ErrorBoundary.js +20 -21
- package/dist/components/entities/wrappers/EntityDetailWrapper.js +5 -5
- package/dist/components/entities/wrappers/EntityFormWrapper.js +2 -2
- package/dist/components/entities/wrappers/EntityListWrapper.js +3 -3
- package/dist/components/permissions/NoPermission.js +10 -10
- package/dist/components/public/pageBuilder/PageRenderer.d.ts.map +1 -1
- package/dist/components/public/pageBuilder/PageRenderer.js +2 -1
- package/dist/components/settings/SettingsPageSkeleton.js +4 -4
- package/dist/components/settings/layouts/SettingsSidebar.js +6 -6
- package/dist/components/shared/MultiSelectFilter.d.ts +3 -1
- package/dist/components/shared/MultiSelectFilter.d.ts.map +1 -1
- package/dist/components/shared/MultiSelectFilter.js +7 -3
- package/dist/components/shared/SearchInput.d.ts +2 -0
- package/dist/components/shared/SearchInput.d.ts.map +1 -1
- package/dist/components/shared/SearchInput.js +64 -6
- package/dist/components/ui/address-input.js +18 -18
- package/dist/components/ui/audio-upload.js +9 -9
- package/dist/components/ui/combobox.d.ts.map +1 -1
- package/dist/components/ui/combobox.js +4 -4
- package/dist/components/ui/country-select.d.ts.map +1 -1
- package/dist/components/ui/country-select.js +3 -3
- package/dist/components/ui/currency-select.d.ts.map +1 -1
- package/dist/components/ui/currency-select.js +3 -3
- package/dist/components/ui/dynamic-icon.d.ts.map +1 -1
- package/dist/components/ui/dynamic-icon.js +3 -2
- package/dist/components/ui/file-upload.js +7 -7
- package/dist/components/ui/image-upload.js +8 -8
- package/dist/components/ui/multi-select.d.ts +2 -1
- package/dist/components/ui/multi-select.d.ts.map +1 -1
- package/dist/components/ui/multi-select.js +4 -3
- package/dist/components/ui/phone-input.d.ts.map +1 -1
- package/dist/components/ui/phone-input.js +3 -3
- package/dist/components/ui/relation-display.js +1 -1
- package/dist/components/ui/simple-relation-select.d.ts +2 -1
- package/dist/components/ui/simple-relation-select.d.ts.map +1 -1
- package/dist/components/ui/simple-relation-select.js +7 -6
- package/dist/components/ui/tags-input.d.ts.map +1 -1
- package/dist/components/ui/tags-input.js +1 -1
- package/dist/components/ui/timezone-select.d.ts.map +1 -1
- package/dist/components/ui/timezone-select.js +3 -3
- package/dist/components/ui/user-display.js +4 -4
- package/dist/components/ui/user-select.d.ts.map +1 -1
- package/dist/components/ui/user-select.js +9 -9
- package/dist/components/ui/video-upload.js +9 -9
- package/dist/contexts/SubscriptionContext.d.ts.map +1 -1
- package/dist/contexts/SubscriptionContext.js +5 -1
- package/dist/contexts/TeamContext.d.ts.map +1 -1
- package/dist/contexts/TeamContext.js +83 -26
- package/dist/emails/otp-verification.d.ts.map +1 -1
- package/dist/emails/otp-verification.js +3 -2
- package/dist/hooks/useAccountPreferencesSync.d.ts +13 -0
- package/dist/hooks/useAccountPreferencesSync.d.ts.map +1 -0
- package/dist/hooks/useAccountPreferencesSync.js +95 -0
- package/dist/hooks/useAuth.d.ts +202 -15
- package/dist/hooks/useAuth.d.ts.map +1 -1
- package/dist/hooks/useAuth.js +49 -13
- package/dist/hooks/useEntityConfig.js +2 -2
- package/dist/hooks/useEntitySearch.js +10 -10
- package/dist/hooks/useNotifications.js +12 -12
- package/dist/hooks/usePrefetch.d.ts.map +1 -1
- package/dist/hooks/usePrefetch.js +6 -2
- package/dist/hooks/useSearch.js +15 -15
- package/dist/hooks/useSessionCookieRefresh.d.ts +18 -1
- package/dist/hooks/useSessionCookieRefresh.d.ts.map +1 -1
- package/dist/hooks/useSessionCookieRefresh.js +17 -4
- package/dist/hooks/useSessionHint.d.ts +12 -0
- package/dist/hooks/useSessionHint.d.ts.map +1 -0
- package/dist/hooks/useSessionHint.js +15 -0
- package/dist/hooks/useTranslationPreload.js +6 -6
- package/dist/i18n.d.ts +13 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +2 -27
- package/dist/lib/actions/entity.actions.d.ts.map +1 -1
- package/dist/lib/actions/entity.actions.js +3 -2
- package/dist/lib/api/auth/dual-auth.d.ts +4 -1
- package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
- package/dist/lib/api/auth/dual-auth.js +5 -16
- package/dist/lib/api/entities.js +33 -33
- package/dist/lib/api/keys.d.ts +27 -27
- package/dist/lib/api/keys.js +27 -27
- package/dist/lib/auth/callback-url.d.ts +11 -0
- package/dist/lib/auth/callback-url.d.ts.map +1 -0
- package/dist/lib/auth/callback-url.js +14 -0
- package/dist/lib/auth/otp-config.d.ts +55 -0
- package/dist/lib/auth/otp-config.d.ts.map +1 -0
- package/dist/lib/auth/otp-config.js +48 -0
- package/dist/lib/auth/registration-helpers.d.ts.map +1 -1
- package/dist/lib/auth/registration-helpers.js +3 -1
- package/dist/lib/auth/session-hint.d.ts +31 -0
- package/dist/lib/auth/session-hint.d.ts.map +1 -0
- package/dist/lib/auth/session-hint.js +37 -0
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +6 -4
- package/dist/lib/blocks/loader.d.ts +3 -11
- package/dist/lib/blocks/loader.d.ts.map +1 -1
- package/dist/lib/blocks/loader.js +1 -9
- package/dist/lib/blocks/loader.server.d.ts +18 -0
- package/dist/lib/blocks/loader.server.d.ts.map +1 -0
- package/dist/lib/blocks/loader.server.js +11 -0
- package/dist/lib/blocks/sanitize-html.d.ts +31 -0
- package/dist/lib/blocks/sanitize-html.d.ts.map +1 -0
- package/dist/lib/blocks/sanitize-html.js +60 -0
- package/dist/lib/config/config-sync.d.ts +1 -0
- package/dist/lib/config/config-sync.d.ts.map +1 -1
- package/dist/lib/config/config-sync.js +4 -1
- package/dist/lib/config/types.d.ts +48 -0
- package/dist/lib/config/types.d.ts.map +1 -1
- package/dist/lib/email/types.d.ts +6 -0
- package/dist/lib/email/types.d.ts.map +1 -1
- package/dist/lib/entities/serialization.d.ts.map +1 -1
- package/dist/lib/entities/serialization.js +3 -6
- package/dist/lib/icons.d.ts +12 -0
- package/dist/lib/icons.d.ts.map +1 -0
- package/dist/lib/icons.js +30 -0
- package/dist/lib/locale.d.ts +25 -1
- package/dist/lib/locale.d.ts.map +1 -1
- package/dist/lib/locale.js +49 -35
- package/dist/lib/mcp/engine.js +1 -1
- package/dist/lib/mcp/errors.js +17 -17
- package/dist/lib/mcp/schema-builder.js +16 -16
- package/dist/lib/mcp/tool-generator.js +18 -18
- package/dist/lib/search-highlighting.d.ts +7 -0
- package/dist/lib/search-highlighting.d.ts.map +1 -1
- package/dist/lib/search-highlighting.js +6 -2
- package/dist/lib/selectors/core-selectors.d.ts +2 -0
- package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/auth.selectors.d.ts +1 -0
- package/dist/lib/selectors/domains/auth.selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/auth.selectors.js +1 -0
- package/dist/lib/selectors/domains/entities.selectors.d.ts +2 -0
- package/dist/lib/selectors/domains/entities.selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/entities.selectors.js +2 -0
- package/dist/lib/selectors/selectors.d.ts +4 -0
- package/dist/lib/selectors/selectors.d.ts.map +1 -1
- package/dist/lib/services/block.service.js +1 -1
- package/dist/lib/teams/active-team-cookie.d.ts +24 -0
- package/dist/lib/teams/active-team-cookie.d.ts.map +1 -0
- package/dist/lib/teams/active-team-cookie.js +17 -0
- package/dist/lib/teams/dashboard-team.d.ts +20 -0
- package/dist/lib/teams/dashboard-team.d.ts.map +1 -0
- package/dist/lib/teams/dashboard-team.js +38 -0
- package/dist/lib/teams/schema.d.ts.map +1 -1
- package/dist/lib/teams/schema.js +1 -1
- package/dist/lib/template-resolver.d.ts.map +1 -1
- package/dist/lib/template-resolver.js +0 -1
- package/dist/lib/theme/get-default-theme-mode.d.ts +7 -4
- package/dist/lib/theme/get-default-theme-mode.d.ts.map +1 -1
- package/dist/lib/theme/get-default-theme-mode.js +2 -44
- package/dist/messages/de/auth.json +4 -2
- package/dist/messages/de/common.json +83 -3
- package/dist/messages/de/email.json +1 -1
- package/dist/messages/de/index.d.ts +87 -0
- package/dist/messages/de/index.d.ts.map +1 -1
- package/dist/messages/de/settings.json +5 -0
- package/dist/messages/en/auth.json +4 -2
- package/dist/messages/en/common.json +83 -3
- package/dist/messages/en/email.json +1 -1
- package/dist/messages/en/index.d.ts +86 -0
- package/dist/messages/en/index.d.ts.map +1 -1
- package/dist/messages/en/settings.json +4 -0
- package/dist/messages/es/auth.json +4 -2
- package/dist/messages/es/common.json +83 -3
- package/dist/messages/es/email.json +1 -1
- package/dist/messages/es/index.d.ts +86 -0
- package/dist/messages/es/index.d.ts.map +1 -1
- package/dist/messages/es/settings.json +4 -0
- package/dist/messages/fr/auth.json +4 -2
- package/dist/messages/fr/common.json +83 -3
- package/dist/messages/fr/email.json +1 -1
- package/dist/messages/fr/index.d.ts +87 -0
- package/dist/messages/fr/index.d.ts.map +1 -1
- package/dist/messages/fr/settings.json +5 -0
- package/dist/messages/it/auth.json +4 -2
- package/dist/messages/it/common.json +83 -3
- package/dist/messages/it/email.json +1 -1
- package/dist/messages/it/index.d.ts +87 -0
- package/dist/messages/it/index.d.ts.map +1 -1
- package/dist/messages/it/settings.json +5 -0
- package/dist/messages/pt/auth.json +4 -2
- package/dist/messages/pt/common.json +83 -3
- package/dist/messages/pt/email.json +1 -1
- package/dist/messages/pt/index.d.ts +87 -0
- package/dist/messages/pt/index.d.ts.map +1 -1
- package/dist/messages/pt/settings.json +5 -0
- package/dist/nextspark-registries.d.ts +54 -0
- package/dist/providers/DashboardProviders.d.ts +13 -10
- package/dist/providers/DashboardProviders.d.ts.map +1 -1
- package/dist/providers/DashboardProviders.js +7 -1
- package/dist/templates/app/(auth)/forgot-password/page.tsx +2 -2
- package/dist/templates/app/(auth)/reset-password/page.tsx +6 -6
- package/dist/templates/app/403/page.tsx +6 -6
- package/dist/templates/app/api/v1/teams/switch/route.ts +24 -5
- package/dist/templates/app/dashboard/(main)/[entity]/layout.tsx +9 -7
- package/dist/templates/app/dashboard/(main)/layout.tsx +4 -3
- package/dist/templates/app/dashboard/settings/api-keys/page.tsx +36 -34
- package/dist/templates/app/dashboard/settings/profile/page.tsx +7 -6
- package/dist/templates/app/dashboard/settings/security/page.tsx +5 -4
- package/dist/templates/app/layout.ppr.tsx +8 -8
- package/dist/templates/app/layout.tsx +12 -17
- package/dist/templates/blocks/cta-section/component.tsx +5 -2
- package/dist/templates/blocks/cta-section/config.ts +3 -2
- package/dist/templates/blocks/cta-section/fields.ts +2 -2
- package/dist/templates/blocks/cta-section/schema.ts +2 -2
- package/dist/templates/blocks/features-grid/component.tsx +10 -2
- package/dist/templates/blocks/features-grid/config.ts +3 -2
- package/dist/templates/blocks/features-grid/fields.ts +2 -2
- package/dist/templates/blocks/features-grid/schema.ts +2 -2
- package/dist/templates/blocks/hero/component.tsx +6 -3
- package/dist/templates/blocks/hero/config.ts +3 -2
- package/dist/templates/blocks/hero/fields.ts +2 -2
- package/dist/templates/blocks/hero/schema.ts +2 -2
- package/dist/templates/blocks/testimonials/component.tsx +6 -3
- package/dist/templates/blocks/testimonials/config.ts +3 -2
- package/dist/templates/blocks/testimonials/fields.ts +2 -2
- package/dist/templates/blocks/testimonials/schema.ts +2 -2
- package/dist/templates/blocks/text-content/component.tsx +7 -3
- package/dist/templates/blocks/text-content/config.ts +3 -2
- package/dist/templates/blocks/text-content/fields.ts +2 -2
- package/dist/templates/blocks/text-content/schema.ts +2 -2
- package/dist/templates/contents/themes/starter/blocks/cta-section/component.tsx +102 -0
- package/dist/templates/contents/themes/starter/blocks/cta-section/config.ts +11 -0
- package/dist/templates/contents/themes/starter/blocks/cta-section/fields.ts +89 -0
- package/dist/templates/contents/themes/starter/blocks/cta-section/index.ts +6 -0
- package/dist/templates/contents/themes/starter/blocks/cta-section/schema.ts +32 -0
- package/dist/templates/contents/themes/starter/blocks/cta-section/thumbnail.png +0 -0
- package/dist/templates/contents/themes/starter/blocks/features-grid/component.tsx +119 -0
- package/dist/templates/contents/themes/starter/blocks/features-grid/config.ts +11 -0
- package/dist/templates/contents/themes/starter/blocks/features-grid/fields.ts +97 -0
- package/dist/templates/contents/themes/starter/blocks/features-grid/index.ts +6 -0
- package/dist/templates/contents/themes/starter/blocks/features-grid/schema.ts +40 -0
- package/dist/templates/contents/themes/starter/blocks/features-grid/thumbnail.png +0 -0
- package/dist/templates/contents/themes/starter/blocks/hero/component.tsx +105 -0
- package/dist/templates/contents/themes/starter/blocks/hero/config.ts +11 -0
- package/dist/templates/contents/themes/starter/blocks/hero/fields.ts +60 -0
- package/dist/templates/contents/themes/starter/blocks/hero/index.ts +6 -0
- package/dist/templates/contents/themes/starter/blocks/hero/schema.ts +33 -0
- package/dist/templates/contents/themes/starter/blocks/hero/thumbnail.png +0 -0
- package/dist/templates/contents/themes/starter/blocks/testimonials/component.tsx +116 -0
- package/dist/templates/contents/themes/starter/blocks/testimonials/config.ts +11 -0
- package/dist/templates/contents/themes/starter/blocks/testimonials/fields.ts +105 -0
- package/dist/templates/contents/themes/starter/blocks/testimonials/index.ts +6 -0
- package/dist/templates/contents/themes/starter/blocks/testimonials/schema.ts +41 -0
- package/dist/templates/contents/themes/starter/blocks/testimonials/thumbnail.png +0 -0
- package/dist/templates/contents/themes/starter/blocks/text-content/component.tsx +100 -0
- package/dist/templates/contents/themes/starter/blocks/text-content/config.ts +11 -0
- package/dist/templates/contents/themes/starter/blocks/text-content/fields.ts +88 -0
- package/dist/templates/contents/themes/starter/blocks/text-content/index.ts +6 -0
- package/dist/templates/contents/themes/starter/blocks/text-content/schema.ts +30 -0
- package/dist/templates/contents/themes/starter/blocks/text-content/thumbnail.png +0 -0
- package/dist/templates/contents/themes/starter/config/app.config.ts +8 -0
- package/dist/templates/contents/themes/starter/entities/tasks/tasks.config.ts +7 -0
- package/dist/templates/contents/themes/starter/tests/cypress/e2e/_utils/devtools/access.cy.ts +9 -1
- package/dist/templates/contents/themes/starter/tests/cypress/e2e/api/entities/tasks/TaskAPIController.js +2 -2
- package/dist/templates/contents/themes/starter/tests/cypress/e2e/uat/entities/tasks/tasks-crud.cy.ts +45 -38
- package/dist/templates/contents/themes/starter/tests/cypress/fixtures/entities.json +16 -0
- package/dist/templates/contents/themes/starter/tests/cypress/src/components/DevKeyringPOM.ts +3 -1
- package/dist/templates/contents/themes/starter/tests/cypress/src/core/BlockEditorBasePOM.ts +3 -3
- package/dist/templates/contents/themes/starter/tests/cypress/src/entities/PagesPOM.ts +137 -0
- package/dist/templates/contents/themes/starter/tests/cypress/src/entities/index.ts +2 -0
- package/dist/templates/contents/themes/starter/tests/cypress/src/index.ts +1 -0
- package/dist/templates/contents/themes/starter/tests/cypress/src/session-helpers.ts +42 -11
- package/dist/templates/contents/themes/starter/tests/cypress/support/commands.ts +29 -18
- package/dist/templates/features/blog/blocks/post-content/component.tsx +5 -1
- package/dist/templates/features/blog/blocks/post-content/schema.ts +1 -1
- package/dist/templates/features/pages/blocks/hero/component.tsx +3 -0
- package/dist/templates/features/pages/blocks/hero/schema.ts +1 -1
- package/dist/templates/proxy.ts +138 -53
- package/dist/types/blocks.d.ts.map +1 -1
- package/dist/types/blocks.js +1 -1
- package/dist/types/task.types.d.ts +1 -1
- package/dist/types/task.types.js +7 -7
- package/dist/utils/dev/TranslationDebugger.js +2 -2
- package/package.json +14 -5
- package/scripts/build/registry/__tests__/block-registry.test.mjs +73 -0
- package/scripts/build/registry/__tests__/icons-discovery.test.mjs +130 -0
- package/scripts/build/registry/__tests__/identifier.test.mjs +65 -0
- package/scripts/build/registry/__tests__/page-generator.test.mjs +430 -0
- package/scripts/build/registry/__tests__/scheduled-actions-registry.test.mjs +41 -0
- package/scripts/build/registry/__tests__/template-registry.test.mjs +89 -0
- package/scripts/build/registry/discovery/icons.mjs +275 -0
- package/scripts/build/registry/discovery/index.mjs +1 -0
- package/scripts/build/registry/generators/block-registry.mjs +203 -55
- package/scripts/build/registry/generators/feature-registry.mjs +9 -2
- package/scripts/build/registry/generators/icon-registry.mjs +56 -0
- package/scripts/build/registry/generators/identifier.mjs +28 -0
- package/scripts/build/registry/generators/index.mjs +1 -0
- package/scripts/build/registry/generators/plugin-registry.mjs +4 -3
- package/scripts/build/registry/generators/scheduled-actions-registry.mjs +4 -2
- package/scripts/build/registry/generators/template-registry.mjs +49 -41
- package/scripts/build/registry/generators/theme-registry.mjs +6 -7
- package/scripts/build/registry/post-build/page-generator.mjs +380 -59
- package/scripts/build/registry.mjs +9 -1
- package/templates/app/(auth)/forgot-password/page.tsx +2 -2
- package/templates/app/(auth)/reset-password/page.tsx +6 -6
- package/templates/app/403/page.tsx +6 -6
- package/templates/app/api/v1/teams/switch/route.ts +24 -5
- package/templates/app/dashboard/(main)/[entity]/layout.tsx +9 -7
- package/templates/app/dashboard/(main)/layout.tsx +4 -3
- package/templates/app/dashboard/settings/api-keys/page.tsx +36 -34
- package/templates/app/dashboard/settings/profile/page.tsx +7 -6
- package/templates/app/dashboard/settings/security/page.tsx +5 -4
- package/templates/app/layout.ppr.tsx +8 -8
- package/templates/app/layout.tsx +12 -17
- package/templates/blocks/cta-section/component.tsx +5 -2
- package/templates/blocks/cta-section/config.ts +3 -2
- package/templates/blocks/cta-section/fields.ts +2 -2
- package/templates/blocks/cta-section/schema.ts +2 -2
- package/templates/blocks/features-grid/component.tsx +10 -2
- package/templates/blocks/features-grid/config.ts +3 -2
- package/templates/blocks/features-grid/fields.ts +2 -2
- package/templates/blocks/features-grid/schema.ts +2 -2
- package/templates/blocks/hero/component.tsx +6 -3
- package/templates/blocks/hero/config.ts +3 -2
- package/templates/blocks/hero/fields.ts +2 -2
- package/templates/blocks/hero/schema.ts +2 -2
- package/templates/blocks/testimonials/component.tsx +6 -3
- package/templates/blocks/testimonials/config.ts +3 -2
- package/templates/blocks/testimonials/fields.ts +2 -2
- package/templates/blocks/testimonials/schema.ts +2 -2
- package/templates/blocks/text-content/component.tsx +7 -3
- package/templates/blocks/text-content/config.ts +3 -2
- package/templates/blocks/text-content/fields.ts +2 -2
- package/templates/blocks/text-content/schema.ts +2 -2
- package/templates/contents/themes/starter/blocks/cta-section/component.tsx +102 -0
- package/templates/contents/themes/starter/blocks/cta-section/config.ts +11 -0
- package/templates/contents/themes/starter/blocks/cta-section/fields.ts +89 -0
- package/templates/contents/themes/starter/blocks/cta-section/index.ts +6 -0
- package/templates/contents/themes/starter/blocks/cta-section/schema.ts +32 -0
- package/templates/contents/themes/starter/blocks/cta-section/thumbnail.png +0 -0
- package/templates/contents/themes/starter/blocks/features-grid/component.tsx +119 -0
- package/templates/contents/themes/starter/blocks/features-grid/config.ts +11 -0
- package/templates/contents/themes/starter/blocks/features-grid/fields.ts +97 -0
- package/templates/contents/themes/starter/blocks/features-grid/index.ts +6 -0
- package/templates/contents/themes/starter/blocks/features-grid/schema.ts +40 -0
- package/templates/contents/themes/starter/blocks/features-grid/thumbnail.png +0 -0
- package/templates/contents/themes/starter/blocks/hero/component.tsx +105 -0
- package/templates/contents/themes/starter/blocks/hero/config.ts +11 -0
- package/templates/contents/themes/starter/blocks/hero/fields.ts +60 -0
- package/templates/contents/themes/starter/blocks/hero/index.ts +6 -0
- package/templates/contents/themes/starter/blocks/hero/schema.ts +33 -0
- package/templates/contents/themes/starter/blocks/hero/thumbnail.png +0 -0
- package/templates/contents/themes/starter/blocks/testimonials/component.tsx +116 -0
- package/templates/contents/themes/starter/blocks/testimonials/config.ts +11 -0
- package/templates/contents/themes/starter/blocks/testimonials/fields.ts +105 -0
- package/templates/contents/themes/starter/blocks/testimonials/index.ts +6 -0
- package/templates/contents/themes/starter/blocks/testimonials/schema.ts +41 -0
- package/templates/contents/themes/starter/blocks/testimonials/thumbnail.png +0 -0
- package/templates/contents/themes/starter/blocks/text-content/component.tsx +100 -0
- package/templates/contents/themes/starter/blocks/text-content/config.ts +11 -0
- package/templates/contents/themes/starter/blocks/text-content/fields.ts +88 -0
- package/templates/contents/themes/starter/blocks/text-content/index.ts +6 -0
- package/templates/contents/themes/starter/blocks/text-content/schema.ts +30 -0
- package/templates/contents/themes/starter/blocks/text-content/thumbnail.png +0 -0
- package/templates/contents/themes/starter/config/app.config.ts +8 -0
- package/templates/contents/themes/starter/entities/tasks/tasks.config.ts +7 -0
- package/templates/contents/themes/starter/tests/cypress/e2e/_utils/devtools/access.cy.ts +9 -1
- package/templates/contents/themes/starter/tests/cypress/e2e/api/entities/tasks/TaskAPIController.js +2 -2
- package/templates/contents/themes/starter/tests/cypress/e2e/uat/entities/tasks/tasks-crud.cy.ts +45 -38
- package/templates/contents/themes/starter/tests/cypress/fixtures/entities.json +16 -0
- package/templates/contents/themes/starter/tests/cypress/src/components/DevKeyringPOM.ts +3 -1
- package/templates/contents/themes/starter/tests/cypress/src/core/BlockEditorBasePOM.ts +3 -3
- package/templates/contents/themes/starter/tests/cypress/src/entities/PagesPOM.ts +137 -0
- package/templates/contents/themes/starter/tests/cypress/src/entities/index.ts +2 -0
- package/templates/contents/themes/starter/tests/cypress/src/index.ts +1 -0
- package/templates/contents/themes/starter/tests/cypress/src/session-helpers.ts +42 -11
- package/templates/contents/themes/starter/tests/cypress/support/commands.ts +29 -18
- package/templates/features/blog/blocks/post-content/component.tsx +5 -1
- package/templates/features/blog/blocks/post-content/schema.ts +1 -1
- package/templates/features/pages/blocks/hero/component.tsx +3 -0
- package/templates/features/pages/blocks/hero/schema.ts +1 -1
- package/templates/proxy.ts +138 -53
- package/tests/jest/__mocks__/@nextsparkjs/registries/block-registry.client.ts +9 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/block-registry.lazy.ts +5 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/icon-registry.ts +42 -0
- package/tests/jest/__mocks__/next-intl-server.js +85 -5
- package/tests/jest/__mocks__/next-server.js +32 -0
|
@@ -39,19 +39,50 @@ const SUPERADMIN_PASSWORD = Cypress.env('SUPERADMIN_PASSWORD') || 'Pandora1234'
|
|
|
39
39
|
const TEST_PASSWORD = Cypress.env('TEST_PASSWORD') || 'Test1234'
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
43
|
-
* Teams: Everpoint Labs, Ironvale Global, Riverstone Ventures
|
|
42
|
+
* Team-role test users.
|
|
44
43
|
*
|
|
45
|
-
*
|
|
44
|
+
* The starter seeds exactly two users (core migration 090): superadmin and
|
|
45
|
+
* developer. The names below come from the default theme's richer sample data,
|
|
46
|
+
* and every one of them has to be pointed at a real user through the matching
|
|
47
|
+
* env var before the specs that use it can run.
|
|
48
|
+
*
|
|
49
|
+
* OWNER is the exception, and it matters: it is what the CRUD specs log in as,
|
|
50
|
+
* so its fallback is the user the starter actually creates — superadmin owns
|
|
51
|
+
* the initial team. Leaving it on a name with no row made the whole UAT suite
|
|
52
|
+
* fail in session setup, before a single assertion.
|
|
46
53
|
*/
|
|
47
54
|
export const DEFAULT_THEME_USERS = {
|
|
48
|
-
OWNER: Cypress.env('OWNER_EMAIL') || '
|
|
55
|
+
OWNER: Cypress.env('OWNER_EMAIL') || 'superadmin@nextspark.dev',
|
|
49
56
|
ADMIN: Cypress.env('ADMIN_EMAIL') || 'james.wilson@nextspark.dev',
|
|
50
57
|
MEMBER: Cypress.env('MEMBER_EMAIL') || 'emily.johnson@nextspark.dev',
|
|
51
58
|
EDITOR: Cypress.env('EDITOR_EMAIL') || 'diego.ramirez@nextspark.dev',
|
|
52
59
|
VIEWER: Cypress.env('VIEWER_EMAIL') || 'sarah.davis@nextspark.dev',
|
|
53
60
|
} as const
|
|
54
61
|
|
|
62
|
+
/**
|
|
63
|
+
* The password that goes with OWNER.
|
|
64
|
+
*
|
|
65
|
+
* OWNER falls back to the seeded superadmin, and that user's password is the
|
|
66
|
+
* superadmin one, not the demo-user one. A theme that points OWNER at a user of
|
|
67
|
+
* its own sets OWNER_EMAIL, and then the demo password is the right default.
|
|
68
|
+
*/
|
|
69
|
+
const OWNER_PASSWORD =
|
|
70
|
+
Cypress.env('OWNER_PASSWORD') ||
|
|
71
|
+
(Cypress.env('OWNER_EMAIL') ? TEST_PASSWORD : SUPERADMIN_PASSWORD)
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Whether a non-owner team role points at a user that exists.
|
|
75
|
+
*
|
|
76
|
+
* Only OWNER has a seeded fallback. The other four keep the default theme's
|
|
77
|
+
* names, which resolve to nothing here until the theme seeds them and sets the
|
|
78
|
+
* matching env var. A spec that needs one guards on this instead of failing in
|
|
79
|
+
* session setup, where the error is a ten-second timeout on the Dev Keyring
|
|
80
|
+
* rather than "this user does not exist".
|
|
81
|
+
*/
|
|
82
|
+
export function hasSeededTeamRoleUser(role: 'ADMIN' | 'MEMBER' | 'EDITOR' | 'VIEWER'): boolean {
|
|
83
|
+
return Boolean(Cypress.env(`${role}_EMAIL`))
|
|
84
|
+
}
|
|
85
|
+
|
|
55
86
|
/**
|
|
56
87
|
* Core System Users (from core/migrations/090_sample_data.sql)
|
|
57
88
|
* These users have special global roles, not team-based roles
|
|
@@ -164,7 +195,7 @@ function apiLogin(email: string, password: string = TEST_PASSWORD): Cypress.Chai
|
|
|
164
195
|
*/
|
|
165
196
|
export function loginAsDefaultOwner() {
|
|
166
197
|
cy.session('default-owner-session', () => {
|
|
167
|
-
apiLogin(DEFAULT_THEME_USERS.OWNER).then((apiLoginSucceeded) => {
|
|
198
|
+
apiLogin(DEFAULT_THEME_USERS.OWNER, OWNER_PASSWORD).then((apiLoginSucceeded) => {
|
|
168
199
|
// If API login succeeded, we need to visit a page before setting localStorage
|
|
169
200
|
if (apiLoginSucceeded) {
|
|
170
201
|
cy.visit('/dashboard', { timeout: 60000 })
|
|
@@ -389,22 +420,22 @@ export function getThemeUsers() {
|
|
|
389
420
|
*/
|
|
390
421
|
export const BILLING_TEAMS = {
|
|
391
422
|
FREE: {
|
|
392
|
-
teamId: 'team-personal-carlos-001',
|
|
423
|
+
teamId: Cypress.env('BILLING_FREE_TEAM_ID') || 'team-personal-carlos-001',
|
|
393
424
|
name: 'Carlos Personal',
|
|
394
425
|
planSlug: 'free',
|
|
395
|
-
owner: 'carlos.mendoza@nextspark.dev'
|
|
426
|
+
owner: Cypress.env('BILLING_FREE_OWNER') || 'carlos.mendoza@nextspark.dev'
|
|
396
427
|
},
|
|
397
428
|
PRO: {
|
|
398
|
-
teamId: 'team-everpoint-001',
|
|
429
|
+
teamId: Cypress.env('BILLING_PRO_TEAM_ID') || 'team-everpoint-001',
|
|
399
430
|
name: 'Everpoint Labs',
|
|
400
431
|
planSlug: 'pro',
|
|
401
|
-
owner: 'carlos.mendoza@nextspark.dev'
|
|
432
|
+
owner: Cypress.env('BILLING_PRO_OWNER') || 'carlos.mendoza@nextspark.dev'
|
|
402
433
|
},
|
|
403
434
|
ENTERPRISE: {
|
|
404
|
-
teamId: 'team-ironvale-002',
|
|
435
|
+
teamId: Cypress.env('BILLING_ENTERPRISE_TEAM_ID') || 'team-ironvale-002',
|
|
405
436
|
name: 'Ironvale Global',
|
|
406
437
|
planSlug: 'enterprise',
|
|
407
|
-
owner: 'ana.garcia@nextspark.dev'
|
|
438
|
+
owner: Cypress.env('BILLING_ENTERPRISE_OWNER') || 'ana.garcia@nextspark.dev'
|
|
408
439
|
}
|
|
409
440
|
} as const
|
|
410
441
|
|
|
@@ -140,18 +140,37 @@ Cypress.Commands.add('logout', () => {
|
|
|
140
140
|
})
|
|
141
141
|
|
|
142
142
|
/**
|
|
143
|
-
*
|
|
143
|
+
* The headers an entity API call needs, `x-team-id` included.
|
|
144
|
+
*
|
|
145
|
+
* The app writes `activeTeamId` into localStorage while the dashboard loads, so
|
|
146
|
+
* a command that runs before any dashboard visit — a `beforeEach` seeding data,
|
|
147
|
+
* typically — finds nothing there. Without the header the API answers 400
|
|
148
|
+
* TEAM_CONTEXT_REQUIRED, and since these requests do not fail on status the
|
|
149
|
+
* seed silently does nothing: the failure only surfaces later, as an assertion
|
|
150
|
+
* about data that was never created. Asking the API for the team is what makes
|
|
151
|
+
* the command work from any starting point.
|
|
144
152
|
*/
|
|
145
|
-
Cypress.
|
|
153
|
+
function apiHeaders(): Cypress.Chainable<Record<string, string>> {
|
|
154
|
+
const base: Record<string, string> = { 'Content-Type': 'application/json' }
|
|
155
|
+
|
|
146
156
|
return cy.window().then((win) => {
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
157
|
+
const stored = win.localStorage.getItem('activeTeamId')
|
|
158
|
+
if (stored) return cy.wrap({ ...base, 'x-team-id': stored })
|
|
159
|
+
|
|
160
|
+
return cy
|
|
161
|
+
.request({ method: 'GET', url: '/api/v1/teams', failOnStatusCode: false })
|
|
162
|
+
.then((response) => {
|
|
163
|
+
const teamId = response.body?.data?.[0]?.id
|
|
164
|
+
return cy.wrap(teamId ? { ...base, 'x-team-id': teamId } : base)
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
}
|
|
154
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Create a task via API
|
|
171
|
+
*/
|
|
172
|
+
Cypress.Commands.add('createTask', (data) => {
|
|
173
|
+
return apiHeaders().then((headers) => {
|
|
155
174
|
return cy.request({
|
|
156
175
|
method: 'POST',
|
|
157
176
|
url: '/api/v1/tasks',
|
|
@@ -171,15 +190,7 @@ Cypress.Commands.add('createTask', (data) => {
|
|
|
171
190
|
* Delete a task via API
|
|
172
191
|
*/
|
|
173
192
|
Cypress.Commands.add('deleteTask', (id: string) => {
|
|
174
|
-
return
|
|
175
|
-
const teamId = win.localStorage.getItem('activeTeamId')
|
|
176
|
-
const headers: Record<string, string> = {
|
|
177
|
-
'Content-Type': 'application/json',
|
|
178
|
-
}
|
|
179
|
-
if (teamId) {
|
|
180
|
-
headers['x-team-id'] = teamId
|
|
181
|
-
}
|
|
182
|
-
|
|
193
|
+
return apiHeaders().then((headers) => {
|
|
183
194
|
return cy.request({
|
|
184
195
|
method: 'DELETE',
|
|
185
196
|
url: `/api/v1/tasks/${id}`,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { cn } from '@nextsparkjs/core/lib/utils'
|
|
3
3
|
import { buildSectionClasses } from '@nextsparkjs/core/types/blocks'
|
|
4
|
+
import { sanitizeBlockHtml } from '@nextsparkjs/core/lib/blocks/sanitize-html'
|
|
4
5
|
import { sel } from '../../lib/selectors'
|
|
5
6
|
import type { PostContentBlockProps } from './schema'
|
|
6
7
|
|
|
@@ -171,7 +172,7 @@ export function PostContentBlock({
|
|
|
171
172
|
// Em/italic
|
|
172
173
|
'prose-em:italic'
|
|
173
174
|
)}
|
|
174
|
-
dangerouslySetInnerHTML={{ __html: content }}
|
|
175
|
+
dangerouslySetInnerHTML={{ __html: sanitizeBlockHtml(content) }}
|
|
175
176
|
/>
|
|
176
177
|
|
|
177
178
|
{/* Optional Divider after content */}
|
|
@@ -195,3 +196,6 @@ export function PostContentBlock({
|
|
|
195
196
|
</section>
|
|
196
197
|
)
|
|
197
198
|
}
|
|
199
|
+
|
|
200
|
+
// The generated block registry imports this as the module's default.
|
|
201
|
+
export default PostContentBlock
|
|
@@ -41,6 +41,6 @@ export const postContentSpecificSchema = z.object({
|
|
|
41
41
|
* Complete Post Content Block Schema
|
|
42
42
|
* Note: postContentSpecificSchema.content overrides baseBlockSchema.content
|
|
43
43
|
*/
|
|
44
|
-
export const schema = baseBlockSchema.
|
|
44
|
+
export const schema = baseBlockSchema.extend(postContentSpecificSchema.shape)
|
|
45
45
|
|
|
46
46
|
export type PostContentBlockProps = z.infer<typeof schema>
|
|
@@ -27,7 +27,7 @@ export const heroSpecificSchema = z.object({
|
|
|
27
27
|
* Complete Hero Block Schema
|
|
28
28
|
* Combines base fields + hero-specific fields
|
|
29
29
|
*/
|
|
30
|
-
export const schema = baseBlockSchema.
|
|
30
|
+
export const schema = baseBlockSchema.extend(heroSpecificSchema.shape)
|
|
31
31
|
|
|
32
32
|
export type HeroBlockProps = z.infer<typeof schema>
|
|
33
33
|
|
package/templates/proxy.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* @nextspark-generated
|
|
3
|
+
*
|
|
4
|
+
* `nextspark sync:app` replaces this file while that tag is present, which is
|
|
5
|
+
* how a release ships changes to it. Delete the tag line to own the file: from
|
|
6
|
+
* then on sync reports it and leaves it alone.
|
|
7
|
+
*
|
|
2
8
|
* NextSpark Proxy (Next.js 16+)
|
|
3
9
|
*
|
|
4
10
|
* Handles authentication, route protection, and user context injection.
|
|
@@ -7,8 +13,8 @@
|
|
|
7
13
|
* Key responsibilities:
|
|
8
14
|
* 1. Theme middleware override support
|
|
9
15
|
* 2. Documentation access control
|
|
10
|
-
* 3. Protected route authentication
|
|
11
|
-
* 4. User header injection for downstream use (x-user-id, x-pathname)
|
|
16
|
+
* 3. Protected route authentication, and the roles /superadmin and /devtools need
|
|
17
|
+
* 4. User header injection for downstream use (x-user-id, x-pathname, x-active-team-id)
|
|
12
18
|
*
|
|
13
19
|
* IMPORTANT: The EntityPermissionLayout depends on x-user-id and x-pathname
|
|
14
20
|
* headers being set here for server-side permission validation.
|
|
@@ -20,6 +26,8 @@ import {
|
|
|
20
26
|
executeThemeMiddleware,
|
|
21
27
|
getThemeAppConfig
|
|
22
28
|
} from '@nextsparkjs/core/lib/middleware'
|
|
29
|
+
import { ACTIVE_TEAM_COOKIE, activeTeamIdForSession } from '@nextsparkjs/core/lib/teams/active-team-cookie'
|
|
30
|
+
import { SESSION_HINT_COOKIE, SESSION_HINT_MAX_AGE, hasSessionCookie } from '@nextsparkjs/core/lib/auth/session-hint'
|
|
23
31
|
|
|
24
32
|
/**
|
|
25
33
|
* Session type for proxy (inline definition)
|
|
@@ -64,6 +72,53 @@ function isPublicPath(pathname: string): boolean {
|
|
|
64
72
|
)
|
|
65
73
|
}
|
|
66
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Whether `pathname` is `prefix` or a path under it: `/devtools/config` is,
|
|
77
|
+
* `/devtools-guide` and `/profile-photo.jpg` are not.
|
|
78
|
+
*/
|
|
79
|
+
function isUnder(pathname: string, prefix: string): boolean {
|
|
80
|
+
return pathname === prefix || pathname.startsWith(`${prefix}/`)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A URL on this app at `pathname`, keeping the base path and locale the request
|
|
85
|
+
* came in with; one built from `request.url` would lose both.
|
|
86
|
+
*/
|
|
87
|
+
function appUrl(request: NextRequest, pathname: string): NextRequest['nextUrl'] {
|
|
88
|
+
const url = request.nextUrl.clone()
|
|
89
|
+
url.pathname = pathname
|
|
90
|
+
url.search = ''
|
|
91
|
+
return url
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Send a visitor without a session to login. LoginForm reads `callbackUrl` and
|
|
96
|
+
* returns them to the page they asked for, query included, once signed in.
|
|
97
|
+
*/
|
|
98
|
+
function redirectToLogin(request: NextRequest): NextResponse {
|
|
99
|
+
const loginUrl = appUrl(request, '/login')
|
|
100
|
+
loginUrl.searchParams.set('callbackUrl', `${request.nextUrl.pathname}${request.nextUrl.search}`)
|
|
101
|
+
return NextResponse.redirect(loginUrl)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Send a signed-in user without the role an area needs back to the dashboard. */
|
|
105
|
+
function redirectAccessDenied(request: NextRequest): NextResponse {
|
|
106
|
+
const dashboardUrl = appUrl(request, '/dashboard')
|
|
107
|
+
dashboardUrl.searchParams.set('error', 'access_denied')
|
|
108
|
+
return NextResponse.redirect(dashboardUrl)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The session the request's cookies carry, asked of this app's own auth route,
|
|
113
|
+
* which is served under the base path like every other route.
|
|
114
|
+
*/
|
|
115
|
+
function getSession(request: NextRequest) {
|
|
116
|
+
return betterFetch<Session>('/api/auth/get-session', {
|
|
117
|
+
baseURL: `${request.nextUrl.origin}${request.nextUrl.basePath}`,
|
|
118
|
+
headers: { cookie: request.headers.get('cookie') || '' },
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
|
|
67
122
|
/**
|
|
68
123
|
* Identity headers this proxy injects for downstream server code.
|
|
69
124
|
*
|
|
@@ -74,7 +129,7 @@ function isPublicPath(pathname: string): boolean {
|
|
|
74
129
|
* dispatches Server Actions by the `Next-Action` header, not by the URL, so an
|
|
75
130
|
* action can be POSTed to a public path with a forged `x-user-id`.
|
|
76
131
|
*/
|
|
77
|
-
const TRUSTED_IDENTITY_HEADERS = ['x-user-id', 'x-user-email', 'x-pathname'] as const
|
|
132
|
+
const TRUSTED_IDENTITY_HEADERS = ['x-user-id', 'x-user-email', 'x-pathname', 'x-active-team-id'] as const
|
|
78
133
|
|
|
79
134
|
/**
|
|
80
135
|
* Build the request headers forwarded to the app: inbound copy minus every
|
|
@@ -89,15 +144,38 @@ function sanitizeRequestHeaders(request: NextRequest): Headers {
|
|
|
89
144
|
return headers
|
|
90
145
|
}
|
|
91
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Keep the readable session hint (lib/auth/session-hint) in line with the
|
|
149
|
+
* session cookie, so client code knows whether to ask for the session. A browser
|
|
150
|
+
* that got its session without client code seeing it (before the hint existed,
|
|
151
|
+
* or through OAuth) gets the hint on its next request, and one whose session
|
|
152
|
+
* cookie is gone loses it. The session itself is not checked here.
|
|
153
|
+
*/
|
|
154
|
+
function syncSessionHint(request: NextRequest, response: NextResponse): NextResponse {
|
|
155
|
+
const hasSession = hasSessionCookie(request.headers.get('cookie'))
|
|
156
|
+
const hinted = request.cookies.get(SESSION_HINT_COOKIE)?.value === '1'
|
|
157
|
+
if (hasSession && !hinted) {
|
|
158
|
+
response.cookies.set(SESSION_HINT_COOKIE, '1', {
|
|
159
|
+
path: '/',
|
|
160
|
+
maxAge: SESSION_HINT_MAX_AGE,
|
|
161
|
+
sameSite: 'lax',
|
|
162
|
+
secure: request.nextUrl.protocol === 'https:',
|
|
163
|
+
})
|
|
164
|
+
} else if (!hasSession && hinted) {
|
|
165
|
+
response.cookies.set(SESSION_HINT_COOKIE, '', { path: '/', maxAge: 0 })
|
|
166
|
+
}
|
|
167
|
+
return response
|
|
168
|
+
}
|
|
169
|
+
|
|
92
170
|
/**
|
|
93
171
|
* Continue to the app with the (sanitized) request headers.
|
|
94
172
|
* Every pass-through in this proxy MUST go through here so the strip applies
|
|
95
173
|
* to public paths, /api/v1 and unmatched routes alike.
|
|
96
174
|
*/
|
|
97
|
-
function passThrough(requestHeaders: Headers): NextResponse {
|
|
98
|
-
return NextResponse.next({
|
|
175
|
+
function passThrough(request: NextRequest, requestHeaders: Headers): NextResponse {
|
|
176
|
+
return syncSessionHint(request, NextResponse.next({
|
|
99
177
|
request: { headers: requestHeaders },
|
|
100
|
-
})
|
|
178
|
+
}))
|
|
101
179
|
}
|
|
102
180
|
|
|
103
181
|
export async function proxy(request: NextRequest) {
|
|
@@ -129,76 +207,71 @@ export async function proxy(request: NextRequest) {
|
|
|
129
207
|
}
|
|
130
208
|
|
|
131
209
|
// 3. Documentation access control
|
|
132
|
-
if (pathname
|
|
210
|
+
if (isUnder(pathname, '/docs')) {
|
|
133
211
|
const appConfig = getThemeAppConfig(activeTheme as string)
|
|
134
212
|
|
|
135
213
|
if (appConfig?.docs?.public === false) {
|
|
136
214
|
try {
|
|
137
|
-
const { data: session } = await
|
|
138
|
-
'/api/auth/get-session',
|
|
139
|
-
{
|
|
140
|
-
baseURL: request.nextUrl.origin,
|
|
141
|
-
headers: { cookie: request.headers.get('cookie') || '' },
|
|
142
|
-
}
|
|
143
|
-
)
|
|
215
|
+
const { data: session } = await getSession(request)
|
|
144
216
|
|
|
145
217
|
if (!session) {
|
|
146
|
-
|
|
147
|
-
loginUrl.searchParams.set('redirect', pathname)
|
|
148
|
-
return NextResponse.redirect(loginUrl)
|
|
218
|
+
return redirectToLogin(request)
|
|
149
219
|
}
|
|
150
220
|
} catch (error) {
|
|
151
|
-
|
|
152
|
-
loginUrl.searchParams.set('redirect', pathname)
|
|
153
|
-
return NextResponse.redirect(loginUrl)
|
|
221
|
+
return redirectToLogin(request)
|
|
154
222
|
}
|
|
155
223
|
}
|
|
156
|
-
return passThrough(requestHeaders)
|
|
224
|
+
return passThrough(request, requestHeaders)
|
|
157
225
|
}
|
|
158
226
|
|
|
159
227
|
// 4. Allow public paths
|
|
160
228
|
if (isPublicPath(pathname)) {
|
|
161
|
-
return passThrough(requestHeaders)
|
|
229
|
+
return passThrough(request, requestHeaders)
|
|
162
230
|
}
|
|
163
231
|
|
|
164
232
|
// 5. API v1 routes handle their own dual authentication
|
|
165
233
|
if (pathname.startsWith('/api/v1')) {
|
|
166
|
-
return passThrough(requestHeaders)
|
|
234
|
+
return passThrough(request, requestHeaders)
|
|
167
235
|
}
|
|
168
236
|
|
|
169
|
-
// 6. Protected routes - require authentication and inject user headers
|
|
170
|
-
|
|
237
|
+
// 6. Protected routes - require authentication and inject user headers.
|
|
238
|
+
// Areas are matched by path segment, so /dashboard-guide is not /dashboard.
|
|
239
|
+
// /superadmin and /devtools also need a role, the same ones SuperAdminGuard
|
|
240
|
+
// and DeveloperGuard let in. The guards decide it again in the browser, but
|
|
241
|
+
// only after the page has been served, so it is decided here first.
|
|
242
|
+
const isAdminRoute = isUnder(pathname, '/admin')
|
|
243
|
+
const isSuperadminRoute = isUnder(pathname, '/superadmin')
|
|
244
|
+
const isDevtoolsRoute = isUnder(pathname, '/devtools')
|
|
171
245
|
const isProtectedRoute =
|
|
172
|
-
pathname
|
|
173
|
-
pathname
|
|
174
|
-
pathname
|
|
175
|
-
pathname
|
|
176
|
-
|
|
177
|
-
|
|
246
|
+
isUnder(pathname, '/dashboard') ||
|
|
247
|
+
isUnder(pathname, '/settings') ||
|
|
248
|
+
isUnder(pathname, '/profile') ||
|
|
249
|
+
isUnder(pathname, '/update-password') ||
|
|
250
|
+
isAdminRoute ||
|
|
251
|
+
isSuperadminRoute ||
|
|
252
|
+
isDevtoolsRoute
|
|
178
253
|
|
|
179
254
|
if (isProtectedRoute) {
|
|
180
255
|
try {
|
|
181
|
-
const { data: session } = await
|
|
182
|
-
'/api/auth/get-session',
|
|
183
|
-
{
|
|
184
|
-
baseURL: request.nextUrl.origin,
|
|
185
|
-
headers: { cookie: request.headers.get('cookie') || '' },
|
|
186
|
-
}
|
|
187
|
-
)
|
|
256
|
+
const { data: session } = await getSession(request)
|
|
188
257
|
|
|
189
258
|
if (!session) {
|
|
190
|
-
|
|
191
|
-
loginUrl.searchParams.set('callbackUrl', pathname)
|
|
192
|
-
return NextResponse.redirect(loginUrl)
|
|
259
|
+
return redirectToLogin(request)
|
|
193
260
|
}
|
|
194
261
|
|
|
262
|
+
const role = session.user?.role
|
|
263
|
+
|
|
195
264
|
// Admin Panel superadmin-only check
|
|
196
|
-
if (isAdminRoute) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
265
|
+
if (isAdminRoute && role !== 'superadmin') {
|
|
266
|
+
return redirectAccessDenied(request)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (isSuperadminRoute && role !== 'superadmin' && role !== 'developer') {
|
|
270
|
+
return redirectAccessDenied(request)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (isDevtoolsRoute && role !== 'developer') {
|
|
274
|
+
return redirectAccessDenied(request)
|
|
202
275
|
}
|
|
203
276
|
|
|
204
277
|
// Inject user headers for downstream use, ONLY from the verified session
|
|
@@ -210,21 +283,33 @@ export async function proxy(request: NextRequest) {
|
|
|
210
283
|
if (session.user?.email) {
|
|
211
284
|
requestHeaders.set('x-user-email', session.user.email)
|
|
212
285
|
}
|
|
286
|
+
// The team this session chose, for the dashboard layouts' permission
|
|
287
|
+
// checks; a cookie another session left behind names no team here.
|
|
288
|
+
const activeTeamId = activeTeamIdForSession(request.cookies.get(ACTIVE_TEAM_COOKIE)?.value, session.session?.id)
|
|
289
|
+
if (activeTeamId) {
|
|
290
|
+
requestHeaders.set('x-active-team-id', activeTeamId)
|
|
291
|
+
}
|
|
213
292
|
|
|
214
|
-
return passThrough(requestHeaders)
|
|
293
|
+
return passThrough(request, requestHeaders)
|
|
215
294
|
} catch (error) {
|
|
216
295
|
console.error('Proxy error:', error)
|
|
217
|
-
|
|
218
|
-
loginUrl.searchParams.set('callbackUrl', pathname)
|
|
219
|
-
return NextResponse.redirect(loginUrl)
|
|
296
|
+
return redirectToLogin(request)
|
|
220
297
|
}
|
|
221
298
|
}
|
|
222
299
|
|
|
223
|
-
return passThrough(requestHeaders)
|
|
300
|
+
return passThrough(request, requestHeaders)
|
|
224
301
|
}
|
|
225
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Everything runs through the proxy except the exact paths of Next's own
|
|
305
|
+
* output: `_next/static/`, the `_next/image` endpoint and `favicon.ico`.
|
|
306
|
+
* Excluding by file extension, or by a prefix without its boundary, would let a
|
|
307
|
+
* dynamic segment such as `alice.png` or `favicon.icoevil` reach the app without
|
|
308
|
+
* the session check and with forged identity headers intact. Files under
|
|
309
|
+
* public/ simply pass through.
|
|
310
|
+
*/
|
|
226
311
|
export const config = {
|
|
227
312
|
matcher: [
|
|
228
|
-
'/((?!_next/static
|
|
313
|
+
'/((?!_next/static/|_next/image$|favicon\\.ico$).*)',
|
|
229
314
|
],
|
|
230
315
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client half of the block registry, for tests.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the mock's configs rather than restating them, so the two halves
|
|
5
|
+
* cannot drift the way the generated pair must not.
|
|
6
|
+
*/
|
|
7
|
+
export { BLOCK_REGISTRY, BLOCK_METADATA } from './block-registry'
|
|
8
|
+
|
|
9
|
+
export const BLOCK_CATEGORIES: string[] = []
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock Icon Registry for Jest tests
|
|
3
|
+
*
|
|
4
|
+
* Holds the real lucide components, not stand-ins: serialization resolves an
|
|
5
|
+
* icon's registry name by identity, so a config's `icon: CheckSquare` only
|
|
6
|
+
* round-trips when the registry holds that same component.
|
|
7
|
+
*/
|
|
8
|
+
import type { LucideIcon } from 'lucide-react'
|
|
9
|
+
import {
|
|
10
|
+
Box,
|
|
11
|
+
CheckSquare,
|
|
12
|
+
Circle,
|
|
13
|
+
FileText,
|
|
14
|
+
Folder,
|
|
15
|
+
Home,
|
|
16
|
+
LayoutGrid,
|
|
17
|
+
Newspaper,
|
|
18
|
+
Search,
|
|
19
|
+
Settings,
|
|
20
|
+
Users
|
|
21
|
+
} from 'lucide-react'
|
|
22
|
+
|
|
23
|
+
export const ICON_REGISTRY: Record<string, LucideIcon> = {
|
|
24
|
+
Box,
|
|
25
|
+
CheckSquare,
|
|
26
|
+
Circle,
|
|
27
|
+
FileText,
|
|
28
|
+
Folder,
|
|
29
|
+
Home,
|
|
30
|
+
LayoutGrid,
|
|
31
|
+
Newspaper,
|
|
32
|
+
Search,
|
|
33
|
+
Settings,
|
|
34
|
+
Users
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type IconName = keyof typeof ICON_REGISTRY
|
|
38
|
+
|
|
39
|
+
export const ICON_METADATA = {
|
|
40
|
+
totalIcons: Object.keys(ICON_REGISTRY).length,
|
|
41
|
+
generatedAt: new Date().toISOString()
|
|
42
|
+
}
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Loads the corresponding `messages/<locale>/<root>.json` file directly and
|
|
5
5
|
* resolves keys inside `namespace` (which can be dotted, e.g. `email.verifyEmail`).
|
|
6
|
-
* Supports ICU-style `{var}` placeholders
|
|
7
|
-
*
|
|
6
|
+
* Supports ICU-style `{var}` placeholders, replaced naively, plus the `plural`
|
|
7
|
+
* argument type (`{var, plural, one {...} other {...}}`) resolved through
|
|
8
|
+
* `Intl.PluralRules` — the subset the email templates actually use. Sufficient
|
|
9
|
+
* for email template snapshot tests, not a general ICU MessageFormat parser.
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
const path = require('path')
|
|
@@ -28,9 +30,87 @@ function loadNamespace(locale, namespace) {
|
|
|
28
30
|
return obj
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Resolves every `{varName, plural, category {text} ...}` block in `template`,
|
|
35
|
+
* picking the branch via `Intl.PluralRules` (falling back to an explicit
|
|
36
|
+
* `=<n>` match, then to `other`) and substituting `#` with the value.
|
|
37
|
+
* Parses by brace-depth rather than regex so a category's text may itself
|
|
38
|
+
* contain other `{placeholder}` tokens.
|
|
39
|
+
*/
|
|
40
|
+
function resolveIcuPlural(template, params, locale) {
|
|
41
|
+
const marker = ', plural,'
|
|
42
|
+
let result = ''
|
|
43
|
+
let i = 0
|
|
44
|
+
|
|
45
|
+
while (i < template.length) {
|
|
46
|
+
if (template[i] !== '{') {
|
|
47
|
+
result += template[i]
|
|
48
|
+
i++
|
|
49
|
+
continue
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const commaIndex = template.indexOf(',', i)
|
|
53
|
+
if (commaIndex === -1 || template.slice(commaIndex, commaIndex + marker.length) !== marker) {
|
|
54
|
+
result += template[i]
|
|
55
|
+
i++
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const varName = template.slice(i + 1, commaIndex).trim()
|
|
60
|
+
let cursor = commaIndex + marker.length
|
|
61
|
+
const categories = {}
|
|
62
|
+
|
|
63
|
+
while (cursor < template.length) {
|
|
64
|
+
while (/\s/.test(template[cursor])) cursor++
|
|
65
|
+
if (template[cursor] === '}') {
|
|
66
|
+
cursor++
|
|
67
|
+
break
|
|
68
|
+
}
|
|
69
|
+
const catStart = cursor
|
|
70
|
+
while (template[cursor] !== '{') cursor++
|
|
71
|
+
const catName = template.slice(catStart, cursor).trim()
|
|
72
|
+
cursor++ // skip the category's opening '{'
|
|
73
|
+
let subDepth = 1
|
|
74
|
+
const subStart = cursor
|
|
75
|
+
while (subDepth > 0) {
|
|
76
|
+
if (template[cursor] === '{') subDepth++
|
|
77
|
+
else if (template[cursor] === '}') subDepth--
|
|
78
|
+
if (subDepth > 0) cursor++
|
|
79
|
+
}
|
|
80
|
+
categories[catName] = template.slice(subStart, cursor)
|
|
81
|
+
cursor++ // skip the category's closing '}'
|
|
82
|
+
while (/\s/.test(template[cursor])) cursor++
|
|
83
|
+
if (template[cursor] === '}') {
|
|
84
|
+
cursor++
|
|
85
|
+
break
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const value = params ? params[varName] : undefined
|
|
90
|
+
const numValue = Number(value)
|
|
91
|
+
let category = categories[`=${numValue}`] !== undefined ? `=${numValue}` : undefined
|
|
92
|
+
if (category === undefined) {
|
|
93
|
+
try {
|
|
94
|
+
category = new Intl.PluralRules(locale || 'en').select(numValue)
|
|
95
|
+
} catch {
|
|
96
|
+
category = 'other'
|
|
97
|
+
}
|
|
98
|
+
if (categories[category] === undefined) category = 'other'
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
result += (categories[category] ?? '').replace(/#/g, String(numValue))
|
|
102
|
+
i = cursor
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return result
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function format(template, params, locale) {
|
|
32
109
|
if (!params) return template
|
|
33
|
-
|
|
110
|
+
const resolved = template.includes(', plural,')
|
|
111
|
+
? resolveIcuPlural(template, params, locale)
|
|
112
|
+
: template
|
|
113
|
+
return resolved.replace(/\{(\w+)\}/g, (_, key) =>
|
|
34
114
|
params[key] === undefined ? `{${key}}` : String(params[key]),
|
|
35
115
|
)
|
|
36
116
|
}
|
|
@@ -45,7 +125,7 @@ async function getTranslations(opts) {
|
|
|
45
125
|
if (value === undefined) {
|
|
46
126
|
throw new Error(`next-intl mock: missing key "${key}" in namespace "${namespace}"`)
|
|
47
127
|
}
|
|
48
|
-
return format(value, params)
|
|
128
|
+
return format(value, params, locale)
|
|
49
129
|
}
|
|
50
130
|
}
|
|
51
131
|
|