@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.
- 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.ts +24 -0
- package/app/plugins/reveal.client.js +53 -0
- package/app/plugins/toast.js +10 -0
- package/app/plugins/vue-query.ts +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 +259 -0
- package/nuxt/osi-sections.ts +56 -0
- package/nuxt.config.ts +60 -0
- package/package.json +43 -3
- 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,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>
|