@osimart/create-storefront 0.0.0-stage → 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.
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.ts +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.ts +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 +259 -0
  200. package/nuxt/osi-sections.ts +56 -0
  201. package/nuxt.config.ts +60 -0
  202. package/package.json +43 -3
  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,442 @@
1
+ <template>
2
+ <div class="font-body min-h-screen bg-gray-50">
3
+ <banner pageTitle="Order detail" />
4
+
5
+ <div class="max-w-[1400px] mx-auto py-10 px-4">
6
+ <!-- ===== SKELETON ===== -->
7
+ <template v-if="isLoading">
8
+ <div class="flex flex-col items-center justify-center mb-10 text-center">
9
+ <skeletor width="250px" height="32px" class="mb-2" />
10
+ <skeletor width="280px" height="24px" />
11
+ </div>
12
+
13
+ <div class="grid grid-cols-1 lg:grid-cols-5 gap-6">
14
+ <div class="lg:col-span-3 flex flex-col gap-6">
15
+ <div class="bg-white p-6 shadow-sm rounded-sm border border-gray-200">
16
+ <skeletor width="200px" height="24px" class="mb-2" />
17
+ <skeletor width="100%" height="16px" class="mb-2" />
18
+ <skeletor width="80%" height="16px" />
19
+ </div>
20
+
21
+ <div class="bg-white p-6 shadow-sm rounded-sm border border-gray-200">
22
+ <skeletor width="180px" height="24px" class="mb-6" />
23
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-y-6 gap-x-12">
24
+ <div class="flex flex-col gap-y-4">
25
+ <div v-for="i in 3" :key="`sk-cust-${i}`">
26
+ <skeletor width="60px" height="16px" class="mb-1" />
27
+ <skeletor width="200px" height="14px" />
28
+ </div>
29
+ </div>
30
+ <div>
31
+ <skeletor width="140px" height="16px" class="mb-2" />
32
+ <div class="space-y-2">
33
+ <skeletor width="150px" height="14px" />
34
+ <skeletor width="180px" height="14px" />
35
+ <skeletor width="100px" height="14px" />
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ </div>
41
+
42
+ <div class="lg:col-span-2">
43
+ <div class="bg-white p-6 shadow-sm rounded-sm border border-gray-200">
44
+ <skeletor width="150px" height="24px" class="mb-4" />
45
+ <div class="space-y-3">
46
+ <skeletor width="100%" height="60px" />
47
+ <skeletor width="100%" height="60px" />
48
+ </div>
49
+ <div class="border-t border-gray-100 mt-4 pt-4 space-y-2">
50
+ <skeletor width="100%" height="20px" />
51
+ <skeletor width="100%" height="20px" />
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ </template>
57
+
58
+ <!-- ===== NOT FOUND ===== -->
59
+ <div
60
+ v-else-if="!orderData?.id"
61
+ class="max-w-md mx-auto bg-white p-8 text-center shadow-sm rounded-sm border border-gray-200"
62
+ >
63
+ <h1 class="text-lg font-semibold text-gray-800">Order not found</h1>
64
+ <p class="text-sm text-gray-500 mt-2">
65
+ We couldn't load this order. It may have been placed in another
66
+ browser or session.
67
+ </p>
68
+ <NuxtLink
69
+ to="/"
70
+ class="osi-btn osi-btn--block mt-6"
71
+ >
72
+ Continue shopping
73
+ </NuxtLink>
74
+ </div>
75
+
76
+ <!-- ===== ORDER ===== -->
77
+ <template v-else>
78
+ <!-- Confirmation framing: only right after checkout. Coming from the
79
+ account's order list, this is a record, not an announcement. -->
80
+ <div
81
+ v-if="placed"
82
+ class="flex flex-col items-center justify-center mb-10 text-center"
83
+ >
84
+ <h1 class="text-3xl font-bold text-gray-800 mb-2">
85
+ Thank you{{ customerFirstName ? ` ${customerFirstName}` : "" }}!
86
+ </h1>
87
+ <p class="text-gray-600 text-lg">
88
+ Your order is completed successfully
89
+ </p>
90
+ </div>
91
+ <div v-else class="mb-10">
92
+ <h1 class="text-2xl font-semibold text-gray-800">
93
+ Order
94
+ <span v-if="orderData.order_number">#{{ orderData.order_number }}</span>
95
+ </h1>
96
+ </div>
97
+
98
+ <div class="grid grid-cols-1 lg:grid-cols-5 gap-6">
99
+ <!-- LEFT -->
100
+ <div class="lg:col-span-3 flex flex-col gap-6">
101
+ <div
102
+ v-if="placed"
103
+ class="bg-white p-6 shadow-sm rounded-sm border border-gray-200"
104
+ >
105
+ <h2 class="text-xl font-semibold text-gray-800 mb-2">
106
+ Your Order is Confirmed
107
+ </h2>
108
+ <p class="text-gray-600 text-sm">
109
+ We have accepted your order, and we're getting it ready.
110
+ <template v-if="orderData.customer?.email">
111
+ A confirmation mail has been sent to
112
+ <span class="font-medium text-gray-800">{{
113
+ orderData.customer.email
114
+ }}</span>
115
+ </template>
116
+ </p>
117
+ </div>
118
+
119
+ <!-- Status history: the only thing this page ever showed that the
120
+ old confirmation page didn't. Hidden when there's nothing but
121
+ the status the order was just created with. -->
122
+ <div
123
+ v-if="sortedStatuses.length > 1 || (!placed && sortedStatuses.length)"
124
+ class="bg-white p-6 shadow-sm rounded-sm border border-gray-200"
125
+ >
126
+ <h2 class="text-xl font-semibold text-gray-800 mb-4">
127
+ Order Status History
128
+ </h2>
129
+ <div class="flex flex-col gap-4">
130
+ <div
131
+ v-for="(status, index) in sortedStatuses"
132
+ :key="index"
133
+ class="border-l-4 border-primaryColor bg-gray-50 p-4 rounded-r-sm"
134
+ >
135
+ <p class="text-sm text-gray-500 mb-1">
136
+ {{ formatDate(status.date) }}
137
+ </p>
138
+ <p class="font-medium text-gray-800">
139
+ {{ status.status_name }}
140
+ </p>
141
+ </div>
142
+ </div>
143
+ </div>
144
+
145
+ <!-- Customer details -->
146
+ <div class="bg-white p-6 shadow-sm rounded-sm border border-gray-200">
147
+ <h2 class="text-xl font-semibold text-gray-800 mb-6">
148
+ Customer Details
149
+ </h2>
150
+
151
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-y-4 gap-x-12">
152
+ <div class="flex flex-col gap-y-4">
153
+ <div>
154
+ <p class="text-black font-semibold mb-1">Name</p>
155
+ <p class="text-gray-600 text-sm">
156
+ {{ userInfo?.first_name || orderData.customer?.first_name }}
157
+ {{ userInfo?.last_name || orderData.customer?.last_name }}
158
+ </p>
159
+ </div>
160
+ <div>
161
+ <p class="text-black font-semibold mb-1">Email</p>
162
+ <p class="text-gray-600 text-sm">
163
+ {{ userInfo?.email || orderData.customer?.email }}
164
+ </p>
165
+ </div>
166
+ <div
167
+ v-if="userInfo?.mobile_number || orderData.customer?.mobile_number"
168
+ >
169
+ <p class="text-black font-semibold mb-1">Mobile</p>
170
+ <p class="text-gray-600 text-sm">
171
+ {{ userInfo?.mobile_number || orderData.customer?.mobile_number }}
172
+ </p>
173
+ </div>
174
+ </div>
175
+
176
+ <div>
177
+ <p class="text-black font-semibold mb-2">Shipping address</p>
178
+ <div class="text-gray-600 text-sm leading-relaxed">
179
+ <p>{{ orderData.detailed_address }}</p>
180
+ <p>
181
+ {{ orderData.country }}<span
182
+ v-if="orderData.zone && orderData.zone.toLowerCase() !== 'default zone'"
183
+ >, {{ orderData.zone }}</span
184
+ >
185
+ </p>
186
+ <p>{{ orderData.postal_code }}</p>
187
+ </div>
188
+ </div>
189
+ </div>
190
+ </div>
191
+ </div>
192
+
193
+ <!-- RIGHT -->
194
+ <div class="lg:col-span-2">
195
+ <div
196
+ class="bg-white p-6 shadow-sm rounded-sm border border-gray-200 w-full"
197
+ >
198
+ <div class="flex justify-between items-center mb-6">
199
+ <h2 class="text-xl font-semibold text-gray-800">Order Details</h2>
200
+ <div v-if="currentStatus" class="flex items-center gap-2">
201
+ <span class="w-2 h-2 rounded-full bg-primaryColor"></span>
202
+ <span class="text-primaryColor font-medium text-sm">
203
+ {{ currentStatus }}
204
+ </span>
205
+ </div>
206
+ </div>
207
+
208
+ <!-- Items -->
209
+ <div class="flex flex-col gap-4 mb-6">
210
+ <div
211
+ v-for="(item, index) in cartItemsWithLink"
212
+ :key="index"
213
+ class="flex gap-4 items-start"
214
+ >
215
+ <component
216
+ :is="item.productLink ? 'RouterLink' : 'div'"
217
+ :to="item.productLink"
218
+ class="flex gap-4 items-start w-full"
219
+ >
220
+ <div
221
+ class="relative w-16 h-16 bg-gray-100 rounded-md flex-shrink-0 border border-gray-200"
222
+ >
223
+ <div
224
+ class="absolute top-0 right-0 bg-gray-500 text-white text-xs w-5 h-5 flex items-center justify-center rounded-full shadow-sm translate-x-1/2 -translate-y-1/2 z-10"
225
+ >
226
+ {{ item.quantity }}
227
+ </div>
228
+ <div class="w-full h-full overflow-hidden rounded-md">
229
+ <img
230
+ :src="mediaUrl(item.image)"
231
+ class="w-full h-full object-cover"
232
+ :alt="item.name"
233
+ />
234
+ </div>
235
+ </div>
236
+
237
+ <div class="flex-1 min-w-0 flex justify-between items-start">
238
+ <div>
239
+ <p class="text-gray-800 font-medium text-sm truncate pr-2">
240
+ {{ item.name }}
241
+ </p>
242
+ <p v-if="item.variant_values" class="text-gray-500 text-xs mt-1">
243
+ {{ item.variant_values }}
244
+ </p>
245
+ </div>
246
+ <p class="text-gray-800 font-medium text-sm whitespace-nowrap">
247
+ {{ currency }}{{ item.checkout_price }}
248
+ </p>
249
+ </div>
250
+ </component>
251
+ </div>
252
+ </div>
253
+
254
+ <!-- Totals -->
255
+ <div class="border-t border-gray-100 pt-4 space-y-3 text-sm">
256
+ <div class="flex justify-between text-gray-600">
257
+ <span>Cart Total</span>
258
+ <span class="font-medium text-gray-800">
259
+ {{ currency }}{{ orderData.checkout_cart_price }}
260
+ </span>
261
+ </div>
262
+
263
+ <div class="flex justify-between text-gray-600">
264
+ <span>Shipping</span>
265
+ <span v-if="orderData.freedelivery" class="font-medium text-green-500">
266
+ Free
267
+ </span>
268
+ <span v-else class="font-medium text-gray-800">
269
+ {{ currency }}{{ orderData.checkout_delivery_fees }}
270
+ </span>
271
+ </div>
272
+
273
+ <div
274
+ v-if="orderData?.promocode"
275
+ class="flex justify-between text-green-500"
276
+ >
277
+ <span>Promocode Applied: {{ orderData?.promocode?.code }}</span>
278
+ <span class="font-medium">
279
+ {{ Number(orderData?.promocode?.discount_value) }}
280
+ {{ orderData?.promocode?.discount_type === "Percentage" ? "%" : currency }}
281
+ Off
282
+ </span>
283
+ </div>
284
+
285
+ <div v-if="orderData?.extra_fees?.length" class="flex flex-col gap-3">
286
+ <div
287
+ v-for="fee in orderData.extra_fees"
288
+ :key="fee.id"
289
+ class="flex justify-between items-start gap-10 text-gray-600"
290
+ >
291
+ <div class="flex items-center gap-2 min-w-0">
292
+ <img
293
+ v-if="fee.extra_fee?.logo?.path"
294
+ :src="mediaUrl(fee.extra_fee.logo)"
295
+ :alt="fee.beneficiary || fee.name"
296
+ class="w-5 h-5 rounded-full object-cover flex-shrink-0 border border-gray-200"
297
+ />
298
+ <div class="min-w-0">
299
+ <p class="truncate" :title="fee.name">{{ fee.name }}</p>
300
+ <p
301
+ v-if="fee.beneficiary"
302
+ class="text-xs text-gray-400 truncate"
303
+ :title="fee.beneficiary"
304
+ >
305
+ {{ fee.beneficiary }}
306
+ </p>
307
+ </div>
308
+ </div>
309
+ <span
310
+ class="font-medium text-gray-800 whitespace-nowrap flex-shrink-0"
311
+ >
312
+ {{ currency }}{{ fee.customer_amount }}
313
+ </span>
314
+ </div>
315
+ </div>
316
+
317
+ <div class="flex justify-between text-gray-600">
318
+ <span>Payment method</span>
319
+ <span class="font-medium text-gray-800 text-right">
320
+ {{ orderData?.order_payments?.[0]?.method_name || "N/A" }}
321
+ </span>
322
+ </div>
323
+ </div>
324
+
325
+ <div
326
+ class="border-t border-gray-100 mt-4 pt-4 flex justify-between items-center"
327
+ >
328
+ <span class="text-black font-bold text-lg">Total</span>
329
+ <span class="text-black font-bold text-lg">
330
+ {{ currency }}{{ orderData.checkout_order_total }}
331
+ </span>
332
+ </div>
333
+ </div>
334
+ </div>
335
+ </div>
336
+ </template>
337
+ </div>
338
+ </div>
339
+ </template>
340
+
341
+ <script setup>
342
+ import { Banner as banner } from "#components";
343
+ import checkoutServices from "#osi/services/CheckoutServices";
344
+ import { authBus } from "#osi/services/AuthBus";
345
+ import { Skeletor } from "vue-skeletor";
346
+ import "vue-skeletor/dist/vue-skeletor.css";
347
+ import { ref, computed, onMounted } from "vue";
348
+
349
+
350
+ definePageMeta({
351
+ name: "order-detail",
352
+ alias: ["/ordersummary"],
353
+ });
354
+
355
+ const route = useRoute();
356
+ const { store } = useStorefront();
357
+ const { mediaUrl } = useMediaUrl();
358
+ const storeName = computed(() => store.value?.name || "");
359
+ const currency = computed(
360
+ () => store.value?.default_currency?.symbol || store.value?.default_currency?.code || "$"
361
+ );
362
+
363
+ useHead({
364
+ title: computed(() =>
365
+ storeName.value ? `Your order` : "Your order",
366
+ ),
367
+ meta: [{ name: "robots", content: "noindex, nofollow" }],
368
+ });
369
+
370
+ const cartItems = ref([]);
371
+ const orderData = ref({});
372
+ const order_id = ref(null);
373
+ const isLoading = ref(true);
374
+
375
+ const placed = computed(() => route.query.placed === "1");
376
+ const userInfo = computed(() => authBus.userInfo);
377
+
378
+ const customerFirstName = computed(
379
+ () => orderData.value?.customer?.first_name || userInfo.value?.first_name || "",
380
+ );
381
+
382
+ const sortedStatuses = computed(() =>
383
+ [...(orderData.value?.statuses || [])].sort(
384
+ (a, b) => new Date(b.date) - new Date(a.date),
385
+ ),
386
+ );
387
+
388
+ const currentStatus = computed(() => sortedStatuses.value[0]?.status_name || "");
389
+
390
+ const cartItemsWithLink = computed(() =>
391
+ cartItems.value.map((item) => {
392
+ const product = item?.product?.product;
393
+ const categorySlug = product?.categories?.[0]?.category?.slugified_name;
394
+ const productSlug = product?.slugified_name;
395
+
396
+ return {
397
+ ...item,
398
+ productLink:
399
+ categorySlug && productSlug ? `/${categorySlug}/${productSlug}` : null,
400
+ };
401
+ }),
402
+ );
403
+
404
+ function formatDate(dateString) {
405
+ if (!dateString) return "";
406
+ return new Date(dateString).toLocaleDateString("en-US", {
407
+ year: "numeric",
408
+ month: "long",
409
+ day: "numeric",
410
+ });
411
+ }
412
+
413
+ onMounted(async () => {
414
+ await authBus.checkAuth();
415
+
416
+ const orderId =
417
+ route.query.id ||
418
+ (typeof window !== "undefined" ? sessionStorage.getItem("orderId") : null);
419
+
420
+ order_id.value = orderId;
421
+
422
+ if (!orderId) {
423
+ isLoading.value = false;
424
+ return;
425
+ }
426
+
427
+ try {
428
+ const response = await checkoutServices.getOrder(orderId);
429
+ orderData.value = response;
430
+ cartItems.value = response?.items || [];
431
+ } catch (error) {
432
+ console.error("[order-detail] Error loading order:", error);
433
+ } finally {
434
+ isLoading.value = false;
435
+ }
436
+ });
437
+ </script>
438
+ <style scoped>
439
+ button:hover {
440
+ opacity: 0.8;
441
+ }
442
+ </style>
@@ -0,0 +1,58 @@
1
+ <template>
2
+ <div>
3
+ <SectionRenderer v-if="data?.sections?.length" :sections="data.sections" />
4
+
5
+ <div v-else-if="!error" class="osi-empty">
6
+ <p>This page hasn't been set up yet.</p>
7
+ </div>
8
+
9
+ <div v-else class="osi-empty">
10
+ <p>We couldn't load this page.</p>
11
+ <pre v-if="isDev" class="osi-empty-detail">{{ error.message }}</pre>
12
+ </div>
13
+ </div>
14
+ </template>
15
+ <script setup>
16
+ import { useRenderContext } from '#osi/composables/useRenderContext';
17
+
18
+ const route = useRoute();
19
+ const { render, store } = useStorefront();
20
+ const { setRenderContext, resource } = useRenderContext();
21
+
22
+ const { data, error } = await useAsyncData(
23
+ `render:${route.path}`,
24
+ () => render(route.path)
25
+ );
26
+
27
+ if (error.value?.status === 404) {
28
+ throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true });
29
+ }
30
+
31
+ setRenderContext(data.value);
32
+ watch(data, (payload) => setRenderContext(payload));
33
+
34
+ const isDev = import.meta.dev;
35
+
36
+ const seo = computed(() => data.value?.seo ?? {});
37
+ const origin = useRequestURL().origin;
38
+
39
+ useHead({
40
+ title: () => seo.value.title_full ?? seo.value.title ?? resource.value?.name ?? store.value?.name ?? '',
41
+ titleTemplate: (title) => title ?? '',
42
+ meta: () => [
43
+ { name: 'description', content: seo.value.description ?? undefined },
44
+ { property: 'og:title', content: seo.value.title_full ?? seo.value.title ?? undefined },
45
+ { property: 'og:description', content: seo.value.description ?? undefined },
46
+ { property: 'og:image', content: seo.value.image ?? undefined },
47
+ { name: 'robots', content: seo.value.no_index ? 'noindex, nofollow' : 'index, follow' },
48
+ ],
49
+ link: () => seo.value.canonical_path
50
+ ? [{ rel: 'canonical', href: `${origin}${seo.value.canonical_path}` }]
51
+ : [],
52
+ });
53
+ </script>
54
+
55
+ <style scoped>
56
+ .osi-empty { padding: 6rem 1rem; text-align: center; color: var(--osi-text); opacity: 0.7; }
57
+ .osi-empty-detail { margin-top: 1rem; font-size: 12px; white-space: pre-wrap; }
58
+ </style>
@@ -0,0 +1,46 @@
1
+ <template>
2
+ <div>
3
+ <banner pageTitle="privacy policy" />
4
+
5
+ <div class="flex justify-center items-center">
6
+ <div
7
+ class="w-full max-w-[1800px] flex flex-col gap-5 py-10 md:py-20 px-4 sm:px-8"
8
+ >
9
+ <template v-if="!htmlContent.privacy">
10
+ <div v-for="n in 3" :key="n" class="flex flex-col gap-2">
11
+ <Skeletor :height="'20px'" :width="'40%'" />
12
+ <Skeletor :height="'12px'" :width="'100%'" />
13
+ <Skeletor :height="'12px'" :width="'90%'" />
14
+ <Skeletor :height="'12px'" :width="'95%'" />
15
+ </div>
16
+ </template>
17
+
18
+ <div v-else v-html="htmlContent.privacy" class="font-body"></div>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ </template>
23
+
24
+ <script>
25
+ import { Banner as banner } from "#components";
26
+ import policiesServices from "../services/PoliciesServices";
27
+ import "vue-skeletor/dist/vue-skeletor.css";
28
+ import { Skeletor } from "vue-skeletor";
29
+
30
+ export default {
31
+ components: {
32
+ banner,
33
+ Skeletor,
34
+ },
35
+ data() {
36
+ return {
37
+ htmlContent: {},
38
+ };
39
+ },
40
+ async mounted() {
41
+ this.htmlContent = await policiesServices.getPolicies();
42
+ },
43
+ };
44
+ </script>
45
+
46
+ <style></style>
@@ -0,0 +1,138 @@
1
+ <template>
2
+ <banner pageTitle="Reset password" />
3
+
4
+ <div class="osi-auth-wrap font-body">
5
+ <form class="osi-auth-card" novalidate @submit.prevent="submitForm">
6
+ <p class="osi-auth-title">Enter your new password</p>
7
+
8
+ <div class="osi-auth-stack">
9
+ <OsiField
10
+ v-model="formData.password"
11
+ id="reset-password"
12
+ label="New Password"
13
+ type="password"
14
+ autocomplete="new-password"
15
+ required
16
+ :error="errors.password"
17
+ @input="clearError('password')"
18
+ />
19
+
20
+ <button
21
+ type="submit"
22
+ class="osi-btn osi-btn--block osi-auth-submit"
23
+ :disabled="isLoading"
24
+ :aria-busy="isLoading"
25
+ >
26
+ <span v-if="isLoading" class="osi-btn-spinner" aria-hidden="true" />
27
+ <span>Submit</span>
28
+ </button>
29
+ </div>
30
+ </form>
31
+ </div>
32
+ </template>
33
+
34
+ <script>
35
+ import { Banner as banner } from "#components";
36
+ import { OsiField } from "#components";
37
+ import AuthenticationService from "../services/Authentication/AuthenticationService";
38
+ import { useToast } from "vue-toastification";
39
+
40
+ export default {
41
+ components: {
42
+ OsiField,
43
+ banner,
44
+ },
45
+ data() {
46
+ return {
47
+ isLoading: false,
48
+ formData: {
49
+ email: "",
50
+ code: "",
51
+ password: "",
52
+ },
53
+ errors: {
54
+ password: false,
55
+ },
56
+ };
57
+ },
58
+ mounted() {
59
+ // Extract email and code from query parameters
60
+ const query = this.$route.query;
61
+ if (query.email) this.formData.email = query.email;
62
+ if (query.code) this.formData.code = query.code;
63
+ },
64
+ methods: {
65
+ async submitForm() {
66
+ this.errors = {}; // Clear previous errors
67
+ const toast = useToast();
68
+ if (!this.formData.password) {
69
+ this.errors.password = "Password is required.";
70
+ return;
71
+ }
72
+
73
+ try {
74
+ this.isLoading = true;
75
+ // Send request to reset password
76
+ await AuthenticationService.resetPassword(
77
+ this.formData.email,
78
+ this.formData.code,
79
+ this.formData.password
80
+ );
81
+ toast.success("Password changed!");
82
+ this.$router.push("/login");
83
+ } catch (error) {
84
+ this.isLoading = false;
85
+
86
+ console.error(error);
87
+ const toast = useToast();
88
+
89
+ // Extract errors from the response
90
+ const errorData = error?.data || {};
91
+ let errorMessages = [];
92
+
93
+ // Loop through the errors object and format them
94
+ for (const key in errorData) {
95
+ if (Array.isArray(errorData[key])) {
96
+ errorMessages.push(`• ${errorData[key].join("\n• ")}`);
97
+ }
98
+ }
99
+
100
+ // Show all errors in the toast
101
+ if (errorMessages.length > 0) {
102
+ toast.error(errorMessages.join("\n"));
103
+ } else {
104
+ toast.error("Something went wrong. Try again.");
105
+ }
106
+ } finally {
107
+ this.isLoading = false;
108
+ }
109
+ },
110
+ clearError(field) {
111
+ this.errors[field] = null;
112
+ this.errors.general = null;
113
+ },
114
+ },
115
+ };
116
+ </script>
117
+
118
+ <style scoped>
119
+ .osi-auth-wrap {
120
+ display: flex;
121
+ justify-content: center;
122
+ padding: 2.5rem 1.25rem;
123
+ }
124
+ @media (min-width: 768px) {
125
+ .osi-auth-wrap {
126
+ padding-block: 5rem;
127
+ }
128
+ }
129
+ .osi-auth-title {
130
+ margin: 0 0 1.25rem;
131
+ text-align: center;
132
+ font-size: 1.5rem;
133
+ line-height: 1.3;
134
+ }
135
+ .osi-auth-submit {
136
+ margin-top: 0.5rem;
137
+ }
138
+ </style>