@stacksjs/defaults 0.70.206 → 0.70.208
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/TODO.md +21 -0
- package/ai/AGENTS.md +79 -0
- package/ai/README.md +45 -0
- package/ai/claude/launch.json +12 -0
- package/ai/skills/stacks-actions/SKILL.md +128 -0
- package/ai/skills/stacks-ai/SKILL.md +276 -0
- package/ai/skills/stacks-alias/SKILL.md +94 -0
- package/ai/skills/stacks-analytics/SKILL.md +91 -0
- package/ai/skills/stacks-api/SKILL.md +738 -0
- package/ai/skills/stacks-arrays/SKILL.md +277 -0
- package/ai/skills/stacks-auth/SKILL.md +422 -0
- package/ai/skills/stacks-auto-imports/SKILL.md +138 -0
- package/ai/skills/stacks-brandkit/SKILL.md +811 -0
- package/ai/skills/stacks-browse/SKILL.md +127 -0
- package/ai/skills/stacks-browse/scripts/browse.ts +492 -0
- package/ai/skills/stacks-browser/SKILL.md +145 -0
- package/ai/skills/stacks-buddy/SKILL.md +676 -0
- package/ai/skills/stacks-build/SKILL.md +163 -0
- package/ai/skills/stacks-cache/SKILL.md +125 -0
- package/ai/skills/stacks-calendar/SKILL.md +113 -0
- package/ai/skills/stacks-chat/SKILL.md +371 -0
- package/ai/skills/stacks-cli/SKILL.md +112 -0
- package/ai/skills/stacks-cloud/SKILL.md +192 -0
- package/ai/skills/stacks-cms/SKILL.md +217 -0
- package/ai/skills/stacks-collections/SKILL.md +82 -0
- package/ai/skills/stacks-commerce/SKILL.md +114 -0
- package/ai/skills/stacks-composables/SKILL.md +161 -0
- package/ai/skills/stacks-config/SKILL.md +144 -0
- package/ai/skills/stacks-configuration/SKILL.md +154 -0
- package/ai/skills/stacks-cron/SKILL.md +123 -0
- package/ai/skills/stacks-crosswind/SKILL.md +190 -0
- package/ai/skills/stacks-dashboard/SKILL.md +173 -0
- package/ai/skills/stacks-database/SKILL.md +286 -0
- package/ai/skills/stacks-datetime/SKILL.md +269 -0
- package/ai/skills/stacks-dependencies/SKILL.md +111 -0
- package/ai/skills/stacks-deploy/SKILL.md +102 -0
- package/ai/skills/stacks-design-brutalist/SKILL.md +110 -0
- package/ai/skills/stacks-design-minimalist/SKILL.md +115 -0
- package/ai/skills/stacks-design-output/SKILL.md +55 -0
- package/ai/skills/stacks-design-soft/SKILL.md +156 -0
- package/ai/skills/stacks-design-taste/SKILL.md +1012 -0
- package/ai/skills/stacks-design-taste-codex/SKILL.md +294 -0
- package/ai/skills/stacks-desktop/SKILL.md +78 -0
- package/ai/skills/stacks-development/SKILL.md +298 -0
- package/ai/skills/stacks-dns/SKILL.md +149 -0
- package/ai/skills/stacks-docs/SKILL.md +109 -0
- package/ai/skills/stacks-email/SKILL.md +236 -0
- package/ai/skills/stacks-enums/SKILL.md +100 -0
- package/ai/skills/stacks-env/SKILL.md +181 -0
- package/ai/skills/stacks-error-handling/SKILL.md +137 -0
- package/ai/skills/stacks-events/SKILL.md +228 -0
- package/ai/skills/stacks-faker/SKILL.md +158 -0
- package/ai/skills/stacks-git/SKILL.md +158 -0
- package/ai/skills/stacks-guard/SKILL.md +98 -0
- package/ai/skills/stacks-health/SKILL.md +75 -0
- package/ai/skills/stacks-http/SKILL.md +316 -0
- package/ai/skills/stacks-i18n/SKILL.md +198 -0
- package/ai/skills/stacks-image-to-code/SKILL.md +629 -0
- package/ai/skills/stacks-imagegen-mobile/SKILL.md +1474 -0
- package/ai/skills/stacks-imagegen-web/SKILL.md +998 -0
- package/ai/skills/stacks-investigate/SKILL.md +119 -0
- package/ai/skills/stacks-jobs/SKILL.md +136 -0
- package/ai/skills/stacks-lint/SKILL.md +184 -0
- package/ai/skills/stacks-listeners/SKILL.md +99 -0
- package/ai/skills/stacks-logging/SKILL.md +78 -0
- package/ai/skills/stacks-mail/SKILL.md +120 -0
- package/ai/skills/stacks-middleware/SKILL.md +249 -0
- package/ai/skills/stacks-migrations/SKILL.md +105 -0
- package/ai/skills/stacks-models/SKILL.md +267 -0
- package/ai/skills/stacks-new-feature/SKILL.md +206 -0
- package/ai/skills/stacks-notifications/SKILL.md +210 -0
- package/ai/skills/stacks-objects/SKILL.md +205 -0
- package/ai/skills/stacks-office-hours/SKILL.md +134 -0
- package/ai/skills/stacks-orm/SKILL.md +342 -0
- package/ai/skills/stacks-path/SKILL.md +118 -0
- package/ai/skills/stacks-payments/SKILL.md +373 -0
- package/ai/skills/stacks-plan-review/SKILL.md +152 -0
- package/ai/skills/stacks-plugins/SKILL.md +149 -0
- package/ai/skills/stacks-push/SKILL.md +376 -0
- package/ai/skills/stacks-query-builder/SKILL.md +128 -0
- package/ai/skills/stacks-queue/SKILL.md +559 -0
- package/ai/skills/stacks-realtime/SKILL.md +442 -0
- package/ai/skills/stacks-redesign/SKILL.md +182 -0
- package/ai/skills/stacks-registry/SKILL.md +58 -0
- package/ai/skills/stacks-repl/SKILL.md +80 -0
- package/ai/skills/stacks-retro/SKILL.md +122 -0
- package/ai/skills/stacks-review/SKILL.md +135 -0
- package/ai/skills/stacks-router/SKILL.md +231 -0
- package/ai/skills/stacks-routes/SKILL.md +105 -0
- package/ai/skills/stacks-scaffolding/SKILL.md +162 -0
- package/ai/skills/stacks-scheduler/SKILL.md +363 -0
- package/ai/skills/stacks-search-engine/SKILL.md +112 -0
- package/ai/skills/stacks-security/SKILL.md +129 -0
- package/ai/skills/stacks-security-audit/SKILL.md +128 -0
- package/ai/skills/stacks-server/SKILL.md +458 -0
- package/ai/skills/stacks-shell/SKILL.md +62 -0
- package/ai/skills/stacks-slug/SKILL.md +87 -0
- package/ai/skills/stacks-sms/SKILL.md +385 -0
- package/ai/skills/stacks-socials/SKILL.md +303 -0
- package/ai/skills/stacks-storage/SKILL.md +576 -0
- package/ai/skills/stacks-strings/SKILL.md +416 -0
- package/ai/skills/stacks-stx/SKILL.md +176 -0
- package/ai/skills/stacks-technical-diagrams/LICENSE +22 -0
- package/ai/skills/stacks-technical-diagrams/SKILL.md +290 -0
- package/ai/skills/stacks-technical-diagrams/agents/openai.yaml +4 -0
- package/ai/skills/stacks-technical-diagrams/assets/template.html +1252 -0
- package/ai/skills/stacks-technical-diagrams/bin/technical-diagrams.mjs +289 -0
- package/ai/skills/stacks-technical-diagrams/bunfig.toml +1 -0
- package/ai/skills/stacks-technical-diagrams/examples/agent-run.lifecycle.json +64 -0
- package/ai/skills/stacks-technical-diagrams/examples/agent-tool-call.workflow.json +88 -0
- package/ai/skills/stacks-technical-diagrams/examples/cache-miss-request.sequence.json +75 -0
- package/ai/skills/stacks-technical-diagrams/examples/product-analytics.dataflow.json +70 -0
- package/ai/skills/stacks-technical-diagrams/examples/web-app.architecture.json +41 -0
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/grid.mjs +62 -0
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/render-architecture.mjs +371 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/README.md +82 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/render-dataflow.mjs +295 -0
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/README.md +91 -0
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/render-lifecycle.mjs +384 -0
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/README.md +84 -0
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/render-sequence.mjs +257 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/cli.mjs +42 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/generated-validators.mjs +13 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/geometry.mjs +230 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/layout-report.mjs +40 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/utils.mjs +88 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/validator.mjs +40 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/README.md +103 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/render-workflow.mjs +481 -0
- package/ai/skills/stacks-technical-diagrams/schemas/README.md +68 -0
- package/ai/skills/stacks-technical-diagrams/schemas/architecture.schema.json +121 -0
- package/ai/skills/stacks-technical-diagrams/schemas/common.schema.json +43 -0
- package/ai/skills/stacks-technical-diagrams/schemas/dataflow.schema.json +221 -0
- package/ai/skills/stacks-technical-diagrams/schemas/lifecycle.schema.json +241 -0
- package/ai/skills/stacks-technical-diagrams/schemas/sequence.schema.json +186 -0
- package/ai/skills/stacks-technical-diagrams/schemas/workflow.schema.json +332 -0
- package/ai/skills/stacks-technical-diagrams/scripts/check-render-output.mjs +297 -0
- package/ai/skills/stacks-technical-diagrams/scripts/render-examples.mjs +28 -0
- package/ai/skills/stacks-testing/SKILL.md +129 -0
- package/ai/skills/stacks-tunnel/SKILL.md +125 -0
- package/ai/skills/stacks-types/SKILL.md +178 -0
- package/ai/skills/stacks-ui/SKILL.md +246 -0
- package/ai/skills/stacks-utils/SKILL.md +444 -0
- package/ai/skills/stacks-validation/SKILL.md +371 -0
- package/ai/skills/stacks-whois/SKILL.md +315 -0
- package/assets/world-map-data.ts +121 -0
- package/bootstrap.ts +114 -0
- package/docs/api-examples.md +46 -0
- package/docs/index.md +24 -0
- package/docs/markdown-examples.md +87 -0
- package/functions/analytics.ts +97 -0
- package/functions/api.ts +143 -0
- package/functions/auth.ts +208 -0
- package/functions/billing/payments.ts +149 -0
- package/functions/blog.ts +120 -0
- package/functions/cms/categorizables.ts +107 -0
- package/functions/cms/pages.ts +108 -0
- package/functions/cms/posts.ts +100 -0
- package/functions/cms/taggables.ts +102 -0
- package/functions/commerce/coupons.ts +128 -0
- package/functions/commerce/customers.ts +128 -0
- package/functions/commerce/gift-cards.ts +119 -0
- package/functions/commerce/orders.ts +112 -0
- package/functions/commerce/payments.ts +119 -0
- package/functions/commerce/products/categories.ts +123 -0
- package/functions/commerce/products/manufacturers.ts +123 -0
- package/functions/commerce/products/products.ts +123 -0
- package/functions/commerce/products/reviews.ts +123 -0
- package/functions/commerce/products/units.ts +123 -0
- package/functions/commerce/products/variants.ts +123 -0
- package/functions/commerce/shippings/delivery-routes.ts +123 -0
- package/functions/commerce/shippings/digital-deliveries.ts +119 -0
- package/functions/commerce/shippings/drivers.ts +123 -0
- package/functions/commerce/shippings/license-keys.ts +123 -0
- package/functions/commerce/shippings/shipping-methods.ts +123 -0
- package/functions/commerce/shippings/shipping-rates.ts +119 -0
- package/functions/commerce/shippings/shipping-zones.ts +119 -0
- package/functions/commerce/taxes.ts +119 -0
- package/functions/commerce/waitlist/products.ts +123 -0
- package/functions/commerce/waitlist/restaurants.ts +123 -0
- package/functions/craft.ts +107 -0
- package/functions/dashboard.ts +153 -0
- package/functions/inbox.ts +95 -0
- package/functions/jobs.ts +86 -0
- package/functions/monitoring/errors.ts +295 -0
- package/functions/queue.ts +111 -0
- package/functions/realtime/websockets.ts +96 -0
- package/functions/realtime.ts +52 -0
- package/functions/releases.ts +49 -0
- package/functions/search.ts +126 -0
- package/functions/toasts.ts +95 -0
- package/functions/uploads.ts +216 -0
- package/ide/.gitignore +4 -0
- package/ide/cursor/rules/auto-imports.mdc +574 -0
- package/ide/cursor/rules/code-style.mdc +12 -0
- package/ide/cursor/rules/documentation.mdc +9 -0
- package/ide/cursor/rules/error-handling-validation.mdc +11 -0
- package/ide/cursor/rules/key-conventions.mdc +11 -0
- package/ide/cursor/rules/project-structure.mdc +169 -0
- package/ide/cursor/rules/readme.mdc +318 -0
- package/ide/cursor/rules/syntax-formatting.mdc +9 -0
- package/ide/cursor/rules/testing.mdc +10 -0
- package/ide/cursor/rules/typescript.mdc +9 -0
- package/ide/cursor/rules/ui-styling.mdc +0 -0
- package/ide/dictionary.txt +397 -0
- package/ide/jetbrains/.fleet/settings.json +4 -0
- package/ide/jetbrains/.idea/.gitignore +5 -0
- package/ide/jetbrains/.idea/.name +1 -0
- package/ide/jetbrains/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/ide/jetbrains/.idea/jsLinters/eslint.xml +7 -0
- package/ide/jetbrains/.idea/modules.xml +8 -0
- package/ide/jetbrains/.idea/stacks.iml +22 -0
- package/ide/jetbrains/.idea/vcs.xml +6 -0
- package/ide/jetbrains/.idea/webResources.xml +14 -0
- package/ide/jetbrains/IntelliJ IDEA Global Settings +0 -0
- package/ide/jetbrains/installed.txt +4 -0
- package/ide/jetbrains/options/github-copilot.xml +5 -0
- package/ide/jetbrains/options/nodejs.xml +7 -0
- package/ide/jetbrains/options/templates.xml +124 -0
- package/ide/jetbrains/templates/JavaScript.xml +6 -0
- package/ide/jetbrains/templates/Stacks.xml +8 -0
- package/ide/vscode/.vscode/README.md +23 -0
- package/ide/vscode/.vscode/extensions.json +5 -0
- package/ide/vscode/.vscode/settings.json +176 -0
- package/ide/vscode/.vscode/stacks.code-snippets +181 -0
- package/ide/vscode/LICENSE.md +21 -0
- package/ide/vscode/README.md +36 -0
- package/ide/vscode/logo.png +0 -0
- package/ide/vscode/package.json +79 -0
- package/ide/zed/.zed/settings.json +139 -0
- package/lang/README.md +28 -0
- package/lang/de.yml +14 -0
- package/lang/en.yml +15 -0
- package/package.json +19 -3
- package/project/bootstrap +161 -0
- package/project/buddy +175 -0
- package/project/storage/framework/tsconfig.app.json +50 -0
- package/project/storage/framework/tsconfig.base.json +58 -0
- package/routes/core.ts +78 -0
- package/routes/dashboard-api.ts +233 -0
- package/routes/dashboard.ts +739 -0
- package/routes/email.ts +129 -0
- package/stores/[name].stx +16 -0
- package/stores/git.ts +71 -0
- package/stores/payment.ts +474 -0
- package/stores/queue.ts +43 -0
- package/stores/user.ts +29 -0
- package/stx-components-plugin.ts +25 -0
- package/styles/design-tokens.css +597 -0
- package/types/dashboard.ts +70 -0
- package/types/defaults.ts +438 -0
- package/vcs/github/CODE_OF_CONDUCT.md +83 -0
- package/vcs/github/CONTRIBUTING.md +187 -0
- package/vcs/github/EXPLANATIONS.md +41 -0
- package/vcs/github/FUNDING.yml +1 -0
- package/vcs/github/ISSUE_TEMPLATE/bug_report.yml +49 -0
- package/vcs/github/ISSUE_TEMPLATE/config.yml +10 -0
- package/vcs/github/ISSUE_TEMPLATE/feature_request.yml +44 -0
- package/vcs/github/SECURITY.md +70 -0
- package/vcs/github/renovate.json +5 -0
- package/vcs/github/stale.yml +14 -0
- package/vcs/github/workflows/README.md +16 -0
- package/vcs/github/workflows/ci.yml +114 -0
- package/vcs/github/workflows/export-size.yml +25 -0
- package/vcs/github/workflows/labeler.yml +13 -0
- package/vcs/github/workflows/release.yml +219 -0
- package/views/[...all].stx +8 -0
- package/views/about.md +3 -0
- package/views/auth/login.stx +1 -0
- package/views/auth/signup.stx +85 -0
- package/views/dashboard/.discovered-models.json +542 -0
- package/views/dashboard/AUDIT.md +904 -0
- package/views/dashboard/[...all].stx +89 -0
- package/views/dashboard/access-tokens/index.stx +177 -0
- package/views/dashboard/actions/index.stx +129 -0
- package/views/dashboard/analytics/blog/index.stx +356 -0
- package/views/dashboard/analytics/browsers/index.stx +263 -0
- package/views/dashboard/analytics/commerce/sales/index.stx +329 -0
- package/views/dashboard/analytics/commerce/web/index.stx +379 -0
- package/views/dashboard/analytics/countries/index.stx +289 -0
- package/views/dashboard/analytics/devices/index.stx +305 -0
- package/views/dashboard/analytics/events/index.stx +536 -0
- package/views/dashboard/analytics/index.stx +88 -0
- package/views/dashboard/analytics/marketing/index.stx +315 -0
- package/views/dashboard/analytics/pages/index.stx +271 -0
- package/views/dashboard/analytics/referrers/index.stx +319 -0
- package/views/dashboard/analytics/web/index.stx +356 -0
- package/views/dashboard/buddy/index.stx +198 -0
- package/views/dashboard/ci/index.stx +462 -0
- package/views/dashboard/cloud/index.stx +452 -0
- package/views/dashboard/commands/index.stx +295 -0
- package/views/dashboard/commerce/categories/index.stx +406 -0
- package/views/dashboard/commerce/coupons/index.stx +485 -0
- package/views/dashboard/commerce/customers/index.stx +410 -0
- package/views/dashboard/commerce/dashboard/index.stx +287 -0
- package/views/dashboard/commerce/delivery/delivery-routes.stx +305 -0
- package/views/dashboard/commerce/delivery/digital-delivery.stx +332 -0
- package/views/dashboard/commerce/delivery/drivers.stx +397 -0
- package/views/dashboard/commerce/delivery/index.stx +18 -0
- package/views/dashboard/commerce/delivery/license-keys.stx +380 -0
- package/views/dashboard/commerce/delivery/shipping-methods.stx +346 -0
- package/views/dashboard/commerce/delivery/shipping-rates.stx +338 -0
- package/views/dashboard/commerce/delivery/shipping-zones.stx +302 -0
- package/views/dashboard/commerce/delivery/tabs.ts +25 -0
- package/views/dashboard/commerce/gift-cards/index.stx +561 -0
- package/views/dashboard/commerce/manufacturers/index.stx +308 -0
- package/views/dashboard/commerce/orders/index.stx +247 -0
- package/views/dashboard/commerce/payments/index.stx +535 -0
- package/views/dashboard/commerce/pos/index.stx +653 -0
- package/views/dashboard/commerce/printers/devices/index.stx +273 -0
- package/views/dashboard/commerce/printers/receipts/index.stx +308 -0
- package/views/dashboard/commerce/products/detail.stx +563 -0
- package/views/dashboard/commerce/products/index.stx +370 -0
- package/views/dashboard/commerce/reviews/index.stx +656 -0
- package/views/dashboard/commerce/reviews/index.ts +0 -0
- package/views/dashboard/commerce/taxes/index.stx +377 -0
- package/views/dashboard/commerce/units/index.stx +317 -0
- package/views/dashboard/commerce/variants/index.stx +319 -0
- package/views/dashboard/commerce/waitlist/products.stx +942 -0
- package/views/dashboard/commerce/waitlist/restaurant.stx +1024 -0
- package/views/dashboard/components/index.stx +330 -0
- package/views/dashboard/composables/index.ts +19 -0
- package/views/dashboard/composables/useChart.ts +77 -0
- package/views/dashboard/composables/useNavigation.ts +99 -0
- package/views/dashboard/composables/useRole.ts +58 -0
- package/views/dashboard/composables/useTheme.ts +74 -0
- package/views/dashboard/content/authors/index.stx +171 -0
- package/views/dashboard/content/blog/index.stx +493 -0
- package/views/dashboard/content/categories/index.stx +169 -0
- package/views/dashboard/content/comments/index.stx +189 -0
- package/views/dashboard/content/dashboard.stx +383 -0
- package/views/dashboard/content/files/index.stx +706 -0
- package/views/dashboard/content/pages/index.stx +174 -0
- package/views/dashboard/content/posts/index.stx +269 -0
- package/views/dashboard/content/seo/index.stx +240 -0
- package/views/dashboard/content/tags/index.stx +167 -0
- package/views/dashboard/data/activity/index.stx +261 -0
- package/views/dashboard/data/dashboard/index.stx +116 -0
- package/views/dashboard/data/subscribers.stx +270 -0
- package/views/dashboard/data/teams.stx +275 -0
- package/views/dashboard/data/users.stx +279 -0
- package/views/dashboard/dependencies/index.stx +178 -0
- package/views/dashboard/deployments/[id].stx +234 -0
- package/views/dashboard/deployments/index.stx +9 -0
- package/views/dashboard/dns/index.stx +163 -0
- package/views/dashboard/environment/index.stx +283 -0
- package/views/dashboard/errors/index.stx +274 -0
- package/views/dashboard/forgot-password.stx +57 -0
- package/views/dashboard/functions/index.stx +159 -0
- package/views/dashboard/health/index.stx +248 -0
- package/views/dashboard/inbox/activity.stx +350 -0
- package/views/dashboard/inbox/index.stx +452 -0
- package/views/dashboard/inbox/settings.stx +290 -0
- package/views/dashboard/index.stx +252 -0
- package/views/dashboard/insights/index.stx +529 -0
- package/views/dashboard/jobs/[id].stx +430 -0
- package/views/dashboard/jobs/history.stx +222 -0
- package/views/dashboard/jobs/index.stx +214 -0
- package/views/dashboard/kanban/[id].stx +899 -0
- package/views/dashboard/kanban/index.stx +260 -0
- package/views/dashboard/layouts/default.stx +565 -0
- package/views/dashboard/layouts/guest.stx +10 -0
- package/views/dashboard/login.stx +48 -0
- package/views/dashboard/logs/index.stx +540 -0
- package/views/dashboard/mailboxes/index.stx +149 -0
- package/views/dashboard/management/permissions/index.stx +490 -0
- package/views/dashboard/marketing/campaigns/index.stx +604 -0
- package/views/dashboard/marketing/lists/index.stx +514 -0
- package/views/dashboard/marketing/reviews/index.stx +475 -0
- package/views/dashboard/marketing/social-posts/index.stx +560 -0
- package/views/dashboard/models/[model].stx +195 -0
- package/views/dashboard/models/index.stx +159 -0
- package/views/dashboard/monitoring/errors/index.stx +415 -0
- package/views/dashboard/notifications/dashboard.stx +383 -0
- package/views/dashboard/notifications/email.stx +336 -0
- package/views/dashboard/notifications/history.stx +373 -0
- package/views/dashboard/notifications/sms.stx +332 -0
- package/views/dashboard/packages/index.stx +195 -0
- package/views/dashboard/queries/[id].stx +381 -0
- package/views/dashboard/queries/history.stx +227 -0
- package/views/dashboard/queries/index.stx +335 -0
- package/views/dashboard/queries/slow.stx +318 -0
- package/views/dashboard/queue/index.stx +427 -0
- package/views/dashboard/realtime/index.stx +429 -0
- package/views/dashboard/register.stx +46 -0
- package/views/dashboard/releases/index.stx +389 -0
- package/views/dashboard/requests/index.stx +462 -0
- package/views/dashboard/serverless/index.stx +475 -0
- package/views/dashboard/servers/[id].stx +299 -0
- package/views/dashboard/servers/index.stx +483 -0
- package/views/dashboard/settings/billing.stx +50 -0
- package/views/dashboard/settings/index.stx +256 -0
- package/views/dashboard/settings/mail.stx +252 -0
- package/views/dashboard/stores/auth.ts +123 -0
- package/views/dashboard/stores/ci.ts +342 -0
- package/views/dashboard/stores/index.ts +129 -0
- package/views/dashboard/stores/kanban.ts +723 -0
- package/views/dashboard/stores/rbac.ts +350 -0
- package/views/dashboard/stx.config.ts +34 -0
- package/views/dashboard/teams/[id].stx +170 -0
- package/views/forum/category.stx +92 -0
- package/views/forum/index.stx +209 -0
- package/views/forum/new-topic.stx +113 -0
- package/views/forum/profile.stx +1 -0
- package/views/forum/topic.stx +143 -0
- package/views/hello-world/[name].stx +47 -0
- package/views/index.stx +21 -0
- package/views/system-tray/index.stx +147 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import type { Ref } from '@stacksjs/stx'
|
|
2
|
+
import type { AuthUser, ErrorResponse, LoginError, LoginResponse, MeResponse, RegisterError, RegisterResponse, UserData } from '../types/dashboard'
|
|
3
|
+
import { useStorage } from '@stacksjs/browser'
|
|
4
|
+
import { ref } from '@stacksjs/stx'
|
|
5
|
+
|
|
6
|
+
const token = useStorage('token', '')
|
|
7
|
+
const user = useStorage<UserData | null>('user', null, undefined, {
|
|
8
|
+
|
|
9
|
+
serializer: {
|
|
10
|
+
read: (v: string): UserData | null => {
|
|
11
|
+
try {
|
|
12
|
+
return v ? (JSON.parse(v) as UserData | null) : null
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
write: (v: UserData | null): string => JSON.stringify(v),
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const baseUrl = process.env.VITE_API_URL || `http://localhost:${process.env.PORT_API || '3008'}`
|
|
23
|
+
|
|
24
|
+
// Create singleton state
|
|
25
|
+
const isAuthenticated = ref(false)
|
|
26
|
+
|
|
27
|
+
export interface AuthComposable {
|
|
28
|
+
isAuthenticated: Ref<boolean>
|
|
29
|
+
user: { value: UserData | null }
|
|
30
|
+
login: (user: AuthUser) => Promise<LoginResponse | LoginError>
|
|
31
|
+
register: (user: AuthUser) => Promise<RegisterResponse | RegisterError>
|
|
32
|
+
fetchAuthUser: () => Promise<UserData | null>
|
|
33
|
+
checkAuthentication: () => Promise<boolean>
|
|
34
|
+
logout: () => void
|
|
35
|
+
getToken: () => string | null
|
|
36
|
+
token: { value: string | null }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function useAuth(): AuthComposable {
|
|
40
|
+
async function fetchAuthUser(): Promise<UserData | null> {
|
|
41
|
+
try {
|
|
42
|
+
if (!token.value) {
|
|
43
|
+
isAuthenticated.value = false
|
|
44
|
+
user.value = null
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const response = await fetch(`${baseUrl}/me`, {
|
|
49
|
+
headers: {
|
|
50
|
+
Authorization: `Bearer ${token.value}`,
|
|
51
|
+
Accept: 'application/json',
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
isAuthenticated.value = false
|
|
57
|
+
user.value = null
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const data = await response.json() as MeResponse
|
|
62
|
+
|
|
63
|
+
user.value = data
|
|
64
|
+
isAuthenticated.value = true
|
|
65
|
+
|
|
66
|
+
return data
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error('Error fetching user:', error)
|
|
70
|
+
isAuthenticated.value = false
|
|
71
|
+
user.value = null
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function checkAuthentication(): Promise<boolean> {
|
|
77
|
+
try {
|
|
78
|
+
const userData = await fetchAuthUser()
|
|
79
|
+
return userData !== null
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error('Error checking authentication:', error)
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function register(user: AuthUser): Promise<RegisterResponse | RegisterError> {
|
|
88
|
+
const url = `${baseUrl}/register`
|
|
89
|
+
const response = await fetch(url, {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
headers: {
|
|
92
|
+
'Content-Type': 'application/json',
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify(user),
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
const errorData = await response.json() as RegisterError
|
|
99
|
+
return errorData
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const data = await response.json() as RegisterResponse | RegisterError
|
|
103
|
+
|
|
104
|
+
if (isRegisterError(data)) {
|
|
105
|
+
return data
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (isRegisterResponse(data)) {
|
|
109
|
+
token.value = data.token
|
|
110
|
+
return data
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return data
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function isRegisterError(data: RegisterResponse | RegisterError): data is RegisterError {
|
|
117
|
+
return 'errors' in data
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function isRegisterResponse(data: RegisterResponse | RegisterError): data is RegisterResponse {
|
|
121
|
+
return 'token' in data && 'user' in data
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function login(user: AuthUser): Promise<LoginResponse | LoginError> {
|
|
125
|
+
try {
|
|
126
|
+
const url = `${baseUrl}/login`
|
|
127
|
+
const response = await fetch(url, {
|
|
128
|
+
method: 'POST',
|
|
129
|
+
headers: {
|
|
130
|
+
'Content-Type': 'application/json',
|
|
131
|
+
},
|
|
132
|
+
body: JSON.stringify(user),
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
if (!response.ok) {
|
|
136
|
+
const errorData = await response.json() as ErrorResponse
|
|
137
|
+
throw new Error(errorData.message || `HTTP error! status: ${response.status}`)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const data = await response.json() as LoginResponse
|
|
141
|
+
|
|
142
|
+
token.value = data.token
|
|
143
|
+
|
|
144
|
+
return data
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
return error as LoginError
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function logout() {
|
|
152
|
+
try {
|
|
153
|
+
if (token.value) {
|
|
154
|
+
await fetch(`${baseUrl}/logout`, {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
headers: {
|
|
157
|
+
Authorization: `Bearer ${token.value}`,
|
|
158
|
+
Accept: 'application/json',
|
|
159
|
+
},
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
console.error('Error during logout:', error)
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
token.value = ''
|
|
168
|
+
user.value = null
|
|
169
|
+
isAuthenticated.value = false
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
user,
|
|
175
|
+
isAuthenticated,
|
|
176
|
+
token,
|
|
177
|
+
getToken: () => token.value,
|
|
178
|
+
register,
|
|
179
|
+
login,
|
|
180
|
+
logout,
|
|
181
|
+
fetchAuthUser,
|
|
182
|
+
checkAuthentication,
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Strict auth guard middleware
|
|
187
|
+
// Usage: call in setup() of page/component, or in router beforeEach
|
|
188
|
+
// Pass { guest: true } for guest-only pages
|
|
189
|
+
export function authGuard(options: { guest?: boolean } = {}): void {
|
|
190
|
+
const guest = options.guest ?? false
|
|
191
|
+
const { isAuthenticated } = useAuth()
|
|
192
|
+
|
|
193
|
+
// Guard against SSR — window is only available in the browser
|
|
194
|
+
if (typeof window === 'undefined') return
|
|
195
|
+
|
|
196
|
+
if (guest) {
|
|
197
|
+
// Guest-only page: block if authenticated
|
|
198
|
+
if (isAuthenticated.value) {
|
|
199
|
+
window.location.replace('/')
|
|
200
|
+
}
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Auth-only page: block if not authenticated
|
|
205
|
+
if (!isAuthenticated.value) {
|
|
206
|
+
window.location.replace('/login')
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { confirmCardSetup, confirmPayment, loadCardElement, loadPaymentElement } from '@stacksjs/browser'
|
|
2
|
+
|
|
3
|
+
export function useBillable() {
|
|
4
|
+
// Resolved per call, not at module scope: `usePaymentStore` is a browser
|
|
5
|
+
// auto-import, so calling it while the module is being evaluated throws
|
|
6
|
+
// "usePaymentStore is not defined" the moment anything imports this file
|
|
7
|
+
// server-side, which the generated auto-import manifest does.
|
|
8
|
+
const paymentStore = usePaymentStore()
|
|
9
|
+
|
|
10
|
+
function convertUnixTimestampToDate(timestamp: number): string {
|
|
11
|
+
// Create a Date object from the Unix timestamp
|
|
12
|
+
const date = new Date(timestamp * 1000) // Multiply by 1000 to convert to milliseconds
|
|
13
|
+
|
|
14
|
+
// Define arrays for month names
|
|
15
|
+
const monthNames = [
|
|
16
|
+
'January',
|
|
17
|
+
'February',
|
|
18
|
+
'March',
|
|
19
|
+
'April',
|
|
20
|
+
'May',
|
|
21
|
+
'June',
|
|
22
|
+
'July',
|
|
23
|
+
'August',
|
|
24
|
+
'September',
|
|
25
|
+
'October',
|
|
26
|
+
'November',
|
|
27
|
+
'December',
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
// Extract day, month, and year
|
|
31
|
+
const day = date.getDate()
|
|
32
|
+
const month = monthNames[date.getMonth()]
|
|
33
|
+
const year = date.getFullYear()
|
|
34
|
+
|
|
35
|
+
// Return the formatted string in "Month Day, Year" format
|
|
36
|
+
return `${month} ${day}, ${year}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function formatTimestampDate(timestamp: string): string {
|
|
40
|
+
const date = new Date(timestamp)
|
|
41
|
+
|
|
42
|
+
const monthNames = [
|
|
43
|
+
'January',
|
|
44
|
+
'February',
|
|
45
|
+
'March',
|
|
46
|
+
'April',
|
|
47
|
+
'May',
|
|
48
|
+
'June',
|
|
49
|
+
'July',
|
|
50
|
+
'August',
|
|
51
|
+
'September',
|
|
52
|
+
'October',
|
|
53
|
+
'November',
|
|
54
|
+
'December',
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
// Extract day, month, and year
|
|
58
|
+
const day = date.getDate()
|
|
59
|
+
const month = monthNames[date.getMonth()]
|
|
60
|
+
const year = date.getFullYear()
|
|
61
|
+
|
|
62
|
+
// Return the formatted string in "Month Day, Year" format
|
|
63
|
+
return `${month} ${day}, ${year}`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function loadCardForm(clientSecret: string): Promise<boolean> {
|
|
67
|
+
const isCreated = await loadCardElement(clientSecret)
|
|
68
|
+
|
|
69
|
+
return isCreated
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function loadPaymentForm(clientSecret: string): Promise<boolean> {
|
|
73
|
+
const isCreated = await loadPaymentElement(clientSecret)
|
|
74
|
+
|
|
75
|
+
return isCreated
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function handleAddPaymentMethod(clientSecret: string, elements: any) {
|
|
79
|
+
try {
|
|
80
|
+
const { error, setupIntent } = await confirmCardSetup(clientSecret, elements)
|
|
81
|
+
|
|
82
|
+
if (error) {
|
|
83
|
+
console.error(error.message)
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
await paymentStore.storePaymentMethod(setupIntent.payment_method)
|
|
87
|
+
|
|
88
|
+
if (!paymentStore.hasPaymentMethods) {
|
|
89
|
+
await paymentStore.setUserDefaultPaymentMethod(setupIntent.payment_method)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
console.error('Error processing payment:', err)
|
|
95
|
+
// Handle any unexpected errors
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function handlePayment(elements: any) {
|
|
100
|
+
try {
|
|
101
|
+
await confirmPayment(elements)
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
console.error('Error processing payment:', err)
|
|
105
|
+
// Handle any unexpected errors
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function isEmpty(object: any) {
|
|
110
|
+
return !object // Checks for null or undefined
|
|
111
|
+
|| (typeof object === 'object'
|
|
112
|
+
&& Object.keys(object).length === 0)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const updatingPlanState = computed(() => {
|
|
116
|
+
return !isEmpty(paymentStore.getCurrentPlan) && paymentStore.getPlanState
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const showCurrentPlan = computed(() => {
|
|
120
|
+
return !isEmpty(paymentStore.getCurrentPlan) && !paymentStore.getPlanState
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const showPlans = computed(() => {
|
|
124
|
+
return isEmpty(paymentStore.getCurrentPlan)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
function editPlan() {
|
|
128
|
+
paymentStore.openPlans()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function cancelEditPlan() {
|
|
132
|
+
paymentStore.closePlans()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
loadCardForm,
|
|
137
|
+
loadPaymentForm,
|
|
138
|
+
handleAddPaymentMethod,
|
|
139
|
+
handlePayment,
|
|
140
|
+
isEmpty,
|
|
141
|
+
convertUnixTimestampToDate,
|
|
142
|
+
formatTimestampDate,
|
|
143
|
+
editPlan,
|
|
144
|
+
updatingPlanState,
|
|
145
|
+
showCurrentPlan,
|
|
146
|
+
cancelEditPlan,
|
|
147
|
+
showPlans,
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown blog admin client.
|
|
3
|
+
*
|
|
4
|
+
* Talks to `/api/dashboard/blog`, which reads and writes `content/blog/*.md` —
|
|
5
|
+
* the same files BunPress renders at `/blog`. Saving here changes a file in the
|
|
6
|
+
* repo, so the dashboard is an editor for git-tracked content, not a database UI.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately dependency-free: no `@stacksjs/stx`, no `@stacksjs/browser`, no
|
|
9
|
+
* signals. The stx client bundler marks those packages external and strips the
|
|
10
|
+
* import when it inlines this module into a page's setup function, which leaves
|
|
11
|
+
* the identifiers undefined at runtime. Reactive state therefore lives in the
|
|
12
|
+
* PAGE's `<script client>` block, where the runtime injects `state`/`derived`,
|
|
13
|
+
* and this module stays a plain async transport that returns data or throws.
|
|
14
|
+
*
|
|
15
|
+
* Server side: storage/framework/core/actions/src/blog-admin.ts
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export interface BlogPost {
|
|
19
|
+
slug: string
|
|
20
|
+
title: string
|
|
21
|
+
description: string
|
|
22
|
+
date: string
|
|
23
|
+
author: string
|
|
24
|
+
authorBio: string
|
|
25
|
+
poster: string
|
|
26
|
+
featured: boolean
|
|
27
|
+
draft: boolean
|
|
28
|
+
body: string
|
|
29
|
+
wordCount: number
|
|
30
|
+
readingTime: number
|
|
31
|
+
updatedAt: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type BlogPostDraft = Omit<BlogPost, 'wordCount' | 'readingTime' | 'updatedAt'>
|
|
35
|
+
|
|
36
|
+
const api = '/api/dashboard/blog'
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Headers for an unsafe (write) request.
|
|
40
|
+
*
|
|
41
|
+
* CSRF protection is default-on for POST/PATCH/DELETE and uses a stateless
|
|
42
|
+
* double-submit cookie: the server seeds a non-HttpOnly `X-CSRF-Token` cookie on
|
|
43
|
+
* safe responses, and the client has to echo it back in the matching header. The
|
|
44
|
+
* list request on page load is what seeds the cookie, so by the time an author
|
|
45
|
+
* can save, the token is there. See defaults/app/Middleware/Csrf.ts.
|
|
46
|
+
*/
|
|
47
|
+
function writeHeaders(): Record<string, string> {
|
|
48
|
+
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
|
|
49
|
+
const match = document.cookie.match(/(?:^|;\s*)X-CSRF-Token=([^;]*)/)
|
|
50
|
+
|
|
51
|
+
if (match)
|
|
52
|
+
headers['X-CSRF-Token'] = decodeURIComponent(match[1])
|
|
53
|
+
|
|
54
|
+
return headers
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Throws with the API's own message when there is one.
|
|
59
|
+
*
|
|
60
|
+
* The blog endpoints answer a rejected write with `{ message }` (slug taken,
|
|
61
|
+
* bad date, ...). Surfacing that beats a bare status code, since it is the text
|
|
62
|
+
* the author needs in order to fix the post.
|
|
63
|
+
*/
|
|
64
|
+
async function unwrap<T>(response: Response): Promise<T> {
|
|
65
|
+
if (response.ok)
|
|
66
|
+
return await response.json() as T
|
|
67
|
+
|
|
68
|
+
let message = `Request failed with status ${response.status}`
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const body = await response.json() as { message?: string }
|
|
72
|
+
if (body?.message)
|
|
73
|
+
message = body.message
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Non-JSON error body; the status-code message stands.
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
throw new Error(message)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface BlogListResponse {
|
|
83
|
+
posts: BlogPost[]
|
|
84
|
+
publishedCount: number
|
|
85
|
+
draftCount: number
|
|
86
|
+
featuredCount: number
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Every post on disk, drafts included. Bodies are omitted from the list. */
|
|
90
|
+
export async function fetchBlogPosts(): Promise<BlogListResponse> {
|
|
91
|
+
const body = await unwrap<BlogListResponse>(await fetch(api))
|
|
92
|
+
|
|
93
|
+
if (!Array.isArray(body?.posts))
|
|
94
|
+
throw new TypeError('The blog API returned an unexpected shape.')
|
|
95
|
+
|
|
96
|
+
return body
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** One post including its markdown body, which the list omits. */
|
|
100
|
+
export async function fetchBlogPost(slug: string): Promise<BlogPost> {
|
|
101
|
+
return await unwrap<BlogPost>(await fetch(`${api}/${encodeURIComponent(slug)}`))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Creates a post, or updates the one at `originalSlug`. */
|
|
105
|
+
export async function saveBlogPost(post: BlogPostDraft, originalSlug = ''): Promise<BlogPost> {
|
|
106
|
+
const isUpdate = !!originalSlug
|
|
107
|
+
|
|
108
|
+
return await unwrap<BlogPost>(await fetch(isUpdate ? `${api}/${encodeURIComponent(originalSlug)}` : api, {
|
|
109
|
+
method: isUpdate ? 'PATCH' : 'POST',
|
|
110
|
+
headers: writeHeaders(),
|
|
111
|
+
body: JSON.stringify(post),
|
|
112
|
+
}))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function deleteBlogPost(slug: string): Promise<void> {
|
|
116
|
+
await unwrap<{ message: string }>(await fetch(`${api}/${encodeURIComponent(slug)}`, {
|
|
117
|
+
method: 'DELETE',
|
|
118
|
+
headers: writeHeaders(),
|
|
119
|
+
}))
|
|
120
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { Categorizables } from '../../types/defaults'
|
|
2
|
+
import { useFetch, useStorage } from '@stacksjs/browser'
|
|
3
|
+
|
|
4
|
+
// Create a persistent categories array using STX useStorage
|
|
5
|
+
const categorizables = useStorage<Categorizables[]>('categorizables', [])
|
|
6
|
+
|
|
7
|
+
const baseURL = process.env.VITE_API_URL || `http://localhost:${process.env.PORT_API || '3008'}`
|
|
8
|
+
|
|
9
|
+
// Basic fetch function to get all categories
|
|
10
|
+
async function fetchCategorizables(): Promise<Categorizables[]> {
|
|
11
|
+
const { error, data } = await useFetch(`${baseURL}/cms/categories`).get().json()
|
|
12
|
+
|
|
13
|
+
const categorizableJson = data.value as Categorizables[]
|
|
14
|
+
if (error.value) {
|
|
15
|
+
console.error('Error fetching categorizables:', error.value)
|
|
16
|
+
return []
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Ensure data is an array before assigning
|
|
20
|
+
if (Array.isArray(categorizableJson)) {
|
|
21
|
+
categorizables.value = categorizableJson
|
|
22
|
+
return categorizableJson
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
console.error('Expected array of categorizables but received:', typeof categorizableJson)
|
|
26
|
+
return []
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function createCategorizable(categorizable: Partial<Categorizables>) {
|
|
31
|
+
const { error, data } = await useFetch(`${baseURL}/cms/categories`)
|
|
32
|
+
.post(JSON.stringify({
|
|
33
|
+
...categorizable,
|
|
34
|
+
name: categorizable.name,
|
|
35
|
+
description: categorizable.description,
|
|
36
|
+
is_active: categorizable.is_active,
|
|
37
|
+
categorizable_type: categorizable.categorizable_type,
|
|
38
|
+
}))
|
|
39
|
+
.json()
|
|
40
|
+
|
|
41
|
+
if (error.value) {
|
|
42
|
+
console.error('Error creating categorizable:', error.value)
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const newCategorizable = data.value as Categorizables
|
|
47
|
+
if (newCategorizable) {
|
|
48
|
+
categorizables.value.push(newCategorizable)
|
|
49
|
+
return newCategorizable
|
|
50
|
+
}
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function updateCategorizable(id: number, categorizable: Partial<Categorizables>) {
|
|
55
|
+
const { error, data } = await useFetch(`${baseURL}/cms/categories/${id}`)
|
|
56
|
+
.patch(JSON.stringify({
|
|
57
|
+
...categorizable,
|
|
58
|
+
name: categorizable.name,
|
|
59
|
+
description: categorizable.description,
|
|
60
|
+
is_active: categorizable.is_active,
|
|
61
|
+
}))
|
|
62
|
+
.json()
|
|
63
|
+
|
|
64
|
+
if (error.value) {
|
|
65
|
+
console.error('Error updating categorizable:', error.value)
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const updatedCategorizable = data.value as Categorizables
|
|
70
|
+
if (updatedCategorizable) {
|
|
71
|
+
const index = categorizables.value.findIndex(c => c.id === id)
|
|
72
|
+
if (index !== -1) {
|
|
73
|
+
categorizables.value[index] = updatedCategorizable
|
|
74
|
+
}
|
|
75
|
+
return updatedCategorizable
|
|
76
|
+
}
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function deleteCategorizable(id: number) {
|
|
81
|
+
const { error } = await useFetch(`${baseURL}/cms/categories/${id}`)
|
|
82
|
+
.delete()
|
|
83
|
+
.json()
|
|
84
|
+
|
|
85
|
+
if (error.value) {
|
|
86
|
+
console.error('Error deleting categorizable:', error.value)
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const index = categorizables.value.findIndex(c => c.id === id)
|
|
91
|
+
if (index !== -1) {
|
|
92
|
+
categorizables.value.splice(index, 1)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return true
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Export the composable
|
|
99
|
+
export function useCategorizables() {
|
|
100
|
+
return {
|
|
101
|
+
categorizables,
|
|
102
|
+
fetchCategorizables,
|
|
103
|
+
createCategorizable,
|
|
104
|
+
updateCategorizable,
|
|
105
|
+
deleteCategorizable,
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { Pages } from '../../types/defaults'
|
|
2
|
+
import { useFetch, useStorage } from '@stacksjs/browser'
|
|
3
|
+
|
|
4
|
+
// Create a persistent pages array using STX useStorage
|
|
5
|
+
const pages = useStorage<Pages[]>('pages', [])
|
|
6
|
+
|
|
7
|
+
const baseURL = process.env.VITE_API_URL || `http://localhost:${process.env.PORT_API || '3008'}`
|
|
8
|
+
|
|
9
|
+
// Basic fetch function to get all pages
|
|
10
|
+
async function fetchPages() {
|
|
11
|
+
const { error, data } = await useFetch(`${baseURL}/cms/pages`).get().json()
|
|
12
|
+
|
|
13
|
+
const pagesJson = data.value as Pages[]
|
|
14
|
+
if (error.value) {
|
|
15
|
+
console.error('Error fetching pages:', error.value)
|
|
16
|
+
return []
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Ensure data is an array before assigning
|
|
20
|
+
if (Array.isArray(data.value)) {
|
|
21
|
+
pages.value = pagesJson
|
|
22
|
+
return data.value
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
console.error('Expected array of pages but received:', typeof data.value)
|
|
26
|
+
return []
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function createPage(page: Partial<Pages>) {
|
|
31
|
+
const { error, data } = await useFetch(`${baseURL}/cms/pages`)
|
|
32
|
+
.post(JSON.stringify({
|
|
33
|
+
...page,
|
|
34
|
+
title: page.title,
|
|
35
|
+
template: page.template,
|
|
36
|
+
views: page.views || 0,
|
|
37
|
+
conversions: page.conversions || 0,
|
|
38
|
+
}))
|
|
39
|
+
.json()
|
|
40
|
+
|
|
41
|
+
if (error.value) {
|
|
42
|
+
console.error('Error creating page:', error.value)
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const newPage = data.value as Pages
|
|
47
|
+
if (newPage) {
|
|
48
|
+
pages.value.push(newPage)
|
|
49
|
+
return newPage
|
|
50
|
+
}
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function updatePage(id: number, page: Partial<Pages>) {
|
|
55
|
+
const { error, data } = await useFetch(`${baseURL}/cms/pages/${id}`)
|
|
56
|
+
.patch(JSON.stringify({
|
|
57
|
+
...page,
|
|
58
|
+
title: page.title,
|
|
59
|
+
template: page.template,
|
|
60
|
+
views: page.views,
|
|
61
|
+
conversions: page.conversions,
|
|
62
|
+
}))
|
|
63
|
+
.json()
|
|
64
|
+
|
|
65
|
+
if (error.value) {
|
|
66
|
+
console.error('Error updating page:', error.value)
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const updatedPage = data.value as Pages
|
|
71
|
+
if (updatedPage) {
|
|
72
|
+
const index = pages.value.findIndex(p => p.id === id)
|
|
73
|
+
if (index !== -1) {
|
|
74
|
+
pages.value[index] = updatedPage
|
|
75
|
+
}
|
|
76
|
+
return updatedPage
|
|
77
|
+
}
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function deletePage(id: number) {
|
|
82
|
+
const { error } = await useFetch(`${baseURL}/cms/pages/${id}`)
|
|
83
|
+
.delete()
|
|
84
|
+
.json()
|
|
85
|
+
|
|
86
|
+
if (error.value) {
|
|
87
|
+
console.error('Error deleting page:', error.value)
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const index = pages.value.findIndex(p => p.id === id)
|
|
92
|
+
if (index !== -1) {
|
|
93
|
+
pages.value.splice(index, 1)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return true
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Export the composable
|
|
100
|
+
export function usePages() {
|
|
101
|
+
return {
|
|
102
|
+
pages,
|
|
103
|
+
fetchPages,
|
|
104
|
+
createPage,
|
|
105
|
+
updatePage,
|
|
106
|
+
deletePage,
|
|
107
|
+
}
|
|
108
|
+
}
|