@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,585 @@
1
+ <template>
2
+ <div class="font-body">
3
+ <banner :pageTitle="isLoginForm ? 'Login' : 'Register'" />
4
+
5
+ <ClientOnly>
6
+ <div class="osi-login-wrap">
7
+ <form class="osi-auth-card" novalidate @submit.prevent="submitForm">
8
+ <!-- Login / Register switch -->
9
+ <div class="osi-segmented" role="tablist">
10
+ <button
11
+ type="button"
12
+ role="tab"
13
+ class="osi-btn"
14
+ :class="{ 'is-active': isLoginForm }"
15
+ :aria-selected="isLoginForm"
16
+ @click="isLoginForm = true"
17
+ >
18
+ Login
19
+ </button>
20
+ <button
21
+ type="button"
22
+ role="tab"
23
+ class="osi-btn"
24
+ :class="{ 'is-active': !isLoginForm }"
25
+ :aria-selected="!isLoginForm"
26
+ @click="isLoginForm = false"
27
+ >
28
+ Register
29
+ </button>
30
+ </div>
31
+
32
+ <!-- Login Form -->
33
+ <div v-if="isLoginForm" class="osi-auth-stack osi-login-body">
34
+ <OsiField
35
+ v-model="formData.email"
36
+ id="login-email"
37
+ label="Email"
38
+ type="email"
39
+ autocomplete="email"
40
+ required
41
+ :error="errors.email"
42
+ @input="clearError('email')"
43
+ />
44
+
45
+ <OsiField
46
+ v-model="formData.password"
47
+ id="login-password"
48
+ label="Password"
49
+ :type="isPasswordVisible ? 'text' : 'password'"
50
+ autocomplete="current-password"
51
+ required
52
+ :error="errors.password ? 'Password is required' : false"
53
+ @input="clearError('password')"
54
+ >
55
+ <template #suffix>
56
+ <button
57
+ type="button"
58
+ class="osi-login-eye"
59
+ :aria-label="isPasswordVisible ? 'Hide password' : 'Show password'"
60
+ @click="togglePasswordVisibility"
61
+ >
62
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
63
+ <path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
64
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
65
+ </svg>
66
+ </button>
67
+ </template>
68
+ </OsiField>
69
+
70
+ <router-link to="/forgot-password/" class="osi-login-link">
71
+ Forgot Your Password?
72
+ </router-link>
73
+
74
+ <button
75
+ type="submit"
76
+ class="osi-btn osi-btn--block osi-login-submit"
77
+ :disabled="isLoginLoading"
78
+ :aria-busy="isLoginLoading"
79
+ >
80
+ <span v-if="isLoginLoading" class="osi-btn-spinner" aria-hidden="true" />
81
+ <span>Login</span>
82
+ </button>
83
+ </div>
84
+
85
+ <!-- Register Form -->
86
+ <div v-else class="osi-auth-stack osi-login-body">
87
+ <div class="osi-auth-row">
88
+ <OsiField
89
+ v-model="registerData.first_name"
90
+ id="register-first-name"
91
+ label="First Name"
92
+ autocomplete="given-name"
93
+ required
94
+ :error="registerErrors.first_name"
95
+ @input="clearRegisterError('first_name')"
96
+ />
97
+ <OsiField
98
+ v-model="registerData.last_name"
99
+ id="register-last-name"
100
+ label="Last Name"
101
+ autocomplete="family-name"
102
+ required
103
+ :error="registerErrors.last_name"
104
+ @input="clearRegisterError('last_name')"
105
+ />
106
+ </div>
107
+
108
+ <OsiField
109
+ v-model="registerData.email"
110
+ id="register-email"
111
+ label="Email"
112
+ type="email"
113
+ autocomplete="email"
114
+ required
115
+ :error="registerErrors.email || emailError"
116
+ @input="clearRegisterError('email')"
117
+ />
118
+
119
+ <OsiField
120
+ label="Mobile number"
121
+ required
122
+ floated
123
+ :label-style="phoneLabelStyle"
124
+ :error="registerErrors.mobile"
125
+ >
126
+ <template #control="{ id, inputClass }">
127
+ <PhoneNumInput
128
+ v-model="registerData.mobile"
129
+ variant="theme"
130
+ :input-id="id"
131
+ :input-class="inputClass"
132
+ :error="!!registerErrors.mobile"
133
+ @update="phoneNumberResults = $event"
134
+ @clear-error="clearRegisterError('mobile')"
135
+ />
136
+ </template>
137
+ </OsiField>
138
+
139
+ <OsiField
140
+ v-model="registerData.password"
141
+ id="register-password"
142
+ label="Password"
143
+ :type="isPasswordVisible ? 'text' : 'password'"
144
+ autocomplete="new-password"
145
+ required
146
+ :error="registerErrors.password"
147
+ @input="validatePasswords(); clearRegisterError('password')"
148
+ >
149
+ <template #suffix>
150
+ <button
151
+ type="button"
152
+ class="osi-login-eye"
153
+ :aria-label="isPasswordVisible ? 'Hide password' : 'Show password'"
154
+ @click="togglePasswordVisibility"
155
+ >
156
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
157
+ <path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
158
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
159
+ </svg>
160
+ </button>
161
+ </template>
162
+ </OsiField>
163
+
164
+ <OsiField
165
+ v-model="confirmPassword"
166
+ id="register-confirm-password"
167
+ label="Confirm Password"
168
+ :type="isConfirmPasswordVisible ? 'text' : 'password'"
169
+ autocomplete="new-password"
170
+ required
171
+ :error="registerErrors.confirmPassword || passwordError"
172
+ @input="validatePasswords(); clearRegisterError('confirmPassword')"
173
+ >
174
+ <template #suffix>
175
+ <button
176
+ type="button"
177
+ class="osi-login-eye"
178
+ :aria-label="isConfirmPasswordVisible ? 'Hide password' : 'Show password'"
179
+ @click="toggleConfirmPasswordVisibility"
180
+ >
181
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
182
+ <path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
183
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
184
+ </svg>
185
+ </button>
186
+ </template>
187
+ </OsiField>
188
+
189
+ <label class="osi-login-terms">
190
+ <input type="checkbox" class="osi-login-check" />
191
+ <span>
192
+ I agree to the
193
+ <a class="osi-login-link">Terms &amp; Conditions</a>
194
+ and
195
+ <a class="osi-login-link">Privacy Policy</a>
196
+ </span>
197
+ </label>
198
+
199
+ <button
200
+ type="submit"
201
+ class="osi-btn osi-btn--block osi-login-submit"
202
+ :disabled="isLoading"
203
+ :aria-busy="isLoading"
204
+ >
205
+ <span v-if="isLoading" class="osi-btn-spinner" aria-hidden="true" />
206
+ <span>Register</span>
207
+ </button>
208
+ </div>
209
+ </form>
210
+ </div>
211
+ </ClientOnly>
212
+ </div>
213
+ </template>
214
+
215
+ <script>
216
+ import { Banner as banner } from "#components";
217
+ import AuthenticationService from "../services/Authentication/AuthenticationService";
218
+ import { useToast } from "vue-toastification";
219
+ import { eventBus } from "../utils/eventBus";
220
+ import { authBus } from "../services/AuthBus";
221
+ import { PhoneNumInput } from "#components";
222
+ import { OsiField } from "#components";
223
+ import { getDeviceInfo } from "#osi/utils/device";
224
+ import CartServices from "#osi/services/CartServices";
225
+
226
+ import { ref } from "vue";
227
+ const phoneNumber = ref();
228
+ const countryCode = ref("FR");
229
+ const phoneNumberResults = ref();
230
+
231
+ export default {
232
+ components: {
233
+ PhoneNumInput,
234
+ OsiField,
235
+ banner,
236
+ },
237
+ setup() {
238
+ // Gives the Options API access to the same theme-controls source
239
+ // OsiField.vue reads internally, so phoneLabelStyle (below) can mirror
240
+ // the theme's current label style.
241
+ const { controls } = useThemeControls();
242
+ return { themeControls: controls };
243
+ },
244
+ data() {
245
+ return {
246
+ isLoginForm: true,
247
+ isPasswordVisible: false,
248
+ isConfirmPasswordVisible: false,
249
+ showErrorMessage: false,
250
+ isLoginLoading: false,
251
+ isLoading: false,
252
+ emailError: "",
253
+ passwordError: "",
254
+ errorMessage: "",
255
+ formData: {
256
+ email: "",
257
+ password: "",
258
+ login_as: "customer",
259
+ },
260
+ confirmPassword: "",
261
+ phoneNumberResults: null,
262
+ registerData: {
263
+ first_name: "",
264
+ last_name: "",
265
+ email: "",
266
+ mobile: "",
267
+ password: "",
268
+ register_as: "customer",
269
+ countryCode: "LB",
270
+ },
271
+ errors: {
272
+ email: false,
273
+ password: false,
274
+ },
275
+ registerErrors: {
276
+ first_name: false,
277
+ last_name: false,
278
+ email: false,
279
+ mobile: false,
280
+ password: false,
281
+ confirmPassword: false,
282
+ },
283
+ };
284
+ },
285
+ computed: {
286
+ // Mobile number field: show the label only when the theme's label
287
+ // style is ABOVE; otherwise keep it visually hidden (sr-only via
288
+ // OsiField's PLACEHOLDER style) rather than letting it float on focus.
289
+ phoneLabelStyle() {
290
+ return this.themeControls?.labelStyle === "ABOVE" ? "ABOVE" : "PLACEHOLDER";
291
+ },
292
+ },
293
+ methods: {
294
+ clearError(field) {
295
+ this.errors[field] = false;
296
+ },
297
+ clearRegisterError(field) {
298
+ this.registerErrors[field] = false;
299
+ },
300
+ //email validation
301
+ validateEmail() {
302
+ const emailPattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;
303
+
304
+ if (!emailPattern.test(this.registerData.email)) {
305
+ this.emailError = "Please enter a valid email address.";
306
+ return;
307
+ } else {
308
+ this.emailError = "";
309
+ }
310
+ },
311
+ //password validation
312
+ validatePasswords() {
313
+ this.passwordError = "";
314
+
315
+ if (!this.registerData.password || !this.confirmPassword) {
316
+ return false; // Prevent form submission if passwords are empty
317
+ }
318
+
319
+ if (this.registerData.password !== this.confirmPassword) {
320
+ this.passwordError = "Passwords do not match";
321
+ return false;
322
+ }
323
+
324
+ return true; // Passwords are valid
325
+ },
326
+
327
+ togglePasswordVisibility() {
328
+ this.isPasswordVisible = !this.isPasswordVisible;
329
+ },
330
+ toggleConfirmPasswordVisibility() {
331
+ this.isConfirmPasswordVisible = !this.isConfirmPasswordVisible;
332
+ },
333
+ showError(message) {
334
+ this.errorMessage = message;
335
+ this.showErrorMessage = true;
336
+
337
+ setTimeout(() => {
338
+ this.showErrorMessage = false;
339
+ }, 10000);
340
+ },
341
+ resetErrorMessage() {
342
+ this.errorMessage = "";
343
+ },
344
+
345
+ async submitForm() {
346
+ const toast = useToast();
347
+ if (this.isLoginForm) {
348
+ let isValid = true;
349
+
350
+ this.errors.email = false;
351
+ this.errors.password = false;
352
+
353
+ if (!this.formData.email) {
354
+ this.errors.email = "Email is required";
355
+ isValid = false;
356
+ } else if (!this.isValidEmail(this.formData.email)) {
357
+ this.errors.email = "Please enter a valid email address";
358
+ isValid = false;
359
+ }
360
+ if (!this.formData.password) {
361
+ this.errors.password = "Password is required";
362
+ isValid = false;
363
+ }
364
+
365
+ if (!isValid) {
366
+ return;
367
+ }
368
+
369
+ try {
370
+ this.isLoginLoading = true;
371
+ const deviceInfo = getDeviceInfo();
372
+ const loginResponse = await this.login({
373
+ ...this.formData,
374
+ ...deviceInfo,
375
+ });
376
+ toast.success("You have successfully signed into your account!");
377
+ authBus.checkAuth();
378
+
379
+ // merge guest cart into the now-authenticated account
380
+ await CartServices.mergeCart();
381
+
382
+ eventBus.emit("cartUpdated");
383
+ const redirectPath = this.$route.query.redirect || "/";
384
+ this.$router.push(redirectPath);
385
+ } catch (error) {
386
+ this.isLoginLoading = false;
387
+ toast.error("Wrong Credentials");
388
+ this.errorMessage = "Wrong email or password";
389
+ this.showErrorMessage = true;
390
+ setTimeout(() => {
391
+ this.showErrorMessage = false;
392
+ }, 10000);
393
+ } finally {
394
+ this.isLoginLoading = false;
395
+ }
396
+ } else {
397
+ let isRegisterValid = true;
398
+ this.registerErrors.first_name = false;
399
+ this.registerErrors.last_name = false;
400
+ this.registerErrors.email = false;
401
+ this.registerErrors.mobile = false;
402
+ this.registerErrors.password = false;
403
+ this.registerErrors.confirmPassword = false;
404
+
405
+ if (!this.registerData.first_name) {
406
+ this.errors.first_name = "First name is required";
407
+ isRegisterValid = false;
408
+ }
409
+ if (!this.registerData.first_name) {
410
+ this.registerErrors.first_name = "First name is required";
411
+ isRegisterValid = false;
412
+ }
413
+ // Validate Last Name
414
+ if (!this.registerData.last_name) {
415
+ this.registerErrors.last_name = "Last name is required";
416
+ isRegisterValid = false;
417
+ }
418
+ if (!this.registerData.mobile) {
419
+ this.registerErrors.mobile = "Mobile number is required";
420
+ isRegisterValid = false;
421
+ } else if (!this.phoneNumberResults?.isValid) {
422
+ this.registerErrors.mobile = "Mobile number is not valid";
423
+ isRegisterValid = false;
424
+ }
425
+ // Validate Email
426
+ if (!this.registerData.email) {
427
+ this.registerErrors.email = "Email is required";
428
+ isRegisterValid = false;
429
+ } else if (!this.isValidEmail(this.registerData.email)) {
430
+ this.registerErrors.email = "Please enter a valid email address";
431
+ isRegisterValid = false;
432
+ }
433
+ if (!this.validatePasswords()) {
434
+ isRegisterValid = false;
435
+ }
436
+
437
+ if (!this.confirmPassword) {
438
+ this.registerErrors.confirmPassword = "Confirm Password is required";
439
+ isRegisterValid = false;
440
+ }
441
+ // Validate Password
442
+ if (!this.registerData.password) {
443
+ this.registerErrors.password = "Password is required";
444
+ isRegisterValid = false;
445
+ }
446
+ if (!isRegisterValid) {
447
+ return;
448
+ }
449
+
450
+ try {
451
+ this.isLoading = true;
452
+ const registerResponse = await this.register(this.registerData);
453
+ //console.log(registerResponse);
454
+ this.$router.push({
455
+ path: "/verificationcode",
456
+ query: { email: this.registerData.email },
457
+ });
458
+ } catch (error) {
459
+ //console.log(error)
460
+ this.isLoading = false;
461
+ if (error) {
462
+ const apiErrors = error;
463
+ let errorMessages = [];
464
+
465
+ this.registerErrors = {
466
+ first_name: "",
467
+ last_name: "",
468
+ email: "",
469
+ mobile: "",
470
+ password: "",
471
+ confirmPassword: "",
472
+ };
473
+
474
+ for (const messages of Object.values(apiErrors)) {
475
+ errorMessages.push(...messages);
476
+ }
477
+
478
+ // Display errors in toast
479
+ if (errorMessages.length > 1) {
480
+ toast.error(`• ${errorMessages.join("\n• ")}`, {
481
+ timeout: 5000,
482
+ closeOnClick: true,
483
+ pauseOnHover: true,
484
+ });
485
+ } else if (errorMessages.length === 1) {
486
+ toast.error(errorMessages[0]);
487
+ }
488
+ } else {
489
+ this.errorMessage = error.message;
490
+ this.showErrorMessage = true;
491
+ setTimeout(() => {
492
+ this.showErrorMessage = false;
493
+ }, 10000);
494
+ }
495
+ } finally {
496
+ this.isLoading = false;
497
+ }
498
+ }
499
+ },
500
+
501
+ isValidEmail(email) {
502
+ const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
503
+ return regex.test(email);
504
+ },
505
+
506
+ async login(formData) {
507
+ try {
508
+ const response = await AuthenticationService.login(formData);
509
+ eventBus.userLoggedIn = true;
510
+ return response;
511
+ } catch (error) {
512
+ console.error("Error during login:", error);
513
+ throw error;
514
+ }
515
+ },
516
+ async register(registerData) {
517
+ try {
518
+ const response = await AuthenticationService.register(registerData);
519
+ return response;
520
+ } catch (error) {
521
+ throw error;
522
+ }
523
+ },
524
+ },
525
+ };
526
+ </script>
527
+
528
+ <style scoped>
529
+ .osi-login-wrap {
530
+ display: flex;
531
+ justify-content: center;
532
+ padding: 2.5rem 1.25rem;
533
+ }
534
+ @media (min-width: 768px) {
535
+ .osi-login-wrap {
536
+ padding-block: 5rem;
537
+ }
538
+ }
539
+ .osi-login-body {
540
+ margin-top: 1.25rem;
541
+ }
542
+ .osi-login-submit {
543
+ margin-top: 0.5rem;
544
+ }
545
+ .osi-login-link {
546
+ width: fit-content;
547
+ font-size: 0.875rem;
548
+ color: inherit;
549
+ opacity: 0.7;
550
+ text-decoration: underline;
551
+ text-underline-offset: 3px;
552
+ cursor: pointer;
553
+ transition: opacity 0.2s ease;
554
+ }
555
+ .osi-login-link:hover {
556
+ opacity: 1;
557
+ }
558
+ .osi-login-eye {
559
+ display: flex;
560
+ padding: 0;
561
+ border: 0;
562
+ background: transparent;
563
+ color: inherit;
564
+ cursor: pointer;
565
+ }
566
+ .osi-login-eye svg {
567
+ width: 1.25rem;
568
+ height: 1.25rem;
569
+ }
570
+ .osi-login-terms {
571
+ display: flex;
572
+ align-items: flex-start;
573
+ gap: 0.75rem;
574
+ font-size: 0.875rem;
575
+ opacity: 0.85;
576
+ cursor: pointer;
577
+ }
578
+ .osi-login-check {
579
+ flex: none;
580
+ width: 1rem;
581
+ height: 1rem;
582
+ margin-top: 0.15rem;
583
+ accent-color: var(--osi-button-bg, var(--osi-btn-bg, currentColor));
584
+ }
585
+ </style>