@stacksjs/defaults 0.70.207 → 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,350 @@
|
|
|
1
|
+
import { defineStore, derived, registerStoresClient, state } from '@stacksjs/stx'
|
|
2
|
+
|
|
3
|
+
interface RoleRow {
|
|
4
|
+
id: number
|
|
5
|
+
name: string
|
|
6
|
+
guardName: string
|
|
7
|
+
description: string | null
|
|
8
|
+
createdAt: string | null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface PermissionRow {
|
|
12
|
+
id: number
|
|
13
|
+
name: string
|
|
14
|
+
guardName: string
|
|
15
|
+
description: string | null
|
|
16
|
+
createdAt: string | null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface UserRow {
|
|
20
|
+
id: number
|
|
21
|
+
name: string | null
|
|
22
|
+
email: string | null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* RBAC management store — backs the `/management/permissions` page
|
|
27
|
+
* (stacksjs/stacks#1845). Loads roles, permissions, and users
|
|
28
|
+
* once on page mount and keeps them in sync via the
|
|
29
|
+
* `/api/dashboard/rbac/*` endpoints. All mutations follow the
|
|
30
|
+
* snapshot → optimistic update → API call → rollback-on-failure
|
|
31
|
+
* shape established by the kanban + ci stores earlier in the
|
|
32
|
+
* session.
|
|
33
|
+
*
|
|
34
|
+
* The two pivot reads (user → roles, role → permissions) are
|
|
35
|
+
* lazy-loaded per selection because they only matter when the
|
|
36
|
+
* admin actually opens the relevant tab/row.
|
|
37
|
+
*/
|
|
38
|
+
export const rbacStore = defineStore('rbac', () => {
|
|
39
|
+
const roles = state<RoleRow[]>([])
|
|
40
|
+
const permissions = state<PermissionRow[]>([])
|
|
41
|
+
const users = state<UserRow[]>([])
|
|
42
|
+
|
|
43
|
+
// Per-user role memberships, keyed by user id. Lazy-loaded when
|
|
44
|
+
// the admin picks a user on the Users tab so the page doesn't
|
|
45
|
+
// burn N+1 round-trips just to show the user list.
|
|
46
|
+
const userRoles = state<Record<number, string[]>>({})
|
|
47
|
+
// Per-role permission memberships, keyed by role name (the URL
|
|
48
|
+
// identifier on the matrix endpoint).
|
|
49
|
+
const rolePermissions = state<Record<string, string[]>>({})
|
|
50
|
+
|
|
51
|
+
const loading = state(true)
|
|
52
|
+
const loadingPivots = state<Record<string, boolean>>({})
|
|
53
|
+
const error = state<string | null>(null)
|
|
54
|
+
|
|
55
|
+
const hasRoles = derived(() => roles().length > 0)
|
|
56
|
+
const hasPermissions = derived(() => permissions().length > 0)
|
|
57
|
+
|
|
58
|
+
// ─── Initial load ──────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
async function load(): Promise<void> {
|
|
61
|
+
loading.set(true)
|
|
62
|
+
error.set(null)
|
|
63
|
+
try {
|
|
64
|
+
const [rolesRes, permsRes, usersRes] = await Promise.all([
|
|
65
|
+
fetch('/api/dashboard/rbac/roles', { headers: { accept: 'application/json' } }),
|
|
66
|
+
fetch('/api/dashboard/rbac/permissions', { headers: { accept: 'application/json' } }),
|
|
67
|
+
fetch('/api/dashboard/rbac/users', { headers: { accept: 'application/json' } }),
|
|
68
|
+
])
|
|
69
|
+
const [rolesData, permsData, usersData] = await Promise.all([
|
|
70
|
+
rolesRes.json() as Promise<{ roles?: RoleRow[], error?: string }>,
|
|
71
|
+
permsRes.json() as Promise<{ permissions?: PermissionRow[], error?: string }>,
|
|
72
|
+
usersRes.json() as Promise<{ users?: UserRow[], error?: string }>,
|
|
73
|
+
])
|
|
74
|
+
// Pick the first error if any of the three failed — the page
|
|
75
|
+
// renders a single banner rather than three.
|
|
76
|
+
const firstError = rolesData.error || permsData.error || usersData.error
|
|
77
|
+
if (firstError)
|
|
78
|
+
error.set(firstError)
|
|
79
|
+
|
|
80
|
+
roles.set(rolesData.roles ?? [])
|
|
81
|
+
permissions.set(permsData.permissions ?? [])
|
|
82
|
+
users.set(usersData.users ?? [])
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
error.set(e instanceof Error ? e.message : String(e))
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
loading.set(false)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ─── Role mutations ────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
async function createRole(input: { name: string, guardName?: string, description?: string }): Promise<RoleRow | null> {
|
|
95
|
+
try {
|
|
96
|
+
const res = await fetch('/api/dashboard/rbac/roles', {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
99
|
+
body: JSON.stringify(input),
|
|
100
|
+
})
|
|
101
|
+
if (!res.ok && res.status !== 409) throw new Error(`HTTP ${res.status}`)
|
|
102
|
+
const data = await res.json() as { role?: RoleRow, error?: string }
|
|
103
|
+
if (data.error || !data.role) throw new Error(data.error ?? 'Create failed')
|
|
104
|
+
roles.set([...roles(), data.role])
|
|
105
|
+
return data.role
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
error.set(e instanceof Error ? e.message : String(e))
|
|
109
|
+
return null
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function deleteRole(name: string, guardName: string = 'web'): Promise<boolean> {
|
|
114
|
+
const snapshot = roles()
|
|
115
|
+
roles.set(snapshot.filter(r => !(r.name === name && r.guardName === guardName)))
|
|
116
|
+
// Cascade: a deleted role drops out of every user's role list +
|
|
117
|
+
// its row in the rolePermissions cache. Mirror the server-side
|
|
118
|
+
// cascade so the optimistic UI doesn't show stale attachments.
|
|
119
|
+
const userRolesSnapshot = userRoles()
|
|
120
|
+
const cleanedUserRoles: Record<number, string[]> = {}
|
|
121
|
+
for (const [uid, names] of Object.entries(userRolesSnapshot))
|
|
122
|
+
cleanedUserRoles[Number(uid)] = names.filter(n => n !== name)
|
|
123
|
+
userRoles.set(cleanedUserRoles)
|
|
124
|
+
|
|
125
|
+
const rolePermsSnapshot = rolePermissions()
|
|
126
|
+
const cleanedRolePerms = { ...rolePermsSnapshot }
|
|
127
|
+
delete cleanedRolePerms[name]
|
|
128
|
+
rolePermissions.set(cleanedRolePerms)
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const res = await fetch(
|
|
132
|
+
`/api/dashboard/rbac/roles/${encodeURIComponent(name)}?guard=${encodeURIComponent(guardName)}`,
|
|
133
|
+
{ method: 'DELETE', headers: { accept: 'application/json' } },
|
|
134
|
+
)
|
|
135
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
136
|
+
return true
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
// Rollback every cascade we applied optimistically.
|
|
140
|
+
roles.set(snapshot)
|
|
141
|
+
userRoles.set(userRolesSnapshot)
|
|
142
|
+
rolePermissions.set(rolePermsSnapshot)
|
|
143
|
+
error.set(e instanceof Error ? e.message : String(e))
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ─── Permission mutations ──────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
async function createPermission(input: { name: string, guardName?: string, description?: string }): Promise<PermissionRow | null> {
|
|
151
|
+
try {
|
|
152
|
+
const res = await fetch('/api/dashboard/rbac/permissions', {
|
|
153
|
+
method: 'POST',
|
|
154
|
+
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
155
|
+
body: JSON.stringify(input),
|
|
156
|
+
})
|
|
157
|
+
if (!res.ok && res.status !== 409) throw new Error(`HTTP ${res.status}`)
|
|
158
|
+
const data = await res.json() as { permission?: PermissionRow, error?: string }
|
|
159
|
+
if (data.error || !data.permission) throw new Error(data.error ?? 'Create failed')
|
|
160
|
+
permissions.set([...permissions(), data.permission])
|
|
161
|
+
return data.permission
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
error.set(e instanceof Error ? e.message : String(e))
|
|
165
|
+
return null
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function deletePermission(name: string, guardName: string = 'web'): Promise<boolean> {
|
|
170
|
+
const snapshot = permissions()
|
|
171
|
+
permissions.set(snapshot.filter(p => !(p.name === name && p.guardName === guardName)))
|
|
172
|
+
// Cascade through the rolePermissions cache: each role drops the
|
|
173
|
+
// deleted permission from its attached list.
|
|
174
|
+
const rolePermsSnapshot = rolePermissions()
|
|
175
|
+
const cleaned: Record<string, string[]> = {}
|
|
176
|
+
for (const [roleName, names] of Object.entries(rolePermsSnapshot))
|
|
177
|
+
cleaned[roleName] = names.filter(n => n !== name)
|
|
178
|
+
rolePermissions.set(cleaned)
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const res = await fetch(
|
|
182
|
+
`/api/dashboard/rbac/permissions/${encodeURIComponent(name)}?guard=${encodeURIComponent(guardName)}`,
|
|
183
|
+
{ method: 'DELETE', headers: { accept: 'application/json' } },
|
|
184
|
+
)
|
|
185
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
186
|
+
return true
|
|
187
|
+
}
|
|
188
|
+
catch (e) {
|
|
189
|
+
permissions.set(snapshot)
|
|
190
|
+
rolePermissions.set(rolePermsSnapshot)
|
|
191
|
+
error.set(e instanceof Error ? e.message : String(e))
|
|
192
|
+
return false
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ─── User-role pivot ───────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
async function loadUserRoles(userId: number): Promise<void> {
|
|
199
|
+
if (userRoles()[userId] !== undefined) return
|
|
200
|
+
const key = `user:${userId}`
|
|
201
|
+
if (loadingPivots()[key]) return
|
|
202
|
+
loadingPivots.set({ ...loadingPivots(), [key]: true })
|
|
203
|
+
try {
|
|
204
|
+
const res = await fetch(`/api/dashboard/rbac/users/${userId}/roles`, {
|
|
205
|
+
headers: { accept: 'application/json' },
|
|
206
|
+
})
|
|
207
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
208
|
+
const data = await res.json() as { roles?: Array<{ name: string }>, error?: string }
|
|
209
|
+
if (data.error) {
|
|
210
|
+
userRoles.set({ ...userRoles(), [userId]: [] })
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
userRoles.set({
|
|
214
|
+
...userRoles(),
|
|
215
|
+
[userId]: (data.roles ?? []).map(r => r.name),
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Soft-fail — page shows an empty role list for the user.
|
|
220
|
+
userRoles.set({ ...userRoles(), [userId]: [] })
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
const next = { ...loadingPivots() }
|
|
224
|
+
delete next[key]
|
|
225
|
+
loadingPivots.set(next)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function syncUserRoles(userId: number, roleNames: string[], guardName: string = 'web'): Promise<boolean> {
|
|
230
|
+
const snapshot = userRoles()[userId] ?? []
|
|
231
|
+
// Optimistic mutation.
|
|
232
|
+
userRoles.set({ ...userRoles(), [userId]: roleNames })
|
|
233
|
+
try {
|
|
234
|
+
const res = await fetch(`/api/dashboard/rbac/users/${userId}/roles`, {
|
|
235
|
+
method: 'POST',
|
|
236
|
+
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
237
|
+
body: JSON.stringify({ roles: roleNames, guardName }),
|
|
238
|
+
})
|
|
239
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
240
|
+
const data = await res.json() as { roles?: Array<{ name: string }>, error?: string }
|
|
241
|
+
if (data.error) throw new Error(data.error)
|
|
242
|
+
// Canonical reconciliation — server returns the final list.
|
|
243
|
+
userRoles.set({
|
|
244
|
+
...userRoles(),
|
|
245
|
+
[userId]: (data.roles ?? []).map(r => r.name),
|
|
246
|
+
})
|
|
247
|
+
return true
|
|
248
|
+
}
|
|
249
|
+
catch (e) {
|
|
250
|
+
userRoles.set({ ...userRoles(), [userId]: snapshot })
|
|
251
|
+
error.set(e instanceof Error ? e.message : String(e))
|
|
252
|
+
return false
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ─── Role-permission pivot ─────────────────────────────────────
|
|
257
|
+
|
|
258
|
+
async function loadRolePermissions(roleName: string, guardName: string = 'web'): Promise<void> {
|
|
259
|
+
if (rolePermissions()[roleName] !== undefined) return
|
|
260
|
+
const key = `role:${roleName}`
|
|
261
|
+
if (loadingPivots()[key]) return
|
|
262
|
+
loadingPivots.set({ ...loadingPivots(), [key]: true })
|
|
263
|
+
try {
|
|
264
|
+
const res = await fetch(
|
|
265
|
+
`/api/dashboard/rbac/roles/${encodeURIComponent(roleName)}/permissions?guard=${encodeURIComponent(guardName)}`,
|
|
266
|
+
{ headers: { accept: 'application/json' } },
|
|
267
|
+
)
|
|
268
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
269
|
+
const data = await res.json() as { permissions?: Array<{ name: string }>, error?: string }
|
|
270
|
+
if (data.error) {
|
|
271
|
+
rolePermissions.set({ ...rolePermissions(), [roleName]: [] })
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
rolePermissions.set({
|
|
275
|
+
...rolePermissions(),
|
|
276
|
+
[roleName]: (data.permissions ?? []).map(p => p.name),
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
rolePermissions.set({ ...rolePermissions(), [roleName]: [] })
|
|
281
|
+
}
|
|
282
|
+
finally {
|
|
283
|
+
const next = { ...loadingPivots() }
|
|
284
|
+
delete next[key]
|
|
285
|
+
loadingPivots.set(next)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function syncRolePermissions(roleName: string, permissionNames: string[], guardName: string = 'web'): Promise<boolean> {
|
|
290
|
+
const snapshot = rolePermissions()[roleName] ?? []
|
|
291
|
+
rolePermissions.set({ ...rolePermissions(), [roleName]: permissionNames })
|
|
292
|
+
try {
|
|
293
|
+
const res = await fetch(
|
|
294
|
+
`/api/dashboard/rbac/roles/${encodeURIComponent(roleName)}/permissions`,
|
|
295
|
+
{
|
|
296
|
+
method: 'POST',
|
|
297
|
+
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
298
|
+
body: JSON.stringify({ permissions: permissionNames, guardName }),
|
|
299
|
+
},
|
|
300
|
+
)
|
|
301
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
302
|
+
const data = await res.json() as { permissions?: Array<{ name: string }>, error?: string }
|
|
303
|
+
if (data.error) throw new Error(data.error)
|
|
304
|
+
rolePermissions.set({
|
|
305
|
+
...rolePermissions(),
|
|
306
|
+
[roleName]: (data.permissions ?? []).map(p => p.name),
|
|
307
|
+
})
|
|
308
|
+
return true
|
|
309
|
+
}
|
|
310
|
+
catch (e) {
|
|
311
|
+
rolePermissions.set({ ...rolePermissions(), [roleName]: snapshot })
|
|
312
|
+
error.set(e instanceof Error ? e.message : String(e))
|
|
313
|
+
return false
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
roles,
|
|
319
|
+
permissions,
|
|
320
|
+
users,
|
|
321
|
+
userRoles,
|
|
322
|
+
rolePermissions,
|
|
323
|
+
loading,
|
|
324
|
+
loadingPivots,
|
|
325
|
+
error,
|
|
326
|
+
hasRoles,
|
|
327
|
+
hasPermissions,
|
|
328
|
+
load,
|
|
329
|
+
createRole,
|
|
330
|
+
deleteRole,
|
|
331
|
+
createPermission,
|
|
332
|
+
deletePermission,
|
|
333
|
+
loadUserRoles,
|
|
334
|
+
syncUserRoles,
|
|
335
|
+
loadRolePermissions,
|
|
336
|
+
syncRolePermissions,
|
|
337
|
+
}
|
|
338
|
+
}, {
|
|
339
|
+
persist: {
|
|
340
|
+
storage: 'sessionStorage',
|
|
341
|
+
key: 'stacks-dashboard-rbac',
|
|
342
|
+
// Don't persist the pivot caches across page reloads — they're
|
|
343
|
+
// cheap to re-fetch and stale data on a sensitive admin surface
|
|
344
|
+
// is worse than a 100ms refetch.
|
|
345
|
+
pick: [],
|
|
346
|
+
},
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
if (typeof window !== 'undefined')
|
|
350
|
+
registerStoresClient({ rbacStore })
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
root: 'resources',
|
|
3
|
+
pagesDir: 'views',
|
|
4
|
+
// STX walks `componentsDir` recursively when resolving `<TagName />` against
|
|
5
|
+
// the filesystem, so dropping the `views/` prefix here points at the real
|
|
6
|
+
// shared components tree (`resources/components/Dashboard/UI/...`) and lets
|
|
7
|
+
// pages use `<PageLayout />`, `<PageHeader />`, etc. without an explicit
|
|
8
|
+
// `import` line in `<script server>`.
|
|
9
|
+
componentsDir: 'components',
|
|
10
|
+
layoutsDir: 'views/layouts',
|
|
11
|
+
partialsDir: 'views/partials',
|
|
12
|
+
|
|
13
|
+
app: {
|
|
14
|
+
head: {
|
|
15
|
+
title: 'Stacks',
|
|
16
|
+
meta: [
|
|
17
|
+
{ charset: 'utf-8' },
|
|
18
|
+
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
|
19
|
+
{ name: 'description', content: 'Build modern web applications, APIs, and cloud infrastructure with an elegant, type-safe framework.' },
|
|
20
|
+
],
|
|
21
|
+
links: [
|
|
22
|
+
{ rel: 'preconnect', href: 'https://fonts.bunny.net' },
|
|
23
|
+
{ rel: 'stylesheet', href: 'https://fonts.bunny.net/css?family=figtree:400,500,600,700' },
|
|
24
|
+
],
|
|
25
|
+
bodyClass: 'min-h-screen font-sans antialiased bg-white dark:bg-neutral-950 text-neutral-600 dark:text-neutral-400',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
router: {
|
|
30
|
+
container: 'main',
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
strict: false,
|
|
34
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
@extends('layouts/default')
|
|
2
|
+
|
|
3
|
+
@section('content')
|
|
4
|
+
<div class="p-6 space-y-6">
|
|
5
|
+
|
|
6
|
+
<PageHeader title="Team Members" description="Manage your team members and invitations" />
|
|
7
|
+
|
|
8
|
+
<form class="px-6 py-4 text-sm bg-white dark:bg-neutral-800 rounded-lg" @submit.prevent="handleInvite">
|
|
9
|
+
<div class="py-2">
|
|
10
|
+
<div class="flex flex-row p-4 text-gray-600 text-sm dark:text-neutral-400 bg-gray-50 dark:bg-neutral-700 rounded-lg">
|
|
11
|
+
<div class="flex-shrink-0 h-6 w-6 text-gray-400 i-hugeicons-information-circle"></div>
|
|
12
|
+
<div class="flex flex-grow ml-4 mt-0.5">
|
|
13
|
+
<p>You can add teammates here.</p>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div :show="hasInviteSuccess" class="mt-3 p-3 bg-green-50 dark:bg-green-900/20 rounded-md">
|
|
18
|
+
<p class="text-green-700 text-sm dark:text-green-400">{{ inviteSuccess }}</p>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div :show="hasInviteError" class="mt-3 p-3 bg-red-50 dark:bg-red-900/20 rounded-md">
|
|
22
|
+
<p class="text-red-700 text-sm dark:text-red-400">{{ inviteError }}</p>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="flex flex-col mt-4">
|
|
26
|
+
<label class="text-gray-700 dark:text-neutral-300">Enter teammate email to invite them.</label>
|
|
27
|
+
<div class="flex flex-col mt-1">
|
|
28
|
+
<input
|
|
29
|
+
type="email"
|
|
30
|
+
x-model="inviteEmail"
|
|
31
|
+
placeholder="teammate@example.com"
|
|
32
|
+
class="block px-4 py-2 w-full text-gray-900 dark:text-neutral-100 dark:bg-neutral-800 border border-gray-300 rounded-md dark:border-neutral-600 focus:border-none focus:ring-2 focus:ring-blue-500 shadow-sm"
|
|
33
|
+
autocorrect="off"
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
<div>
|
|
39
|
+
<div class="flex justify-end mt-4">
|
|
40
|
+
<Button type="submit" variant="primary" :disabled="isInviting">{{ inviteButtonLabel() }}</Button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</form>
|
|
44
|
+
|
|
45
|
+
<div class="mt-16 px-6 py-4 text-sm bg-white dark:bg-neutral-800 rounded-lg">
|
|
46
|
+
<div class="sm:flex sm:items-center">
|
|
47
|
+
<div class="sm:flex-auto">
|
|
48
|
+
<h1 class="font-semibold leading-6 text-base text-gray-900 dark:text-neutral-100">Teammates</h1>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="flow-root mt-8">
|
|
52
|
+
<div class="overflow-x-auto -mx-4 -my-2 sm:-mx-6 lg:-mx-8">
|
|
53
|
+
<div class="inline-block py-2 sm:px-6 lg:px-8 min-w-full align-middle">
|
|
54
|
+
<table class="min-w-full divide-gray-300 divide-y dark:divide-neutral-600">
|
|
55
|
+
<thead>
|
|
56
|
+
<tr>
|
|
57
|
+
<th scope="col" class="pl-4 pr-3 py-3 sm:pl-0 font-medium text-gray-500 text-left text-xs tracking-wide uppercase dark:text-neutral-400">Name</th>
|
|
58
|
+
<th scope="col" class="px-3 py-3 font-medium text-gray-500 text-left text-xs tracking-wide uppercase dark:text-neutral-400">Email</th>
|
|
59
|
+
<th scope="col" class="px-3 py-3 font-medium text-gray-500 text-left text-xs tracking-wide uppercase dark:text-neutral-400">Role</th>
|
|
60
|
+
<th scope="col" class="relative pl-3 pr-4 py-3 sm:pr-0"><span class="sr-only">Actions</span></th>
|
|
61
|
+
</tr>
|
|
62
|
+
</thead>
|
|
63
|
+
<tbody class="bg-white dark:bg-neutral-800 divide-gray-200 divide-y dark:divide-neutral-600">
|
|
64
|
+
<tr :show="isLoading">
|
|
65
|
+
<td colspan="4" class="py-8 text-center text-gray-500 text-sm dark:text-neutral-400">Loading teammates...</td>
|
|
66
|
+
</tr>
|
|
67
|
+
<tr :show="isEmpty">
|
|
68
|
+
<td colspan="4" class="py-8 text-center text-gray-500 text-sm dark:text-neutral-400">No teammates yet. Invite someone above.</td>
|
|
69
|
+
</tr>
|
|
70
|
+
<template :for="teammate in teammates">
|
|
71
|
+
<tr>
|
|
72
|
+
<td class="pl-4 pr-3 py-4 sm:pl-0 font-medium text-gray-900 text-sm whitespace-nowrap dark:text-neutral-100">{{ teammate.name }}</td>
|
|
73
|
+
<td class="px-3 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ teammate.email }}</td>
|
|
74
|
+
<td class="px-3 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ teammate.role }}</td>
|
|
75
|
+
<td class="relative pl-3 pr-4 py-4 sm:pr-0 font-medium text-right text-sm whitespace-nowrap">
|
|
76
|
+
<button class="text-gray-400 hover:text-neutral-500 duration-150 transition-colors">
|
|
77
|
+
<div class="h-6 w-6 i-hugeicons-ellipsis-vertical"></div>
|
|
78
|
+
</button>
|
|
79
|
+
</td>
|
|
80
|
+
</tr>
|
|
81
|
+
</template>
|
|
82
|
+
</tbody>
|
|
83
|
+
</table>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<script client>
|
|
91
|
+
import { get, post } from '../../../functions/api'
|
|
92
|
+
|
|
93
|
+
interface Teammate {
|
|
94
|
+
id?: number
|
|
95
|
+
name: string
|
|
96
|
+
email: string
|
|
97
|
+
role: string
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const route = useRoute()
|
|
101
|
+
|
|
102
|
+
const teammates = state<Teammate[]>([])
|
|
103
|
+
const isLoading = state(true)
|
|
104
|
+
|
|
105
|
+
const inviteEmail = state('')
|
|
106
|
+
const inviteError = state('')
|
|
107
|
+
const inviteSuccess = state('')
|
|
108
|
+
const isInviting = state(false)
|
|
109
|
+
|
|
110
|
+
const hasInviteSuccess = derived(() => inviteSuccess() !== '')
|
|
111
|
+
const hasInviteError = derived(() => inviteError() !== '')
|
|
112
|
+
const isEmpty = derived(() => !isLoading() && teammates().length === 0)
|
|
113
|
+
|
|
114
|
+
function inviteButtonLabel(): string {
|
|
115
|
+
return isInviting() ? 'Inviting...' : 'Invite'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function fetchTeammates(): Promise<void> {
|
|
119
|
+
isLoading.set(true)
|
|
120
|
+
try {
|
|
121
|
+
const data = await get(`/teams/${route.params.id}/members`)
|
|
122
|
+
teammates.set(data.data || [])
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
teammates.set([])
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
isLoading.set(false)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function validateEmail(email: string): boolean {
|
|
133
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function handleInvite(): Promise<void> {
|
|
137
|
+
inviteError.set('')
|
|
138
|
+
inviteSuccess.set('')
|
|
139
|
+
|
|
140
|
+
const email = inviteEmail().trim()
|
|
141
|
+
if (!email) {
|
|
142
|
+
inviteError.set('Please enter an email address.')
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
if (!validateEmail(email)) {
|
|
146
|
+
inviteError.set('Please enter a valid email address.')
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
isInviting.set(true)
|
|
151
|
+
try {
|
|
152
|
+
await post(`/teams/${route.params.id}/invite`, { email })
|
|
153
|
+
inviteSuccess.set(`Invitation sent to ${email}.`)
|
|
154
|
+
inviteEmail.set('')
|
|
155
|
+
await fetchTeammates()
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
inviteError.set(e instanceof Error ? e.message : 'Failed to send invitation.')
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
isInviting.set(false)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
onMount(() => {
|
|
166
|
+
fetchTeammates()
|
|
167
|
+
})
|
|
168
|
+
</script>
|
|
169
|
+
|
|
170
|
+
@endsection
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
const category = ''
|
|
3
|
+
const subcategory = ''
|
|
4
|
+
const filter = ''
|
|
5
|
+
</script>
|
|
6
|
+
<ForumLayout :route="routeType">
|
|
7
|
+
<div class="grid gap-8 grid-cols-1 lg:grid-cols-3">
|
|
8
|
+
<div class="lg:col-span-2">
|
|
9
|
+
<ForumBreadcrumb :items="breadcrumbItems" />
|
|
10
|
+
|
|
11
|
+
<div class="mb-6">
|
|
12
|
+
<div class="flex items-center justify-between px-4 py-3 bg-gray-100 rounded-t-md">
|
|
13
|
+
<div>
|
|
14
|
+
<h1 class="font-bold text-xl">{{ categoryTitle }}</h1>
|
|
15
|
+
<p class="mt-1 text-gray-600 text-sm">{{ categoryDescription }}</p>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div>
|
|
19
|
+
<a href="/forum/new-topic" class="px-4 py-2 text-white bg-primary rounded-md hover:opacity-90">New Topic</a>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<ForumTopicList :title="listTitle">
|
|
25
|
+
@foreach (topic in topics)
|
|
26
|
+
<ForumTopicItem
|
|
27
|
+
:key="topic.id"
|
|
28
|
+
:title="topic.title"
|
|
29
|
+
:link="topic.link"
|
|
30
|
+
:authorName="topic.authorName"
|
|
31
|
+
:authorAvatar="topic.authorAvatar"
|
|
32
|
+
:createdAt="topic.createdAt"
|
|
33
|
+
:viewCount="topic.viewCount"
|
|
34
|
+
:replyCount="topic.replyCount"
|
|
35
|
+
:likeCount="topic.likeCount"
|
|
36
|
+
:category="topic.category"
|
|
37
|
+
:categoryLink="topic.categoryLink"
|
|
38
|
+
:excerpt="topic.excerpt || ''"
|
|
39
|
+
:isPinned="!!topic.isPinned"
|
|
40
|
+
:isSolved="!!topic.isSolved"
|
|
41
|
+
:lastReplyAuthorName="topic.lastReplyAuthorName || ''"
|
|
42
|
+
:lastReplyAuthorAvatar="topic.lastReplyAuthorAvatar || ''"
|
|
43
|
+
:lastReplyAt="topic.lastReplyAt || ''"
|
|
44
|
+
/>
|
|
45
|
+
@endforeach
|
|
46
|
+
</ForumTopicList>
|
|
47
|
+
|
|
48
|
+
<ForumPagination :currentPage="1" :totalPages="5" />
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="lg:col-span-1">
|
|
52
|
+
<ForumSidebar
|
|
53
|
+
:stats="{
|
|
54
|
+
topics: 540,
|
|
55
|
+
posts: 3287,
|
|
56
|
+
members: 1245,
|
|
57
|
+
newestMember: 'NewUser123'
|
|
58
|
+
}"
|
|
59
|
+
:popularTopics="[
|
|
60
|
+
{
|
|
61
|
+
id: 1,
|
|
62
|
+
title: 'How to implement authentication in a new Stacks.js project?',
|
|
63
|
+
link: '/forum/topic/1',
|
|
64
|
+
replyCount: 3,
|
|
65
|
+
viewCount: 45
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: 2,
|
|
69
|
+
title: 'Best practices for database migrations in Stacks.js',
|
|
70
|
+
link: '/forum/topic/3',
|
|
71
|
+
replyCount: 15,
|
|
72
|
+
viewCount: 128
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 3,
|
|
76
|
+
title: 'Introducing myself to the community!',
|
|
77
|
+
link: '/forum/topic/2',
|
|
78
|
+
replyCount: 8,
|
|
79
|
+
viewCount: 32
|
|
80
|
+
}
|
|
81
|
+
]"
|
|
82
|
+
:onlineUsers="[
|
|
83
|
+
{ id: 1, name: 'JohnDoe', avatar: '/images/avatars/default.png' },
|
|
84
|
+
{ id: 2, name: 'DatabaseGuru', avatar: '/images/avatars/default.png' },
|
|
85
|
+
{ id: 3, name: 'NewUser123', avatar: '/images/avatars/default.png' },
|
|
86
|
+
{ id: 4, name: 'ExpertUser', avatar: '/images/avatars/default.png' },
|
|
87
|
+
{ id: 5, name: 'StacksMaster', avatar: '/images/avatars/default.png' }
|
|
88
|
+
]"
|
|
89
|
+
/>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</ForumLayout>
|