@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.
Files changed (206) hide show
  1. package/README.md +80 -2
  2. package/THEME_DEV_API.md +80 -0
  3. package/app/app.vue +154 -0
  4. package/app/assets/css/osi-controls.css +824 -0
  5. package/app/components/SectionRenderer.vue +46 -0
  6. package/app/components/builder/BuilderOverlay.vue +582 -0
  7. package/app/components/checkout/ExtraFees.vue +329 -0
  8. package/app/components/checkout/PaymentHandoff.vue +388 -0
  9. package/app/components/common/Banner.vue +35 -0
  10. package/app/components/common/ConfirmDeleteModal.vue +117 -0
  11. package/app/components/common/DropdownSelect.vue +432 -0
  12. package/app/components/common/Loader.vue +32 -0
  13. package/app/components/common/OsiField.vue +218 -0
  14. package/app/components/common/PhoneNumInput.vue +434 -0
  15. package/app/composables/UseBodyScrollLock.js +122 -0
  16. package/app/composables/UseThemeGroup.js +575 -0
  17. package/app/composables/useAddToCart.js +87 -0
  18. package/app/composables/useAnalytics.js +15 -0
  19. package/app/composables/useAnnouncements.js +29 -0
  20. package/app/composables/useApiErrorToast.js +104 -0
  21. package/app/composables/useAuth.js +99 -0
  22. package/app/composables/useBanners.js +25 -0
  23. package/app/composables/useBuilderPreview.js +320 -0
  24. package/app/composables/useButtonStyle.js +107 -0
  25. package/app/composables/useCarousel.js +160 -0
  26. package/app/composables/useCart.js +109 -0
  27. package/app/composables/useCheckout.js +76 -0
  28. package/app/composables/useClassificationItems.js +269 -0
  29. package/app/composables/useClassificationPage.js +545 -0
  30. package/app/composables/useClassificationUrl.js +63 -0
  31. package/app/composables/useColorScheme.js +44 -0
  32. package/app/composables/useContactForm.js +53 -0
  33. package/app/composables/useContentTree.js +770 -0
  34. package/app/composables/useDragScroll.js +73 -0
  35. package/app/composables/useFooterSocials.js +107 -0
  36. package/app/composables/useInputStyle.js +45 -0
  37. package/app/composables/useLayoutShell.js +114 -0
  38. package/app/composables/useLazyVideo.js +36 -0
  39. package/app/composables/useMediaTypes.js +66 -0
  40. package/app/composables/useMediaUrl.js +71 -0
  41. package/app/composables/useMenu.js +100 -0
  42. package/app/composables/useNavStyle.js +219 -0
  43. package/app/composables/usePlaceholders.js +294 -0
  44. package/app/composables/usePolicies.js +26 -0
  45. package/app/composables/useProductCardMedia.js +135 -0
  46. package/app/composables/useProductCardStyle.js +132 -0
  47. package/app/composables/useProductDetail.js +982 -0
  48. package/app/composables/useProductGallery.js +166 -0
  49. package/app/composables/useProductItems.js +270 -0
  50. package/app/composables/useProductUrl.js +60 -0
  51. package/app/composables/useProductVariants.js +210 -0
  52. package/app/composables/useQuickShop.js +115 -0
  53. package/app/composables/useRelatedProducts.js +186 -0
  54. package/app/composables/useRenderContext.js +45 -0
  55. package/app/composables/useScopedCss.js +106 -0
  56. package/app/composables/useSearch.js +35 -0
  57. package/app/composables/useSectionData.js +462 -0
  58. package/app/composables/useSectionShell.js +113 -0
  59. package/app/composables/useSidebar.js +69 -0
  60. package/app/composables/useStoreData.js +57 -0
  61. package/app/composables/useStoreInit.js +52 -0
  62. package/app/composables/useStorefront.js +239 -0
  63. package/app/composables/useT.js +21 -0
  64. package/app/composables/useTextStyles.js +84 -0
  65. package/app/composables/useThemeControls.js +34 -0
  66. package/app/composables/useThemeSeo.js +91 -0
  67. package/app/composables/useThemeTokens.js +171 -0
  68. package/app/composables/useWishlist.js +66 -0
  69. package/app/logic/components/AnnouncementBar.js +118 -0
  70. package/app/logic/components/CardGrid.js +125 -0
  71. package/app/logic/components/CardList.js +98 -0
  72. package/app/logic/components/CartDrawer.js +273 -0
  73. package/app/logic/components/CartItem.js +31 -0
  74. package/app/logic/components/ClassificationBento.js +6 -0
  75. package/app/logic/components/ClassificationCard.js +6 -0
  76. package/app/logic/components/ClassificationCarousel.js +115 -0
  77. package/app/logic/components/ClassificationEditorial.js +41 -0
  78. package/app/logic/components/ClassificationFeatured.js +57 -0
  79. package/app/logic/components/ClassificationGrid.js +6 -0
  80. package/app/logic/components/ClassificationNav.js +61 -0
  81. package/app/logic/components/ClassificationPart.js +72 -0
  82. package/app/logic/components/FooterBlock.js +91 -0
  83. package/app/logic/components/FooterSocials.js +60 -0
  84. package/app/logic/components/HeaderDropdownList.js +29 -0
  85. package/app/logic/components/HeaderIcon.js +68 -0
  86. package/app/logic/components/HeaderLogo.js +39 -0
  87. package/app/logic/components/HeaderNav.js +268 -0
  88. package/app/logic/components/MobileMenu.js +280 -0
  89. package/app/logic/components/ProductBuyButtons.js +66 -0
  90. package/app/logic/components/ProductCard.js +160 -0
  91. package/app/logic/components/ProductFilters.js +37 -0
  92. package/app/logic/components/ProductGallery.js +19 -0
  93. package/app/logic/components/ProductInfoBlock.js +411 -0
  94. package/app/logic/components/ProductLightbox.js +12 -0
  95. package/app/logic/components/ProductQuantity.js +31 -0
  96. package/app/logic/components/ProductVariantPicker.js +125 -0
  97. package/app/logic/components/QuickBuy.js +194 -0
  98. package/app/logic/components/SearchOverlay.js +41 -0
  99. package/app/logic/components/SidebarMenuItem.js +39 -0
  100. package/app/logic/components/SiteFooter.js +476 -0
  101. package/app/logic/components/SiteHeader.js +266 -0
  102. package/app/logic/components/WhatsAppButton.js +75 -0
  103. package/app/logic/layouts/default.js +66 -0
  104. package/app/logic/sections/AnnouncementBarSection.js +6 -0
  105. package/app/logic/sections/BlockCarousel.js +103 -0
  106. package/app/logic/sections/ClassificationListSection.js +40 -0
  107. package/app/logic/sections/ContactFormSection.js +294 -0
  108. package/app/logic/sections/ContactInfoSection.js +419 -0
  109. package/app/logic/sections/ContentNode.js +189 -0
  110. package/app/logic/sections/CountdownSection.js +254 -0
  111. package/app/logic/sections/CustomHtmlSection.js +51 -0
  112. package/app/logic/sections/DividerSection.js +31 -0
  113. package/app/logic/sections/FaqSection.js +53 -0
  114. package/app/logic/sections/FeatureListSection.js +6 -0
  115. package/app/logic/sections/FeaturedProductSection.js +40 -0
  116. package/app/logic/sections/FooterSection.js +16 -0
  117. package/app/logic/sections/FooterUtilitiesSection.js +24 -0
  118. package/app/logic/sections/GallerySection.js +232 -0
  119. package/app/logic/sections/HeaderSection.js +24 -0
  120. package/app/logic/sections/HeroSection.js +24 -0
  121. package/app/logic/sections/LogoListSection.js +6 -0
  122. package/app/logic/sections/MainClassificationSection.js +473 -0
  123. package/app/logic/sections/MainProductSection.js +211 -0
  124. package/app/logic/sections/MainSearchSection.js +6 -0
  125. package/app/logic/sections/MapSection.js +92 -0
  126. package/app/logic/sections/MediaContentSection.js +38 -0
  127. package/app/logic/sections/MediaWithTextSection.js +238 -0
  128. package/app/logic/sections/NewsletterSection.js +126 -0
  129. package/app/logic/sections/ProductHighlightSection.js +201 -0
  130. package/app/logic/sections/ProductListSection.js +45 -0
  131. package/app/logic/sections/ProductRecommendationsSection.js +113 -0
  132. package/app/logic/sections/RichTextSection.js +6 -0
  133. package/app/logic/sections/ScrollingTextSection.js +69 -0
  134. package/app/logic/sections/SearchHeaderSection.js +22 -0
  135. package/app/logic/sections/SlideshowSection.js +118 -0
  136. package/app/logic/sections/SpacerSection.js +37 -0
  137. package/app/logic/sections/SplitShowcaseSection.js +6 -0
  138. package/app/logic/sections/TestimonialListSection.js +6 -0
  139. package/app/logic/sections/VideoSection.js +235 -0
  140. package/app/pages/[category]/[product].vue +196 -0
  141. package/app/pages/__builder-preview.vue +38 -0
  142. package/app/pages/categories/[slug].vue +208 -0
  143. package/app/pages/checkout.vue +1972 -0
  144. package/app/pages/forgot-password.vue +115 -0
  145. package/app/pages/index.vue +64 -0
  146. package/app/pages/login.vue +585 -0
  147. package/app/pages/order-detail.vue +442 -0
  148. package/app/pages/pages/[handle].vue +58 -0
  149. package/app/pages/privacy-policy.vue +46 -0
  150. package/app/pages/resetpassword.vue +138 -0
  151. package/app/pages/resetverificationcode.vue +163 -0
  152. package/app/pages/result.vue +203 -0
  153. package/app/pages/return-policy.vue +50 -0
  154. package/app/pages/search.vue +100 -0
  155. package/app/pages/shipping-policy.vue +46 -0
  156. package/app/pages/user-profile/components/AddAddressModal.vue +452 -0
  157. package/app/pages/user-profile/components/Whishlist.vue +137 -0
  158. package/app/pages/user-profile/components/left-bar.vue +233 -0
  159. package/app/pages/user-profile/components/orders.vue +162 -0
  160. package/app/pages/user-profile/components/profile-details.vue +566 -0
  161. package/app/pages/user-profile/components/settings.vue +194 -0
  162. package/app/pages/user-profile/index.vue +252 -0
  163. package/app/pages/verificationcode.vue +188 -0
  164. package/app/plugins/analytics.client.js +99 -0
  165. package/app/plugins/geo.js +24 -0
  166. package/app/plugins/reveal.client.js +53 -0
  167. package/app/plugins/toast.js +10 -0
  168. package/app/plugins/vue-query.js +5 -0
  169. package/app/services/ApiServices.js +74 -0
  170. package/app/services/AuthBus.js +162 -0
  171. package/app/services/Authentication/AuthenticationService.js +133 -0
  172. package/app/services/Authentication/TokenService.js +62 -0
  173. package/app/services/CartServices.js +91 -0
  174. package/app/services/CartToken.js +26 -0
  175. package/app/services/CategoryServices.js +54 -0
  176. package/app/services/CheckoutServices.js +113 -0
  177. package/app/services/HomeServices.js +133 -0
  178. package/app/services/NewsletterServices.js +29 -0
  179. package/app/services/PaymentServices.js +71 -0
  180. package/app/services/PoliciesServices.js +16 -0
  181. package/app/services/ProductServices.js +79 -0
  182. package/app/services/SearchServices.js +18 -0
  183. package/app/services/UserServices.js +105 -0
  184. package/app/services/UtilsServices.js +46 -0
  185. package/app/services/WishlistServices.js +51 -0
  186. package/app/services/storefront.service.js +57 -0
  187. package/app/stores/userStore.js +13 -0
  188. package/app/theme/categoryTree.js +68 -0
  189. package/app/theme/controls.js +88 -0
  190. package/app/theme/registry.js +56 -0
  191. package/app/utils/analytics.js +864 -0
  192. package/app/utils/device.js +50 -0
  193. package/app/utils/eventBus.js +24 -0
  194. package/app/utils/icon-presets.js +24 -0
  195. package/app/utils/productOffers.js +118 -0
  196. package/app/utils/requestCache.js +36 -0
  197. package/app/utils/scrollToError.js +59 -0
  198. package/app/utils/toPlainError.js +30 -0
  199. package/bin/osimart-theme.mjs +282 -0
  200. package/nuxt/osi-sections.ts +56 -0
  201. package/nuxt.config.ts +60 -0
  202. package/package.json +44 -4
  203. package/public/css/input.css +29 -0
  204. package/public/css/output.css +2716 -0
  205. package/public/favicon-32x32.png +0 -0
  206. 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>