@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,91 @@
|
|
|
1
|
+
import { apiService, getBaseUrl } from './ApiServices';
|
|
2
|
+
import { getCartToken, setCartToken, clearCartToken } from './CartToken';
|
|
3
|
+
import { analytics } from '#osi/utils/analytics';
|
|
4
|
+
|
|
5
|
+
export const cartHeaders = () => {
|
|
6
|
+
const token = getCartToken();
|
|
7
|
+
return token ? { "X-Cart-Token": token } : {};
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// Every cart response — view, add, remove, merge — comes through here, so it is
|
|
11
|
+
// also where analytics learns what the cart holds. (Used to tell what an edit
|
|
12
|
+
// changed: the server's answer to a remove no longer contains the removed line.)
|
|
13
|
+
const persistCartToken = (responseData) => {
|
|
14
|
+
if (responseData?.cart_token) setCartToken(responseData.cart_token);
|
|
15
|
+
analytics.rememberCart(responseData);
|
|
16
|
+
return responseData;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Add to / remove from a cart line (a line is a variant id).
|
|
21
|
+
*
|
|
22
|
+
* `context` is optional: callers that hold the product (product page, card,
|
|
23
|
+
* quick buy) pass `{ product, variant }` so the analytics event carries its
|
|
24
|
+
* brand and category, which the cart payload doesn't repeat. Cart-side callers
|
|
25
|
+
* (drawer, checkout) omit it and get it from what was remembered at add time.
|
|
26
|
+
*
|
|
27
|
+
* This is the single place add_to_cart / remove_from_cart are reported: every
|
|
28
|
+
* cart edit in the storefront goes through here, once.
|
|
29
|
+
*/
|
|
30
|
+
const EditCart = async (item_id, quantity = 1, action, context) => {
|
|
31
|
+
try {
|
|
32
|
+
const baseUrl = getBaseUrl();
|
|
33
|
+
const before = analytics.peekCartLine(item_id);
|
|
34
|
+
const responseData = await apiService(`${baseUrl}store/apis/cart/update-item/`, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: cartHeaders(),
|
|
37
|
+
body: JSON.stringify({ quantity, item_id, action }),
|
|
38
|
+
});
|
|
39
|
+
persistCartToken(responseData);
|
|
40
|
+
analytics.cartEdited({
|
|
41
|
+
itemId: item_id,
|
|
42
|
+
quantity,
|
|
43
|
+
action,
|
|
44
|
+
before,
|
|
45
|
+
after: responseData?.cart?.[item_id] ?? null,
|
|
46
|
+
context,
|
|
47
|
+
});
|
|
48
|
+
return responseData;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('Cart fetch error:', error);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const fetchCart = async () => {
|
|
56
|
+
try {
|
|
57
|
+
const baseUrl = getBaseUrl();
|
|
58
|
+
const responseData = await apiService(`${baseUrl}store/apis/cart/view/`, {
|
|
59
|
+
method: 'GET',
|
|
60
|
+
headers: cartHeaders(),
|
|
61
|
+
});
|
|
62
|
+
return persistCartToken(responseData);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('Cart fetch error:', error);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const mergeCart = async () => {
|
|
70
|
+
const token = getCartToken();
|
|
71
|
+
if (!token) return null;
|
|
72
|
+
try {
|
|
73
|
+
const baseUrl = getBaseUrl();
|
|
74
|
+
const responseData = await apiService(`${baseUrl}store/apis/cart/merge/`, {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: cartHeaders(),
|
|
77
|
+
body: JSON.stringify({ cart_token: token }),
|
|
78
|
+
});
|
|
79
|
+
clearCartToken();
|
|
80
|
+
return responseData;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error('Cart merge error:', error);
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export default {
|
|
88
|
+
EditCart,
|
|
89
|
+
fetchCart,
|
|
90
|
+
mergeCart,
|
|
91
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const KEY = "cart_token";
|
|
2
|
+
|
|
3
|
+
export const getCartToken = () => {
|
|
4
|
+
try {
|
|
5
|
+
return localStorage.getItem(KEY);
|
|
6
|
+
} catch {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const setCartToken = (token) => {
|
|
12
|
+
if (!token) return;
|
|
13
|
+
try {
|
|
14
|
+
localStorage.setItem(KEY, token);
|
|
15
|
+
} catch {
|
|
16
|
+
/* ignore */
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const clearCartToken = () => {
|
|
21
|
+
try {
|
|
22
|
+
localStorage.removeItem(KEY);
|
|
23
|
+
} catch {
|
|
24
|
+
/* ignore */
|
|
25
|
+
}
|
|
26
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { apiService, getBaseUrl } from "./ApiServices";
|
|
2
|
+
|
|
3
|
+
// See HomeServices.js — optional trailing baseUrl, resolved by the caller
|
|
4
|
+
// before its first await when this is used from SSR (category/listing pages).
|
|
5
|
+
|
|
6
|
+
const getCategoryProducts = async (id, params = {}, baseUrl) => {
|
|
7
|
+
try {
|
|
8
|
+
baseUrl = baseUrl || getBaseUrl();
|
|
9
|
+
const { page, pageSize, ordering, values } = params;
|
|
10
|
+
const searchParams = new URLSearchParams();
|
|
11
|
+
|
|
12
|
+
if (Array.isArray(id)) {
|
|
13
|
+
id.forEach(catId => {
|
|
14
|
+
if (catId) searchParams.append('categories', catId);
|
|
15
|
+
});
|
|
16
|
+
} else if (id) {
|
|
17
|
+
searchParams.append('categories', id);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (page) searchParams.set('page', page);
|
|
21
|
+
if (pageSize) searchParams.set('page_size', pageSize);
|
|
22
|
+
if (ordering) searchParams.set('ordering', ordering);
|
|
23
|
+
if (Array.isArray(values)) {
|
|
24
|
+
values.forEach((v) => {
|
|
25
|
+
if (v) searchParams.append('variant_values', v);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const responseData = await apiService(`${baseUrl}store/apis/products/?${searchParams.toString()}`, {
|
|
30
|
+
method: 'GET',
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return responseData;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getFilters = async (baseUrl) => {
|
|
40
|
+
try {
|
|
41
|
+
baseUrl = baseUrl || getBaseUrl();
|
|
42
|
+
const responseData = await apiService(`${baseUrl}store/apis/variant-types/`, {
|
|
43
|
+
method: 'GET',
|
|
44
|
+
});
|
|
45
|
+
return responseData;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default {
|
|
52
|
+
getCategoryProducts,
|
|
53
|
+
getFilters,
|
|
54
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { apiService, getBaseUrl } from "./ApiServices";
|
|
2
|
+
import { getCartToken, setCartToken } from "./CartToken";
|
|
3
|
+
|
|
4
|
+
const cartHeaders = () => {
|
|
5
|
+
const token = getCartToken();
|
|
6
|
+
return token ? { "X-Cart-Token": token } : {};
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const fetchCart = async () => {
|
|
10
|
+
try {
|
|
11
|
+
const baseUrl = getBaseUrl();
|
|
12
|
+
const responseData = await apiService(`${baseUrl}store/apis/cart/view/`, {
|
|
13
|
+
method: "GET",
|
|
14
|
+
headers: cartHeaders(),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
if (responseData?.cart_token) setCartToken(responseData.cart_token);
|
|
18
|
+
return responseData;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error("Cart fetch error:", error);
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Posts the payload as built by the checkout page. The cart itself is resolved
|
|
26
|
+
// server-side from X-Cart-Token — the `cart` key in the body is a leftover the
|
|
27
|
+
// backend ignores, kept so the request shape matches what the live stores send.
|
|
28
|
+
const placeOrder = async (data) => {
|
|
29
|
+
try {
|
|
30
|
+
const baseUrl = getBaseUrl();
|
|
31
|
+
const responseData = await apiService(`${baseUrl}store/apis/checkout/`, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: cartHeaders(),
|
|
34
|
+
body: JSON.stringify(data),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return responseData;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error("Error in placeOrder:", error);
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const getOrder = async (orderId) => {
|
|
45
|
+
try {
|
|
46
|
+
const baseUrl = getBaseUrl();
|
|
47
|
+
const responseData = await apiService(
|
|
48
|
+
`${baseUrl}store/apis/order-summaries/${orderId}/`,
|
|
49
|
+
{
|
|
50
|
+
method: "GET",
|
|
51
|
+
headers: cartHeaders(),
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return responseData;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error("Error fetching order summary:", error);
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const promoCode = async (code) => {
|
|
63
|
+
try {
|
|
64
|
+
const baseUrl = getBaseUrl();
|
|
65
|
+
const responseData = await apiService(
|
|
66
|
+
`${baseUrl}store/apis/promocodes/?code=${code}`,
|
|
67
|
+
{ method: "GET" },
|
|
68
|
+
);
|
|
69
|
+
return responseData;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error("Promo code error:", error);
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const freeDeliveries = async (country) => {
|
|
77
|
+
try {
|
|
78
|
+
const baseUrl = getBaseUrl();
|
|
79
|
+
const responseData = await apiService(
|
|
80
|
+
`${baseUrl}store/apis/freedeliveries/?countries=${country}`,
|
|
81
|
+
{ method: "GET" },
|
|
82
|
+
);
|
|
83
|
+
return responseData;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error("Free deliveries error:", error);
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Optional per-store checkout add-ons (donations, packaging, service fees).
|
|
91
|
+
// Rendered by components/checkout/ExtraFees.vue and sent back as
|
|
92
|
+
// payload.extra_fees on checkout.
|
|
93
|
+
const extraFees = async () => {
|
|
94
|
+
try {
|
|
95
|
+
const baseUrl = getBaseUrl();
|
|
96
|
+
const responseData = await apiService(`${baseUrl}store/apis/extrafees/`, {
|
|
97
|
+
method: "GET",
|
|
98
|
+
});
|
|
99
|
+
return responseData;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error("Extra fees error:", error);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export default {
|
|
107
|
+
fetchCart,
|
|
108
|
+
placeOrder,
|
|
109
|
+
getOrder,
|
|
110
|
+
promoCode,
|
|
111
|
+
freeDeliveries,
|
|
112
|
+
extraFees,
|
|
113
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { apiService, getBaseUrl } from "./ApiServices";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Every method here accepts an optional trailing `baseUrl`. Pass it when
|
|
5
|
+
* calling from SSR (resolve it in the composable/setup with
|
|
6
|
+
* useState('base-url') or useStoreInit(), *before* your first await, then
|
|
7
|
+
* hand it down). Without it, this falls back to getBaseUrl() internally —
|
|
8
|
+
* fine for client-only calls (button handlers, onMounted), but on the server
|
|
9
|
+
* that internal call happens after this function's own await boundary, where
|
|
10
|
+
* Nuxt's instance context is already gone. See ApiServices.js and
|
|
11
|
+
* storefront.service.js for the full explanation.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const getCategories = async (baseUrl) => {
|
|
15
|
+
try {
|
|
16
|
+
baseUrl = baseUrl || getBaseUrl();
|
|
17
|
+
const responseData = await apiService(`${baseUrl}store/apis/categories/`, {
|
|
18
|
+
method: 'GET',
|
|
19
|
+
});
|
|
20
|
+
return responseData;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const getNewArrivals = async (pageSize = 10, baseUrl) => {
|
|
27
|
+
try {
|
|
28
|
+
baseUrl = baseUrl || getBaseUrl();
|
|
29
|
+
const responseData = await apiService(`${baseUrl}store/apis/products/?page_size=${pageSize}`, {
|
|
30
|
+
method: 'GET',
|
|
31
|
+
});
|
|
32
|
+
return responseData;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const getSectionProduct = async (section, pageSize = 10, baseUrl) => {
|
|
39
|
+
try {
|
|
40
|
+
baseUrl = baseUrl || getBaseUrl();
|
|
41
|
+
const responseData = await apiService(`${baseUrl}store/apis/products/?sections=${section}&page_size=${pageSize}`, {
|
|
42
|
+
method: 'GET',
|
|
43
|
+
});
|
|
44
|
+
return responseData;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const getTrendingProducts = async (baseUrl) => {
|
|
51
|
+
try {
|
|
52
|
+
baseUrl = baseUrl || getBaseUrl();
|
|
53
|
+
const responseData = await apiService(`${baseUrl}store/apis/products/`, {
|
|
54
|
+
method: 'GET',
|
|
55
|
+
});
|
|
56
|
+
return responseData;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const getBanners = async (baseUrl) => {
|
|
63
|
+
try {
|
|
64
|
+
baseUrl = baseUrl || getBaseUrl();
|
|
65
|
+
const responseData = await apiService(`${baseUrl}store/apis/banners/`, {
|
|
66
|
+
method: "GET",
|
|
67
|
+
});
|
|
68
|
+
return responseData;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const getCollections = async (baseUrl) => {
|
|
75
|
+
try {
|
|
76
|
+
baseUrl = baseUrl || getBaseUrl();
|
|
77
|
+
const responseData = await apiService(`${baseUrl}store/apis/collections/`, {
|
|
78
|
+
method: 'GET',
|
|
79
|
+
});
|
|
80
|
+
return responseData;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const getCollectionProducts = async (id, params = {}, baseUrl) => {
|
|
87
|
+
try {
|
|
88
|
+
baseUrl = baseUrl || getBaseUrl();
|
|
89
|
+
const { page, pageSize, ordering, values } = params;
|
|
90
|
+
const searchParams = new URLSearchParams();
|
|
91
|
+
if (id) {
|
|
92
|
+
searchParams.set('collections', id);
|
|
93
|
+
}
|
|
94
|
+
if (page) searchParams.set('page', page);
|
|
95
|
+
if (pageSize) searchParams.set('page_size', pageSize);
|
|
96
|
+
if (ordering) searchParams.set('ordering', ordering);
|
|
97
|
+
if (Array.isArray(values)) {
|
|
98
|
+
values.forEach((v) => {
|
|
99
|
+
if (v) searchParams.append('variant_values', v);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const responseData = await apiService(`${baseUrl}store/apis/products/?${searchParams.toString()}`, {
|
|
104
|
+
method: 'GET',
|
|
105
|
+
});
|
|
106
|
+
return responseData;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const getAnnouncements = async (baseUrl) => {
|
|
113
|
+
try {
|
|
114
|
+
baseUrl = baseUrl || getBaseUrl();
|
|
115
|
+
const responseData = await apiService(`${baseUrl}store/apis/announcementbars/`, {
|
|
116
|
+
method: "GET",
|
|
117
|
+
});
|
|
118
|
+
return responseData;
|
|
119
|
+
} catch (error) {
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export default {
|
|
125
|
+
getNewArrivals,
|
|
126
|
+
getCategories,
|
|
127
|
+
getTrendingProducts,
|
|
128
|
+
getBanners,
|
|
129
|
+
getSectionProduct,
|
|
130
|
+
getCollections,
|
|
131
|
+
getCollectionProducts,
|
|
132
|
+
getAnnouncements,
|
|
133
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { apiService, getBaseUrl } from "./ApiServices";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Newsletter subscriptions.
|
|
5
|
+
*
|
|
6
|
+
* ⚠ There is no subscribe endpoint in the storefront API yet — UserServices has
|
|
7
|
+
* contact() but nothing for mailing lists, and Terazar's footer form only ever
|
|
8
|
+
* called alert(). So this file is the one place that has to change once the
|
|
9
|
+
* backend route exists.
|
|
10
|
+
*
|
|
11
|
+
* If the real path differs, edit ENDPOINT and nothing else: NewsletterSection
|
|
12
|
+
* doesn't know the URL.
|
|
13
|
+
*
|
|
14
|
+
* Expected contract:
|
|
15
|
+
* POST <base>store/apis/newsletter-subscribers/
|
|
16
|
+
* { email, source }
|
|
17
|
+
* 201 -> created; 400 with { email: [...] } -> already subscribed / invalid
|
|
18
|
+
*/
|
|
19
|
+
const ENDPOINT = "store/apis/newsletter-subscribers/";
|
|
20
|
+
|
|
21
|
+
const subscribe = async (email, source = "STOREFRONT") => {
|
|
22
|
+
const baseUrl = getBaseUrl();
|
|
23
|
+
return apiService(`${baseUrl}${ENDPOINT}`, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
body: JSON.stringify({ email, source }),
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default { subscribe };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { apiService, getBaseUrl } from "./ApiServices";
|
|
2
|
+
|
|
3
|
+
// The tenant is resolved from the Host/Origin by StoreResolutionMiddleware, so
|
|
4
|
+
// there's no ?store= param here — and no VITE_BASE_URL either: one Nuxt process
|
|
5
|
+
// serves every store, so the host is read at request time via getBaseUrl().
|
|
6
|
+
const paymentsUrl = () => `${getBaseUrl()}store/apis/payments/`;
|
|
7
|
+
|
|
8
|
+
const REFERENCE_KEY = "pay_reference";
|
|
9
|
+
|
|
10
|
+
// Only methods that are active, unlisted-free and fully configured come back —
|
|
11
|
+
// the server already filtered, so the storefront renders whatever it gets.
|
|
12
|
+
const getMethods = async () => {
|
|
13
|
+
try {
|
|
14
|
+
const responseData = await apiService(`${paymentsUrl()}methods/`, {
|
|
15
|
+
method: "GET",
|
|
16
|
+
});
|
|
17
|
+
// tolerate both a bare array and a DRF paginated envelope
|
|
18
|
+
return Array.isArray(responseData) ? responseData : responseData?.results || [];
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error("Payment methods fetch error:", error);
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const completeClientSecretPayment = async (reference, transientToken) => {
|
|
26
|
+
try {
|
|
27
|
+
return await apiService(`${paymentsUrl()}verify/`, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
body: JSON.stringify({ reference, transient_token: transientToken }),
|
|
30
|
+
});
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error("Payment completion error:", error);
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const verify = async (reference) => {
|
|
38
|
+
try {
|
|
39
|
+
return await apiService(
|
|
40
|
+
`${paymentsUrl()}verify/?reference=${encodeURIComponent(reference)}`,
|
|
41
|
+
{ method: "GET" },
|
|
42
|
+
);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error("Payment verify error:", error);
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const setReference = (reference) => {
|
|
50
|
+
if (typeof window === "undefined" || !reference) return;
|
|
51
|
+
sessionStorage.setItem(REFERENCE_KEY, reference);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const getReference = () => {
|
|
55
|
+
if (typeof window === "undefined") return null;
|
|
56
|
+
return sessionStorage.getItem(REFERENCE_KEY);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const clearReference = () => {
|
|
60
|
+
if (typeof window === "undefined") return;
|
|
61
|
+
sessionStorage.removeItem(REFERENCE_KEY);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default {
|
|
65
|
+
getMethods,
|
|
66
|
+
verify,
|
|
67
|
+
completeClientSecretPayment,
|
|
68
|
+
setReference,
|
|
69
|
+
getReference,
|
|
70
|
+
clearReference,
|
|
71
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { apiService, getBaseUrl } from "./ApiServices";
|
|
2
|
+
|
|
3
|
+
const getPolicies = async () => {
|
|
4
|
+
try {
|
|
5
|
+
const baseUrl = getBaseUrl();
|
|
6
|
+
const responseData = await apiService(`${baseUrl}store/apis/policies/`, {
|
|
7
|
+
method: 'GET',
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
return responseData;
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.log(error);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default { getPolicies };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { apiService, getBaseUrl } from "./ApiServices";
|
|
2
|
+
|
|
3
|
+
// See HomeServices.js — optional trailing baseUrl, resolved by the caller
|
|
4
|
+
// before its first await when this is used from SSR (product detail pages).
|
|
5
|
+
|
|
6
|
+
const getProductDetail = async (id, baseUrl) => {
|
|
7
|
+
try {
|
|
8
|
+
baseUrl = baseUrl || getBaseUrl();
|
|
9
|
+
const responseData = await apiService(`${baseUrl}store/apis/products/${id}`, {
|
|
10
|
+
method: 'GET',
|
|
11
|
+
credentials: "include",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
return responseData;
|
|
15
|
+
} catch (error) {
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const getProductId = async (id, baseUrl) => {
|
|
21
|
+
try {
|
|
22
|
+
baseUrl = baseUrl || getBaseUrl();
|
|
23
|
+
const responseData = await apiService(`${baseUrl}store/apis/products/?slugified_name=${id}`, {
|
|
24
|
+
method: 'GET',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return responseData;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const getProductsByIds = async (productIds, baseUrl) => {
|
|
34
|
+
try {
|
|
35
|
+
baseUrl = baseUrl || getBaseUrl();
|
|
36
|
+
const searchParams = new URLSearchParams();
|
|
37
|
+
|
|
38
|
+
if (Array.isArray(productIds)) {
|
|
39
|
+
productIds.forEach(productId => {
|
|
40
|
+
if (productId) searchParams.append('id', productId);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const responseData = await apiService(`${baseUrl}store/apis/products/?${searchParams.toString()}`, {
|
|
45
|
+
method: 'GET',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return responseData;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const listProducts = async (params = {}, baseUrl) => {
|
|
55
|
+
try {
|
|
56
|
+
baseUrl = baseUrl || getBaseUrl();
|
|
57
|
+
const searchParams = new URLSearchParams();
|
|
58
|
+
|
|
59
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
60
|
+
if (value !== undefined && value !== null && value !== "") {
|
|
61
|
+
searchParams.append(key, value);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return await apiService(`${baseUrl}store/apis/products/?${searchParams.toString()}`, {
|
|
66
|
+
method: 'GET',
|
|
67
|
+
});
|
|
68
|
+
} catch (error) {
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
export default {
|
|
75
|
+
getProductDetail,
|
|
76
|
+
getProductId,
|
|
77
|
+
getProductsByIds,
|
|
78
|
+
listProducts
|
|
79
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { apiService, getBaseUrl } from "./ApiServices";
|
|
2
|
+
|
|
3
|
+
const search = async (searchQuery) => {
|
|
4
|
+
try {
|
|
5
|
+
const baseUrl = getBaseUrl();
|
|
6
|
+
const responseData = await apiService(`${baseUrl}store/apis/products/?search=${encodeURIComponent(searchQuery)}`, {
|
|
7
|
+
method: 'GET',
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
return responseData;
|
|
11
|
+
} catch (error) {
|
|
12
|
+
throw error;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
search,
|
|
18
|
+
};
|