@stacksjs/defaults 0.70.54
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/LICENSE.md +21 -0
- package/README.md +2 -0
- package/package.json +20 -0
- package/resources/assets/fonts/013717e9.woff +0 -0
- package/resources/assets/fonts/03355817.woff2 +0 -0
- package/resources/assets/fonts/0380de7e.woff2 +0 -0
- package/resources/assets/fonts/0ba5d32a.woff +0 -0
- package/resources/assets/fonts/0bd57ddf.woff2 +0 -0
- package/resources/assets/fonts/19de0a60.woff2 +0 -0
- package/resources/assets/fonts/20b87e84.woff +0 -0
- package/resources/assets/fonts/4bbc67e1.woff +0 -0
- package/resources/assets/fonts/576fb0d7.woff +0 -0
- package/resources/assets/fonts/5ea10ee5.woff2 +0 -0
- package/resources/assets/fonts/6413fc0a.woff +0 -0
- package/resources/assets/fonts/72c114cf.woff +0 -0
- package/resources/assets/fonts/73c57e73.woff2 +0 -0
- package/resources/assets/fonts/813dca6c.woff +0 -0
- package/resources/assets/fonts/8a9b0995.woff2 +0 -0
- package/resources/assets/fonts/90b4832f.woff +0 -0
- package/resources/assets/fonts/9394ab85.woff2 +0 -0
- package/resources/assets/fonts/9b3c7d7f.woff2 +0 -0
- package/resources/assets/fonts/Monaco.ttf +0 -0
- package/resources/assets/fonts/b1b36d74.woff +0 -0
- package/resources/assets/fonts/b5d74798.woff2 +0 -0
- package/resources/assets/fonts/b9f81a4d.woff +0 -0
- package/resources/assets/fonts/c9784008.woff2 +0 -0
- package/resources/assets/fonts/cf692227.woff +0 -0
- package/resources/assets/fonts/d1c6ab51.woff2 +0 -0
- package/resources/assets/fonts/e2eb62fc.woff +0 -0
- package/resources/assets/fonts/fe21f8d8.woff2 +0 -0
- package/resources/assets/fonts/nps/CampmateScript-Regular.woff2 +0 -0
- package/resources/assets/fonts/nps/NPS_2026-variable.woff2 +0 -0
- package/resources/assets/fonts/nps/RedwoodSerif-Regular.woff2 +0 -0
- package/resources/assets/fonts/nps/SequoiaSans-Regular.woff2 +0 -0
- package/resources/assets/fonts/nps/Switchback-Regular.woff2 +0 -0
- package/resources/assets/images/app-icon.png +0 -0
- package/resources/assets/images/bg-img.png +0 -0
- package/resources/assets/images/park-ridge.svg +15 -0
- package/resources/assets/images/topography.svg +1 -0
- package/resources/assets/scripts/main.ts +2153 -0
- package/resources/assets/styles/README.md +19 -0
- package/resources/assets/styles/docs/main.css +1 -0
- package/resources/assets/styles/docs/overrides.css +1 -0
- package/resources/assets/styles/docs/vars.css +8 -0
- package/resources/assets/styles/main.css +1419 -0
- package/resources/components/Button.stx +70 -0
- package/resources/components/Buttons/BaseButton.stx +39 -0
- package/resources/components/Buttons/Counter.stx +28 -0
- package/resources/components/Buttons/ToggleDark.stx +11 -0
- package/resources/components/Container.stx +10 -0
- package/resources/components/Dashboard/.gitkeep +0 -0
- package/resources/components/Dashboard/Auth/ForgotPassword.stx +127 -0
- package/resources/components/Dashboard/Auth/Login.stx +212 -0
- package/resources/components/Dashboard/Auth/Register.stx +306 -0
- package/resources/components/Dashboard/Avatar.stx +123 -0
- package/resources/components/Dashboard/Billing/ActivePlan.stx +57 -0
- package/resources/components/Dashboard/Billing/CardBrands.stx +32 -0
- package/resources/components/Dashboard/Billing/CardForm.stx +34 -0
- package/resources/components/Dashboard/Billing/OneTimePayment.stx +28 -0
- package/resources/components/Dashboard/Billing/PaymentForm.stx +33 -0
- package/resources/components/Dashboard/Billing/PaymentMethod.stx +66 -0
- package/resources/components/Dashboard/Billing/PaymentMethodList.stx +52 -0
- package/resources/components/Dashboard/Billing/Plans.stx +136 -0
- package/resources/components/Dashboard/Buttons/AppButton.stx +48 -0
- package/resources/components/Dashboard/Buttons/IconButton.stx +20 -0
- package/resources/components/Dashboard/Card.stx +78 -0
- package/resources/components/Dashboard/Chart.stx +75 -0
- package/resources/components/Dashboard/CodeEditor.stx +41 -0
- package/resources/components/Dashboard/Commerce/BulkActionBar.stx +70 -0
- package/resources/components/Dashboard/Commerce/CategoriesTable.stx +133 -0
- package/resources/components/Dashboard/Commerce/CouponsTable.stx +161 -0
- package/resources/components/Dashboard/Commerce/CustomersTable.stx +142 -0
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesTable.stx +80 -0
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryTable.stx +85 -0
- package/resources/components/Dashboard/Commerce/Delivery/DriversTable.stx +99 -0
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeysTable.stx +92 -0
- package/resources/components/Dashboard/Commerce/Delivery/LicenseTemplatesTable.stx +88 -0
- package/resources/components/Dashboard/Commerce/Delivery/Pagination.stx +122 -0
- package/resources/components/Dashboard/Commerce/Delivery/SearchFilter.stx +23 -0
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodsTable.stx +80 -0
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRatesTable.stx +59 -0
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZonesTable.stx +71 -0
- package/resources/components/Dashboard/Commerce/Delivery/TabNavigation.stx +36 -0
- package/resources/components/Dashboard/Commerce/Forms/CouponsForm.stx +191 -0
- package/resources/components/Dashboard/Commerce/OrdersTable.stx +131 -0
- package/resources/components/Dashboard/Commerce/ProductsTable.stx +170 -0
- package/resources/components/Dashboard/CreateRecordModal.stx +363 -0
- package/resources/components/Dashboard/DashboardLayout.stx +155 -0
- package/resources/components/Dashboard/Deployments/ActivityFeed.stx +54 -0
- package/resources/components/Dashboard/Deployments/DeployScript.stx +23 -0
- package/resources/components/Dashboard/Deployments/DeploymentHistory.stx +97 -0
- package/resources/components/Dashboard/Deployments/DeploymentList.stx +94 -0
- package/resources/components/Dashboard/Deployments/LiveTerminalOutput.stx +18 -0
- package/resources/components/Dashboard/Elements/Dropdown.stx +57 -0
- package/resources/components/Dashboard/Email/EmailActivityChart.stx +111 -0
- package/resources/components/Dashboard/Email/EmailCompose.stx +72 -0
- package/resources/components/Dashboard/Email/EmailDetail.stx +105 -0
- package/resources/components/Dashboard/Email/EmailList.stx +117 -0
- package/resources/components/Dashboard/Email/EmailSearch.stx +40 -0
- package/resources/components/Dashboard/Email/EmailSidebar.stx +133 -0
- package/resources/components/Dashboard/GlobalSearch.stx +124 -0
- package/resources/components/Dashboard/MobileSidebar.stx +47 -0
- package/resources/components/Dashboard/Modals/BaseModal.stx +50 -0
- package/resources/components/Dashboard/Modals/Popups/Alert.stx +112 -0
- package/resources/components/Dashboard/Modals/Popups/Toast.stx +70 -0
- package/resources/components/Dashboard/Modals/ToastWrapper.stx +35 -0
- package/resources/components/Dashboard/Monitoring/ErrorDetailModal.stx +228 -0
- package/resources/components/Dashboard/Monitoring/ErrorsTable.stx +184 -0
- package/resources/components/Dashboard/Navbar.stx +194 -0
- package/resources/components/Dashboard/NavbarModern.stx +188 -0
- package/resources/components/Dashboard/NotificationErrorModal.stx +105 -0
- package/resources/components/Dashboard/NotificationStatusBadge.stx +20 -0
- package/resources/components/Dashboard/Pagination.stx +90 -0
- package/resources/components/Dashboard/Queue/QueueTable.stx +127 -0
- package/resources/components/Dashboard/Settings/Forms/AIForm.stx +126 -0
- package/resources/components/Dashboard/Settings/Forms/AnalyticsForm.stx +52 -0
- package/resources/components/Dashboard/Settings/Forms/AppForm.stx +157 -0
- package/resources/components/Dashboard/Settings/Forms/CLIForm.stx +66 -0
- package/resources/components/Dashboard/Settings/Forms/CacheForm.stx +253 -0
- package/resources/components/Dashboard/Settings/Forms/CloudForm.stx +441 -0
- package/resources/components/Dashboard/Settings/Forms/DNSForm.stx +307 -0
- package/resources/components/Dashboard/Settings/Forms/DatabaseForm.stx +168 -0
- package/resources/components/Dashboard/Settings/Forms/EmailForm.stx +100 -0
- package/resources/components/Dashboard/Settings/Forms/FileSystemForm.stx +259 -0
- package/resources/components/Dashboard/Settings/Forms/HashingForm.stx +119 -0
- package/resources/components/Dashboard/Settings/Forms/LibraryForm.stx +389 -0
- package/resources/components/Dashboard/Settings/Forms/QueueForm.stx +219 -0
- package/resources/components/Dashboard/Settings/Forms/SearchEngineForm.stx +48 -0
- package/resources/components/Dashboard/Settings/Forms/SecurityForm.stx +143 -0
- package/resources/components/Dashboard/Settings/Forms/ServicesForm.stx +194 -0
- package/resources/components/Dashboard/Settings/Forms/StorageForm.stx +191 -0
- package/resources/components/Dashboard/Settings/Forms/TeamsForm.stx +33 -0
- package/resources/components/Dashboard/Settings/SettingsFormManager.stx +181 -0
- package/resources/components/Dashboard/SettingsHeader.stx +88 -0
- package/resources/components/Dashboard/SettingsSidebar.stx +234 -0
- package/resources/components/Dashboard/Sidebar.stx +23 -0
- package/resources/components/Dashboard/SidebarModern.stx +249 -0
- package/resources/components/Dashboard/Skeleton/LoadingCard.stx +15 -0
- package/resources/components/Dashboard/Skeleton/LoadingDetails.stx +26 -0
- package/resources/components/Dashboard/Table.stx +82 -0
- package/resources/components/Dashboard/TerminalContainer.stx +97 -0
- package/resources/components/Dashboard/Tooltip.stx +166 -0
- package/resources/components/Dashboard/Transaction/index.stx +88 -0
- package/resources/components/Dashboard/UI/ActivityTable.stx +54 -0
- package/resources/components/Dashboard/UI/Avatar.stx +102 -0
- package/resources/components/Dashboard/UI/Badge.stx +66 -0
- package/resources/components/Dashboard/UI/Button.stx +73 -0
- package/resources/components/Dashboard/UI/Card.stx +51 -0
- package/resources/components/Dashboard/UI/ChartCard.stx +36 -0
- package/resources/components/Dashboard/UI/Checkbox.stx +74 -0
- package/resources/components/Dashboard/UI/ConfirmDialog.stx +74 -0
- package/resources/components/Dashboard/UI/DataTable.stx +45 -0
- package/resources/components/Dashboard/UI/Dropdown.stx +72 -0
- package/resources/components/Dashboard/UI/DropdownItem.stx +80 -0
- package/resources/components/Dashboard/UI/EmptyState.stx +50 -0
- package/resources/components/Dashboard/UI/FilterBar.stx +48 -0
- package/resources/components/Dashboard/UI/Input.stx +77 -0
- package/resources/components/Dashboard/UI/Modal.stx +75 -0
- package/resources/components/Dashboard/UI/PageHeader.stx +69 -0
- package/resources/components/Dashboard/UI/PageLayout.stx +84 -0
- package/resources/components/Dashboard/UI/Pagination.stx +126 -0
- package/resources/components/Dashboard/UI/QuickLinks.stx +31 -0
- package/resources/components/Dashboard/UI/RouterLink.stx +31 -0
- package/resources/components/Dashboard/UI/Select.stx +84 -0
- package/resources/components/Dashboard/UI/ServiceHealth.stx +38 -0
- package/resources/components/Dashboard/UI/StatsCard.stx +79 -0
- package/resources/components/Dashboard/UI/Table.stx +143 -0
- package/resources/components/Dashboard/UI/Tabs.stx +45 -0
- package/resources/components/Dashboard/UI/Textarea.stx +93 -0
- package/resources/components/Dashboard/UI/Toggle.stx +84 -0
- package/resources/components/Dashboard/UI/WindowControls.stx +135 -0
- package/resources/components/Dashboard/Widget.stx +89 -0
- package/resources/components/Dashboard/WindowControls.stx +119 -0
- package/resources/components/Dashboard/index.ts +106 -0
- package/resources/components/Docs/Demo/ComboboxCode.md +114 -0
- package/resources/components/Docs/Demo/ComboboxDemo.stx +91 -0
- package/resources/components/Docs/Demo/DialogCode.md +42 -0
- package/resources/components/Docs/Demo/DialogDemo.stx +76 -0
- package/resources/components/Docs/Demo/DocsPlayground.stx +39 -0
- package/resources/components/Docs/Demo/DropdownCode.md +109 -0
- package/resources/components/Docs/Demo/DropdownDemo.stx +82 -0
- package/resources/components/Docs/Demo/Hero.stx +100 -0
- package/resources/components/Docs/Demo/ListboxCode.md +87 -0
- package/resources/components/Docs/Demo/ListboxDemo.stx +59 -0
- package/resources/components/Docs/Demo/NotificationCode.md +173 -0
- package/resources/components/Docs/Demo/NotificationDemo.stx +99 -0
- package/resources/components/Docs/Demo/PopoverCode.md +161 -0
- package/resources/components/Docs/Demo/PopoverDemo.stx +96 -0
- package/resources/components/Docs/Demo/RadioGroupCode.md +98 -0
- package/resources/components/Docs/Demo/RadioGroupDemo.stx +53 -0
- package/resources/components/Docs/Demo/StepperCode.md +216 -0
- package/resources/components/Docs/Demo/StepperDemo.stx +137 -0
- package/resources/components/Docs/Demo/SwitchCode.md +24 -0
- package/resources/components/Docs/Demo/SwitchDemo.stx +28 -0
- package/resources/components/Docs/Home.stx +7 -0
- package/resources/components/Docs/HomeContributors.stx +23 -0
- package/resources/components/Docs/HomeSponsors.stx +17 -0
- package/resources/components/Docs/HomeTeam.stx +18 -0
- package/resources/components/Docs/TeamMember.stx +79 -0
- package/resources/components/Email/EmailButton.stx +32 -0
- package/resources/components/Email/EmailDivider.stx +20 -0
- package/resources/components/Email/EmailImage.stx +24 -0
- package/resources/components/Email/EmailLayout.stx +55 -0
- package/resources/components/Email/EmailLink.stx +22 -0
- package/resources/components/Email/EmailSection.stx +20 -0
- package/resources/components/Email/EmailText.stx +30 -0
- package/resources/components/Forum/ForumBreadcrumb.stx +38 -0
- package/resources/components/Forum/ForumCategory.stx +16 -0
- package/resources/components/Forum/ForumCategoryItem.stx +46 -0
- package/resources/components/Forum/ForumLayout.stx +36 -0
- package/resources/components/Forum/ForumPagination.stx +39 -0
- package/resources/components/Forum/ForumPost.stx +71 -0
- package/resources/components/Forum/ForumReplyForm.stx +76 -0
- package/resources/components/Forum/ForumSidebar.stx +86 -0
- package/resources/components/Forum/ForumTopicItem.stx +77 -0
- package/resources/components/Forum/ForumTopicList.stx +23 -0
- package/resources/components/HelloWorld.stx +253 -0
- package/resources/components/Logo.stx +10 -0
- package/resources/components/Marketing/CallToAction.stx +30 -0
- package/resources/components/Marketing/ComingSoon.stx +155 -0
- package/resources/components/Marketing/Faqs.stx +99 -0
- package/resources/components/Marketing/Feature.stx +32 -0
- package/resources/components/Marketing/Fields.stx +28 -0
- package/resources/components/Marketing/Footer.stx +33 -0
- package/resources/components/Marketing/Header.stx +51 -0
- package/resources/components/Marketing/Hero.stx +46 -0
- package/resources/components/Marketing/Layout.stx +26 -0
- package/resources/components/Marketing/NavLink.stx +13 -0
- package/resources/components/Marketing/Plan.stx +43 -0
- package/resources/components/Marketing/Pricing.stx +120 -0
- package/resources/components/Marketing/PrimaryFeatures.stx +80 -0
- package/resources/components/Marketing/PrimaryFeatures2.stx +98 -0
- package/resources/components/Marketing/SecondaryFeatures.stx +80 -0
- package/resources/components/Marketing/Testimonials.stx +120 -0
- package/resources/components/Pagination.stx +149 -0
- package/resources/components/Playground.stx +9 -0
- package/resources/components/README.md +87 -0
- package/resources/components/Storefront/AnnouncementBar.stx +17 -0
- package/resources/components/Storefront/CartDrawer.stx +227 -0
- package/resources/components/Storefront/CheckoutSteps.stx +50 -0
- package/resources/components/Storefront/Footer.stx +81 -0
- package/resources/components/Storefront/Header.stx +66 -0
- package/resources/components/Storefront/OrderSummary.stx +79 -0
- package/resources/components/Storefront/ProductCard.stx +63 -0
- package/resources/components/Storefront/ProductGallery.stx +88 -0
- package/resources/components/Storefront/QuantitySelector.stx +97 -0
- package/resources/components/index.ts +13 -0
- package/resources/emails/email-verification.stx +46 -0
- package/resources/emails/layouts/base.html +113 -0
- package/resources/emails/password-changed.stx +55 -0
- package/resources/emails/password-reset.stx +43 -0
- package/resources/emails/subscription-confirmation.stx +99 -0
- package/resources/emails/welcome.stx +48 -0
- package/resources/functions/README.md +28 -0
- package/resources/functions/counter.ts +13 -0
- package/resources/functions/dark.ts +4 -0
- package/resources/functions/dashboard/analytics.ts +238 -0
- package/resources/functions/dashboard/config-io.ts +316 -0
- package/resources/functions/dashboard/data.ts +375 -0
- package/resources/functions/dashboard/library.ts +250 -0
- package/resources/functions/dashboard/sidebar.ts +537 -0
- package/resources/functions/dashboard/system.ts +150 -0
- package/resources/layouts/403.stx +188 -0
- package/resources/layouts/404.stx +34 -0
- package/resources/layouts/README.md +14 -0
- package/resources/layouts/dashboard/default.stx +20 -0
- package/resources/layouts/dashboard/guest.stx +10 -0
- package/resources/layouts/default.stx +3 -0
- package/resources/layouts/maintenance.stx +11 -0
- package/resources/layouts/storefront.stx +73 -0
- package/resources/layouts/system-tray/default.stx +3 -0
- package/resources/modules/.gitkeep +0 -0
- package/resources/plugins/.gitkeep +0 -0
- package/resources/plugins/preloader.ts +308 -0
- package/resources/types/README.md +3 -0
- package/resources/views/cart.stx +149 -0
- package/resources/views/checkout/contact.stx +101 -0
- package/resources/views/checkout/payment.stx +114 -0
- package/resources/views/checkout/shipping.stx +120 -0
- package/resources/views/coming-soon.stx +9 -0
- package/resources/views/dashboard/custom-page.stx +45 -0
- package/resources/views/dashboard/index.stx +24 -0
- package/resources/views/emails/welcome.stx +0 -0
- package/resources/views/errors/tester.stx +26 -0
- package/resources/views/index.stx +2865 -0
- package/resources/views/orders/[id].stx +130 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main Preloader
|
|
3
|
+
*
|
|
4
|
+
* This file is loaded before the main (Bun) process is started.
|
|
5
|
+
* You may use this file to preload/define plugins that will
|
|
6
|
+
* automatically be injected into the Bun process.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Skip preloader for fast CLI commands (e.g. `buddy dev`, `buddy --version`) to
|
|
10
|
+
// maximize startup speed. We must NOT skip when running a server script directly
|
|
11
|
+
// (e.g. `bun --watch storage/framework/core/actions/src/dev/api.ts`) — Bun
|
|
12
|
+
// consumes `--watch` so `args` ends up empty in that subprocess, which used to
|
|
13
|
+
// trip the `args.length === 0` short-circuit and silently disable auto-imports.
|
|
14
|
+
const args = process.argv.slice(2)
|
|
15
|
+
const fastCommands = [
|
|
16
|
+
'dev',
|
|
17
|
+
'build',
|
|
18
|
+
'test',
|
|
19
|
+
'lint',
|
|
20
|
+
'--version',
|
|
21
|
+
'-v',
|
|
22
|
+
'version',
|
|
23
|
+
'--help',
|
|
24
|
+
'-h',
|
|
25
|
+
'help',
|
|
26
|
+
// Database / codegen — must not pull the full auto-import graph (router,
|
|
27
|
+
// orm models, …) before bun-query-builder can diff schemas. A broken
|
|
28
|
+
// `@stacksjs/bun-router` install used to make `generate:migrations` exit 1
|
|
29
|
+
// with no output because the preloader died while loading `@stacksjs/router`.
|
|
30
|
+
'migrate',
|
|
31
|
+
'fresh',
|
|
32
|
+
'seed',
|
|
33
|
+
'generate',
|
|
34
|
+
'make',
|
|
35
|
+
'key:generate',
|
|
36
|
+
'scaffold:crud',
|
|
37
|
+
]
|
|
38
|
+
const isRepl = !process.argv[1]
|
|
39
|
+
const skipPreloader = isRepl || (args.length > 0 && fastCommands.some(cmd => args[0] === cmd || args[0].startsWith(`${cmd}:`)))
|
|
40
|
+
|
|
41
|
+
if (!skipPreloader) {
|
|
42
|
+
// Detect production/deployment commands and set environment accordingly BEFORE loading env files
|
|
43
|
+
// This ensures the correct .env.{env} file is loaded with proper encryption/decryption
|
|
44
|
+
const productionCommands = ['cloud:remove', 'cloud:rm', 'cloud:destroy', 'cloud:cleanup', 'cloud:clean-up', 'undeploy']
|
|
45
|
+
const isProductionCommand = productionCommands.includes(args[0])
|
|
46
|
+
|
|
47
|
+
// Handle deploy command which can have an optional env argument: `deploy [env]`
|
|
48
|
+
const isDeployCommand = args[0] === 'deploy'
|
|
49
|
+
if (isDeployCommand) {
|
|
50
|
+
// Check if second arg is an environment (not a flag starting with -)
|
|
51
|
+
const deployEnv = args[1] && !args[1].startsWith('-') ? args[1] : 'production'
|
|
52
|
+
process.env.APP_ENV = deployEnv
|
|
53
|
+
process.env.NODE_ENV = deployEnv
|
|
54
|
+
}
|
|
55
|
+
else if (isProductionCommand) {
|
|
56
|
+
process.env.APP_ENV = 'production'
|
|
57
|
+
process.env.NODE_ENV = 'production'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Load .env files with encryption support using our native Bun plugin
|
|
61
|
+
const { autoLoadEnv } = await import('@stacksjs/env')
|
|
62
|
+
|
|
63
|
+
// Auto-load .env files based on environment
|
|
64
|
+
// Set quiet: true to prevent duplicate logging across multiple processes
|
|
65
|
+
//
|
|
66
|
+
// keysFile MUST be passed here: autoLoadEnv only resolves a decryption
|
|
67
|
+
// private key from a keys FILE when explicitly told which one to read —
|
|
68
|
+
// without it, an encrypted .env.production (DOTENV_PUBLIC_KEY_PRODUCTION
|
|
69
|
+
// + `encrypted:...` values) loads with every encrypted value left as raw
|
|
70
|
+
// ciphertext in process.env, silently breaking anything that reads
|
|
71
|
+
// process.env.SOME_SECRET directly (e.g. deploy-time credentials like
|
|
72
|
+
// HCLOUD_TOKEN/PORKBUN_API_KEY) with no error — it just looks like a
|
|
73
|
+
// bogus/expired credential downstream.
|
|
74
|
+
autoLoadEnv({ quiet: true, keysFile: '.env.keys' })
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// stx template engine plugin
|
|
78
|
+
// enables .stx file processing
|
|
79
|
+
// NOTE: Commented out until bun-plugin-stx is properly installed
|
|
80
|
+
// Uncomment after running: bun add bun-plugin-stx
|
|
81
|
+
// eslint-disable-next-line antfu/no-top-level-await
|
|
82
|
+
// await import('bun-plugin-stx')
|
|
83
|
+
|
|
84
|
+
// Auto-import ALL Stacks framework modules into globalThis
|
|
85
|
+
// This allows using Action, response, Activity, etc. without ANY imports.
|
|
86
|
+
// Exported so server entrypoints (e.g. `dev/api.ts`) can opt back in
|
|
87
|
+
// explicitly when needed — see #1835 root cause 3.
|
|
88
|
+
export async function loadAutoImports() {
|
|
89
|
+
const { Glob } = await import('bun')
|
|
90
|
+
const path = await import('@stacksjs/path')
|
|
91
|
+
|
|
92
|
+
// CRITICAL: Never overwrite these built-in globals
|
|
93
|
+
const protectedGlobals = new Set([
|
|
94
|
+
'process', 'globalThis', 'global', 'window', 'self',
|
|
95
|
+
'console', 'require', 'module', 'exports', '__dirname', '__filename',
|
|
96
|
+
'Buffer', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
|
|
97
|
+
'setImmediate', 'clearImmediate', 'queueMicrotask',
|
|
98
|
+
'fetch', 'Request', 'Response', 'Headers', 'URL', 'URLSearchParams',
|
|
99
|
+
'TextEncoder', 'TextDecoder', 'Blob', 'File', 'FormData',
|
|
100
|
+
'crypto', 'performance', 'navigator', 'location',
|
|
101
|
+
'Promise', 'Symbol', 'Proxy', 'Reflect', 'WeakMap', 'WeakSet', 'Map', 'Set',
|
|
102
|
+
'Array', 'Object', 'String', 'Number', 'Boolean', 'Date', 'RegExp', 'Error',
|
|
103
|
+
'JSON', 'Math', 'Intl', 'eval', 'isNaN', 'isFinite', 'parseInt', 'parseFloat',
|
|
104
|
+
'encodeURI', 'encodeURIComponent', 'decodeURI', 'decodeURIComponent',
|
|
105
|
+
'Bun', 'Deno', 'Node',
|
|
106
|
+
])
|
|
107
|
+
|
|
108
|
+
// 1. Load Stacks framework packages into globalThis
|
|
109
|
+
const stacksPackages = [
|
|
110
|
+
'@stacksjs/actions',
|
|
111
|
+
'@stacksjs/router',
|
|
112
|
+
'@stacksjs/orm',
|
|
113
|
+
'@stacksjs/validation',
|
|
114
|
+
'@stacksjs/strings',
|
|
115
|
+
'@stacksjs/arrays',
|
|
116
|
+
'@stacksjs/objects',
|
|
117
|
+
'@stacksjs/collections',
|
|
118
|
+
'@stacksjs/path',
|
|
119
|
+
'@stacksjs/storage',
|
|
120
|
+
'@stacksjs/env',
|
|
121
|
+
'@stacksjs/config',
|
|
122
|
+
'@stacksjs/logging',
|
|
123
|
+
'@stacksjs/cache',
|
|
124
|
+
'@stacksjs/queue',
|
|
125
|
+
'@stacksjs/events',
|
|
126
|
+
'@stacksjs/notifications',
|
|
127
|
+
'@stacksjs/email',
|
|
128
|
+
'@stacksjs/security',
|
|
129
|
+
'@stacksjs/auth',
|
|
130
|
+
'@stacksjs/database',
|
|
131
|
+
'@stacksjs/error-handling',
|
|
132
|
+
// Signal helpers (`state`, `derived`, `effect`) and browser-side
|
|
133
|
+
// composables (`useDark`, `usePreferredDark`, `useStorage`) need to
|
|
134
|
+
// land in globalThis before step 2 imports any file under
|
|
135
|
+
// resources/functions/ — counter.ts and dark.ts call those at
|
|
136
|
+
// module top level, so missing globals throw and leave the module
|
|
137
|
+
// in TDZ, surfacing later as `Cannot access 'count' before
|
|
138
|
+
// initialization` when the auto-imports barrel re-imports them.
|
|
139
|
+
'@stacksjs/stx',
|
|
140
|
+
'@stacksjs/browser',
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
for (const pkg of stacksPackages) {
|
|
144
|
+
try {
|
|
145
|
+
const module = await import(pkg)
|
|
146
|
+
for (const [name, value] of Object.entries(module)) {
|
|
147
|
+
// Skip default exports and protected globals
|
|
148
|
+
if (name === 'default' || protectedGlobals.has(name)) continue
|
|
149
|
+
if (typeof value !== 'undefined') {
|
|
150
|
+
(globalThis as any)[name] = value
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
// Package might not exist or not be built yet
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 2. Load all functions from resources/functions
|
|
159
|
+
const functionsPath = path.resourcesPath('functions')
|
|
160
|
+
const glob = new Glob('**/*.ts')
|
|
161
|
+
|
|
162
|
+
for await (const file of glob.scan({
|
|
163
|
+
cwd: functionsPath,
|
|
164
|
+
absolute: true,
|
|
165
|
+
onlyFiles: true,
|
|
166
|
+
})) {
|
|
167
|
+
if (file.endsWith('.d.ts')) continue
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const module = await import(file)
|
|
171
|
+
for (const [name, value] of Object.entries(module)) {
|
|
172
|
+
// Skip default exports and protected globals
|
|
173
|
+
if (name === 'default' || protectedGlobals.has(name)) continue
|
|
174
|
+
if (typeof value !== 'undefined') {
|
|
175
|
+
(globalThis as any)[name] = value
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
// Some files may have client-side dependencies, skip them
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 3. Load all defineModel()-based model instances into globalThis
|
|
184
|
+
// This enables using Post.where('title', 'test') without imports
|
|
185
|
+
// Priority: user models (app/Models) override framework defaults
|
|
186
|
+
const modelDirs = [
|
|
187
|
+
path.userModelsPath(),
|
|
188
|
+
path.storagePath('framework/defaults/app/Models'),
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
const loadedModels = new Set<string>()
|
|
192
|
+
|
|
193
|
+
for (const dir of modelDirs) {
|
|
194
|
+
try {
|
|
195
|
+
for await (const file of glob.scan({
|
|
196
|
+
cwd: dir,
|
|
197
|
+
absolute: true,
|
|
198
|
+
onlyFiles: true,
|
|
199
|
+
})) {
|
|
200
|
+
if (file.endsWith('.d.ts') || file.endsWith('/index.ts')) continue
|
|
201
|
+
|
|
202
|
+
const modelName = file.split('/').pop()?.replace('.ts', '') || ''
|
|
203
|
+
if (!modelName || loadedModels.has(modelName) || protectedGlobals.has(modelName)) continue
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
const module = await import(file)
|
|
207
|
+
if (module.default) {
|
|
208
|
+
(globalThis as any)[modelName] = module.default
|
|
209
|
+
loadedModels.add(modelName)
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
// Model may have unresolved dependencies during bootstrap
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} catch {
|
|
216
|
+
// Directory may not exist
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 4. Load all Job instances from app/Jobs into globalThis
|
|
221
|
+
// This enables using SendWelcomeEmail.dispatch() without imports
|
|
222
|
+
const jobsDir = path.userJobsPath()
|
|
223
|
+
const loadedJobs = new Set<string>()
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
for await (const file of glob.scan({
|
|
227
|
+
cwd: jobsDir,
|
|
228
|
+
absolute: true,
|
|
229
|
+
onlyFiles: true,
|
|
230
|
+
})) {
|
|
231
|
+
if (file.endsWith('.d.ts') || file.endsWith('/index.ts')) continue
|
|
232
|
+
|
|
233
|
+
const jobName = file.split('/').pop()?.replace('.ts', '') || ''
|
|
234
|
+
if (!jobName || loadedJobs.has(jobName) || protectedGlobals.has(jobName)) continue
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const module = await import(file)
|
|
238
|
+
if (module.default) {
|
|
239
|
+
(globalThis as any)[jobName] = module.default
|
|
240
|
+
loadedJobs.add(jobName)
|
|
241
|
+
}
|
|
242
|
+
} catch {
|
|
243
|
+
// Job may have unresolved dependencies during bootstrap
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
247
|
+
// Directory may not exist
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 5. Load all Controller classes into globalThis
|
|
251
|
+
// This enables using ComingSoonController without imports
|
|
252
|
+
// Priority: user controllers > default controllers
|
|
253
|
+
const controllerDirs = [
|
|
254
|
+
path.userControllersPath(),
|
|
255
|
+
path.storagePath('framework/defaults/app/Controllers'),
|
|
256
|
+
]
|
|
257
|
+
const loadedControllers = new Set<string>()
|
|
258
|
+
|
|
259
|
+
for (const dir of controllerDirs) {
|
|
260
|
+
try {
|
|
261
|
+
for await (const file of glob.scan({
|
|
262
|
+
cwd: dir,
|
|
263
|
+
absolute: true,
|
|
264
|
+
onlyFiles: true,
|
|
265
|
+
})) {
|
|
266
|
+
if (file.endsWith('.d.ts') || file.endsWith('/index.ts')) continue
|
|
267
|
+
|
|
268
|
+
const controllerName = file.split('/').pop()?.replace('.ts', '') || ''
|
|
269
|
+
if (!controllerName || loadedControllers.has(controllerName) || protectedGlobals.has(controllerName)) continue
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
const module = await import(file)
|
|
273
|
+
if (module.default) {
|
|
274
|
+
(globalThis as any)[controllerName] = module.default
|
|
275
|
+
loadedControllers.add(controllerName)
|
|
276
|
+
}
|
|
277
|
+
} catch {
|
|
278
|
+
// Controller may have unresolved dependencies during bootstrap
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
} catch {
|
|
282
|
+
// Directory may not exist
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Load auto-imports for server/API contexts (serve, any server process)
|
|
288
|
+
// Skip for fast commands (dev, build, test, etc.) and CLI info commands.
|
|
289
|
+
// `args[0]` may be undefined when running a script directly (e.g. dev subprocess);
|
|
290
|
+
// guard so .includes() doesn't accidentally treat that as a CLI info command.
|
|
291
|
+
// `./buddy dev` server subprocesses set STACKS_DEV_SERVER=1. They load only
|
|
292
|
+
// what they need themselves — the API injects globals via injectGlobalAutoImports
|
|
293
|
+
// and reads the cached package-discovery manifest, while the frontend/docs
|
|
294
|
+
// servers never touch models — so the preloader's eager ~800ms auto-import +
|
|
295
|
+
// discoverPackages pass is redundant work on the critical boot path. Skip it.
|
|
296
|
+
const skipAutoImports = skipPreloader || process.env.STACKS_DEV_SERVER === '1' || (args.length > 0 && ['--version', '-v', 'version', '--help', '-h', 'help'].includes(args[0]))
|
|
297
|
+
if (!skipAutoImports) {
|
|
298
|
+
await loadAutoImports()
|
|
299
|
+
|
|
300
|
+
// Run package auto-discovery after all imports are loaded
|
|
301
|
+
try {
|
|
302
|
+
const { discoverPackages } = await import('@stacksjs/actions')
|
|
303
|
+
await discoverPackages()
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
// Discovery may fail during early bootstrap — not critical
|
|
307
|
+
}
|
|
308
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
@extends('layouts/storefront')
|
|
2
|
+
|
|
3
|
+
@section('title', 'Your cart')
|
|
4
|
+
|
|
5
|
+
<script server>
|
|
6
|
+
const Database = require('bun:sqlite').Database
|
|
7
|
+
const path = require('path')
|
|
8
|
+
|
|
9
|
+
const dbPath = path.resolve(process.cwd(), process.env.DB_DATABASE_PATH || 'database/stacks.sqlite')
|
|
10
|
+
const db = new Database(dbPath, { readonly: true })
|
|
11
|
+
|
|
12
|
+
const token = (typeof requestContext !== 'undefined') ? requestContext.cookie('stacks_cart') : null
|
|
13
|
+
let items = []
|
|
14
|
+
let cart = null
|
|
15
|
+
|
|
16
|
+
if (token) {
|
|
17
|
+
cart = db.query(`SELECT * FROM carts WHERE session_token = ?1 AND status = 'active' LIMIT 1`).get(token)
|
|
18
|
+
if (cart) {
|
|
19
|
+
items = db.query(`
|
|
20
|
+
SELECT ci.id, ci.quantity, ci.unit_price, ci.total_price, ci.product_name,
|
|
21
|
+
ci.product_image, ci.product_sku, p.weight_grams
|
|
22
|
+
FROM cart_items ci
|
|
23
|
+
LEFT JOIN products p ON p.slug = ci.product_sku
|
|
24
|
+
WHERE ci.cart_id = ?1
|
|
25
|
+
ORDER BY ci.id ASC
|
|
26
|
+
`).all(cart.id).map((row) => {
|
|
27
|
+
const qty = Number(row.quantity)
|
|
28
|
+
const price = Number(row.unit_price)
|
|
29
|
+
const lineTotal = Number(row.total_price)
|
|
30
|
+
return {
|
|
31
|
+
id: row.id,
|
|
32
|
+
slug: row.product_sku,
|
|
33
|
+
name: row.product_name,
|
|
34
|
+
weight: row.weight_grams ? `${row.weight_grams}g` : '',
|
|
35
|
+
qty,
|
|
36
|
+
priceLabel: `$${price.toFixed(2)}`,
|
|
37
|
+
lineTotal,
|
|
38
|
+
lineTotalLabel: `$${lineTotal.toFixed(2)}`,
|
|
39
|
+
image: row.product_image,
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Mirror the rule used by PlaceOrderAction so the price the shopper
|
|
46
|
+
// sees in their cart matches the total they're actually charged. Both
|
|
47
|
+
// sides read STOREFRONT_FREE_SHIPPING_THRESHOLD / STOREFRONT_FLAT_SHIPPING
|
|
48
|
+
// from the environment with the same defaults — keep them in sync via
|
|
49
|
+
// app/Actions/Storefront/_shipping.ts when changing.
|
|
50
|
+
const FREE_SHIPPING_THRESHOLD = Number(process.env.STOREFRONT_FREE_SHIPPING_THRESHOLD ?? 40) || 40
|
|
51
|
+
const FLAT_SHIPPING = Number(process.env.STOREFRONT_FLAT_SHIPPING ?? 5) || 5
|
|
52
|
+
const subtotal = items.reduce((sum, i) => sum + i.lineTotal, 0)
|
|
53
|
+
const shipping = subtotal <= 0 ? 0 : (subtotal >= FREE_SHIPPING_THRESHOLD ? 0 : FLAT_SHIPPING)
|
|
54
|
+
const total = subtotal + shipping
|
|
55
|
+
const remainingForFree = Math.max(0, FREE_SHIPPING_THRESHOLD - subtotal)
|
|
56
|
+
|
|
57
|
+
// Pre-format every numeric so the OrderSummary component template doesn't
|
|
58
|
+
// have to call .toFixed() on a defineProps local — STX's SSR pass doesn't
|
|
59
|
+
// reliably expose those locals, so callers do the formatting.
|
|
60
|
+
const subtotalLabel = `$${subtotal.toFixed(2)}`
|
|
61
|
+
const shippingLabel = shipping === 0 ? 'Free' : `$${shipping.toFixed(2)}`
|
|
62
|
+
const totalLabel = `$${total.toFixed(2)}`
|
|
63
|
+
const freeShippingNote = (shipping > 0 && remainingForFree > 0)
|
|
64
|
+
? `Add $${remainingForFree.toFixed(2)} more for free shipping.`
|
|
65
|
+
: ''
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
@section('content')
|
|
69
|
+
<div class="mx-auto px-6 py-12 max-w-5xl">
|
|
70
|
+
|
|
71
|
+
<p class="font-medium text-stone-500 text-xs tracking-wider uppercase">Your bowl</p>
|
|
72
|
+
<h1 class="mt-2 font-bold text-4xl text-stone-900 tracking-tight">Cart</h1>
|
|
73
|
+
<p class="mt-2 text-stone-600">{{ items.length }} item{{ items.length === 1 ? '' : 's' }}{{ items.length > 0 ? ' ready to ship' : '' }}.</p>
|
|
74
|
+
|
|
75
|
+
@if (items.length === 0)
|
|
76
|
+
<div class="mt-12 p-12 md:p-16 text-center bg-stone-50 border border-dashed border-stone-300 rounded-3xl">
|
|
77
|
+
<div class="inline-flex items-center justify-center mx-auto h-14 w-14 bg-stone-100 rounded-full">
|
|
78
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7 text-stone-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 00-3 3h15.75m-12.75-3h11.218c.51 0 .956-.343 1.087-.835l1.872-7.024A1.125 1.125 0 0019.797 5.25H5.106M7.5 14.25L5.106 5.25M6 20.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm12.75 0a.75.75 0 11-1.5 0 .75.75 0 011.5 0z" /></svg>
|
|
79
|
+
</div>
|
|
80
|
+
<p class="mt-5 font-medium text-base text-stone-700">Your cart is empty.</p>
|
|
81
|
+
<p class="mt-1 text-sm text-stone-500">Pick a treat and we'll start your bowl.</p>
|
|
82
|
+
<a href="/products" class="inline-flex gap-2 items-center mt-6 px-6 py-3 font-semibold text-sm text-stone-50 bg-stone-900 hover:bg-stone-800 rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-stone-50 focus-visible:ring-stone-900 transition-colors">
|
|
83
|
+
Browse the shop
|
|
84
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3" /></svg>
|
|
85
|
+
</a>
|
|
86
|
+
</div>
|
|
87
|
+
@else
|
|
88
|
+
<div class="grid gap-10 items-start lg:gap-12 lg:grid-cols-[2fr_1fr] mt-10">
|
|
89
|
+
|
|
90
|
+
<div class="space-y-4">
|
|
91
|
+
@foreach (items as item)
|
|
92
|
+
<article class="flex gap-4 p-4 bg-stone-50 border border-stone-200 rounded-2xl hover:border-stone-300 transition-colors">
|
|
93
|
+
<a
|
|
94
|
+
href="/products/{{ item.slug }}"
|
|
95
|
+
class="block overflow-hidden flex-shrink-0 h-24 w-24 bg-stone-200 rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-stone-50 focus-visible:ring-stone-900"
|
|
96
|
+
aria-label="View {{ item.name }}"
|
|
97
|
+
>
|
|
98
|
+
<img src="{{ item.image }}" alt="" class="object-cover h-full w-full">
|
|
99
|
+
</a>
|
|
100
|
+
<div class="flex flex-1 flex-col min-w-0">
|
|
101
|
+
<div class="flex gap-3 items-start justify-between">
|
|
102
|
+
<div class="min-w-0">
|
|
103
|
+
<a href="/products/{{ item.slug }}" class="block font-semibold text-stone-900 truncate hover:underline">{{ item.name }}</a>
|
|
104
|
+
@if (item.weight)
|
|
105
|
+
<p class="mt-0.5 text-stone-500 text-xs">{{ item.weight }} · {{ item.priceLabel }} each</p>
|
|
106
|
+
@else
|
|
107
|
+
<p class="mt-0.5 text-stone-500 text-xs">{{ item.priceLabel }} each</p>
|
|
108
|
+
@endif
|
|
109
|
+
</div>
|
|
110
|
+
<p class="font-semibold text-stone-900 whitespace-nowrap">{{ item.lineTotalLabel }}</p>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="flex gap-3 items-end justify-between mt-auto pt-3">
|
|
113
|
+
<form action="/api/cart/update" method="POST" class="inline-flex overflow-hidden items-center border border-stone-300 rounded-full" data-cart-row>
|
|
114
|
+
<input type="hidden" name="item_id" value="{{ item.id }}">
|
|
115
|
+
<button type="submit" name="quantity" value="{{ item.qty - 1 }}" class="px-3 py-1.5 text-stone-600 hover:text-stone-900 focus-visible:bg-stone-100 hover:bg-stone-100 focus-visible:outline-none transition-colors" aria-label="Decrease quantity">
|
|
116
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14"/></svg>
|
|
117
|
+
</button>
|
|
118
|
+
<span class="px-2 min-w-[1.5rem] font-semibold tabular-nums text-center text-sm text-stone-900">{{ item.qty }}</span>
|
|
119
|
+
<button type="submit" name="quantity" value="{{ item.qty + 1 }}" class="px-3 py-1.5 text-stone-600 hover:text-stone-900 focus-visible:bg-stone-100 hover:bg-stone-100 focus-visible:outline-none transition-colors" aria-label="Increase quantity">
|
|
120
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 5v14m-7-7h14"/></svg>
|
|
121
|
+
</button>
|
|
122
|
+
</form>
|
|
123
|
+
<form action="/api/cart/update" method="POST">
|
|
124
|
+
<input type="hidden" name="item_id" value="{{ item.id }}">
|
|
125
|
+
<input type="hidden" name="quantity" value="0">
|
|
126
|
+
<button type="submit" class="font-medium text-stone-500 text-xs underline-offset-4 focus-visible:text-stone-900 focus-visible:underline hover:text-stone-900 hover:underline focus-visible:outline-none transition-colors" aria-label="Remove {{ item.name }}">Remove</button>
|
|
127
|
+
</form>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</article>
|
|
131
|
+
@endforeach
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<OrderSummary
|
|
135
|
+
subtotalLabel="{{ subtotalLabel }}"
|
|
136
|
+
shippingLabel="{{ shippingLabel }}"
|
|
137
|
+
totalLabel="{{ totalLabel }}"
|
|
138
|
+
freeShippingNote="{{ freeShippingNote }}"
|
|
139
|
+
ctaHref="/checkout/contact"
|
|
140
|
+
ctaLabel="Checkout"
|
|
141
|
+
secondaryHref="/products"
|
|
142
|
+
secondaryLabel="Keep shopping"
|
|
143
|
+
:sticky="true"
|
|
144
|
+
/>
|
|
145
|
+
|
|
146
|
+
</div>
|
|
147
|
+
@endif
|
|
148
|
+
</div>
|
|
149
|
+
@endsection
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
@extends('layouts/storefront')
|
|
2
|
+
|
|
3
|
+
@section('title', 'Checkout — Contact')
|
|
4
|
+
|
|
5
|
+
<script server>
|
|
6
|
+
const Database = require('bun:sqlite').Database
|
|
7
|
+
const path = require('path')
|
|
8
|
+
|
|
9
|
+
const dbPath = path.resolve(process.cwd(), process.env.DB_DATABASE_PATH || 'database/stacks.sqlite')
|
|
10
|
+
const db = new Database(dbPath, { readonly: true })
|
|
11
|
+
|
|
12
|
+
const token = (typeof requestContext !== 'undefined') ? requestContext.cookie('stacks_cart') : null
|
|
13
|
+
let items = []
|
|
14
|
+
let cart = null
|
|
15
|
+
|
|
16
|
+
if (token) {
|
|
17
|
+
cart = db.query(`SELECT * FROM carts WHERE session_token = ?1 AND status = 'active' LIMIT 1`).get(token)
|
|
18
|
+
if (cart) {
|
|
19
|
+
items = db.query(`
|
|
20
|
+
SELECT product_name, quantity, total_price
|
|
21
|
+
FROM cart_items WHERE cart_id = ?1 ORDER BY id ASC
|
|
22
|
+
`).all(cart.id).map((row) => {
|
|
23
|
+
const qty = Number(row.quantity)
|
|
24
|
+
const lineTotal = Number(row.total_price)
|
|
25
|
+
return {
|
|
26
|
+
name: row.product_name,
|
|
27
|
+
qty,
|
|
28
|
+
lineTotal,
|
|
29
|
+
lineTotalLabel: `$${lineTotal.toFixed(2)}`,
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Mirror the rule in PlaceOrderAction's _shipping helper so the running
|
|
36
|
+
// totals on every checkout step match what gets charged.
|
|
37
|
+
const FREE_SHIPPING_THRESHOLD = Number(process.env.STOREFRONT_FREE_SHIPPING_THRESHOLD ?? 40) || 40
|
|
38
|
+
const FLAT_SHIPPING = Number(process.env.STOREFRONT_FLAT_SHIPPING ?? 5) || 5
|
|
39
|
+
const subtotal = items.reduce((s, i) => s + i.lineTotal, 0)
|
|
40
|
+
const shipping = subtotal <= 0 ? 0 : (subtotal >= FREE_SHIPPING_THRESHOLD ? 0 : FLAT_SHIPPING)
|
|
41
|
+
const total = subtotal + shipping
|
|
42
|
+
const remainingForFree = Math.max(0, FREE_SHIPPING_THRESHOLD - subtotal)
|
|
43
|
+
|
|
44
|
+
const subtotalLabel = `$${subtotal.toFixed(2)}`
|
|
45
|
+
const shippingLabel = shipping === 0 ? 'Free' : `$${shipping.toFixed(2)}`
|
|
46
|
+
const totalLabel = `$${total.toFixed(2)}`
|
|
47
|
+
const freeShippingNote = (shipping > 0 && remainingForFree > 0)
|
|
48
|
+
? `Add $${remainingForFree.toFixed(2)} more for free shipping.`
|
|
49
|
+
: ''
|
|
50
|
+
|
|
51
|
+
const prefilledEmail = cart?.email || ''
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
@section('content')
|
|
55
|
+
<div class="mx-auto px-6 py-12 max-w-5xl">
|
|
56
|
+
<CheckoutSteps current="contact" />
|
|
57
|
+
|
|
58
|
+
@if (items.length === 0)
|
|
59
|
+
<div class="p-12 text-center border border-dashed border-stone-300 rounded-3xl">
|
|
60
|
+
<p class="text-stone-500">Your cart is empty.</p>
|
|
61
|
+
<a href="/products" class="inline-flex mt-6 px-6 py-3 font-semibold text-sm text-stone-50 bg-stone-900 hover:bg-stone-800 rounded-full">Browse the shop</a>
|
|
62
|
+
</div>
|
|
63
|
+
@else
|
|
64
|
+
<div class="grid gap-10 items-start md:grid-cols-[1fr_360px]">
|
|
65
|
+
<div>
|
|
66
|
+
<h1 class="font-bold text-3xl text-stone-900 tracking-tight">Contact</h1>
|
|
67
|
+
<p class="mt-2 text-stone-600">We'll send your receipt and tracking here.</p>
|
|
68
|
+
|
|
69
|
+
<form action="/api/checkout/contact" method="POST" class="mt-8 space-y-5">
|
|
70
|
+
<div>
|
|
71
|
+
<label class="font-semibold text-stone-500 text-xs tracking-wider uppercase">Email</label>
|
|
72
|
+
<input
|
|
73
|
+
type="email"
|
|
74
|
+
name="email"
|
|
75
|
+
required
|
|
76
|
+
autocomplete="email"
|
|
77
|
+
value="{{ prefilledEmail }}"
|
|
78
|
+
class="mt-1 px-4 py-3 w-full text-sm border border-stone-300 rounded-2xl focus:border-stone-900 focus:outline-none"
|
|
79
|
+
>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="flex gap-4 items-center justify-between">
|
|
82
|
+
<a href="/cart" class="text-sm text-stone-500 hover:text-stone-900">← Back to cart</a>
|
|
83
|
+
<button type="submit" class="px-6 py-3 font-semibold text-sm text-stone-50 bg-stone-900 hover:bg-stone-800 rounded-full transition-colors">
|
|
84
|
+
Continue to shipping
|
|
85
|
+
</button>
|
|
86
|
+
</div>
|
|
87
|
+
</form>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<OrderSummary
|
|
91
|
+
:items="items"
|
|
92
|
+
subtotalLabel="{{ subtotalLabel }}"
|
|
93
|
+
shippingLabel="{{ shippingLabel }}"
|
|
94
|
+
totalLabel="{{ totalLabel }}"
|
|
95
|
+
freeShippingNote="{{ freeShippingNote }}"
|
|
96
|
+
:showItems="true"
|
|
97
|
+
/>
|
|
98
|
+
</div>
|
|
99
|
+
@endif
|
|
100
|
+
</div>
|
|
101
|
+
@endsection
|