@osimart/create-storefront 0.0.0-stage → 1.0.1
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/README.md +80 -2
- package/THEME_DEV_API.md +80 -0
- package/app/app.vue +154 -0
- package/app/assets/css/osi-controls.css +824 -0
- package/app/components/SectionRenderer.vue +46 -0
- package/app/components/builder/BuilderOverlay.vue +582 -0
- package/app/components/checkout/ExtraFees.vue +329 -0
- package/app/components/checkout/PaymentHandoff.vue +388 -0
- package/app/components/common/Banner.vue +35 -0
- package/app/components/common/ConfirmDeleteModal.vue +117 -0
- package/app/components/common/DropdownSelect.vue +432 -0
- package/app/components/common/Loader.vue +32 -0
- package/app/components/common/OsiField.vue +218 -0
- package/app/components/common/PhoneNumInput.vue +434 -0
- package/app/composables/UseBodyScrollLock.js +122 -0
- package/app/composables/UseThemeGroup.js +575 -0
- package/app/composables/useAddToCart.js +87 -0
- package/app/composables/useAnalytics.js +15 -0
- package/app/composables/useAnnouncements.js +29 -0
- package/app/composables/useApiErrorToast.js +104 -0
- package/app/composables/useAuth.js +99 -0
- package/app/composables/useBanners.js +25 -0
- package/app/composables/useBuilderPreview.js +320 -0
- package/app/composables/useButtonStyle.js +107 -0
- package/app/composables/useCarousel.js +160 -0
- package/app/composables/useCart.js +109 -0
- package/app/composables/useCheckout.js +76 -0
- package/app/composables/useClassificationItems.js +269 -0
- package/app/composables/useClassificationPage.js +545 -0
- package/app/composables/useClassificationUrl.js +63 -0
- package/app/composables/useColorScheme.js +44 -0
- package/app/composables/useContactForm.js +53 -0
- package/app/composables/useContentTree.js +770 -0
- package/app/composables/useDragScroll.js +73 -0
- package/app/composables/useFooterSocials.js +107 -0
- package/app/composables/useInputStyle.js +45 -0
- package/app/composables/useLayoutShell.js +114 -0
- package/app/composables/useLazyVideo.js +36 -0
- package/app/composables/useMediaTypes.js +66 -0
- package/app/composables/useMediaUrl.js +71 -0
- package/app/composables/useMenu.js +100 -0
- package/app/composables/useNavStyle.js +219 -0
- package/app/composables/usePlaceholders.js +294 -0
- package/app/composables/usePolicies.js +26 -0
- package/app/composables/useProductCardMedia.js +135 -0
- package/app/composables/useProductCardStyle.js +132 -0
- package/app/composables/useProductDetail.js +982 -0
- package/app/composables/useProductGallery.js +166 -0
- package/app/composables/useProductItems.js +270 -0
- package/app/composables/useProductUrl.js +60 -0
- package/app/composables/useProductVariants.js +210 -0
- package/app/composables/useQuickShop.js +115 -0
- package/app/composables/useRelatedProducts.js +186 -0
- package/app/composables/useRenderContext.js +45 -0
- package/app/composables/useScopedCss.js +106 -0
- package/app/composables/useSearch.js +35 -0
- package/app/composables/useSectionData.js +462 -0
- package/app/composables/useSectionShell.js +113 -0
- package/app/composables/useSidebar.js +69 -0
- package/app/composables/useStoreData.js +57 -0
- package/app/composables/useStoreInit.js +52 -0
- package/app/composables/useStorefront.js +239 -0
- package/app/composables/useT.js +21 -0
- package/app/composables/useTextStyles.js +84 -0
- package/app/composables/useThemeControls.js +34 -0
- package/app/composables/useThemeSeo.js +91 -0
- package/app/composables/useThemeTokens.js +171 -0
- package/app/composables/useWishlist.js +66 -0
- package/app/logic/components/AnnouncementBar.js +118 -0
- package/app/logic/components/CardGrid.js +125 -0
- package/app/logic/components/CardList.js +98 -0
- package/app/logic/components/CartDrawer.js +273 -0
- package/app/logic/components/CartItem.js +31 -0
- package/app/logic/components/ClassificationBento.js +6 -0
- package/app/logic/components/ClassificationCard.js +6 -0
- package/app/logic/components/ClassificationCarousel.js +115 -0
- package/app/logic/components/ClassificationEditorial.js +41 -0
- package/app/logic/components/ClassificationFeatured.js +57 -0
- package/app/logic/components/ClassificationGrid.js +6 -0
- package/app/logic/components/ClassificationNav.js +61 -0
- package/app/logic/components/ClassificationPart.js +72 -0
- package/app/logic/components/FooterBlock.js +91 -0
- package/app/logic/components/FooterSocials.js +60 -0
- package/app/logic/components/HeaderDropdownList.js +29 -0
- package/app/logic/components/HeaderIcon.js +68 -0
- package/app/logic/components/HeaderLogo.js +39 -0
- package/app/logic/components/HeaderNav.js +268 -0
- package/app/logic/components/MobileMenu.js +280 -0
- package/app/logic/components/ProductBuyButtons.js +66 -0
- package/app/logic/components/ProductCard.js +160 -0
- package/app/logic/components/ProductFilters.js +37 -0
- package/app/logic/components/ProductGallery.js +19 -0
- package/app/logic/components/ProductInfoBlock.js +411 -0
- package/app/logic/components/ProductLightbox.js +12 -0
- package/app/logic/components/ProductQuantity.js +31 -0
- package/app/logic/components/ProductVariantPicker.js +125 -0
- package/app/logic/components/QuickBuy.js +194 -0
- package/app/logic/components/SearchOverlay.js +41 -0
- package/app/logic/components/SidebarMenuItem.js +39 -0
- package/app/logic/components/SiteFooter.js +476 -0
- package/app/logic/components/SiteHeader.js +266 -0
- package/app/logic/components/WhatsAppButton.js +75 -0
- package/app/logic/layouts/default.js +66 -0
- package/app/logic/sections/AnnouncementBarSection.js +6 -0
- package/app/logic/sections/BlockCarousel.js +103 -0
- package/app/logic/sections/ClassificationListSection.js +40 -0
- package/app/logic/sections/ContactFormSection.js +294 -0
- package/app/logic/sections/ContactInfoSection.js +419 -0
- package/app/logic/sections/ContentNode.js +189 -0
- package/app/logic/sections/CountdownSection.js +254 -0
- package/app/logic/sections/CustomHtmlSection.js +51 -0
- package/app/logic/sections/DividerSection.js +31 -0
- package/app/logic/sections/FaqSection.js +53 -0
- package/app/logic/sections/FeatureListSection.js +6 -0
- package/app/logic/sections/FeaturedProductSection.js +40 -0
- package/app/logic/sections/FooterSection.js +16 -0
- package/app/logic/sections/FooterUtilitiesSection.js +24 -0
- package/app/logic/sections/GallerySection.js +232 -0
- package/app/logic/sections/HeaderSection.js +24 -0
- package/app/logic/sections/HeroSection.js +24 -0
- package/app/logic/sections/LogoListSection.js +6 -0
- package/app/logic/sections/MainClassificationSection.js +473 -0
- package/app/logic/sections/MainProductSection.js +211 -0
- package/app/logic/sections/MainSearchSection.js +6 -0
- package/app/logic/sections/MapSection.js +92 -0
- package/app/logic/sections/MediaContentSection.js +38 -0
- package/app/logic/sections/MediaWithTextSection.js +238 -0
- package/app/logic/sections/NewsletterSection.js +126 -0
- package/app/logic/sections/ProductHighlightSection.js +201 -0
- package/app/logic/sections/ProductListSection.js +45 -0
- package/app/logic/sections/ProductRecommendationsSection.js +113 -0
- package/app/logic/sections/RichTextSection.js +6 -0
- package/app/logic/sections/ScrollingTextSection.js +69 -0
- package/app/logic/sections/SearchHeaderSection.js +22 -0
- package/app/logic/sections/SlideshowSection.js +118 -0
- package/app/logic/sections/SpacerSection.js +37 -0
- package/app/logic/sections/SplitShowcaseSection.js +6 -0
- package/app/logic/sections/TestimonialListSection.js +6 -0
- package/app/logic/sections/VideoSection.js +235 -0
- package/app/pages/[category]/[product].vue +196 -0
- package/app/pages/__builder-preview.vue +38 -0
- package/app/pages/categories/[slug].vue +208 -0
- package/app/pages/checkout.vue +1972 -0
- package/app/pages/forgot-password.vue +115 -0
- package/app/pages/index.vue +64 -0
- package/app/pages/login.vue +585 -0
- package/app/pages/order-detail.vue +442 -0
- package/app/pages/pages/[handle].vue +58 -0
- package/app/pages/privacy-policy.vue +46 -0
- package/app/pages/resetpassword.vue +138 -0
- package/app/pages/resetverificationcode.vue +163 -0
- package/app/pages/result.vue +203 -0
- package/app/pages/return-policy.vue +50 -0
- package/app/pages/search.vue +100 -0
- package/app/pages/shipping-policy.vue +46 -0
- package/app/pages/user-profile/components/AddAddressModal.vue +452 -0
- package/app/pages/user-profile/components/Whishlist.vue +137 -0
- package/app/pages/user-profile/components/left-bar.vue +233 -0
- package/app/pages/user-profile/components/orders.vue +162 -0
- package/app/pages/user-profile/components/profile-details.vue +566 -0
- package/app/pages/user-profile/components/settings.vue +194 -0
- package/app/pages/user-profile/index.vue +252 -0
- package/app/pages/verificationcode.vue +188 -0
- package/app/plugins/analytics.client.js +99 -0
- package/app/plugins/geo.js +24 -0
- package/app/plugins/reveal.client.js +53 -0
- package/app/plugins/toast.js +10 -0
- package/app/plugins/vue-query.js +5 -0
- package/app/services/ApiServices.js +74 -0
- package/app/services/AuthBus.js +162 -0
- package/app/services/Authentication/AuthenticationService.js +133 -0
- package/app/services/Authentication/TokenService.js +62 -0
- package/app/services/CartServices.js +91 -0
- package/app/services/CartToken.js +26 -0
- package/app/services/CategoryServices.js +54 -0
- package/app/services/CheckoutServices.js +113 -0
- package/app/services/HomeServices.js +133 -0
- package/app/services/NewsletterServices.js +29 -0
- package/app/services/PaymentServices.js +71 -0
- package/app/services/PoliciesServices.js +16 -0
- package/app/services/ProductServices.js +79 -0
- package/app/services/SearchServices.js +18 -0
- package/app/services/UserServices.js +105 -0
- package/app/services/UtilsServices.js +46 -0
- package/app/services/WishlistServices.js +51 -0
- package/app/services/storefront.service.js +57 -0
- package/app/stores/userStore.js +13 -0
- package/app/theme/categoryTree.js +68 -0
- package/app/theme/controls.js +88 -0
- package/app/theme/registry.js +56 -0
- package/app/utils/analytics.js +864 -0
- package/app/utils/device.js +50 -0
- package/app/utils/eventBus.js +24 -0
- package/app/utils/icon-presets.js +24 -0
- package/app/utils/productOffers.js +118 -0
- package/app/utils/requestCache.js +36 -0
- package/app/utils/scrollToError.js +59 -0
- package/app/utils/toPlainError.js +30 -0
- package/bin/osimart-theme.mjs +282 -0
- package/nuxt/osi-sections.ts +56 -0
- package/nuxt.config.ts +60 -0
- package/package.json +44 -4
- package/public/css/input.css +29 -0
- package/public/css/output.css +2716 -0
- package/public/favicon-32x32.png +0 -0
- package/public/robots.txt +2 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a group of theme settings with defaults applied.
|
|
3
|
+
*
|
|
4
|
+
* `theme.settings` is a flat key/value map — the `group` attribute in
|
|
5
|
+
* THEME_SETTINGS_SCHEMA is a dashboard UI concern only, it doesn't nest the
|
|
6
|
+
* stored JSON. So `useThemeGroup('HEADER')` filters by prefix and layers the
|
|
7
|
+
* defaults below underneath.
|
|
8
|
+
*
|
|
9
|
+
* Defaults live here rather than being read from the schema because the
|
|
10
|
+
* storefront never receives the schema — bootstrap sends resolved values only.
|
|
11
|
+
* A merchant on an older theme version is missing keys the storefront already
|
|
12
|
+
* uses, and every one of those has to render as something.
|
|
13
|
+
*
|
|
14
|
+
* Keep these in sync with builder/schemas.py's THEME_SETTINGS_SCHEMA. If they drift,
|
|
15
|
+
* the storefront wins for missing keys and the dashboard wins for set ones —
|
|
16
|
+
* which shows up as "the default looks different from what the editor shows".
|
|
17
|
+
*/
|
|
18
|
+
const DEFAULTS = {
|
|
19
|
+
// Shopify's "Logo and favicon"
|
|
20
|
+
BRAND: {
|
|
21
|
+
LOGO: null,
|
|
22
|
+
LOGO_MOBILE: null,
|
|
23
|
+
LOGO_WIDTH: 120,
|
|
24
|
+
LOGO_WIDTH_MOBILE: null,
|
|
25
|
+
FAVICON: null,
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
// "Search engine listing and sharing"
|
|
29
|
+
SEO: {
|
|
30
|
+
SEO_TITLE: "",
|
|
31
|
+
SEO_DESCRIPTION: "",
|
|
32
|
+
SEO_TITLE_FORMAT: "PAGE_STORE",
|
|
33
|
+
SEO_TITLE_SEPARATOR: "|",
|
|
34
|
+
SEO_SHARE_IMAGE: null,
|
|
35
|
+
SEO_NO_INDEX: false,
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
// "Social media" — the store-wide accounts. The footer uses its own
|
|
39
|
+
// FOOTER_SOCIAL_* slots first and falls back to these.
|
|
40
|
+
SOCIAL_MEDIA: {
|
|
41
|
+
SOCIAL_INSTAGRAM_URL: "",
|
|
42
|
+
SOCIAL_FACEBOOK_URL: "",
|
|
43
|
+
SOCIAL_TIKTOK_URL: "",
|
|
44
|
+
SOCIAL_TWITTER_URL: "",
|
|
45
|
+
SOCIAL_YOUTUBE_URL: "",
|
|
46
|
+
SOCIAL_PINTEREST_URL: "",
|
|
47
|
+
SOCIAL_LINKEDIN_URL: "",
|
|
48
|
+
SOCIAL_SNAPCHAT_URL: "",
|
|
49
|
+
SOCIAL_WHATSAPP_URL: "",
|
|
50
|
+
SOCIAL_X_HANDLE: "",
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
WHATSAPP: {
|
|
54
|
+
WHATSAPP_ENABLED: false,
|
|
55
|
+
WHATSAPP_NUMBER: "",
|
|
56
|
+
WHATSAPP_MESSAGE: "",
|
|
57
|
+
WHATSAPP_POSITION: "BOTTOM_RIGHT",
|
|
58
|
+
WHATSAPP_SHOW_ON: "ALL",
|
|
59
|
+
WHATSAPP_HIDE_ON_CHECKOUT: true,
|
|
60
|
+
WHATSAPP_ICON: "DEFAULT",
|
|
61
|
+
WHATSAPP_IMAGE: null,
|
|
62
|
+
WHATSAPP_SVG: "",
|
|
63
|
+
WHATSAPP_STYLE: "FILLED",
|
|
64
|
+
WHATSAPP_SHAPE: "CIRCLE",
|
|
65
|
+
WHATSAPP_SIZE: 56,
|
|
66
|
+
WHATSAPP_ICON_SIZE: 30,
|
|
67
|
+
WHATSAPP_BG: "#25D366",
|
|
68
|
+
WHATSAPP_ICON_COLOR: "#FFFFFF",
|
|
69
|
+
WHATSAPP_SHADOW: true,
|
|
70
|
+
WHATSAPP_PULSE: false,
|
|
71
|
+
WHATSAPP_LABEL: "",
|
|
72
|
+
WHATSAPP_LABEL_SHOW: "HOVER",
|
|
73
|
+
WHATSAPP_OFFSET_X: 20,
|
|
74
|
+
WHATSAPP_OFFSET_Y: 20,
|
|
75
|
+
WHATSAPP_OFFSET_Y_MOBILE: 16,
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
// "Animations"
|
|
79
|
+
ANIMATIONS: {
|
|
80
|
+
ANIMATION_REVEAL: false,
|
|
81
|
+
ANIMATION_HOVER: "NONE",
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
CUSTOM_CSS: {
|
|
85
|
+
CUSTOM_CSS: "",
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
ANNOUNCEMENT: {
|
|
89
|
+
ANNOUNCEMENT_COLOR_SCHEME: null,
|
|
90
|
+
ANNOUNCEMENT_AUTOPLAY: true,
|
|
91
|
+
ANNOUNCEMENT_SPEED: 5,
|
|
92
|
+
ANNOUNCEMENT_SHOW_ARROWS_MOBILE: true,
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
HEADER: {
|
|
96
|
+
// structure
|
|
97
|
+
HEADER_LAYOUT: "LOGO_CENTER",
|
|
98
|
+
HEADER_MENU: "main-menu",
|
|
99
|
+
HEADER_STICKY: "ON_SCROLL_UP",
|
|
100
|
+
|
|
101
|
+
// colour — scheme first, the three overrides fall through to it when unset
|
|
102
|
+
HEADER_COLOR_SCHEME: null,
|
|
103
|
+
HEADER_BACKGROUND: null,
|
|
104
|
+
HEADER_TEXT_COLOR: null,
|
|
105
|
+
HEADER_ICON_COLOR: null,
|
|
106
|
+
|
|
107
|
+
// icons
|
|
108
|
+
HEADER_ICON_SET: "LINE",
|
|
109
|
+
HEADER_ICON_SIZE: 20,
|
|
110
|
+
|
|
111
|
+
// search
|
|
112
|
+
HEADER_SHOW_SEARCH: true,
|
|
113
|
+
HEADER_SEARCH_STYLE: "ICON_OVERLAY",
|
|
114
|
+
HEADER_SEARCH_PANEL_HEIGHT: "50vh",
|
|
115
|
+
|
|
116
|
+
// cart
|
|
117
|
+
HEADER_CART_COUNT: "BADGE",
|
|
118
|
+
|
|
119
|
+
// chrome
|
|
120
|
+
HEADER_SHOW_ACCOUNT: true,
|
|
121
|
+
HEADER_SHOW_BORDER: true,
|
|
122
|
+
HEADER_TRANSPARENT_ON_INDEX: false,
|
|
123
|
+
HEADER_PADDING_Y: 12,
|
|
124
|
+
|
|
125
|
+
// nav links — desktop inline navigation only; the drawer is SIDEBAR
|
|
126
|
+
HEADER_MENU_GAP: 24,
|
|
127
|
+
HEADER_MENU_GAP_MOBILE: 4,
|
|
128
|
+
HEADER_MENU_FONT: "BODY",
|
|
129
|
+
HEADER_MENU_SIZE: 14,
|
|
130
|
+
HEADER_MENU_WEIGHT: "400",
|
|
131
|
+
HEADER_MENU_COLOR: null,
|
|
132
|
+
HEADER_MENU_HOVER_COLOR: null,
|
|
133
|
+
HEADER_MENU_LETTER_SPACING: 0,
|
|
134
|
+
HEADER_MENU_TRANSFORM: "NONE",
|
|
135
|
+
HEADER_MENU_HOVER_EFFECT: "UNDERLINE_GROW",
|
|
136
|
+
HEADER_MENU_ACTIVE_STYLE: "UNDERLINE",
|
|
137
|
+
HEADER_MENU_ACTIVE_COLOR: null,
|
|
138
|
+
HEADER_MENU_ACTIVE_BACKGROUND: null,
|
|
139
|
+
HEADER_MENU_ACTIVE_UNDERLINE_WIDTH: 2,
|
|
140
|
+
HEADER_MENU_ACTIVE_UNDERLINE_OFFSET: 6,
|
|
141
|
+
|
|
142
|
+
// mobile menu trigger — icon style/size/colour reuse HEADER_ICON_*
|
|
143
|
+
HEADER_SHOW_MENU_BUTTON: true,
|
|
144
|
+
HEADER_MENU_BUTTON_VISIBILITY: "MOBILE_ONLY",
|
|
145
|
+
HEADER_MENU_BUTTON_SIZE: 40,
|
|
146
|
+
HEADER_MENU_BUTTON_RADIUS: 0,
|
|
147
|
+
HEADER_MENU_BUTTON_BACKGROUND: null,
|
|
148
|
+
HEADER_MENU_BUTTON_HOVER_BACKGROUND: null,
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
// The sidebar renders HEADER_MENU. There is no SIDEBAR_MENU by design: two
|
|
152
|
+
// menus to keep in sync is one menu that silently goes stale.
|
|
153
|
+
SIDEBAR: {
|
|
154
|
+
SIDEBAR_ENABLED: true,
|
|
155
|
+
SIDEBAR_POSITION: "START",
|
|
156
|
+
SIDEBAR_WIDTH: 380,
|
|
157
|
+
SIDEBAR_WIDTH_MOBILE: 88,
|
|
158
|
+
SIDEBAR_ANIMATION: "SLIDE",
|
|
159
|
+
SIDEBAR_ANIMATION_DURATION: 500,
|
|
160
|
+
SIDEBAR_OVERLAY: true,
|
|
161
|
+
SIDEBAR_OVERLAY_COLOR: "#000000",
|
|
162
|
+
SIDEBAR_OVERLAY_OPACITY: 45,
|
|
163
|
+
SIDEBAR_CLOSE_ON_OVERLAY: true,
|
|
164
|
+
SIDEBAR_LOCK_SCROLL: true,
|
|
165
|
+
SIDEBAR_COLOR_SCHEME: null,
|
|
166
|
+
SIDEBAR_BACKGROUND: null,
|
|
167
|
+
SIDEBAR_TEXT_COLOR: null,
|
|
168
|
+
SIDEBAR_MUTED_COLOR: null,
|
|
169
|
+
SIDEBAR_LINK_COLOR: null,
|
|
170
|
+
SIDEBAR_LINK_HOVER_COLOR: null,
|
|
171
|
+
SIDEBAR_ACTIVE_COLOR: null,
|
|
172
|
+
SIDEBAR_ACTIVE_BACKGROUND: null,
|
|
173
|
+
SIDEBAR_BORDER_COLOR: null,
|
|
174
|
+
SIDEBAR_ICON_COLOR: null,
|
|
175
|
+
SIDEBAR_SHOW_BORDER: false,
|
|
176
|
+
SIDEBAR_BORDER_WIDTH: 1,
|
|
177
|
+
SIDEBAR_RADIUS: 0,
|
|
178
|
+
SIDEBAR_SHOW_DIVIDERS: false,
|
|
179
|
+
SIDEBAR_PADDING_TOP: 8,
|
|
180
|
+
SIDEBAR_PADDING_BOTTOM: 24,
|
|
181
|
+
SIDEBAR_PADDING_X: 20,
|
|
182
|
+
SIDEBAR_LINK_GAP: 2,
|
|
183
|
+
SIDEBAR_LINK_PADDING_Y: 12,
|
|
184
|
+
SIDEBAR_GROUP_GAP: 20,
|
|
185
|
+
SIDEBAR_LINK_FONT: "BODY",
|
|
186
|
+
SIDEBAR_LINK_SIZE: 16,
|
|
187
|
+
SIDEBAR_LINK_SIZE_MOBILE: 16,
|
|
188
|
+
SIDEBAR_LINK_WEIGHT: "500",
|
|
189
|
+
SIDEBAR_LINK_LINE_HEIGHT: 130,
|
|
190
|
+
SIDEBAR_LINK_LETTER_SPACING: 0,
|
|
191
|
+
SIDEBAR_LINK_TRANSFORM: "NONE",
|
|
192
|
+
SIDEBAR_SUBMENU_SIZE: 14,
|
|
193
|
+
SIDEBAR_SUBMENU_WEIGHT: "400",
|
|
194
|
+
SIDEBAR_SUBMENU_COLOR: null,
|
|
195
|
+
SIDEBAR_SUBMENU_MODE: "ACCORDION",
|
|
196
|
+
SIDEBAR_MAX_DEPTH: 3,
|
|
197
|
+
SIDEBAR_SHOW_ARROWS: true,
|
|
198
|
+
SIDEBAR_ARROW_STYLE: "CHEVRON",
|
|
199
|
+
SIDEBAR_ARROW_SIZE: 16,
|
|
200
|
+
SIDEBAR_SUBMENU_ANIMATION: "SLIDE",
|
|
201
|
+
SIDEBAR_SUBMENU_MULTIPLE_OPEN: false,
|
|
202
|
+
SIDEBAR_SUBMENU_INDENT: 16,
|
|
203
|
+
SIDEBAR_SHOW_HEADER: true,
|
|
204
|
+
SIDEBAR_HEADER_HEIGHT: 64,
|
|
205
|
+
SIDEBAR_HEADER_BORDER: true,
|
|
206
|
+
SIDEBAR_SHOW_LOGO: true,
|
|
207
|
+
SIDEBAR_LOGO_WIDTH: 100,
|
|
208
|
+
SIDEBAR_LOGO_ALIGNMENT: "LEFT",
|
|
209
|
+
SIDEBAR_SHOW_CLOSE: true,
|
|
210
|
+
SIDEBAR_CLOSE_POSITION: "END",
|
|
211
|
+
SIDEBAR_SHOW_FOOTER: true,
|
|
212
|
+
SIDEBAR_FOOTER_SHOW_ACCOUNT: true,
|
|
213
|
+
SIDEBAR_FOOTER_SHOW_SEARCH: false,
|
|
214
|
+
SIDEBAR_FOOTER_SHOW_CART: false,
|
|
215
|
+
SIDEBAR_FOOTER_SHOW_SOCIAL: true,
|
|
216
|
+
SIDEBAR_FOOTER_TEXT: null,
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
// Mirrors THEME_SETTINGS_SCHEMA's FOOTER group one-for-one. Every id, and
|
|
220
|
+
// every default matching the schema's — a value that differs here is a
|
|
221
|
+
// setting that looks one way in the editor and another on a store that
|
|
222
|
+
// hasn't saved it yet.
|
|
223
|
+
FOOTER: {
|
|
224
|
+
FOOTER_COLOR_SCHEME: null,
|
|
225
|
+
// Appearance overrides over the scheme (empty = the scheme's).
|
|
226
|
+
FOOTER_BACKGROUND: null,
|
|
227
|
+
FOOTER_TEXT_COLOR: null,
|
|
228
|
+
FOOTER_LINK_COLOR: null,
|
|
229
|
+
FOOTER_LINK_HOVER_COLOR: null,
|
|
230
|
+
// The bar under the footer: its own scheme / background (empty = the footer's).
|
|
231
|
+
FOOTER_BOTTOM_COLOR_SCHEME: null,
|
|
232
|
+
FOOTER_BOTTOM_BACKGROUND: null,
|
|
233
|
+
FOOTER_CONTAINER: "NORMAL",
|
|
234
|
+
FOOTER_CONTAINER_PADDING_X: 16,
|
|
235
|
+
FOOTER_SHOW_DIVIDER: true,
|
|
236
|
+
FOOTER_DIVIDER_COLOR: null,
|
|
237
|
+
FOOTER_PADDING_TOP: 64,
|
|
238
|
+
FOOTER_PADDING_BOTTOM: 32,
|
|
239
|
+
|
|
240
|
+
// logo
|
|
241
|
+
FOOTER_LOGO: null,
|
|
242
|
+
FOOTER_LOGO_POSITION: "TOP_CENTER", // HIDDEN | TOP_CENTER | INLINE_START | INLINE_END
|
|
243
|
+
FOOTER_LOGO_WIDTH: 64,
|
|
244
|
+
FOOTER_LOGO_WIDTH_MOBILE: null,
|
|
245
|
+
FOOTER_LOGO_ALIGNMENT: "CENTER",
|
|
246
|
+
FOOTER_SHOW_BRAND_DESCRIPTION: false,
|
|
247
|
+
FOOTER_BRAND_DESCRIPTION: "",
|
|
248
|
+
FOOTER_BRAND_DESCRIPTION_WIDTH: 32,
|
|
249
|
+
|
|
250
|
+
// newsletter
|
|
251
|
+
FOOTER_SHOW_NEWSLETTER: false,
|
|
252
|
+
FOOTER_NEWSLETTER_POSITION: "START", // START | END
|
|
253
|
+
FOOTER_NEWSLETTER_WIDTH: 32,
|
|
254
|
+
FOOTER_NEWSLETTER_LAYOUT: "INLINE", // INLINE | STACKED
|
|
255
|
+
FOOTER_NEWSLETTER_ALIGNMENT: "LEFT",
|
|
256
|
+
FOOTER_NEWSLETTER_HEADING: "Be a part of our world",
|
|
257
|
+
FOOTER_NEWSLETTER_TEXT: "",
|
|
258
|
+
FOOTER_NEWSLETTER_PLACEHOLDER: "Your email",
|
|
259
|
+
FOOTER_NEWSLETTER_BUTTON_LABEL: "Sign up",
|
|
260
|
+
FOOTER_NEWSLETTER_SUCCESS_MESSAGE: "Thanks for subscribing!",
|
|
261
|
+
FOOTER_NEWSLETTER_SHOW_CONSENT: true,
|
|
262
|
+
FOOTER_NEWSLETTER_CONSENT_TEXT: "",
|
|
263
|
+
|
|
264
|
+
// columns
|
|
265
|
+
FOOTER_COLUMNS_ALIGNMENT: "END", // START | END | SPREAD
|
|
266
|
+
FOOTER_COLUMNS_VERTICAL_ALIGNMENT: "TOP",
|
|
267
|
+
FOOTER_COLUMN_WIDTH_MODE: "EQUAL", // EQUAL | CUSTOM
|
|
268
|
+
FOOTER_COLUMN_GAP: 64,
|
|
269
|
+
FOOTER_COLUMN_ROW_GAP: 16,
|
|
270
|
+
FOOTER_LINK_GAP: 12,
|
|
271
|
+
FOOTER_COLUMN_TEXT_ALIGNMENT: "LEFT",
|
|
272
|
+
|
|
273
|
+
// No default: the schema's FOOTER_MENU_1 has none either, so a store with
|
|
274
|
+
// no footer menu shows no first column rather than a broken link to a
|
|
275
|
+
// "footer-menu" handle that may not exist.
|
|
276
|
+
FOOTER_MENU_1: "",
|
|
277
|
+
FOOTER_MENU_1_TITLE: "",
|
|
278
|
+
FOOTER_MENU_1_WIDTH: 33,
|
|
279
|
+
FOOTER_MENU_2: "",
|
|
280
|
+
FOOTER_MENU_2_TITLE: "",
|
|
281
|
+
FOOTER_MENU_2_WIDTH: 33,
|
|
282
|
+
FOOTER_MENU_3: "",
|
|
283
|
+
FOOTER_MENU_3_TITLE: "",
|
|
284
|
+
FOOTER_MENU_3_WIDTH: 34,
|
|
285
|
+
|
|
286
|
+
// typography — the two text_style() sets, spelled out
|
|
287
|
+
FOOTER_HEADING_SIZE: 13,
|
|
288
|
+
FOOTER_HEADING_SIZE_MOBILE: null,
|
|
289
|
+
FOOTER_HEADING_WEIGHT: "600",
|
|
290
|
+
FOOTER_HEADING_COLOR: null,
|
|
291
|
+
FOOTER_HEADING_LINE_HEIGHT: 130,
|
|
292
|
+
FOOTER_HEADING_LETTER_SPACING: 0,
|
|
293
|
+
FOOTER_HEADING_FONT: "BODY",
|
|
294
|
+
FOOTER_HEADING_TRANSFORM: "UPPERCASE",
|
|
295
|
+
|
|
296
|
+
FOOTER_LINK_SIZE: 14,
|
|
297
|
+
FOOTER_LINK_SIZE_MOBILE: null,
|
|
298
|
+
FOOTER_LINK_WEIGHT: "400",
|
|
299
|
+
FOOTER_LINK_COLOR: null,
|
|
300
|
+
FOOTER_LINK_LINE_HEIGHT: 130,
|
|
301
|
+
FOOTER_LINK_LETTER_SPACING: 0,
|
|
302
|
+
FOOTER_LINK_FONT: "BODY",
|
|
303
|
+
FOOTER_LINK_TRANSFORM: "NONE",
|
|
304
|
+
|
|
305
|
+
FOOTER_TEXT: "",
|
|
306
|
+
|
|
307
|
+
// social — the URL slots are the source the footer prefers; when every one
|
|
308
|
+
// is empty it falls back to store.social_medias
|
|
309
|
+
FOOTER_SHOW_SOCIAL: true,
|
|
310
|
+
FOOTER_SOCIAL_INSTAGRAM_URL: "",
|
|
311
|
+
FOOTER_SOCIAL_FACEBOOK_URL: "",
|
|
312
|
+
FOOTER_SOCIAL_TIKTOK_URL: "",
|
|
313
|
+
FOOTER_SOCIAL_TWITTER_URL: "",
|
|
314
|
+
FOOTER_SOCIAL_YOUTUBE_URL: "",
|
|
315
|
+
FOOTER_SOCIAL_PINTEREST_URL: "",
|
|
316
|
+
FOOTER_SOCIAL_LINKEDIN_URL: "",
|
|
317
|
+
FOOTER_SOCIAL_POSITION: "OWN_COLUMN", // OWN_COLUMN | UNDER_MENU | UNDER_NEWSLETTER | WITH_COPYRIGHT
|
|
318
|
+
FOOTER_SOCIAL_ALIGNMENT: "LEFT",
|
|
319
|
+
FOOTER_SOCIAL_LABEL: "Socials",
|
|
320
|
+
FOOTER_SOCIAL_DISPLAY: "TEXT", // TEXT | ICONS | ICON_TEXT
|
|
321
|
+
FOOTER_SOCIAL_ICON_STYLE: "PLAIN", // shape: PLAIN | CIRCLE | SQUARE
|
|
322
|
+
FOOTER_SOCIAL_ICON_BACKGROUND: "FILLED", // fill: FILLED | OUTLINE | NONE
|
|
323
|
+
FOOTER_SOCIAL_ICON_SIZE: 18,
|
|
324
|
+
FOOTER_SOCIAL_ICON_COLOR: null,
|
|
325
|
+
FOOTER_SOCIAL_GAP: 16,
|
|
326
|
+
|
|
327
|
+
// locale
|
|
328
|
+
FOOTER_SHOW_LANGUAGE_SELECTOR: false,
|
|
329
|
+
FOOTER_SHOW_COUNTRY_SELECTOR: false,
|
|
330
|
+
|
|
331
|
+
// bottom row
|
|
332
|
+
FOOTER_SHOW_PAYMENT_ICONS: false,
|
|
333
|
+
FOOTER_PAYMENT_ICON_SIZE: 20,
|
|
334
|
+
FOOTER_PAYMENT_ICON_GAP: 8,
|
|
335
|
+
FOOTER_COPYRIGHT: "",
|
|
336
|
+
FOOTER_BOTTOM_LAYOUT: "INLINE", // INLINE | THREE_ZONE
|
|
337
|
+
FOOTER_BOTTOM_ALIGNMENT: "CENTER", // START | CENTER | SPREAD
|
|
338
|
+
FOOTER_SHOW_BOTTOM_BAR_DIVIDER: true,
|
|
339
|
+
FOOTER_BOTTOM_PADDING_Y: 16,
|
|
340
|
+
FOOTER_SHOW_POWERED_BY: false,
|
|
341
|
+
FOOTER_POWERED_BY_TEXT: "",
|
|
342
|
+
FOOTER_POWERED_BY_URL: "",
|
|
343
|
+
FOOTER_SHOW_POLICY_LINKS: true,
|
|
344
|
+
FOOTER_POLICY_1_LABEL: "",
|
|
345
|
+
FOOTER_POLICY_1_URL: "",
|
|
346
|
+
FOOTER_POLICY_2_LABEL: "",
|
|
347
|
+
FOOTER_POLICY_2_URL: "",
|
|
348
|
+
FOOTER_POLICY_3_LABEL: "",
|
|
349
|
+
FOOTER_POLICY_3_URL: "",
|
|
350
|
+
FOOTER_POLICY_4_LABEL: "",
|
|
351
|
+
FOOTER_POLICY_4_URL: "",
|
|
352
|
+
|
|
353
|
+
// mobile
|
|
354
|
+
FOOTER_MOBILE_COLUMN_LAYOUT: "STACKED", // STACKED | ACCORDION
|
|
355
|
+
FOOTER_MOBILE_ACCORDION_EXPAND_FIRST: false,
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
// Every storefront button that isn't individually styled: cart drawer,
|
|
359
|
+
// login, checkout, account pages, and any section button left on defaults.
|
|
360
|
+
BUTTONS: {
|
|
361
|
+
BUTTON_STYLE: "SOLID", // SOLID | OUTLINE | UNDERLINE | TEXT
|
|
362
|
+
BUTTON_RADIUS: 4,
|
|
363
|
+
BUTTON_BORDER_WIDTH: 1,
|
|
364
|
+
BUTTON_SHADOW: "NONE", // NONE | SMALL | MEDIUM | LARGE (legacy: true/false)
|
|
365
|
+
BUTTON_SHADOW_COLOR: null,
|
|
366
|
+
BUTTON_PADDING_Y: 12,
|
|
367
|
+
BUTTON_PADDING_X: 28,
|
|
368
|
+
BUTTON_PADDING_Y_MOBILE: null,
|
|
369
|
+
BUTTON_PADDING_X_MOBILE: null,
|
|
370
|
+
BUTTON_FONT: "BODY",
|
|
371
|
+
BUTTON_FONT_SIZE: 14,
|
|
372
|
+
BUTTON_FONT_WEIGHT: "MEDIUM",
|
|
373
|
+
BUTTON_TRANSFORM: "NONE",
|
|
374
|
+
BUTTON_LETTER_SPACING: 0,
|
|
375
|
+
// colours — unset follows the section's colour scheme
|
|
376
|
+
BUTTON_BG_COLOR: null,
|
|
377
|
+
BUTTON_TEXT_COLOR: null,
|
|
378
|
+
BUTTON_BORDER_COLOR: null,
|
|
379
|
+
BUTTON_HOVER_EFFECT: "FADE", // FADE | DARKEN | INVERT | LIFT | NONE
|
|
380
|
+
BUTTON_HOVER_BG_COLOR: null,
|
|
381
|
+
BUTTON_HOVER_TEXT_COLOR: null,
|
|
382
|
+
BUTTON_HOVER_BORDER_COLOR: null,
|
|
383
|
+
BUTTON_HOVER_SHADOW: null,
|
|
384
|
+
SECONDARY_BUTTON_BG_COLOR: null,
|
|
385
|
+
SECONDARY_BUTTON_TEXT_COLOR: null,
|
|
386
|
+
SECONDARY_BUTTON_BORDER_COLOR: null,
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
// Every storefront field: same controls CONTACT_FORM has per section.
|
|
390
|
+
INPUTS: {
|
|
391
|
+
INPUT_STYLE: "OUTLINE", // OUTLINE | FILLED | UNDERLINE
|
|
392
|
+
INPUT_LABEL_STYLE: "FLOATING", // FLOATING | ABOVE | PLACEHOLDER
|
|
393
|
+
INPUT_SHOW_REQUIRED_MARK: true,
|
|
394
|
+
INPUT_RADIUS: 4,
|
|
395
|
+
INPUT_BORDER_WIDTH: 1,
|
|
396
|
+
INPUT_SHADOW: "NONE",
|
|
397
|
+
INPUT_SHADOW_COLOR: null,
|
|
398
|
+
INPUT_PADDING_Y: 10,
|
|
399
|
+
INPUT_PADDING_X: 12,
|
|
400
|
+
INPUT_PADDING_Y_MOBILE: null,
|
|
401
|
+
INPUT_PADDING_X_MOBILE: null,
|
|
402
|
+
INPUT_FONT_SIZE: 15,
|
|
403
|
+
INPUT_FONT_SIZE_MOBILE: null,
|
|
404
|
+
INPUT_GAP: 16,
|
|
405
|
+
INPUT_BG_COLOR: null,
|
|
406
|
+
INPUT_TEXT_COLOR: null,
|
|
407
|
+
INPUT_BORDER_COLOR: null,
|
|
408
|
+
INPUT_FOCUS_COLOR: null,
|
|
409
|
+
INPUT_PLACEHOLDER_COLOR: null,
|
|
410
|
+
INPUT_ERROR_COLOR: null,
|
|
411
|
+
INPUT_LABEL_SIZE: 12,
|
|
412
|
+
INPUT_LABEL_WEIGHT: "MEDIUM",
|
|
413
|
+
INPUT_LABEL_TRANSFORM: "NONE",
|
|
414
|
+
INPUT_LABEL_LETTER_SPACING: 0,
|
|
415
|
+
INPUT_LABEL_COLOR: null,
|
|
416
|
+
},
|
|
417
|
+
|
|
418
|
+
CART: {
|
|
419
|
+
CART_TYPE: "DRAWER",
|
|
420
|
+
CART_DRAWER_TITLE: "Your cart",
|
|
421
|
+
CART_DRAWER_SHOW_COUNT: true,
|
|
422
|
+
CART_DRAWER_WIDTH: 440,
|
|
423
|
+
CART_DRAWER_COLOR_SCHEME: null,
|
|
424
|
+
CART_DRAWER_BACKGROUND: null,
|
|
425
|
+
CART_DRAWER_TEXT_COLOR: null,
|
|
426
|
+
CART_DRAWER_EMPTY_TITLE: "Your cart is empty",
|
|
427
|
+
CART_DRAWER_EMPTY_TEXT: "Have a look around — we think you will find something you love.",
|
|
428
|
+
CART_DRAWER_EMPTY_BUTTON: "Continue shopping",
|
|
429
|
+
CART_DRAWER_EMPTY_URL: "/categories/all",
|
|
430
|
+
CART_DRAWER_FREE_SHIPPING: false,
|
|
431
|
+
CART_DRAWER_FREE_SHIPPING_THRESHOLD: 100,
|
|
432
|
+
CART_DRAWER_FREE_SHIPPING_MESSAGE: "You're {amount} away from free shipping",
|
|
433
|
+
CART_DRAWER_FREE_SHIPPING_SUCCESS: "You've got free shipping!",
|
|
434
|
+
CART_DRAWER_PROGRESS_COLOR: null,
|
|
435
|
+
CART_DRAWER_PROGRESS_DONE_COLOR: null,
|
|
436
|
+
CART_DRAWER_RECOMMENDATIONS: true,
|
|
437
|
+
CART_DRAWER_RECOMMENDATIONS_TITLE: "You may also like",
|
|
438
|
+
CART_DRAWER_RECOMMENDATIONS_PRODUCTS: [],
|
|
439
|
+
CART_DRAWER_RECOMMENDATIONS_LIMIT: 4,
|
|
440
|
+
CART_DRAWER_RECS_LAYOUT: "GRID",
|
|
441
|
+
CART_DRAWER_RECS_COLUMNS: 2,
|
|
442
|
+
CART_DRAWER_RECS_GAP: 12,
|
|
443
|
+
CART_DRAWER_RECS_IMAGE_RATIO: "PORTRAIT",
|
|
444
|
+
CART_DRAWER_RECS_IMAGE_SIZE: 72,
|
|
445
|
+
CART_DRAWER_RECS_IMAGE_RADIUS: 6,
|
|
446
|
+
CART_DRAWER_RECS_CARD_STYLE: "NONE",
|
|
447
|
+
CART_DRAWER_RECS_LINE_COLOR: null,
|
|
448
|
+
CART_DRAWER_RECS_CARD_RADIUS: 8,
|
|
449
|
+
CART_DRAWER_RECS_CARD_PADDING_X: 0,
|
|
450
|
+
CART_DRAWER_RECS_CARD_PADDING_Y: 0,
|
|
451
|
+
// Buttons: empty = the theme's (Theme settings → Buttons).
|
|
452
|
+
CART_DRAWER_CHECKOUT_STYLE: "THEME",
|
|
453
|
+
CART_DRAWER_EMPTY_BTN_STYLE: "THEME",
|
|
454
|
+
CART_DRAWER_RECS_ADD_STYLE: "THEME",
|
|
455
|
+
CART_DRAWER_QTY_SIZE: 30,
|
|
456
|
+
CART_DRAWER_RECS_CARD_BACKGROUND: null,
|
|
457
|
+
CART_DRAWER_RECS_PRICE_POSITION: "BELOW",
|
|
458
|
+
CART_DRAWER_RECS_NAME_LINES: 2,
|
|
459
|
+
CART_DRAWER_RECS_NAME_SIZE: 13,
|
|
460
|
+
CART_DRAWER_RECS_PRICE_SIZE: 13,
|
|
461
|
+
CART_DRAWER_RECS_PRICE_COLOR: null,
|
|
462
|
+
CART_DRAWER_RECS_SHOW_ADD: false,
|
|
463
|
+
CART_DRAWER_RECS_ADD_LABEL: "Add",
|
|
464
|
+
CART_DRAWER_SUBTOTAL_NOTE: "Taxes and shipping calculated at checkout.",
|
|
465
|
+
CART_DRAWER_CHECKOUT_LABEL: "Checkout",
|
|
466
|
+
CART_DRAWER_SHOW_CONTINUE: true,
|
|
467
|
+
},
|
|
468
|
+
|
|
469
|
+
// Set once for the store. Sections may override individual keys, but the
|
|
470
|
+
// default is that every card in the store looks the same.
|
|
471
|
+
PRODUCT_CARD: {
|
|
472
|
+
// structure — these three are the ones that also produce a class
|
|
473
|
+
CARD_TEXT_POSITION: "BELOW", // BELOW | OVERLAY_BOTTOM | OVERLAY_HOVER
|
|
474
|
+
CARD_CTA_STYLE: "HOVER_OVER_IMAGE", // HOVER_OVER_IMAGE | UNDER_CONTENT | NONE
|
|
475
|
+
CARD_WISHLIST_POSITION: "TOP_END", // TOP_END | TOP_START | WITH_TITLE | NONE
|
|
476
|
+
|
|
477
|
+
// image
|
|
478
|
+
CARD_IMAGE_RATIO: "PORTRAIT", // PORTRAIT | SQUARE | LANDSCAPE | NATURAL
|
|
479
|
+
CARD_IMAGE_FIT: "COVER", // COVER | CONTAIN
|
|
480
|
+
CARD_SHOW_SECOND_IMAGE_ON_HOVER: true,
|
|
481
|
+
|
|
482
|
+
// text
|
|
483
|
+
CARD_TEXT_ALIGN: "START", // START | CENTER | END
|
|
484
|
+
CARD_NAME_SIZE: 14,
|
|
485
|
+
CARD_PRICE_SIZE: 14,
|
|
486
|
+
CARD_CONTENT_GAP: 4,
|
|
487
|
+
CARD_CONTENT_PADDING: 12,
|
|
488
|
+
|
|
489
|
+
// chrome
|
|
490
|
+
CARD_BADGE_POSITION: "TOP_START", // TOP_START | TOP_END | BOTTOM_START
|
|
491
|
+
CARD_BORDER_RADIUS: 0,
|
|
492
|
+
CARD_BACKGROUND: null,
|
|
493
|
+
CARD_TEXT_COLOR: null,
|
|
494
|
+
CARD_OVERLAY_SCRIM: "linear-gradient(transparent, rgba(0,0,0,0.55))",
|
|
495
|
+
CARD_CTA_FULL_WIDTH: true,
|
|
496
|
+
|
|
497
|
+
// behaviour
|
|
498
|
+
CARD_SHOW_QUICK_SHOP: true,
|
|
499
|
+
CARD_SHOW_SWATCHES: true,
|
|
500
|
+
|
|
501
|
+
// escape hatch — scoped to the card and sanitised, see scopeCardCss()
|
|
502
|
+
CARD_CUSTOM_CSS: "",
|
|
503
|
+
},
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Which stored keys belong to which group.
|
|
508
|
+
*
|
|
509
|
+
* Needed because the group name isn't always the key prefix: BRAND's keys are
|
|
510
|
+
* LOGO / FAVICON, CART's are CART_TYPE / SHOW_VENDOR, and PRODUCT_CARD's are
|
|
511
|
+
* CARD_*. Prefixes are matched with startsWith, so SIDEBAR_LOGO_WIDTH does not
|
|
512
|
+
* fall into BRAND — it doesn't start with "LOGO".
|
|
513
|
+
*/
|
|
514
|
+
const GROUP_KEYS = {
|
|
515
|
+
BRAND: ["LOGO", "FAVICON"],
|
|
516
|
+
SEO: ["SEO_"],
|
|
517
|
+
SOCIAL_MEDIA: ["SOCIAL_"],
|
|
518
|
+
WHATSAPP: ["WHATSAPP_"],
|
|
519
|
+
ANIMATIONS: ["ANIMATION_"],
|
|
520
|
+
CUSTOM_CSS: ["CUSTOM_CSS"],
|
|
521
|
+
ANNOUNCEMENT: ["ANNOUNCEMENT_"],
|
|
522
|
+
HEADER: ["HEADER_"],
|
|
523
|
+
SIDEBAR: ["SIDEBAR_"],
|
|
524
|
+
FOOTER: ["FOOTER_"],
|
|
525
|
+
BUTTONS: ["BUTTON_", "SECONDARY_BUTTON_"],
|
|
526
|
+
INPUTS: ["INPUT_"],
|
|
527
|
+
CART: ["CART_", "SHOW_VENDOR"],
|
|
528
|
+
PRODUCT_CARD: ["CARD_"],
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* @param {string} group BRAND | ANNOUNCEMENT | HEADER | SIDEBAR | FOOTER | BUTTONS | INPUTS | CART | PRODUCT_CARD
|
|
533
|
+
* @returns {ComputedRef<object>} settings for that group, defaults filled in
|
|
534
|
+
*/
|
|
535
|
+
export function useThemeGroup(group) {
|
|
536
|
+
const { theme } = useStorefront();
|
|
537
|
+
const fallback = DEFAULTS[group] ?? {};
|
|
538
|
+
const prefixes = GROUP_KEYS[group] ?? [];
|
|
539
|
+
|
|
540
|
+
return computed(() => {
|
|
541
|
+
const settings = theme.value ?? {};
|
|
542
|
+
const resolved = { ...fallback };
|
|
543
|
+
|
|
544
|
+
// Iterate the PAYLOAD, not the fallback.
|
|
545
|
+
//
|
|
546
|
+
// The reverse — for (key of Object.keys(fallback)) — turns DEFAULTS into an
|
|
547
|
+
// allow-list: any setting the backend sends that hasn't also been added
|
|
548
|
+
// here is silently dropped, so a new schema field renders as its hardcoded
|
|
549
|
+
// fallback and editing it in the builder appears to do nothing. DEFAULTS is
|
|
550
|
+
// meant to fill gaps in old themes, not to gate new ones.
|
|
551
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
552
|
+
const belongs = key in fallback || prefixes.some((p) => key.startsWith(p));
|
|
553
|
+
if (!belongs) continue;
|
|
554
|
+
// An unset setting arrives as undefined or null; false and 0 are real
|
|
555
|
+
// values a merchant chose and must survive.
|
|
556
|
+
if (value !== undefined && value !== null) resolved[key] = value;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (import.meta.dev) {
|
|
560
|
+
for (const key of Object.keys(settings)) {
|
|
561
|
+
if (!prefixes.some((p) => key.startsWith(p))) continue;
|
|
562
|
+
if (key in fallback) continue;
|
|
563
|
+
console.warn(
|
|
564
|
+
`[theme] ${key} has no entry in THEME_GROUP_DEFAULTS.${group}. It ` +
|
|
565
|
+
`still renders, but a store that hasn't saved it gets undefined ` +
|
|
566
|
+
`instead of the schema default.`
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return resolved;
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
export { DEFAULTS as THEME_GROUP_DEFAULTS };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import CartServices from "#osi/services/CartServices";
|
|
2
|
+
import { useToast } from "vue-toastification";
|
|
3
|
+
import { useCart } from "./useCart";
|
|
4
|
+
import { useSidebar } from "./useSidebar";
|
|
5
|
+
|
|
6
|
+
export function useAddToCart() {
|
|
7
|
+
const router = useRouter();
|
|
8
|
+
const { fetchCart } = useCart();
|
|
9
|
+
const { toggleCartSidebar } = useSidebar();
|
|
10
|
+
const { cartType } = useStorefront();
|
|
11
|
+
|
|
12
|
+
function openCartUi() {
|
|
13
|
+
if (cartType.value === "PAGE") {
|
|
14
|
+
router.push("/cart");
|
|
15
|
+
} else {
|
|
16
|
+
toggleCartSidebar(true);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A real price somewhere: the range ("25 - 27") or the single variant's. */
|
|
21
|
+
function isPriced(product) {
|
|
22
|
+
const parts = String(product?.discounted_price_range ?? product?.price_range ?? "")
|
|
23
|
+
.split("-")
|
|
24
|
+
.map((v) => parseFloat(v))
|
|
25
|
+
.filter((n) => Number.isFinite(n) && n > 0);
|
|
26
|
+
if (parts.length) return true;
|
|
27
|
+
return (product?.product_variants ?? []).some((v) => Number(v.price) > 0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function handleAddToCart(product) {
|
|
31
|
+
if (!product) return;
|
|
32
|
+
// No price: not for sale yet — never added (the button says so too).
|
|
33
|
+
if (!isPriced(product)) return;
|
|
34
|
+
|
|
35
|
+
// If product is already added (from Quick Shop), just open UI
|
|
36
|
+
if (product.added) {
|
|
37
|
+
openCartUi();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// If product has multiple variants, redirect to detail page
|
|
42
|
+
if (Array.isArray(product.product_variants) && product.product_variants.length > 1) {
|
|
43
|
+
const categorySlug = product.categories?.[0]?.category?.slugified_name || 'products';
|
|
44
|
+
router.push(`/${categorySlug}/${product.slugified_name}/`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const variantId = product.product_variants?.[0]?.id;
|
|
50
|
+
const quantityStep = product.quantity_step || 1;
|
|
51
|
+
|
|
52
|
+
if (!variantId) {
|
|
53
|
+
throw new Error("No variant available for this product.");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const response = await CartServices.EditCart(variantId, quantityStep, "add", {
|
|
57
|
+
product,
|
|
58
|
+
variant: product.product_variants?.[0],
|
|
59
|
+
});
|
|
60
|
+
if (response) {
|
|
61
|
+
await fetchCart();
|
|
62
|
+
openCartUi();
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error("[useAddToCart] Error adding to cart:", error);
|
|
66
|
+
try {
|
|
67
|
+
const toast = useToast();
|
|
68
|
+
const data = error?.data;
|
|
69
|
+
|
|
70
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
71
|
+
toast.error(data.length === 1 ? data[0] : data.map(e => `• ${e}`).join('\n'));
|
|
72
|
+
} else if (data && typeof data === 'object') {
|
|
73
|
+
const messages = Object.values(data).flat();
|
|
74
|
+
toast.error(messages.length === 1 ? messages[0] : messages.map(m => `• ${m}`).join('\n'));
|
|
75
|
+
} else {
|
|
76
|
+
toast.error(error?.message || 'An error occurred while adding to cart.');
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
/* ignore toast context error */
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
handleAddToCart,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { analytics } from "#osi/utils/analytics";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Analytics for components and composables.
|
|
5
|
+
*
|
|
6
|
+
* const analytics = useAnalytics();
|
|
7
|
+
* analytics.viewItem(product);
|
|
8
|
+
*
|
|
9
|
+
* Safe everywhere: on the server, in the page-builder preview, and in a store
|
|
10
|
+
* with no tracking IDs every method is a silent no-op, so callers never check.
|
|
11
|
+
* See utils/analytics.js for the event list and how each maps to GA4 and Meta.
|
|
12
|
+
*/
|
|
13
|
+
export function useAnalytics() {
|
|
14
|
+
return analytics;
|
|
15
|
+
}
|