This commit is contained in:
2025-06-27 16:02:00 +02:00
parent 96dbc38c3a
commit 012058b998
21 changed files with 1299 additions and 433 deletions

View File

@ -0,0 +1,6 @@
<template>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Modi perspiciatis adipisci quam odio natus odit excepturi
eveniet vitae. Fugit dicta officiis quos quia debitis perspiciatis porro ducimus earum placeat sunt?
</template>
<script lang="ts"></script>

View File

@ -1,7 +1,196 @@
<template>
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Veritatis dignissimos impedit eligendi quaerat. Doloribus
eius nisi facere suscipit pariatur reprehenderit reiciendis tempora est accusamus adipisci hic voluptate asperiores,
voluptas enim.
<UiContainer>
<form class="w-[85%] mx-auto" @submit.prevent="checkoutStore.sendForm">
<div v-if="userStore.isLogged" class="space-25-55">
<div class="w-full flex items-center justify-center">
<div class="flex justify-between">
<div class="flex items-center gap-[25px] text-gray dark:text-button-disabled">
<div class="px-6 py-3 mx-auto">
{{ $t("login") }}
</div>
<div
class="cursor-pointer transition-all text-inter hover:bg-button-hover bg-button text-white font-medium rounded-xl px-6 py-3">
{{ $t("address") }}
</div>
<div class="px-6 py-3 mx-auto">
{{ $t("summary") }}
</div>
<div class="px-6 py-3 mx-auto">
{{ $t("order_placed") }}
</div>
</div>
</div>
</div>
<div class="space-y-[30px]">
<h2 class="h2-bold-bounded">
{{ $t("Account address") }}
</h2>
<div class="flex flex-col gap-[30px] xl:flex-row">
<div class="flex flex-col w-1/2 gap-[30px]">
<CheckoutInput v-model="checkoutStore.userName" :id="1" disabled>{{ $t("first_name")
}} </CheckoutInput>
<CheckoutInput v-model="checkoutStore.lastName" :id="2" disabled>{{ $t("surname")
}} </CheckoutInput>
<CheckoutInput v-model="checkoutStore.address" :id="3" disabled>{{ $t("address")
}} </CheckoutInput>
<CheckoutInput v-model="checkoutStore.postCode" :id="4" disabled>{{ $t("post_code")
}} </CheckoutInput>
</div>
<div class="flex flex-col w-1/2 gap-[30px]">
<CheckoutInput v-model="checkoutStore.city" :id="5" disabled>{{ $t("city")
}} </CheckoutInput>
<CheckoutInput v-model="checkoutStore.country" :id="6" disabled>{{ $t("country")
}} </CheckoutInput>
<CheckoutInput v-model="checkoutStore.phoneNumber" :id="7" disabled>{{ $t("phone")
}} </CheckoutInput>
</div>
</div>
</div>
<div class="space-y-[30px]">
<h2 class="h2-bold-bounded">
{{ $t("Shipping details") }}
</h2>
<div class="relative">
<input v-model="checkoutStore.accountPhoneNumber" type="text"
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>
<div class="flex items-center gap-2">
<input @change="(event: Event) => {
const target = event.target as HTMLInputElement
target.checked ? checkoutStore.vUseAccountPhoneNumber = true : checkoutStore.vUseAccountPhoneNumber = false
}" type="checkbox" class="border border-button !bg-inherit" />
<p>{{ $t('use_account_phone') }}</p>
</div>
</div>
<div class="space-y-[30px]">
<h2 class="h2-bold-bounded">
{{ $t("Select delivery address") }}
</h2>
<div class="flex items-center justify-center gap-[25px] h-[225px]">
<div class="w-[500px] flex flex-col gap-4 h-full">
<div v-for="(item, index) in checkoutStore.addressesList" :key="index"
:class="['flex h-full flex-col py-[15px] px-[25px] gap-[15px] rounded-lg border-2', checkoutStore.activeAddress === item ? 'border-button' : 'border-block']">
<div
:class="['flex flex-col justify-between mt-1 h-full', checkoutStore.activeAddress !== item && 'text-gray dark:text-button-disabled']">
<span>{{ item.address.name }} {{ item.address.surname }}</span>
<span>{{ item.address.street }}</span>
<span>{{ item.address.postcode }} {{ item.address.city }}</span>
<span>{{ item.address.country_iso }}</span>
</div>
<div class="flex items-center gap-2 border-t pt-[15px] border-block">
<input :checked="checkoutStore.activeAddress ? true : false" @change="(event: Event) => {
const target = event.target as HTMLInputElement
target.checked ? checkoutStore.activeAddress = item : checkoutStore.activeAddress = null;
checkoutStore.isOpen = false;
}" type="checkbox" class="border border-button !bg-inherit" />
<p>{{ $t('choose_default_address') }}</p>
</div>
</div>
</div>
<p class="uppercase">{{ $t("or") }}</p>
<div @click="() => {
checkoutStore.isOpen = !checkoutStore.isOpen
checkoutStore.activeAddress = null
}"
:class="['cursor-pointer w-[500px] py-[15px] px-[25px] rounded-lg border-2 flex flex-col items-center justify-center h-full', checkoutStore.isOpen ? 'border-button text-button' : 'text-gray border-block ']">
<h4
:class="['font-inter text-base leading-[150%] uppercase text-[16px] sm:text-[20px] border-b', checkoutStore.isOpen ? 'border-button' : 'border-gray']">
{{ $t("add_new_address") }}
</h4>
</div>
</div>
<div v-if="checkoutStore.isOpen"
class="flex flex-col items-center gap-[30px] justify-center w-full">
<div class="flex flex-col gap-[30px] xl:flex-row w-full">
<div class="flex flex-col w-1/2 gap-[30px]">
<CheckoutInput v-model="checkoutStore.vNewAddressName" :placeholder="$t('first_name')"
:id="8">{{ $t("first_name") }}
</CheckoutInput>
<CheckoutInput v-model="checkoutStore.vNewAddressAddress" :placeholder="$t('address')"
:id="9">{{ $t("address") }}
</CheckoutInput>
<CheckoutInput v-model="checkoutStore.vNewAddressCity" :placeholder="$t('city')"
:id="10">{{ $t("city") }}
</CheckoutInput>
</div>
<div class="flex flex-col w-1/2 gap-[30px]">
<CheckoutInput v-model="checkoutStore.vNewAddressSurname" :placeholder="$t('surname')"
:id="11">{{ $t("surname") }}
</CheckoutInput>
<CheckoutInput v-model="checkoutStore.vNewAddressCountry" :placeholder="$t('country')"
:id="12">{{ $t("country") }}
</CheckoutInput>
<CheckoutInput v-model="checkoutStore.vNewAddressCode" :placeholder="$t('post_code')"
:id="13">{{ $t("post_code") }}
</CheckoutInput>
</div>
</div>
<form @submit.prevent="checkoutStore.uploadAddress()">
<span v-if="addressValidation === false" class="text-red"> {{
$t("Remember to select a shipping address") }}</span>
<div class="group flex cursor-pointer items-center justify-start gap-2 whitespace-nowrap">
<button type="submit"
: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") }}
</button>
</div>
</form>
</div>
</div>
<div class="flex justify-center">
<div class="group flex cursor-pointer items-center justify-start gap-2 whitespace-nowrap">
<button
: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") }}
</button>
<div
:class="['flex h-[40px] w-[40px] items-center justify-center rounded-[10px] p-2.5 transition-all sm:h-[50px] sm:w-[50px] md:h-[65px] md:w-[65px] md:rounded-[15px]', checkoutStore.activeAddress ? 'bg-button text-text-dark group-hover:bg-button-hover' : ' bg-button-disabled text-gray']">
<svg class="" width=" 26" height="26" viewBox="0 0 26 26" fill="none"
xmlns="http://www.w3.org/2000/svg">
<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"
fill="currentColor" />
</svg>
</div>
</div>
</div>
</div>
</form>
</UiContainer>
</template>
<script lang="ts" setup></script>
<script setup lang="ts">
import CheckoutInput from '../ui/CheckoutInput.vue';
const checkoutStore = useCheckoutStore();
const userStore = useUserStore()
const router = useRoute();
const addressValidation = ref<null | boolean>(null);
watch(
() => checkoutStore.vUseAccountPhoneNumber,
(newValue) => {
if (newValue == true) {
checkoutStore.accountPhoneNumber = checkoutStore.phoneNumber;
} else {
checkoutStore.accountPhoneNumber = "";
}
}
);
// fetchWithCookie(useRequestEvent(), `restricted/cart/checkout`, {
// method: "PUT",
// });
// if change needs to be applied then provide it in second argument of function below
// $setSeoMetaFromTranslation(useStore(), {});
checkoutStore.restrictedAddress()
checkoutStore.restrictedAddressOfficial()
</script>

View File

@ -1,7 +1,7 @@
<template>
<UiContainer 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-[60%] h-[830px]" :style="{
backgroundImage: `url('/api/files/${component.image_collection}/${component.section_id}/${component.section_img[0]}?thumb=1200x0')`,
backgroundImage: `url('/api/public/file/${component.img[0]}_l.webp')`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}" />
@ -9,46 +9,111 @@
<div class="space-25-55">
<div class="flex flex-wrap-reverse gap-y-4 justify-between">
<h2 class="h2-bold-bounded">{{ $t('login') }}</h2>
<button
<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">{{
$t('back_to_home') }}</button>
</div>
<div class="space-y-[15px]">
<p class="pl-6">{{ $t('email') }}</p>
<input v-model="store.email" :placeholder="$t('email')" type="text"
<input v-model="userStore.email" :placeholder="$t('email')" 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" />
</div>
<div class="space-y-[15px]">
<p class="pl-6">{{ $t('password') }}</p>
<input v-model="store.password" :placeholder="$t('placeholder_password')" type="text"
<input v-model="userStore.password" :placeholder="$t('placeholder_password')" 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" />
</div>
</div>
<p class="text-button hover:text-button-hover transition-all font-medium mt-[30px] cursor-pointer">{{
$t('forgot_password_question')
}}</p>
}}</p>
<div class="py-[25px] sm:py-12 border-b border-gray flex justify-center w-full">
<UiButtonArrow @click="store.logIn()" type="fill" :arrow="true">{{ $t('login') }}</UiButtonArrow>
<UiButtonArrow @click="userStore.logIn()" type="fill" :arrow="true">{{ $t('login') }}</UiButtonArrow>
</div>
<div class="mt-[25px] sm:mt-[30px] w-full flex justify-center gap-3">
<p class="cursor-pointer hover:underline transition-all">{{
$t('no_account')
}}</p>
}}</p>
<p class="text-button cursor-pointer hover:text-button-hover">{{
$t('sign_up_now')
}}</p>
}}</p>
</div>
</div>
</UiContainer>
<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="{
backgroundImage: `url('/api/public/file/${component.img[0]}_l.webp')`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}" />
<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-y-[15px]">
<div class="flex flex-wrap-reverse gap-y-4 justify-between">
<h2 class="h2-bold-bounded">{{ $t('verify_login') }}</h2>
<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">{{
$t('back_to_home') }}</button>
</div>
<p>{{ $t('send_code') }} <span class="text-button font-semibold">test@ma-al.com</span> {{
$t('by_email') }}
</p>
</div>
<div class="space-y-[15px]">
<p class="pl-6">{{ $t('code') }}</p>
<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" />
</div>
</div>
<div class="py-[25px] sm:py-12 flex justify-center w-full">
<UiButtonArrow @click="userStore.logIn()" type="fill" :arrow="true">{{ $t('confirm') }}</UiButtonArrow>
</div>
</div>
</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>
<script lang="ts" setup>
defineProps<{ component: Component }>();
type Component = {
image_collection: string;
section_id: string;
section_img: string;
section_lang_data: {};
};
defineProps<{
component: {
id: number
name: string
img: string[]
component_name: string
is_no_lang: boolean
page_name: string
front_section_lang: {
data: {
}
id_front_section: number
id_lang: number
}[]
}
}>();
const store = useStore()
const userStore = useUserStore()
const menuStore = useMenuStore()
</script>

View File

@ -2,7 +2,7 @@
<div
class="w-[150px] sm:w-[260px] md:w-[330px] px-2 py-3 sm:py-5 sm:px-[15px] bg-block rounded-2xl flex flex-col items-center gap-[15px] sm:gap-[50px]">
<img :src="`https://www.yourgold.cz/api/public/file/${props.product?.cover_picture_uuid}.webp`" alt="Product Image"
class="max-h-[95px] sm:max-h-[180px] md:max-h-[205px] rounded-[5px]"
class="h-[95px] sm:h-[180px] md:h-[205px] rounded-[5px]"
onerror="this.onerror=null; this.src='/photo.svg';" />
<div class="flex flex-col justify-between h-full w-full gap-[7px] sSm:gap-[15px]">

View File

@ -1,91 +1,135 @@
<template>
<SectionShopPageCurrencyRatesBar class="mb-[25px] sm:mb-[55px] xl:mb-[75px]" />
<UiContainer>
<div class="flex flex-col gap-[25px] sm:gap-10 xl:flex-row">
<!-- button to open categories -->
<div class="xl:hidden flex items-center w-full">
<button @click="openCategories = !openCategories"
class="h-[40px] w-full cursor-pointer 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">
Otevřené kategorie a filtry
</button>
</div>
<SectionShopPageCurrencyRatesBar
class="mb-[25px] sm:mb-[55px] xl:mb-[75px]"
/>
<UiContainer>
<div class="flex flex-col gap-[25px] sm:gap-10 xl:flex-row">
<!-- button to open categories -->
<div class="xl:hidden flex items-center w-full">
<button
class="h-[40px] w-full cursor-pointer 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"
@click="openCategories = !openCategories"
>
Otevřené kategorie a filtry
</button>
</div>
<Transition>
<div v-if="openCategories" class="min-w-[250px] px-5 sm:p-0 xl:hidden">
<h1 class="font-bounded leading-[140%] font-bold text-[24px] mb-[25px]">
{{ $t("category") }}
</h1>
<div class="flex flex-col gap-[25px]">
<div>
<div v-if="categoriesList && categoriesList.length < 1" class="animate-pulse">
<div
class="flex items-center justify-between mt-4 text-white rounded-lg cursor-pointer xl:pr-24">
<div class="w-32 h-4 bg-gray-200 rounded"></div>
<div class="w-4 h-4 bg-gray-200 rounded-full"></div>
</div>
</div>
<CategoryTree :data="categoriesList" @change-category="changeCategory($event)"
:active="categoryId" />
</div>
<div>
<p class="mb-[25px] text-lg font-extrabold text-black dark:text-white">
{{ $t("filtered_by") }}
</p>
<div v-for="(item, itemIndex) in filters" :key="itemIndex"
:class="['mb-[30px]', visibleFeatures[item.feature] && 'border-b border-block pb-2']">
<span
class="flex justify-between items-center font-bold cursor-pointer mb-[25px] text-base"
@click="toggleFeature(item.feature)">
{{ item.feature }}
<span :class="[visibleFeatures[item.feature] && 'rotate-180', 'transition-all']"><i
class="iconify i-lucide:chevron-down text-button shrink-0 size-6 ms-auto"></i></span>
</span>
<ul v-show="visibleFeatures[item.feature]" class="flex flex-col gap-5">
<li v-for="filter in item.feature_values" :key="filter.value_id"
class="flex items-center gap-[10px] cursor-pointer">
<input :id="`${filter.value_id}`" :value="`${filter.parent}.${filter.value_id}`"
v-model="selectedFilters" type="checkbox"
class="border-button !bg-inherit" />
<label :for="`${filter.value_id}`"
class="cursor-pointer flex items-center justify-between w-full text-base">
<span>{{ filter.value }}</span>
<span>12</span>
</label>
</li>
</ul>
</div>
</div>
</div>
<Transition>
<div v-if="openCategories" class="min-w-[250px] px-5 sm:p-0 xl:hidden">
<h1
class="font-bounded leading-[140%] font-bold text-[24px] mb-[25px]"
>
{{ $t("category") }}
</h1>
<div class="flex flex-col gap-[25px]">
<div>
<div
v-if="categoriesList && categoriesList.length < 1"
class="animate-pulse"
>
<div
class="flex items-center justify-between mt-4 text-white rounded-lg cursor-pointer xl:pr-24"
>
<div class="w-32 h-4 bg-gray-200 rounded" />
<div class="w-4 h-4 bg-gray-200 rounded-full" />
</div>
</Transition>
</div>
<CategoryTree
:data="categoriesList"
:active="categoryId"
@change-category="changeCategory($event)"
/>
</div>
<div>
<p
class="mb-[25px] text-lg font-extrabold text-black dark:text-white"
>
{{ $t("filtered_by") }}
</p>
<!-- categories -->
<div class="min-w-[250px] hidden xl:block">
<h1 class="font-bounded leading-[140%] font-bold text-[40px] mb-[55px]">
{{ $t("category") }}
</h1>
<div class="flex flex-col gap-12">
<div>
<div
v-for="(item, itemIndex) in filters"
:key="itemIndex"
:class="[
'mb-[30px]',
visibleFeatures[item.feature] && 'border-b border-block pb-2',
]"
>
<span
class="flex justify-between items-center font-bold cursor-pointer mb-[25px] text-base"
@click="toggleFeature(item.feature)"
>
{{ item.feature }}
<span
:class="[
visibleFeatures[item.feature] && 'rotate-180',
'transition-all',
]"
><i
class="iconify i-lucide:chevron-down text-button shrink-0 size-6 ms-auto"
/></span>
</span>
<ul
v-show="visibleFeatures[item.feature]"
class="flex flex-col gap-5"
>
<li
v-for="filter in item.feature_values"
:key="filter.value_id"
class="flex items-center gap-[10px] cursor-pointer"
>
<input
:id="`${filter.value_id}`"
v-model="selectedFilters"
:value="`${filter.parent}.${filter.value_id}`"
type="checkbox"
class="border-button !bg-inherit"
/>
<label
:for="`${filter.value_id}`"
class="cursor-pointer flex items-center justify-between w-full text-base"
>
<span>{{ filter.value }}</span>
<span>12</span>
</label>
</li>
</ul>
</div>
</div>
</div>
</div>
</Transition>
<div v-if="categoriesList && categoriesList.length < 1" class="animate-pulse">
<div
class="flex items-center justify-between mt-4 text-white rounded-lg cursor-pointer xl:pr-24">
<div class="w-32 h-4 bg-gray-200 rounded"></div>
<div class="w-4 h-4 bg-gray-200 rounded-full"></div>
</div>
</div>
<CategoryTree :data="categoriesList" @change-category="changeCategory($event)"
:active="categoryId" />
</div>
<div>
<p class="mb-10 text-2xl font-extrabold text-black dark:text-white">
{{ $t("filtered_by") }}
</p>
<!-- categories -->
<div class="min-w-[250px] hidden xl:block">
<h1 class="font-bounded leading-[140%] font-bold text-[40px] mb-[55px]">
{{ $t("category") }}
</h1>
<div class="flex flex-col gap-12">
<div>
<div
v-if="categoriesList && categoriesList.length < 1"
class="animate-pulse"
>
<div
class="flex items-center justify-between mt-4 text-white rounded-lg cursor-pointer xl:pr-24"
>
<div class="w-32 h-4 bg-gray-200 rounded" />
<div class="w-4 h-4 bg-gray-200 rounded-full" />
</div>
</div>
<CategoryTree
:data="categoriesList"
:active="categoryId"
@change-category="changeCategory($event)"
/>
</div>
<div>
<p class="mb-10 text-2xl font-extrabold text-black dark:text-white">
{{ $t("filtered_by") }}
</p>
<!-- <div v-if="filters.length < 1" class="mb-8 text-white animate-pulse">
<!-- <div v-if="filters.length < 1" class="mb-8 text-white animate-pulse">
<div v-for="i in 5"
class="mt-10 flex justify-between font-bold text-black cursor-pointer 2xl:pr-24 dark:text-gray">
<div class="w-32 h-4 bg-gray-200 rounded"></div>
@ -93,110 +137,162 @@
</div>
</div> -->
<div v-for="(item, itemIndex) in filters" :key="itemIndex"
:class="['mb-[30px]', visibleFeatures[item.feature] && 'border-b border-block pb-2']">
<span class="flex justify-between items-center font-bold cursor-pointer mb-[25px]"
@click="toggleFeature(item.feature)">
{{ item.feature }}
<span :class="[visibleFeatures[item.feature] && 'rotate-180', 'transition-all']"><i
class="iconify i-lucide:chevron-down text-button shrink-0 size-6 ms-auto"></i></span>
</span>
<ul v-show="visibleFeatures[item.feature]" class="flex flex-col gap-5">
<li v-for="filter in item.feature_values" :key="filter.value_id"
class="flex items-center gap-[10px] cursor-pointer">
<!-- <input :id="`${filter.value_id}`" :value="`${filter.parent}.${filter.value_id}`"
<div
v-for="(item, itemIndex) in filters"
:key="itemIndex"
:class="[
'mb-[30px]',
visibleFeatures[item.feature] && 'border-b border-block pb-2',
]"
>
<span
class="flex justify-between items-center font-bold cursor-pointer mb-[25px]"
@click="toggleFeature(item.feature)"
>
{{ item.feature }}
<span
:class="[
visibleFeatures[item.feature] && 'rotate-180',
'transition-all',
]"
><i
class="iconify i-lucide:chevron-down text-button shrink-0 size-6 ms-auto"
/></span>
</span>
<ul
v-show="visibleFeatures[item.feature]"
class="flex flex-col gap-5"
>
<li
v-for="filter in item.feature_values"
:key="filter.value_id"
class="flex items-center gap-[10px] cursor-pointer"
>
<!-- <input :id="`${filter.value_id}`" :value="`${filter.parent}.${filter.value_id}`"
v-model="selectedFilters" type="checkbox" class="border-button !bg-inherit" />
<label :for="`${filter.value_id}`" class="cursor-pointer">{{ filter.value }}</label> -->
<input :id="`${filter.value_id}`" :value="`${filter.parent}.${filter.value_id}`"
v-model="selectedFilters" type="checkbox" class="border-button !bg-inherit" />
<label :for="`${filter.value_id}`"
class="cursor-pointer flex items-center justify-between w-full">
<span>{{ filter.value }}</span>
<span>12</span>
</label>
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="w-full space-y-10">
<!-- pop-up -->
<div v-if="isInfo"
class="w-full xl:w-[70%] mx-auto border-y border-block py-[15px] sm:p-[30px] flex gap-[55px] relative">
<UButton @click="closeElement()" size="xl" icon="i-lucide-x" variant="ghost"
class="p-0 absolute right-0 top-2 sm:right-2 sm:top-2 cursor-pointer text-button font-light hover:bg-inherit hover:text-button-hover" />
<div class="flex flex-col sm:flex-row gap-[25px]">
<div class="flex flex-col justify-between gap-[25px]">
<h4 class="font-inter text-lg sm:text-[24px] leading-[150%] md:leading-[120%] font-bold">
{{ component.front_section_lang[0].data.title }}
</h4>
<p>{{ component.front_section_lang[0].data.description }}</p>
</div>
<img class="max-w-[150px] mx-auto" :src="`/api/public/file/${component.img[0]}_m.webp')`" />
</div>
</div>
<div v-if="products.length < 1" class="grid gap-12 pt-32 pb-16 md:grid-cols-2 2xl:grid-cols-3">
<TheProductSkeleton v-for="index in 6"></TheProductSkeleton>
</div>
<!-- products -->
<div v-else ref="loadingElement" class="flex flex-wrap justify-center gap-5 sm:gap-10">
<Product v-for="product in products" :key="product.id" :product="product" />
</div>
<div v-if="reachedEnd" class="w-full flex justify-center">
<p>
{{ $t("FrontTranslations", "You reached end of the list.") }}
</p>
</div>
<input
:id="`${filter.value_id}`"
v-model="selectedFilters"
:value="`${filter.parent}.${filter.value_id}`"
type="checkbox"
class="border-button !bg-inherit"
/>
<label
:for="`${filter.value_id}`"
class="cursor-pointer flex items-center justify-between w-full"
>
<span>{{ filter.value }}</span>
<span>12</span>
</label>
</li>
</ul>
</div>
</div>
</div>
</UiContainer>
</div>
<div class="w-full space-y-10">
<!-- pop-up -->
<div
v-if="isInfo"
class="w-full xl:w-[70%] mx-auto border-y border-block py-[15px] sm:p-[30px] flex gap-[55px] relative"
>
<UButton
variant="ghost"
class="p-0 absolute right-0 top-2 sm:right-2 sm:top-2 cursor-pointer text-button font-light hover:bg-inherit hover:text-button-hover"
size="xl"
icon="i-lucide-x"
@click="closeElement()"
/>
<div class="flex flex-col sm:flex-row gap-[25px]">
<div class="flex flex-col justify-between gap-[25px]">
<h4
class="font-inter text-lg sm:text-[24px] leading-[150%] md:leading-[120%] font-bold"
>
{{ component.front_section_lang[0].data.title }}
</h4>
<p>{{ component.front_section_lang[0].data.description }}</p>
</div>
<img
class="max-w-[150px] mx-auto"
:src="`/api/public/file/${component.img[0]}_m.webp')`"
/>
</div>
</div>
<div
v-if="products.length < 1"
class="grid gap-12 pt-32 pb-16 md:grid-cols-2 2xl:grid-cols-3"
>
<TheProductSkeleton v-for="index in 6" :key="index" />
</div>
<!-- products -->
<div
v-else
ref="loadingElement"
class="flex flex-wrap justify-center gap-5 sm:gap-10"
>
<Product
v-for="product in products"
:key="product.id"
:product="product"
/>
</div>
<div v-if="reachedEnd" class="w-full flex justify-center">
<p>
{{ $t("FrontTranslations", "You reached end of the list.") }}
</p>
</div>
</div>
</div>
</UiContainer>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Product from "./Product.vue";
import type { Feature, GenericResponse, GenericResponseChildren, GenericResponseItems, ProductType } from "~/types";
import type {
Feature,
GenericResponse,
GenericResponseChildren,
GenericResponseItems,
ProductType,
} from "~/types";
import CategoryTree from "./CategoryTree.vue";
const { $session } = useNuxtApp();
watch(() => $session.cookieData, async () => await getProducts(), { deep: true });
const props = defineProps<{
component: {
id: number
name: string
img: string[]
component_name: string
is_no_lang: boolean
page_name: string
front_section_lang: {
data: {
title: string;
description: string
}
id_front_section: number
id_lang: number
}[]
}
watch(
() => $session.cookieData,
async () => await getProducts(),
{ deep: true }
);
defineProps<{
component: {
id: number;
name: string;
img: string[];
component_name: string;
is_no_lang: boolean;
page_name: string;
front_section_lang: {
data: {
title: string;
description: string;
};
id_front_section: number;
id_lang: number;
}[];
};
}>();
const openCategories = ref(false);
const isInfo = ref<boolean>(true);
const selectedFilters = ref<any>([]);
const categoryId = ref<number>(1);
const itemsCount = ref(0);
const loading = ref(false);
const reachedEnd = ref(false);
@ -209,249 +305,253 @@ const maxElements = ref(0);
const products = ref([] as ProductType[]);
async function getProducts() {
try {
const { data } = await useMyFetch<GenericResponseItems<ProductType[]>>(
`/api/public/products/category/${categoryId.value}?p=${page.value}&elems=${elems.value}`,
{
headers: {
"Content-Type": "application/json",
},
onErrorOccured: (_, status) => {
throw new Error(`HTTP error: ${status}`);
},
}
);
try {
const { data } = await useMyFetch<GenericResponseItems<ProductType[]>>(
`/api/public/products/category/${categoryId.value}?p=${page.value}&elems=${elems.value}`,
{
headers: {
"Content-Type": "application/json",
},
onErrorOccured: (_, status) => {
throw new Error(`HTTP error: ${status}`);
},
}
);
products.value = data.items;
maxElements.value = data.items_count + 1;
} catch (error) {
console.error("getProducts error:", error);
}
products.value = data.items;
maxElements.value = data.items_count + 1;
} catch (error) {
console.error("getProducts error:", error);
}
}
const filters = ref([] as Feature[]);
async function getCategory() {
try {
const { data } = await useMyFetch<GenericResponse<object>>(
`/api/public/products/category/1/classification`,
{
headers: {
"Content-Type": "application/json",
},
onErrorOccured: (_, status) => {
throw new Error(`HTTP error: ${status}`);
},
}
);
try {
const { data } = await useMyFetch<GenericResponse<object>>(
`/api/public/products/category/1/classification`,
{
headers: {
"Content-Type": "application/json",
},
onErrorOccured: (_, status) => {
throw new Error(`HTTP error: ${status}`);
},
}
);
filters.value = data as Feature[];
filters.value.forEach((el) => {
const parentId = el.feature_id;
el.feature_values.forEach((el) => {
el.parent = parentId;
});
});
} catch (error) {
console.error("getCategory error:", error);
}
filters.value = data as Feature[];
filters.value.forEach((el: Feature) => {
const parentId = el.feature_id;
el.feature_values.forEach((el) => {
el.parent = parentId;
});
});
} catch (error) {
console.error("getCategory error:", error);
}
}
const categoriesList = ref();
async function getCategoryTree() {
try {
const { data } = await useMyFetch<GenericResponseChildren<ProductType[]>>(
`/api/public/categories/tree`,
{
headers: {
"Content-Type": "application/json",
},
onErrorOccured: (_, status) => {
throw new Error(`HTTP error: ${status}`);
},
}
);
try {
const { data } = await useMyFetch<GenericResponseChildren<ProductType[]>>(
`/api/public/categories/tree`,
{
headers: {
"Content-Type": "application/json",
},
onErrorOccured: (_, status) => {
throw new Error(`HTTP error: ${status}`);
},
}
);
categoriesList.value = data.children;
} catch (error) {
console.error("getCategory error:", error);
}
categoriesList.value = data.children;
} catch (error) {
console.error("getCategory error:", error);
}
}
getProducts()
getCategory()
getCategoryTree()
getProducts();
getCategory();
getCategoryTree();
const closeElement = () => {
isInfo.value = false;
isInfo.value = false;
};
onMounted(() => {
window.addEventListener("scroll", scrollEvent);
window.addEventListener("scroll", scrollEvent);
});
onUnmounted(() => {
window.removeEventListener("scroll", scrollEvent);
window.removeEventListener("scroll", scrollEvent);
});
async function scrollEvent(e: Event) {
let maxScrollY = window.scrollY || document.documentElement.scrollHeight - document.documentElement.clientHeight;
async function scrollEvent() {
const maxScrollY =
window.scrollY ||
document.documentElement.scrollHeight -
document.documentElement.clientHeight;
if (window.scrollY >= maxScrollY - 500 && !reachedEnd.value && !loading.value) {
loading.value = true;
await loadMoreProducts();
loading.value = false;
}
if (
window.scrollY >= maxScrollY - 500 &&
!reachedEnd.value &&
!loading.value
) {
loading.value = true;
await loadMoreProducts();
loading.value = false;
}
}
filters.value.forEach((item) => {
visibleFeatures[item.feature] = false;
visibleFeatures[item.feature] = false;
});
const visibleFeatures = reactive<any>({});
function toggleFeature(feature: any) {
if (visibleFeatures.hasOwnProperty(feature)) {
visibleFeatures[feature] = !visibleFeatures[feature];
} else {
visibleFeatures[feature] = true;
}
const visibleFeatures = reactive<Record<string, boolean>>({});
function toggleFeature(feature: string) {
if (feature in visibleFeatures) {
visibleFeatures[feature] = !visibleFeatures[feature];
} else {
visibleFeatures[feature] = true;
}
}
class FilteredQueryString extends URLSearchParams {
override append(name: string, value: string): void {
if (value == null) {
return;
}
super.append(name, value);
override append(name: string, value: string): void {
if (value == null) {
return;
}
super.append(name, value);
}
}
async function loadMoreProducts() {
let qParams = new FilteredQueryString();
const qParams = new FilteredQueryString();
page.value = page.value + 1;
page.value = page.value + 1;
qParams.append("p", `${page.value}`);
qParams.append("elems", `${elems.value}`);
qParams.append("features", selectedFilters.value.length > 0 ? selectedFilters.value : null);
qParams.append("p", `${page.value}`);
qParams.append("elems", `${elems.value}`);
qParams.append(
"features",
selectedFilters.value.length > 0 ? selectedFilters.value : null
);
try {
const { data } = await useMyFetch<GenericResponseItems<ProductType[]>>(
`/api/public/products/category/${categoryId.value}?${qParams.toString()}`,
{
headers: {
"Content-Type": "application/json",
},
onErrorOccured: (_, status) => {
throw new Error(`HTTP error: ${status}`);
},
}
);
try {
const { data } = await useMyFetch<GenericResponseItems<ProductType[]>>(
`/api/public/products/category/${categoryId.value}?${qParams.toString()}`,
{
headers: {
"Content-Type": "application/json",
},
onErrorOccured: (_, status) => {
throw new Error(`HTTP error: ${status}`);
},
}
);
maxElements.value = data.items_count;
maxElements.value = data.items_count;
if (data.items) {
products.value.push(...(data.items as ProductType[]));
} else {
reachedEnd.value = true;
}
if (products.value.length >= maxElements.value) {
reachedEnd.value = true;
}
} catch (error) {
console.error("getCategory error:", error);
if (data.items) {
products.value.push(...(data.items as ProductType[]));
} else {
reachedEnd.value = true;
}
if (products.value.length >= maxElements.value) {
reachedEnd.value = true;
}
} catch (error) {
console.error("getCategory error:", error);
}
}
const changeCategory = (item: any) => {
categoryId.value = item.id;
categoryId.value = item.id;
};
watch(selectedFilters, async (newQuestion) => {
if (newQuestion) {
page.value = 1;
reachedEnd.value = false;
loadingElement.value?.scrollIntoView();
watch(selectedFilters, async (newQuestion: string) => {
if (newQuestion) {
page.value = 1;
reachedEnd.value = false;
loadingElement.value?.scrollIntoView();
let qParams = new FilteredQueryString();
const qParams = new FilteredQueryString();
qParams.append("p", `${page.value}`);
qParams.append("elems", `${elems.value}`);
qParams.append("features", selectedFilters.value.length > 0 ? selectedFilters.value : null);
qParams.append("p", `${page.value}`);
qParams.append("elems", `${elems.value}`);
qParams.append(
"features",
selectedFilters.value.length > 0 ? selectedFilters.value : null
);
try {
const { data } = await useMyFetch<GenericResponseItems<ProductType[]>>(
`/api/public/products/category/1?${qParams.toString()}`,
{
headers: {
"Content-Type": "application/json",
},
onErrorOccured: (_, status) => {
throw new Error(`HTTP error: ${status}`);
},
}
);
products.value = data.items;
maxElements.value = data.items_count;
} catch (error) {
console.error("selectedFilters error:", error);
try {
const { data } = await useMyFetch<GenericResponseItems<ProductType[]>>(
`/api/public/products/category/1?${qParams.toString()}`,
{
headers: {
"Content-Type": "application/json",
},
onErrorOccured: (_, status) => {
throw new Error(`HTTP error: ${status}`);
},
}
);
products.value = data.items;
maxElements.value = data.items_count;
} catch (error) {
console.error("selectedFilters error:", error);
}
}
});
watch(categoryId, async (newQuestion) => {
if (newQuestion) {
page.value = 1;
reachedEnd.value = false;
loadingElement.value?.scrollIntoView();
watch(categoryId, async (newCategoryId) => {
if (newCategoryId) {
page.value = 1;
reachedEnd.value = false;
loadingElement.value?.scrollIntoView();
let qParams = new FilteredQueryString();
const qParams = new FilteredQueryString();
qParams.append("p", `${page.value}`);
qParams.append("elems", `${elems.value}`);
qParams.append(
"features",
selectedFilters.value.length > 0 ? selectedFilters.value : null
);
qParams.append("p", `${page.value}`);
qParams.append("elems", `${elems.value}`);
qParams.append("features", selectedFilters.value.length > 0 ? selectedFilters.value : null);
try {
const { data } = await useMyFetch<GenericResponseItems<ProductType[]>>(
`api/public/products/category/${categoryId.value}?${qParams.toString()}`,
{
headers: {
"Content-Type": "application/json",
},
onErrorOccured: (_, status) => {
throw new Error(`HTTP error: ${status}`);
},
}
);
products.value = data.items;
maxElements.value = data.items_count;
} catch (error) {
console.error("getCategory error:", error);
try {
const { data } = await useMyFetch<GenericResponseItems<ProductType[]>>(
`api/public/products/category/${newCategoryId}?${qParams.toString()}`,
{
headers: { "Content-Type": "application/json" },
onErrorOccured: (_, status) => {
throw new Error(`HTTP error: ${status}`);
},
}
);
products.value = data.items;
maxElements.value = data.items_count;
} catch (error) {
console.error("getCategory error:", error);
}
}
});
</script>
<style scoped>
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
opacity: 0;
}
</style>
</style>