@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.
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.js +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.js +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 +282 -0
  200. package/nuxt/osi-sections.ts +56 -0
  201. package/nuxt.config.ts +60 -0
  202. package/package.json +44 -4
  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,566 @@
1
+ <template>
2
+ <div class="w-full xlg1:w-[70%] flex flex-col gap-5 font-body">
3
+ <!-- The confirmation modal -->
4
+ <ConfirmDeleteModal
5
+ v-if="isDeleteModalVisible"
6
+ :jobId="addressidtodelete"
7
+ :isVisible="isDeleteModalVisible"
8
+ @confirm-delete="deleteAddress"
9
+ :isLoading="isDeleteLoading"
10
+ @cancel-delete="closeDeleteModal"
11
+ />
12
+ <!-- User Information Section -->
13
+ <div class="osi-profile-card w-full h-fit border border-gray-100 shadow-md p-5 bg-gray-50">
14
+ <div class="w-full flex justify-between items-center gap-3">
15
+ <h1 class="text-primaryColor font-body text-xl">Profile</h1>
16
+
17
+ <button
18
+ type="button"
19
+ class="osi-btn osi-btn--icon osi-profile-icon-btn"
20
+ :aria-label="isEditing ? 'Cancel editing' : 'Edit profile'"
21
+ @click="toggleProfileForm"
22
+ >
23
+ <svg
24
+ v-if="!isEditing"
25
+ xmlns="http://www.w3.org/2000/svg"
26
+ viewBox="0 0 24 24"
27
+ fill="none"
28
+ stroke="currentColor"
29
+ stroke-linecap="round"
30
+ stroke-linejoin="round"
31
+ stroke-width="1.5"
32
+ class="w-5"
33
+ >
34
+ <path d="M16.5 3.5a2.1 2.1 0 1 1 3 3L6 20l-4 1 1-4L16.5 3.5z" />
35
+ </svg>
36
+
37
+ <svg
38
+ v-else
39
+ xmlns="http://www.w3.org/2000/svg"
40
+ viewBox="0 0 24 24"
41
+ fill="none"
42
+ stroke="currentColor"
43
+ stroke-width="1.5"
44
+ stroke-linecap="round"
45
+ stroke-linejoin="round"
46
+ class="w-5"
47
+ >
48
+ <path d="M18 6 6 18" />
49
+ <path d="m6 6 12 12" />
50
+ </svg>
51
+ </button>
52
+ </div>
53
+ <div class="grid grid-cols-1 s:grid-cols-2 gap-x-5 gap-y-5 h-fit mt-5">
54
+ <OsiField
55
+ v-model="userData.first_name"
56
+ id="profile-first-name"
57
+ label="First Name"
58
+ autocomplete="given-name"
59
+ :disabled="!isEditing"
60
+ :error="userErrors.first_name"
61
+ @input="clearUserError('first_name')"
62
+ />
63
+
64
+ <OsiField
65
+ v-model="userData.last_name"
66
+ id="profile-last-name"
67
+ label="Last Name"
68
+ autocomplete="family-name"
69
+ :disabled="!isEditing"
70
+ :error="userErrors.last_name"
71
+ @input="clearUserError('last_name')"
72
+ />
73
+
74
+ <OsiField
75
+ v-model="userData.email"
76
+ id="profile-email"
77
+ label="Email"
78
+ type="email"
79
+ autocomplete="email"
80
+ :disabled="!isEditing"
81
+ :error="userErrors.email"
82
+ @input="clearUserError('email')"
83
+ />
84
+
85
+ <OsiField
86
+ v-if="isEditing"
87
+ label="Mobile Number"
88
+ floated
89
+ :error="userErrors.mobile_number"
90
+ >
91
+ <template #control="{ id, inputClass }">
92
+ <PhoneNumInput
93
+ v-model="userData.mobile_number"
94
+ variant="theme"
95
+ :input-id="id"
96
+ :input-class="inputClass"
97
+ :error="!!userErrors.mobile_number"
98
+ @update="phoneNumberResults = $event"
99
+ @clear-error="clearUserError('mobile_number')"
100
+ />
101
+ </template>
102
+ </OsiField>
103
+ <OsiField
104
+ v-else
105
+ v-model="userData.mobile_number"
106
+ id="profile-mobile"
107
+ label="Mobile Number"
108
+ type="tel"
109
+ disabled
110
+ />
111
+ </div>
112
+
113
+ <div
114
+ v-if="isEditing"
115
+ class="w-full flex justify-center items-center px-3 py-2 my-3"
116
+ >
117
+ <button
118
+ type="button"
119
+ class="osi-btn"
120
+ :disabled="isEditProfileLoading"
121
+ :aria-busy="isEditProfileLoading"
122
+ @click="saveProfile"
123
+ >
124
+ <span v-if="isEditProfileLoading" class="osi-btn-spinner" aria-hidden="true" />
125
+ <span>Update Profile</span>
126
+ </button>
127
+ </div>
128
+ </div>
129
+
130
+ <div class="osi-profile-card w-full h-fit border border-gray-100 shadow-md p-5 bg-gray-50">
131
+ <div class="w-full flex justify-between items-center gap-3 pr-2">
132
+ <h1 class="text-primaryColor font-body text-xl">Addresses</h1>
133
+
134
+ <button
135
+ type="button"
136
+ class="osi-btn osi-btn--icon osi-profile-icon-btn"
137
+ aria-label="Add address"
138
+ @click="toggleAddressForm"
139
+ >
140
+ <svg
141
+ xmlns="http://www.w3.org/2000/svg"
142
+ fill="none"
143
+ viewBox="0 0 24 24"
144
+ stroke-width="1.5"
145
+ stroke="currentColor"
146
+ class="w-5 h-5"
147
+ >
148
+ <path
149
+ stroke-linecap="round"
150
+ stroke-linejoin="round"
151
+ d="M12 4.5v15m7.5-7.5h-15"
152
+ />
153
+ </svg>
154
+ </button>
155
+ </div>
156
+
157
+ <div
158
+ v-if="
159
+ user?.customer_addresses &&
160
+ user?.customer_addresses.length === 0
161
+ "
162
+ >
163
+ <div class="w-full flex flex-col justify-center items-center gap-5">
164
+ <svg
165
+ xmlns="http://www.w3.org/2000/svg"
166
+ fill="none"
167
+ viewBox="0 0 24 24"
168
+ stroke-width="1.5"
169
+ stroke="currentColor"
170
+ class="w-[50%] s:w-[200px] text-primaryColor opacity-10"
171
+ >
172
+ <path
173
+ stroke-linecap="round"
174
+ stroke-linejoin="round"
175
+ d="M15 10.5a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
176
+ />
177
+ <path
178
+ stroke-linecap="round"
179
+ stroke-linejoin="round"
180
+ d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1 1 15 0Z"
181
+ />
182
+ </svg>
183
+
184
+ <p
185
+ class="text-primaryColor font-body text-xl s:text-2xl text-center"
186
+ >
187
+ You have not added any addresses yet
188
+ </p>
189
+ <div class="text-center text-gray-500">
190
+ <p class="font-body text-sm">
191
+ Add your addresses for a quicker and more efficient checkout.
192
+ </p>
193
+ </div>
194
+ </div>
195
+ </div>
196
+ <div v-else class="w-full grid grid-cols-1 s:grid-cols-2 gap-3 mt-5">
197
+ <div
198
+ v-for="address in user?.customer_addresses"
199
+ :key="address.id"
200
+ class="w-full flex flex-col gap-3"
201
+ >
202
+ <div class="h-[200px]">
203
+ <div
204
+ class="bg-white shadow-sm h-[200px] flex flex-col gap-3 p-3"
205
+ >
206
+
207
+ <div class="flex w-full justify-end gap-1">
208
+ <span
209
+ @click="editAddress(address)"
210
+ class="cursor-pointer text-center text-primaryColor text-xs duration-200 mr-1 inline-block"
211
+ >
212
+ <svg data-v-52d6a4bb="" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" class="w-5 text-primaryColor"><path data-v-52d6a4bb="" d="M16.5 3.5a2.1 2.1 0 1 1 3 3L6 20l-4 1 1-4L16.5 3.5z"></path></svg>
213
+ </span>
214
+ <span
215
+ class="cursor-pointer text-center text-red-500 text-xs duration-200 inline-block"
216
+ @click.stop="openDeleteModal(address.id)"
217
+ >
218
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-red-500" viewBox="0 0 24 24" fill="none">
219
+ <path d="M10 11V17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
220
+ <path d="M14 11V17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
221
+ <path d="M4 7H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
222
+ <path d="M6 7H12H18V18C18 19.6569 16.6569 21 15 21H9C7.34315 21 6 19.6569 6 18V7Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
223
+ <path d="M9 5C9 3.89543 9.89543 3 11 3H13C14.1046 3 15 3.89543 15 5V7H9V5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
224
+ </svg>
225
+ </span>
226
+ </div>
227
+
228
+ <div
229
+ class="w-full bg-white text-gray-500 text-sm s:text-base grid gap-2"
230
+ >
231
+ <p class="font-body">
232
+ <span class="font-semibold">Country:</span>
233
+ {{ address.country_name }}
234
+ </p>
235
+ <p v-if="address.zone" class="font-body">
236
+ <span class="font-semibold">Zone:</span>
237
+ {{ address.zone_name }}
238
+ </p>
239
+ <p
240
+ class="font-body overflow-hidden text-ellipsis line-clamp-2"
241
+ >
242
+ <span class="font-semibold">Address:</span>
243
+ {{ address.address }}
244
+ </p>
245
+ <p class="font-body" v-if="address.postal_code">
246
+ <span class="font-semibold">Zip Code:</span>
247
+ {{ address.postal_code }}
248
+ </p>
249
+ </div>
250
+
251
+
252
+ </div>
253
+ </div>
254
+
255
+
256
+ <div class="mt-5 font-body">
257
+ </div>
258
+
259
+ </div>
260
+ </div>
261
+ </div>
262
+
263
+ <!-- Add Address Modal -->
264
+ <AddAddressModal
265
+ :isVisible="isAddAddressModalVisible"
266
+ :countries="countries"
267
+ :addressToEdit="addressToEdit"
268
+ @close="closeAddAddressModal"
269
+ @address-added="onAddressAdded"
270
+ />
271
+ </div>
272
+ </template>
273
+
274
+ <script>
275
+ import UserServices from "../../../services/UserServices";
276
+ import { useToast } from "vue-toastification";
277
+ import { PhoneNumInput } from "#components";
278
+ import { OsiField } from "#components";
279
+ import { authBus } from "../../../services/AuthBus";
280
+ import { ConfirmDeleteModal } from "#components";
281
+ import AddAddressModal from "./AddAddressModal.vue";
282
+
283
+ export default {
284
+ components: {
285
+ PhoneNumInput,
286
+ OsiField,
287
+ ConfirmDeleteModal,
288
+ AddAddressModal,
289
+ },
290
+ data() {
291
+ return {
292
+ isEditing: false,
293
+ phoneNumberResults: null,
294
+ isEditProfileLoading: false,
295
+ isAddAddressModalVisible: false,
296
+ isLoading: false,
297
+ isEditLoading: false,
298
+ isDeleteModalVisible: false,
299
+ isDeleteLoading:false,
300
+ addressidtodelete: null,
301
+ emailError: "",
302
+ countryCode: "LB",
303
+ userData: {
304
+ first_name: "",
305
+ last_name: "",
306
+ email: "",
307
+ mobile_number: "",
308
+ },
309
+ userErrors: {
310
+ first_name: false,
311
+ last_name: false,
312
+ email: false,
313
+ mobile_number: false,
314
+ },
315
+ addressToEdit: null,
316
+ };
317
+ },
318
+ props: {
319
+ countries: {
320
+ type: Object,
321
+ },
322
+ user: {
323
+ type: Object,
324
+ required: true,
325
+ },
326
+ addresses: {
327
+ type: Array,
328
+ required: true,
329
+ },
330
+ },
331
+ mounted() {
332
+ if (this.user) {
333
+ this.userData.first_name = this.user.first_name;
334
+ this.userData.last_name = this.user.last_name;
335
+ this.userData.email = this.user.email;
336
+ this.userData.mobile_number = this.user.mobile_number;
337
+ }
338
+ },
339
+ methods: {
340
+ clearError(field) {
341
+ this.errors[field] = false;
342
+ },
343
+ clearUserError(field) {
344
+ this.userErrors[field] = false;
345
+ },
346
+ validateEmail() {
347
+ const emailPattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;
348
+
349
+ if (!emailPattern.test(this.userData.email)) {
350
+ this.emailError = "Please enter a valid email address.";
351
+ return;
352
+ } else {
353
+ this.emailError = "";
354
+ }
355
+ },
356
+ async deleteAddress() {
357
+ const toast = useToast();
358
+ try {
359
+ if (!this.addressidtodelete) {
360
+ console.error("No job ID to delete.");
361
+ return;
362
+ }
363
+ this.isDeleteLoading = true
364
+ const response = await UserServices.deleteAddress(
365
+ this.addressidtodelete
366
+ );
367
+ toast.success("Address deleted");
368
+ this.$emit("address-added");
369
+ this.isDeleteLoading = false;
370
+ this.isDeleteModalVisible = false;
371
+ } catch (error) {
372
+ console.error("Error deleting address:", error);
373
+ toast.error("Something went wrong");
374
+ } finally {
375
+ this.isDeleteLoading = false;
376
+ this.isDeleteModalVisible = false;
377
+
378
+ }
379
+ },
380
+
381
+ openDeleteModal(jobId) {
382
+ this.addressidtodelete = jobId;
383
+ this.isDeleteModalVisible = true;
384
+ },
385
+
386
+ closeDeleteModal() {
387
+ this.isDeleteModalVisible = false;
388
+ },
389
+ async updateAddress() {
390
+ const toast = useToast();
391
+ const updatedData = {};
392
+ const addressId = this.originalAddress.id;
393
+
394
+ for (const key in this.editAddressFormData.address) {
395
+ if (
396
+ key !== "id" &&
397
+ this.editAddressFormData.address[key] !== this.originalAddress[key]
398
+ ) {
399
+ updatedData[key] = this.editAddressFormData.address[key];
400
+ }
401
+ }
402
+
403
+ const countryChanged =
404
+ this.selectedCountry &&
405
+ this.selectedCountry.id !== this.originalAddress.country;
406
+
407
+ if (countryChanged) {
408
+ updatedData.country = this.selectedCountry.id;
409
+
410
+ if (this.selectedCountry.shipping_zones.length === 0) {
411
+ updatedData.zone = null;
412
+ }
413
+ }
414
+
415
+ if (countryChanged && this.selectedCountry.shipping_zones.length > 0) {
416
+ if (!this.selectedZone) {
417
+ toast.error("Please select a zone if required.");
418
+ return;
419
+ } else {
420
+ updatedData.zone = this.selectedZone.id;
421
+ }
422
+ }
423
+
424
+ const zoneChanged =
425
+ this.selectedZone && this.selectedZone.id !== this.originalAddress.zone;
426
+
427
+ if (zoneChanged) {
428
+ updatedData.zone = this.selectedZone.id;
429
+ }
430
+
431
+ if (Object.keys(updatedData).length === 0) {
432
+ return;
433
+ }
434
+
435
+
436
+ try {
437
+ this.isEditLoading = true;
438
+ await UserServices.editAddress(addressId, updatedData);
439
+ toast.success("Address edited");
440
+
441
+ this.originalAddress = { ...this.originalAddress, ...updatedData };
442
+
443
+ this.isEditing = false;
444
+ this.selectedAddress = null;
445
+
446
+ this.$emit("address-added");
447
+ } catch (error) {
448
+ toast.error("Something went wrong");
449
+ } finally {
450
+ this.isEditLoading = false;
451
+ }
452
+ },
453
+ toggleAddressForm() {
454
+ this.isAddAddressModalVisible = true;
455
+ if (this.isAddAddressModalVisible) {
456
+ this.addressToEdit = null;
457
+ this.showEditAddressForm = false;
458
+ this.selectedAddress = null;
459
+ }
460
+ },
461
+
462
+ closeAddAddressModal() {
463
+ this.isAddAddressModalVisible = false;
464
+ this.addressToEdit = null;
465
+ },
466
+
467
+ onAddressAdded() {
468
+ this.$emit("address-added");
469
+ this.closeAddAddressModal();
470
+ },
471
+
472
+ editAddress(address) {
473
+ this.addressToEdit = address;
474
+ this.isAddAddressModalVisible = true;
475
+ },
476
+
477
+ toggleProfileForm() {
478
+ this.isEditing = !this.isEditing;
479
+ },
480
+ isValidEmail(email) {
481
+ const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
482
+ return regex.test(email);
483
+ },
484
+ async saveProfile() {
485
+ let isValid = true;
486
+ this.userErrors.email = false;
487
+ this.userErrors.first_name = false;
488
+ this.userErrors.last_name = false;
489
+ this.userErrors.mobile_number = false;
490
+ if (!this.userData.first_name) {
491
+ this.userErrors.first_name = "Last name is required";
492
+ isValid = false;
493
+ }
494
+ if (!this.userData.last_name) {
495
+ this.userErrors.last_name = "Last name is required";
496
+ isValid = false;
497
+ }
498
+ if (!this.userData.mobile_number) {
499
+ this.userErrors.mobile_number = "Mobile number is required";
500
+ isValid = false;
501
+ } else if (this.phoneNumberResults && !this.phoneNumberResults.isValid) {
502
+ this.userErrors.mobile_number = "Mobile number is not valid";
503
+ isValid = false;
504
+ }
505
+ if (!this.userData.email) {
506
+ this.userErrors.email = "Email is required";
507
+ isValid = false;
508
+ } else if (!this.isValidEmail(this.userData.email)) {
509
+ this.userErrors.email = "Please enter a valid email address";
510
+ isValid = false;
511
+ }
512
+ if (!isValid) {
513
+ return;
514
+ }
515
+ const toast = useToast();
516
+ try {
517
+ this.isEditing = true;
518
+ this.isEditProfileLoading = true;
519
+ const response = await UserServices.updateUserDetail(this.userData);
520
+ toast.success("Profile updated successfully");
521
+ await authBus.checkAuth();
522
+ } catch (error) {
523
+ const data = error?.data;
524
+ const errorMessages = Array.isArray(data)
525
+ ? data
526
+ : data && typeof data === 'object'
527
+ ? Object.values(data).flat()
528
+ : [error?.message || 'Something went wrong'];
529
+
530
+ if (errorMessages.length > 1) {
531
+ toast.error(`• ${errorMessages.join("\n• ")}`, {
532
+ timeout: 5000,
533
+ closeOnClick: true,
534
+ pauseOnHover: true,
535
+ });
536
+ } else {
537
+ toast.error(errorMessages[0]);
538
+ }
539
+
540
+ this.isEditing = false;
541
+ this.isEditProfileLoading = false;
542
+ } finally {
543
+ this.isEditing = false;
544
+ this.isEditProfileLoading = false;
545
+ }
546
+ },
547
+ },
548
+ };
549
+ </script>
550
+
551
+ <style scoped>
552
+ /* Fields and buttons come from the theme (OsiField / .osi-btn). */
553
+ .osi-profile-card {
554
+ /* Floating labels cut the field border with a chip in this colour. */
555
+ --osi-field-chip: #f9fafb;
556
+ }
557
+ .osi-profile-icon-btn {
558
+ --btn-pad-y: 5px;
559
+ width: 30px;
560
+ height: 30px;
561
+ }
562
+ .osi-profile-icon-btn svg {
563
+ width: 1.25rem;
564
+ height: 1.25rem;
565
+ }
566
+ </style>