From 5d590594749f3adec4fdbb2768d270b88227b77d Mon Sep 17 00:00:00 2001 From: Arina Yakovenko Date: Wed, 2 Jul 2025 15:55:13 +0200 Subject: [PATCH] summary checkout --- components/HeaderBlock.vue | 29 +-- components/section/CheckoutSummary.vue | 192 +++++++++++++++++- components/section/Product.vue | 7 + public/photo.svg | 2 +- stores/checkoutStore.ts | 267 +++++++++++++++++++++---- stores/menuStore.ts | 219 ++++++++++---------- types/checkout.ts | 39 ++++ 7 files changed, 581 insertions(+), 174 deletions(-) diff --git a/components/HeaderBlock.vue b/components/HeaderBlock.vue index 4120f0e..fca22e9 100644 --- a/components/HeaderBlock.vue +++ b/components/HeaderBlock.vue @@ -32,30 +32,11 @@
-

- button -

-
- -
+
+ +
{{ userStore.user }}
diff --git a/components/section/CheckoutSummary.vue b/components/section/CheckoutSummary.vue index 8e57df7..31439ef 100644 --- a/components/section/CheckoutSummary.vue +++ b/components/section/CheckoutSummary.vue @@ -9,8 +9,160 @@
{{ $t("login") }}
-
- {{ $t("address") }} +
+
+

{{ component.front_section_lang[0].data.product_list }}

+
+
+
+
+ +
+ +
+
+

+ {{ item.name }} +

+
+
+

+ {{ item.total_price }} +

+
+
+
+
+
+
+
+
+

{{ component.front_section_lang[0].data.account_address }} +

+
+ {{ checkoutStore.defaultAddress?.address.name }} {{ + checkoutStore.defaultAddress?.address.surname }} + {{ checkoutStore.defaultAddress?.address.street }} + {{ checkoutStore.defaultAddress?.address.postcode }} {{ + checkoutStore.defaultAddress?.address.city }} +
+
+
+

{{ component.front_section_lang[0].data.note }}

+ +
+
+
+

Typ doručení

+
+
+
+
+
+
+
+
+
+ {{ delivery.delivery_supplier_name }} + {{ delivery.country_name }} +
+
+

+ {{ menuStore.formatPrice(Number(delivery.shippment_price)) }} +

+
+
+
+
+
+

{{ component.front_section_lang[0].data.payment_method }}

+
+ + + +
+
+
+
+
+

{{ component.front_section_lang[0].data.subtotal }}

+

€5,043.18

+
+
+

{{ component.front_section_lang[0].data.shipping_cost }}

+

€5,043.18

+
+
+

{{ component.front_section_lang[0].data.total }}

+

+ €5,043.18 +

+
+
+
+
+
+ + +

{{ $t("I accept ") }} {{ $t("the legal statement*") }}

+
+ + {{ $t("You need to accept this") }} + + +
+ + +

{{ $t("I accept ") }} {{ $t("general terms and conditions*") }}

+
+ + {{ $t("You need to accept this") }} + +
+ + + {{ $t("Buy") }} +
diff --git a/components/section/Product.vue b/components/section/Product.vue index 4c4845f..ecd2b79 100644 --- a/components/section/Product.vue +++ b/components/section/Product.vue @@ -64,4 +64,11 @@ const props = defineProps({ const productStore = useProductStore() const menuStore = useMenuStore() + +const isError = ref(false); + +function handleImageError(event: Event) { + isError.value = true; + (event.target as HTMLImageElement).src = '/photo.svg'; +} diff --git a/public/photo.svg b/public/photo.svg index c1b7162..03e867c 100644 --- a/public/photo.svg +++ b/public/photo.svg @@ -1,3 +1,3 @@ - + diff --git a/stores/checkoutStore.ts b/stores/checkoutStore.ts index 3d9b17d..f1485c4 100644 --- a/stores/checkoutStore.ts +++ b/stores/checkoutStore.ts @@ -1,14 +1,25 @@ -import { validation } from '../utils/validation' -import { REGEX_PHONE } from '../utils/regex' -import type { GenericResponse, GenericResponseItems, UserCart } from '~/types' -import type { AddressesList, UserAddressOfficial } from '~/types/checkout' -import type { CartProduct } from '~/types/product' +import type { GenericResponse, GenericResponseItems, UserCart } from "~/types"; +import type { + Address, + AddressesList, + CheckoutOrder, + Payment, + UserAddressOfficial, +} from "~/types/checkout"; +import { validation } from "../utils/validation"; +import { REGEX_PHONE } from "../utils/regex"; +import type { CartProduct } from "~/types/product"; -export const useCheckoutStore = defineStore('checkoutStore', () => { - const { $toast } = useNuxtApp() - const menuStore = useMenuStore() - const selectedIso = ref(menuStore.selectedCountry) - const showSummary = ref(false) +export const useCheckoutStore = defineStore("checkoutStore", () => { + const { $toast } = useNuxtApp(); + const menuStore = useMenuStore(); + const selectedIso = ref(menuStore.selectedCountry); + + const vLegal = ref(false); + const vTerms = ref(false); + const vNote = ref(""); + const legalValidation = ref(false); + const termsValidation = ref(false); // get address list const addressesList = ref() @@ -141,8 +152,8 @@ export const useCheckoutStore = defineStore('checkoutStore', () => { } const phoneValidation = ref(null) - const userStore = useUserStore() - // send form + // send checkout form + const userStore = useUserStore(); async function sendForm() { const phoneNum = vUseAccountPhoneNumber.value ? accountPhoneNumber.value @@ -189,12 +200,12 @@ export const useCheckoutStore = defineStore('checkoutStore', () => { $toast.success('Form successfully sent', { autoClose: 5000, dangerouslyHTMLString: true, - }) - // redirectToSummary(); - showSummary.value = true - } - else { - $toast.error('Failed to send form. Please try again.', { + }); + menuStore.navigateToItem( + menuStore.menuItems?.find((item) => item.id === 13) + ); + } else { + $toast.error("Failed to send form. Please try again.", { autoClose: 5000, dangerouslyHTMLString: true, }) @@ -209,6 +220,7 @@ export const useCheckoutStore = defineStore('checkoutStore', () => { activeAddress.value = item } + // get checkout async function getCheckout() { try { await useMyFetch>( @@ -269,19 +281,20 @@ export const useCheckoutStore = defineStore('checkoutStore', () => { const shippingPrice = ref() async function getDeliveryOptions() { try { - const { data } = await useMyFetch< - GenericResponseItems<{ - items: [ - { - country_iso: string - country_name: string - delivery_supplier_id: number - delivery_supplier_name: string - id: number - shippment_price: string - }, - ] - }> + const res = await useMyFetch< + GenericResponseItems + // { + // items: [ + // { + // country_iso: string; + // country_name: string; + // delivery_supplier_id: number; + // delivery_supplier_name: string; + // id: number; + // shippment_price: string; + // } + // ]; + // } >(`/api/restricted/cart/checkout/delivery-options`, { headers: { 'Content-Type': 'application/json', @@ -294,18 +307,44 @@ export const useCheckoutStore = defineStore('checkoutStore', () => { }, }) - console.log(data) - deliveryOption.value = data.items - currentDelivery.value = data.items[0] - shippingPrice.value = data.items[0].shippment_price - fullPrice.value = Number(fullPrice.value) + Number(shippingPrice.value) - } - catch (error) { - console.error('getUserCart error:', error) + const data = { + items: [ + { + id: 31, + shippment_price: "2", + delivery_supplier_id: 4, + delivery_supplier_name: "Personal collection", + country_iso: "pl", + country_name: "Poland", + }, + { + id: 34, + shippment_price: "20", + delivery_supplier_id: 1, + delivery_supplier_name: "Ceska Posta", + country_iso: "pl", + country_name: "Poland", + }, + ], + items_count: 2, + }; + + deliveryOption.value = data.items; + currentDelivery.value = data.items[0]; + shippingPrice.value = data.items[0].shippment_price; + fullPrice.value = Number(fullPrice.value) + Number(shippingPrice.value); + } catch (error) { + console.error("getUserCart error:", error); } } - const defaultAddress = ref() + const setCurrentDelivery = (item: any) => { + shippingPrice.value = item.shippment_price; + currentDelivery.value = item; + fullPrice.value = Number(fullPrice.value) + Number(shippingPrice.value); + }; + + const defaultAddress = ref(); async function getDefAddress() { try { const { data } = await useMyFetch< @@ -337,6 +376,137 @@ export const useCheckoutStore = defineStore('checkoutStore', () => { } } + // get bank data + const paymentMethods = ref([] as Payment[]); + const fullAddress = ref
(); + const currentPayment = ref(null); + async function getBankAccount() { + try { + const { data } = await useMyFetch>( + `/api/restricted/suitable-bank-accounts/${menuStore.selectedCurrency.iso_code}/${fullAddress.value?.country_iso}`, + { + headers: { + "Content-Type": "application/json", + }, + onErrorOccured: async (_, status) => { + throw createError({ + statusCode: status, + statusMessage: `HTTP error: ${status}`, + }); + }, + } + ); + + paymentMethods.value = data; + currentPayment.value = data[0]; + } catch (error) { + console.error("getUserCart error:", error); + } + } + + // get order (summary) + async function getOrder() { + try { + const { data } = await useMyFetch>( + `/api/restricted/cart/checkout/order`, + { + headers: { + "Content-Type": "application/json", + }, + onErrorOccured: async (_, status) => { + throw createError({ + statusCode: status, + statusMessage: `HTTP error: ${status}`, + }); + }, + } + ); + + fullAddress.value = data.delivery_details.address; + console.log(fullAddress.value); + } catch (error) { + console.error("getOrder error:", error); + } + } + + async function setNewAddress(index: number) { + currentPayment.value = paymentMethods.value.find( + (item, index) => index === index + ); + } + + // send summary form + async function sendSummaryForm() { + vLegal.value + ? (legalValidation.value = false) + : (legalValidation.value = true); + vTerms.value + ? (termsValidation.value = false) + : (termsValidation.value = true); + // if (vTerms.value && vLegal.value) { + // isModalOpen.value = true; + // } + + try { + const res = await useMyFetch>( + `/api/restricted/cart/checkout/delivery`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + accept_general_conditions: true, + accept_long_purchase: true, + address: fullAddress.value, + delivery_option_id: currentDelivery.value.id, + note: vNote.value, + }), + onErrorOccured: async (_, status) => { + throw createError({ + statusCode: status, + statusMessage: `HTTP error: ${status}`, + }); + }, + } + ); + + putCheckoutBankAccount(); + } catch (error) { + console.error("uploadAddress error:", error); + } + } + + // put checkout bank-account + async function putCheckoutBankAccount() { + try { + const res = await useMyFetch>( + `restricted/cart/checkout/bank-account/${currentPayment.value?.id}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + accept_general_conditions: true, + accept_long_purchase: true, + address: fullAddress.value, + delivery_option_id: currentDelivery.value.id, + note: vNote.value, + }), + onErrorOccured: async (_, status) => { + throw createError({ + statusCode: status, + statusMessage: `HTTP error: ${status}`, + }); + }, + } + ); + } catch (error) { + console.error("uploadAddress error:", error); + } + } + return { addressesList, activeAddress, @@ -361,7 +531,6 @@ export const useCheckoutStore = defineStore('checkoutStore', () => { vNewAddressCode, vNewAddressCity, vNewAddressCountry, - showSummary, products, fullPrice, @@ -370,6 +539,14 @@ export const useCheckoutStore = defineStore('checkoutStore', () => { currentDelivery, shippingPrice, defaultAddress, + paymentMethods, + currentPayment, + + vLegal, + vTerms, + vNote, + legalValidation, + termsValidation, changePrefix, getCheckout, @@ -381,5 +558,9 @@ export const useCheckoutStore = defineStore('checkoutStore', () => { getUserCart, getDeliveryOptions, getDefAddress, - } -}) + setCurrentDelivery, + getBankAccount, + getOrder, + setNewAddress, + }; +}); diff --git a/stores/menuStore.ts b/stores/menuStore.ts index 9e412f4..aef0538 100644 --- a/stores/menuStore.ts +++ b/stores/menuStore.ts @@ -1,4 +1,4 @@ -import { useStore } from './store' +import { useStore } from "./store"; import type { Country, Currency, @@ -7,78 +7,78 @@ import type { GenericResponseItems, Language, UIFrontMenu, -} from '~/types' -import { useMyFetch } from '#imports' +} from "~/types"; +import { useMyFetch } from "#imports"; function buildTreeRecursive( data: (FrontMenu | UIFrontMenu)[], - parentId: number, + parentId: number ): UIFrontMenu[] { const children = data.filter( (item): item is UIFrontMenu => - item.id_parent === parentId && !item.is_default, - ) + item.id_parent === parentId && !item.is_default + ); - return children.map(item => ({ + return children.map((item) => ({ ...item, children: buildTreeRecursive(data, item.id), - })) + })); } -export const useMenuStore = defineStore('menuStore', () => { - const store = useStore() - const { $i18n } = useNuxtApp() +export const useMenuStore = defineStore("menuStore", () => { + const store = useStore(); + const { $i18n } = useNuxtApp(); // const session = useSession(); - const { $session } = useNuxtApp() - const router = useRouter() - const route = useRoute() + const { $session } = useNuxtApp(); + const router = useRouter(); + const route = useRoute(); - const openMenu = ref(false) - const openDropDown = ref(false) + const openMenu = ref(false); + const openDropDown = ref(false); - const defaultMenu = ref() + const defaultMenu = ref(); - const menu = ref([] as UIFrontMenu[]) - const menuItems = ref([] as FrontMenu[]) + const menu = ref([] as UIFrontMenu[]); + const menuItems = ref([] as FrontMenu[]); // curr/country - const selectedCountry = ref({} as Country) - const selectedPhoneCountry = ref({} as Country) - const selectedCurrency = ref({} as Currency) - const selectedLanguage = ref({} as Language) + const selectedCountry = ref({} as Country); + const selectedPhoneCountry = ref({} as Country); + const selectedCurrency = ref({} as Currency); + const selectedLanguage = ref({} as Language); - const countries = ref([] as Country[]) - const currencies = ref([] as Currency[]) - const languages = ref([] as Language[]) + const countries = ref([] as Country[]); + const currencies = ref([] as Currency[]); + const languages = ref([] as Language[]); const getLocales = async () => { const { data: countriesList } = await useMyFetch< GenericResponse - >(`/api/public/country/list`) - countries.value = countriesList + >(`/api/public/country/list`); + countries.value = countriesList; selectedCountry.value = countriesList.find( - country => country.iso_code === $session.currentCountryIso.value, - ) as Country + (country) => country.iso_code === $session.currentCountryIso.value + ) as Country; selectedPhoneCountry.value = countriesList.find( - country => country.iso_code === $session.currentCountryIso.value, - ) as Country + (country) => country.iso_code === $session.currentCountryIso.value + ) as Country; const { data: currenciesList } = await useMyFetch< GenericResponseItems - >(`/api/public/currencies`) - currencies.value = currenciesList.items + >(`/api/public/currencies`); + currencies.value = currenciesList.items; selectedCurrency.value = currenciesList.items.find( - currency => currency.iso_code === $session.currentCurrencyIso.value, - ) as Currency + (currency) => currency.iso_code === $session.currentCurrencyIso.value + ) as Currency; const { data: languagesList } = await useMyFetch< GenericResponseItems - >(`/api/public/languages`) - languages.value = languagesList.items + >(`/api/public/languages`); + languages.value = languagesList.items; selectedLanguage.value = languagesList.items.find( - language => language.iso_code === $session.currentLanguageIso.value, - ) as Language - } + (language) => language.iso_code === $session.currentLanguageIso.value + ) as Language; + }; const loadMenu = async () => { try { @@ -86,80 +86,81 @@ export const useMenuStore = defineStore('menuStore', () => { `/api/public/front/menu`, { onErrorOccured: (err, status) => { - console.log(err, status) + console.log(err, status); }, - }, - ) + } + ); - menuItems.value = data + menuItems.value = data; - const root = data.find(item => item.is_root) as UIFrontMenu - defaultMenu.value = data.find(item => item.is_default) + const root = data.find((item) => item.is_root) as UIFrontMenu; + defaultMenu.value = data.find((item) => item.is_default); if (root) { - menu.value = buildTreeRecursive(data, root.id) - } - else { - console.warn('Root menu item not found') - menu.value = [] + menu.value = buildTreeRecursive(data, root.id); + } else { + console.warn("Root menu item not found"); + menu.value = []; } + } catch (error) { + console.log(error); } - catch (error) { - console.log(error) - } - } + }; const navigateToItem = (item?: UIFrontMenu) => { if (item) { router.push({ params: { slug: item.front_menu_lang[0].link_rewrite, id: item.id }, name: `id-slug___${$i18n.locale.value}`, - }) - openDropDown.value = false - } - else { + }); + openDropDown.value = false; + } else { router.push({ params: { slug: defaultMenu.value.front_menu_lang[0].link_rewrite, id: defaultMenu.value.id, }, name: `id-slug___${$i18n.locale.value}`, - }) + }); } - } + }; function navigateToShop() { - navigateToItem(menuItems.value?.find(item => item.id === 5)) + navigateToItem(menuItems.value?.find((item) => item.id === 5)); } function getProductMenu() { - return menuItems.value?.find(item => item.id === 13) + return menuItems.value?.find((item) => item.id === 13); } - const getFirstImage = (size: 'l' | 'm' | 's' = 'm', needbaseurl: boolean) => { - const req = useRequestEvent() - const url = useRequestURL() + const getFirstImage = (size: "l" | "m" | "s" = "m", needbaseurl: boolean) => { + const req = useRequestEvent(); + const url = useRequestURL(); // let img = ""; - const img: string[] = [] + const img: string[] = []; for (const s in store.components) { - if (store.components[s].front_section.img.length === 0) continue + if (store.components[s].front_section.img.length === 0) continue; img.push( - `/api/public/file/${store.components[s].front_section.img[0]}_${size}.webp`, - ) - if (img.length > 0) break + `/api/public/file/${store.components[s].front_section.img[0]}_${size}.webp` + ); + if (img.length > 0) break; } if (img.length > 0) { if (needbaseurl) { - return `${req?.headers.get('x-forwarded-proto') || url.protocol}://${req?.headers.get('x-forwarded-host') || url.host || req?.headers.get('host')}${img[0]}` + return `${req?.headers.get("x-forwarded-proto") || url.protocol}://${ + req?.headers.get("x-forwarded-host") || + url.host || + req?.headers.get("host") + }${img[0]}`; } - return img[0] + return img[0]; } - return '' - } + return ""; + }; const headMeta = computed(() => { const item = menuItems.value?.find( - item => item.id.toString() === route.params.id, - ) + (item) => item.id.toString() === route.params.id + ); const meta = { title: item?.front_menu_lang[0].meta_title, @@ -168,66 +169,77 @@ export const useMenuStore = defineStore('menuStore', () => { }, link: [ // { rel: "manifest", href: "/api/manifest.json" } - { rel: 'icon', type: 'image/x-icon', href: '/favicon.png' }, + { rel: "icon", type: "image/x-icon", href: "/favicon.png" }, ], script: [ { - src: 'https://leiadmin.com/leitag.js?lei=894500UT83EISNNA8D04&color=dark', + src: "https://leiadmin.com/leitag.js?lei=894500UT83EISNNA8D04&color=dark", defer: true, }, ], meta: [ { - hid: 'description', - name: 'description', + hid: "description", + name: "description", content: item?.front_menu_lang[0].meta_description, }, { - property: 'og:title', + property: "og:title", content: item?.front_menu_lang[0].meta_title, }, { - property: 'og:description', + property: "og:description", content: item?.front_menu_lang[0].meta_description, }, { - property: 'og:image', - content: getFirstImage('m', true), + property: "og:image", + content: getFirstImage("m", true), }, { - property: 'twitter:title', + property: "twitter:title", content: item?.front_menu_lang[0].meta_title, }, { - property: 'twitter:description', + property: "twitter:description", content: item?.front_menu_lang[0].meta_description, }, { - property: 'twitter:image', - content: getFirstImage('m', true), + property: "twitter:image", + content: getFirstImage("m", true), }, ], - } + }; - const preload = getFirstImage('l', false) + const preload = getFirstImage("l", false); if (preload) { - meta.link.push({ rel: 'preload', as: 'image', href: preload } as never) + meta.link.push({ rel: "preload", as: "image", href: preload } as never); } - return meta - }) + return meta; + }); + + const formatPrice = (value: number): string => { + return value.toLocaleString(selectedLanguage.value.iso_code, { + minimumFractionDigits: selectedCurrency.value.precision, + maximumFractionDigits: selectedCurrency.value.precision, + currency: selectedCurrency.value.iso_code, + style: "currency", + currencyDisplay: "symbol", + currencySign: "accounting", + }); + }; // watches watch( () => $session.cookieData, async () => { - await getLocales() - await loadMenu() - await store.getMinValue() - await store.getCalculator() + await getLocales(); + await loadMenu(); + await store.getMinValue(); + await store.getCalculator(); }, - { deep: true }, - ) + { deep: true } + ); return { menu, @@ -243,10 +255,11 @@ export const useMenuStore = defineStore('menuStore', () => { selectedLanguage, defaultMenu, headMeta, + navigateToShop, loadMenu, navigateToItem, getLocales, getProductMenu, - } -}) + }; +}); diff --git a/types/checkout.ts b/types/checkout.ts index 1ede39f..628d753 100644 --- a/types/checkout.ts +++ b/types/checkout.ts @@ -30,3 +30,42 @@ export interface UserAddressOfficial { } } } + +export interface Payment { + bank_name: string; + city: string; + country_account_number: string; + country_iso: string; + country_name: string; + currency_iso: string; + iban: string; + id: number; + postcode: string; + street_and_number: string; + swift: string; +} + +export interface CheckoutOrder { + cart_id: number; + currency_iso: string; + customer_id: number; + delivery_details: { + address: Address; + contact_email: string; + contact_phone_number: string; + delivery_option_id: number; + note: string; + }; + payment_bank_account_id: number; +} + +export interface Address { + city: string; + country_iso: { + str: string; + }; + name: string; + postcode: string; + street: string; + surname: string; +}