login/checkout

This commit is contained in:
2025-06-30 15:27:46 +02:00
parent 012058b998
commit fd4b122936
11 changed files with 336 additions and 212 deletions

View File

@ -10,5 +10,5 @@
<script setup lang="ts"> <script setup lang="ts">
const userStore = useUserStore() const userStore = useUserStore()
// await userStore.checkIsLogged() await userStore.checkIsLogged()
</script> </script>

View File

@ -1,3 +1,4 @@
/* ===== Base toast style (applies to all toasts) ===== */
.Toastify__toast { .Toastify__toast {
font-family: var(--font-inter); font-family: var(--font-inter);
background-color: var(--color-bg-light); background-color: var(--color-bg-light);
@ -7,29 +8,44 @@
box-shadow: none; box-shadow: none;
} }
/* ===== Base toast style for dark mode ===== */
.dark .Toastify__toast { .dark .Toastify__toast {
background-color: var(--color-bg-dark); background-color: var(--color-bg-dark);
border: 1px solid var(--color-block); border: 1px solid var(--color-block);
color: #ffffff; color: #ffffff;
} }
/* ===== Error toast: red background and white text ===== */
.Toastify__toast--error { .Toastify__toast--error {
background-color: #dc3545; background-color: var(--color-bg-light);
color: #fff;
} }
/* ===== Error toast in dark mode: darker red background ===== */
.dark .Toastify__toast--error { .dark .Toastify__toast--error {
background-color: #c82333; background-color: var(--color-bg-dark);
} }
/* ===== Default progress bar color (used for success and others) ===== */
.Toastify__progress-bar { .Toastify__progress-bar {
background-color: var(--color-accent-green-dark); background-color: var(--color-accent-green-dark);
} }
/* ===== Error toast: custom progress bar color ===== */
.Toastify__toast--error .Toastify__progress-bar {
background-color: #dc3545;
}
/* ===== Success toast: icon color ===== */
.Toastify__toast--success .Toastify__toast-icon svg { .Toastify__toast--success .Toastify__toast-icon svg {
fill: var(--color-accent-green-dark); fill: var(--color-accent-green-dark);
} }
/* ===== Error toast: icon color ===== */
.Toastify__toast--error .Toastify__toast-icon svg {
fill: #dc3545;
}
/* ===== Close button color in dark mode ===== */
.dark .Toastify__close-button { .dark .Toastify__close-button {
color: #fff; color: #fff;
} }

View File

@ -65,9 +65,11 @@
{{ productStore.cart.total_value }} {{ productStore.cart.total_value }}
</p> </p>
</div> </div>
<UiButtonArrow @click="menuStore.navigateToItem(menuStore.menuItems?.find((item) => item.id === 12))" <UiButtonArrow @click="() => {
class="w-full" type="fill" :arrow="true" :full="true">{{ menuStore.navigateToItem(menuStore.menuItems?.find((item) => item.id === 12))
$t('to_checkout') }} openCart = false
}" class="w-full" type="fill" :arrow="true" :full="true">{{
$t('to_checkout') }}
</UiButtonArrow> </UiButtonArrow>
</div> </div>
<div v-else <div v-else

View File

@ -12,7 +12,7 @@
<div class="flex flex-col items-start gap-1"> <div class="flex flex-col items-start gap-1">
<p>{{ $t("country") }}</p> <p>{{ $t("country") }}</p>
<div <div
class="bg-inherit w-full ring-0 cursor-pointer focus:ring-0 outline-none focus-visible:ring-0 space-y-1"> class="w-full ring-0 cursor-pointer focus:ring-0 outline-none focus-visible:ring-0 space-y-1">
<div class="p-0" @click="openDrop('country')"> <div class="p-0" @click="openDrop('country')">
<div class="flex items-center gap-2 text-xl font-medium uppercase text-text-light dark:text-text-dark"> <div class="flex items-center gap-2 text-xl font-medium uppercase text-text-light dark:text-text-dark">
{{ $session.currentCountryIso }} <span> <i {{ $session.currentCountryIso }} <span> <i
@ -21,7 +21,7 @@
</div> </div>
<div v-if="dropCountry" <div v-if="dropCountry"
class="rounded-[5px] data-highlighted:not-data-disabled:before:bg-button/50 bg-inherit ring-0 cursor-pointer w-full focus:ring-0 outline-none focus-visible:ring-0 border border-button py-[10px] px-[5px]"> class="bg-bg-light dark:bg-bg-dark rounded-[5px] data-highlighted:not-data-disabled:before:bg-button/50 ring-0 cursor-pointer w-full focus:ring-0 outline-none focus-visible:ring-0 border border-button py-[10px] px-[5px]">
<div class="overflow-auto h-[200px] w-full"> <div class="overflow-auto h-[200px] w-full">
<p @click="() => { $session.setCountry(item.iso_code); $session.loadSession(); dropCountry = false }" <p @click="() => { $session.setCountry(item.iso_code); $session.loadSession(); dropCountry = false }"
class="w-full hover:bg-block dark:hover:bg-button pl-2 py-2 text-base text-text-light dark:text-text-dark rounded-[5px]" class="w-full hover:bg-block dark:hover:bg-button pl-2 py-2 text-base text-text-light dark:text-text-dark rounded-[5px]"
@ -35,7 +35,7 @@
<div class="flex flex-col items-start gap-[6px]"> <div class="flex flex-col items-start gap-[6px]">
<p>{{ $t("currency") }}</p> <p>{{ $t("currency") }}</p>
<div <div
class="bg-inherit w-full ring-0 cursor-pointer focus:ring-0 outline-none focus-visible:ring-0 space-y-1"> class="w-full ring-0 cursor-pointer focus:ring-0 outline-none focus-visible:ring-0 space-y-1">
<div class="p-0" @click="openDrop()"> <div class="p-0" @click="openDrop()">
<div class="flex items-center gap-2 text-xl font-medium uppercase text-text-light dark:text-text-dark"> <div class="flex items-center gap-2 text-xl font-medium uppercase text-text-light dark:text-text-dark">
{{ $session.currentCurrencyIso }}<span> <i {{ $session.currentCurrencyIso }}<span> <i
@ -44,7 +44,7 @@
</div> </div>
<div v-if="dropCurrency" <div v-if="dropCurrency"
class="rounded-[5px] data-highlighted:not-data-disabled:before:bg-button/50 bg-inherit ring-0 cursor-pointer w-full focus:ring-0 outline-none focus-visible:ring-0 border border-button py-[10px] px-[5px]"> class="bg-bg-light dark:bg-bg-dark rounded-[5px] data-highlighted:not-data-disabled:before:bg-button/50 ring-0 cursor-pointer w-full focus:ring-0 outline-none focus-visible:ring-0 border border-button py-[10px] px-[5px]">
<div class="overflow-auto w-full"> <div class="overflow-auto w-full">
<p @click="() => { $session.setCurrency(item.iso_code); $session.loadSession(); dropCurrency = false }" <p @click="() => { $session.setCurrency(item.iso_code); $session.loadSession(); dropCurrency = false }"
class="w-full hover:bg-block dark:hover:bg-button pl-1 py-2 text-base text-text-light dark:text-text-dark rounded-[5px]" class="w-full hover:bg-block dark:hover:bg-button pl-1 py-2 text-base text-text-light dark:text-text-dark rounded-[5px]"

View File

@ -18,8 +18,15 @@
</ClientOnly> </ClientOnly>
<div class="w-full flex items-center justify-between"> <div class="w-full flex items-center justify-between">
<div class="flex items-center gap-[30px]"> <div class="flex items-center gap-[30px]">
<i @click="menuStore.navigateToItem(menuStore.menuItems?.find((item) => item.id === 11))" <div>
class="uil uil-user text-[31px] cursor-pointer"></i> <i v-if="!userStore.isLogged"
@click="menuStore.navigateToItem(menuStore.menuItems?.find((item) => item.id === 11))"
class="uil uil-user text-[31px] cursor-pointer"></i>
<div v-else class="py-[6px] px-3 border border-block rounded-sm">
<!-- {{ userStore.user }} -->
Arina Yakovenko
</div>
</div>
<CartPopup /> <CartPopup />
</div> </div>
<div class="flex"> <div class="flex">
@ -74,7 +81,7 @@
{{ item.front_menu_lang[0].name }} {{ item.front_menu_lang[0].name }}
</div> </div>
<!-- <i class="uil uil-arrow-up-right text-[35px]"></i> --> <!-- <i class="uil uil-arrow-up-right text-[35px]"></i> -->
<svg class="" width="20" height="20" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="20" height="20" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path <path
d="M25.1274 1.87258C25.1274 1.3203 24.6797 0.872582 24.1274 0.872584L15.1274 0.872583C14.5751 0.872583 14.1274 1.3203 14.1274 1.87258C14.1274 2.42487 14.5751 2.87258 15.1274 2.87258L23.1274 2.87258L23.1274 10.8726C23.1274 11.4249 23.5751 11.8726 24.1274 11.8726C24.6797 11.8726 25.1274 11.4249 25.1274 10.8726L25.1274 1.87258ZM1.5 24.5L2.20711 25.2071L24.8345 2.57969L24.1274 1.87258L23.4203 1.16548L0.792893 23.7929L1.5 24.5Z" d="M25.1274 1.87258C25.1274 1.3203 24.6797 0.872582 24.1274 0.872584L15.1274 0.872583C14.5751 0.872583 14.1274 1.3203 14.1274 1.87258C14.1274 2.42487 14.5751 2.87258 15.1274 2.87258L23.1274 2.87258L23.1274 10.8726C23.1274 11.4249 23.5751 11.8726 24.1274 11.8726C24.6797 11.8726 25.1274 11.4249 25.1274 10.8726L25.1274 1.87258ZM1.5 24.5L2.20711 25.2071L24.8345 2.57969L24.1274 1.87258L23.4203 1.16548L0.792893 23.7929L1.5 24.5Z"
fill="currentColor" /> fill="currentColor" />
@ -119,7 +126,7 @@
{{ item.front_menu_lang[0].name }} {{ item.front_menu_lang[0].name }}
</div> </div>
<!-- <i class="uil uil-arrow-up-right text-[35px]"></i> --> <!-- <i class="uil uil-arrow-up-right text-[35px]"></i> -->
<svg class="" width="20" height="20" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="20" height="20" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path <path
d="M25.1274 1.87258C25.1274 1.3203 24.6797 0.872582 24.1274 0.872584L15.1274 0.872583C14.5751 0.872583 14.1274 1.3203 14.1274 1.87258C14.1274 2.42487 14.5751 2.87258 15.1274 2.87258L23.1274 2.87258L23.1274 10.8726C23.1274 11.4249 23.5751 11.8726 24.1274 11.8726C24.6797 11.8726 25.1274 11.4249 25.1274 10.8726L25.1274 1.87258ZM1.5 24.5L2.20711 25.2071L24.8345 2.57969L24.1274 1.87258L23.4203 1.16548L0.792893 23.7929L1.5 24.5Z" d="M25.1274 1.87258C25.1274 1.3203 24.6797 0.872582 24.1274 0.872584L15.1274 0.872583C14.5751 0.872583 14.1274 1.3203 14.1274 1.87258C14.1274 2.42487 14.5751 2.87258 15.1274 2.87258L23.1274 2.87258L23.1274 10.8726C23.1274 11.4249 23.5751 11.8726 24.1274 11.8726C24.6797 11.8726 25.1274 11.4249 25.1274 10.8726L25.1274 1.87258ZM1.5 24.5L2.20711 25.2071L24.8345 2.57969L24.1274 1.87258L23.4203 1.16548L0.792893 23.7929L1.5 24.5Z"
fill="currentColor" /> fill="currentColor" />
@ -179,7 +186,7 @@
{{ item.front_menu_lang[0].name }} {{ item.front_menu_lang[0].name }}
</div> </div>
<!-- <i class="uil uil-arrow-up-right text-[35px]"></i> --> <!-- <i class="uil uil-arrow-up-right text-[35px]"></i> -->
<svg class="" width="20" height="20" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="20" height="20" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<path <path
d="M25.1274 1.87258C25.1274 1.3203 24.6797 0.872582 24.1274 0.872584L15.1274 0.872583C14.5751 0.872583 14.1274 1.3203 14.1274 1.87258C14.1274 2.42487 14.5751 2.87258 15.1274 2.87258L23.1274 2.87258L23.1274 10.8726C23.1274 11.4249 23.5751 11.8726 24.1274 11.8726C24.6797 11.8726 25.1274 11.4249 25.1274 10.8726L25.1274 1.87258ZM1.5 24.5L2.20711 25.2071L24.8345 2.57969L24.1274 1.87258L23.4203 1.16548L0.792893 23.7929L1.5 24.5Z" d="M25.1274 1.87258C25.1274 1.3203 24.6797 0.872582 24.1274 0.872584L15.1274 0.872583C14.5751 0.872583 14.1274 1.3203 14.1274 1.87258C14.1274 2.42487 14.5751 2.87258 15.1274 2.87258L23.1274 2.87258L23.1274 10.8726C23.1274 11.4249 23.5751 11.8726 24.1274 11.8726C24.6797 11.8726 25.1274 11.4249 25.1274 10.8726L25.1274 1.87258ZM1.5 24.5L2.20711 25.2071L24.8345 2.57969L24.1274 1.87258L23.4203 1.16548L0.792893 23.7929L1.5 24.5Z"
fill="currentColor" /> fill="currentColor" />
@ -216,6 +223,7 @@ import CountryCurrencySelector from "./CountryCurrencySelector.vue";
import LangSwitcher from "./LangSwitcher.vue"; import LangSwitcher from "./LangSwitcher.vue";
const menuStore = useMenuStore(); const menuStore = useMenuStore();
const userStore = useUserStore();
const productStore = useProductStore(); const productStore = useProductStore();
const open = ref(false); const open = ref(false);
const colorMode = useColorMode(); const colorMode = useColorMode();

View File

@ -1,6 +1,6 @@
<template> <template>
<UiContainer> <UiContainer>
<form class="w-[85%] mx-auto" @submit.prevent="checkoutStore.sendForm"> <div class="w-[85%] mx-auto">
<div v-if="userStore.isLogged" class="space-25-55"> <div v-if="userStore.isLogged" class="space-25-55">
<div class="w-full flex items-center justify-center"> <div class="w-full flex items-center justify-center">
@ -29,21 +29,21 @@
<div class="flex flex-col gap-[30px] xl:flex-row"> <div class="flex flex-col gap-[30px] xl:flex-row">
<div class="flex flex-col w-1/2 gap-[30px]"> <div class="flex flex-col w-1/2 gap-[30px]">
<CheckoutInput v-model="checkoutStore.userName" :id="1" disabled>{{ $t("first_name") <CheckoutInput v-model="checkoutStore.userName" :id="1" disabled>{{ $t("first_name")
}} </CheckoutInput> }} </CheckoutInput>
<CheckoutInput v-model="checkoutStore.lastName" :id="2" disabled>{{ $t("surname") <CheckoutInput v-model="checkoutStore.lastName" :id="2" disabled>{{ $t("surname")
}} </CheckoutInput> }} </CheckoutInput>
<CheckoutInput v-model="checkoutStore.address" :id="3" disabled>{{ $t("address") <CheckoutInput v-model="checkoutStore.address" :id="3" disabled>{{ $t("address")
}} </CheckoutInput> }} </CheckoutInput>
<CheckoutInput v-model="checkoutStore.postCode" :id="4" disabled>{{ $t("post_code") <CheckoutInput v-model="checkoutStore.postCode" :id="4" disabled>{{ $t("post_code")
}} </CheckoutInput> }} </CheckoutInput>
</div> </div>
<div class="flex flex-col w-1/2 gap-[30px]"> <div class="flex flex-col w-1/2 gap-[30px]">
<CheckoutInput v-model="checkoutStore.city" :id="5" disabled>{{ $t("city") <CheckoutInput v-model="checkoutStore.city" :id="5" disabled>{{ $t("city")
}} </CheckoutInput> }} </CheckoutInput>
<CheckoutInput v-model="checkoutStore.country" :id="6" disabled>{{ $t("country") <CheckoutInput v-model="checkoutStore.country" :id="6" disabled>{{ $t("country")
}} </CheckoutInput> }} </CheckoutInput>
<CheckoutInput v-model="checkoutStore.phoneNumber" :id="7" disabled>{{ $t("phone") <CheckoutInput v-model="checkoutStore.accountPhoneNumber" :id="7" disabled>{{ $t("phone")
}} </CheckoutInput> }} </CheckoutInput>
</div> </div>
</div> </div>
</div> </div>
@ -51,14 +51,52 @@
<h2 class="h2-bold-bounded"> <h2 class="h2-bold-bounded">
{{ $t("Shipping details") }} {{ $t("Shipping details") }}
</h2> </h2>
<div class="relative"> <div class="relative border border-block rounded-lg px-6 h-[67px] w-full">
<input v-model="checkoutStore.accountPhoneNumber" type="text" <div class="flex items-center gap-[10px]">
class="border border-block placeholder:text-bg-dark dark:placeholder:text-bg-light rounded-lg px-6 h-[67px] w-full focus:outline-none focus:ring-0 focus:border-2 text-button" /> <div class="flex items-center gap-[25px]" v-if="!checkoutStore.vUseAccountPhoneNumber">
<div class="flex flex-col items-start gap-[25px]">
<div ref="dropdownIsoRef"
class="pl-[25px] relative w-full ring-0 cursor-pointer focus:ring-0 outline-none focus-visible:ring-0">
<div class="p-0" @click="dropIso = !dropIso">
<div
class="flex items-center gap-2 text-xl font-medium uppercase text-text-light dark:text-text-dark">
{{ checkoutStore.selectedIso.name }} <span> <i
class="uil uil-angle-down text-2xl font-light cursor-pointer"></i></span>
</div>
</div>
<div v-if="dropIso"
class="absolute w-full mt-2 left-0 bg-bg-light dark:bg-bg-dark rounded-[5px] data-highlighted:not-data-disabled:before:bg-button/50 ring-0 cursor-pointer focus:ring-0 outline-none focus-visible:ring-0 border border-button py-[10px] px-[5px]">
<div class="overflow-auto h-[200px] w-full">
<p @click="() => { checkoutStore.selectedIso = item; dropIso = false; checkoutStore.changePrefix(item.call_prefix as string) }"
class="w-full hover:bg-block dark:hover:bg-button pl-2 py-2 text-base text-text-light dark:text-text-dark rounded-[5px]"
v-for="item in menuStore.countries" :key="item.iso_code">
{{ item?.name }}
</p>
</div>
</div>
</div>
</div>
<p class="text-xl">{{ checkoutStore.currentPrefix }}</p>
</div>
<input
:value="checkoutStore.vUseAccountPhoneNumber ? checkoutStore.accountPhoneNumber : checkoutStore.phoneNumber"
:disabled="checkoutStore.vUseAccountPhoneNumber" @input="(e) => {
if (!checkoutStore.vUseAccountPhoneNumber) {
checkoutStore.phoneNumber = (e.target as HTMLInputElement).value;
}
}" type="tel" placeholder="123 xxxx xxx"
class="placeholder:text-xl placeholder:text-gray placeholder:uppercase dark:placeholder:text-bg-light rounded-lg h-[67px] w-full focus:outline-none focus:ring-0 focus:border-0" />
</div>
<p v-if="checkoutStore.phoneValidation === false && !checkoutStore.vUseAccountPhoneNumber"
class="text-red-500">Invalid phone number</p>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<input @change="(event: Event) => { <input @change="(event: Event) => {
const target = event.target as HTMLInputElement const target = event.target as HTMLInputElement
target.checked ? checkoutStore.vUseAccountPhoneNumber = true : checkoutStore.vUseAccountPhoneNumber = false target.checked ? checkoutStore.vUseAccountPhoneNumber = true : checkoutStore.vUseAccountPhoneNumber = false
checkoutStore.phoneValidation = null
}" type="checkbox" class="border border-button !bg-inherit" /> }" type="checkbox" class="border border-button !bg-inherit" />
<p>{{ $t('use_account_phone') }}</p> <p>{{ $t('use_account_phone') }}</p>
</div> </div>
@ -119,30 +157,55 @@
<CheckoutInput v-model="checkoutStore.vNewAddressSurname" :placeholder="$t('surname')" <CheckoutInput v-model="checkoutStore.vNewAddressSurname" :placeholder="$t('surname')"
:id="11">{{ $t("surname") }} :id="11">{{ $t("surname") }}
</CheckoutInput> </CheckoutInput>
<CheckoutInput v-model="checkoutStore.vNewAddressCountry" :placeholder="$t('country')" <div class="space-y-[15px]">
:id="12">{{ $t("country") }} <p class="pl-6">
</CheckoutInput> {{ $t("country") }}
</p>
<div ref="dropdownCountryRef"
class="relative w-full ring-0 cursor-pointer focus:ring-0 outline-none focus-visible:ring-0">
<div class="border border-block placeholder:text-gray dark:placeholder:text-button-disabled rounded-lg px-6 h-[67px] w-full focus:outline-none focus:ring-0 focus:border-2 flex items-center justify-start"
@click="dropCountry = !dropCountry">
<div
class="flex items-center gap-2 text-xl font-medium uppercase text-text-light dark:text-text-dark">
{{ checkoutStore.vNewAddressCountry ?
checkoutStore.vNewAddressCountry.name : '-' }} <span> <i
class="uil uil-angle-down text-2xl font-light cursor-pointer"></i></span>
</div>
</div>
<div v-if="dropCountry"
class="absolute z-50 w-full mt-2 left-0 bg-bg-light dark:bg-bg-dark rounded-[5px] data-highlighted:not-data-disabled:before:bg-button/50 ring-0 cursor-pointer focus:ring-0 outline-none focus-visible:ring-0 border border-button py-[10px] px-[5px]">
<div class="overflow-auto h-[200px] w-full">
<p @click="() => { checkoutStore.vNewAddressCountry = item; dropCountry = false }"
class="w-full hover:bg-block dark:hover:bg-button pl-2 py-2 text-base text-text-light dark:text-text-dark rounded-[5px]"
v-for="item in menuStore.countries" :key="item.iso_code">
{{ item?.name }}
</p>
</div>
</div>
</div>
</div>
<CheckoutInput v-model="checkoutStore.vNewAddressCode" :placeholder="$t('post_code')" <CheckoutInput v-model="checkoutStore.vNewAddressCode" :placeholder="$t('post_code')"
:id="13">{{ $t("post_code") }} :id="13">{{ $t("post_code") }}
</CheckoutInput> </CheckoutInput>
</div> </div>
</div> </div>
<form @submit.prevent="checkoutStore.uploadAddress()"> <div>
<span v-if="addressValidation === false" class="text-red"> {{ <span v-if="addressValidation === false" class="text-red"> {{
$t("Remember to select a shipping address") }}</span> $t("Remember to select a shipping address") }}</span>
<div class="group flex cursor-pointer items-center justify-start gap-2 whitespace-nowrap"> <div class="group flex cursor-pointer items-center justify-start gap-2 whitespace-nowrap">
<button type="submit" <button @click="checkoutStore.uploadAddress()"
:class="['h-[40px] cursor-pointer min-w-40 rounded-[10px] px-[22px] transition-all sm:h-[50px] md:h-[65px] md:rounded-[15px] md:px-[42px] bg-button text-text-dark group-hover:bg-button-hover']"> :class="['h-[40px] cursor-pointer min-w-40 rounded-[10px] px-[22px] transition-all sm:h-[50px] md:h-[65px] md:rounded-[15px] md:px-[42px] bg-button text-text-dark group-hover:bg-button-hover']">
{{ $t("add_new_address") }} {{ $t("add_new_address") }}
</button> </button>
</div> </div>
</form> </div>
</div> </div>
</div> </div>
<div class="flex justify-center"> <div class="flex justify-center">
<div class="group flex cursor-pointer items-center justify-start gap-2 whitespace-nowrap"> <div class="group flex cursor-pointer items-center justify-start gap-2 whitespace-nowrap">
<button <button @click="checkoutStore.sendForm()" :disabled="!checkoutStore.activeAddress"
:class="['h-[40px] cursor-pointer min-w-40 rounded-[10px] px-[22px] transition-all sm:h-[50px] md:h-[65px] md:rounded-[15px] md:px-[42px]', checkoutStore.activeAddress ? 'bg-button text-text-dark group-hover:bg-button-hover' : ' bg-button-disabled text-gray']"> :class="['h-[40px] cursor-pointer min-w-40 rounded-[10px] px-[22px] transition-all sm:h-[50px] md:h-[65px] md:rounded-[15px] md:px-[42px]', checkoutStore.activeAddress ? 'bg-button text-text-dark group-hover:bg-button-hover' : ' bg-button-disabled text-gray']">
{{ $t("continue") }} {{ $t("continue") }}
</button> </button>
@ -158,39 +221,34 @@
</div> </div>
</div> </div>
</div> </div>
</form> </div>
</UiContainer> </UiContainer>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { LazyColorScheme } from '#components';
import CheckoutInput from '../ui/CheckoutInput.vue'; import CheckoutInput from '../ui/CheckoutInput.vue';
import { onClickOutside } from "@vueuse/core";
const checkoutStore = useCheckoutStore(); const checkoutStore = useCheckoutStore();
const userStore = useUserStore() const userStore = useUserStore()
const router = useRoute(); const menuStore = useMenuStore()
const dropIso = ref(false)
const dropCountry = ref(false)
const addressValidation = ref<null | boolean>(null); const addressValidation = ref<null | boolean>(null);
watch( const dropdownIsoRef = ref(null);
() => checkoutStore.vUseAccountPhoneNumber, const dropdownCountryRef = ref(null);
(newValue) => { onClickOutside(dropdownIsoRef, () => {
if (newValue == true) { dropIso.value = false
checkoutStore.accountPhoneNumber = checkoutStore.phoneNumber; });
} else {
checkoutStore.accountPhoneNumber = "";
}
}
);
onClickOutside(dropdownCountryRef, () => {
dropCountry.value = false
});
// fetchWithCookie(useRequestEvent(), `restricted/cart/checkout`, { checkoutStore.getCheckout()
// method: "PUT", checkoutStore.getAddressList()
// }); checkoutStore.getUserData()
// if change needs to be applied then provide it in second argument of function below
// $setSeoMetaFromTranslation(useStore(), {});
checkoutStore.restrictedAddress()
checkoutStore.restrictedAddressOfficial()
</script> </script>

View File

@ -41,59 +41,37 @@
</div> </div>
</UiContainer> </UiContainer>
<UiContainer v-if="!userStore.vCodeVerify" class="flex py-20 sm:py-14"> <UiContainer v-if="userStore.vCodeVerify" class="flex py-20 sm:py-14">
<div class="hidden xl:block rounded-2xl min-w-[50%] h-[830px]" :style="{ <div class="hidden xl:block rounded-2xl min-w-[60%] h-[830px]" :style="{
backgroundImage: `url('/api/public/file/${component.img[0]}_l.webp')`, backgroundImage: `url('/api/public/file/${component.img[0]}_l.webp')`,
backgroundSize: 'cover', backgroundSize: 'cover',
backgroundPosition: 'center', backgroundPosition: 'center',
}" /> }" />
<div class="w-full sm:w-[80%] mx-auto my-auto xl:w-full xl:px-12 "> <div class="w-full sm:w-[80%] mx-auto my-auto xl:w-full xl:px-12 ">
<div class="space-25-55"> <div class="space-25-55">
<div class="space-y-[15px]"> <div class="flex flex-wrap-reverse gap-y-4 justify-between">
<div class="flex flex-wrap-reverse gap-y-4 justify-between"> <h2 class="h2-bold-bounded">{{ $t('verify_login') }}</h2>
<h2 class="h2-bold-bounded">{{ $t('verify_login') }}</h2> <button @click="menuStore.navigateToItem()"
<button @click="menuStore.navigateToItem()" class="h-[40px] sm:h-[43px] px-[10px] sm:px-[17px] border border-gray dark:border-button-disabled text-gray dark:text-button-disabled hover:bg-gray transition-all hover:text-white rounded-[8px] cursor-pointer">{{
class="h-[40px] sm:h-[43px] px-[10px] sm:px-[17px] border border-gray dark:border-button-disabled text-gray dark:text-button-disabled hover:bg-gray transition-all hover:text-white rounded-[8px] cursor-pointer">{{ $t('back_to_home') }}</button>
$t('back_to_home') }}</button> </div>
</div> <div class="space-y-[30px]">
<p>{{ $t('send_code') }} <span class="text-button font-semibold">test@ma-al.com</span> {{ <p>{{ $t('send_code') }} <span class="text-button font-semibold">{{ userStore.email }}</span> {{
$t('by_email') }} $t('by_email') }}
</p> </p>
</div> <div class="space-y-[15px]">
<div class="space-y-[15px]"> <p class="pl-6">{{ $t('code') }}</p>
<p class="pl-6">{{ $t('code') }}</p> <input v-model="userStore.vCode" :placeholder="$t('code')" type="text"
<input v-model="userStore.vCode" :placeholder="$t('code')" type="text" class="border-2 border-block placeholder:text-gray dark:placeholder:text-button-disabled text-bg-dark dark:text-bg-light rounded-lg px-6 h-[50px] sm:h-[67px] w-full focus:outline-none focus:ring-0 focus:border-2" />
class="border-2 border-block placeholder:text-gray dark:placeholder:text-button-disabled text-bg-dark dark:text-bg-light rounded-lg px-6 h-[50px] sm:h-[67px] w-full focus:outline-none focus:ring-0 focus:border-2" /> </div>
</div> </div>
</div> </div>
<div class="py-[25px] sm:py-12 flex justify-center w-full"> <div class="py-[25px] sm:py-12 flex justify-center w-full">
<UiButtonArrow @click="userStore.logIn()" type="fill" :arrow="true">{{ $t('confirm') }}</UiButtonArrow> <UiButtonArrow @click="userStore.sendFormCode(true)" type="fill" :arrow="true">{{ $t('confirm') }}
</UiButtonArrow>
</div> </div>
</div> </div>
</UiContainer> </UiContainer>
<!-- v-if="store.vCodeVerify" -->
<div class="max-w-[590px] w-full">
<h3 class="mb-1 text-2xl font-bold">
{{ $t("FrontTranslations", "Verify your login") }}
</h3>
<span class="text-sm font-bold text-black">
{{ $t("FrontTranslations", "We send code to") }} <span class="text-yellow"> {{ userStore.vEmail }}</span>
{{ $t("FrontTranslations", "by") }} e-mail.</span>
<!-- <form
@submit.prevent="useUserStore().sendFormCode(`${vCode}`, vEmail, useRoute().query.to ? (useRoute().query.to as string) : undefined)">
<div class="flex flex-col w-full gap-10 mt-6">
<BaseInput v-model="vCode" :id="1"> {{ $t("FrontTranslations", "Code") }}</BaseInput>
</div>
<div class="flex flex-col">
<div class="flex">
<BaseButtonLogin id="2" type="submit">
{{ $t("FrontTranslations", "Confirm") }}
</BaseButtonLogin>
</div>
</div>
</form> -->
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -113,7 +91,6 @@ defineProps<{
}[] }[]
} }
}>(); }>();
const store = useStore()
const userStore = useUserStore() const userStore = useUserStore()
const menuStore = useMenuStore() const menuStore = useMenuStore()
</script> </script>

View File

@ -9,11 +9,11 @@
:placeholder="placeholder" :disabled="disabled" :placeholder="placeholder" :disabled="disabled"
@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)" @input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
@focus="$emit('focus')" @blur="$emit('blur')" @focus="$emit('focus')" @blur="$emit('blur')"
class="border border-block placeholder:text-gray dark:placeholder:text-button-disabled rounded-lg px-6 h-[67px] w-full focus:outline-none focus:ring-0 focus:border-2 text-button" /> class="border border-block placeholder:text-gray dark:placeholder:text-button-disabled rounded-lg px-6 h-[67px] w-full focus:outline-none focus:ring-0 focus:border-2" />
<i v-if="disabled" <i v-if="disabled"
class="uil uil-lock-alt text-[22px] absolute right-6 top-1/2 -translate-y-1/2 text-gray" /> class="uil uil-lock-alt text-[22px] absolute right-6 top-1/2 -translate-y-1/2 text-gray" />
<div v-if="type === 'password'" class="order-2 ml-1.5 cursor-pointer" :title="!isPasswordVisible ? $t('Panel.Component.InputDefault', 'Show password') : $t('Panel.Component.InputDefault', 'Hide password') <div v-if="type === 'password'" class="order-2 ml-1.5 cursor-pointer" :title="!isPasswordVisible ? $t('show_password') : $t('hide_password')
" @click="isPasswordVisible = !isPasswordVisible"> " @click="isPasswordVisible = !isPasswordVisible">
<FaceObserver class="ml-4 text-xl leading-6" :isPasswordVisible="isPasswordVisible" /> <FaceObserver class="ml-4 text-xl leading-6" :isPasswordVisible="isPasswordVisible" />
</div> </div>

View File

@ -1,47 +1,32 @@
import type { GenericResponse } from "~/types"; import type { GenericResponse } from "~/types";
import type { AddressesList } from "~/types/checkout"; import type { AddressesList, UserAddressOfficial } from "~/types/checkout";
import { validation } from "../utils/validation"; import { validation } from "../utils/validation";
import { REGEX_PHONE } from "../utils/regex"; import { REGEX_PHONE } from "../utils/regex";
export const useCheckoutStore = defineStore("checkoutStore", () => { export const useCheckoutStore = defineStore("checkoutStore", () => {
const { $toast } = useNuxtApp(); const { $toast } = useNuxtApp();
const menuStore = useMenuStore();
const selectedIso = ref(menuStore.selectedCountry);
// get address list
const addressesList = ref<AddressesList[]>(); const addressesList = ref<AddressesList[]>();
const activeAddress = ref<AddressesList | null>(); const activeAddress = ref<AddressesList | null>();
async function restrictedAddress() { async function getAddressList() {
try { try {
// const { data } = await useMyFetch<GenericResponse<object>>( const { data } = await useMyFetch<GenericResponse<AddressesList[]>>(
// `/api/restricted/user/addresses`, `/api/restricted/user/addresses`,
// {
// headers: {
// "Content-Type": "application/json",
// },
// onErrorOccured: async (_, status) => {
// throw createError({
// statusCode: status,
// statusMessage: `HTTP error: ${status}`,
// });
// },
// }
// );
const data = [
{ {
address: { headers: {
city: "Bochnia", "Content-Type": "application/json",
country_iso: "pl",
name: "John",
postcode: "32-700",
street: "Karosek",
surname: "Kornelsky",
}, },
address_id: 2, onErrorOccured: async (_, status) => {
alias: "home", throw createError({
customer_id: 4, statusCode: status,
is_default: true, statusMessage: `HTTP error: ${status}`,
is_official: false, });
}, },
]; }
);
addressesList.value = data; addressesList.value = data;
activeAddress.value = addressesList.value[0]; activeAddress.value = addressesList.value[0];
@ -50,6 +35,7 @@ export const useCheckoutStore = defineStore("checkoutStore", () => {
} }
} }
// get user data
const userName = ref(""); const userName = ref("");
const lastName = ref(""); const lastName = ref("");
const address = ref(""); const address = ref("");
@ -58,65 +44,47 @@ export const useCheckoutStore = defineStore("checkoutStore", () => {
const country = ref(""); const country = ref("");
const phoneNumber = ref(""); const phoneNumber = ref("");
const accountPhoneNumber = ref(""); const accountPhoneNumber = ref("");
async function restrictedAddressOfficial() { async function getUserData() {
try { try {
// const { data } = await useMyFetch<GenericResponse<object>>( const { data } = await useMyFetch<GenericResponse<UserAddressOfficial>>(
// `/api/restricted/user/address/official`, `/api/restricted/user/address/official`,
// { {
// headers: { headers: {
// "Content-Type": "application/json", "Content-Type": "application/json",
// }, },
// onErrorOccured: async (_, status) => { onErrorOccured: async (_, status) => {
// throw createError({ throw createError({
// statusCode: status, statusCode: status,
// statusMessage: `HTTP error: ${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; userName.value = data.address.name;
lastName.value = data.address.surname; lastName.value = data.address.surname;
address.value = data.address.street; address.value = data.address.street;
postCode.value = data.address.postcode; postCode.value = data.address.postcode;
city.value = data.address.city; city.value = data.address.city;
country.value = data.address.country_iso; country.value = data.address.country.country_lang[0].name;
// resolve this
// accountPhoneNumber.value = useUserStore().fullUserData.phone_number;
phoneNumber.value = "+36 789 3773 737";
} catch (error) { } catch (error) {
console.error("restrictedAddressOfficial error:", error); console.error("getUserData error:", error);
} }
} }
// upload new address
const vNewAddressName = ref(""); const vNewAddressName = ref("");
const vNewAddressSurname = ref(""); const vNewAddressSurname = ref("");
const vNewAddressAddress = ref(""); const vNewAddressAddress = ref("");
const vNewAddressCode = ref(""); const vNewAddressCode = ref("");
const vNewAddressCity = ref(""); const vNewAddressCity = ref("");
const vNewAddressCountry = ref(""); const vNewAddressCountry = ref();
const vUseAccountPhoneNumber = ref(false); const vUseAccountPhoneNumber = ref(false);
const isOpen = ref<boolean>(false); const isOpen = ref<boolean>(false);
async function uploadAddress() { async function uploadAddress() {
try { try {
const res = await useMyFetch<GenericResponse<object>>( const res = await useMyFetch<GenericResponse<object>>(
`/api/restricted/user/address/official`, `/api/restricted/user/address`,
{ {
method: "POST", method: "POST",
headers: { headers: {
@ -125,7 +93,7 @@ export const useCheckoutStore = defineStore("checkoutStore", () => {
body: JSON.stringify({ body: JSON.stringify({
address: { address: {
city: vNewAddressCity.value, city: vNewAddressCity.value,
// country_iso: vNewAddressCountry.value?.iso_code, country_iso: vNewAddressCountry.value.iso_code,
name: vNewAddressName.value, name: vNewAddressName.value,
postcode: vNewAddressCode.value, postcode: vNewAddressCode.value,
street: vNewAddressAddress.value, street: vNewAddressAddress.value,
@ -147,7 +115,7 @@ export const useCheckoutStore = defineStore("checkoutStore", () => {
dangerouslyHTMLString: true, dangerouslyHTMLString: true,
}); });
isOpen.value = false; isOpen.value = false;
restrictedAddress(); getAddressList();
} else { } else {
$toast.error("Failed to add address. Please try again.", { $toast.error("Failed to add address. Please try again.", {
autoClose: 5000, autoClose: 5000,
@ -159,23 +127,32 @@ export const useCheckoutStore = defineStore("checkoutStore", () => {
} }
} }
const currentPrefix = ref<string | number>("+43"); const currentPrefix = ref<string | number>(
const changePrefix = (item: any) => { menuStore.selectedCountry.call_prefix
);
const changePrefix = (item: string) => {
currentPrefix.value = item; currentPrefix.value = item;
}; };
const phoneValidation = ref<boolean | null>(null); const phoneValidation = ref<boolean | null>(null);
const userStore = useUserStore();
// send form
async function sendForm() { async function sendForm() {
let phoneNum = `${currentPrefix.value}${phoneNumber.value}` let phoneNum = vUseAccountPhoneNumber.value
.replaceAll(" ", "") ? accountPhoneNumber.value
.trim(); : `${currentPrefix.value}${phoneNumber.value}`.replaceAll(" ", "").trim();
// if (vUseAccountPhoneNumber.value) { // if (vUseAccountPhoneNumber.value) {
// phoneNum = phoneNumber.value; // phoneNum = phoneNumber.value;
// } // }
phoneValidation.value = validation(phoneNum, 1, 49, REGEX_PHONE); phoneValidation.value = validation(phoneNum, 1, 49, REGEX_PHONE);
if (!phoneValidation.value && !vUseAccountPhoneNumber.value) {
return;
}
try { try {
const res = await useMyFetch<GenericResponse<object>>( const res = await useMyFetch<GenericResponse<object>>(
`restricted/cart/checkout/delivery`, `/api/restricted/cart/checkout/delivery`,
{ {
method: "PUT", method: "PUT",
headers: { headers: {
@ -191,7 +168,7 @@ export const useCheckoutStore = defineStore("checkoutStore", () => {
surname: activeAddress.value?.address.surname, surname: activeAddress.value?.address.surname,
}, },
phone_number: phoneNum, phone_number: phoneNum,
// email: useUserStore().fullUserData.email, email: userStore.fullUserData?.email,
}), }),
onErrorOccured: async (_, status) => { onErrorOccured: async (_, status) => {
throw createError({ throw createError({
@ -223,10 +200,34 @@ export const useCheckoutStore = defineStore("checkoutStore", () => {
activeAddress.value = item; activeAddress.value = item;
}; };
async function getCheckout() {
try {
const res = await useMyFetch<GenericResponse<object>>(
`/api/restricted/cart/checkout`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
onErrorOccured: async (_, status) => {
throw createError({
statusCode: status,
statusMessage: `HTTP error: ${status}`,
});
},
}
);
} catch (error) {
console.error("uploadAddress error:", error);
}
}
return { return {
addressesList, addressesList,
activeAddress, activeAddress,
isOpen, isOpen,
selectedIso,
phoneValidation,
userName, userName,
lastName, lastName,
@ -246,8 +247,10 @@ export const useCheckoutStore = defineStore("checkoutStore", () => {
vNewAddressCity, vNewAddressCity,
vNewAddressCountry, vNewAddressCountry,
restrictedAddress, changePrefix,
restrictedAddressOfficial, getCheckout,
getAddressList,
getUserData,
changeActive, changeActive,
uploadAddress, uploadAddress,
sendForm, sendForm,

View File

@ -3,6 +3,10 @@ import type { Customer } from "~/types/user";
export const useUserStore = defineStore("userStore", () => { export const useUserStore = defineStore("userStore", () => {
const store = useStore(); const store = useStore();
const menuStore = useMenuStore();
const checkoutStore = useCheckoutStore();
const { $toast } = useNuxtApp();
const fullUserData = ref<Customer | null>(null); const fullUserData = ref<Customer | null>(null);
const isLogged = ref<boolean>(true); const isLogged = ref<boolean>(true);
@ -10,28 +14,26 @@ export const useUserStore = defineStore("userStore", () => {
async function checkIsLogged() { async function checkIsLogged() {
try { try {
const { data } = await useMyFetch< const { data } = await useMyFetch<GenericResponse<Customer>>(
GenericResponse<{ loggedin: boolean } | Customer> `/api/public/user`,
>(`/api/public/user`, { {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
onErrorOccured: async (_, status) => { onErrorOccured: async (_, status) => {
throw createError({ throw createError({
statusCode: status, statusCode: status,
statusMessage: `HTTP error: ${status}`, statusMessage: `HTTP error: ${status}`,
}); });
}, },
}); }
);
if ("loggedin" in data && data.loggedin === true) { if ("loggedin" in data && data.loggedin === true) {
isLogged.value = true; isLogged.value = true;
user.value = null; user.value = `${data.first_name} ${data.last_name}` as any;
fullUserData.value = null; fullUserData.value = data;
} else if ("first_name" in data && "last_name" in data) { checkoutStore.accountPhoneNumber = fullUserData.value.phone_number;
isLogged.value = true;
user.value = `${data.first_name} ${data.last_name}`;
fullUserData.value = data as Customer;
} else { } else {
isLogged.value = false; isLogged.value = false;
user.value = null; user.value = null;
@ -48,7 +50,6 @@ export const useUserStore = defineStore("userStore", () => {
const vLogin = ref<boolean>(true); const vLogin = ref<boolean>(true);
const vCodeVerify = ref<boolean>(false); const vCodeVerify = ref<boolean>(false);
const vCode = ref<number | null>(null); const vCode = ref<number | null>(null);
const vEmail = ref<string>("");
async function logIn() { async function logIn() {
try { try {
const data = await useMyFetch<GenericResponse<object>>( const data = await useMyFetch<GenericResponse<object>>(
@ -71,35 +72,77 @@ export const useUserStore = defineStore("userStore", () => {
if (data.status === 200 || data.status === 201) { if (data.status === 200 || data.status === 201) {
console.log(vCodeVerify.value); console.log(vCodeVerify.value);
// $toast.success("Address successfully added", { $toast.success("Code successfully sent to your email", {
// autoClose: 5000, autoClose: 5000,
// dangerouslyHTMLString: true, dangerouslyHTMLString: true,
// }); });
vLogin.value = false; vLogin.value = false;
vCodeVerify.value = true; vCodeVerify.value = true;
} else {
$toast.error("Failed to sent code to your email. Please try again.", {
autoClose: 5000,
dangerouslyHTMLString: true,
});
} }
// else {
// $toast.error("Failed to add address. Please try again.", {
// autoClose: 5000,
// dangerouslyHTMLString: true,
// });
// }
store.minValue = data; store.minValue = data;
} catch (error) { } catch (error) {
console.error("getList error:", error); console.error("getList error:", error);
} }
} }
const sendFormCode = async (redirect?: boolean) => {
try {
const data = await useMyFetch<GenericResponse<object>>(
`/api/public/user/session/confirm`,
{
method: "POST",
body: JSON.stringify({
code: vCode.value,
mail: email.value,
}),
headers: {
"Content-Type": "application/json",
},
onErrorOccured: (_, status) => {
throw new Error(`HTTP error: ${status}`);
},
}
);
await checkIsLogged();
if (isLogged.value) {
if (redirect) {
console.log(isLogged.value);
menuStore.navigateToItem();
} else {
// window.location.href = atob(redirect);
}
} else {
useNuxtApp().$toast.error(`Error occurred: Failed to confirm code`, {
autoClose: 5000,
dangerouslyHTMLString: true,
});
}
} catch (e) {
useNuxtApp().$toast.error(`Invalid code provided`, {
autoClose: 5000,
dangerouslyHTMLString: true,
});
}
};
return { return {
isLogged, isLogged,
user, user,
fullUserData, fullUserData,
vCodeVerify, vCodeVerify,
vCode, vCode,
vEmail,
email, email,
password, password,
logIn, logIn,
checkIsLogged, checkIsLogged,
sendFormCode,
}; };
}); });

View File

@ -13,3 +13,20 @@ export interface AddressesList {
is_default: boolean; is_default: boolean;
is_official: boolean; is_official: boolean;
} }
export interface UserAddressOfficial {
address: {
name: string;
surname: string;
street: string;
postcode: string;
city: string;
country: {
country_lang: [
{
name: string;
}
];
};
};
}