@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,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>