@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,208 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useRenderContext } from "#osi/composables/useRenderContext";
|
|
3
|
+
import { ALL_SLUG, useClassificationCatalog } from "#osi/composables/useClassificationPage";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Category AND collection listing — one page for both.
|
|
7
|
+
*
|
|
8
|
+
* /categories/<slug> a category
|
|
9
|
+
* /collections/<slug> a collection (alias of this route)
|
|
10
|
+
* /collections/all every product (also /categories/all)
|
|
11
|
+
*
|
|
12
|
+
* Same shape as every theme-driven route: ask the backend which template serves
|
|
13
|
+
* the path, publish the payload, render its sections. The listing itself is the
|
|
14
|
+
* MAIN_CLASSIFICATION section, so its look is edited in the dashboard like any
|
|
15
|
+
* other section.
|
|
16
|
+
*
|
|
17
|
+
* Like Shopify's main-collection section, the page must always have one: if the
|
|
18
|
+
* CLASSIFICATION template doesn't contain a MAIN_CLASSIFICATION (an older theme,
|
|
19
|
+
* or no template at all), a default one is put at the top so the page still
|
|
20
|
+
* works. A 404 only happens when the category/collection itself doesn't exist.
|
|
21
|
+
*
|
|
22
|
+
* Static `categories/` wins over the dynamic `[category]/[product]` route, so
|
|
23
|
+
* `/categories/shoes` lands here, not on the product page.
|
|
24
|
+
*/
|
|
25
|
+
definePageMeta({
|
|
26
|
+
name: "classification",
|
|
27
|
+
alias: ["/collections/:slug"],
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const MAIN = "MAIN_CLASSIFICATION";
|
|
31
|
+
const defaultMain = () => ({ key: "main-classification", type: MAIN, settings: {}, blocks: [] });
|
|
32
|
+
|
|
33
|
+
const route = useRoute();
|
|
34
|
+
const { render, store } = useStorefront();
|
|
35
|
+
const { setRenderContext } = useRenderContext();
|
|
36
|
+
const catalog = useClassificationCatalog();
|
|
37
|
+
const { mediaUrl } = useMediaUrl();
|
|
38
|
+
const origin = useRequestURL().origin;
|
|
39
|
+
|
|
40
|
+
const slug = String(route.params.slug ?? "");
|
|
41
|
+
const kind = slug === ALL_SLUG ? "ALL" : route.path.startsWith("/collections") ? "COLLECTION" : "CATEGORY";
|
|
42
|
+
|
|
43
|
+
const renderData = useAsyncData(`render:${route.path}`, () => render(route.path));
|
|
44
|
+
|
|
45
|
+
const [{ data, error }] = await Promise.all([renderData, catalog.ready]);
|
|
46
|
+
|
|
47
|
+
const row = computed(() => (kind === "ALL" ? null : catalog.find(kind, slug)));
|
|
48
|
+
const isNotFound = (e) => e?.status === 404 || e?.statusCode === 404;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* render() answers an unknown path with the NOT_FOUND template (status 200),
|
|
52
|
+
* not an error. That payload is not this page — using it rendered the 404
|
|
53
|
+
* sections on /categories/all. Only a CLASSIFICATION payload counts.
|
|
54
|
+
*/
|
|
55
|
+
const templateName = (payload) => {
|
|
56
|
+
const t = payload?.template;
|
|
57
|
+
return String((t && typeof t === "object" ? t.name : t) ?? "").toUpperCase();
|
|
58
|
+
};
|
|
59
|
+
const resolved = computed(() => {
|
|
60
|
+
if (!data.value) return null;
|
|
61
|
+
const name = templateName(data.value);
|
|
62
|
+
// No name in the payload at all: trust it. Any other named template: not ours.
|
|
63
|
+
return !name || name === "CLASSIFICATION" ? data.value : null;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The CLASSIFICATION template's sections, borrowed from a real listing.
|
|
68
|
+
*
|
|
69
|
+
* Every category and collection page renders the same CLASSIFICATION
|
|
70
|
+
* template — only the resource differs. So when the backend can't route this
|
|
71
|
+
* path (typically /categories/all on a resolve_path() that doesn't know "all"
|
|
72
|
+
* yet), rendering any real category returns exactly the sections the merchant
|
|
73
|
+
* designed. Without this, the fallback was a bare MAIN_CLASSIFICATION with
|
|
74
|
+
* no settings, which is why /categories/all ignored every edit made in the
|
|
75
|
+
* dashboard while sub-category pages followed them.
|
|
76
|
+
*
|
|
77
|
+
* Only runs when the path itself didn't resolve; one extra request, cached
|
|
78
|
+
* per donor path.
|
|
79
|
+
*/
|
|
80
|
+
const donorSlug = computed(() => {
|
|
81
|
+
if (resolved.value || !(kind === "ALL" || row.value)) return null;
|
|
82
|
+
const useCollections = kind === "COLLECTION" || (kind === "ALL" && route.path.startsWith("/collections"));
|
|
83
|
+
const pool = useCollections
|
|
84
|
+
? catalog.collections.data.value ?? []
|
|
85
|
+
: catalog.tree.value.roots ?? [];
|
|
86
|
+
const pick = pool.find((r) => (r.slugified_name || r.slug) && (r.slugified_name || r.slug) !== slug);
|
|
87
|
+
const donor = pick?.slugified_name || pick?.slug;
|
|
88
|
+
return donor ? `/${useCollections ? "collections" : "categories"}/${donor}` : null;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const donor = donorSlug.value
|
|
92
|
+
? await useAsyncData(`render:${donorSlug.value}`, () => render(donorSlug.value))
|
|
93
|
+
: { data: ref(null) };
|
|
94
|
+
|
|
95
|
+
const donorSections = computed(() => {
|
|
96
|
+
const value = donor.data.value;
|
|
97
|
+
if (!value) return null;
|
|
98
|
+
const name = templateName(value);
|
|
99
|
+
return !name || name === "CLASSIFICATION" ? value.sections ?? [] : null;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const withMain = (sections) =>
|
|
103
|
+
sections.some((section) => section.type === MAIN) ? sections : [defaultMain(), ...sections];
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The payload the page actually renders.
|
|
107
|
+
*
|
|
108
|
+
* CLASSIFICATION payload -> its sections, MAIN_CLASSIFICATION ensured
|
|
109
|
+
* not resolved, "all" / item -> the template's sections via a real
|
|
110
|
+
* listing (above), else a default section
|
|
111
|
+
* not resolved, unknown slug -> real 404
|
|
112
|
+
*/
|
|
113
|
+
const payload = computed(() => {
|
|
114
|
+
if (resolved.value) {
|
|
115
|
+
return {
|
|
116
|
+
...resolved.value,
|
|
117
|
+
resource: resolved.value.resource ?? (row.value ? { type: kind, ...row.value } : { type: kind }),
|
|
118
|
+
sections: withMain(resolved.value.sections ?? []),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (kind === "ALL" || row.value) {
|
|
122
|
+
return {
|
|
123
|
+
template: "CLASSIFICATION",
|
|
124
|
+
resource: row.value
|
|
125
|
+
? { type: kind, id: row.value.id, name: row.value.name, slug }
|
|
126
|
+
: { type: "ALL", name: "All products", slug: ALL_SLUG },
|
|
127
|
+
sections: withMain(donorSections.value ?? []),
|
|
128
|
+
seo: {},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (!payload.value) {
|
|
135
|
+
throw createError({
|
|
136
|
+
statusCode: 404,
|
|
137
|
+
statusMessage: kind === "COLLECTION" ? "Collection not found" : "Category not found",
|
|
138
|
+
fatal: true,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Anything other than a 404 (backend down, 500) still renders the default
|
|
143
|
+
// page from the catalog rather than a blank screen — but it's logged.
|
|
144
|
+
if (import.meta.dev && !resolved.value) {
|
|
145
|
+
if (error.value && !isNotFound(error.value)) {
|
|
146
|
+
console.warn("[classification] render failed, using the default layout:", error.value);
|
|
147
|
+
} else {
|
|
148
|
+
console.warn(
|
|
149
|
+
`[classification] render() did not resolve ${route.path} to CLASSIFICATION; ` +
|
|
150
|
+
(donorSections.value
|
|
151
|
+
? `using the template's sections from ${donorSlug.value}. `
|
|
152
|
+
: "using the default layout. ") +
|
|
153
|
+
"Add match_all_products() to resolve_path() to fix this at the source."
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
setRenderContext(payload.value);
|
|
159
|
+
watch(payload, (value) => setRenderContext(value));
|
|
160
|
+
|
|
161
|
+
/* ------------------------------------------------------------------- SEO */
|
|
162
|
+
|
|
163
|
+
const seo = computed(() => payload.value?.seo ?? {});
|
|
164
|
+
const name = computed(
|
|
165
|
+
() => payload.value?.resource?.name || row.value?.name || (kind === "ALL" ? "All products" : "")
|
|
166
|
+
);
|
|
167
|
+
const description = computed(
|
|
168
|
+
() =>
|
|
169
|
+
seo.value.description ||
|
|
170
|
+
String(row.value?.description ?? "").replace(/<[^>]*>?/gm, "").trim().slice(0, 300) ||
|
|
171
|
+
undefined
|
|
172
|
+
);
|
|
173
|
+
const image = computed(
|
|
174
|
+
() => seo.value.image || mediaUrl(row.value?.main_image ?? row.value?.image ?? null) || undefined
|
|
175
|
+
);
|
|
176
|
+
const title = computed(
|
|
177
|
+
() => seo.value.title_full ?? seo.value.title ?? [name.value, store.value?.name].filter(Boolean).join(" | ")
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
useHead({
|
|
181
|
+
title,
|
|
182
|
+
titleTemplate: (value) => value ?? "",
|
|
183
|
+
meta: () => [
|
|
184
|
+
{ name: "description", content: description.value },
|
|
185
|
+
{ property: "og:type", content: "website" },
|
|
186
|
+
{ property: "og:title", content: name.value || undefined },
|
|
187
|
+
{ property: "og:description", content: description.value },
|
|
188
|
+
{ property: "og:image", content: image.value },
|
|
189
|
+
{ name: "twitter:card", content: "summary_large_image" },
|
|
190
|
+
{ name: "twitter:title", content: name.value || undefined },
|
|
191
|
+
{ name: "twitter:image", content: image.value },
|
|
192
|
+
{ name: "robots", content: seo.value.no_index ? "noindex, nofollow" : "index, follow" },
|
|
193
|
+
],
|
|
194
|
+
link: () => [
|
|
195
|
+
{
|
|
196
|
+
rel: "canonical",
|
|
197
|
+
// Filters, sort and page are views of the same listing.
|
|
198
|
+
href: `${origin}${seo.value.canonical_path || route.path}`,
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
});
|
|
202
|
+
</script>
|
|
203
|
+
|
|
204
|
+
<template>
|
|
205
|
+
<div>
|
|
206
|
+
<SectionRenderer :sections="payload.sections" />
|
|
207
|
+
</div>
|
|
208
|
+
</template>
|