merage with frontend and contact us model

This commit is contained in:
Parsa Nazer
2025-04-22 02:43:57 +03:30
15 changed files with 326 additions and 170 deletions
+5 -2
View File
@@ -150,9 +150,12 @@ watch(
</div> </div>
</div> </div>
<span class="font-semibold typo-sub-h-sm lg:typo-sub-h-xl text-black"> <NuxtLink
:to="`product/${data.product.id}`"
class="font-semibold typo-sub-h-sm lg:typo-sub-h-xl text-black underline underline-offset-2"
>
{{ data.product.title }} {{ data.product.title }}
</span> </NuxtLink>
<div class="flex items-center justify-start gap-1.5"> <div class="flex items-center justify-start gap-1.5">
<div <div
+106 -30
View File
@@ -12,11 +12,9 @@ const {} = toRefs(props);
</script> </script>
<template> <template>
<div class="relative w-full flex flex-col justify-center min-h-[450px] h-svh"> <div class="relative w-full flex flex-col justify-center py-32 lg:py-48">
<div class="flex-col-center gap-6 mb-24 sm:mb-32 container"> <div class="flex-col-center gap-6 mb-24 sm:mb-32 container">
<span class="typo-h-6 max-sm:text-xl md:typo-h-5 lg:typo-h-4 text-black"> <span class="typo-h-6 max-sm:text-xl md:typo-h-5 lg:typo-h-4 text-black"> مجله در ستون و سطرآنچ </span>
مجله در ستون و سطرآنچ
</span>
<p class="text-slate-500 text-center max-w-[750px] typo-p-sm md:typo-p-lg xl:typo-p-xl"> <p class="text-slate-500 text-center max-w-[750px] typo-p-sm md:typo-p-lg xl:typo-p-xl">
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. چاپگرها و لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. چاپگرها و
متون بلکه روزنامه و مجله در ستون و سطرآنچنان که متون بلکه روزنامه و مجله در ستون و سطرآنچنان که
@@ -30,13 +28,19 @@ const {} = toRefs(props);
<div class="text-[30px] lg:text-[40px] text-white whitespace-nowrap font-semibold opacity-85"> <div class="text-[30px] lg:text-[40px] text-white whitespace-nowrap font-semibold opacity-85">
HEYMLZ HEYMLZ
</div> </div>
<NuxtImg src="/img/heymlz/heymlz-logo.png" class="h-[25px] sm:h-[45px] invert opacity-85" /> <NuxtImg
src="/img/heymlz/heymlz-logo.png"
class="h-[25px] sm:h-[45px] invert opacity-85"
/>
</template> </template>
<template v-for="i in 10"> <template v-for="i in 10">
<div class="text-[30px] lg:text-[40px] text-white whitespace-nowrap font-semibold opacity-85"> <div class="text-[30px] lg:text-[40px] text-white whitespace-nowrap font-semibold opacity-85">
HEYMLZ HEYMLZ
</div> </div>
<NuxtImg src="/img/heymlz/heymlz-logo.png" class="h-[25px] sm:h-[45px] invert opacity-85" /> <NuxtImg
src="/img/heymlz/heymlz-logo.png"
class="h-[25px] sm:h-[45px] invert opacity-85"
/>
</template> </template>
</div> </div>
</div> </div>
@@ -46,32 +50,104 @@ const {} = toRefs(props);
class="bg-slate-100/70 flex items-center pr-20 gap-12 sm:gap-20 w-max animate-marquee h-[90px] sm:h-[140px]" class="bg-slate-100/70 flex items-center pr-20 gap-12 sm:gap-20 w-max animate-marquee h-[90px] sm:h-[140px]"
> >
<template v-for="i in 1"> <template v-for="i in 1">
<NuxtImg src="/img/brands/brand-1.png" class="h-[25px] sm:h-[45px]" /> <NuxtImg
<NuxtImg src="/img/brands/brand-2.png" class="h-[25px] sm:h-[45px]" /> src="/img/brands/brand-1.png"
<NuxtImg src="/img/brands/brand-3.png" class="h-[25px] sm:h-[45px]" /> class="h-[25px] sm:h-[45px]"
<NuxtImg src="/img/brands/brand-4.png" class="h-[25px] sm:h-[45px]" /> />
<NuxtImg src="/img/brands/brand-5.png" class="h-[25px] sm:h-[45px]" /> <NuxtImg
<NuxtImg src="/img/brands/brand-6.png" class="h-[25px] sm:h-[45px]" /> src="/img/brands/brand-2.png"
<NuxtImg src="/img/brands/brand-1.png" class="h-[25px] sm:h-[45px]" /> class="h-[25px] sm:h-[45px]"
<NuxtImg src="/img/brands/brand-2.png" class="h-[25px] sm:h-[45px]" /> />
<NuxtImg src="/img/brands/brand-3.png" class="h-[25px] sm:h-[45px]" /> <NuxtImg
<NuxtImg src="/img/brands/brand-4.png" class="h-[25px] sm:h-[45px]" /> src="/img/brands/brand-3.png"
<NuxtImg src="/img/brands/brand-5.png" class="h-[25px] sm:h-[45px]" /> class="h-[25px] sm:h-[45px]"
<NuxtImg src="/img/brands/brand-6.png" class="h-[25px] sm:h-[45px]" /> />
<NuxtImg
src="/img/brands/brand-4.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-5.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-6.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-1.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-2.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-3.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-4.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-5.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-6.png"
class="h-[25px] sm:h-[45px]"
/>
</template> </template>
<template v-for="i in 1"> <template v-for="i in 1">
<NuxtImg src="/img/brands/brand-1.png" class="h-[25px] sm:h-[45px]" /> <NuxtImg
<NuxtImg src="/img/brands/brand-2.png" class="h-[25px] sm:h-[45px]" /> src="/img/brands/brand-1.png"
<NuxtImg src="/img/brands/brand-3.png" class="h-[25px] sm:h-[45px]" /> class="h-[25px] sm:h-[45px]"
<NuxtImg src="/img/brands/brand-4.png" class="h-[25px] sm:h-[45px]" /> />
<NuxtImg src="/img/brands/brand-5.png" class="h-[25px] sm:h-[45px]" /> <NuxtImg
<NuxtImg src="/img/brands/brand-6.png" class="h-[25px] sm:h-[45px]" /> src="/img/brands/brand-2.png"
<NuxtImg src="/img/brands/brand-1.png" class="h-[25px] sm:h-[45px]" /> class="h-[25px] sm:h-[45px]"
<NuxtImg src="/img/brands/brand-2.png" class="h-[25px] sm:h-[45px]" /> />
<NuxtImg src="/img/brands/brand-3.png" class="h-[25px] sm:h-[45px]" /> <NuxtImg
<NuxtImg src="/img/brands/brand-4.png" class="h-[25px] sm:h-[45px]" /> src="/img/brands/brand-3.png"
<NuxtImg src="/img/brands/brand-5.png" class="h-[25px] sm:h-[45px]" /> class="h-[25px] sm:h-[45px]"
<NuxtImg src="/img/brands/brand-6.png" class="h-[25px] sm:h-[45px]" /> />
<NuxtImg
src="/img/brands/brand-4.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-5.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-6.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-1.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-2.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-3.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-4.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-5.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-6.png"
class="h-[25px] sm:h-[45px]"
/>
</template> </template>
</div> </div>
</div> </div>
+10 -4
View File
@@ -18,10 +18,16 @@ withDefaults(defineProps<Props>(), {
<template> <template>
<div class="w-full flex flex-col gap-2"> <div class="w-full flex flex-col gap-2">
<div class="flex items-center gap-1 ps-2"> <div class="flex items-center gap-1 ps-2">
<label :for="id" class="typo-label-xs lg:typo-label-sm">{{ <label
label :for="id"
}}</label> class="typo-label-xs lg:typo-label-sm"
<span v-if="!!required && required" class="text-danger-600">*</span> >{{ label }}</label
>
<span
v-if="!!required && required"
class="text-danger-600"
>*</span
>
</div> </div>
<slot /> <slot />
<div <div
+1 -1
View File
@@ -123,7 +123,7 @@ const isHomePage = computed(() => route.path === "/");
<div class="header-navbar-item flex items-center justify-end h-full"> <div class="header-navbar-item flex items-center justify-end h-full">
<NuxtImg <NuxtImg
src="/logo/logo-row.png" src="/img/heymlz/heymlz-logomotion.gif"
class="h-2/3" class="h-2/3"
/> />
</div> </div>
+125 -48
View File
@@ -20,6 +20,8 @@ defineProps<Props>();
const { token } = useAuth(); const { token } = useAuth();
const { data: account } = useGetAccount(); const { data: account } = useGetAccount();
const route = useRoute();
// emit // emit
const emit = defineEmits(["update:modelValue"]); const emit = defineEmits(["update:modelValue"]);
@@ -29,13 +31,22 @@ const emit = defineEmits(["update:modelValue"]);
const closeSideDrawer = () => { const closeSideDrawer = () => {
emit("update:modelValue", false); emit("update:modelValue", false);
}; };
// watch
watch(
() => route.fullPath,
() => {
closeSideDrawer();
}
);
</script> </script>
<template> <template>
<Transition name="fade"> <Transition name="fade">
<div <div
v-if="modelValue" v-if="modelValue"
class="md:hidden fixed inset-0 h-svh z-999 size-full bg-black/50 cursor-pointer" class="md:hidden fixed inset-0 min-h-svh z-1001 size-full bg-black/50 cursor-pointer"
@click="closeSideDrawer" @click="closeSideDrawer"
/> />
</Transition> </Transition>
@@ -43,61 +54,127 @@ const closeSideDrawer = () => {
<div <div
@click.stop @click.stop
:class="modelValue ? 'translate-x-0' : 'translate-x-[100%]'" :class="modelValue ? 'translate-x-0' : 'translate-x-[100%]'"
class="md:hidden cursor-default flex top-0 right-0 fixed z-999 transition-all duration-500 rounded-e-xl flex-col bg-white w-[300px] h-full gap-8 pt-12" class="md:hidden cursor-default flex top-0 right-0 fixed z-1002 transition-all duration-500 flex-col bg-white w-[300px] h-full px-4"
> >
<div class="flex items-center flex-col justify-end gap-[1.5rem]"> <div class="flex items-center flex-col justify-end gap-2 border-b border-slate-200 py-5">
<Tooltip v-if="!!account && !!token" title="حساب کاربری"> <NuxtLink
<NuxtLink v-if="!!account && !!token"
:to="{ name: 'profile' }" :to="{ name: 'profile' }"
class="flex items-center justify-center" class="w-full flex items-center justify-between gap-3 p-2 transition-all"
> active-class="bg-black rounded-md text-white **:stroke-white"
<Avatar >
class="!size-7" <div class="flex items-center gap-3">
:src="account.profile_photo" <div class="size-5 flex-center">
:alt=" <Avatar
account.first_name && account.last_name class="!size-5"
? `${account.first_name.charAt( :src="account.profile_photo"
0 :alt="
)} ${account.last_name.charAt(0)}` account.first_name && account.last_name
: 'بدون نام کاربری' ? `${account.first_name.charAt(0)} ${account.last_name.charAt(0)}`
" : 'بدون نام کاربری'
/> "
</NuxtLink> />
</Tooltip> </div>
<Tooltip v-else title="ورود"> <span class="text-xs"> {{ account.first_name }} {{ account.last_name }} </span>
<NuxtLink to="/signin" class="flex-center"> </div>
<Icon <Icon
name="ci:profile" name="bi:chevron-left"
size="24px" size="12"
class="**:stroke-black" class="**:stroke-black/50 opacity-70"
/> />
</NuxtLink> </NuxtLink>
</Tooltip>
<Tooltip title="محصولات"> <NuxtLink
<NuxtLink to="/products" class="flex-center"> v-else
<Icon to="/signin"
name="ci:search" class="w-full flex items-center justify-between gap-4 p-2 transition-all"
size="21px" active-class="bg-black rounded-md text-white **:stroke-white"
class="**:stroke-black" >
/> <div class="flex items-center gap-3">
</NuxtLink> <div class="size-5 flex-center">
</Tooltip> <Icon
<Tooltip title="سبد خرید"> name="ci:profile"
<NuxtLink to="/cart" class="flex-center"> size="18"
<Icon name="ci:cart" size="24px" class="**:stroke-black" /> class="**:stroke-black"
</NuxtLink> />
</Tooltip> </div>
<span class="text-xs"> ورود به حساب </span>
</div>
<Icon
name="bi:chevron-left"
size="12"
class="**:stroke-black/50 opacity-70"
/>
</NuxtLink>
<!--
<NuxtLink
to="/products"
class="w-full flex items-center justify-between gap-4 p-2"
active-class="bg-black rounded-md text-white **:stroke-white"
>
<div class="flex items-center gap-3">
<div class="size-5 flex-center">
<Icon
name="ci:search"
size="18"
class="**:stroke-black"
/>
</div>
<span class="text-xs"> جست و جو </span>
</div>
<Icon
name="bi:chevron-left"
size="12"
class="**:stroke-black/50 opacity-70"
/>
</NuxtLink> -->
<NuxtLink
to="/cart"
class="w-full flex items-center justify-between gap-4 p-2 transition-all"
active-class="bg-black rounded-md text-white **:stroke-white"
>
<div class="flex items-center gap-3">
<div class="size-5 flex-center">
<Icon
name="ci:cart"
size="19"
class="**:stroke-black"
/>
</div>
<span class="text-xs"> سبد خرید </span>
</div>
<Icon
name="bi:chevron-left"
size="12"
class="**:stroke-black/50 opacity-70"
/>
</NuxtLink>
</div> </div>
<nav <nav class="flex-center flex-col gap-2 typo-label-sm font-light text-black/80 py-5">
class="flex-center flex-col gap-[2.5rem] typo-label-sm font-light text-black/80"
>
<NuxtLink <NuxtLink
v-for="(link, index) in NAV_LINKS" v-for="(link, index) in NAV_LINKS"
:key="index" :key="index"
:to="link.path" :to="link.path"
class="w-full flex items-center justify-between gap-3 p-2 transition-all"
active-class="bg-black rounded-md text-white **:stroke-white"
> >
{{ link.title }} <div class="flex items-center gap-3">
<div class="size-5 flex-center">
<Icon
:name="link.icon"
size="18"
class="**:stroke-black"
/>
</div>
<span class="text-xs"> {{ link.title }}</span>
</div>
<Icon
name="bi:chevron-left"
size="12"
class="**:stroke-black/50 opacity-70"
/>
</NuxtLink> </NuxtLink>
</nav> </nav>
</div> </div>
+4 -4
View File
@@ -20,13 +20,13 @@ const onSwiper = (swiper: SwiperClass) => {
<template> <template>
<section <section
ref="sectionTarget" ref="sectionTarget"
class="flex flex-col justify-center gap-4 bg-black h-[110svh] sm:h-[150svh] relative overflow-hidden" class="flex flex-col justify-center gap-4 bg-black sm:min-h-[110svh] relative overflow-hidden shrink-0 py-24 lg:py-32"
> >
<div class="w-full relative translate-y-[-55px] sm:translate-y-[-130px] flex-center z-10 container"> <div class="w-full relative flex-center z-10 container">
<span class="text-white typo-h-6 md:typo-h-5 lg:typo-h-4"> دسته بندی ها </span> <span class="text-white typo-h-6 md:typo-h-5 lg:typo-h-4"> دسته بندی ها </span>
</div> </div>
<div class="w-full my-20 relative"> <div class="w-full mt-44 lg:mt-64 relative">
<NuxtImg <NuxtImg
class="aspect-square w-[240px] md:w-[300px] lg:w-[350px] translate-y-[-164px] md:translate-y-[-206px] lg:translate-y-[-240px] absolute left-1/2 -translate-x-1/2 z-10" class="aspect-square w-[240px] md:w-[300px] lg:w-[350px] translate-y-[-164px] md:translate-y-[-206px] lg:translate-y-[-240px] absolute left-1/2 -translate-x-1/2 z-10"
:style="{ :style="{
@@ -89,7 +89,7 @@ const onSwiper = (swiper: SwiperClass) => {
</div> </div>
</div> </div>
<div class="w-full flex justify-center items-center"> <div class="w-full flex justify-center items-center mt-14">
<NuxtLink to="/category"> <NuxtLink to="/category">
<Button <Button
variant="primary" variant="primary"
+10 -9
View File
@@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
// state // state
import useGetArticles from "~/composables/api/blog/useGetArticles"; import useGetArticles from "~/composables/api/blog/useGetArticles";
@@ -10,22 +9,24 @@ const { data: articles, suspense } = useGetArticles(page);
// ssr // ssr
await suspense(); await suspense();
</script> </script>
<template> <template>
<section class="mt-20 container"> <section class="container">
<div class="flex items-center justify-between mb-12 md:mb-20"> <div class="flex items-center justify-between mb-12 md:mb-20">
<span class="typo-h-6 max-sm:text-xl md:typo-h-5 lg:typo-h-4 text-black"> <span class="typo-h-6 max-sm:text-xl md:typo-h-5 lg:typo-h-4 text-black"> مقالات اخیر سایت </span>
مقالات اخیر سایت
</span>
<NuxtLink to="/articles"> <NuxtLink to="/articles">
<Button variant="primary" class="rounded-full max-sm:typo-label-sm max-sm:py-2" <Button
end-icon="ci:arrow-left"> variant="primary"
class="rounded-full max-sm:typo-label-sm max-sm:py-2"
end-icon="ci:arrow-left"
>
نمایش همه نمایش همه
</Button> </Button>
</NuxtLink> </NuxtLink>
</div> </div>
<ArticlesList :articles="[...articles!.results,...articles!.results,...articles!.results,...articles!.results,...articles!.results,...articles!.results]" /> <ArticlesList
:articles="[...articles!.results,...articles!.results,...articles!.results,...articles!.results,...articles!.results,...articles!.results]"
/>
</section> </section>
</template> </template>
+1 -1
View File
@@ -72,7 +72,7 @@ watch(
</script> </script>
<template> <template>
<div class="container mb-40 lg:mb-80 mt-20"> <div class="container mb-40 lg:mb-40 max-lg:mt-20 lg:-mt-32">
<div> <div>
<div class="flex flex-col items-center gap-3 mb-10 lg:mb-16"> <div class="flex flex-col items-center gap-3 mb-10 lg:mb-16">
<span class="typo-p-sm md:typo-p-md text-slate-500"> مقایسه محصولات </span> <span class="typo-p-sm md:typo-p-md text-slate-500"> مقایسه محصولات </span>
+10 -11
View File
@@ -85,14 +85,14 @@ onUnmounted(() => {
<template> <template>
<section <section
id="products-showcase-container" id="products-showcase-container"
class="perspective-midrange relative z-[99999]" class="perspective-midrange relative z-[999]"
> >
<div class="w-full h-[102svh] bg-black"> <div class="w-full min-h-[120svh] lg:min-h-[102svh] bg-black">
<NuxtLink <NuxtLink
v-for="slide in homeData!.show_case_slider" v-for="slide in homeData!.show_case_slider"
:key="slide.id" :key="slide.id"
:to="slide.link" :to="slide.link"
class="showcase-slide origin-bottom absolute size-full bg-transparent flex items-center justify-center" class="showcase-slide origin-bottom absolute size-full bg-transparent flex items-center justify-center max-lg:-mt-16 lg:mt-5"
> >
<NuxtImg <NuxtImg
class="w-[280px] xs:w-[350px] lg:w-[500px] xl:w-[650px] z-20 mb-30 sm:mb-20 lg:mb-30" class="w-[280px] xs:w-[350px] lg:w-[500px] xl:w-[650px] z-20 mb-30 sm:mb-20 lg:mb-30"
@@ -102,12 +102,8 @@ onUnmounted(() => {
}" }"
alt="" alt=""
/> />
<div <div class="flex flex-col items-center justify-center gap-6 text-center absolute z-20 mt-20">
class="flex flex-col items-center justify-center gap-6 text-center absolute z-20 mt-20" <span class="text-white typo-h-6 sm:typo-h-5 lg:typo-h-4 xl:typo-h-3">
>
<span
class="text-white typo-h-6 sm:typo-h-5 lg:typo-h-4 xl:typo-h-3"
>
{{ slide.title }} {{ slide.title }}
</span> </span>
<p <p
@@ -115,10 +111,13 @@ onUnmounted(() => {
> >
{{ slide.description }} {{ slide.description }}
</p> </p>
<NuxtLink :to="slide.link" class="relative"> <NuxtLink
:to="slide.link"
class="relative"
>
<NuxtImg <NuxtImg
src="/img/heymlz/heymlz-falling.gif" src="/img/heymlz/heymlz-falling.gif"
class="absolute top-[106px] sm:top-[105px] lg:top-[117px] left-1/2 -translate-1/2 w-[250px] drop-shadow-md" class="absolute top-[101px] sm:top-[100px] lg:top-[117px] left-1/2 -translate-1/2 w-[200px] lg:w-[250px] drop-shadow-md"
/> />
<Button <Button
variant="primary" variant="primary"
+4
View File
@@ -94,17 +94,21 @@ export const NAV_LINKS = [
{ {
title: "خانه", title: "خانه",
path: "/", path: "/",
icon: "ci:home",
}, },
{ {
title: "محصولات", title: "محصولات",
path: "/products", path: "/products",
icon: "ci:airdrop",
}, },
{ {
title: "دسته بندی ها", title: "دسته بندی ها",
path: "/category", path: "/category",
icon: "ci:delivery-boxes",
}, },
{ {
title: "ارتباط با ما", title: "ارتباط با ما",
path: "/contact-us", path: "/contact-us",
icon: "ci:call",
}, },
]; ];
+2 -2
View File
@@ -33,12 +33,12 @@ onMounted(() => {
<LoadingOverlay /> <LoadingOverlay />
<Hero class="mb-20 max-md:mt-[80px]" /> <Hero class="mb-20 max-md:mt-[80px]" />
<Preview /> <Preview />
<ProductsShowcase class="mb-40" /> <ProductsShowcase class="lg:mb-12" />
<ProductsGrid <ProductsGrid
title="محصولات پرفروش" title="محصولات پرفروش"
:products="[...homeData!.products,...homeData!.products]" :products="[...homeData!.products,...homeData!.products]"
/> />
<Categories class="mt-40" /> <Categories class="mt-12" />
<Brands /> <Brands />
<LatestStories class="mb-20" /> <LatestStories class="mb-20" />
</div> </div>
+27 -23
View File
@@ -1,9 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
// import // import
import useGetProducts, { import useGetProducts, { type GetProductsFilters } from "~/composables/api/products/useGetProducts";
type GetProductsFilters,
} from "~/composables/api/products/useGetProducts";
import { PRODUCT_RANGE } from "~/constants"; import { PRODUCT_RANGE } from "~/constants";
// state // state
@@ -59,12 +57,8 @@ watch(
<template> <template>
<div class="w-full container flex flex-col"> <div class="w-full container flex flex-col">
<div <div class="w-full flex flex-col lg:flex-row justify-end items-end py-[3.5rem] lg:py-[5rem] gap-10 lg:gap-5">
class="w-full flex flex-col lg:flex-row justify-end items-end py-[3.5rem] lg:py-[5rem] gap-10 lg:gap-5" <div class="flex flex-col items-center lg:items-start gap-[1rem] lg:gap-[1.5rem] text-black w-full">
>
<div
class="flex flex-col items-center lg:items-start gap-[1rem] lg:gap-[1.5rem] text-black w-full"
>
<!-- <div class="flex-center gap-[.75rem]"> <!-- <div class="flex-center gap-[.75rem]">
<span class="text-xs lg:text-sm">خانه</span> <span class="text-xs lg:text-sm">خانه</span>
<span class="text-xs lg:text-sm">/</span> <span class="text-xs lg:text-sm">/</span>
@@ -75,9 +69,7 @@ watch(
<h1 class="typo-h-5 lg:typo-h-4">لیست محصولات</h1> <h1 class="typo-h-5 lg:typo-h-4">لیست محصولات</h1>
</div> </div>
<div <div class="w-full flex items-center justify-between lg:justify-end gap-4">
class="w-full flex items-center justify-between lg:justify-end gap-4"
>
<Input <Input
placeholder="جست و جو محصول ..." placeholder="جست و جو محصول ..."
v-model="search" v-model="search"
@@ -96,9 +88,7 @@ watch(
<Suspense> <Suspense>
<FilterButton /> <FilterButton />
<template #fallback> <template #fallback>
<Skeleton <Skeleton class="!size-11 lg:!w-[10.35rem] lg:!h-[3.35rem] shrink-0 !rounded-xl" />
class="!size-11 lg:!w-[10.35rem] lg:!h-[3.35rem] shrink-0 !rounded-xl"
/>
</template> </template>
</Suspense> </Suspense>
</div> </div>
@@ -107,24 +97,35 @@ watch(
v-if="productsIsLoading" v-if="productsIsLoading"
class="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-y-8 gap-5 sm:gap-8 w-full" class="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-y-8 gap-5 sm:gap-8 w-full"
> >
<div class="w-full flex flex-col gap-3" v-for="i in 8" :key="i"> <div
class="w-full flex flex-col gap-3"
v-for="i in 8"
:key="i"
>
<Skeleton <Skeleton
v-for="i in 3" v-for="i in 3"
:key="i" :key="i"
class="w-full" class="w-full"
:class="{ :class="{
'!h-[11.75rem] lg:!h-[22.5rem] !rounded-2xl': i == 1, '!h-[11.75rem] lg:!h-[22.5rem] !rounded-2xl': i == 1,
'!h-[1.35rem] lg:!h-[1.5rem] !rounded-sm lg:!hidden': [ '!h-[1.35rem] lg:!h-[1.5rem] !rounded-sm lg:!hidden': [2, 3].includes(i),
2, 3,
].includes(i),
'!w-1/2': i == 2, '!w-1/2': i == 2,
}" }"
/> />
</div> </div>
</ul> </ul>
<div v-else class="w-full h-max"> <div
<div v-if="!products!.length" class="flex flex-grow w-full"> v-else
<Placeholder title="محصولی یافت نشد :(" icon="bi:search" /> class="w-full h-max"
>
<div
v-if="!products!.length"
class="flex flex-grow w-full"
>
<Placeholder
title="محصولی یافت نشد :("
icon="bi:search"
/>
</div> </div>
<ProductsGrid <ProductsGrid
:with-header="false" :with-header="false"
@@ -135,7 +136,10 @@ watch(
v-if="data && paginationData && data.count > 10" v-if="data && paginationData && data.count > 10"
class="w-full flex-center py-10" class="w-full flex-center py-10"
> >
<Pagination :items="paginationData" :total="data.count" /> <Pagination
:items="paginationData"
:total="data.count"
/>
</div> </div>
</div> </div>
</div> </div>
+20 -34
View File
@@ -39,10 +39,7 @@ const formRules = computed(() => {
return { return {
phone: { phone: {
required: helpers.withMessage("Phone is required", required), required: helpers.withMessage("Phone is required", required),
phoneValidator: helpers.withMessage( phoneValidator: helpers.withMessage("شماره تلفن وارد شده معتبر نمی باشد", helpers.regex(/^[1-9][0-9]{9}$/)),
"شماره تلفن وارد شده معتبر نمی باشد",
helpers.regex(/^[1-9][0-9]{9}$/)
),
}, },
}; };
}); });
@@ -63,11 +60,7 @@ const {
}); });
const { mutateAsync: sendOtp, isPending: sendOtpIsPending } = useOtp(); const { mutateAsync: sendOtp, isPending: sendOtpIsPending } = useOtp();
const { const { mutateAsync: signIn, isPending: signInIsPending, status: signInStatus } = useSignIn();
mutateAsync: signIn,
isPending: signInIsPending,
status: signInStatus,
} = useSignIn();
// computed // computed
@@ -152,9 +145,7 @@ const resetForm = () => {
<template> <template>
<div class="w-full flex h-svh items-center relative container"> <div class="w-full flex h-svh items-center relative container">
<div class="pattern -z-10 size-full fixed inset-0" /> <div class="pattern -z-10 size-full fixed inset-0" />
<div <div class="flex items-center justify-center flex-col size-full translate-y-[-100px]">
class="flex items-center justify-center flex-col size-full translate-y-[-100px]"
>
<img <img
class="aspect-square w-[250px] sm:w-[325px] translate-y-[90px] sm:translate-y-[120px] animate-fade-in" class="aspect-square w-[250px] sm:w-[325px] translate-y-[90px] sm:translate-y-[120px] animate-fade-in"
src="/img/heymlz/heymlz-signin.gif" src="/img/heymlz/heymlz-signin.gif"
@@ -166,11 +157,12 @@ const resetForm = () => {
<div <div
class="max-w-[600px] w-full p-6 h-[350px] sm:h-[400px] flex flex-col items-center bg-white border shadow-black/10 justify-center border-slate-300 rounded-3xl" class="max-w-[600px] w-full p-6 h-[350px] sm:h-[400px] flex flex-col items-center bg-white border shadow-black/10 justify-center border-slate-300 rounded-3xl"
> >
<h1 class="typo-h-6 sm:typo-h-5 mt-8"> <h1 class="typo-h-6 sm:typo-h-5 mt-8">شماره خود را وارد کنید</h1>
شماره خود را وارد کنید
</h1>
<form @submit.prevent class="max-w-[500px] w-full mt-12"> <form
@submit.prevent
class="max-w-[500px] w-full mt-12"
>
<Input <Input
v-if="!showOtp" v-if="!showOtp"
class="w-full tracking-[3px] persian-number" class="w-full tracking-[3px] persian-number"
@@ -186,9 +178,7 @@ const resetForm = () => {
name="twemoji:flag-iran" name="twemoji:flag-iran"
size="24" size="24"
/> />
<span class="text-slate-500 typo-label-sm"> <span class="text-slate-500 typo-label-sm"> +۹۸ </span>
+۹۸
</span>
</div> </div>
</template> </template>
</Input> </Input>
@@ -196,13 +186,7 @@ const resetForm = () => {
<OtpInput <OtpInput
v-else v-else
v-model="otpCode" v-model="otpCode"
:status=" :status="signInStatus === 'success' ? 'success' : signInStatus === 'error' ? 'error' : 'idle'"
signInStatus === 'success'
? 'success'
: signInStatus === 'error'
? 'error'
: 'idle'
"
:disabled="signInIsPending || sendOtpIsPending" :disabled="signInIsPending || sendOtpIsPending"
:autofocus="true" :autofocus="true"
@complete="handleLogin" @complete="handleLogin"
@@ -220,7 +204,10 @@ const resetForm = () => {
ارسال کد ارسال کد
</Button> </Button>
<div v-else class="flex items-center w-full gap-4 mt-4"> <div
v-else
class="flex items-center w-full gap-4 mt-4"
>
<Button <Button
class="rounded-full w-full mt-4 max-sm:h-[45px]" class="rounded-full w-full mt-4 max-sm:h-[45px]"
type="button" type="button"
@@ -235,11 +222,7 @@ const resetForm = () => {
type="submit" type="submit"
@click="resendOtp" @click="resendOtp"
:loading="signInIsPending || sendOtpIsPending" :loading="signInIsPending || sendOtpIsPending"
:disabled=" :disabled="signInIsPending || isResendOtpBlocked || sendOtpIsPending"
signInIsPending ||
isResendOtpBlocked ||
sendOtpIsPending
"
> >
ارسال مجدد کد ارسال مجدد کد
{{ isResendOtpBlocked ? otpBlockerTimePassed : "" }} {{ isResendOtpBlocked ? otpBlockerTimePassed : "" }}
@@ -250,8 +233,11 @@ const resetForm = () => {
to="/" to="/"
class="flex items-center gap-2 justify-center mt-6" class="flex items-center gap-2 justify-center mt-6"
> >
<span> بازگشت به فروشگاه </span> <Icon
<Icon name="ci:left-rotation" size="24" /> name="ci:left-rotation"
class="lg:text-xl"
/>
<span class="text-xs lg:text-sm"> بازگشت به فروشگاه </span>
</NuxtLink> </NuxtLink>
</form> </form>
</div> </div>
Binary file not shown.

After

Width:  |  Height:  |  Size: 648 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB