@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,194 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="osi-settings-card w-full xlg1:w-[70%] h-fit shadow-md bg-gray-50 flex justify-center items-center p-5 font-body"
|
|
4
|
+
>
|
|
5
|
+
<form
|
|
6
|
+
novalidate
|
|
7
|
+
class="w-full s:w-fit flex flex-col gap-5 justify-center items-center relative"
|
|
8
|
+
@submit.prevent="changePassword"
|
|
9
|
+
>
|
|
10
|
+
<div class="w-full s:w-[550px] h-fit flex flex-col gap-5 p-5 s:p-9">
|
|
11
|
+
<p class="text-primaryColor text-center font-body text-xl">
|
|
12
|
+
Change Password
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<OsiField
|
|
16
|
+
v-model="currentPassword"
|
|
17
|
+
id="currentPassword"
|
|
18
|
+
label="Current Password"
|
|
19
|
+
type="password"
|
|
20
|
+
autocomplete="current-password"
|
|
21
|
+
:error="errors.currentPassword"
|
|
22
|
+
@input="clearError('currentPassword')"
|
|
23
|
+
/>
|
|
24
|
+
|
|
25
|
+
<OsiField
|
|
26
|
+
v-model="newPassword"
|
|
27
|
+
id="newPassword"
|
|
28
|
+
label="New Password"
|
|
29
|
+
type="password"
|
|
30
|
+
autocomplete="new-password"
|
|
31
|
+
:error="errors.newPassword"
|
|
32
|
+
@input="validatePasswordMatch"
|
|
33
|
+
/>
|
|
34
|
+
|
|
35
|
+
<OsiField
|
|
36
|
+
v-model="confirmPassword"
|
|
37
|
+
id="confirmPassword"
|
|
38
|
+
label="Confirm New Password"
|
|
39
|
+
type="password"
|
|
40
|
+
autocomplete="new-password"
|
|
41
|
+
:error="errors.confirmPassword"
|
|
42
|
+
@input="validatePasswordMatch"
|
|
43
|
+
/>
|
|
44
|
+
|
|
45
|
+
<button
|
|
46
|
+
type="submit"
|
|
47
|
+
class="osi-btn osi-settings-submit"
|
|
48
|
+
:disabled="isLoading"
|
|
49
|
+
:aria-busy="isLoading"
|
|
50
|
+
>
|
|
51
|
+
<span v-if="isLoading" class="osi-btn-spinner" aria-hidden="true" />
|
|
52
|
+
<span>Change Password</span>
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
</form>
|
|
56
|
+
</div>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
<script>
|
|
60
|
+
import AuthenticationService from "../../../services/Authentication/AuthenticationService";
|
|
61
|
+
import { useToast } from "vue-toastification";
|
|
62
|
+
import { OsiField } from "#components";
|
|
63
|
+
export default {
|
|
64
|
+
components: {
|
|
65
|
+
OsiField,
|
|
66
|
+
},
|
|
67
|
+
data() {
|
|
68
|
+
return {
|
|
69
|
+
currentPassword: "",
|
|
70
|
+
newPassword: "",
|
|
71
|
+
confirmPassword: "",
|
|
72
|
+
isLoading: false,
|
|
73
|
+
errors: {
|
|
74
|
+
currentPassword: "",
|
|
75
|
+
newPassword: "",
|
|
76
|
+
confirmPassword: "",
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
methods: {
|
|
81
|
+
validatePasswordMatch() {
|
|
82
|
+
this.clearError("newPassword");
|
|
83
|
+
this.clearError("confirmPassword");
|
|
84
|
+
|
|
85
|
+
if (this.newPassword && this.confirmPassword) {
|
|
86
|
+
if (this.newPassword !== this.confirmPassword) {
|
|
87
|
+
this.errors.confirmPassword = "Passwords do not match";
|
|
88
|
+
} else {
|
|
89
|
+
this.errors.confirmPassword = "";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
clearError(field) {
|
|
95
|
+
this.errors[field] = "";
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
validateForm() {
|
|
99
|
+
let isValid = true;
|
|
100
|
+
|
|
101
|
+
this.errors = {
|
|
102
|
+
currentPassword: "",
|
|
103
|
+
newPassword: "",
|
|
104
|
+
confirmPassword: "",
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
if (!this.currentPassword) {
|
|
108
|
+
this.errors.currentPassword = "Current Password is required";
|
|
109
|
+
isValid = false;
|
|
110
|
+
}
|
|
111
|
+
if (!this.newPassword) {
|
|
112
|
+
this.errors.newPassword = "New Password is required";
|
|
113
|
+
isValid = false;
|
|
114
|
+
}
|
|
115
|
+
if (!this.confirmPassword) {
|
|
116
|
+
this.errors.confirmPassword = "Confirm Password is required";
|
|
117
|
+
isValid = false;
|
|
118
|
+
} else if (this.newPassword !== this.confirmPassword) {
|
|
119
|
+
this.errors.confirmPassword = "Passwords do not match";
|
|
120
|
+
isValid = false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return isValid;
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
async changePassword() {
|
|
127
|
+
const toast = useToast();
|
|
128
|
+
|
|
129
|
+
if (!this.validateForm()) return;
|
|
130
|
+
|
|
131
|
+
const formData = {
|
|
132
|
+
old_password: this.currentPassword,
|
|
133
|
+
new_password: this.newPassword,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
//console.log(formData);
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
this.isLoading = true;
|
|
140
|
+
const response = await AuthenticationService.changePassword(formData);
|
|
141
|
+
toast.success("Password changed successfully");
|
|
142
|
+
//console.log(response);
|
|
143
|
+
|
|
144
|
+
// Reset form fields on success
|
|
145
|
+
this.currentPassword = "";
|
|
146
|
+
this.newPassword = "";
|
|
147
|
+
this.confirmPassword = "";
|
|
148
|
+
} catch (error) {
|
|
149
|
+
this.isLoading = false;
|
|
150
|
+
if (error?.data && typeof error.data === 'object') {
|
|
151
|
+
this.handleApiErrors(error.data);
|
|
152
|
+
} else {
|
|
153
|
+
toast.error(error?.message || "An unexpected error occurred.");
|
|
154
|
+
}
|
|
155
|
+
} finally {
|
|
156
|
+
this.isLoading = false;
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
// Method to format and display errors in toast
|
|
161
|
+
handleApiErrors(errors) {
|
|
162
|
+
const toast = useToast();
|
|
163
|
+
let formattedErrors = [];
|
|
164
|
+
|
|
165
|
+
for (const key in errors) {
|
|
166
|
+
if (Array.isArray(errors[key])) {
|
|
167
|
+
errors[key].forEach((message) => {
|
|
168
|
+
formattedErrors.push(`• ${message}`);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (formattedErrors.length) {
|
|
174
|
+
toast.error(formattedErrors.join("\n"));
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
</script>
|
|
180
|
+
|
|
181
|
+
<style scoped>
|
|
182
|
+
.osi-settings-card {
|
|
183
|
+
--osi-field-chip: #f9fafb;
|
|
184
|
+
}
|
|
185
|
+
.osi-settings-submit {
|
|
186
|
+
align-self: center;
|
|
187
|
+
margin-top: 0.75rem;
|
|
188
|
+
}
|
|
189
|
+
@media (max-width: 479px) {
|
|
190
|
+
.osi-settings-submit {
|
|
191
|
+
align-self: stretch;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
</style>
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<banner pageTitle="user details" />
|
|
4
|
+
<div class="w-full flex justify-center items-center">
|
|
5
|
+
<div
|
|
6
|
+
class="w-full flex flex-col justify-center items-center max-w-[1800px] pt-20 pb-10 md:py-20 px-5 md:px-9 gap-5"
|
|
7
|
+
>
|
|
8
|
+
<div
|
|
9
|
+
v-if="isLoading"
|
|
10
|
+
class="w-full xlg1:w-[70%] shadow-md border border-gray-100 bg-gray-50 flex flex-col gap-5"
|
|
11
|
+
>
|
|
12
|
+
<!-- Profile Card Skeleton -->
|
|
13
|
+
<div
|
|
14
|
+
class="w-full pb-10 flex flex-col justify-center items-center gap-5"
|
|
15
|
+
>
|
|
16
|
+
<div
|
|
17
|
+
class="rounded-full shadow-md flex justify-center items-center absolute"
|
|
18
|
+
>
|
|
19
|
+
<Skeletor
|
|
20
|
+
type="circle"
|
|
21
|
+
width="100"
|
|
22
|
+
height="100"
|
|
23
|
+
class="rounded-full"
|
|
24
|
+
/>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<!-- Sidebar Links Skeleton -->
|
|
29
|
+
<div
|
|
30
|
+
class="h-fit p-5 flex flex-col sm:flex-row sm:gap-5 md:gap-10 sm:justify-center items-start sm:items-center"
|
|
31
|
+
>
|
|
32
|
+
<div
|
|
33
|
+
v-for="i in 5"
|
|
34
|
+
:key="i"
|
|
35
|
+
class="w-full py-3 flex items-center gap-2"
|
|
36
|
+
>
|
|
37
|
+
<Skeletor type="text" height="20" width="100%" />
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
<leftBar
|
|
42
|
+
v-else
|
|
43
|
+
:user="userData"
|
|
44
|
+
:activeStep="currentStepComponent"
|
|
45
|
+
@select-step="changeStep"
|
|
46
|
+
/>
|
|
47
|
+
|
|
48
|
+
<div v-if="isLoading" class="w-full xlg1:w-[70%] flex flex-col gap-5">
|
|
49
|
+
<!-- User Information Section Skeleton -->
|
|
50
|
+
<div
|
|
51
|
+
class="grid grid-cols-1 s:grid-cols-2 gap-x-5 gap-y-8 h-fit border border-gray-100 shadow-md p-5 bg-gray-50"
|
|
52
|
+
>
|
|
53
|
+
<div v-for="i in 4" :key="i" class="flex flex-col gap-2">
|
|
54
|
+
<Skeletor height="20px" width="120px" class="rounded" />
|
|
55
|
+
<Skeletor
|
|
56
|
+
height="48px"
|
|
57
|
+
width="100%"
|
|
58
|
+
class="rounded-none border"
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<!-- Addresses Section Skeleton -->
|
|
64
|
+
<div
|
|
65
|
+
class="w-full h-fit border border-gray-100 shadow-md p-5 bg-gray-50"
|
|
66
|
+
>
|
|
67
|
+
<div class="w-full flex justify-between items-center gap-3">
|
|
68
|
+
<Skeletor height="24px" width="150px" />
|
|
69
|
+
<Skeletor height="30px" width="30px" class="rounded-md" />
|
|
70
|
+
</div>
|
|
71
|
+
<div class="w-full grid grid-cols-1 s:grid-cols-2 mt-5 gap-3">
|
|
72
|
+
<div
|
|
73
|
+
v-for="i in 2"
|
|
74
|
+
:key="i"
|
|
75
|
+
class="h-[200px] bg-gray-100 shadow-sm flex flex-col justify-between p-3"
|
|
76
|
+
>
|
|
77
|
+
<div
|
|
78
|
+
class="w-full text-gray-500 text-sm s:text-base grid gap-2"
|
|
79
|
+
>
|
|
80
|
+
<!-- Country -->
|
|
81
|
+
<div class="flex items-center gap-2">
|
|
82
|
+
<Skeletor height="16px" width="50px" />
|
|
83
|
+
<Skeletor height="16px" width="150px" />
|
|
84
|
+
</div>
|
|
85
|
+
<!-- Zone -->
|
|
86
|
+
<div class="flex items-center gap-2">
|
|
87
|
+
<Skeletor height="16px" width="50px" />
|
|
88
|
+
<Skeletor height="16px" width="150px" />
|
|
89
|
+
</div>
|
|
90
|
+
<!-- Address -->
|
|
91
|
+
<div class="flex items-center gap-2">
|
|
92
|
+
<Skeletor height="16px" width="50px" />
|
|
93
|
+
<Skeletor height="16px" width="150px" />
|
|
94
|
+
</div>
|
|
95
|
+
<!-- Zip Code -->
|
|
96
|
+
<div class="flex items-center gap-2">
|
|
97
|
+
<Skeletor height="16px" width="50px" />
|
|
98
|
+
<Skeletor height="16px" width="150px" />
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<!-- Action Buttons -->
|
|
103
|
+
<div class="flex gap-3">
|
|
104
|
+
<Skeletor height="24px" width="90px" />
|
|
105
|
+
<Skeletor height="24px" width="90px" />
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
<keep-alive v-else>
|
|
112
|
+
<component
|
|
113
|
+
:is="currentStepComponent"
|
|
114
|
+
ref="currentFormComponent"
|
|
115
|
+
:user="userData"
|
|
116
|
+
:addresses="userAddresses"
|
|
117
|
+
:wishlistItems="wishlistItems"
|
|
118
|
+
:countries="countries"
|
|
119
|
+
:allOrders="allOrders"
|
|
120
|
+
@add-address="openAddAddressModal"
|
|
121
|
+
@update-wishlist="updateWishlist"
|
|
122
|
+
@edit-address="openEditAddressModal"
|
|
123
|
+
@address-added="fetchUserInfo"
|
|
124
|
+
/>
|
|
125
|
+
</keep-alive>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</template>
|
|
130
|
+
|
|
131
|
+
<script>
|
|
132
|
+
import { Banner as banner } from "#components";
|
|
133
|
+
import leftBar from "./components/left-bar.vue";
|
|
134
|
+
import profileDetails from "./components/profile-details.vue";
|
|
135
|
+
import settings from "./components/settings.vue";
|
|
136
|
+
import { authBus } from "../../services/AuthBus";
|
|
137
|
+
import Whishlist from "./components/Whishlist.vue";
|
|
138
|
+
import Orders from "./components/orders.vue";
|
|
139
|
+
import utilsServices from "../../services/UtilsServices";
|
|
140
|
+
import UserServices from "../../services/UserServices";
|
|
141
|
+
import WishlistServices from "../../services/WishlistServices";
|
|
142
|
+
import "vue-skeletor/dist/vue-skeletor.css";
|
|
143
|
+
import { Skeletor } from "vue-skeletor";
|
|
144
|
+
definePageMeta({
|
|
145
|
+
middleware: ["auth"],
|
|
146
|
+
});
|
|
147
|
+
export default {
|
|
148
|
+
components: {
|
|
149
|
+
banner,
|
|
150
|
+
leftBar,
|
|
151
|
+
profileDetails,
|
|
152
|
+
settings,
|
|
153
|
+
Whishlist,
|
|
154
|
+
Orders,
|
|
155
|
+
Skeletor,
|
|
156
|
+
},
|
|
157
|
+
data() {
|
|
158
|
+
return {
|
|
159
|
+
countries: [],
|
|
160
|
+
zones: [],
|
|
161
|
+
steps: ["profileDetails", "Whishlist", "Orders", "settings"],
|
|
162
|
+
isLoading: true,
|
|
163
|
+
currentStepIndex: 0,
|
|
164
|
+
userData: {
|
|
165
|
+
firstName: "John",
|
|
166
|
+
lastName: "Doe",
|
|
167
|
+
email: "john.doe@example.com",
|
|
168
|
+
phoneNumber: "+1234567890",
|
|
169
|
+
},
|
|
170
|
+
userAddresses: [
|
|
171
|
+
{
|
|
172
|
+
id: 1,
|
|
173
|
+
type: "Residential",
|
|
174
|
+
address: "123 Main Street, Springfield",
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: 2,
|
|
178
|
+
type: "Company",
|
|
179
|
+
address: "456 Business Ave, Metropolis",
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
wishlistItems: [],
|
|
183
|
+
allOrders: [],
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
methods: {
|
|
187
|
+
updateWishlist(updatedWishlist) {
|
|
188
|
+
// Update the parent state with the new wishlist
|
|
189
|
+
this.wishlistItems = updatedWishlist;
|
|
190
|
+
},
|
|
191
|
+
changeStep(selectedStep) {
|
|
192
|
+
this.currentStepIndex = this.steps.indexOf(selectedStep);
|
|
193
|
+
},
|
|
194
|
+
openAddAddressModal() {
|
|
195
|
+
//console.log("Open Add Address Modal");
|
|
196
|
+
},
|
|
197
|
+
openEditAddressModal(id) {
|
|
198
|
+
//console.log("Edit Address Modal for ID:", id);
|
|
199
|
+
},
|
|
200
|
+
async fetchUserInfo() {
|
|
201
|
+
try {
|
|
202
|
+
const response = await UserServices.getUserDetail();
|
|
203
|
+
this.userData = await response;
|
|
204
|
+
//console.log(this.userData)
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error("Error fetching user info:", error);
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
async fetchOrders() {
|
|
210
|
+
try {
|
|
211
|
+
const response = await UserServices.getUserOrders();
|
|
212
|
+
this.allOrders = await response;
|
|
213
|
+
//console.log(this.allOrders);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
//console.log("Error fetching orders:", error);
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
async fetchWishlisted() {
|
|
219
|
+
try {
|
|
220
|
+
const response = await WishlistServices.getWishlists();
|
|
221
|
+
this.wishlistItems = await response;
|
|
222
|
+
//console.log(this.wishlistItems)
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error(error);
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
async mounted() {
|
|
229
|
+
try {
|
|
230
|
+
await Promise.all([
|
|
231
|
+
this.fetchUserInfo(),
|
|
232
|
+
this.fetchOrders(),
|
|
233
|
+
this.fetchWishlisted(),
|
|
234
|
+
utilsServices.getCountries().then((result) => {
|
|
235
|
+
this.countries = result;
|
|
236
|
+
}),
|
|
237
|
+
]);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.error("Error loading data:", error);
|
|
240
|
+
} finally {
|
|
241
|
+
this.isLoading = false;
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
computed: {
|
|
245
|
+
currentStepComponent() {
|
|
246
|
+
return this.steps[this.currentStepIndex];
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
</script>
|
|
251
|
+
|
|
252
|
+
<style></style>
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<banner pageTitle="Verification Code" />
|
|
3
|
+
|
|
4
|
+
<div
|
|
5
|
+
class="mx-10 flex flex-col items-center justify-center py-10 font-body text-base 2xl:text-xl"
|
|
6
|
+
>
|
|
7
|
+
<h2
|
|
8
|
+
class="text-2xl px-4 py-4 font-body s:text-4xl font-bold text-primaryColor text-center max-w-[400px] sm:max-w-[600px]"
|
|
9
|
+
>
|
|
10
|
+
We have sent a verification code to your email address
|
|
11
|
+
</h2>
|
|
12
|
+
|
|
13
|
+
<div class="flex gap-4 flex-col">
|
|
14
|
+
<!-- <div
|
|
15
|
+
v-if="errorMessage"
|
|
16
|
+
class="text-primaryColor text-sm font-semibold text-center mb-2"
|
|
17
|
+
>
|
|
18
|
+
{{ errorMessage }}
|
|
19
|
+
</div> -->
|
|
20
|
+
<div class="flex gap-2 s:gap-4 justify-center">
|
|
21
|
+
<input
|
|
22
|
+
v-for="(digit, index) in code"
|
|
23
|
+
:key="index"
|
|
24
|
+
v-model="code[index]"
|
|
25
|
+
ref="inputs"
|
|
26
|
+
type="text"
|
|
27
|
+
maxlength="1"
|
|
28
|
+
inputmode="numeric"
|
|
29
|
+
autocomplete="one-time-code"
|
|
30
|
+
:aria-label="`Digit ${index + 1}`"
|
|
31
|
+
class="osi-input osi-otp"
|
|
32
|
+
@input="handleInput(index)"
|
|
33
|
+
@keydown.backspace="handleBackspace(index, $event)"
|
|
34
|
+
:disabled="index > 0 && !code[index - 1]"
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
37
|
+
<div>
|
|
38
|
+
<p class="text-primaryColor text-center text-sm">
|
|
39
|
+
Didn't receive code?
|
|
40
|
+
<button type="button" class="osi-btn osi-btn--text osi-verify-resend" @click="resendCode">
|
|
41
|
+
Resend it
|
|
42
|
+
</button>
|
|
43
|
+
</p>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
<button
|
|
47
|
+
type="button"
|
|
48
|
+
class="osi-btn osi-verify-submit"
|
|
49
|
+
:disabled="isLoading"
|
|
50
|
+
:aria-busy="isLoading"
|
|
51
|
+
@click="submitCode"
|
|
52
|
+
>
|
|
53
|
+
<span v-if="isLoading" class="osi-btn-spinner" aria-hidden="true" />
|
|
54
|
+
<span>Submit</span>
|
|
55
|
+
</button>
|
|
56
|
+
</div>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
<script>
|
|
60
|
+
import { Banner as banner } from "#components";
|
|
61
|
+
import AuthenticationService from "../services/Authentication/AuthenticationService";
|
|
62
|
+
import { authBus } from "../services/AuthBus";
|
|
63
|
+
import { eventBus } from "../utils/eventBus";
|
|
64
|
+
import { useToast } from "vue-toastification";
|
|
65
|
+
import FadeLoader from "vue-spinner/src/FadeLoader.vue";
|
|
66
|
+
import { getDeviceInfo } from "#osi/utils/device";
|
|
67
|
+
import CartServices from "#osi/services/CartServices";
|
|
68
|
+
|
|
69
|
+
export default {
|
|
70
|
+
components: {
|
|
71
|
+
FadeLoader,
|
|
72
|
+
banner,
|
|
73
|
+
},
|
|
74
|
+
data() {
|
|
75
|
+
return {
|
|
76
|
+
code: ["", "", "", ""],
|
|
77
|
+
email: "",
|
|
78
|
+
errorMessage: "",
|
|
79
|
+
isLoading: false,
|
|
80
|
+
formData: {
|
|
81
|
+
profile: {
|
|
82
|
+
checkpoint: 0,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
created() {
|
|
88
|
+
this.email = this.$route.query.email || "";
|
|
89
|
+
},
|
|
90
|
+
mounted() {
|
|
91
|
+
this.$nextTick(() => {
|
|
92
|
+
this.$refs.inputs[0].focus();
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
methods: {
|
|
97
|
+
handleInput(index) {
|
|
98
|
+
this.code[index] = this.code[index].replace(/\D/g, "");
|
|
99
|
+
|
|
100
|
+
if (this.code[index] && index < this.code.length - 1) {
|
|
101
|
+
this.$nextTick(() => {
|
|
102
|
+
this.$refs.inputs[index + 1].focus();
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
handleBackspace(index, event) {
|
|
107
|
+
if (!this.code[index] && index > 0) {
|
|
108
|
+
this.$nextTick(() => {
|
|
109
|
+
this.$refs.inputs[index - 1].focus();
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
async resendCode() {
|
|
115
|
+
const toast = useToast();
|
|
116
|
+
this.code = ["", "", "", ""];
|
|
117
|
+
this.$nextTick(() => {
|
|
118
|
+
this.$refs.inputs[0].focus();
|
|
119
|
+
});
|
|
120
|
+
try {
|
|
121
|
+
const response = await AuthenticationService.resendVerificationCode(
|
|
122
|
+
this.email
|
|
123
|
+
);
|
|
124
|
+
toast.success("Verification code resent");
|
|
125
|
+
} catch (error) {
|
|
126
|
+
//console.log(error);
|
|
127
|
+
if (Array.isArray(error?.data?.code)) {
|
|
128
|
+
this.errorMessage = error.data.code[0];
|
|
129
|
+
} else {
|
|
130
|
+
this.errorMessage = "Something went wrong. Please try again.";
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
async submitCode() {
|
|
135
|
+
const toast = useToast();
|
|
136
|
+
this.errorMessage = "";
|
|
137
|
+
if (this.code.includes("")) {
|
|
138
|
+
toast.error("Please complete the verification code.");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
this.isLoading = true;
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const deviceInfo = getDeviceInfo();
|
|
145
|
+
const response = await AuthenticationService.verificationCode(
|
|
146
|
+
this.email,
|
|
147
|
+
this.code.join(""),
|
|
148
|
+
deviceInfo,
|
|
149
|
+
);
|
|
150
|
+
toast.success("You have successfully signed into your account!");
|
|
151
|
+
authBus.checkAuth();
|
|
152
|
+
// merge guest cart into the newly-verified account
|
|
153
|
+
try {
|
|
154
|
+
await CartServices.mergeCart();
|
|
155
|
+
} catch (mergeError) {
|
|
156
|
+
console.error("Cart merge failed:", mergeError);
|
|
157
|
+
// don't block the verification success flow on a merge failure
|
|
158
|
+
}
|
|
159
|
+
eventBus.emit("cartUpdated");
|
|
160
|
+
const redirectPath = this.$route.query.redirect || "/";
|
|
161
|
+
this.$router.push(redirectPath);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
this.isLoading = false;
|
|
164
|
+
//console.log(error);
|
|
165
|
+
if (Array.isArray(error?.data?.code)) {
|
|
166
|
+
toast.error(error.data.code[0]);
|
|
167
|
+
this.errorMessage = error.data.code[0];
|
|
168
|
+
} else {
|
|
169
|
+
toast.error("Something went wrong. Please try again.");
|
|
170
|
+
}
|
|
171
|
+
} finally {
|
|
172
|
+
this.isLoading = false;
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
</script>
|
|
178
|
+
|
|
179
|
+
<style scoped>
|
|
180
|
+
.osi-verify-submit {
|
|
181
|
+
margin-top: 2.5rem;
|
|
182
|
+
--btn-tt: uppercase;
|
|
183
|
+
}
|
|
184
|
+
.osi-verify-resend {
|
|
185
|
+
--btn-fw: 600;
|
|
186
|
+
--btn-fg: currentColor;
|
|
187
|
+
}
|
|
188
|
+
</style>
|