@wix/agent-skills 1.0.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 +111 -0
- package/package.json +29 -0
- package/skills/wix-app/SKILL.md +344 -0
- package/skills/wix-app/references/APP_IDENTIFIERS.md +35 -0
- package/skills/wix-app/references/APP_MARKET_REVIEW.md +311 -0
- package/skills/wix-app/references/APP_VALIDATION.md +171 -0
- package/skills/wix-app/references/BACKEND_API.md +233 -0
- package/skills/wix-app/references/BACKEND_EVENT.md +116 -0
- package/skills/wix-app/references/CODE_QUALITY.md +33 -0
- package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +587 -0
- package/skills/wix-app/references/DASHBOARD_MENU_PLUGIN.md +157 -0
- package/skills/wix-app/references/DASHBOARD_MODAL.md +283 -0
- package/skills/wix-app/references/DASHBOARD_PAGE.md +403 -0
- package/skills/wix-app/references/DASHBOARD_PLUGIN.md +205 -0
- package/skills/wix-app/references/DATA_COLLECTION.md +451 -0
- package/skills/wix-app/references/DOCUMENTATION.md +42 -0
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +65 -0
- package/skills/wix-app/references/EMBEDDED_SCRIPT.md +468 -0
- package/skills/wix-app/references/EXTENSION_REGISTRATION.md +96 -0
- package/skills/wix-app/references/SERVICE_PLUGIN.md +235 -0
- package/skills/wix-app/references/SITE_PLUGIN.md +436 -0
- package/skills/wix-app/references/STORES_VERSIONING.md +334 -0
- package/skills/wix-app/references/backend-event/COMMON-EVENTS.md +84 -0
- package/skills/wix-app/references/custom-element-widget/SETTINGS_PANEL.md +345 -0
- package/skills/wix-app/references/dashboard-menu-plugin/blog-slots.md +71 -0
- package/skills/wix-app/references/dashboard-menu-plugin/bookings-slots.md +58 -0
- package/skills/wix-app/references/dashboard-menu-plugin/crm-slots.md +8 -0
- package/skills/wix-app/references/dashboard-menu-plugin/ecommerce-slots.md +17 -0
- package/skills/wix-app/references/dashboard-menu-plugin/events-slots.md +29 -0
- package/skills/wix-app/references/dashboard-menu-plugin/restaurants-slots.md +27 -0
- package/skills/wix-app/references/dashboard-menu-plugin/stores-slots.md +35 -0
- package/skills/wix-app/references/dashboard-page/API_SPEC.md +138 -0
- package/skills/wix-app/references/dashboard-page/DASHBOARD_API.md +573 -0
- package/skills/wix-app/references/dashboard-page/DYNAMIC_PARAMETERS.md +269 -0
- package/skills/wix-app/references/dashboard-page/ECOM_NAVIGATION.md +164 -0
- package/skills/wix-app/references/dashboard-plugin/SLOTS.md +294 -0
- package/skills/wix-app/references/data-collection/WIX_DATA.md +270 -0
- package/skills/wix-app/references/data-collection/extension-template.ts +107 -0
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +138 -0
- package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +454 -0
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONFIGURATION.md +165 -0
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +576 -0
- package/skills/wix-app/references/editor-react-component/DIRECTIONALITY.md +170 -0
- package/skills/wix-app/references/editor-react-component/EDIT-FLOW.md +6 -0
- package/skills/wix-app/references/editor-react-component/PARTS.md +25 -0
- package/skills/wix-app/references/editor-react-component/PROPS-VS-CSS.md +203 -0
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +167 -0
- package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +137 -0
- package/skills/wix-app/references/service-plugin/ADDITIONAL-FEES.md +112 -0
- package/skills/wix-app/references/service-plugin/BOOKINGS-STAFF-SORTING.md +118 -0
- package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +87 -0
- package/skills/wix-app/references/service-plugin/GIFT-CARDS.md +86 -0
- package/skills/wix-app/references/service-plugin/PAYMENT-SETTINGS.md +135 -0
- package/skills/wix-app/references/service-plugin/SHIPPING-RATES.md +79 -0
- package/skills/wix-app/references/service-plugin/VALIDATIONS.md +68 -0
- package/skills/wix-app/references/site-plugin/EXAMPLES.md +311 -0
- package/skills/wix-app/references/site-plugin/SLOTS.md +263 -0
- package/skills/wix-design-system/SKILL.md +129 -0
- package/skills/wix-design-system/references/file-structure.md +56 -0
- package/skills/wix-design-system/scripts/wds.cjs +531 -0
- package/skills/wix-headless/SKILL.md +418 -0
- package/skills/wix-headless/references/DISCOVERY.md +332 -0
- package/skills/wix-headless/references/ORCHESTRATION.md +269 -0
- package/skills/wix-headless/references/SETUP.md +154 -0
- package/skills/wix-headless/references/blog/BLOG_CONTENT.md +152 -0
- package/skills/wix-headless/references/blog/BLOG_PAGES.md +299 -0
- package/skills/wix-headless/references/blog/BLOG_SETUP.md +218 -0
- package/skills/wix-headless/references/blog/INSTRUCTIONS.md +32 -0
- package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +373 -0
- package/skills/wix-headless/references/cms/FAQ_KNOWLEDGE_BASE.md +229 -0
- package/skills/wix-headless/references/cms/INSTRUCTIONS.md +40 -0
- package/skills/wix-headless/references/cms/PORTFOLIO.md +454 -0
- package/skills/wix-headless/references/cms/RESOURCE_LIBRARY.md +356 -0
- package/skills/wix-headless/references/cms/TEAM_DIRECTORY.md +299 -0
- package/skills/wix-headless/references/commands/install-app.md +42 -0
- package/skills/wix-headless/references/commands/known-apps.json +5 -0
- package/skills/wix-headless/references/commands/mcp-bootstrap.md +28 -0
- package/skills/wix-headless/references/designer/INSTRUCTIONS.md +828 -0
- package/skills/wix-headless/references/ecom/CART_PAGES.md +120 -0
- package/skills/wix-headless/references/ecom/CART_WIRING.md +103 -0
- package/skills/wix-headless/references/ecom/COMPONENTS_CSS.md +132 -0
- package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +59 -0
- package/skills/wix-headless/references/forms/CONTACT_FORM.md +477 -0
- package/skills/wix-headless/references/forms/INSTRUCTIONS.md +43 -0
- package/skills/wix-headless/references/gift-cards/COMPONENTS.md +119 -0
- package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +51 -0
- package/skills/wix-headless/references/gift-cards/PAGES.md +129 -0
- package/skills/wix-headless/references/images/INSTRUCTIONS.md +359 -0
- package/skills/wix-headless/references/shared/BUILD_NOISE.md +8 -0
- package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +185 -0
- package/skills/wix-headless/references/shared/IMPLEMENTER.md +155 -0
- package/skills/wix-headless/references/shared/MCP_PREFIX.md +105 -0
- package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +97 -0
- package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +423 -0
- package/skills/wix-headless/references/shared/STYLING.md +117 -0
- package/skills/wix-headless/references/stores/BACK_IN_STOCK.md +178 -0
- package/skills/wix-headless/references/stores/CATEGORY_PAGES.md +86 -0
- package/skills/wix-headless/references/stores/COMPONENTS_CSS.md +129 -0
- package/skills/wix-headless/references/stores/HOME_AND_NAV.md +227 -0
- package/skills/wix-headless/references/stores/INSTRUCTIONS.md +96 -0
- package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +249 -0
- package/skills/wix-headless/references/stores/PRODUCT_PAGES.md +213 -0
- package/skills/wix-headless/references/stores/SHARED_WIRING.md +164 -0
- package/skills/wix-headless/references/verticals/_schema.json +160 -0
- package/skills/wix-headless/references/verticals/_schema.md +185 -0
- package/skills/wix-headless/references/verticals/blog.md +125 -0
- package/skills/wix-headless/references/verticals/cms.md +121 -0
- package/skills/wix-headless/references/verticals/ecom.md +96 -0
- package/skills/wix-headless/references/verticals/forms.md +106 -0
- package/skills/wix-headless/references/verticals/gift-cards.md +121 -0
- package/skills/wix-headless/references/verticals/stores.md +167 -0
- package/skills/wix-headless/scripts/check-manifest.mjs +198 -0
- package/skills/wix-headless/scripts/copy-utility-templates.mjs +153 -0
- package/skills/wix-headless/scripts/emit-design-tokens.mjs +77 -0
- package/skills/wix-headless/scripts/finalize-run-json.mjs +141 -0
- package/skills/wix-headless/scripts/init-site-json.mjs +63 -0
- package/skills/wix-headless/scripts/patch-decorative-slots.mjs +124 -0
- package/skills/wix-headless/scripts/plan-entity-image-waves.mjs +190 -0
- package/skills/wix-headless/scripts/preview.sh +45 -0
- package/skills/wix-headless/scripts/release.sh +73 -0
- package/skills/wix-headless/scripts/scaffold.sh +53 -0
- package/skills/wix-headless/scripts/seed-utilities.sh +55 -0
- package/skills/wix-headless/shared-utilities/analytics.ts +40 -0
- package/skills/wix-headless/shared-utilities/ricos.ts +179 -0
- package/skills/wix-headless/shared-utilities/wix-image.ts +72 -0
- package/skills/wix-headless/templates/ecom/CartBadge.tsx +84 -0
- package/skills/wix-headless/templates/ecom/CartView.tsx +543 -0
- package/skills/wix-headless/templates/ecom/cart.astro +13 -0
- package/skills/wix-headless/templates/ecom/components-ecom.css +215 -0
- package/skills/wix-headless/templates/ecom/discounts.ts +71 -0
- package/skills/wix-headless/templates/ecom/thank-you.astro +24 -0
- package/skills/wix-headless/templates/gift-cards/GiftCardPurchase.tsx +172 -0
- package/skills/wix-headless/templates/gift-cards/_home-teaser-snippet.astro +42 -0
- package/skills/wix-headless/templates/gift-cards/_nav-snippet.astro +11 -0
- package/skills/wix-headless/templates/gift-cards/components-gift-cards.css +219 -0
- package/skills/wix-headless/templates/gift-cards/gift-cards.astro +51 -0
- package/skills/wix-headless/templates/gift-cards/gift-cards.ts +75 -0
- package/skills/wix-headless/templates/stores/AddToCartButton.tsx +77 -0
- package/skills/wix-headless/templates/stores/BackInStockForm.tsx +176 -0
- package/skills/wix-headless/templates/stores/CategoryRail.astro +69 -0
- package/skills/wix-headless/templates/stores/ProductCard.astro +79 -0
- package/skills/wix-headless/templates/stores/ProductPurchase.tsx +397 -0
- package/skills/wix-headless/templates/stores/SeoTags.astro +21 -0
- package/skills/wix-headless/templates/stores/back-in-stock.ts +67 -0
- package/skills/wix-headless/templates/stores/categories.ts +155 -0
- package/skills/wix-headless/templates/stores/category/[slug].astro +130 -0
- package/skills/wix-headless/templates/stores/components-stores.css +295 -0
- package/skills/wix-headless/templates/stores/products/[slug].astro +128 -0
- package/skills/wix-headless/templates/stores/products/index.astro +101 -0
- package/skills/wix-manage/SKILL.md +280 -0
- package/skills/wix-manage/references/app-installation/install-wix-apps.md +155 -0
- package/skills/wix-manage/references/app-installation/list-installed-apps.md +124 -0
- package/skills/wix-manage/references/blog/how-to-create-blog-posts.md +226 -0
- package/skills/wix-manage/references/bookings/booking-service-policy-setup.md +95 -0
- package/skills/wix-manage/references/bookings/booking-system-integration-gaps.md +416 -0
- package/skills/wix-manage/references/bookings/bookings-staff-setup.md +255 -0
- package/skills/wix-manage/references/bookings/create-and-update-booking-services.md +462 -0
- package/skills/wix-manage/references/bookings/end-to-end-booking-flow.md +278 -0
- package/skills/wix-manage/references/bookings/external-calendar-integration.md +258 -0
- package/skills/wix-manage/references/bookings/multi-resource-service-creation.md +231 -0
- package/skills/wix-manage/references/calendar/configure-default-business-hours.md +226 -0
- package/skills/wix-manage/references/cms/cms-data-items-crud.md +422 -0
- package/skills/wix-manage/references/cms/cms-data-operations-extended.md +263 -0
- package/skills/wix-manage/references/cms/cms-ecommerce-catalog-integration.md +136 -0
- package/skills/wix-manage/references/cms/cms-references-and-relationships.md +160 -0
- package/skills/wix-manage/references/cms/cms-schema-management.md +143 -0
- package/skills/wix-manage/references/contacts/bulk-delete-contacts.md +73 -0
- package/skills/wix-manage/references/contacts/bulk-label-and-unlabel-contacts.md +67 -0
- package/skills/wix-manage/references/domains/domain-search-and-purchase.md +434 -0
- package/skills/wix-manage/references/ecommerce/api-recommendation-tracking.md +420 -0
- package/skills/wix-manage/references/ecommerce/flow-add-free-shipping.md +267 -0
- package/skills/wix-manage/references/ecommerce/flow-bundle-and-save.md +290 -0
- package/skills/wix-manage/references/ecommerce/flow-fix-coverage-gaps.md +300 -0
- package/skills/wix-manage/references/ecommerce/flow-optimize-shipping-rates.md +364 -0
- package/skills/wix-manage/references/ecommerce/flow-seasonal-promotion.md +315 -0
- package/skills/wix-manage/references/ecommerce/flow-stock-mover.md +308 -0
- package/skills/wix-manage/references/ecommerce/flow-upsell-boost.md +271 -0
- package/skills/wix-manage/references/ecommerce/goal-clear-inventory.md +118 -0
- package/skills/wix-manage/references/ecommerce/goal-drive-cross-sells.md +122 -0
- package/skills/wix-manage/references/ecommerce/goal-increase-aov.md +132 -0
- package/skills/wix-manage/references/ecommerce/goal-reduce-cart-abandonment.md +148 -0
- package/skills/wix-manage/references/ecommerce/goal-seasonal-revenue.md +146 -0
- package/skills/wix-manage/references/ecommerce/guardrail-discount-conflicts.md +118 -0
- package/skills/wix-manage/references/ecommerce/guardrail-margin-protection.md +88 -0
- package/skills/wix-manage/references/ecommerce/guardrail-rate-pricing-sanity.md +108 -0
- package/skills/wix-manage/references/ecommerce/guardrail-shipping-health.md +124 -0
- package/skills/wix-manage/references/ecommerce/recipe-apply-shipping-recommendations.md +299 -0
- package/skills/wix-manage/references/ecommerce/recommend-ecommerce-strategy.md +582 -0
- package/skills/wix-manage/references/ecommerce/setup-coupons.md +338 -0
- package/skills/wix-manage/references/ecommerce/setup-discount-rules.md +444 -0
- package/skills/wix-manage/references/ecommerce/setup-shipping-rates.md +86 -0
- package/skills/wix-manage/references/ecommerce/setup-shipping-regions.md +56 -0
- package/skills/wix-manage/references/ecommerce/setup-store-pickup-location.md +209 -0
- package/skills/wix-manage/references/ecommerce/skill-graph.md +139 -0
- package/skills/wix-manage/references/ecommerce/troubleshoot-checkout-delivery-dropoff.md +124 -0
- package/skills/wix-manage/references/ecommerce/troubleshoot-discount-not-applying.md +95 -0
- package/skills/wix-manage/references/events/list-events.md +203 -0
- package/skills/wix-manage/references/forms/create-form.md +260 -0
- package/skills/wix-manage/references/get-paid/create-payment-links.md +212 -0
- package/skills/wix-manage/references/get-paid/how-to-setup-wix-payments.md +57 -0
- package/skills/wix-manage/references/get-paid/payment-links-for-bookings.md +101 -0
- package/skills/wix-manage/references/media/upload-media-to-wix.md +216 -0
- package/skills/wix-manage/references/pricing-plans/create-and-update-pricing-plans.md +77 -0
- package/skills/wix-manage/references/pricing-plans/pricing-plans-bookings-integration.md +166 -0
- package/skills/wix-manage/references/restaurants/wix-restaurants-setup.md +336 -0
- package/skills/wix-manage/references/rich-content/ricos-converter-service.md +295 -0
- package/skills/wix-manage/references/site-properties/change-payment-currency-site-properties.md +59 -0
- package/skills/wix-manage/references/sites/create-site-from-template.md +183 -0
- package/skills/wix-manage/references/sites/query-sites.md +125 -0
- package/skills/wix-manage/references/stores/add-store-pages-to-site.md +57 -0
- package/skills/wix-manage/references/stores/bulk-create-products-with-options.md +1041 -0
- package/skills/wix-manage/references/stores/create-product-catalog-v1.md +125 -0
- package/skills/wix-manage/references/stores/create-product-from-image.md +824 -0
- package/skills/wix-manage/references/stores/create-product-with-options-catalog-v3.md +566 -0
- package/skills/wix-manage/references/stores/find-products-query-and-search-catalog-v3.md +194 -0
- package/skills/wix-manage/references/stores/query-products-catalog-v1.md +80 -0
- package/skills/wix-manage/references/stores/setup-online-store-catalog-v3.md +99 -0
- package/skills/wix-manage/references/stores/update-product-pre-order.md +213 -0
- package/skills/wix-manage/references/stores/update-product-with-options.md +318 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/* src/styles/components-ecom.css
|
|
2
|
+
*
|
|
3
|
+
* Ecom-pack component CSS. Owned by the ecom Phase 3 components agent.
|
|
4
|
+
*
|
|
5
|
+
* This file is the SINGLE source of truth for ecom-component classes.
|
|
6
|
+
* The designer's global.css does NOT publish .cart-summary, .cart-total,
|
|
7
|
+
* .cart-empty, or .checkout-btn — those all live here. See
|
|
8
|
+
* references/shared/STYLING.md § "Component-specific CSS is owned by the
|
|
9
|
+
* component, not the designer".
|
|
10
|
+
*
|
|
11
|
+
* Class groups in this file:
|
|
12
|
+
* - .cart-summary / .cart-total / .cart-empty / .checkout-btn → /cart page summary panel and empty state
|
|
13
|
+
* - .cart-item / .cart-item-* / .qty-* → CartView.tsx line items
|
|
14
|
+
* - .cart-discount / .cart-applied-discounts → CartView.tsx discount rows
|
|
15
|
+
* - .cart-badge / .cart-badge-* → CartBadge.tsx (mounted in Navigation)
|
|
16
|
+
*
|
|
17
|
+
* Tailwind v4 requires @reference in non-entrypoint CSS files that use @apply
|
|
18
|
+
* — without these, utilities like `size-6` fail at build with "Cannot apply
|
|
19
|
+
* unknown utility class". Do not remove.
|
|
20
|
+
*/
|
|
21
|
+
@reference "tailwindcss";
|
|
22
|
+
@reference "./global.css";
|
|
23
|
+
|
|
24
|
+
/* =========================================================================
|
|
25
|
+
Cart summary panel + total + checkout button + empty state.
|
|
26
|
+
Owned here, NOT in global.css. The .checkout-btn extends .btn-primary
|
|
27
|
+
(which IS designer-owned) and adds full-width + :disabled treatment.
|
|
28
|
+
========================================================================= */
|
|
29
|
+
.cart-summary {
|
|
30
|
+
background: var(--color-paper-warm, var(--color-cream));
|
|
31
|
+
border: 1px solid var(--color-rule);
|
|
32
|
+
padding: var(--spacing-xl);
|
|
33
|
+
border-radius: var(--radius-md);
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
gap: var(--spacing-sm);
|
|
37
|
+
}
|
|
38
|
+
@media (min-width: 768px) {
|
|
39
|
+
.cart-summary {
|
|
40
|
+
position: sticky;
|
|
41
|
+
top: var(--spacing-xl);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.cart-empty {
|
|
46
|
+
text-align: center;
|
|
47
|
+
padding-block: var(--spacing-3xl);
|
|
48
|
+
color: var(--color-mute);
|
|
49
|
+
}
|
|
50
|
+
.cart-empty .checkout-btn {
|
|
51
|
+
display: inline-flex;
|
|
52
|
+
width: auto;
|
|
53
|
+
max-width: 18rem;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* .checkout-btn composes the designer's `btn` @utility primitive plus the brand's
|
|
57
|
+
primary fill. Do NOT use `@apply btn-primary` here — Tailwind v4 only @applies
|
|
58
|
+
utilities declared with `@utility` (which `btn` is in global.css), not plain
|
|
59
|
+
custom classes like `.btn-primary` (which is just a regular CSS rule). The build
|
|
60
|
+
fails with "Cannot apply unknown utility class `btn-primary`" if you try.
|
|
61
|
+
The Phase 3 agent's brand-token swap (bark → navy/charcoal/coral/etc.) adapts
|
|
62
|
+
the var names without changing the composition shape. an earlier run on 2026-04-30 hit
|
|
63
|
+
this; the recovery cost ~1 min and a rebuild. */
|
|
64
|
+
.checkout-btn {
|
|
65
|
+
@apply btn;
|
|
66
|
+
background: var(--color-bark);
|
|
67
|
+
color: var(--color-cream);
|
|
68
|
+
box-shadow: var(--shadow-sm);
|
|
69
|
+
width: 100%;
|
|
70
|
+
margin-top: var(--spacing-md);
|
|
71
|
+
}
|
|
72
|
+
.checkout-btn:hover:not(:disabled) {
|
|
73
|
+
background: var(--color-tobacco);
|
|
74
|
+
box-shadow: var(--shadow-md);
|
|
75
|
+
}
|
|
76
|
+
.checkout-btn:disabled {
|
|
77
|
+
opacity: 0.5;
|
|
78
|
+
cursor: not-allowed;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* --- Cart badge icon safety net --- */
|
|
82
|
+
/* CartBadge.tsx renders an inline SVG bag icon + optional count span. The designer
|
|
83
|
+
owns .cart-badge in global.css; these rules guarantee an icon-button layout
|
|
84
|
+
(relative positioning anchor, centered icon, count pill overlap) even if the
|
|
85
|
+
designer's rule is purely aesthetic. */
|
|
86
|
+
.cart-badge {
|
|
87
|
+
@apply relative inline-flex items-center justify-center;
|
|
88
|
+
}
|
|
89
|
+
.cart-badge-icon {
|
|
90
|
+
@apply size-6 shrink-0;
|
|
91
|
+
}
|
|
92
|
+
.cart-badge-count {
|
|
93
|
+
@apply absolute -top-1 -right-2 min-w-5 h-5 px-1 inline-flex items-center justify-center rounded-full bg-bark text-cream text-xs font-body tabular-nums leading-none;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* --- Cart layout safety net --- */
|
|
97
|
+
/* .cart-item is a global contract class styled by the designer in global.css.
|
|
98
|
+
These rules ensure the row layout works even if the designer omits flex. */
|
|
99
|
+
.cart-item {
|
|
100
|
+
@apply flex flex-row gap-md;
|
|
101
|
+
}
|
|
102
|
+
.cart-item-image {
|
|
103
|
+
@apply shrink-0;
|
|
104
|
+
}
|
|
105
|
+
/* Clickable image + name link back to /products/<slug>.
|
|
106
|
+
At-rest underline + pointer cursor so the link is discoverable without
|
|
107
|
+
hovering (critical on touch). Muted rule-color underline keeps visual
|
|
108
|
+
weight in line with the body text; hover/focus escalates to tobacco. */
|
|
109
|
+
.cart-item-image-link {
|
|
110
|
+
@apply block shrink-0 cursor-pointer;
|
|
111
|
+
}
|
|
112
|
+
.cart-item-image-link:hover .cart-item-image,
|
|
113
|
+
.cart-item-image-link:focus-visible .cart-item-image {
|
|
114
|
+
@apply border-ink;
|
|
115
|
+
}
|
|
116
|
+
.cart-item-name-link {
|
|
117
|
+
@apply text-inherit cursor-pointer no-underline;
|
|
118
|
+
/* border-bottom is more predictable than text-decoration-line for inline
|
|
119
|
+
<a> inside <h3> (no text-decoration inheritance quirks). Muted ink at
|
|
120
|
+
rest, tobacco on hover. */
|
|
121
|
+
border-bottom: 1px solid var(--color-muted);
|
|
122
|
+
padding-bottom: 1px;
|
|
123
|
+
transition: color 150ms ease, border-color 150ms ease;
|
|
124
|
+
}
|
|
125
|
+
.cart-item-name-link:hover,
|
|
126
|
+
.cart-item-name-link:focus-visible {
|
|
127
|
+
@apply text-tobacco outline-none;
|
|
128
|
+
border-bottom-color: var(--color-tobacco);
|
|
129
|
+
}
|
|
130
|
+
.cart-item-info {
|
|
131
|
+
@apply flex-1 min-w-0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* --- Description lines & modifiers --- */
|
|
135
|
+
.cart-item-option {
|
|
136
|
+
@apply text-sm text-bark/70 font-body my-0.5;
|
|
137
|
+
}
|
|
138
|
+
.cart-item-modifiers {
|
|
139
|
+
@apply mt-1;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* --- Quantity stepper --- */
|
|
143
|
+
.cart-item-qty {
|
|
144
|
+
@apply inline-flex items-center gap-sm mt-sm w-fit;
|
|
145
|
+
}
|
|
146
|
+
.qty-btn {
|
|
147
|
+
@apply size-7 inline-flex items-center justify-center border border-bark rounded bg-transparent text-bark cursor-pointer font-body text-base leading-none;
|
|
148
|
+
}
|
|
149
|
+
.qty-btn:disabled {
|
|
150
|
+
@apply opacity-30 cursor-not-allowed;
|
|
151
|
+
}
|
|
152
|
+
.qty-value {
|
|
153
|
+
@apply font-body text-sm min-w-6 text-center tabular-nums;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* --- Unavailable state --- */
|
|
157
|
+
.cart-item-unavailable {
|
|
158
|
+
@apply text-sm text-red-600 font-medium font-body;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* --- Price + remove --- */
|
|
162
|
+
.cart-item-actions {
|
|
163
|
+
@apply flex flex-col items-end gap-1 shrink-0;
|
|
164
|
+
}
|
|
165
|
+
.cart-item-prices {
|
|
166
|
+
@apply flex items-center gap-sm;
|
|
167
|
+
}
|
|
168
|
+
.cart-item-full-price {
|
|
169
|
+
@apply text-sm text-bark/70 line-through font-body;
|
|
170
|
+
}
|
|
171
|
+
.cart-item-unit-price {
|
|
172
|
+
@apply font-body font-semibold whitespace-nowrap;
|
|
173
|
+
}
|
|
174
|
+
.cart-item-line-total {
|
|
175
|
+
@apply font-body text-xs text-bark/70 m-0;
|
|
176
|
+
}
|
|
177
|
+
.cart-item-remove {
|
|
178
|
+
@apply text-sm bg-transparent border-none text-bark/60 font-body cursor-pointer underline;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* --- Cart summary: discount + total rows ---
|
|
182
|
+
Subtotal uses the designer's .cart-subtotal contract class in global.css.
|
|
183
|
+
These rows appear only when priceSummary exposes a discount or a total
|
|
184
|
+
that differs from subtotal (see CartView.tsx extractSummary). */
|
|
185
|
+
.cart-discount {
|
|
186
|
+
@apply flex items-baseline justify-between gap-sm font-body text-sm mt-sm;
|
|
187
|
+
}
|
|
188
|
+
.cart-discount-name {
|
|
189
|
+
@apply text-xs text-bark/60;
|
|
190
|
+
}
|
|
191
|
+
.cart-discount-amount {
|
|
192
|
+
@apply whitespace-nowrap;
|
|
193
|
+
}
|
|
194
|
+
.cart-total {
|
|
195
|
+
border-top: 1px solid var(--color-rule);
|
|
196
|
+
padding-top: var(--spacing-md);
|
|
197
|
+
margin-top: var(--spacing-sm);
|
|
198
|
+
font-family: var(--font-display);
|
|
199
|
+
font-size: 1.25rem;
|
|
200
|
+
font-weight: 500;
|
|
201
|
+
display: flex;
|
|
202
|
+
justify-content: space-between;
|
|
203
|
+
align-items: baseline;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* Applied-discounts row — name-only (no amount) for cases where the API
|
|
207
|
+
reports a promo NAME via appliedDiscounts[].discountName but no cart-level
|
|
208
|
+
amount in priceSummary.discount (common for line-item automatic discounts
|
|
209
|
+
— the savings already show inline via fullPrice/price strikethrough). */
|
|
210
|
+
.cart-applied-discounts {
|
|
211
|
+
@apply flex items-baseline justify-between gap-sm font-body text-sm mt-sm;
|
|
212
|
+
}
|
|
213
|
+
.cart-applied-discounts-name {
|
|
214
|
+
@apply text-right text-bark/80 italic;
|
|
215
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Live discount-rule fetch + per-product matching for storefront indicators.
|
|
2
|
+
// Owned by the ecom pack because Wix Discount Rules is an @wix/ecom primitive.
|
|
3
|
+
// Consumers: stores pack (ProductCard ribbon, product-detail offer callout,
|
|
4
|
+
// home featured grid). Future ecom-using verticals (bookings, events) can
|
|
5
|
+
// reuse the same util without change.
|
|
6
|
+
//
|
|
7
|
+
// Gotchas encoded here — do not remove:
|
|
8
|
+
// 1. queryDiscountRules needs ECOM.DISCOUNT_RULES_READ — must be called via
|
|
9
|
+
// auth.elevate(), otherwise a visitor client returns no rules silently.
|
|
10
|
+
// 2. Scope objects are returned with `_id` (underscore), not `id`. Check both.
|
|
11
|
+
// 3. "All products" scope shape: { _id: "all_<appDefId>", catalogItemFilter:
|
|
12
|
+
// { catalogAppId, catalogItemIds: [] } }. Empty array + app id = applies
|
|
13
|
+
// to every product.
|
|
14
|
+
|
|
15
|
+
import { discountRules } from "@wix/ecom";
|
|
16
|
+
import { auth } from "@wix/essentials";
|
|
17
|
+
|
|
18
|
+
export interface DiscountOffer {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
offer: string;
|
|
22
|
+
targetType: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function applies(scopes: any[] | undefined, productId: string): boolean {
|
|
26
|
+
if (!scopes || scopes.length === 0) return false;
|
|
27
|
+
for (const s of scopes) {
|
|
28
|
+
const scopeId = s?._id ?? s?.id;
|
|
29
|
+
if (typeof scopeId === "string" && scopeId.startsWith("all_")) return true;
|
|
30
|
+
const ids = s?.catalogItemFilter?.catalogItemIds;
|
|
31
|
+
if (Array.isArray(ids) && ids.length === 0 && s?.catalogItemFilter?.catalogAppId) return true;
|
|
32
|
+
if (Array.isArray(ids) && ids.includes(productId)) return true;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function ruleApplies(rule: any, productId: string): boolean {
|
|
38
|
+
const values = rule?.discounts?.values ?? [];
|
|
39
|
+
for (const v of values) {
|
|
40
|
+
if (v?.targetType === "SPECIFIC_ITEMS") {
|
|
41
|
+
if (applies(v?.specificItemsInfo?.scopes, productId)) return true;
|
|
42
|
+
} else if (v?.targetType === "BUY_X_GET_Y") {
|
|
43
|
+
if (applies(v?.buyXGetYInfo?.customerBuys?.scopes, productId)) return true;
|
|
44
|
+
if (applies(v?.buyXGetYInfo?.customerGets?.scopes, productId)) return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function fetchLiveOffers(): Promise<any[]> {
|
|
51
|
+
try {
|
|
52
|
+
const elevated = auth.elevate(discountRules.queryDiscountRules);
|
|
53
|
+
const res: any = await elevated({ paging: { limit: 50 } } as any);
|
|
54
|
+
const list = res?.discountRules ?? res?.items ?? [];
|
|
55
|
+
return list.filter((r: any) => r?.active !== false && r?.status !== "EXPIRED");
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error("[discounts] queryDiscountRules failed:", err);
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function offersForProduct(rules: any[], productId: string): DiscountOffer[] {
|
|
63
|
+
return rules
|
|
64
|
+
.filter((r) => ruleApplies(r, productId))
|
|
65
|
+
.map((r) => ({
|
|
66
|
+
id: r._id ?? r.id,
|
|
67
|
+
name: r.name ?? "",
|
|
68
|
+
offer: (r.offer ?? r.name ?? "").toString(),
|
|
69
|
+
targetType: r?.discounts?.values?.[0]?.targetType ?? "",
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Layout from "../layouts/Layout.astro";
|
|
3
|
+
|
|
4
|
+
const orderId = Astro.url.searchParams.get("orderId");
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<Layout title="Thank You">
|
|
8
|
+
<main class="py-4xl">
|
|
9
|
+
<div class="max-w-prose mx-auto px-xl text-center flex flex-col gap-lg">
|
|
10
|
+
<h1 class="font-display text-5xl">Thank You!</h1>
|
|
11
|
+
<p class="text-ink-soft">Your order has been placed successfully.</p>
|
|
12
|
+
{orderId && <p class="text-mute text-sm tracking-wide">Order ID: {orderId}</p>}
|
|
13
|
+
<a href="/products" class="btn-primary self-center mt-md">Continue Shopping</a>
|
|
14
|
+
</div>
|
|
15
|
+
</main>
|
|
16
|
+
</Layout>
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
import { trackEvent } from "../utils/analytics";
|
|
20
|
+
const orderId = new URLSearchParams(window.location.search).get("orderId");
|
|
21
|
+
if (orderId) {
|
|
22
|
+
trackEvent("Purchase", { id: orderId, origin: "Thank You Page" });
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { currentCart } from "@wix/ecom";
|
|
3
|
+
import { trackEvent } from "../utils/analytics";
|
|
4
|
+
import {
|
|
5
|
+
WIX_GIFT_CARDS_APP_ID,
|
|
6
|
+
type GiftCardProduct,
|
|
7
|
+
type GiftCardPresetVariant,
|
|
8
|
+
} from "../utils/gift-cards";
|
|
9
|
+
|
|
10
|
+
interface GiftCardPurchaseProps {
|
|
11
|
+
product: GiftCardProduct;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// catalogReference shape captured from a real paid Wix-Editor order:
|
|
15
|
+
// { appId: WIX_GIFT_CARDS_APP_ID,
|
|
16
|
+
// catalogItemId: <giftCardProduct.id>,
|
|
17
|
+
// options: { variantId, quantity, currency, giftingInfo, wixGiftCardsAppNewCatalog: true } }
|
|
18
|
+
// On ORDER_PAID, Wix's gift-card backend auto-issues a redeemable card with
|
|
19
|
+
// source: "ORDER" and emails the recipient — no follow-up API call needed.
|
|
20
|
+
export default function GiftCardPurchase({ product }: GiftCardPurchaseProps) {
|
|
21
|
+
const [variantId, setVariantId] = useState<string>(product.presetVariants[0]?.id ?? "");
|
|
22
|
+
const [recipientFirstName, setRecipientFirstName] = useState("");
|
|
23
|
+
const [recipientLastName, setRecipientLastName] = useState("");
|
|
24
|
+
const [recipientEmail, setRecipientEmail] = useState("");
|
|
25
|
+
const [greetingMessage, setGreetingMessage] = useState("");
|
|
26
|
+
const [status, setStatus] = useState<"idle" | "adding" | "added" | "error">("idle");
|
|
27
|
+
const [error, setError] = useState<string | null>(null);
|
|
28
|
+
|
|
29
|
+
const selectedVariant: GiftCardPresetVariant | undefined =
|
|
30
|
+
product.presetVariants.find((v) => v.id === variantId);
|
|
31
|
+
|
|
32
|
+
const emailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(recipientEmail);
|
|
33
|
+
const canSubmit =
|
|
34
|
+
!!selectedVariant &&
|
|
35
|
+
recipientFirstName.trim().length > 0 &&
|
|
36
|
+
emailValid &&
|
|
37
|
+
status !== "adding";
|
|
38
|
+
|
|
39
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
if (!canSubmit || !selectedVariant) return;
|
|
42
|
+
setStatus("adding");
|
|
43
|
+
setError(null);
|
|
44
|
+
try {
|
|
45
|
+
const { cart } = await currentCart.addToCurrentCart({
|
|
46
|
+
lineItems: [
|
|
47
|
+
{
|
|
48
|
+
catalogReference: {
|
|
49
|
+
appId: WIX_GIFT_CARDS_APP_ID,
|
|
50
|
+
catalogItemId: product.id,
|
|
51
|
+
options: {
|
|
52
|
+
variantId: selectedVariant.id,
|
|
53
|
+
quantity: 1,
|
|
54
|
+
giftingInfo: {
|
|
55
|
+
recipientInfo: {
|
|
56
|
+
email: recipientEmail.trim(),
|
|
57
|
+
firstName: recipientFirstName.trim(),
|
|
58
|
+
lastName: recipientLastName.trim(),
|
|
59
|
+
},
|
|
60
|
+
greetingMessage: greetingMessage.trim(),
|
|
61
|
+
},
|
|
62
|
+
wixGiftCardsAppNewCatalog: true,
|
|
63
|
+
} as Record<string, unknown>,
|
|
64
|
+
},
|
|
65
|
+
quantity: 1,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
window.dispatchEvent(new CustomEvent("cart-updated", { detail: { cart, delta: 1 } }));
|
|
70
|
+
trackEvent("AddToCart", {
|
|
71
|
+
id: product.id,
|
|
72
|
+
name: product.name,
|
|
73
|
+
price: Number(selectedVariant.price.amount),
|
|
74
|
+
quantity: 1,
|
|
75
|
+
variant: selectedVariant.price.formattedAmount,
|
|
76
|
+
origin: "Gift Card Page",
|
|
77
|
+
});
|
|
78
|
+
setStatus("added");
|
|
79
|
+
window.location.href = "/cart";
|
|
80
|
+
} catch (err: any) {
|
|
81
|
+
console.error("[gift-cards] addToCurrentCart failed:", err);
|
|
82
|
+
setError(err?.message ?? "Could not add the gift card to your cart. Please try again.");
|
|
83
|
+
setStatus("error");
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<form className="gift-card-form" onSubmit={handleSubmit} noValidate>
|
|
89
|
+
<fieldset className="gift-card-fieldset">
|
|
90
|
+
<legend className="option-label">Amount</legend>
|
|
91
|
+
<div className="gift-card-amounts">
|
|
92
|
+
{product.presetVariants.map((v) => {
|
|
93
|
+
const sel = v.id === variantId;
|
|
94
|
+
return (
|
|
95
|
+
<button
|
|
96
|
+
key={v.id}
|
|
97
|
+
type="button"
|
|
98
|
+
onClick={() => setVariantId(v.id)}
|
|
99
|
+
className={`option-pill${sel ? " selected" : ""}`}
|
|
100
|
+
aria-pressed={sel}
|
|
101
|
+
>
|
|
102
|
+
{v.price.formattedAmount}
|
|
103
|
+
</button>
|
|
104
|
+
);
|
|
105
|
+
})}
|
|
106
|
+
</div>
|
|
107
|
+
</fieldset>
|
|
108
|
+
|
|
109
|
+
<div className="gift-card-grid">
|
|
110
|
+
<label className="gift-card-field">
|
|
111
|
+
<span className="option-label">Recipient first name *</span>
|
|
112
|
+
<input
|
|
113
|
+
type="text"
|
|
114
|
+
value={recipientFirstName}
|
|
115
|
+
onChange={(e) => setRecipientFirstName(e.target.value)}
|
|
116
|
+
required
|
|
117
|
+
autoComplete="off"
|
|
118
|
+
/>
|
|
119
|
+
</label>
|
|
120
|
+
|
|
121
|
+
<label className="gift-card-field">
|
|
122
|
+
<span className="option-label">Recipient last name</span>
|
|
123
|
+
<input
|
|
124
|
+
type="text"
|
|
125
|
+
value={recipientLastName}
|
|
126
|
+
onChange={(e) => setRecipientLastName(e.target.value)}
|
|
127
|
+
autoComplete="off"
|
|
128
|
+
/>
|
|
129
|
+
</label>
|
|
130
|
+
|
|
131
|
+
<label className="gift-card-field gift-card-field-wide">
|
|
132
|
+
<span className="option-label">Recipient email *</span>
|
|
133
|
+
<input
|
|
134
|
+
type="email"
|
|
135
|
+
value={recipientEmail}
|
|
136
|
+
onChange={(e) => setRecipientEmail(e.target.value)}
|
|
137
|
+
required
|
|
138
|
+
autoComplete="off"
|
|
139
|
+
/>
|
|
140
|
+
</label>
|
|
141
|
+
|
|
142
|
+
<label className="gift-card-field gift-card-field-wide">
|
|
143
|
+
<span className="option-label">Personal message</span>
|
|
144
|
+
<textarea
|
|
145
|
+
value={greetingMessage}
|
|
146
|
+
onChange={(e) => setGreetingMessage(e.target.value.slice(0, 500))}
|
|
147
|
+
maxLength={500}
|
|
148
|
+
rows={3}
|
|
149
|
+
placeholder="A short note to the recipient…"
|
|
150
|
+
/>
|
|
151
|
+
<span className="gift-card-counter">{greetingMessage.length} / 500</span>
|
|
152
|
+
</label>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
{error && <p className="gift-card-error" role="alert">{error}</p>}
|
|
156
|
+
|
|
157
|
+
<button
|
|
158
|
+
type="submit"
|
|
159
|
+
disabled={!canSubmit}
|
|
160
|
+
className="add-to-cart-btn"
|
|
161
|
+
>
|
|
162
|
+
{status === "adding"
|
|
163
|
+
? "Adding…"
|
|
164
|
+
: status === "added"
|
|
165
|
+
? "Added ✓"
|
|
166
|
+
: selectedVariant
|
|
167
|
+
? `Add ${selectedVariant.price.formattedAmount} gift card to cart`
|
|
168
|
+
: "Add to cart"}
|
|
169
|
+
</button>
|
|
170
|
+
</form>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{/*
|
|
2
|
+
index.astro contribution. Insert the block below immediately AFTER the
|
|
3
|
+
`<!-- home:gift-cards -->` marker. ALSO add to the file's frontmatter
|
|
4
|
+
(above the `---` close):
|
|
5
|
+
|
|
6
|
+
import { getGiftCardProduct } from "../utils/gift-cards";
|
|
7
|
+
import { resolveWixImageUrl } from "../utils/wix-image";
|
|
8
|
+
|
|
9
|
+
const giftCardProduct = await getGiftCardProduct();
|
|
10
|
+
const giftCardImage = giftCardProduct
|
|
11
|
+
? resolveWixImageUrl(giftCardProduct.image, 800, 600)
|
|
12
|
+
: null;
|
|
13
|
+
|
|
14
|
+
If `resolveWixImageUrl` is already imported by the stores patcher, do not
|
|
15
|
+
re-import — Astro frontmatter is TypeScript and duplicate imports build-fail.
|
|
16
|
+
|
|
17
|
+
The "A gift, considered" eyebrow can be replaced with brand-appropriate copy
|
|
18
|
+
during adaptation; keep the structure as-is.
|
|
19
|
+
*/}
|
|
20
|
+
{giftCardProduct && (
|
|
21
|
+
<section class="py-3xl md:py-4xl">
|
|
22
|
+
<a href="/gift-cards" class="max-w-wide mx-auto px-xl grid grid-cols-1 md:grid-cols-2 gap-xl md:gap-3xl items-center group">
|
|
23
|
+
<div class="aspect-[4/3] bg-cream rounded-sm overflow-hidden">
|
|
24
|
+
{giftCardImage && (
|
|
25
|
+
<img
|
|
26
|
+
src={giftCardImage}
|
|
27
|
+
alt={giftCardProduct.image?.altText || giftCardProduct.name}
|
|
28
|
+
loading="lazy"
|
|
29
|
+
decoding="async"
|
|
30
|
+
class="w-full h-full object-cover"
|
|
31
|
+
/>
|
|
32
|
+
)}
|
|
33
|
+
</div>
|
|
34
|
+
<div class="flex flex-col gap-md">
|
|
35
|
+
<p class="eyebrow">A gift, considered</p>
|
|
36
|
+
<h2 class="font-display text-3xl leading-tight">{giftCardProduct.name}</h2>
|
|
37
|
+
<p class="text-ink-soft leading-relaxed">{giftCardProduct.description}</p>
|
|
38
|
+
<span class="text-sm tracking-wide uppercase mt-sm">Send a gift card →</span>
|
|
39
|
+
</div>
|
|
40
|
+
</a>
|
|
41
|
+
</section>
|
|
42
|
+
)}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{/*
|
|
2
|
+
Navigation.astro contribution. Insert the line below immediately AFTER the
|
|
3
|
+
`<!-- nav:links -->` marker. ALSO add to the file's frontmatter:
|
|
4
|
+
|
|
5
|
+
import { getGiftCardProduct } from "../utils/gift-cards";
|
|
6
|
+
const giftCardsEnabled = (await getGiftCardProduct()) !== null;
|
|
7
|
+
|
|
8
|
+
Wrap the await in try/catch only if other awaits in the file are also
|
|
9
|
+
guarded (the helper itself never throws). The snippet itself is one line.
|
|
10
|
+
*/}
|
|
11
|
+
{giftCardsEnabled && <a href="/gift-cards">Gift Cards</a>}
|