@base44/app-plugin-commerce 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +117 -0
- package/base44/agents/commerce/StoreAdmin.jsonc +64 -0
- package/base44/entities/commerce.Cart.jsonc +73 -0
- package/base44/entities/commerce.Coupon.jsonc +113 -0
- package/base44/entities/commerce.Customer.jsonc +96 -0
- package/base44/entities/commerce.DownloadPermission.jsonc +54 -0
- package/base44/entities/commerce.EmailLog.jsonc +43 -0
- package/base44/entities/commerce.Order.jsonc +287 -0
- package/base44/entities/commerce.OrderNote.jsonc +31 -0
- package/base44/entities/commerce.OrderRefund.jsonc +64 -0
- package/base44/entities/commerce.PaymentGateway.jsonc +48 -0
- package/base44/entities/commerce.Product.jsonc +291 -0
- package/base44/entities/commerce.ProductAttribute.jsonc +39 -0
- package/base44/entities/commerce.ProductAttributeTerm.jsonc +38 -0
- package/base44/entities/commerce.ProductCategory.jsonc +51 -0
- package/base44/entities/commerce.ProductReview.jsonc +48 -0
- package/base44/entities/commerce.ProductTag.jsonc +30 -0
- package/base44/entities/commerce.ProductVariation.jsonc +167 -0
- package/base44/entities/commerce.ShippingClass.jsonc +30 -0
- package/base44/entities/commerce.ShippingZone.jsonc +41 -0
- package/base44/entities/commerce.ShippingZoneMethod.jsonc +84 -0
- package/base44/entities/commerce.StoreSettings.jsonc +23 -0
- package/base44/entities/commerce.TaxClass.jsonc +23 -0
- package/base44/entities/commerce.TaxRate.jsonc +68 -0
- package/base44/entities/commerce.Webhook.jsonc +57 -0
- package/base44/entities/commerce.WebhookDelivery.jsonc +45 -0
- package/base44/functions/commerce/admin-coupons/entry.ts +100 -0
- package/base44/functions/commerce/admin-customers/entry.ts +141 -0
- package/base44/functions/commerce/admin-orders/entry.ts +396 -0
- package/base44/functions/commerce/admin-orders/helpers.ts +246 -0
- package/base44/functions/commerce/admin-products/entry.ts +506 -0
- package/base44/functions/commerce/admin-refunds/entry.ts +158 -0
- package/base44/functions/commerce/admin-reports/entry.ts +283 -0
- package/base44/functions/commerce/admin-reviews/entry.ts +66 -0
- package/base44/functions/commerce/admin-tools/entry.ts +261 -0
- package/base44/functions/commerce/admin-webhooks/entry.ts +52 -0
- package/base44/functions/commerce/payment-webhook/entry.ts +135 -0
- package/base44/functions/commerce/payments/entry.ts +238 -0
- package/base44/functions/commerce/seed-store/defaults.ts +162 -0
- package/base44/functions/commerce/seed-store/entry.ts +310 -0
- package/base44/functions/commerce/seed-store/sample-data.ts +349 -0
- package/base44/functions/commerce/storefront-account/entry.ts +207 -0
- package/base44/functions/commerce/storefront-cart/cart-pricing.ts +258 -0
- package/base44/functions/commerce/storefront-cart/entry.ts +283 -0
- package/base44/functions/commerce/storefront-catalog/entry.ts +459 -0
- package/base44/functions/commerce/storefront-checkout/cart-pricing.ts +258 -0
- package/base44/functions/commerce/storefront-checkout/entry.ts +485 -0
- package/base44/shared/commerce/auth.ts +60 -0
- package/base44/shared/commerce/coupons.ts +257 -0
- package/base44/shared/commerce/data/continents.ts +75 -0
- package/base44/shared/commerce/data/countries.ts +307 -0
- package/base44/shared/commerce/data/currencies.ts +46 -0
- package/base44/shared/commerce/email-templates.ts +240 -0
- package/base44/shared/commerce/emails.ts +225 -0
- package/base44/shared/commerce/money.ts +66 -0
- package/base44/shared/commerce/orders.ts +251 -0
- package/base44/shared/commerce/payments.ts +495 -0
- package/base44/shared/commerce/reviews.ts +36 -0
- package/base44/shared/commerce/scan.ts +57 -0
- package/base44/shared/commerce/sequence.ts +35 -0
- package/base44/shared/commerce/settings.ts +57 -0
- package/base44/shared/commerce/shipping.ts +215 -0
- package/base44/shared/commerce/stock.ts +227 -0
- package/base44/shared/commerce/stripe.ts +463 -0
- package/base44/shared/commerce/tax.ts +136 -0
- package/base44/shared/commerce/totals.ts +314 -0
- package/base44/shared/commerce/webhooks.ts +116 -0
- package/package.json +37 -0
- package/scripts/install.js +156 -0
- package/skills/commerce/SKILL.md +62 -0
- package/skills/commerce/docs/api-admin.md +186 -0
- package/skills/commerce/docs/api-storefront.md +408 -0
- package/skills/commerce/installation-guidelines.md +91 -0
- package/skills/commerce/post-installation.md +157 -0
- package/skills/commerce/references/emails.md +13 -0
- package/skills/commerce/references/guest-access-security.md +18 -0
- package/skills/commerce/references/limits-and-performance.md +16 -0
- package/skills/commerce/references/media-and-downloads.md +4 -0
- package/skills/commerce/references/online-payments.md +201 -0
- package/skills/commerce/references/product-render.md +87 -0
- package/skills/commerce/references/scheduled-work.md +19 -0
- package/skills/commerce/references/storefront-product-page.md +83 -0
- package/skills/commerce/references/webhooks.md +8 -0
- package/src/commerce/admin/README.md +107 -0
- package/src/commerce/admin/bot/Markdown.jsx +138 -0
- package/src/commerce/admin/bot/StoreAdminBot.jsx +249 -0
- package/src/commerce/admin/bot/pipe-tables.js +116 -0
- package/src/commerce/admin/components/AddressForm.jsx +78 -0
- package/src/commerce/admin/components/ConfirmDialog.jsx +52 -0
- package/src/commerce/admin/components/CountrySelect.jsx +81 -0
- package/src/commerce/admin/components/DataTable.jsx +192 -0
- package/src/commerce/admin/components/DateRangePicker.jsx +91 -0
- package/src/commerce/admin/components/EmptyState.jsx +17 -0
- package/src/commerce/admin/components/MediaUploader.jsx +116 -0
- package/src/commerce/admin/components/MetaDataEditor.jsx +45 -0
- package/src/commerce/admin/components/MoneyInput.jsx +50 -0
- package/src/commerce/admin/components/PageHeader.jsx +29 -0
- package/src/commerce/admin/components/RichTextarea.jsx +21 -0
- package/src/commerce/admin/components/SearchSelect.jsx +142 -0
- package/src/commerce/admin/components/StatusBadge.jsx +17 -0
- package/src/commerce/admin/context/BasePathContext.jsx +26 -0
- package/src/commerce/admin/context/SettingsContext.jsx +207 -0
- package/src/commerce/admin/hooks/useAsync.js +46 -0
- package/src/commerce/admin/hooks/useDebounce.js +11 -0
- package/src/commerce/admin/hooks/useMoney.js +52 -0
- package/src/commerce/admin/hooks/usePagedList.js +83 -0
- package/src/commerce/admin/hooks/usePaymentProvider.js +27 -0
- package/src/commerce/admin/hooks/useRealtime.js +129 -0
- package/src/commerce/admin/index.jsx +34 -0
- package/src/commerce/admin/layout/AccessDenied.jsx +54 -0
- package/src/commerce/admin/layout/AdminLayout.jsx +33 -0
- package/src/commerce/admin/layout/AuthGuard.jsx +84 -0
- package/src/commerce/admin/layout/Sidebar.jsx +130 -0
- package/src/commerce/admin/layout/Topbar.jsx +94 -0
- package/src/commerce/admin/lib/api.js +55 -0
- package/src/commerce/admin/lib/constants.js +157 -0
- package/src/commerce/admin/lib/format.js +27 -0
- package/src/commerce/admin/lib/geo-data.js +125 -0
- package/src/commerce/admin/lib/order-utils.js +147 -0
- package/src/commerce/admin/lib/paths.js +35 -0
- package/src/commerce/admin/lib/product-utils.js +55 -0
- package/src/commerce/admin/pages/Dashboard.jsx +245 -0
- package/src/commerce/admin/pages/coupons/CouponEditor.jsx +565 -0
- package/src/commerce/admin/pages/coupons/CouponsList.jsx +172 -0
- package/src/commerce/admin/pages/customers/CustomerEditor.jsx +318 -0
- package/src/commerce/admin/pages/customers/CustomersList.jsx +169 -0
- package/src/commerce/admin/pages/orders/OrderEditor.jsx +952 -0
- package/src/commerce/admin/pages/orders/OrdersList.jsx +227 -0
- package/src/commerce/admin/pages/orders/components/AddProductDialog.jsx +149 -0
- package/src/commerce/admin/pages/orders/components/DownloadPermissionsPanel.jsx +119 -0
- package/src/commerce/admin/pages/orders/components/LineItemsTable.jsx +208 -0
- package/src/commerce/admin/pages/orders/components/OrderNotesPanel.jsx +123 -0
- package/src/commerce/admin/pages/orders/components/PaymentPanel.jsx +199 -0
- package/src/commerce/admin/pages/orders/components/RefundPanel.jsx +239 -0
- package/src/commerce/admin/pages/orders/components/TotalsBox.jsx +52 -0
- package/src/commerce/admin/pages/products/AttributeTerms.jsx +180 -0
- package/src/commerce/admin/pages/products/Attributes.jsx +183 -0
- package/src/commerce/admin/pages/products/Categories.jsx +236 -0
- package/src/commerce/admin/pages/products/ProductEditor.jsx +267 -0
- package/src/commerce/admin/pages/products/ProductsList.jsx +391 -0
- package/src/commerce/admin/pages/products/Reviews.jsx +255 -0
- package/src/commerce/admin/pages/products/Tags.jsx +150 -0
- package/src/commerce/admin/pages/products/components/ProductDataPanel.jsx +132 -0
- package/src/commerce/admin/pages/products/components/PublishBox.jsx +101 -0
- package/src/commerce/admin/pages/products/components/TaxonomyPanel.jsx +243 -0
- package/src/commerce/admin/pages/products/components/tabs/AdvancedTab.jsx +48 -0
- package/src/commerce/admin/pages/products/components/tabs/AttributesTab.jsx +208 -0
- package/src/commerce/admin/pages/products/components/tabs/DownloadsTab.jsx +91 -0
- package/src/commerce/admin/pages/products/components/tabs/ExternalTab.jsx +41 -0
- package/src/commerce/admin/pages/products/components/tabs/GeneralTab.jsx +103 -0
- package/src/commerce/admin/pages/products/components/tabs/InventoryTab.jsx +93 -0
- package/src/commerce/admin/pages/products/components/tabs/LinkedTab.jsx +102 -0
- package/src/commerce/admin/pages/products/components/tabs/ShippingTab.jsx +86 -0
- package/src/commerce/admin/pages/products/components/tabs/VariationsTab.jsx +377 -0
- package/src/commerce/admin/pages/reports/Reports.jsx +416 -0
- package/src/commerce/admin/pages/settings/EmailsSettings.jsx +240 -0
- package/src/commerce/admin/pages/settings/GeneralSettings.jsx +232 -0
- package/src/commerce/admin/pages/settings/InventorySettings.jsx +146 -0
- package/src/commerce/admin/pages/settings/PaymentsSettings.jsx +260 -0
- package/src/commerce/admin/pages/settings/ProductsSettings.jsx +118 -0
- package/src/commerce/admin/pages/settings/SettingsLayout.jsx +53 -0
- package/src/commerce/admin/pages/settings/ShippingSettings.jsx +304 -0
- package/src/commerce/admin/pages/settings/ShippingZoneEditor.jsx +514 -0
- package/src/commerce/admin/pages/settings/TaxRatesTable.jsx +231 -0
- package/src/commerce/admin/pages/settings/TaxSettings.jsx +281 -0
- package/src/commerce/admin/pages/settings/useGroupForm.jsx +76 -0
- package/src/commerce/admin/pages/status/WebhookEditor.jsx +296 -0
- package/src/commerce/admin/pages/status/Webhooks.jsx +53 -0
- package/src/commerce/admin/routes.jsx +151 -0
- package/src/commerce/utils/index.js +19 -0
- package/src/commerce/utils/shipping-promos.js +99 -0
- package/src/commerce/utils/variants.js +411 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commerce/seed-store — idempotent store initialization.
|
|
3
|
+
*
|
|
4
|
+
* Flow: (1) canary schema validation (probe create+delete per entity; abort
|
|
5
|
+
* with 422 schema_incompatible and write nothing on any failure), (2) seed
|
|
6
|
+
* required defaults (settings groups, gateways, tax classes, fallback zone),
|
|
7
|
+
* (3) optional sample catalog (~12 products across every product type, with
|
|
8
|
+
* images and descriptions, plus three variable products with variants) — only
|
|
9
|
+
* when with_sample_data=true and the store has zero products, with best-effort
|
|
10
|
+
* rollback on mid-failure.
|
|
11
|
+
*
|
|
12
|
+
* Body: { with_sample_data?: boolean, store_name?: string }
|
|
13
|
+
*
|
|
14
|
+
* `store_name` is required on a first seed: a function's env is only
|
|
15
|
+
* BASE44_APP_ID, so it cannot read the app's name, and subjects need one.
|
|
16
|
+
*/
|
|
17
|
+
import { createClientFromRequest } from "npm:@base44/sdk";
|
|
18
|
+
import { HttpError, requireAdmin } from "../../../shared/commerce/auth.ts";
|
|
19
|
+
import { round2 } from "../../../shared/commerce/money.ts";
|
|
20
|
+
import {
|
|
21
|
+
GATEWAY_DEFAULTS,
|
|
22
|
+
REST_OF_WORLD_EXAMPLE_METHOD,
|
|
23
|
+
REST_OF_WORLD_ZONE,
|
|
24
|
+
SETTINGS_DEFAULTS,
|
|
25
|
+
TAX_CLASS_DEFAULTS,
|
|
26
|
+
} from "./defaults.ts";
|
|
27
|
+
import {
|
|
28
|
+
SAMPLE_ATTRIBUTE_TERMS,
|
|
29
|
+
SAMPLE_ATTRIBUTES,
|
|
30
|
+
SAMPLE_CATEGORIES,
|
|
31
|
+
SAMPLE_COUPONS,
|
|
32
|
+
SAMPLE_PRODUCTS,
|
|
33
|
+
SAMPLE_TAX_RATES,
|
|
34
|
+
} from "./sample-data.ts";
|
|
35
|
+
|
|
36
|
+
const ok = (data: unknown, status = 200) => Response.json({ success: true, data }, { status });
|
|
37
|
+
const fail = (status: number, error: string, code?: string, extra?: Record<string, unknown>) =>
|
|
38
|
+
Response.json({ success: false, error, code, ...extra }, { status });
|
|
39
|
+
|
|
40
|
+
Deno.serve(async (req) => {
|
|
41
|
+
try {
|
|
42
|
+
const base44 = createClientFromRequest(req);
|
|
43
|
+
await requireAdmin(base44);
|
|
44
|
+
const sr = base44.asServiceRole;
|
|
45
|
+
const body = await req.json().catch(() => ({}));
|
|
46
|
+
const withSample = !!body.with_sample_data;
|
|
47
|
+
const storeName = String(body.store_name ?? "").trim();
|
|
48
|
+
|
|
49
|
+
// ── 1. canary schema validation — abort before writing anything real ────
|
|
50
|
+
const canaryErrors = await runCanaries(sr, withSample);
|
|
51
|
+
if (canaryErrors.length) {
|
|
52
|
+
return fail(422,
|
|
53
|
+
"Entity schemas have been modified and are incompatible with the seeder.",
|
|
54
|
+
"schema_incompatible",
|
|
55
|
+
{ errors: canaryErrors });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── 2. required defaults (idempotent) ────────────────────────────────────
|
|
59
|
+
const seeded = { settings_groups: 0, gateways: 0, tax_classes: 0, zones: 0, zone_methods: 0 };
|
|
60
|
+
|
|
61
|
+
const existingSettings = (await sr.entities["commerce.StoreSettings"].list(undefined, 100)) ?? [];
|
|
62
|
+
const existingGroups = new Set(existingSettings.map((s: any) => s.group_id));
|
|
63
|
+
// A nameless store renders subjects like "[]: New order #1002", so refuse to
|
|
64
|
+
// create one rather than seeding a blank the caller never sees.
|
|
65
|
+
if (!existingGroups.has("general") && !storeName) {
|
|
66
|
+
return fail(400, "store_name is required — pass the app's name as the platform shows it.", "store_name_required");
|
|
67
|
+
}
|
|
68
|
+
for (const group of SETTINGS_DEFAULTS) {
|
|
69
|
+
if (existingGroups.has(group.group_id)) continue;
|
|
70
|
+
const values = group.group_id === "general"
|
|
71
|
+
? { ...group.values, store_name: storeName }
|
|
72
|
+
: group.values;
|
|
73
|
+
await sr.entities["commerce.StoreSettings"].create({ ...group, values });
|
|
74
|
+
seeded.settings_groups++;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// A passed store_name must never be silently dropped: fill a blank one on an
|
|
78
|
+
// already-seeded store, but never overwrite a name the merchant chose.
|
|
79
|
+
let storeNameAction = storeName ? "created" : "unchanged";
|
|
80
|
+
if (storeName && existingGroups.has("general")) {
|
|
81
|
+
const general = existingSettings.find((r: any) => r.group_id === "general");
|
|
82
|
+
const current = String(general?.values?.store_name ?? "").trim();
|
|
83
|
+
if (!current) {
|
|
84
|
+
await sr.entities["commerce.StoreSettings"].update(general.id, {
|
|
85
|
+
values: { ...(general.values ?? {}), store_name: storeName },
|
|
86
|
+
});
|
|
87
|
+
storeNameAction = "filled";
|
|
88
|
+
} else {
|
|
89
|
+
storeNameAction = current === storeName ? "unchanged" : "kept_existing";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const gw of GATEWAY_DEFAULTS) {
|
|
94
|
+
const hits = (await sr.entities["commerce.PaymentGateway"].filter({ slug: gw.slug }, undefined, 1)) ?? [];
|
|
95
|
+
if (hits.length) continue;
|
|
96
|
+
await sr.entities["commerce.PaymentGateway"].create(gw);
|
|
97
|
+
seeded.gateways++;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (const tc of TAX_CLASS_DEFAULTS) {
|
|
101
|
+
const hits = (await sr.entities["commerce.TaxClass"].filter({ slug: tc.slug }, undefined, 1)) ?? [];
|
|
102
|
+
if (hits.length) continue;
|
|
103
|
+
await sr.entities["commerce.TaxClass"].create(tc);
|
|
104
|
+
seeded.tax_classes++;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let zone = ((await sr.entities["commerce.ShippingZone"].filter({ name: REST_OF_WORLD_ZONE.name }, undefined, 1)) ?? [])[0];
|
|
108
|
+
if (!zone) {
|
|
109
|
+
zone = await sr.entities["commerce.ShippingZone"].create(REST_OF_WORLD_ZONE);
|
|
110
|
+
seeded.zones++;
|
|
111
|
+
}
|
|
112
|
+
const zoneMethods = (await sr.entities["commerce.ShippingZoneMethod"].filter({ zone_id: zone.id }, undefined, 5)) ?? [];
|
|
113
|
+
if (!zoneMethods.length) {
|
|
114
|
+
await sr.entities["commerce.ShippingZoneMethod"].create({ ...REST_OF_WORLD_EXAMPLE_METHOD, zone_id: zone.id });
|
|
115
|
+
seeded.zone_methods++;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── 3. optional sample catalog ───────────────────────────────────────────
|
|
119
|
+
let sampleResult: Record<string, number> | null = null;
|
|
120
|
+
if (withSample) {
|
|
121
|
+
const anyProduct = (await sr.entities["commerce.Product"].list(undefined, 1)) ?? [];
|
|
122
|
+
if (!anyProduct.length) {
|
|
123
|
+
sampleResult = await seedSampleData(sr);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const currentName = String(
|
|
128
|
+
(existingSettings.find((r: any) => r.group_id === "general")?.values?.store_name ?? storeName) || storeName,
|
|
129
|
+
);
|
|
130
|
+
return ok({
|
|
131
|
+
seeded,
|
|
132
|
+
sample_data: sampleResult ?? false,
|
|
133
|
+
store_name: { value: storeNameAction === "kept_existing" ? currentName : storeName, action: storeNameAction },
|
|
134
|
+
});
|
|
135
|
+
} catch (e) {
|
|
136
|
+
if (e instanceof HttpError) return fail(e.status, e.message, e.code);
|
|
137
|
+
console.error("commerce/seed-store error:", e);
|
|
138
|
+
return fail(500, (e as Error).message ?? "Internal error");
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// ── canaries ─────────────────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
/** Minimal-valid probe records per entity the seeder writes. */
|
|
145
|
+
function canarySpecs(withSample: boolean): Array<{ entity: string; record: Record<string, any> }> {
|
|
146
|
+
const base = [
|
|
147
|
+
{ entity: "commerce.StoreSettings", record: { group_id: "general", values: { __canary: true } } },
|
|
148
|
+
{ entity: "commerce.PaymentGateway", record: { slug: "__canary", title: "Canary", enabled: false, order: 999, settings: {} } },
|
|
149
|
+
{ entity: "commerce.TaxClass", record: { slug: "__canary", name: "Canary" } },
|
|
150
|
+
{ entity: "commerce.ShippingZone", record: { name: "__canary", order: 998, locations: [] } },
|
|
151
|
+
{ entity: "commerce.ShippingZoneMethod", record: { zone_id: "__canary", method_id: "flat_rate", title: "Canary", enabled: false, order: 0, settings: { cost: 0 } } },
|
|
152
|
+
];
|
|
153
|
+
const sample = [
|
|
154
|
+
{ entity: "commerce.ProductCategory", record: { name: "__canary", slug: "__canary", count: 0 } },
|
|
155
|
+
{ entity: "commerce.ProductAttribute", record: { name: "__canary", slug: "__canary", type: "select" } },
|
|
156
|
+
{ entity: "commerce.ProductAttributeTerm", record: { attribute_id: "__canary", name: "__canary", slug: "__canary" } },
|
|
157
|
+
{ entity: "commerce.Product", record: { name: "__canary", type: "simple", status: "draft", regular_price: 1, price: 1 } },
|
|
158
|
+
{ entity: "commerce.ProductVariation", record: { product_id: "__canary", attributes: [], status: "draft" } },
|
|
159
|
+
{ entity: "commerce.Coupon", record: { code: "__canary", discount_type: "percent", amount: 1, usage_count: 0, used_by: [] } },
|
|
160
|
+
{ entity: "commerce.TaxRate", record: { country: "ZZ", rate: 1, name: "__canary", priority: 1, compound: false, shipping: true, tax_class: "standard" } },
|
|
161
|
+
];
|
|
162
|
+
return withSample ? [...base, ...sample] : base;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function runCanaries(sr: any, withSample: boolean): Promise<Array<{ entity: string; error: string }>> {
|
|
166
|
+
const errors: Array<{ entity: string; error: string }> = [];
|
|
167
|
+
for (const spec of canarySpecs(withSample)) {
|
|
168
|
+
let created: any = null;
|
|
169
|
+
try {
|
|
170
|
+
created = await sr.entities[spec.entity].create(spec.record);
|
|
171
|
+
} catch (e) {
|
|
172
|
+
errors.push({ entity: spec.entity, error: String((e as Error)?.message ?? e) });
|
|
173
|
+
} finally {
|
|
174
|
+
if (created?.id) {
|
|
175
|
+
try { await sr.entities[spec.entity].delete(created.id); } catch { /* orphaned canary is harmless */ }
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return errors;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── sample catalog ───────────────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
/** price/on_sale/stock_status derivation for seeded products (mirrors commerce/admin-products). */
|
|
185
|
+
function deriveSeedProduct(p: any): any {
|
|
186
|
+
const out = { ...p };
|
|
187
|
+
out.on_sale = out.sale_price != null;
|
|
188
|
+
const effective = out.on_sale ? out.sale_price : out.regular_price;
|
|
189
|
+
if (effective != null) out.price = round2(Number(effective));
|
|
190
|
+
if (out.manage_stock) {
|
|
191
|
+
const qty = Number(out.stock_quantity ?? 0);
|
|
192
|
+
out.stock_status = qty > 0 ? "instock" : (out.backorders && out.backorders !== "no" ? "onbackorder" : "outofstock");
|
|
193
|
+
} else if (!out.stock_status) {
|
|
194
|
+
out.stock_status = "instock";
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function seedSampleData(sr: any): Promise<Record<string, number>> {
|
|
200
|
+
// rollback ledger — delete in reverse order on failure
|
|
201
|
+
const created: Array<{ entity: string; id: string }> = [];
|
|
202
|
+
const track = async (entity: string, record: Record<string, any>) => {
|
|
203
|
+
const rec = await sr.entities[entity].create(record);
|
|
204
|
+
created.push({ entity, id: rec.id });
|
|
205
|
+
return rec;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
// categories
|
|
210
|
+
const categoryBySlug: Record<string, any> = {};
|
|
211
|
+
for (const cat of SAMPLE_CATEGORIES) {
|
|
212
|
+
categoryBySlug[cat.slug] = await track("commerce.ProductCategory", cat);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// attributes + terms
|
|
216
|
+
const attributeBySlug: Record<string, any> = {};
|
|
217
|
+
for (const attr of SAMPLE_ATTRIBUTES) {
|
|
218
|
+
const rec = await track("commerce.ProductAttribute", attr);
|
|
219
|
+
attributeBySlug[attr.slug] = rec;
|
|
220
|
+
for (const term of SAMPLE_ATTRIBUTE_TERMS[attr.slug] ?? []) {
|
|
221
|
+
await track("commerce.ProductAttributeTerm", { ...term, attribute_id: rec.id, count: 0 });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// products (two passes: grouped references resolved after simple products exist)
|
|
226
|
+
const productByKey: Record<string, any> = {};
|
|
227
|
+
let variationCount = 0;
|
|
228
|
+
const specs = [...SAMPLE_PRODUCTS].sort((a, b) => (a.type === "grouped" ? 1 : 0) - (b.type === "grouped" ? 1 : 0));
|
|
229
|
+
|
|
230
|
+
for (const spec of specs) {
|
|
231
|
+
const { key, categories, attributes, default_attributes, variations, grouped_keys, options: _o, ...fields } = spec;
|
|
232
|
+
const record: any = deriveSeedProduct({
|
|
233
|
+
...fields,
|
|
234
|
+
slug: fields.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""),
|
|
235
|
+
category_ids: (categories ?? []).map((slug: string) => categoryBySlug[slug]?.id).filter(Boolean),
|
|
236
|
+
tag_ids: [],
|
|
237
|
+
images: fields.images ?? [],
|
|
238
|
+
meta_data: [],
|
|
239
|
+
total_sales: 0,
|
|
240
|
+
});
|
|
241
|
+
if (attributes) {
|
|
242
|
+
record.attributes = attributes.map((a: any, i: number) => ({
|
|
243
|
+
attribute_id: attributeBySlug[a.slug]?.id ?? "",
|
|
244
|
+
name: attributeBySlug[a.slug]?.name ?? a.slug,
|
|
245
|
+
position: i,
|
|
246
|
+
visible: a.visible !== false,
|
|
247
|
+
variation: !!a.variation,
|
|
248
|
+
options: a.options ?? [],
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
if (default_attributes) {
|
|
252
|
+
record.default_attributes = default_attributes.map((d: any) => ({
|
|
253
|
+
attribute_id: attributeBySlug[d.slug]?.id ?? "",
|
|
254
|
+
name: attributeBySlug[d.slug]?.name ?? d.slug,
|
|
255
|
+
option: d.option,
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
if (grouped_keys) {
|
|
259
|
+
record.grouped_products = grouped_keys.map((k: string) => productByKey[k]?.id).filter(Boolean);
|
|
260
|
+
}
|
|
261
|
+
const product = await track("commerce.Product", record);
|
|
262
|
+
productByKey[key] = product;
|
|
263
|
+
|
|
264
|
+
for (const v of variations ?? []) {
|
|
265
|
+
const attrs = Object.entries(v.options).map(([slug, option]) => ({
|
|
266
|
+
attribute_id: attributeBySlug[slug]?.id ?? "",
|
|
267
|
+
name: attributeBySlug[slug]?.name ?? slug,
|
|
268
|
+
option,
|
|
269
|
+
}));
|
|
270
|
+
await track("commerce.ProductVariation", deriveSeedProduct({
|
|
271
|
+
product_id: product.id,
|
|
272
|
+
attributes: attrs,
|
|
273
|
+
status: "publish",
|
|
274
|
+
sku: `${record.sku}-${Object.values(v.options).join("-").toUpperCase()}`,
|
|
275
|
+
regular_price: v.regular_price,
|
|
276
|
+
...(v.sale_price != null ? { sale_price: v.sale_price } : {}),
|
|
277
|
+
manage_stock: "yes",
|
|
278
|
+
stock_quantity: v.stock_quantity,
|
|
279
|
+
backorders: v.backorders ?? "no",
|
|
280
|
+
...(v.image ? { image: v.image } : {}),
|
|
281
|
+
}));
|
|
282
|
+
variationCount++;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// taxonomy counts for the seeded catalog
|
|
287
|
+
for (const cat of Object.values(categoryBySlug) as any[]) {
|
|
288
|
+
const count = specs.filter((s) => (s.categories ?? []).some((slug: string) => categoryBySlug[slug]?.id === cat.id)).length;
|
|
289
|
+
await sr.entities["commerce.ProductCategory"].update(cat.id, { count });
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
for (const coupon of SAMPLE_COUPONS) await track("commerce.Coupon", coupon);
|
|
293
|
+
for (const rate of SAMPLE_TAX_RATES) await track("commerce.TaxRate", rate);
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
categories: SAMPLE_CATEGORIES.length,
|
|
297
|
+
attributes: SAMPLE_ATTRIBUTES.length,
|
|
298
|
+
products: SAMPLE_PRODUCTS.length,
|
|
299
|
+
variations: variationCount,
|
|
300
|
+
coupons: SAMPLE_COUPONS.length,
|
|
301
|
+
tax_rates: SAMPLE_TAX_RATES.length,
|
|
302
|
+
};
|
|
303
|
+
} catch (e) {
|
|
304
|
+
// best-effort rollback, newest first
|
|
305
|
+
for (const { entity, id } of created.reverse()) {
|
|
306
|
+
try { await sr.entities[entity].delete(id); } catch { /* leave orphans; commerce/admin-tools can clean */ }
|
|
307
|
+
}
|
|
308
|
+
throw new HttpError(500, `Sample data seeding failed and was rolled back: ${(e as Error).message}`, "sample_seed_failed");
|
|
309
|
+
}
|
|
310
|
+
}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional demo catalog: 3 categories, 2 global attributes (+terms),
|
|
3
|
+
* ~12 products covering every product type (including 3 variable products
|
|
4
|
+
* with variants), 2 coupons, 2 tax rates. Every product ships with a
|
|
5
|
+
* description, short description and a small image gallery so the storefront
|
|
6
|
+
* and admin look populated out of the box.
|
|
7
|
+
* Only installed when with_sample_data=true AND the store has no products.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Demo photography is served from the Unsplash CDN, referenced by immutable
|
|
12
|
+
* photo id. Unlike a random-image service, every id below was picked to actually
|
|
13
|
+
* depict the product it illustrates, and it resolves to the same photo on every
|
|
14
|
+
* seed run with no upload step. Swap these for Core.UploadFile URLs in a real
|
|
15
|
+
* store.
|
|
16
|
+
*/
|
|
17
|
+
const IMG = (photoId: string, w = 800, h = 800) =>
|
|
18
|
+
`https://images.unsplash.com/${photoId}?w=${w}&h=${h}&fit=crop&q=80`;
|
|
19
|
+
|
|
20
|
+
const gallery = (name: string, photoIds: string[]) =>
|
|
21
|
+
photoIds.map((photoId, position) => ({
|
|
22
|
+
src: IMG(photoId),
|
|
23
|
+
name,
|
|
24
|
+
alt: position === 0 ? name : `${name} — view ${position + 1}`,
|
|
25
|
+
position,
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
export const SAMPLE_CATEGORIES = [
|
|
29
|
+
{ name: "Clothing", slug: "clothing", description: "Apparel and wearables", display: "default", menu_order: 0, count: 0 },
|
|
30
|
+
{ name: "Accessories", slug: "accessories", description: "Small goods and extras", display: "default", menu_order: 1, count: 0 },
|
|
31
|
+
{ name: "Downloads", slug: "downloads", description: "Digital products", display: "default", menu_order: 2, count: 0 },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export const SAMPLE_ATTRIBUTES = [
|
|
35
|
+
{ name: "Color", slug: "color", type: "select", order_by: "menu_order", has_archives: false },
|
|
36
|
+
{ name: "Size", slug: "size", type: "select", order_by: "menu_order", has_archives: false },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
export const SAMPLE_ATTRIBUTE_TERMS: Record<string, Array<{ name: string; slug: string; menu_order: number }>> = {
|
|
40
|
+
color: [
|
|
41
|
+
{ name: "Red", slug: "red", menu_order: 0 },
|
|
42
|
+
{ name: "Blue", slug: "blue", menu_order: 1 },
|
|
43
|
+
{ name: "Black", slug: "black", menu_order: 2 },
|
|
44
|
+
{ name: "Green", slug: "green", menu_order: 3 },
|
|
45
|
+
],
|
|
46
|
+
size: [
|
|
47
|
+
{ name: "S", slug: "s", menu_order: 0 },
|
|
48
|
+
{ name: "M", slug: "m", menu_order: 1 },
|
|
49
|
+
{ name: "L", slug: "l", menu_order: 2 },
|
|
50
|
+
{ name: "XL", slug: "xl", menu_order: 3 },
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Per-colour variation photos, keyed by product: a colour swatch is only useful
|
|
56
|
+
* if it shows the right *garment* in that colour, so these can't be shared
|
|
57
|
+
* across product types. Products with no colour-accurate photo available (the
|
|
58
|
+
* tote) are deliberately absent — their variations fall back to the product
|
|
59
|
+
* gallery, which beats showing an unrelated item.
|
|
60
|
+
*/
|
|
61
|
+
const VARIATION_IMAGES: Record<string, Record<string, string>> = {
|
|
62
|
+
"V-Neck T-Shirt": {
|
|
63
|
+
Red: "photo-1634849112476-88cb7e60392b",
|
|
64
|
+
Blue: "photo-1650590122055-576096fde8f9",
|
|
65
|
+
},
|
|
66
|
+
"Colorblock Pullover Hoodie": {
|
|
67
|
+
Black: "photo-1636170030727-11b73d53e1eb",
|
|
68
|
+
Blue: "photo-1617171594202-100a53bdfe04",
|
|
69
|
+
Green: "photo-1536992266094-82847e1fd431",
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** Variation image for a colour, or undefined to inherit the product gallery. */
|
|
74
|
+
const colorImage = (name: string, color: string) => {
|
|
75
|
+
const photoId = VARIATION_IMAGES[name]?.[color];
|
|
76
|
+
return photoId
|
|
77
|
+
? { src: IMG(photoId), name: `${name} — ${color}`, alt: `${name} in ${color}` }
|
|
78
|
+
: undefined;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Product specs — category/attribute references are by slug and resolved to
|
|
83
|
+
* ids at seed time. `variations` uses attribute slug→option pairs and may carry
|
|
84
|
+
* a per-variation `image`.
|
|
85
|
+
*/
|
|
86
|
+
export const SAMPLE_PRODUCTS: any[] = [
|
|
87
|
+
{
|
|
88
|
+
key: "tshirt",
|
|
89
|
+
name: "Classic T-Shirt",
|
|
90
|
+
type: "simple",
|
|
91
|
+
status: "publish",
|
|
92
|
+
categories: ["clothing"],
|
|
93
|
+
regular_price: 19.99,
|
|
94
|
+
sku: "DEMO-TSHIRT",
|
|
95
|
+
manage_stock: true,
|
|
96
|
+
stock_quantity: 50,
|
|
97
|
+
weight: 0.2,
|
|
98
|
+
dimensions: { length: 30, width: 25, height: 2 },
|
|
99
|
+
images: gallery("Classic T-Shirt", ["photo-1521572163474-6864f9cf17ab", "photo-1581655353564-df123a1eb820"]),
|
|
100
|
+
short_description: "A soft, breathable everyday classic tee.",
|
|
101
|
+
description:
|
|
102
|
+
"<p>Our best-selling everyday tee, cut from 100% combed cotton for a soft hand-feel that only gets better with every wash.</p>" +
|
|
103
|
+
"<ul><li>100% combed ring-spun cotton</li><li>Pre-shrunk, tagless collar</li><li>Reinforced double-stitched hems</li></ul>",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
key: "hoodie",
|
|
107
|
+
name: "Zip Hoodie",
|
|
108
|
+
type: "simple",
|
|
109
|
+
status: "publish",
|
|
110
|
+
categories: ["clothing"],
|
|
111
|
+
regular_price: 44.99,
|
|
112
|
+
sale_price: 34.99,
|
|
113
|
+
sku: "DEMO-HOODIE",
|
|
114
|
+
manage_stock: true,
|
|
115
|
+
stock_quantity: 30,
|
|
116
|
+
weight: 0.6,
|
|
117
|
+
images: gallery("Zip Hoodie", ["photo-1622866654199-d36cf0709720", "photo-1556821840-3a63f95609a7"]),
|
|
118
|
+
short_description: "Cozy fleece-lined zip-up hoodie, currently on sale.",
|
|
119
|
+
description:
|
|
120
|
+
"<p>Stay warm without the bulk. This fleece-lined zip hoodie pairs a brushed-soft interior with a durable outer shell.</p>" +
|
|
121
|
+
"<ul><li>Brushed fleece interior</li><li>Full-length YKK zipper</li><li>Split kangaroo front pockets</li></ul>",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
key: "cap",
|
|
125
|
+
name: "Logo Cap",
|
|
126
|
+
type: "simple",
|
|
127
|
+
status: "publish",
|
|
128
|
+
categories: ["accessories"],
|
|
129
|
+
regular_price: 14.99,
|
|
130
|
+
sku: "DEMO-CAP",
|
|
131
|
+
sold_individually: true,
|
|
132
|
+
manage_stock: true,
|
|
133
|
+
stock_quantity: 40,
|
|
134
|
+
images: gallery("Logo Cap", ["photo-1588850561407-ed78c282e89b", "photo-1575428652377-a2d80e2277fc"]),
|
|
135
|
+
short_description: "One per customer — adjustable embroidered logo cap.",
|
|
136
|
+
description:
|
|
137
|
+
"<p>A six-panel cotton cap with a low-profile fit and an embroidered front logo. Sold individually.</p>" +
|
|
138
|
+
"<ul><li>100% cotton twill</li><li>Adjustable metal buckle strap</li><li>Curved, pre-shaped brim</li></ul>",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
key: "stickers",
|
|
142
|
+
name: "Sticker Pack",
|
|
143
|
+
type: "simple",
|
|
144
|
+
status: "publish",
|
|
145
|
+
categories: ["accessories"],
|
|
146
|
+
regular_price: 4.99,
|
|
147
|
+
sku: "DEMO-STICKERS",
|
|
148
|
+
manage_stock: true,
|
|
149
|
+
stock_quantity: 200,
|
|
150
|
+
images: gallery("Sticker Pack", ["photo-1452860606245-08befc0ff44b"]),
|
|
151
|
+
short_description: "10 assorted weatherproof vinyl stickers.",
|
|
152
|
+
description:
|
|
153
|
+
"<p>Ten assorted die-cut vinyl stickers — perfect for laptops, water bottles and notebooks.</p>" +
|
|
154
|
+
"<ul><li>Weatherproof, UV-resistant vinyl</li><li>Dishwasher-safe adhesive</li><li>Residue-free removal</li></ul>",
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
key: "album",
|
|
158
|
+
name: "Demo Album (Digital Download)",
|
|
159
|
+
type: "simple",
|
|
160
|
+
status: "publish",
|
|
161
|
+
categories: ["downloads"],
|
|
162
|
+
regular_price: 9.99,
|
|
163
|
+
sku: "DEMO-ALBUM",
|
|
164
|
+
virtual: true,
|
|
165
|
+
downloadable: true,
|
|
166
|
+
downloads: [{ name: "Album ZIP", file_url: "https://example.com/downloads/demo-album.zip" }],
|
|
167
|
+
download_limit: 3,
|
|
168
|
+
download_expiry: 30,
|
|
169
|
+
images: gallery("Demo Album", ["photo-1619983081563-430f63602796"]),
|
|
170
|
+
short_description: "Instant digital download — ten tracks in high quality.",
|
|
171
|
+
description:
|
|
172
|
+
"<p>A demo digital product delivered as an instant download. Ten tracks in lossless quality, DRM-free.</p>" +
|
|
173
|
+
"<ul><li>Download limit: 3 times per purchase</li><li>Access expires 30 days after purchase</li><li>DRM-free FLAC + MP3</li></ul>",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
key: "poster",
|
|
177
|
+
name: "Art Print Poster",
|
|
178
|
+
type: "simple",
|
|
179
|
+
status: "publish",
|
|
180
|
+
categories: ["accessories"],
|
|
181
|
+
regular_price: 12.99,
|
|
182
|
+
sku: "DEMO-POSTER",
|
|
183
|
+
manage_stock: true,
|
|
184
|
+
stock_quantity: 3, // demonstrates the low-stock dashboard list
|
|
185
|
+
images: gallery("Art Print Poster", ["photo-1513519245088-0e12902e5a38", "photo-1541961017774-22349e4a1262"]),
|
|
186
|
+
short_description: "A3 gallery-grade art print — nearly sold out!",
|
|
187
|
+
description:
|
|
188
|
+
"<p>An A3 matte art print on heavyweight archival stock. A limited run, so grab one while stock lasts.</p>" +
|
|
189
|
+
"<ul><li>200 gsm acid-free matte paper</li><li>Fade-resistant giclée inks</li><li>Ships flat in a rigid mailer</li></ul>",
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
key: "vneck",
|
|
193
|
+
name: "V-Neck T-Shirt",
|
|
194
|
+
type: "variable",
|
|
195
|
+
status: "publish",
|
|
196
|
+
categories: ["clothing"],
|
|
197
|
+
sku: "DEMO-VNECK",
|
|
198
|
+
images: gallery("V-Neck T-Shirt", ["photo-1583743814966-8936f5b7be1a", "photo-1618453292610-4119319e5ac7"]),
|
|
199
|
+
short_description: "Fitted v-neck tee available in two colors and three sizes.",
|
|
200
|
+
description:
|
|
201
|
+
"<p>A fitted v-neck tee in a lightweight jersey knit, available across multiple colors and sizes.</p>" +
|
|
202
|
+
"<ul><li>Slim, tailored fit</li><li>Lightweight 150 gsm jersey</li><li>Six color/size combinations</li></ul>",
|
|
203
|
+
attributes: [
|
|
204
|
+
{ slug: "color", visible: true, variation: true, options: ["Red", "Blue"] },
|
|
205
|
+
{ slug: "size", visible: true, variation: true, options: ["S", "M", "L"] },
|
|
206
|
+
],
|
|
207
|
+
default_attributes: [{ slug: "color", option: "Red" }, { slug: "size", option: "M" }],
|
|
208
|
+
variations: [
|
|
209
|
+
{ options: { color: "Red", size: "S" }, regular_price: 21.99, stock_quantity: 10, image: colorImage("V-Neck T-Shirt", "Red") },
|
|
210
|
+
{ options: { color: "Red", size: "M" }, regular_price: 21.99, stock_quantity: 10, image: colorImage("V-Neck T-Shirt", "Red") },
|
|
211
|
+
{ options: { color: "Red", size: "L" }, regular_price: 23.99, stock_quantity: 10, image: colorImage("V-Neck T-Shirt", "Red") },
|
|
212
|
+
{ options: { color: "Blue", size: "S" }, regular_price: 21.99, stock_quantity: 10, image: colorImage("V-Neck T-Shirt", "Blue") },
|
|
213
|
+
{ options: { color: "Blue", size: "M" }, regular_price: 21.99, stock_quantity: 10, image: colorImage("V-Neck T-Shirt", "Blue") },
|
|
214
|
+
{ options: { color: "Blue", size: "L" }, regular_price: 23.99, stock_quantity: 10, image: colorImage("V-Neck T-Shirt", "Blue") },
|
|
215
|
+
],
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
key: "pullover",
|
|
219
|
+
name: "Colorblock Pullover Hoodie",
|
|
220
|
+
type: "variable",
|
|
221
|
+
status: "publish",
|
|
222
|
+
categories: ["clothing"],
|
|
223
|
+
sku: "DEMO-PULLOVER",
|
|
224
|
+
images: gallery("Colorblock Pullover Hoodie", ["photo-1617171594202-100a53bdfe04", "photo-1515886657613-9f3515b0c78f"]),
|
|
225
|
+
short_description: "Heavyweight pullover hoodie in three colors and three sizes.",
|
|
226
|
+
description:
|
|
227
|
+
"<p>A heavyweight brushed-back pullover hoodie with a double-layer hood and ribbed cuffs. Some combinations are on sale.</p>" +
|
|
228
|
+
"<ul><li>380 gsm brushed-back fleece</li><li>Double-layer drawcord hood</li><li>Nine color/size combinations</li></ul>",
|
|
229
|
+
attributes: [
|
|
230
|
+
{ slug: "color", visible: true, variation: true, options: ["Black", "Blue", "Green"] },
|
|
231
|
+
{ slug: "size", visible: true, variation: true, options: ["M", "L", "XL"] },
|
|
232
|
+
],
|
|
233
|
+
default_attributes: [{ slug: "color", option: "Black" }, { slug: "size", option: "L" }],
|
|
234
|
+
variations: [
|
|
235
|
+
{ options: { color: "Black", size: "M" }, regular_price: 49.99, stock_quantity: 12, image: colorImage("Colorblock Pullover Hoodie", "Black") },
|
|
236
|
+
{ options: { color: "Black", size: "L" }, regular_price: 49.99, stock_quantity: 12, image: colorImage("Colorblock Pullover Hoodie", "Black") },
|
|
237
|
+
{ options: { color: "Black", size: "XL" }, regular_price: 52.99, stock_quantity: 8, image: colorImage("Colorblock Pullover Hoodie", "Black") },
|
|
238
|
+
{ options: { color: "Blue", size: "M" }, regular_price: 49.99, sale_price: 42.99, stock_quantity: 10, image: colorImage("Colorblock Pullover Hoodie", "Blue") },
|
|
239
|
+
{ options: { color: "Blue", size: "L" }, regular_price: 49.99, sale_price: 42.99, stock_quantity: 10, image: colorImage("Colorblock Pullover Hoodie", "Blue") },
|
|
240
|
+
{ options: { color: "Blue", size: "XL" }, regular_price: 52.99, sale_price: 45.99, stock_quantity: 6, image: colorImage("Colorblock Pullover Hoodie", "Blue") },
|
|
241
|
+
{ options: { color: "Green", size: "M" }, regular_price: 49.99, stock_quantity: 9, image: colorImage("Colorblock Pullover Hoodie", "Green") },
|
|
242
|
+
{ options: { color: "Green", size: "L" }, regular_price: 49.99, stock_quantity: 9, image: colorImage("Colorblock Pullover Hoodie", "Green") },
|
|
243
|
+
{ options: { color: "Green", size: "XL" }, regular_price: 52.99, stock_quantity: 4, image: colorImage("Colorblock Pullover Hoodie", "Green") },
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
key: "tote",
|
|
248
|
+
name: "Canvas Tote Bag",
|
|
249
|
+
type: "variable",
|
|
250
|
+
status: "publish",
|
|
251
|
+
categories: ["accessories"],
|
|
252
|
+
sku: "DEMO-TOTE",
|
|
253
|
+
images: gallery("Canvas Tote Bag", ["photo-1544816155-12df9643f363"]),
|
|
254
|
+
short_description: "Sturdy canvas tote — a single-attribute variable product in three colors.",
|
|
255
|
+
description:
|
|
256
|
+
"<p>A roomy, sturdy cotton-canvas tote with reinforced handles. Demonstrates a variable product driven by a single attribute (color).</p>" +
|
|
257
|
+
"<ul><li>12 oz heavyweight cotton canvas</li><li>Reinforced 28 cm handles</li><li>Interior slip pocket</li></ul>",
|
|
258
|
+
attributes: [
|
|
259
|
+
{ slug: "color", visible: true, variation: true, options: ["Black", "Blue", "Green"] },
|
|
260
|
+
],
|
|
261
|
+
default_attributes: [{ slug: "color", option: "Black" }],
|
|
262
|
+
variations: [
|
|
263
|
+
{ options: { color: "Black" }, regular_price: 18.99, stock_quantity: 25, image: colorImage("Canvas Tote Bag", "Black") },
|
|
264
|
+
{ options: { color: "Blue" }, regular_price: 18.99, stock_quantity: 20, image: colorImage("Canvas Tote Bag", "Blue") },
|
|
265
|
+
{ options: { color: "Green" }, regular_price: 18.99, stock_quantity: 0, backorders: "notify", image: colorImage("Canvas Tote Bag", "Green") },
|
|
266
|
+
],
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
key: "mug",
|
|
270
|
+
name: "Partner Mug (External)",
|
|
271
|
+
type: "external",
|
|
272
|
+
status: "publish",
|
|
273
|
+
categories: ["accessories"],
|
|
274
|
+
regular_price: 11.99,
|
|
275
|
+
sku: "DEMO-MUG",
|
|
276
|
+
external_url: "https://example.com/partner/mug",
|
|
277
|
+
button_text: "Buy at partner store",
|
|
278
|
+
images: gallery("Partner Mug", ["photo-1514228742587-6b1558fcca3d", "photo-1516390118834-21602d501886"]),
|
|
279
|
+
short_description: "Sold on our partner's site — an external/affiliate product.",
|
|
280
|
+
description:
|
|
281
|
+
"<p>A 350 ml ceramic mug sold through our partner store. An example of an external/affiliate product whose buy button links off-site.</p>" +
|
|
282
|
+
"<ul><li>350 ml ceramic body</li><li>Dishwasher & microwave safe</li><li>Fulfilled by our partner</li></ul>",
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
key: "bundle",
|
|
286
|
+
name: "Starter Bundle",
|
|
287
|
+
type: "grouped",
|
|
288
|
+
status: "publish",
|
|
289
|
+
categories: ["clothing", "accessories"],
|
|
290
|
+
grouped_keys: ["tshirt", "cap", "stickers"], // resolved to grouped_products ids at seed time
|
|
291
|
+
images: gallery("Starter Bundle", ["photo-1479064555552-3ef4979f8908"]),
|
|
292
|
+
short_description: "Tee, cap and sticker pack together in one grouped listing.",
|
|
293
|
+
description:
|
|
294
|
+
"<p>A grouped product that bundles the Classic T-Shirt, Logo Cap and Sticker Pack into a single listing. Customers pick quantities for each item.</p>",
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
key: "beanie",
|
|
298
|
+
name: "Wool Beanie",
|
|
299
|
+
type: "simple",
|
|
300
|
+
status: "publish",
|
|
301
|
+
categories: ["accessories"],
|
|
302
|
+
regular_price: 16.99,
|
|
303
|
+
sku: "DEMO-BEANIE",
|
|
304
|
+
manage_stock: true,
|
|
305
|
+
stock_quantity: 0,
|
|
306
|
+
backorders: "notify", // demonstrates onbackorder status
|
|
307
|
+
images: gallery("Wool Beanie", ["photo-1576871337632-b9aef4c17ab9", "photo-1510598969022-c4c6c5d05769"]),
|
|
308
|
+
short_description: "Out of stock — available on backorder.",
|
|
309
|
+
description:
|
|
310
|
+
"<p>A warm, double-layer knit beanie in a soft merino-blend yarn. Currently out of stock but available to order on backorder.</p>" +
|
|
311
|
+
"<ul><li>Merino wool blend</li><li>Double-layer ribbed knit</li><li>One size, stretch fit</li></ul>",
|
|
312
|
+
},
|
|
313
|
+
];
|
|
314
|
+
|
|
315
|
+
export const SAMPLE_COUPONS = [
|
|
316
|
+
{
|
|
317
|
+
code: "welcome10",
|
|
318
|
+
discount_type: "percent",
|
|
319
|
+
amount: 10,
|
|
320
|
+
description: "10% off your first order (demo coupon)",
|
|
321
|
+
free_shipping: false,
|
|
322
|
+
individual_use: true,
|
|
323
|
+
usage_limit_per_user: 1,
|
|
324
|
+
usage_count: 0,
|
|
325
|
+
used_by: [] as string[],
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
code: "save5",
|
|
329
|
+
discount_type: "fixed_cart",
|
|
330
|
+
amount: 5,
|
|
331
|
+
description: "$5 off orders over $25 (demo coupon)",
|
|
332
|
+
minimum_amount: 25,
|
|
333
|
+
usage_count: 0,
|
|
334
|
+
used_by: [] as string[],
|
|
335
|
+
},
|
|
336
|
+
];
|
|
337
|
+
|
|
338
|
+
export const SAMPLE_TAX_RATES = [
|
|
339
|
+
{
|
|
340
|
+
country: "US", state: "CA", postcodes: [] as string[], cities: [] as string[],
|
|
341
|
+
rate: 7.25, name: "CA State Tax", priority: 1, compound: false, shipping: true,
|
|
342
|
+
tax_class: "standard", menu_order: 0,
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
country: "GB", state: "", postcodes: [] as string[], cities: [] as string[],
|
|
346
|
+
rate: 20, name: "VAT", priority: 1, compound: false, shipping: true,
|
|
347
|
+
tax_class: "standard", menu_order: 1,
|
|
348
|
+
},
|
|
349
|
+
];
|