diff --git a/app.vue b/app.vue index ff379be..1c8d689 100644 --- a/app.vue +++ b/app.vue @@ -1,7 +1,5 @@ - + diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..497f805 Binary files /dev/null and b/bun.lockb differ diff --git a/components/CartPopup.vue b/components/CartPopup.vue index 70302f6..5d9e217 100644 --- a/components/CartPopup.vue +++ b/components/CartPopup.vue @@ -7,7 +7,7 @@ {{ productStore.cart.cart_items.length }}
+ class="absolute left-1/2 transform -translate-x-1/2 w-full px-4 sm:max-w-[768px] sm:px-[17px] md:max-w-[1000px] md:px-6 xl:max-w-[1920px] xl:px-20 right-0 z-50 flex items-center justify-end top-[100px] sm:top-[100px] md:top-[140px]">
@@ -36,7 +36,7 @@ class="text-accent-green-light dark:text-accent-green-dark font-inter text-[12px] sm:text-[21px] md:text-2xl leading-[150%] font-bold"> {{ item.total_price }}

-
+
- {{ $t('to_checkout') }} + {{ + $t('to_checkout') }}
+ class="w-full p-[25px] sm:p-[50px] bg-bg-light dark:bg-bg-dark border border-button rounded-xl sm:rounded-[32px] flex items-center justify-center">
-

- košík je prázdný

+ class="border border-block inline-flex items-center justify-center h-[140px] sm:h-[200px] text-center rounded-[8px]"> +

+ {{ $t('empty_cart') }}

@@ -83,10 +86,11 @@ \ No newline at end of file diff --git a/components/section/CheckoutMain.vue b/components/section/CheckoutMain.vue index 3e79761..57f270c 100644 --- a/components/section/CheckoutMain.vue +++ b/components/section/CheckoutMain.vue @@ -1,7 +1,196 @@ - \ No newline at end of file + diff --git a/components/section/LoginMain.vue b/components/section/LoginMain.vue index f1d7f60..1399901 100644 --- a/components/section/LoginMain.vue +++ b/components/section/LoginMain.vue @@ -1,7 +1,7 @@ \ No newline at end of file diff --git a/components/section/Product.vue b/components/section/Product.vue index fdf858a..7b2f3b1 100644 --- a/components/section/Product.vue +++ b/components/section/Product.vue @@ -2,7 +2,7 @@
Product Image
diff --git a/components/section/ShopMain.vue b/components/section/ShopMain.vue index e767c9c..5a3d0fb 100644 --- a/components/section/ShopMain.vue +++ b/components/section/ShopMain.vue @@ -1,91 +1,135 @@ \ No newline at end of file + diff --git a/components/ui/CheckoutInput.vue b/components/ui/CheckoutInput.vue new file mode 100644 index 0000000..6a745fa --- /dev/null +++ b/components/ui/CheckoutInput.vue @@ -0,0 +1,57 @@ + + + \ No newline at end of file diff --git a/components/ui/FaceObserver.vue b/components/ui/FaceObserver.vue new file mode 100644 index 0000000..4e4a4a0 --- /dev/null +++ b/components/ui/FaceObserver.vue @@ -0,0 +1,15 @@ + + + \ No newline at end of file diff --git a/components/ui/ImgWrapper.vue b/components/ui/ImgWrapper.vue index 87b2743..af1d284 100644 --- a/components/ui/ImgWrapper.vue +++ b/components/ui/ImgWrapper.vue @@ -11,15 +11,15 @@ - - + +
- +
diff --git a/pages/[id]/[slug].vue b/pages/[id]/[slug].vue index 13f2016..51bac5f 100644 --- a/pages/[id]/[slug].vue +++ b/pages/[id]/[slug].vue @@ -13,14 +13,10 @@ gsap.registerPlugin(ScrollTrigger) watch(useColorMode(), (color) => { console.log(color); - + }) onMounted(() => { - - - - const anim = gsap.fromTo( 'h1', { @@ -45,12 +41,12 @@ onMounted(() => { const animh2 = gsap.fromTo( 'h2', { - opacity: 0, - color: 'var(--color-accent-green-light)', + // opacity: 0, + // color: 'var(--color-accent-green-light)', }, { - opacity: 1, - duration: 1, + // opacity: 1, + // duration: 1, ease: 'power2.out', } ) diff --git a/stores/checkoutStore.ts b/stores/checkoutStore.ts new file mode 100644 index 0000000..986ec8e --- /dev/null +++ b/stores/checkoutStore.ts @@ -0,0 +1,255 @@ +import type { GenericResponse } from "~/types"; +import type { AddressesList } from "~/types/checkout"; +import { validation } from "../utils/validation"; +import { REGEX_PHONE } from "../utils/regex"; + +export const useCheckoutStore = defineStore("checkoutStore", () => { + const { $toast } = useNuxtApp(); + + const addressesList = ref(); + const activeAddress = ref(); + async function restrictedAddress() { + try { + // const { data } = await useMyFetch>( + // `/api/restricted/user/addresses`, + // { + // headers: { + // "Content-Type": "application/json", + // }, + // onErrorOccured: async (_, status) => { + // throw createError({ + // statusCode: status, + // statusMessage: `HTTP error: ${status}`, + // }); + // }, + // } + // ); + + const data = [ + { + address: { + city: "Bochnia", + country_iso: "pl", + name: "John", + postcode: "32-700", + street: "Karosek", + surname: "Kornelsky", + }, + address_id: 2, + alias: "home", + customer_id: 4, + is_default: true, + is_official: false, + }, + ]; + + addressesList.value = data; + activeAddress.value = addressesList.value[0]; + } catch (error) { + console.error("restrictedAddress error:", error); + } + } + + const userName = ref(""); + const lastName = ref(""); + const address = ref(""); + const postCode = ref(""); + const city = ref(""); + const country = ref(""); + const phoneNumber = ref(""); + const accountPhoneNumber = ref(""); + async function restrictedAddressOfficial() { + try { + // const { data } = await useMyFetch>( + // `/api/restricted/user/address/official`, + // { + // headers: { + // "Content-Type": "application/json", + // }, + // onErrorOccured: async (_, status) => { + // throw createError({ + // statusCode: status, + // statusMessage: `HTTP error: ${status}`, + // }); + // }, + // } + // ); + + const data = { + address: { + city: "Bochnia", + country_iso: "pl", + name: "John", + postcode: "32-700", + street: "Karosek", + surname: "Kornelsky", + }, + address_id: 2, + alias: "home", + customer_id: 4, + is_default: true, + is_official: false, + }; + + userName.value = data.address.name; + lastName.value = data.address.surname; + address.value = data.address.street; + postCode.value = data.address.postcode; + city.value = data.address.city; + country.value = data.address.country_iso; + // resolve this + // accountPhoneNumber.value = useUserStore().fullUserData.phone_number; + phoneNumber.value = "+36 789 3773 737"; + } catch (error) { + console.error("restrictedAddressOfficial error:", error); + } + } + + const vNewAddressName = ref(""); + const vNewAddressSurname = ref(""); + const vNewAddressAddress = ref(""); + const vNewAddressCode = ref(""); + const vNewAddressCity = ref(""); + const vNewAddressCountry = ref(""); + const vUseAccountPhoneNumber = ref(false); + const isOpen = ref(false); + async function uploadAddress() { + try { + const res = await useMyFetch>( + `/api/restricted/user/address/official`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + address: { + city: vNewAddressCity.value, + // country_iso: vNewAddressCountry.value?.iso_code, + name: vNewAddressName.value, + postcode: vNewAddressCode.value, + street: vNewAddressAddress.value, + surname: vNewAddressSurname.value, + }, + }), + onErrorOccured: async (_, status) => { + throw createError({ + statusCode: status, + statusMessage: `HTTP error: ${status}`, + }); + }, + } + ); + + if (res.status === 200) { + $toast.success("Address successfully added", { + autoClose: 5000, + dangerouslyHTMLString: true, + }); + isOpen.value = false; + restrictedAddress(); + } else { + $toast.error("Failed to add address. Please try again.", { + autoClose: 5000, + dangerouslyHTMLString: true, + }); + } + } catch (error) { + console.error("uploadAddress error:", error); + } + } + + const currentPrefix = ref("+43"); + const changePrefix = (item: any) => { + currentPrefix.value = item; + }; + const phoneValidation = ref(null); + async function sendForm() { + let phoneNum = `${currentPrefix.value}${phoneNumber.value}` + .replaceAll(" ", "") + .trim(); + // if (vUseAccountPhoneNumber.value) { + // phoneNum = phoneNumber.value; + // } + + phoneValidation.value = validation(phoneNum, 1, 49, REGEX_PHONE); + try { + const res = await useMyFetch>( + `restricted/cart/checkout/delivery`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + address: { + city: activeAddress.value?.address.city, + country_iso: activeAddress.value?.address.country_iso, + name: activeAddress.value?.address.name, + postcode: activeAddress.value?.address.postcode, + street: activeAddress.value?.address.street, + surname: activeAddress.value?.address.surname, + }, + phone_number: phoneNum, + // email: useUserStore().fullUserData.email, + }), + onErrorOccured: async (_, status) => { + throw createError({ + statusCode: status, + statusMessage: `HTTP error: ${status}`, + }); + }, + } + ); + + if (res.status === 200) { + $toast.success("Form successfully sent", { + autoClose: 5000, + dangerouslyHTMLString: true, + }); + // redirectToSummary(); + } else { + $toast.error("Failed to send form. Please try again.", { + autoClose: 5000, + dangerouslyHTMLString: true, + }); + } + } catch (error) { + console.error("uploadAddress error:", error); + } + } + + const changeActive = (item: any) => { + activeAddress.value = item; + }; + + return { + addressesList, + activeAddress, + isOpen, + + userName, + lastName, + address, + postCode, + city, + country, + phoneNumber, + accountPhoneNumber, + vUseAccountPhoneNumber, + + currentPrefix, + vNewAddressName, + vNewAddressSurname, + vNewAddressAddress, + vNewAddressCode, + vNewAddressCity, + vNewAddressCountry, + + restrictedAddress, + restrictedAddressOfficial, + changeActive, + uploadAddress, + sendForm, + }; +}); diff --git a/stores/menuStore.ts b/stores/menuStore.ts index 8dca1e9..8f6dd8d 100644 --- a/stores/menuStore.ts +++ b/stores/menuStore.ts @@ -56,7 +56,6 @@ export const useMenuStore = defineStore("menuStore", () => { const currencies = ref([] as Currency[]); const languages = ref([] as Language[]); - const getLocales = async () => { const { data: countriesList } = await useMyFetch>(`/api/public/country/list`); countries.value = countriesList; @@ -96,7 +95,6 @@ export const useMenuStore = defineStore("menuStore", () => { console.warn("Root menu item not found"); menu.value = []; } - } catch (error) { console.log(error); } diff --git a/stores/store.ts b/stores/store.ts index 9690dd3..e5283fe 100644 --- a/stores/store.ts +++ b/stores/store.ts @@ -8,7 +8,7 @@ import type { FrontPageSection } from "~/types/frontSection"; export const useStore = defineStore("store", () => { const currentPageID = ref(""); - + const { $toast } = useNuxtApp(); // calculator const monthlySavings = ref(137); @@ -16,19 +16,13 @@ export const useStore = defineStore("store", () => { const totalInvestment: Ref = ref(0); const minValue = ref(); - // login - const email = ref(); - const password = ref(); - const components = ref({} as FrontPageSection[]); - const getSections = async (id: string) => { const { data } = await useMyFetch>( `/api/public/front/sections/${id}` - ) - components.value = data - + ); + components.value = data; }; async function getComponents(): Promise { @@ -56,7 +50,6 @@ export const useStore = defineStore("store", () => { name: componentName, component: child.front_section, componentInstance: nonReactiveComponent, - }); } catch (error) { console.error(`Failed to load component ${componentName}`, error); @@ -103,39 +96,12 @@ export const useStore = defineStore("store", () => { } ); - minValue.value = data; } catch (error) { console.error("getList error:", error); } } - async function logIn() { - try { - const { data } = await useMyFetch>( - `/api/public/user/session/start`, - { - method: "POST", - body: JSON.stringify({ - mail: email.value, - password: password.value, - }), - headers: { - "Content-Type": "application/json", - }, - onErrorOccured: (_, status) => { - throw new Error(`HTTP error: ${status}`); - }, - } - ); - - minValue.value = data; - } catch (error) { - console.error("getList error:", error); - } - } - - return { currentPageID, components, @@ -143,9 +109,6 @@ export const useStore = defineStore("store", () => { monthlySavings, storagePeriod, minValue, - email, - password, - logIn, getCalculator, getComponents, getSections, diff --git a/stores/userStore.ts b/stores/userStore.ts new file mode 100644 index 0000000..f96f041 --- /dev/null +++ b/stores/userStore.ts @@ -0,0 +1,105 @@ +import type { GenericResponse } from "~/types"; +import type { Customer } from "~/types/user"; + +export const useUserStore = defineStore("userStore", () => { + const store = useStore(); + + const fullUserData = ref(null); + const isLogged = ref(true); + const user = ref(null); + + async function checkIsLogged() { + try { + const { data } = await useMyFetch< + GenericResponse<{ loggedin: boolean } | Customer> + >(`/api/public/user`, { + headers: { + "Content-Type": "application/json", + }, + onErrorOccured: async (_, status) => { + throw createError({ + statusCode: status, + statusMessage: `HTTP error: ${status}`, + }); + }, + }); + + if ("loggedin" in data && data.loggedin === true) { + isLogged.value = true; + user.value = null; + fullUserData.value = null; + } else if ("first_name" in data && "last_name" in data) { + isLogged.value = true; + user.value = `${data.first_name} ${data.last_name}`; + fullUserData.value = data as Customer; + } else { + isLogged.value = false; + user.value = null; + fullUserData.value = null; + } + } catch (error) { + console.error("checkIsLogged error:", error); + } + } + + // login + const email = ref(); + const password = ref(); + const vLogin = ref(true); + const vCodeVerify = ref(false); + const vCode = ref(null); + const vEmail = ref(""); + async function logIn() { + try { + const data = await useMyFetch>( + `/api/public/user/session/start`, + { + method: "POST", + body: JSON.stringify({ + mail: email.value, + password: password.value, + }), + headers: { + "Content-Type": "application/json", + }, + onErrorOccured: (_, status) => { + throw new Error(`HTTP error: ${status}`); + }, + } + ); + + if (data.status === 200 || data.status === 201) { + console.log(vCodeVerify.value); + + // $toast.success("Address successfully added", { + // autoClose: 5000, + // dangerouslyHTMLString: true, + // }); + vLogin.value = false; + vCodeVerify.value = true; + } + // else { + // $toast.error("Failed to add address. Please try again.", { + // autoClose: 5000, + // dangerouslyHTMLString: true, + // }); + // } + + store.minValue = data; + } catch (error) { + console.error("getList error:", error); + } + } + return { + isLogged, + user, + fullUserData, + vCodeVerify, + vCode, + vEmail, + email, + password, + logIn, + checkIsLogged, + }; +}); diff --git a/types/checkout.ts b/types/checkout.ts new file mode 100644 index 0000000..1ecbce8 --- /dev/null +++ b/types/checkout.ts @@ -0,0 +1,15 @@ +export interface AddressesList { + address: { + city: string; + country_iso: string; + name: string; + postcode: string; + street: string; + surname: string; + }; + address_id: number; + alias: string; + customer_id: number; + is_default: boolean; + is_official: boolean; +} diff --git a/types/user.ts b/types/user.ts new file mode 100644 index 0000000..1fe45b2 --- /dev/null +++ b/types/user.ts @@ -0,0 +1,49 @@ +export interface Customer { + active: boolean; + agreed_for_newsletter: boolean; + bank_accounts: { + bank_currency_iso: string; + bank_name: string; + customer_id: number; + iban: string; + swift: string; + verified: boolean; + }[]; + birthday_date: string; + communication_languag_id: number; + document_verified: boolean; + documents: { + file: string; + id: number; + name: string; + size: number; + typ: string; + }[]; + email: string; + entity: { + city: string; + country_iso: string; + customer_id: number; + extra_enitity_id: string; + name: string; + national_court_register_number: string; + postcode: string; + statistical_number: string; + street: string; + vat_number: string; + web_pages_list: string; + }[]; + first_name: string; + is_entity: boolean; + is_partner: boolean; + is_root: boolean; + last_name: string; + metadata: { + id: number; + metadata: string; + type: string; + }[]; + partner_code: string; + phone_number: string; + taxes_country_iso: string; +} diff --git a/utils/regex.js b/utils/regex.js new file mode 100644 index 0000000..d17cc34 --- /dev/null +++ b/utils/regex.js @@ -0,0 +1,34 @@ +const REGEX_EMAIL = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/i, + // Minimum eight characters, at least one uppercase letter, one lowercase letter, one number and one special character + // REGEX_PASSWORD = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/, + // REGEX_PASSWORD = /^(?=.*[A-Za-z])(?=.*\d)(?=.*\W)[A-Za-z\d^\S]{8,}$/, + REGEX_PASSWORD = new RegExp(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*\W)[A-Za-z\d^\S]{8,}$/), + REGEX_CODE = /.{6}/, + // REGEX_PHONE = /^\+?[1-9][0-9]{7,14}$/, + REGEX_PHONE = new RegExp(/^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/im), + REGEX_DATE = /^[+-]?\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/, + + // Only numbers + REGEX_ONLYNUMBERS = /^[0-9]*$/, + + // Number (price) + REGEX_NUMBER = /^(?!0*[.,]0*$|[.,]0*$|0*$)\d+[,.]?\d{0,2}$/, + REGEX_NUMBER_WITH_ZERO = /^[0-9]{1,10}([.][0-9]{1,2})?$/, + REGEX_URL = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)$/ + + // URL + + // REGEX_URL = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)$/ + +export { + REGEX_EMAIL, + REGEX_PASSWORD, + REGEX_CODE, + REGEX_PHONE, + REGEX_ONLYNUMBERS, + REGEX_NUMBER, + REGEX_NUMBER_WITH_ZERO, + REGEX_DATE, + REGEX_URL, +} + diff --git a/utils/validation.ts b/utils/validation.ts new file mode 100644 index 0000000..de1e51e --- /dev/null +++ b/utils/validation.ts @@ -0,0 +1,12 @@ +const validation = function (item:string, min:number, max:number, regEx = /.*/) { + if ( + item == undefined || + item.length < min || + item.length > max || + !regEx.test(item) + ) return false; + else return true; + }; + + + export { validation }; \ No newline at end of file