@voltro/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/LICENSE +57 -0
- package/README.md +26 -0
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +20236 -0
- package/bin/voltro.mjs +43 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +9 -0
- package/dist/commands-CfPH2Wf4.js +18061 -0
- package/dist/frameworkInspectState-CX2250XB.js +86 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +5 -0
- package/dist/inspectState.d.ts +116 -0
- package/dist/inspectState.js +2 -0
- package/dist/startup.d.ts +26 -0
- package/dist/startup.js +2 -0
- package/dist/startupRunner-CRhuUl91.js +71 -0
- package/package.json +88 -0
- package/templates/AGENTS.core.md +258 -0
- package/templates/AGENTS.md +351 -0
- package/templates/agent-docs/_index.md +93 -0
- package/templates/agent-docs/_manifest.json +655 -0
- package/templates/agent-docs/ai.md +1845 -0
- package/templates/agent-docs/authentication.md +1788 -0
- package/templates/agent-docs/caching.md +624 -0
- package/templates/agent-docs/cli.md +1650 -0
- package/templates/agent-docs/configuration.md +295 -0
- package/templates/agent-docs/data.md +2172 -0
- package/templates/agent-docs/database/advancedqueries.md +1583 -0
- package/templates/agent-docs/database/columntypes.md +1200 -0
- package/templates/agent-docs/database/hosting.md +881 -0
- package/templates/agent-docs/database/migrations.md +2938 -0
- package/templates/agent-docs/database/misc.md +270 -0
- package/templates/agent-docs/database/overview.md +108 -0
- package/templates/agent-docs/database/querying.md +1622 -0
- package/templates/agent-docs/database/scaling.md +331 -0
- package/templates/agent-docs/database/schema.md +1458 -0
- package/templates/agent-docs/database/seedsdialects.md +1235 -0
- package/templates/agent-docs/database/transactions.md +285 -0
- package/templates/agent-docs/deployment.md +999 -0
- package/templates/agent-docs/internationalization.md +359 -0
- package/templates/agent-docs/introduction.md +438 -0
- package/templates/agent-docs/multi-tenancy.md +610 -0
- package/templates/agent-docs/observability.md +350 -0
- package/templates/agent-docs/plugins.md +1273 -0
- package/templates/agent-docs/reference.md +978 -0
- package/templates/agent-docs/routing.md +1553 -0
- package/templates/agent-docs/scheduling.md +666 -0
- package/templates/agent-docs/schema-driven-ui.md +607 -0
- package/templates/agent-docs/security.md +42 -0
- package/templates/agent-docs/templates/apibackends.md +3214 -0
- package/templates/agent-docs/templates/appshells.md +2062 -0
- package/templates/agent-docs/templates/custom.md +128 -0
- package/templates/agent-docs/templates/overview.md +122 -0
- package/templates/agent-docs/templates/serverless.md +315 -0
- package/templates/agent-docs/testing.md +376 -0
- package/templates/agent-docs/workflows.md +1351 -0
- package/templates/apps/api-ai/README.md +105 -0
- package/templates/apps/api-ai/actions/summarize.action.server.tsx +32 -0
- package/templates/apps/api-ai/actions/summarize.action.ts +33 -0
- package/templates/apps/api-ai/agents/support.agent.server.tsx +31 -0
- package/templates/apps/api-ai/agents/support.agent.tsx +27 -0
- package/templates/apps/api-ai/app.config.ts +57 -0
- package/templates/apps/api-ai/database/schema.ts +67 -0
- package/templates/apps/api-ai/package.json +27 -0
- package/templates/apps/api-ai/seeds/docs.seed.ts +67 -0
- package/templates/apps/api-ai/template.json +6 -0
- package/templates/apps/api-ai/tests/summarize.test.ts +50 -0
- package/templates/apps/api-ai/tools/searchDocs.tool.tsx +60 -0
- package/templates/apps/api-ai/tsconfig.json +5 -0
- package/templates/apps/api-auth/.env +17 -0
- package/templates/apps/api-auth/README.md +109 -0
- package/templates/apps/api-auth/actions/me.action.server.ts +20 -0
- package/templates/apps/api-auth/actions/me.action.ts +19 -0
- package/templates/apps/api-auth/app.config.ts +59 -0
- package/templates/apps/api-auth/database/schema.ts +26 -0
- package/templates/apps/api-auth/package.json +28 -0
- package/templates/apps/api-auth/template.json +6 -0
- package/templates/apps/api-auth/tests/me.test.ts +37 -0
- package/templates/apps/api-auth/tsconfig.json +5 -0
- package/templates/apps/api-backend/README.md +25 -0
- package/templates/apps/api-backend/app.config.ts +39 -0
- package/templates/apps/api-backend/database/schema.ts +57 -0
- package/templates/apps/api-backend/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend/package.json +28 -0
- package/templates/apps/api-backend/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend/template.json +6 -0
- package/templates/apps/api-backend/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend/tsconfig.json +5 -0
- package/templates/apps/api-backend-deactivation/README.md +49 -0
- package/templates/apps/api-backend-deactivation/actions/users.get.action.server.ts +17 -0
- package/templates/apps/api-backend-deactivation/actions/users.get.action.ts +18 -0
- package/templates/apps/api-backend-deactivation/app.config.ts +20 -0
- package/templates/apps/api-backend-deactivation/database/schema.ts +41 -0
- package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.server.ts +6 -0
- package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.ts +16 -0
- package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.server.ts +10 -0
- package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.ts +19 -0
- package/templates/apps/api-backend-deactivation/package.json +28 -0
- package/templates/apps/api-backend-deactivation/template.json +6 -0
- package/templates/apps/api-backend-deactivation/tests/users.deactivate.test.ts +47 -0
- package/templates/apps/api-backend-deactivation/tsconfig.json +5 -0
- package/templates/apps/api-backend-mail/README.md +39 -0
- package/templates/apps/api-backend-mail/actions/sendWelcome.action.server.ts +15 -0
- package/templates/apps/api-backend-mail/actions/sendWelcome.action.ts +21 -0
- package/templates/apps/api-backend-mail/app.config.ts +34 -0
- package/templates/apps/api-backend-mail/database/schema.ts +57 -0
- package/templates/apps/api-backend-mail/emails/welcome.email.tsx +106 -0
- package/templates/apps/api-backend-mail/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend-mail/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend-mail/package.json +30 -0
- package/templates/apps/api-backend-mail/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend-mail/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend-mail/template.json +6 -0
- package/templates/apps/api-backend-mail/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend-mail/tsconfig.json +5 -0
- package/templates/apps/api-backend-mariadb/.env.example +35 -0
- package/templates/apps/api-backend-mariadb/README.md +31 -0
- package/templates/apps/api-backend-mariadb/app.config.ts +68 -0
- package/templates/apps/api-backend-mariadb/database/schema.ts +57 -0
- package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend-mariadb/package.json +30 -0
- package/templates/apps/api-backend-mariadb/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend-mariadb/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend-mariadb/template.json +6 -0
- package/templates/apps/api-backend-mariadb/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend-mariadb/tsconfig.json +5 -0
- package/templates/apps/api-backend-storage/README.md +86 -0
- package/templates/apps/api-backend-storage/actions/uploadAvatar.action.server.ts +21 -0
- package/templates/apps/api-backend-storage/actions/uploadAvatar.action.ts +21 -0
- package/templates/apps/api-backend-storage/actions/uploadDocument.action.server.ts +21 -0
- package/templates/apps/api-backend-storage/actions/uploadDocument.action.ts +20 -0
- package/templates/apps/api-backend-storage/app.config.ts +38 -0
- package/templates/apps/api-backend-storage/database/schema.ts +57 -0
- package/templates/apps/api-backend-storage/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend-storage/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend-storage/package.json +27 -0
- package/templates/apps/api-backend-storage/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend-storage/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend-storage/template.json +6 -0
- package/templates/apps/api-backend-storage/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend-storage/tsconfig.json +5 -0
- package/templates/apps/api-data-advanced/.env +17 -0
- package/templates/apps/api-data-advanced/README.md +88 -0
- package/templates/apps/api-data-advanced/app.config.ts +56 -0
- package/templates/apps/api-data-advanced/database/actors.entity.ts +14 -0
- package/templates/apps/api-data-advanced/database/authors.entity.ts +30 -0
- package/templates/apps/api-data-advanced/database/authors.relations.ts +15 -0
- package/templates/apps/api-data-advanced/database/books.entity.ts +59 -0
- package/templates/apps/api-data-advanced/database/books.relations.ts +10 -0
- package/templates/apps/api-data-advanced/database/index.ts +23 -0
- package/templates/apps/api-data-advanced/database/tenants.entity.ts +11 -0
- package/templates/apps/api-data-advanced/package.json +28 -0
- package/templates/apps/api-data-advanced/queries/authors.withBooks.query.server.ts +18 -0
- package/templates/apps/api-data-advanced/queries/authors.withBooks.query.ts +40 -0
- package/templates/apps/api-data-advanced/queries/books.search.query.server.ts +17 -0
- package/templates/apps/api-data-advanced/queries/books.search.query.ts +39 -0
- package/templates/apps/api-data-advanced/seeds/catalog.seed.ts +101 -0
- package/templates/apps/api-data-advanced/template.json +6 -0
- package/templates/apps/api-data-advanced/tests/queries.test.ts +124 -0
- package/templates/apps/api-data-advanced/tsconfig.json +5 -0
- package/templates/apps/api-durable/README.md +81 -0
- package/templates/apps/api-durable/aggregates/orderStats.aggregate.ts +49 -0
- package/templates/apps/api-durable/app.config.ts +41 -0
- package/templates/apps/api-durable/database/schema.ts +65 -0
- package/templates/apps/api-durable/mutations/orders.approve.mutation.server.ts +43 -0
- package/templates/apps/api-durable/mutations/orders.approve.mutation.ts +23 -0
- package/templates/apps/api-durable/mutations/orders.place.mutation.server.ts +45 -0
- package/templates/apps/api-durable/mutations/orders.place.mutation.ts +40 -0
- package/templates/apps/api-durable/package.json +28 -0
- package/templates/apps/api-durable/schedules/nightlyReport.cron.tsx +37 -0
- package/templates/apps/api-durable/startup/warm.startup.tsx +33 -0
- package/templates/apps/api-durable/subscribers/orderChanges.subscribe.ts +27 -0
- package/templates/apps/api-durable/template.json +6 -0
- package/templates/apps/api-durable/tests/orders.place.test.ts +91 -0
- package/templates/apps/api-durable/triggers/order.placed.trigger.tsx +21 -0
- package/templates/apps/api-durable/tsconfig.json +5 -0
- package/templates/apps/api-durable/workflows/order.fulfill.workflow.server.tsx +103 -0
- package/templates/apps/api-durable/workflows/order.fulfill.workflow.tsx +25 -0
- package/templates/apps/api-feature-flags/README.md +63 -0
- package/templates/apps/api-feature-flags/actions/notes.export.action.server.ts +11 -0
- package/templates/apps/api-feature-flags/actions/notes.export.action.ts +18 -0
- package/templates/apps/api-feature-flags/app.config.ts +41 -0
- package/templates/apps/api-feature-flags/database/schema.ts +37 -0
- package/templates/apps/api-feature-flags/mutations/notes.create.mutation.server.ts +29 -0
- package/templates/apps/api-feature-flags/mutations/notes.create.mutation.ts +28 -0
- package/templates/apps/api-feature-flags/package.json +29 -0
- package/templates/apps/api-feature-flags/template.json +6 -0
- package/templates/apps/api-feature-flags/tests/notes.create.test.ts +67 -0
- package/templates/apps/api-feature-flags/tsconfig.json +5 -0
- package/templates/apps/api-governance/.env +4 -0
- package/templates/apps/api-governance/README.md +61 -0
- package/templates/apps/api-governance/actions/profiles.get.action.server.ts +17 -0
- package/templates/apps/api-governance/actions/profiles.get.action.ts +17 -0
- package/templates/apps/api-governance/app.config.ts +35 -0
- package/templates/apps/api-governance/database/schema.ts +41 -0
- package/templates/apps/api-governance/mutations/profiles.create.mutation.server.ts +17 -0
- package/templates/apps/api-governance/mutations/profiles.create.mutation.ts +21 -0
- package/templates/apps/api-governance/package.json +29 -0
- package/templates/apps/api-governance/template.json +6 -0
- package/templates/apps/api-governance/tests/profiles.create.test.ts +59 -0
- package/templates/apps/api-governance/tsconfig.json +5 -0
- package/templates/apps/api-kv/README.md +100 -0
- package/templates/apps/api-kv/actions/sync.pull.action.server.ts +75 -0
- package/templates/apps/api-kv/actions/sync.pull.action.ts +25 -0
- package/templates/apps/api-kv/actions/sync.reset.action.server.ts +25 -0
- package/templates/apps/api-kv/actions/sync.reset.action.ts +22 -0
- package/templates/apps/api-kv/actions/sync.status.action.server.ts +28 -0
- package/templates/apps/api-kv/actions/sync.status.action.ts +17 -0
- package/templates/apps/api-kv/app.config.ts +48 -0
- package/templates/apps/api-kv/database/schema.ts +62 -0
- package/templates/apps/api-kv/package.json +28 -0
- package/templates/apps/api-kv/queries/events.list.query.server.ts +14 -0
- package/templates/apps/api-kv/queries/events.list.query.ts +23 -0
- package/templates/apps/api-kv/template.json +6 -0
- package/templates/apps/api-kv/tests/sync.test.ts +103 -0
- package/templates/apps/api-kv/tsconfig.json +5 -0
- package/templates/apps/api-moderation/README.md +46 -0
- package/templates/apps/api-moderation/app.config.ts +33 -0
- package/templates/apps/api-moderation/database/schema.ts +45 -0
- package/templates/apps/api-moderation/mutations/comments.create.mutation.server.ts +8 -0
- package/templates/apps/api-moderation/mutations/comments.create.mutation.ts +20 -0
- package/templates/apps/api-moderation/mutations/posts.create.mutation.server.ts +9 -0
- package/templates/apps/api-moderation/mutations/posts.create.mutation.ts +25 -0
- package/templates/apps/api-moderation/package.json +29 -0
- package/templates/apps/api-moderation/template.json +6 -0
- package/templates/apps/api-moderation/tests/posts.create.test.ts +79 -0
- package/templates/apps/api-moderation/tsconfig.json +5 -0
- package/templates/apps/api-observability/README.md +106 -0
- package/templates/apps/api-observability/app.config.ts +32 -0
- package/templates/apps/api-observability/database/schema.ts +38 -0
- package/templates/apps/api-observability/mutations/notes.create.mutation.server.ts +33 -0
- package/templates/apps/api-observability/mutations/notes.create.mutation.ts +15 -0
- package/templates/apps/api-observability/package.json +29 -0
- package/templates/apps/api-observability/queries/notes.list.query.server.ts +7 -0
- package/templates/apps/api-observability/queries/notes.list.query.ts +14 -0
- package/templates/apps/api-observability/template.json +6 -0
- package/templates/apps/api-observability/tests/notes.create.test.ts +36 -0
- package/templates/apps/api-observability/tsconfig.json +5 -0
- package/templates/apps/api-ratelimit/README.md +49 -0
- package/templates/apps/api-ratelimit/app.config.ts +42 -0
- package/templates/apps/api-ratelimit/database/schema.ts +37 -0
- package/templates/apps/api-ratelimit/mutations/notes.create.mutation.server.ts +16 -0
- package/templates/apps/api-ratelimit/mutations/notes.create.mutation.ts +34 -0
- package/templates/apps/api-ratelimit/package.json +29 -0
- package/templates/apps/api-ratelimit/template.json +6 -0
- package/templates/apps/api-ratelimit/tests/notes.create.test.ts +69 -0
- package/templates/apps/api-ratelimit/tsconfig.json +5 -0
- package/templates/apps/api-rbac/README.md +59 -0
- package/templates/apps/api-rbac/app.config.ts +48 -0
- package/templates/apps/api-rbac/database/schema.ts +37 -0
- package/templates/apps/api-rbac/mutations/notes.create.mutation.server.ts +22 -0
- package/templates/apps/api-rbac/mutations/notes.create.mutation.ts +26 -0
- package/templates/apps/api-rbac/package.json +29 -0
- package/templates/apps/api-rbac/template.json +6 -0
- package/templates/apps/api-rbac/tests/notes.create.test.ts +80 -0
- package/templates/apps/api-rbac/tsconfig.json +5 -0
- package/templates/apps/api-rest/README.md +85 -0
- package/templates/apps/api-rest/app.config.ts +70 -0
- package/templates/apps/api-rest/database/schema.ts +52 -0
- package/templates/apps/api-rest/lib/product.ts +30 -0
- package/templates/apps/api-rest/package.json +27 -0
- package/templates/apps/api-rest/routes/v1/products.create.route.tsx +45 -0
- package/templates/apps/api-rest/routes/v1/products.delete.route.tsx +25 -0
- package/templates/apps/api-rest/routes/v1/products.get.route.tsx +29 -0
- package/templates/apps/api-rest/routes/v1/products.list.route.tsx +43 -0
- package/templates/apps/api-rest/template.json +6 -0
- package/templates/apps/api-rest/tests/products.create.test.ts +53 -0
- package/templates/apps/api-rest/tsconfig.json +5 -0
- package/templates/apps/api-saas/README.md +106 -0
- package/templates/apps/api-saas/app.config.ts +51 -0
- package/templates/apps/api-saas/database/schema.ts +50 -0
- package/templates/apps/api-saas/mutations/projects.create.mutation.server.ts +52 -0
- package/templates/apps/api-saas/mutations/projects.create.mutation.ts +18 -0
- package/templates/apps/api-saas/package.json +31 -0
- package/templates/apps/api-saas/queries/projects.list.query.server.ts +7 -0
- package/templates/apps/api-saas/queries/projects.list.query.ts +18 -0
- package/templates/apps/api-saas/template.json +6 -0
- package/templates/apps/api-saas/tests/projects.create.test.ts +47 -0
- package/templates/apps/api-saas/tsconfig.json +5 -0
- package/templates/apps/api-search/README.md +68 -0
- package/templates/apps/api-search/app.config.ts +31 -0
- package/templates/apps/api-search/database/schema.ts +47 -0
- package/templates/apps/api-search/lib/search.ts +27 -0
- package/templates/apps/api-search/mutations/articles.create.mutation.server.ts +19 -0
- package/templates/apps/api-search/mutations/articles.create.mutation.ts +35 -0
- package/templates/apps/api-search/package.json +29 -0
- package/templates/apps/api-search/queries/articles.list.query.server.ts +14 -0
- package/templates/apps/api-search/queries/articles.list.query.ts +19 -0
- package/templates/apps/api-search/seeds/articles.seed.ts +41 -0
- package/templates/apps/api-search/startup/searchBackfill.startup.tsx +24 -0
- package/templates/apps/api-search/template.json +6 -0
- package/templates/apps/api-search/tests/articles.create.test.ts +69 -0
- package/templates/apps/api-search/tsconfig.json +5 -0
- package/templates/apps/api-versioning/README.md +51 -0
- package/templates/apps/api-versioning/actions/documents.asOf.action.server.ts +13 -0
- package/templates/apps/api-versioning/actions/documents.asOf.action.ts +14 -0
- package/templates/apps/api-versioning/actions/documents.history.action.server.ts +15 -0
- package/templates/apps/api-versioning/actions/documents.history.action.ts +17 -0
- package/templates/apps/api-versioning/app.config.ts +24 -0
- package/templates/apps/api-versioning/database/schema.ts +38 -0
- package/templates/apps/api-versioning/mutations/documents.create.mutation.server.ts +8 -0
- package/templates/apps/api-versioning/mutations/documents.create.mutation.ts +21 -0
- package/templates/apps/api-versioning/mutations/documents.update.mutation.server.ts +9 -0
- package/templates/apps/api-versioning/mutations/documents.update.mutation.ts +21 -0
- package/templates/apps/api-versioning/package.json +29 -0
- package/templates/apps/api-versioning/template.json +6 -0
- package/templates/apps/api-versioning/tests/documents.create.test.ts +37 -0
- package/templates/apps/api-versioning/tsconfig.json +5 -0
- package/templates/apps/api-webhooks/.env +6 -0
- package/templates/apps/api-webhooks/README.md +106 -0
- package/templates/apps/api-webhooks/app.config.ts +16 -0
- package/templates/apps/api-webhooks/database/schema.ts +49 -0
- package/templates/apps/api-webhooks/events/order.completed.webhook.tsx +22 -0
- package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.server.ts +37 -0
- package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.ts +15 -0
- package/templates/apps/api-webhooks/package.json +28 -0
- package/templates/apps/api-webhooks/queries/orders.list.query.server.ts +7 -0
- package/templates/apps/api-webhooks/queries/orders.list.query.ts +15 -0
- package/templates/apps/api-webhooks/template.json +6 -0
- package/templates/apps/api-webhooks/tests/orders.fulfill.test.ts +51 -0
- package/templates/apps/api-webhooks/tsconfig.json +5 -0
- package/templates/apps/api-webhooks/webhooks/orders.webhook.tsx +35 -0
- package/templates/apps/changelog/README.md +77 -0
- package/templates/apps/changelog/app.config.ts +27 -0
- package/templates/apps/changelog/content/releases/0.1.0.mdx +19 -0
- package/templates/apps/changelog/content/releases/0.2.0.mdx +28 -0
- package/templates/apps/changelog/package.json +30 -0
- package/templates/apps/changelog/scripts/generate-rss.mjs +38 -0
- package/templates/apps/changelog/src/globals.css +66 -0
- package/templates/apps/changelog/src/globals.d.ts +16 -0
- package/templates/apps/changelog/src/lib/locale.test.ts +72 -0
- package/templates/apps/changelog/src/lib/locale.ts +55 -0
- package/templates/apps/changelog/src/lib/releases.ts +21 -0
- package/templates/apps/changelog/src/locales/de.ts +22 -0
- package/templates/apps/changelog/src/locales/en.ts +30 -0
- package/templates/apps/changelog/src/pages/[locale]/[slug].tsx +24 -0
- package/templates/apps/changelog/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/changelog/src/pages/[locale]/mirrors.test.tsx +95 -0
- package/templates/apps/changelog/src/pages/[slug].test.tsx +127 -0
- package/templates/apps/changelog/src/pages/[slug].tsx +52 -0
- package/templates/apps/changelog/src/pages/index.test.tsx +112 -0
- package/templates/apps/changelog/src/pages/index.tsx +55 -0
- package/templates/apps/changelog/src/pages/layout.tsx +84 -0
- package/templates/apps/changelog/template.json +6 -0
- package/templates/apps/changelog/tsconfig.json +11 -0
- package/templates/apps/edge-functions/README.md +55 -0
- package/templates/apps/edge-functions/functions/aiComplete.serverless.ts +58 -0
- package/templates/apps/edge-functions/functions/currencyConvert.serverless.ts +47 -0
- package/templates/apps/edge-functions/functions/geoGreeting.serverless.ts +44 -0
- package/templates/apps/edge-functions/functions/health.serverless.ts +29 -0
- package/templates/apps/edge-functions/functions/resolveLink.serverless.ts +32 -0
- package/templates/apps/edge-functions/functions/shareLink.serverless.ts +30 -0
- package/templates/apps/edge-functions/functions/slackNotify.serverless.ts +40 -0
- package/templates/apps/edge-functions/functions/verifySignature.serverless.ts +52 -0
- package/templates/apps/edge-functions/package.json +21 -0
- package/templates/apps/edge-functions/template.json +6 -0
- package/templates/apps/edge-functions/tsconfig.json +10 -0
- package/templates/apps/frontend-admin/README.md +40 -0
- package/templates/apps/frontend-admin/app.config.ts +37 -0
- package/templates/apps/frontend-admin/package.json +30 -0
- package/templates/apps/frontend-admin/src/config.ts +8 -0
- package/templates/apps/frontend-admin/src/globals.css +76 -0
- package/templates/apps/frontend-admin/src/globals.d.ts +6 -0
- package/templates/apps/frontend-admin/src/lib/admin.ts +16 -0
- package/templates/apps/frontend-admin/src/lib/auth.ts +24 -0
- package/templates/apps/frontend-admin/src/locales/de.ts +67 -0
- package/templates/apps/frontend-admin/src/locales/en.ts +79 -0
- package/templates/apps/frontend-admin/src/locales/index.ts +15 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/index.test.tsx +55 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/index.tsx +32 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/layout.tsx +29 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/login.test.tsx +73 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/login.tsx +35 -0
- package/templates/apps/frontend-admin/src/pages/admin/[entity].tsx +121 -0
- package/templates/apps/frontend-admin/src/pages/admin/entity.test.tsx +119 -0
- package/templates/apps/frontend-admin/src/pages/admin/error.tsx +20 -0
- package/templates/apps/frontend-admin/src/pages/admin/fallbacks.test.tsx +68 -0
- package/templates/apps/frontend-admin/src/pages/admin/index.test.tsx +88 -0
- package/templates/apps/frontend-admin/src/pages/admin/index.tsx +65 -0
- package/templates/apps/frontend-admin/src/pages/admin/layout.test.tsx +114 -0
- package/templates/apps/frontend-admin/src/pages/admin/layout.tsx +97 -0
- package/templates/apps/frontend-admin/src/pages/admin/loading.tsx +15 -0
- package/templates/apps/frontend-admin/src/pages/admin/not-found.tsx +15 -0
- package/templates/apps/frontend-admin/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-admin/src/pages/layouts.test.tsx +54 -0
- package/templates/apps/frontend-admin/template.json +6 -0
- package/templates/apps/frontend-admin/tsconfig.json +11 -0
- package/templates/apps/frontend-app/README.md +98 -0
- package/templates/apps/frontend-app/app.config.ts +43 -0
- package/templates/apps/frontend-app/package.json +30 -0
- package/templates/apps/frontend-app/src/locales/de.ts +36 -0
- package/templates/apps/frontend-app/src/locales/en.ts +43 -0
- package/templates/apps/frontend-app/src/locales/index.ts +15 -0
- package/templates/apps/frontend-app/src/pages/index.test.tsx +167 -0
- package/templates/apps/frontend-app/src/pages/index.tsx +136 -0
- package/templates/apps/frontend-app/src/pages/layout.tsx +41 -0
- package/templates/apps/frontend-app/src/pages/schema-ui.test.tsx +99 -0
- package/templates/apps/frontend-app/src/pages/schema-ui.tsx +74 -0
- package/templates/apps/frontend-app/template.json +6 -0
- package/templates/apps/frontend-app/tsconfig.json +11 -0
- package/templates/apps/frontend-blank/README.md +18 -0
- package/templates/apps/frontend-blank/app.config.ts +29 -0
- package/templates/apps/frontend-blank/package.json +29 -0
- package/templates/apps/frontend-blank/src/locales/de.ts +15 -0
- package/templates/apps/frontend-blank/src/locales/en.ts +22 -0
- package/templates/apps/frontend-blank/src/locales/index.ts +15 -0
- package/templates/apps/frontend-blank/src/pages/index.test.tsx +55 -0
- package/templates/apps/frontend-blank/src/pages/index.tsx +27 -0
- package/templates/apps/frontend-blank/src/pages/layout.test.tsx +54 -0
- package/templates/apps/frontend-blank/src/pages/layout.tsx +35 -0
- package/templates/apps/frontend-blank/template.json +6 -0
- package/templates/apps/frontend-blank/tsconfig.json +11 -0
- package/templates/apps/frontend-contact/README.md +65 -0
- package/templates/apps/frontend-contact/app.config.ts +25 -0
- package/templates/apps/frontend-contact/functions/sendMessage.serverless.ts +69 -0
- package/templates/apps/frontend-contact/package.json +33 -0
- package/templates/apps/frontend-contact/src/components/ContactForm.island.test.tsx +142 -0
- package/templates/apps/frontend-contact/src/components/ContactForm.island.tsx +104 -0
- package/templates/apps/frontend-contact/src/config.ts +12 -0
- package/templates/apps/frontend-contact/src/globals.css +84 -0
- package/templates/apps/frontend-contact/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-contact/src/locales/de.ts +26 -0
- package/templates/apps/frontend-contact/src/locales/en.ts +29 -0
- package/templates/apps/frontend-contact/src/pages/[locale]/index.tsx +14 -0
- package/templates/apps/frontend-contact/src/pages/index.test.tsx +70 -0
- package/templates/apps/frontend-contact/src/pages/index.tsx +63 -0
- package/templates/apps/frontend-contact/src/pages/layout.tsx +65 -0
- package/templates/apps/frontend-contact/template.json +6 -0
- package/templates/apps/frontend-contact/tsconfig.json +11 -0
- package/templates/apps/frontend-dashboard/README.md +54 -0
- package/templates/apps/frontend-dashboard/app.config.ts +39 -0
- package/templates/apps/frontend-dashboard/package.json +29 -0
- package/templates/apps/frontend-dashboard/src/config.ts +8 -0
- package/templates/apps/frontend-dashboard/src/globals.css +72 -0
- package/templates/apps/frontend-dashboard/src/globals.d.ts +6 -0
- package/templates/apps/frontend-dashboard/src/lib/auth.ts +27 -0
- package/templates/apps/frontend-dashboard/src/locales/de.ts +49 -0
- package/templates/apps/frontend-dashboard/src/locales/en.ts +60 -0
- package/templates/apps/frontend-dashboard/src/locales/index.ts +15 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/index.test.tsx +55 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/index.tsx +34 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/layout.tsx +32 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/login.test.tsx +74 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/login.tsx +40 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/error.tsx +21 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/fallbacks.test.tsx +68 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/index.test.tsx +55 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/index.tsx +38 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.test.tsx +88 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.tsx +69 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/loading.tsx +21 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/not-found.tsx +19 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/settings.test.tsx +69 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/settings.tsx +35 -0
- package/templates/apps/frontend-dashboard/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-dashboard/src/pages/layouts.test.tsx +54 -0
- package/templates/apps/frontend-dashboard/template.json +6 -0
- package/templates/apps/frontend-dashboard/tsconfig.json +11 -0
- package/templates/apps/frontend-docs/README.md +19 -0
- package/templates/apps/frontend-docs/app.config.ts +25 -0
- package/templates/apps/frontend-docs/package.json +29 -0
- package/templates/apps/frontend-docs/src/globals.css +32 -0
- package/templates/apps/frontend-docs/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-docs/src/lib/locale.ts +56 -0
- package/templates/apps/frontend-docs/src/locales/de.ts +24 -0
- package/templates/apps/frontend-docs/src/locales/en.ts +28 -0
- package/templates/apps/frontend-docs/src/pages/[locale]/docs/[...slug].tsx +18 -0
- package/templates/apps/frontend-docs/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-docs/src/pages/[locale]/mirrors.test.tsx +67 -0
- package/templates/apps/frontend-docs/src/pages/docs/[...slug].test.tsx +115 -0
- package/templates/apps/frontend-docs/src/pages/docs/[...slug].tsx +82 -0
- package/templates/apps/frontend-docs/src/pages/index.test.tsx +90 -0
- package/templates/apps/frontend-docs/src/pages/index.tsx +45 -0
- package/templates/apps/frontend-docs/src/pages/layout.test.tsx +84 -0
- package/templates/apps/frontend-docs/src/pages/layout.tsx +66 -0
- package/templates/apps/frontend-docs/template.json +6 -0
- package/templates/apps/frontend-docs/tsconfig.json +11 -0
- package/templates/apps/frontend-i18n/README.md +61 -0
- package/templates/apps/frontend-i18n/app.config.ts +33 -0
- package/templates/apps/frontend-i18n/package.json +28 -0
- package/templates/apps/frontend-i18n/src/globals.css +46 -0
- package/templates/apps/frontend-i18n/src/globals.d.ts +6 -0
- package/templates/apps/frontend-i18n/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-i18n/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-i18n/src/locales/de.ts +24 -0
- package/templates/apps/frontend-i18n/src/locales/en.ts +25 -0
- package/templates/apps/frontend-i18n/src/pages/[locale]/about.tsx +11 -0
- package/templates/apps/frontend-i18n/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-i18n/src/pages/[locale]/mirrors.test.tsx +50 -0
- package/templates/apps/frontend-i18n/src/pages/about.test.tsx +75 -0
- package/templates/apps/frontend-i18n/src/pages/about.tsx +31 -0
- package/templates/apps/frontend-i18n/src/pages/index.test.tsx +102 -0
- package/templates/apps/frontend-i18n/src/pages/index.tsx +43 -0
- package/templates/apps/frontend-i18n/src/pages/layout.test.tsx +86 -0
- package/templates/apps/frontend-i18n/src/pages/layout.tsx +70 -0
- package/templates/apps/frontend-i18n/template.json +6 -0
- package/templates/apps/frontend-i18n/tsconfig.json +11 -0
- package/templates/apps/frontend-landing/README.md +17 -0
- package/templates/apps/frontend-landing/app.config.ts +25 -0
- package/templates/apps/frontend-landing/package.json +29 -0
- package/templates/apps/frontend-landing/src/globals.css +23 -0
- package/templates/apps/frontend-landing/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-landing/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-landing/src/locales/de.ts +24 -0
- package/templates/apps/frontend-landing/src/locales/en.ts +26 -0
- package/templates/apps/frontend-landing/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-landing/src/pages/[locale]/mirrors.test.tsx +37 -0
- package/templates/apps/frontend-landing/src/pages/index.test.tsx +110 -0
- package/templates/apps/frontend-landing/src/pages/index.tsx +75 -0
- package/templates/apps/frontend-landing/src/pages/layout.test.tsx +84 -0
- package/templates/apps/frontend-landing/src/pages/layout.tsx +66 -0
- package/templates/apps/frontend-landing/template.json +6 -0
- package/templates/apps/frontend-landing/tsconfig.json +11 -0
- package/templates/apps/frontend-spa/README.md +45 -0
- package/templates/apps/frontend-spa/app.config.ts +27 -0
- package/templates/apps/frontend-spa/package.json +29 -0
- package/templates/apps/frontend-spa/src/globals.css +84 -0
- package/templates/apps/frontend-spa/src/locales/de.ts +22 -0
- package/templates/apps/frontend-spa/src/locales/en.ts +29 -0
- package/templates/apps/frontend-spa/src/locales/index.ts +15 -0
- package/templates/apps/frontend-spa/src/pages/index.test.tsx +137 -0
- package/templates/apps/frontend-spa/src/pages/index.tsx +123 -0
- package/templates/apps/frontend-spa/src/pages/layout.tsx +27 -0
- package/templates/apps/frontend-spa/template.json +6 -0
- package/templates/apps/frontend-spa/tsconfig.json +11 -0
- package/templates/apps/frontend-ssr/README.md +68 -0
- package/templates/apps/frontend-ssr/app.config.ts +32 -0
- package/templates/apps/frontend-ssr/package.json +29 -0
- package/templates/apps/frontend-ssr/src/globals.css +67 -0
- package/templates/apps/frontend-ssr/src/locales/de.ts +41 -0
- package/templates/apps/frontend-ssr/src/locales/en.ts +54 -0
- package/templates/apps/frontend-ssr/src/locales/index.ts +16 -0
- package/templates/apps/frontend-ssr/src/pages/feed-swr.test.tsx +69 -0
- package/templates/apps/frontend-ssr/src/pages/feed-swr.tsx +54 -0
- package/templates/apps/frontend-ssr/src/pages/feed.test.tsx +73 -0
- package/templates/apps/frontend-ssr/src/pages/feed.tsx +64 -0
- package/templates/apps/frontend-ssr/src/pages/index.test.tsx +89 -0
- package/templates/apps/frontend-ssr/src/pages/index.tsx +72 -0
- package/templates/apps/frontend-ssr/src/pages/layout.tsx +37 -0
- package/templates/apps/frontend-ssr/template.json +6 -0
- package/templates/apps/frontend-ssr/tsconfig.json +11 -0
- package/templates/apps/frontend-ssr-api/README.md +50 -0
- package/templates/apps/frontend-ssr-api/app.config.ts +43 -0
- package/templates/apps/frontend-ssr-api/package.json +30 -0
- package/templates/apps/frontend-ssr-api/src/globals.css +38 -0
- package/templates/apps/frontend-ssr-api/src/globals.d.ts +6 -0
- package/templates/apps/frontend-ssr-api/src/locales/de.ts +20 -0
- package/templates/apps/frontend-ssr-api/src/locales/en.ts +31 -0
- package/templates/apps/frontend-ssr-api/src/locales/index.ts +16 -0
- package/templates/apps/frontend-ssr-api/src/pages/index.test.tsx +105 -0
- package/templates/apps/frontend-ssr-api/src/pages/index.tsx +83 -0
- package/templates/apps/frontend-ssr-api/src/pages/layout.tsx +28 -0
- package/templates/apps/frontend-ssr-api/template.json +6 -0
- package/templates/apps/frontend-ssr-api/tsconfig.json +11 -0
- package/templates/apps/frontend-static-blog/README.md +49 -0
- package/templates/apps/frontend-static-blog/app.config.ts +34 -0
- package/templates/apps/frontend-static-blog/package.json +28 -0
- package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.test.tsx +65 -0
- package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.tsx +35 -0
- package/templates/apps/frontend-static-blog/src/content/posts.ts +64 -0
- package/templates/apps/frontend-static-blog/src/globals.css +75 -0
- package/templates/apps/frontend-static-blog/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-static-blog/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-static-blog/src/locales/de.ts +19 -0
- package/templates/apps/frontend-static-blog/src/locales/en.ts +26 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/blog/[slug].tsx +20 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/mirrors.test.tsx +62 -0
- package/templates/apps/frontend-static-blog/src/pages/blog/[slug].test.tsx +116 -0
- package/templates/apps/frontend-static-blog/src/pages/blog/[slug].tsx +69 -0
- package/templates/apps/frontend-static-blog/src/pages/index.test.tsx +100 -0
- package/templates/apps/frontend-static-blog/src/pages/index.tsx +58 -0
- package/templates/apps/frontend-static-blog/src/pages/layout.tsx +63 -0
- package/templates/apps/frontend-static-blog/template.json +6 -0
- package/templates/apps/frontend-static-blog/tsconfig.json +11 -0
- package/templates/baselines/bare/.env.example +37 -0
- package/templates/baselines/bare/README.md +47 -0
- package/templates/baselines/bare/baseline.json +31 -0
- package/templates/baselines/bare/deploy/README.md +43 -0
- package/templates/baselines/bare/deploy/voltro.service.example +36 -0
- package/templates/baselines/compose/.env.example +46 -0
- package/templates/baselines/compose/README.md +69 -0
- package/templates/baselines/compose/baseline.json +51 -0
- package/templates/baselines/compose/docker/.dockerignore +38 -0
- package/templates/baselines/compose/docker/api.Dockerfile +57 -0
- package/templates/baselines/compose/docker/dev.Dockerfile +35 -0
- package/templates/baselines/compose/docker/web.Dockerfile +59 -0
- package/templates/baselines/compose/docker-compose.dev.yml +89 -0
- package/templates/baselines/compose/docker-compose.prod.yml +87 -0
- package/templates/baselines/compose/docker-compose.yml +41 -0
- package/templates/baselines/compose-mariadb/.env.example +57 -0
- package/templates/baselines/compose-mariadb/README.md +78 -0
- package/templates/baselines/compose-mariadb/baseline.json +51 -0
- package/templates/baselines/compose-mariadb/docker/.dockerignore +38 -0
- package/templates/baselines/compose-mariadb/docker/api.Dockerfile +57 -0
- package/templates/baselines/compose-mariadb/docker/dev.Dockerfile +35 -0
- package/templates/baselines/compose-mariadb/docker/mariadb-init.sql +6 -0
- package/templates/baselines/compose-mariadb/docker/web.Dockerfile +59 -0
- package/templates/baselines/compose-mariadb/docker-compose.dev.yml +117 -0
- package/templates/baselines/compose-mariadb/docker-compose.prod.yml +114 -0
- package/templates/baselines/compose-mariadb/docker-compose.yml +79 -0
- package/templates/baselines/helm/.env.example +39 -0
- package/templates/baselines/helm/README.md +98 -0
- package/templates/baselines/helm/baseline.json +53 -0
- package/templates/baselines/helm/charts/voltro-app/.helmignore +10 -0
- package/templates/baselines/helm/charts/voltro-app/Chart.yaml +10 -0
- package/templates/baselines/helm/charts/voltro-app/templates/_helpers.tpl +36 -0
- package/templates/baselines/helm/charts/voltro-app/templates/configmap.yaml +13 -0
- package/templates/baselines/helm/charts/voltro-app/templates/deployment-api.yaml +120 -0
- package/templates/baselines/helm/charts/voltro-app/templates/deployment-web.yaml +45 -0
- package/templates/baselines/helm/charts/voltro-app/templates/ingress.yaml +37 -0
- package/templates/baselines/helm/charts/voltro-app/templates/postgres-service.yaml +19 -0
- package/templates/baselines/helm/charts/voltro-app/templates/postgres-statefulset.yaml +73 -0
- package/templates/baselines/helm/charts/voltro-app/templates/secret.yaml +33 -0
- package/templates/baselines/helm/charts/voltro-app/templates/service-api.yaml +19 -0
- package/templates/baselines/helm/charts/voltro-app/templates/service-web.yaml +19 -0
- package/templates/baselines/helm/charts/voltro-app/values-dev.yaml +21 -0
- package/templates/baselines/helm/charts/voltro-app/values-prod.yaml +58 -0
- package/templates/baselines/helm/charts/voltro-app/values-staging.yaml +25 -0
- package/templates/baselines/helm/charts/voltro-app/values.yaml +109 -0
- package/templates/baselines/helm/deploy/README.md +94 -0
- package/templates/patches/@effect__cluster@0.59.0.patch +262 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// `/` — the bilingual landing template. A statically pre-rendered marketing
|
|
4
|
+
// page with zero framework JS on the wire (`renderMode='static'`,
|
|
5
|
+
// `interactive='none'`), localised per URL locale (Strategy B). Two things
|
|
6
|
+
// worth testing:
|
|
7
|
+
// 1. `meta({ locale })` is localized — it reads the REAL catalog via
|
|
8
|
+
// getCatalog, so we assert en vs de titles/descriptions directly (no mock).
|
|
9
|
+
// 2. the rendered body shows localized copy. The page pulls strings from
|
|
10
|
+
// @voltro/i18n (<T>); we mock it to resolve against the real catalog for a
|
|
11
|
+
// controllable "current locale". The hero <h1> carries a `{{capProjectName}}`
|
|
12
|
+
// scaffold placeholder, so we assert the heading EXISTS, not its text.
|
|
13
|
+
|
|
14
|
+
import { afterEach, describe, expect, test, vi } from 'vitest'
|
|
15
|
+
import { act, createElement, type ReactNode } from 'react'
|
|
16
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
17
|
+
import en from '../locales/en'
|
|
18
|
+
import de from '../locales/de'
|
|
19
|
+
|
|
20
|
+
// Controllable "active locale" for the i18n mock.
|
|
21
|
+
const catalogs: Record<string, Record<string, string>> = { en, de }
|
|
22
|
+
let currentLocale = 'en'
|
|
23
|
+
|
|
24
|
+
// Minimal ICU-ish interpolation: replace {key} with values[key]. Rich-text
|
|
25
|
+
// tags (`<strong>`, `<code>`) are left inline as text — enough to assert the
|
|
26
|
+
// page rendered the right (localized) copy; react-intl injects real elements.
|
|
27
|
+
const fill = (msg: string, values?: Record<string, unknown>): string =>
|
|
28
|
+
values ? msg.replace(/\{(\w+)\}/g, (_, k: string) => String(values[k] ?? `{${k}}`)) : msg
|
|
29
|
+
|
|
30
|
+
vi.mock('@voltro/i18n', () => ({
|
|
31
|
+
// The locale catalogs call these at module top-level; identity mirrors the
|
|
32
|
+
// real (compile-time-only) implementations so importing them doesn't throw.
|
|
33
|
+
defineCatalog: <T,>(c: T): T => c,
|
|
34
|
+
defineLocale:
|
|
35
|
+
() =>
|
|
36
|
+
<T,>(c: T): T =>
|
|
37
|
+
c,
|
|
38
|
+
T: ({ id, values }: { id: string; values?: Record<string, unknown> }) =>
|
|
39
|
+
fill(catalogs[currentLocale]?.[id] ?? id, values),
|
|
40
|
+
}))
|
|
41
|
+
|
|
42
|
+
const { default: Index, meta, renderMode, interactive } = await import('./index')
|
|
43
|
+
|
|
44
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
45
|
+
|
|
46
|
+
let container: HTMLDivElement
|
|
47
|
+
let root: Root
|
|
48
|
+
|
|
49
|
+
const render = (node: ReactNode): void => {
|
|
50
|
+
container = document.createElement('div')
|
|
51
|
+
document.body.appendChild(container)
|
|
52
|
+
act(() => {
|
|
53
|
+
root = createRoot(container)
|
|
54
|
+
root.render(node)
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
currentLocale = 'en'
|
|
60
|
+
if (root) act(() => root.unmount())
|
|
61
|
+
container?.remove()
|
|
62
|
+
document.body.innerHTML = ''
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
describe('index — config + localized meta', () => {
|
|
66
|
+
test('is a static page with no interactive JS on the wire', () => {
|
|
67
|
+
expect(renderMode).toBe('static')
|
|
68
|
+
expect(interactive).toBe('none')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('meta() returns the English catalog title/description for locale en', () => {
|
|
72
|
+
const m = meta({ locale: 'en' })
|
|
73
|
+
expect(m.title).toBe('Home')
|
|
74
|
+
expect(m.description).toContain('landing page')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('meta() returns the German catalog title/description for locale de', () => {
|
|
78
|
+
const m = meta({ locale: 'de' })
|
|
79
|
+
expect(m.title).toBe('Start')
|
|
80
|
+
expect(m.description).toContain('Landingpage')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('meta() falls back to the default (en) catalog for an unknown locale', () => {
|
|
84
|
+
expect(meta({ locale: 'zz' }).title).toBe('Home')
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe('index — localized render', () => {
|
|
89
|
+
test('renders a hero heading + the English Features section', () => {
|
|
90
|
+
currentLocale = 'en'
|
|
91
|
+
render(createElement(Index))
|
|
92
|
+
expect(container.querySelector('main h1')).toBeTruthy()
|
|
93
|
+
const featureSection = container.querySelector('#features')
|
|
94
|
+
expect(featureSection).toBeTruthy()
|
|
95
|
+
expect(featureSection!.querySelector('h2')!.textContent).toBe('Features')
|
|
96
|
+
const bullets = Array.from(container.querySelectorAll('#features li'))
|
|
97
|
+
expect(bullets).toHaveLength(3)
|
|
98
|
+
expect(container.textContent).toContain('Reactive subscriptions')
|
|
99
|
+
expect(container.querySelector('a[href="#features"]')).toBeTruthy()
|
|
100
|
+
expect(container.textContent).toContain('Ready to start?')
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('renders the German copy when the active locale is de', () => {
|
|
104
|
+
currentLocale = 'de'
|
|
105
|
+
render(createElement(Index))
|
|
106
|
+
expect(container.querySelector('#features h2')!.textContent).toBe('Funktionen')
|
|
107
|
+
expect(container.textContent).toContain('Reaktive Subscriptions')
|
|
108
|
+
expect(container.textContent).toContain('Bereit loszulegen?')
|
|
109
|
+
})
|
|
110
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Landing page for the {{projectName}} project. Pre-rendered at build time
|
|
2
|
+
// by `voltro build` (renderMode='static'), then served straight from the
|
|
3
|
+
// file system by `voltro start`. `interactive='none'` strips ALL framework
|
|
4
|
+
// JS from the wire — each locale is plain, pre-rendered HTML + CSS.
|
|
5
|
+
//
|
|
6
|
+
// Bilingual (Strategy B): mounted at `/` (default locale) and mirrored at
|
|
7
|
+
// `/de` by src/pages/[locale]/index.tsx. `meta` is a FUNCTION of `{ locale }`
|
|
8
|
+
// so each variant's <title> / <description> is localised in the emitted HTML.
|
|
9
|
+
|
|
10
|
+
import type { ReactNode } from 'react'
|
|
11
|
+
import type { PageMeta } from '@voltro/web'
|
|
12
|
+
import { T } from '@voltro/i18n'
|
|
13
|
+
import { getCatalog } from '../lib/locale'
|
|
14
|
+
|
|
15
|
+
// Pre-render at build time. Removes the runtime dependency entirely
|
|
16
|
+
// for plain-content pages — no rpc client, no react hydration. If you
|
|
17
|
+
// later add a sign-up form or anything interactive, switch to
|
|
18
|
+
// `interactive: 'islands'` and mark the form component as an island.
|
|
19
|
+
export const renderMode = 'static' as const
|
|
20
|
+
export const interactive = 'none' as const
|
|
21
|
+
|
|
22
|
+
export const meta = ({ locale }: { locale: string }): PageMeta => {
|
|
23
|
+
const c = getCatalog(locale)
|
|
24
|
+
return {
|
|
25
|
+
title: c['meta.home.title'],
|
|
26
|
+
description: c['meta.home.description'],
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Rich-text tag handlers — each `<tag>` used inside a message needs a
|
|
31
|
+
// matching value function so react-intl injects the real element.
|
|
32
|
+
const strong = (chunks: ReactNode): ReactNode => <strong>{chunks}</strong>
|
|
33
|
+
const code = (chunks: ReactNode): ReactNode => <code>{chunks}</code>
|
|
34
|
+
|
|
35
|
+
export default function Index(): ReactNode {
|
|
36
|
+
return (
|
|
37
|
+
<main>
|
|
38
|
+
<section>
|
|
39
|
+
<h1>{{capProjectName}}</h1>
|
|
40
|
+
<p>
|
|
41
|
+
<T id="hero.tagline" />
|
|
42
|
+
</p>
|
|
43
|
+
<a href="#features">
|
|
44
|
+
<T id="hero.cta" />
|
|
45
|
+
</a>
|
|
46
|
+
</section>
|
|
47
|
+
|
|
48
|
+
<section id="features">
|
|
49
|
+
<h2>
|
|
50
|
+
<T id="features.heading" />
|
|
51
|
+
</h2>
|
|
52
|
+
<ul>
|
|
53
|
+
<li>
|
|
54
|
+
<T id="features.reactive" values={{ strong }} />
|
|
55
|
+
</li>
|
|
56
|
+
<li>
|
|
57
|
+
<T id="features.tenant" values={{ strong }} />
|
|
58
|
+
</li>
|
|
59
|
+
<li>
|
|
60
|
+
<T id="features.conventions" values={{ strong, code }} />
|
|
61
|
+
</li>
|
|
62
|
+
</ul>
|
|
63
|
+
</section>
|
|
64
|
+
|
|
65
|
+
<section>
|
|
66
|
+
<h2>
|
|
67
|
+
<T id="cta.heading" />
|
|
68
|
+
</h2>
|
|
69
|
+
<p>
|
|
70
|
+
<T id="cta.body" values={{ code }} />
|
|
71
|
+
</p>
|
|
72
|
+
</section>
|
|
73
|
+
</main>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// Root layout — renders the top nav whose Home link + language switcher are
|
|
4
|
+
// locale-prefixed off the CURRENT url (useUrlLocale → useLocation). We mock
|
|
5
|
+
// useLocation (@voltro/web) to drive the active locale, and mock @voltro/i18n so
|
|
6
|
+
// <I18nProvider> is a pass-through and <T> resolves against the real catalogs.
|
|
7
|
+
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
|
9
|
+
import { act, createElement, type ReactNode } from 'react'
|
|
10
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
11
|
+
import en from '../locales/en'
|
|
12
|
+
import de from '../locales/de'
|
|
13
|
+
|
|
14
|
+
const location = vi.fn<() => string>()
|
|
15
|
+
vi.mock('@voltro/web', () => ({ useLocation: () => location() }))
|
|
16
|
+
|
|
17
|
+
const catalogs: Record<string, Record<string, string>> = { en, de }
|
|
18
|
+
const localeFor = (path: string): string => (/^\/de(?:\/|$)/.test(path) ? 'de' : 'en')
|
|
19
|
+
|
|
20
|
+
vi.mock('@voltro/i18n', () => ({
|
|
21
|
+
// The locale catalogs call these at module top-level; identity mirrors the
|
|
22
|
+
// real (compile-time-only) implementations so importing them doesn't throw.
|
|
23
|
+
defineCatalog: <T,>(c: T): T => c,
|
|
24
|
+
defineLocale:
|
|
25
|
+
() =>
|
|
26
|
+
<T,>(c: T): T =>
|
|
27
|
+
c,
|
|
28
|
+
I18nProvider: ({ children }: { children?: ReactNode }) => children,
|
|
29
|
+
// The nav uses <T> for link labels; resolve against the catalog of the
|
|
30
|
+
// locale implied by the current path (matches what useUrlLocale computes).
|
|
31
|
+
T: ({ id }: { id: string }) => catalogs[localeFor(location())]?.[id] ?? id,
|
|
32
|
+
}))
|
|
33
|
+
|
|
34
|
+
const { default: Layout } = await import('./layout')
|
|
35
|
+
|
|
36
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
37
|
+
|
|
38
|
+
let container: HTMLDivElement
|
|
39
|
+
let root: Root
|
|
40
|
+
|
|
41
|
+
const render = (node: ReactNode): void => {
|
|
42
|
+
container = document.createElement('div')
|
|
43
|
+
document.body.appendChild(container)
|
|
44
|
+
act(() => {
|
|
45
|
+
root = createRoot(container)
|
|
46
|
+
root.render(node)
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const hrefs = (): Array<string | null> =>
|
|
51
|
+
Array.from(container.querySelectorAll('a')).map((a) => a.getAttribute('href'))
|
|
52
|
+
|
|
53
|
+
beforeEach(() => location.mockReset())
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
if (root) act(() => root.unmount())
|
|
57
|
+
container?.remove()
|
|
58
|
+
document.body.innerHTML = ''
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('landing layout — English (bare path)', () => {
|
|
62
|
+
test('renders a bare Home link, an English nav label and the DE switcher target', () => {
|
|
63
|
+
location.mockReturnValue('/')
|
|
64
|
+
render(createElement(Layout, { children: createElement('p', null, 'page') }))
|
|
65
|
+
const h = hrefs()
|
|
66
|
+
expect(h).toContain('/')
|
|
67
|
+
// the language switcher keeps you on the same bare page, re-prefixed per locale
|
|
68
|
+
expect(h).toContain('/de')
|
|
69
|
+
expect(container.textContent).toContain('Home')
|
|
70
|
+
expect(container.textContent).toContain('page')
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('landing layout — German (/de prefix)', () => {
|
|
75
|
+
test('prefixes the home link with /de and renders German labels', () => {
|
|
76
|
+
location.mockReturnValue('/de')
|
|
77
|
+
render(createElement(Layout, { children: null }))
|
|
78
|
+
const h = hrefs()
|
|
79
|
+
expect(h).toContain('/de')
|
|
80
|
+
// switching back to en strips the prefix
|
|
81
|
+
expect(h).toContain('/')
|
|
82
|
+
expect(container.textContent).toContain('Start')
|
|
83
|
+
})
|
|
84
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Root layout. Strategy B (URL-prefix i18n): an INNER <I18nProvider> reads
|
|
2
|
+
// the locale FROM THE URL, so the pre-render pipeline resolves the right
|
|
3
|
+
// catalog per locale-prefixed path (and client-side nav swaps it without a
|
|
4
|
+
// full reload). The language switcher is plain <a> links — no JS needed, so
|
|
5
|
+
// this stays compatible with the pages' `interactive: 'none'`.
|
|
6
|
+
//
|
|
7
|
+
// `<html>`/`<head>`/`<body>` are owned by the framework shell — don't render
|
|
8
|
+
// them here. Page <title> + meta come from each page's `meta` export.
|
|
9
|
+
|
|
10
|
+
import type { ReactNode } from 'react'
|
|
11
|
+
import { I18nProvider, T } from '@voltro/i18n'
|
|
12
|
+
import { useLocation } from '@voltro/web'
|
|
13
|
+
import {
|
|
14
|
+
CATALOGS,
|
|
15
|
+
DEFAULT_LOCALE,
|
|
16
|
+
SUPPORTED_LOCALES,
|
|
17
|
+
stripLocalePrefix,
|
|
18
|
+
useUrlLocale,
|
|
19
|
+
withLocalePrefix,
|
|
20
|
+
} from '../lib/locale'
|
|
21
|
+
import '../globals.css'
|
|
22
|
+
|
|
23
|
+
function TopNav(): ReactNode {
|
|
24
|
+
const locale = useUrlLocale()
|
|
25
|
+
// The path WITHOUT its locale prefix — so the language switch keeps you
|
|
26
|
+
// on the same page (e.g. /de → /, then re-prefix per locale).
|
|
27
|
+
const barePath = stripLocalePrefix(useLocation())
|
|
28
|
+
|
|
29
|
+
// Plain <a> (not <Link>): these hrefs are COMPUTED per locale, and
|
|
30
|
+
// <Link to> wants a statically-known `VoltroUrl` literal. Each language
|
|
31
|
+
// is its own crawlable URL, reached by a full navigation.
|
|
32
|
+
return (
|
|
33
|
+
<nav className="topnav">
|
|
34
|
+
<a href={withLocalePrefix('/', locale)}>
|
|
35
|
+
<T id="nav.home" />
|
|
36
|
+
</a>
|
|
37
|
+
<span className="spacer" />
|
|
38
|
+
<span className="lang">
|
|
39
|
+
<span className="label">
|
|
40
|
+
<T id="nav.language" />:
|
|
41
|
+
</span>
|
|
42
|
+
{SUPPORTED_LOCALES.map((code) => (
|
|
43
|
+
<a
|
|
44
|
+
key={code}
|
|
45
|
+
href={withLocalePrefix(barePath, code)}
|
|
46
|
+
aria-current={code === locale ? 'true' : undefined}
|
|
47
|
+
>
|
|
48
|
+
{code.toUpperCase()}
|
|
49
|
+
</a>
|
|
50
|
+
))}
|
|
51
|
+
</span>
|
|
52
|
+
</nav>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default function Layout({ children }: { readonly children: ReactNode }): ReactNode {
|
|
57
|
+
const locale = useUrlLocale()
|
|
58
|
+
return (
|
|
59
|
+
<I18nProvider locale={locale} messages={CATALOGS[locale]} defaultLocale={DEFAULT_LOCALE}>
|
|
60
|
+
<div className="shell">
|
|
61
|
+
<TopNav />
|
|
62
|
+
{children}
|
|
63
|
+
</div>
|
|
64
|
+
</I18nProvider>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "frontend-landing",
|
|
3
|
+
"kind": "web",
|
|
4
|
+
"summary": "Bilingual (en/de) marketing landing page: hero, features grid, CTA. Static-render mode with zero JS on the wire; URL-prefix i18n (`/` + `/de`) pre-renders one HTML file per locale. Flip interactive: 'islands' for interactive pieces.",
|
|
5
|
+
"tags": ["web", "frontend", "landing", "marketing", "static", "i18n", "intl", "localization"]
|
|
6
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"jsx": "react-jsx",
|
|
5
|
+
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "Bundler"
|
|
8
|
+
},
|
|
9
|
+
"include": ["src/**/*"],
|
|
10
|
+
"exclude": ["node_modules", "dist", ".framework"]
|
|
11
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# {{projectName}} / {{appName}}
|
|
2
|
+
|
|
3
|
+
Pure SPA (template: **frontend-spa**) — a fully client-rendered tool
|
|
4
|
+
with no backend and no SSR.
|
|
5
|
+
|
|
6
|
+
## Boot
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pnpm install
|
|
10
|
+
pnpm --filter @{{projectName}}/{{appName}} dev
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## When to reach for `renderMode: 'spa'`
|
|
14
|
+
|
|
15
|
+
Use it when the page is heavily interactive AND its state lives in the
|
|
16
|
+
browser, so a server-rendered first paint would just be discarded on
|
|
17
|
+
hydration. This template's bill splitter is the canonical case: every
|
|
18
|
+
input is interactive and the last tip % is remembered in `localStorage`
|
|
19
|
+
— there is nothing meaningful for the server to render.
|
|
20
|
+
|
|
21
|
+
For content pages (marketing, blog, docs) prefer `renderMode: 'static'`
|
|
22
|
+
and reach for `island()` when only a few widgets are interactive — see
|
|
23
|
+
the **frontend-static-blog** template. Use `spa` only when the JS *is*
|
|
24
|
+
the page.
|
|
25
|
+
|
|
26
|
+
## What this template demonstrates
|
|
27
|
+
|
|
28
|
+
| Concept | Where |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `renderMode = 'spa'` (client-render, no pre-render) | `src/pages/index.tsx` |
|
|
31
|
+
| Default `interactive: 'full'` (whole tree hydrates) | `src/pages/index.tsx` |
|
|
32
|
+
| Browser-only state via `localStorage` (read in an effect) | `src/pages/index.tsx` |
|
|
33
|
+
| Zero backend — no rpc client, no loader | the whole app |
|
|
34
|
+
|
|
35
|
+
## Build + serve
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pnpm --filter @{{projectName}}/{{appName}} build
|
|
39
|
+
pnpm --filter @{{projectName}}/{{appName}} start
|
|
40
|
+
# or push the static dist/ to a CDN:
|
|
41
|
+
voltro static deploy --host cloudflare-pages --project-name {{appName}} --spa
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The `--spa` flag serves `index.html` for unknown routes — the correct
|
|
45
|
+
fallback for a single-page app on a CDN.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Web app for the {{projectName}} project — a pure SPA (single-page app).
|
|
2
|
+
//
|
|
3
|
+
// Pages here set `renderMode = 'spa'`: the framework skips pre-rendering
|
|
4
|
+
// and the client renders on load. That is the right call when the page's
|
|
5
|
+
// state lives entirely in the browser (here: localStorage), so an SSR'd
|
|
6
|
+
// first paint would just be thrown away on hydration.
|
|
7
|
+
import { defineEnv, envVar } from '@voltro/env'
|
|
8
|
+
|
|
9
|
+
export const env = defineEnv({
|
|
10
|
+
VOLTRO_PUBLIC_APP_NAME: envVar.string({ access: 'public', default: '{{capProjectName}}' }),
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export default {
|
|
14
|
+
type: 'web' as const,
|
|
15
|
+
name: '{{capProjectName}}{{capAppName}}',
|
|
16
|
+
port: {{port}},
|
|
17
|
+
theme: 'system' as const,
|
|
18
|
+
env,
|
|
19
|
+
// Bilingual by default. The framework auto-wires a cookie-driven
|
|
20
|
+
// <I18nProvider> from these codes and loads the matching
|
|
21
|
+
// `src/locales/<code>.ts` catalog — so `<T id="…" />` / `useT()` /
|
|
22
|
+
// `useLocale()` work app-wide with no manual setup. The <LocaleSwitcher>
|
|
23
|
+
// in the layout writes the `voltro:lang` cookie + reloads. Grow the app to
|
|
24
|
+
// more languages by adding a code here + a `src/locales/<code>.ts`.
|
|
25
|
+
locales: ['en', 'de'] as const,
|
|
26
|
+
defaultLocale: 'en' as const,
|
|
27
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@{{projectName}}/{{appName}}",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "voltro dev .",
|
|
8
|
+
"build": "voltro build .",
|
|
9
|
+
"start": "voltro start .",
|
|
10
|
+
"test": "voltro test",
|
|
11
|
+
"typecheck": "tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@voltro/cli": "0.1.0",
|
|
15
|
+
"@voltro/client": "0.1.0",
|
|
16
|
+
"@voltro/env": "0.1.0",
|
|
17
|
+
"@voltro/i18n": "0.1.0",
|
|
18
|
+
"@voltro/ui-shadcn": "0.1.0",
|
|
19
|
+
"@voltro/web": "0.1.0",
|
|
20
|
+
"react": "^19.0.0",
|
|
21
|
+
"react-dom": "^19.0.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/react": "^19.0.0",
|
|
25
|
+
"@types/react-dom": "^19.0.0",
|
|
26
|
+
"typescript": "^5.7.0",
|
|
27
|
+
"vitest": "^3.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/* Self-contained styles — plain CSS, no Tailwind / kit dependency. */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--bg: #f4f4f5;
|
|
5
|
+
--card: #ffffff;
|
|
6
|
+
--fg: #18181b;
|
|
7
|
+
--muted: #71717a;
|
|
8
|
+
--accent: #0d9488;
|
|
9
|
+
--border: #e4e4e7;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
:root.dark {
|
|
13
|
+
--bg: #09090b;
|
|
14
|
+
--card: #18181b;
|
|
15
|
+
--fg: #fafafa;
|
|
16
|
+
--muted: #a1a1aa;
|
|
17
|
+
--accent: #2dd4bf;
|
|
18
|
+
--border: #27272a;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
* { box-sizing: border-box; }
|
|
22
|
+
html, body { margin: 0; min-height: 100vh; }
|
|
23
|
+
|
|
24
|
+
body {
|
|
25
|
+
background: var(--bg);
|
|
26
|
+
color: var(--fg);
|
|
27
|
+
font: 16px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
28
|
+
display: flex;
|
|
29
|
+
justify-content: center;
|
|
30
|
+
padding: 3rem 1rem;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.card {
|
|
34
|
+
background: var(--card);
|
|
35
|
+
border: 1px solid var(--border);
|
|
36
|
+
border-radius: 14px;
|
|
37
|
+
padding: 1.75rem;
|
|
38
|
+
width: 100%;
|
|
39
|
+
max-width: 26rem;
|
|
40
|
+
box-shadow: 0 1px 3px rgb(0 0 0 / 0.06);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
h1 { font-size: 1.4rem; margin: 0 0 0.25rem; }
|
|
44
|
+
.muted { color: var(--muted); font-size: 0.9rem; margin: 0 0 1.5rem; }
|
|
45
|
+
|
|
46
|
+
label { display: block; font-size: 0.85rem; color: var(--muted); margin: 1rem 0 0.35rem; }
|
|
47
|
+
|
|
48
|
+
input[type="number"], input[type="text"] {
|
|
49
|
+
width: 100%;
|
|
50
|
+
padding: 0.6rem 0.75rem;
|
|
51
|
+
font-size: 1rem;
|
|
52
|
+
color: var(--fg);
|
|
53
|
+
background: var(--bg);
|
|
54
|
+
border: 1px solid var(--border);
|
|
55
|
+
border-radius: 8px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.row { display: flex; gap: 0.75rem; }
|
|
59
|
+
.row > div { flex: 1; }
|
|
60
|
+
|
|
61
|
+
.presets { display: flex; gap: 0.5rem; margin-top: 0.5rem; flex-wrap: wrap; }
|
|
62
|
+
.presets button {
|
|
63
|
+
flex: 1;
|
|
64
|
+
padding: 0.5rem;
|
|
65
|
+
font-size: 0.9rem;
|
|
66
|
+
border: 1px solid var(--border);
|
|
67
|
+
background: var(--bg);
|
|
68
|
+
color: var(--fg);
|
|
69
|
+
border-radius: 8px;
|
|
70
|
+
cursor: pointer;
|
|
71
|
+
}
|
|
72
|
+
.presets button.active { border-color: var(--accent); color: var(--accent); font-weight: 600; }
|
|
73
|
+
|
|
74
|
+
.result {
|
|
75
|
+
margin-top: 1.75rem;
|
|
76
|
+
padding-top: 1.25rem;
|
|
77
|
+
border-top: 1px solid var(--border);
|
|
78
|
+
display: flex;
|
|
79
|
+
justify-content: space-between;
|
|
80
|
+
align-items: baseline;
|
|
81
|
+
}
|
|
82
|
+
.result .big { font-size: 2rem; font-weight: 700; color: var(--accent); }
|
|
83
|
+
.result .line { display: flex; justify-content: space-between; font-size: 0.95rem; }
|
|
84
|
+
.result .col { display: flex; flex-direction: column; gap: 0.4rem; flex: 1; }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// German catalog. Mirrors every key in `en.ts` — `defineLocale<typeof en>()`
|
|
2
|
+
// fails the build if a key is missing or misspelled. Translate the prose only;
|
|
3
|
+
// keep ICU placeholders and rich tags identical to `en.ts`.
|
|
4
|
+
|
|
5
|
+
import { defineLocale } from '@voltro/i18n'
|
|
6
|
+
import en from './en'
|
|
7
|
+
|
|
8
|
+
export default defineLocale<typeof en>()({
|
|
9
|
+
'meta.home.title': 'Rechnungsteiler',
|
|
10
|
+
|
|
11
|
+
'lang.label': 'Sprache',
|
|
12
|
+
|
|
13
|
+
'home.heading': 'Rechnungsteiler',
|
|
14
|
+
'home.subtitle': '100 % im Browser · dein letztes Trinkgeld wird lokal gemerkt',
|
|
15
|
+
'field.bill': 'Rechnungsbetrag',
|
|
16
|
+
'field.tip': 'Trinkgeld',
|
|
17
|
+
'field.custom': 'Eigener %',
|
|
18
|
+
'field.people': 'Personen',
|
|
19
|
+
'result.tip': 'Trinkgeld',
|
|
20
|
+
'result.total': 'Gesamt',
|
|
21
|
+
'result.perPerson': 'pro Person',
|
|
22
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Base catalog — the source of truth for this app's UI strings. Every key here
|
|
2
|
+
// MUST be mirrored in `de.ts` (and any other locale); `defineLocale<typeof en>()`
|
|
3
|
+
// enforces that parity at build time, so a missing translation fails typecheck
|
|
4
|
+
// rather than silently falling back.
|
|
5
|
+
//
|
|
6
|
+
// Messages use ICU syntax: `{name}` placeholders, `{n, plural, …}`, and rich
|
|
7
|
+
// tags like `<code>…</code>` (rendered by passing a matching function to
|
|
8
|
+
// `<T values={{ code: (c) => <code>{c}</code> }} />`).
|
|
9
|
+
|
|
10
|
+
import { defineCatalog } from '@voltro/i18n'
|
|
11
|
+
|
|
12
|
+
export default defineCatalog({
|
|
13
|
+
// Browser tab title — locale-aware via the page's `meta({ locale })`.
|
|
14
|
+
'meta.home.title': 'Bill splitter',
|
|
15
|
+
|
|
16
|
+
// ---- Layout shell (pages/layout.tsx) ----
|
|
17
|
+
'lang.label': 'Language',
|
|
18
|
+
|
|
19
|
+
// ---- Bill-splitter page (pages/index.tsx) ----
|
|
20
|
+
'home.heading': 'Bill splitter',
|
|
21
|
+
'home.subtitle': '100% client-side · your last tip % is remembered locally',
|
|
22
|
+
'field.bill': 'Bill amount',
|
|
23
|
+
'field.tip': 'Tip',
|
|
24
|
+
'field.custom': 'Custom %',
|
|
25
|
+
'field.people': 'People',
|
|
26
|
+
'result.tip': 'Tip',
|
|
27
|
+
'result.total': 'Total',
|
|
28
|
+
'result.perPerson': 'per person',
|
|
29
|
+
} as const)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Catalog lookup OUTSIDE React — for a page's `meta({ locale })` locale-aware
|
|
2
|
+
// <title>. This app is cookie-i18n (no `/<locale>` URL prefix), so @voltro/web
|
|
3
|
+
// hands `meta` the active locale from the `voltro:lang` cookie. Inside React,
|
|
4
|
+
// use `<T>` / `useT()` via the framework's auto-wired <I18nProvider> instead.
|
|
5
|
+
|
|
6
|
+
import en from './en'
|
|
7
|
+
import de from './de'
|
|
8
|
+
|
|
9
|
+
// Both catalogs share `en`'s keys (defineLocale enforces the mirror), so a
|
|
10
|
+
// known-key lookup returns `string` — not `string | undefined` — which is what
|
|
11
|
+
// `PageMeta.title` requires under exactOptionalPropertyTypes.
|
|
12
|
+
export type Messages = Record<keyof typeof en, string>
|
|
13
|
+
|
|
14
|
+
export const getCatalog = (locale?: string): Messages =>
|
|
15
|
+
(locale === 'de' ? de : en) as Messages
|