@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,514 @@
|
|
|
1
|
+
@extends('layouts/default')
|
|
2
|
+
|
|
3
|
+
@section('content')
|
|
4
|
+
|
|
5
|
+
@title('Dashboard - Marketing Lists')
|
|
6
|
+
<PageLayout>
|
|
7
|
+
<div class="sm:flex sm:items-center sm:justify-between">
|
|
8
|
+
<PageHeader title="Marketing Lists" description="Manage your email and SMS subscriber lists" />
|
|
9
|
+
<div class="mt-4 sm:mt-0">
|
|
10
|
+
<button @click="openAddListModal()" class="inline-flex items-center px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md shadow-sm">
|
|
11
|
+
<div class="mr-2 h-5 w-5 i-hugeicons-plus-sign"></div>
|
|
12
|
+
New List
|
|
13
|
+
</button>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="grid gap-5 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 mt-6">
|
|
18
|
+
<div class="flex overflow-hidden items-center p-5 bg-white dark:bg-neutral-800 rounded-lg shadow">
|
|
19
|
+
<div class="p-2 h-10 w-10 bg-blue-100 dark:bg-blue-900 rounded-md">
|
|
20
|
+
<div class="h-6 w-6 text-blue-600 dark:text-blue-300 i-hugeicons-folder-01"></div>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="ml-5">
|
|
23
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">Total Lists</dt>
|
|
24
|
+
<dd class="font-medium text-gray-900 text-lg dark:text-white">{{ totalLists() }}</dd>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="flex overflow-hidden items-center p-5 bg-white dark:bg-neutral-800 rounded-lg shadow">
|
|
28
|
+
<div class="p-2 h-10 w-10 bg-blue-100 dark:bg-blue-900 rounded-md">
|
|
29
|
+
<div class="h-6 w-6 text-blue-600 dark:text-blue-300 i-hugeicons-mail-01"></div>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="ml-5">
|
|
32
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">Email Lists</dt>
|
|
33
|
+
<dd class="font-medium text-gray-900 text-lg dark:text-white">{{ totalEmailLists() }}</dd>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="flex overflow-hidden items-center p-5 bg-white dark:bg-neutral-800 rounded-lg shadow">
|
|
37
|
+
<div class="p-2 h-10 w-10 bg-purple-100 dark:bg-purple-900 rounded-md">
|
|
38
|
+
<div class="h-6 w-6 text-purple-600 dark:text-purple-300 i-hugeicons-message-multiple-02"></div>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="ml-5">
|
|
41
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">SMS Lists</dt>
|
|
42
|
+
<dd class="font-medium text-gray-900 text-lg dark:text-white">{{ totalSmsLists() }}</dd>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="flex overflow-hidden items-center p-5 bg-white dark:bg-neutral-800 rounded-lg shadow">
|
|
46
|
+
<div class="p-2 h-10 w-10 bg-green-100 dark:bg-green-900 rounded-md">
|
|
47
|
+
<div class="h-6 w-6 text-green-600 dark:text-green-300 i-hugeicons-user-multiple"></div>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="ml-5">
|
|
50
|
+
<dt class="font-medium text-gray-500 text-sm dark:text-neutral-400">Total Subscribers</dt>
|
|
51
|
+
<dd class="font-medium text-gray-900 text-lg dark:text-white">{{ formatNumber(totalSubscribers()) }}</dd>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between mt-6">
|
|
57
|
+
<div class="relative flex-1 max-w-sm">
|
|
58
|
+
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
|
|
59
|
+
<div class="h-5 w-5 text-gray-400 i-hugeicons-search-01"></div>
|
|
60
|
+
</div>
|
|
61
|
+
<input x-model="searchQuery" type="text" placeholder="Search lists..." class="block pl-10 pr-3 py-1.5 w-full text-gray-900 sm:leading-6 sm:text-sm dark:text-white placeholder:text-gray-400 dark:placeholder:text-neutral-500 dark:bg-neutral-800 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-700 focus:ring-2 focus:ring-blue-600" />
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<div class="flex flex-col gap-4 sm:flex-row">
|
|
65
|
+
<select x-model="typeFilter" class="block pl-3 pr-10 py-1.5 text-gray-900 sm:leading-6 sm:text-sm dark:text-white dark:bg-neutral-800 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-700 focus:ring-2 focus:ring-blue-600">
|
|
66
|
+
<option value="all">All Types</option>
|
|
67
|
+
<template :for="type in listTypes">
|
|
68
|
+
<option :value="type">{{ type }}</option>
|
|
69
|
+
</template>
|
|
70
|
+
</select>
|
|
71
|
+
|
|
72
|
+
<select x-model="statusFilter" class="block pl-3 pr-10 py-1.5 text-gray-900 sm:leading-6 sm:text-sm dark:text-white dark:bg-neutral-800 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-700 focus:ring-2 focus:ring-blue-600">
|
|
73
|
+
<option value="all">All Statuses</option>
|
|
74
|
+
<template :for="status in listStatuses">
|
|
75
|
+
<option :value="status">{{ status }}</option>
|
|
76
|
+
</template>
|
|
77
|
+
</select>
|
|
78
|
+
|
|
79
|
+
<select x-model="sortBy" class="block pl-3 pr-10 py-1.5 text-gray-900 sm:leading-6 sm:text-sm dark:text-white dark:bg-neutral-800 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-700 focus:ring-2 focus:ring-blue-600">
|
|
80
|
+
<option value="dateCreated">Sort by Created</option>
|
|
81
|
+
<option value="name">Sort by Name</option>
|
|
82
|
+
<option value="subscriberCount">Sort by Subscribers</option>
|
|
83
|
+
<option value="openRate">Sort by Open Rate</option>
|
|
84
|
+
</select>
|
|
85
|
+
|
|
86
|
+
<select x-model="sortOrder" class="block pl-3 pr-10 py-1.5 text-gray-900 sm:leading-6 sm:text-sm dark:text-white dark:bg-neutral-800 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-700 focus:ring-2 focus:ring-blue-600">
|
|
87
|
+
<option value="desc">Descending</option>
|
|
88
|
+
<option value="asc">Ascending</option>
|
|
89
|
+
</select>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div :show="selectedLists().length > 0" class="flex items-center justify-between mt-4 p-3 bg-blue-50 dark:bg-blue-900/30 rounded-md">
|
|
94
|
+
<span class="text-blue-700 text-sm dark:text-blue-200">{{ selectedLists().length }} list(s) selected</span>
|
|
95
|
+
<div class="flex gap-2">
|
|
96
|
+
<button @click="bulkChangeStatus('Archived')" class="px-2.5 py-1.5 font-semibold text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-700 hover:bg-gray-50 dark:hover:bg-neutral-600 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 shadow-sm">Archive</button>
|
|
97
|
+
<button @click="bulkDelete()" class="px-2.5 py-1.5 font-semibold text-sm text-white bg-red-600 hover:bg-red-500 rounded-md shadow-sm">Delete</button>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div class="overflow-hidden mt-6 ring-1 ring-black ring-opacity-5 sm:rounded-lg shadow">
|
|
102
|
+
<table class="min-w-full divide-gray-300 divide-y dark:divide-neutral-700">
|
|
103
|
+
<thead class="bg-gray-50 dark:bg-neutral-700">
|
|
104
|
+
<tr>
|
|
105
|
+
<th scope="col" class="relative pl-4 pr-3 py-3.5 sm:pl-6 w-12">
|
|
106
|
+
<input type="checkbox" :checked="allSelected()" @change="toggleSelectAll()" class="h-4 w-4 text-blue-600 dark:bg-neutral-700 border-gray-300 rounded dark:border-neutral-600 focus:ring-blue-600" />
|
|
107
|
+
</th>
|
|
108
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-white">Name</th>
|
|
109
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-white">Type</th>
|
|
110
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-white">Subscribers</th>
|
|
111
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-white">Open Rate</th>
|
|
112
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-white">Click Rate</th>
|
|
113
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-white">Last Sent</th>
|
|
114
|
+
<th scope="col" class="px-3 py-3.5 font-semibold text-gray-900 text-left text-sm dark:text-white">Status</th>
|
|
115
|
+
<th scope="col" class="relative pl-3 pr-4 py-3.5 sm:pr-6"><span class="sr-only">Actions</span></th>
|
|
116
|
+
</tr>
|
|
117
|
+
</thead>
|
|
118
|
+
<tbody class="bg-white dark:bg-neutral-800 divide-gray-200 divide-y dark:divide-neutral-700">
|
|
119
|
+
<template :for="list in paginatedLists">
|
|
120
|
+
<tr class="hover:bg-gray-50 dark:hover:bg-neutral-700/50">
|
|
121
|
+
<td class="relative pl-4 pr-3 py-4 sm:pl-6">
|
|
122
|
+
<input type="checkbox" :checked="isListSelected(list.id)" @change="toggleListSelection(list.id)" class="h-4 w-4 text-blue-600 dark:bg-neutral-700 border-gray-300 rounded dark:border-neutral-600 focus:ring-blue-600" />
|
|
123
|
+
</td>
|
|
124
|
+
<td class="px-3 py-4">
|
|
125
|
+
<div class="flex items-center">
|
|
126
|
+
<div class="font-medium text-gray-900 text-sm dark:text-white">{{ list.name }}</div>
|
|
127
|
+
<span :show="list.isDefault" class="inline-flex items-center ml-2 px-2 py-0.5 font-medium text-blue-700 text-xs dark:text-blue-300 bg-blue-100 dark:bg-blue-900 rounded">Default</span>
|
|
128
|
+
</div>
|
|
129
|
+
<div class="max-w-md text-gray-500 text-xs truncate dark:text-neutral-400">{{ list.description }}</div>
|
|
130
|
+
</td>
|
|
131
|
+
<td class="px-3 py-4 text-sm whitespace-nowrap">
|
|
132
|
+
<span class="{{ typeBadgeClass(list.type) }}">{{ list.type }}</span>
|
|
133
|
+
</td>
|
|
134
|
+
<td class="px-3 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ formatNumber(list.subscriberCount) }}</td>
|
|
135
|
+
<td class="px-3 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ formatRate(list.openRate) }}</td>
|
|
136
|
+
<td class="px-3 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ formatRate(list.clickRate) }}</td>
|
|
137
|
+
<td class="px-3 py-4 text-gray-500 text-sm whitespace-nowrap dark:text-neutral-400">{{ formatDate(list.lastSent) }}</td>
|
|
138
|
+
<td class="px-3 py-4 text-sm whitespace-nowrap">
|
|
139
|
+
<span class="{{ statusBadgeClass(list.status) }}">{{ list.status }}</span>
|
|
140
|
+
</td>
|
|
141
|
+
<td class="relative pl-3 pr-4 py-4 sm:pr-6 font-medium text-right text-sm whitespace-nowrap">
|
|
142
|
+
<div class="flex justify-end space-x-2">
|
|
143
|
+
<button @click="duplicateList(list.id)" class="text-gray-600 dark:text-neutral-300 hover:text-gray-900 dark:hover:text-white" title="Duplicate">
|
|
144
|
+
<div class="h-5 w-5 i-hugeicons-copy"></div>
|
|
145
|
+
</button>
|
|
146
|
+
<button @click="openEditListModal(list.id)" class="text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300" title="Edit">
|
|
147
|
+
<div class="h-5 w-5 i-hugeicons-edit-01"></div>
|
|
148
|
+
</button>
|
|
149
|
+
<button @click="deleteList(list.id)" class="text-red-600 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300" title="Delete">
|
|
150
|
+
<div class="h-5 w-5 i-hugeicons-waste"></div>
|
|
151
|
+
</button>
|
|
152
|
+
</div>
|
|
153
|
+
</td>
|
|
154
|
+
</tr>
|
|
155
|
+
</template>
|
|
156
|
+
<tr :show="paginatedLists().length === 0">
|
|
157
|
+
<td colspan="9" class="px-4 py-8 text-center text-gray-500 text-sm dark:text-neutral-400">No lists found.</td>
|
|
158
|
+
</tr>
|
|
159
|
+
</tbody>
|
|
160
|
+
</table>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<div :show="totalPages() > 1" class="flex items-center justify-between mt-6">
|
|
164
|
+
<p class="text-gray-700 text-sm dark:text-neutral-300">Page {{ currentPage() }} of {{ totalPages() }}</p>
|
|
165
|
+
<nav class="inline-flex -space-x-px rounded-md shadow-sm isolate">
|
|
166
|
+
<button @click="prevPage()" :disabled="currentPage() === 1" class="inline-flex relative items-center px-2 py-2 text-gray-400 dark:text-neutral-500 hover:bg-gray-50 dark:hover:bg-neutral-700 ring-1 ring-gray-300 ring-inset rounded-l-md dark:ring-neutral-600 disabled:opacity-50 disabled:cursor-not-allowed">
|
|
167
|
+
<div class="h-5 w-5 i-hugeicons-arrow-left-01"></div>
|
|
168
|
+
</button>
|
|
169
|
+
<button @click="nextPage()" :disabled="currentPage() === totalPages()" class="inline-flex relative items-center px-2 py-2 text-gray-400 dark:text-neutral-500 hover:bg-gray-50 dark:hover:bg-neutral-700 ring-1 ring-gray-300 ring-inset rounded-r-md dark:ring-neutral-600 disabled:opacity-50 disabled:cursor-not-allowed">
|
|
170
|
+
<div class="h-5 w-5 i-hugeicons-arrow-right-01"></div>
|
|
171
|
+
</button>
|
|
172
|
+
</nav>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<div :show="showListModal()" @click.self="closeListModal()" class="flex fixed inset-0 z-50 items-center justify-center p-4 bg-black bg-opacity-50">
|
|
176
|
+
<div class="overflow-y-auto p-6 max-h-[90vh] w-full sm:max-w-2xl bg-white dark:bg-neutral-800 rounded-lg shadow-xl">
|
|
177
|
+
<div class="flex items-start justify-between mb-4">
|
|
178
|
+
<h3 class="font-semibold text-gray-900 text-lg dark:text-white">{{ isEditMode() ? 'Edit List' : 'New List' }}</h3>
|
|
179
|
+
<button @click="closeListModal()" class="text-gray-400 dark:text-neutral-300 hover:text-gray-600">
|
|
180
|
+
<i class="h-5 w-5 i-hugeicons-cancel-01"></i>
|
|
181
|
+
</button>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<div class="space-y-4">
|
|
185
|
+
<div>
|
|
186
|
+
<label class="block font-medium text-gray-900 text-sm dark:text-white">Name</label>
|
|
187
|
+
<input x-model="listFormName" type="text" placeholder="e.g. Newsletter Subscribers" class="block mt-2 py-1.5 w-full text-gray-900 sm:text-sm dark:text-white dark:bg-neutral-700 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 focus:ring-2 focus:ring-blue-600 shadow-sm" />
|
|
188
|
+
</div>
|
|
189
|
+
<div>
|
|
190
|
+
<label class="block font-medium text-gray-900 text-sm dark:text-white">Description</label>
|
|
191
|
+
<textarea x-model="listFormDescription" rows="2" placeholder="Brief description of this list" class="block mt-2 py-1.5 w-full text-gray-900 sm:text-sm dark:text-white dark:bg-neutral-700 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 focus:ring-2 focus:ring-blue-600 shadow-sm"></textarea>
|
|
192
|
+
</div>
|
|
193
|
+
<div>
|
|
194
|
+
<label class="block font-medium text-gray-900 text-sm dark:text-white">Tags (comma-separated)</label>
|
|
195
|
+
<input x-model="listFormTags" type="text" placeholder="e.g. newsletter, weekly" class="block mt-2 py-1.5 w-full text-gray-900 sm:text-sm dark:text-white dark:bg-neutral-700 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 focus:ring-2 focus:ring-blue-600 shadow-sm" />
|
|
196
|
+
</div>
|
|
197
|
+
<div class="grid gap-4 grid-cols-1 sm:grid-cols-2">
|
|
198
|
+
<div>
|
|
199
|
+
<label class="block font-medium text-gray-900 text-sm dark:text-white">Type</label>
|
|
200
|
+
<select x-model="listFormType" class="block mt-2 pl-3 pr-10 py-1.5 w-full text-gray-900 sm:text-sm dark:text-white dark:bg-neutral-700 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 focus:ring-2 focus:ring-blue-600">
|
|
201
|
+
<option value="email">Email</option>
|
|
202
|
+
<option value="sms">SMS</option>
|
|
203
|
+
<option value="both">Both</option>
|
|
204
|
+
</select>
|
|
205
|
+
</div>
|
|
206
|
+
<div>
|
|
207
|
+
<label class="block font-medium text-gray-900 text-sm dark:text-white">Status</label>
|
|
208
|
+
<select x-model="listFormStatus" class="block mt-2 pl-3 pr-10 py-1.5 w-full text-gray-900 sm:text-sm dark:text-white dark:bg-neutral-700 border-0 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 focus:ring-2 focus:ring-blue-600">
|
|
209
|
+
<option value="Active">Active</option>
|
|
210
|
+
<option value="Draft">Draft</option>
|
|
211
|
+
<option value="Archived">Archived</option>
|
|
212
|
+
</select>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
<div class="flex items-center">
|
|
216
|
+
<input id="isDefault" :checked="listFormIsDefault" @change="toggleListFormIsDefault()" type="checkbox" class="h-4 w-4 text-blue-600 border-gray-300 rounded dark:border-neutral-600 focus:ring-blue-500" />
|
|
217
|
+
<label for="isDefault" class="ml-2 text-gray-700 text-sm dark:text-neutral-300">Set as default list</label>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<div class="flex justify-end mt-6 space-x-2">
|
|
222
|
+
<button @click="closeListModal()" class="inline-flex justify-center px-3 py-2 font-semibold text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-700 hover:bg-gray-50 dark:hover:bg-neutral-600 ring-1 ring-gray-300 ring-inset rounded-md dark:ring-neutral-600 shadow-sm">Cancel</button>
|
|
223
|
+
<button @click="saveList()" :disabled="!listFormName()" class="inline-flex justify-center px-3 py-2 font-semibold text-sm text-white bg-blue-600 hover:bg-blue-500 rounded-md shadow-sm disabled:opacity-50">{{ isEditMode() ? 'Save' : 'Create' }}</button>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
</PageLayout>
|
|
228
|
+
|
|
229
|
+
<script client>
|
|
230
|
+
// Filter options — moved out of the former `<script server>` block
|
|
231
|
+
// (stacksjs/stacks#1838).
|
|
232
|
+
const listTypes = ['email', 'sms', 'both']
|
|
233
|
+
const listStatuses = ['Active', 'Draft', 'Archived']
|
|
234
|
+
|
|
235
|
+
interface MarketingList {
|
|
236
|
+
id: number
|
|
237
|
+
name: string
|
|
238
|
+
description: string
|
|
239
|
+
type: 'email' | 'sms' | 'both'
|
|
240
|
+
tags: string[]
|
|
241
|
+
subscriberCount: number
|
|
242
|
+
openRate?: number
|
|
243
|
+
clickRate?: number
|
|
244
|
+
unsubscribeRate?: number
|
|
245
|
+
lastSent: string | null
|
|
246
|
+
dateCreated: string
|
|
247
|
+
status: 'Active' | 'Draft' | 'Archived'
|
|
248
|
+
isDefault: boolean
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const initialLists: MarketingList[] = [
|
|
252
|
+
{ id: 1, name: 'Newsletter Subscribers', description: 'Main newsletter list for weekly updates', type: 'email', tags: ['newsletter', 'main'], subscriberCount: 1245, openRate: 24.5, clickRate: 3.2, unsubscribeRate: 0.5, lastSent: '2023-12-15', dateCreated: '2023-01-10', status: 'Active', isDefault: true },
|
|
253
|
+
{ id: 2, name: 'Product Announcements', description: 'Customers interested in new product releases', type: 'email', tags: ['products', 'announcements'], subscriberCount: 876, openRate: 32.1, clickRate: 5.7, unsubscribeRate: 0.3, lastSent: '2023-12-01', dateCreated: '2023-02-15', status: 'Active', isDefault: false },
|
|
254
|
+
{ id: 3, name: 'SMS Promotions', description: 'Customers who opted in for SMS promotional messages', type: 'sms', tags: ['sms', 'promotions'], subscriberCount: 542, openRate: undefined, clickRate: 8.3, unsubscribeRate: 1.2, lastSent: '2023-11-25', dateCreated: '2023-03-20', status: 'Active', isDefault: false },
|
|
255
|
+
{ id: 4, name: 'Abandoned Cart Reminders', description: 'Customers who abandoned their shopping carts', type: 'both', tags: ['cart', 'recovery'], subscriberCount: 328, openRate: 41.2, clickRate: 12.5, unsubscribeRate: 0.8, lastSent: '2023-12-10', dateCreated: '2023-04-05', status: 'Active', isDefault: false },
|
|
256
|
+
{ id: 5, name: 'VIP Customers', description: 'High-value customers for exclusive offers', type: 'both', tags: ['vip', 'exclusive'], subscriberCount: 156, openRate: 58.7, clickRate: 24.3, unsubscribeRate: 0.1, lastSent: '2023-12-05', dateCreated: '2023-05-12', status: 'Active', isDefault: false },
|
|
257
|
+
{ id: 6, name: 'Re-engagement Campaign', description: 'Inactive customers we want to win back', type: 'email', tags: ['inactive', 'win-back'], subscriberCount: 723, openRate: 12.8, clickRate: 1.9, unsubscribeRate: 3.2, lastSent: '2023-11-15', dateCreated: '2023-06-20', status: 'Active', isDefault: false },
|
|
258
|
+
{ id: 7, name: 'Event Attendees', description: 'Customers who registered for upcoming events', type: 'both', tags: ['events', 'webinars'], subscriberCount: 412, openRate: 38.5, clickRate: 9.7, unsubscribeRate: 0.4, lastSent: '2023-12-08', dateCreated: '2023-07-15', status: 'Active', isDefault: false },
|
|
259
|
+
{ id: 8, name: 'Beta Testers', description: 'Customers interested in testing new features', type: 'email', tags: ['beta', 'testing'], subscriberCount: 189, openRate: 45.2, clickRate: 18.6, unsubscribeRate: 0.2, lastSent: '2023-11-30', dateCreated: '2023-08-10', status: 'Active', isDefault: false },
|
|
260
|
+
]
|
|
261
|
+
|
|
262
|
+
const lists = state<MarketingList[]>(initialLists)
|
|
263
|
+
const searchQuery = state('')
|
|
264
|
+
const typeFilter = state('all')
|
|
265
|
+
const statusFilter = state('all')
|
|
266
|
+
const sortBy = state<'dateCreated' | 'name' | 'subscriberCount' | 'openRate'>('dateCreated')
|
|
267
|
+
const sortOrder = state<'asc' | 'desc'>('desc')
|
|
268
|
+
const currentPage = state(1)
|
|
269
|
+
const itemsPerPage = 8
|
|
270
|
+
const selectedLists = state<number[]>([])
|
|
271
|
+
|
|
272
|
+
const showListModal = state(false)
|
|
273
|
+
const isEditMode = state(false)
|
|
274
|
+
const editingListId = state<number | null>(null)
|
|
275
|
+
const listFormName = state('')
|
|
276
|
+
const listFormDescription = state('')
|
|
277
|
+
const listFormTags = state('')
|
|
278
|
+
const listFormType = state<'email' | 'sms' | 'both'>('email')
|
|
279
|
+
const listFormStatus = state<'Active' | 'Draft' | 'Archived'>('Draft')
|
|
280
|
+
const listFormIsDefault = state(false)
|
|
281
|
+
|
|
282
|
+
const totalLists = derived(() => lists().length)
|
|
283
|
+
const totalEmailLists = derived(() => lists().filter(l => l.type === 'email' || l.type === 'both').length)
|
|
284
|
+
const totalSmsLists = derived(() => lists().filter(l => l.type === 'sms' || l.type === 'both').length)
|
|
285
|
+
const totalSubscribers = derived(() => lists().reduce((sum, l) => sum + l.subscriberCount, 0))
|
|
286
|
+
|
|
287
|
+
const filteredLists = derived(() => {
|
|
288
|
+
const q = searchQuery().toLowerCase()
|
|
289
|
+
const type = typeFilter()
|
|
290
|
+
const status = statusFilter()
|
|
291
|
+
const sort = sortBy()
|
|
292
|
+
const order = sortOrder()
|
|
293
|
+
return lists()
|
|
294
|
+
.filter((list) => {
|
|
295
|
+
const matchesSearch = !q
|
|
296
|
+
|| list.name.toLowerCase().includes(q)
|
|
297
|
+
|| list.description.toLowerCase().includes(q)
|
|
298
|
+
|| list.tags.some(t => t.toLowerCase().includes(q))
|
|
299
|
+
const matchesType = type === 'all' || list.type === type
|
|
300
|
+
const matchesStatus = status === 'all' || list.status === status
|
|
301
|
+
return matchesSearch && matchesType && matchesStatus
|
|
302
|
+
})
|
|
303
|
+
.sort((a, b) => {
|
|
304
|
+
let cmp = 0
|
|
305
|
+
if (sort === 'dateCreated')
|
|
306
|
+
cmp = new Date(a.dateCreated).getTime() - new Date(b.dateCreated).getTime()
|
|
307
|
+
else if (sort === 'name')
|
|
308
|
+
cmp = a.name.localeCompare(b.name)
|
|
309
|
+
else if (sort === 'subscriberCount')
|
|
310
|
+
cmp = a.subscriberCount - b.subscriberCount
|
|
311
|
+
else
|
|
312
|
+
cmp = (a.openRate ?? 0) - (b.openRate ?? 0)
|
|
313
|
+
return order === 'asc' ? cmp : -cmp
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
const totalPages = derived(() => Math.max(1, Math.ceil(filteredLists().length / itemsPerPage)))
|
|
318
|
+
|
|
319
|
+
const paginatedLists = derived(() => {
|
|
320
|
+
const start = (currentPage() - 1) * itemsPerPage
|
|
321
|
+
return filteredLists().slice(start, start + itemsPerPage)
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
const allSelected = derived(() => {
|
|
325
|
+
const visible = paginatedLists()
|
|
326
|
+
return visible.length > 0 && visible.every(l => selectedLists().includes(l.id))
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
effect(() => {
|
|
330
|
+
filteredLists()
|
|
331
|
+
currentPage.set(1)
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
effect(() => {
|
|
335
|
+
if (currentPage() > totalPages())
|
|
336
|
+
currentPage.set(totalPages())
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
function formatNumber(n: number) {
|
|
340
|
+
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function formatDate(date: string | null) {
|
|
344
|
+
if (!date)
|
|
345
|
+
return '-'
|
|
346
|
+
return new Date(date).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function formatRate(rate: number | undefined) {
|
|
350
|
+
if (rate === undefined)
|
|
351
|
+
return '-'
|
|
352
|
+
return `${rate.toFixed(1)}%`
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function statusBadgeClass(status: string) {
|
|
356
|
+
const base = 'inline-flex items-center px-2 py-1 font-medium text-xs rounded-full ring-1 ring-inset'
|
|
357
|
+
switch (status) {
|
|
358
|
+
case 'Active': return `${base} bg-green-50 text-green-700 ring-green-600/20 dark:bg-green-900/30 dark:text-green-400`
|
|
359
|
+
case 'Draft': return `${base} bg-yellow-50 text-yellow-700 ring-yellow-600/20 dark:bg-yellow-900/30 dark:text-yellow-400`
|
|
360
|
+
case 'Archived': return `${base} bg-gray-50 text-gray-700 ring-gray-600/20 dark:bg-neutral-900/30 dark:text-neutral-400`
|
|
361
|
+
default: return `${base} bg-gray-50 text-gray-700 ring-gray-600/20 dark:bg-neutral-900/30 dark:text-neutral-400`
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function typeBadgeClass(type: string) {
|
|
366
|
+
const base = 'inline-flex items-center px-2 py-1 font-medium text-xs rounded-full ring-1 ring-inset'
|
|
367
|
+
switch (type) {
|
|
368
|
+
case 'email': return `${base} bg-blue-50 text-blue-700 ring-blue-600/20 dark:bg-blue-900/30 dark:text-blue-400`
|
|
369
|
+
case 'sms': return `${base} bg-purple-50 text-purple-700 ring-purple-600/20 dark:bg-purple-900/30 dark:text-purple-400`
|
|
370
|
+
case 'both': return `${base} bg-indigo-50 text-indigo-700 ring-indigo-600/20 dark:bg-indigo-900/30 dark:text-indigo-400`
|
|
371
|
+
default: return `${base} bg-gray-50 text-gray-700 ring-gray-600/20 dark:bg-neutral-900/30 dark:text-neutral-400`
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function isListSelected(id: number) {
|
|
376
|
+
return selectedLists().includes(id)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function toggleListSelection(id: number) {
|
|
380
|
+
const current = selectedLists()
|
|
381
|
+
selectedLists.set(current.includes(id) ? current.filter(x => x !== id) : [...current, id])
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function toggleSelectAll() {
|
|
385
|
+
selectedLists.set(allSelected() ? [] : paginatedLists().map(l => l.id))
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function toggleListFormIsDefault() {
|
|
389
|
+
listFormIsDefault.set(!listFormIsDefault())
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function nextPage() {
|
|
393
|
+
if (currentPage() < totalPages())
|
|
394
|
+
currentPage.set(currentPage() + 1)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function prevPage() {
|
|
398
|
+
if (currentPage() > 1)
|
|
399
|
+
currentPage.set(currentPage() - 1)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function openAddListModal() {
|
|
403
|
+
editingListId.set(null)
|
|
404
|
+
isEditMode.set(false)
|
|
405
|
+
listFormName.set('')
|
|
406
|
+
listFormDescription.set('')
|
|
407
|
+
listFormTags.set('')
|
|
408
|
+
listFormType.set('email')
|
|
409
|
+
listFormStatus.set('Draft')
|
|
410
|
+
listFormIsDefault.set(false)
|
|
411
|
+
showListModal.set(true)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function openEditListModal(id: number) {
|
|
415
|
+
const list = lists().find(l => l.id === id)
|
|
416
|
+
if (!list)
|
|
417
|
+
return
|
|
418
|
+
editingListId.set(id)
|
|
419
|
+
isEditMode.set(true)
|
|
420
|
+
listFormName.set(list.name)
|
|
421
|
+
listFormDescription.set(list.description)
|
|
422
|
+
listFormTags.set(list.tags.join(', '))
|
|
423
|
+
listFormType.set(list.type)
|
|
424
|
+
listFormStatus.set(list.status)
|
|
425
|
+
listFormIsDefault.set(list.isDefault)
|
|
426
|
+
showListModal.set(true)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function closeListModal() {
|
|
430
|
+
showListModal.set(false)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function saveList() {
|
|
434
|
+
const name = listFormName().trim()
|
|
435
|
+
if (!name)
|
|
436
|
+
return
|
|
437
|
+
const tags = listFormTags().split(',').map(t => t.trim()).filter(t => t.length > 0)
|
|
438
|
+
if (isEditMode()) {
|
|
439
|
+
const id = editingListId()
|
|
440
|
+
let next = lists().map(l => l.id === id
|
|
441
|
+
? { ...l, name, description: listFormDescription(), tags, type: listFormType(), status: listFormStatus(), isDefault: listFormIsDefault() }
|
|
442
|
+
: l)
|
|
443
|
+
if (listFormIsDefault())
|
|
444
|
+
next = next.map(l => l.id === id ? l : { ...l, isDefault: false })
|
|
445
|
+
lists.set(next)
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
const nextId = Math.max(0, ...lists().map(l => l.id)) + 1
|
|
449
|
+
let next = [...lists(), {
|
|
450
|
+
id: nextId,
|
|
451
|
+
name,
|
|
452
|
+
description: listFormDescription(),
|
|
453
|
+
type: listFormType(),
|
|
454
|
+
tags,
|
|
455
|
+
subscriberCount: 0,
|
|
456
|
+
openRate: undefined,
|
|
457
|
+
clickRate: undefined,
|
|
458
|
+
unsubscribeRate: undefined,
|
|
459
|
+
lastSent: null,
|
|
460
|
+
dateCreated: new Date().toISOString().split('T')[0]!,
|
|
461
|
+
status: listFormStatus(),
|
|
462
|
+
isDefault: listFormIsDefault(),
|
|
463
|
+
}]
|
|
464
|
+
if (listFormIsDefault())
|
|
465
|
+
next = next.map(l => l.id === nextId ? l : { ...l, isDefault: false })
|
|
466
|
+
lists.set(next)
|
|
467
|
+
}
|
|
468
|
+
closeListModal()
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function deleteList(id: number) {
|
|
472
|
+
const list = lists().find(l => l.id === id)
|
|
473
|
+
if (list?.isDefault)
|
|
474
|
+
return
|
|
475
|
+
lists.set(lists().filter(l => l.id !== id))
|
|
476
|
+
selectedLists.set(selectedLists().filter(x => x !== id))
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function duplicateList(id: number) {
|
|
480
|
+
const original = lists().find(l => l.id === id)
|
|
481
|
+
if (!original)
|
|
482
|
+
return
|
|
483
|
+
const nextId = Math.max(0, ...lists().map(l => l.id)) + 1
|
|
484
|
+
lists.set([...lists(), {
|
|
485
|
+
...original,
|
|
486
|
+
id: nextId,
|
|
487
|
+
name: `${original.name} (Copy)`,
|
|
488
|
+
subscriberCount: 0,
|
|
489
|
+
lastSent: null,
|
|
490
|
+
dateCreated: new Date().toISOString().split('T')[0]!,
|
|
491
|
+
isDefault: false,
|
|
492
|
+
}])
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function bulkDelete() {
|
|
496
|
+
const ids = selectedLists()
|
|
497
|
+
if (ids.length === 0)
|
|
498
|
+
return
|
|
499
|
+
const selected = lists().filter(l => ids.includes(l.id))
|
|
500
|
+
if (selected.some(l => l.isDefault))
|
|
501
|
+
return
|
|
502
|
+
lists.set(lists().filter(l => !ids.includes(l.id)))
|
|
503
|
+
selectedLists.set([])
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function bulkChangeStatus(status: 'Active' | 'Draft' | 'Archived') {
|
|
507
|
+
const ids = selectedLists()
|
|
508
|
+
if (ids.length === 0)
|
|
509
|
+
return
|
|
510
|
+
lists.set(lists().map(l => ids.includes(l.id) ? { ...l, status } : l))
|
|
511
|
+
}
|
|
512
|
+
</script>
|
|
513
|
+
|
|
514
|
+
@endsection
|