This commit is contained in:
Parsa Nazer
2025-02-06 21:35:50 +03:30
11 changed files with 283 additions and 119 deletions
+6
View File
@@ -266,3 +266,9 @@
padding-inline: 5rem;
}
}
@layer {
button {
@apply cursor-pointer;
}
}
+6 -4
View File
@@ -13,18 +13,20 @@ defineProps<Props>();
<template>
<AvatarRoot
class="bg-black inline-flex size-[45px] select-none items-center justify-center overflow-hidden rounded-full align-middle"
class="inline-flex size-[45px] select-none items-center justify-center rounded-full align-middle"
>
<AvatarImage
class="h-full w-full rounded-[inherit] object-cover"
class="h-full w-full rounded-full object-cover"
:src="src"
:alt="alt"
/>
<AvatarFallback
class="text-slate-300 leading-1 flex h-full w-full items-center justify-center bg-white text-sm font-medium"
class="flex-center size-full text-sm font-medium rounded-full"
:delay-ms="600"
>
{{ alt }}
<div class="size-full rounded-full flex-center">
<Icon name="ci:profile" size="16" class="**:stroke-black" />
</div>
</AvatarFallback>
</AvatarRoot>
</template>
+55
View File
@@ -0,0 +1,55 @@
<script setup lang="ts">
// types
type Props = {
items: DropdownItem[];
};
// props
defineProps<Props>();
// state
const toggleState = ref(false);
</script>
<template>
<DropdownMenuRoot v-model:open="toggleState" dir="rtl">
<DropdownMenuTrigger>
<slot name="trigger" />
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent
v-if="items"
class="min-w-[220px] w-full outline-none bg-white border border-gray-300 rounded-xl font-iran-yekan-x p-2 shadow-[0px_10px_38px_-10px_rgba(22,_23,_24,_0.35),_0px_10px_20px_-15px_rgba(22,_23,_24,_0.2)] will-change-[opacity,transform] data-[side=top]:animate-slide-down-fade data-[side=right]:animate-slide-left-fade data-[side=bottom]:animate-slide-up-fade data-[side=left]:animate-slide-right-fade z-10000"
:side-offset="5"
>
<DropdownMenuItem
v-for="(item, index) in items"
:key="index"
@click="item.onClick"
:class="
item.itemClass ? item.itemClass : 'hover:!text-black'
"
class="group text-xs leading-none text-black hover:!bg-gray-100 rounded-md flex items-center justify-between py-3 px-1.5 relative select-none outline-none data-[disabled]:opacity-50 data-[disabled]:pointer-events-none data-[highlighted]:bg-black data-[highlighted]:text-white"
>
<span>
{{ item.title }}
</span>
<Icon
:name="item.icon"
:class="item.iconClass"
size="16"
class="**:stroke-black"
/>
</DropdownMenuItem>
<DropdownMenuArrow class="fill-white stroke-gray-300" />
</DropdownMenuContent>
<slot v-else name="content" />
</DropdownMenuPortal>
</DropdownMenuRoot>
</template>
+51 -31
View File
@@ -1,4 +1,8 @@
<script setup lang="ts"></script>
<script setup lang="ts">
// imports
import { NAV_LINKS } from "~/constants";
</script>
<template>
<footer class="w-full bg-black flex-center">
@@ -7,15 +11,16 @@
class="h-full flex flex-col items-start justify-between pe-[5rem] py-[5rem]"
>
<div class="flex flex-col items-start gap-[1.5rem] w-full">
<span class="text-white typo-sub-h-xl"
>Subscribe to our newsletter
<span class="text-white typo-sub-h-xl font-light"
>از آخرین اخبار، نوشتهها، مقالات و تخفیفها با خبر شوید
😎
</span>
<div class="flex flex-col items-start gap-[.75rem] w-full">
<div
class="flex items-center justify-start gap-[.5rem] w-full"
>
<Input
placeholder="Enter your email"
placeholder="آدرس الکترونیکی شما"
class="bg-slate-950 border-slate-800 hover:border-slate-800 w-8/12"
/>
<Button
@@ -24,13 +29,16 @@
/>
</div>
<span class="text-slate-400 typo-p-sm">
By subscribing, you agree to our responsible data
use
با عضویت, شما با
<NuxtLink to="#" class="text-cyan-500 underline"
>قوانین و مقررات</NuxtLink
>
سایت موافقت می کنید.
</span>
</div>
</div>
<span class="text-white typo-label-sm">
© 2024 Nova - Tech Store. Powered by Capricorn Engineering
<span class="text-white typo-label-sm font-light">
© ۲۰۲۵ - ملز; هوشمند ترین وبسایت ایران
</span>
</div>
@@ -38,39 +46,51 @@
class="flex flex-col items-start ps-[5rem] py-[5rem] gap-[6.875rem]"
>
<div class="w-full flex items-start gap-[3rem]">
<div class="flex flex-col gap-[.75rem] text-white w-full">
<NuxtLink to="#" class="typo-h-5"> About </NuxtLink>
<NuxtLink to="#" class="typo-h-5"> Journal </NuxtLink>
<NuxtLink to="#" class="typo-h-5"> FAQs </NuxtLink>
<NuxtLink to="#" class="typo-h-5">
Contact us
<div class="flex flex-col gap-[1.5rem] w-full">
<NuxtLink
v-for="(link, index) in NAV_LINKS"
:key="index"
:to="link.path"
class="typo-h-5 font-light text-white hover:text-white/70 transition-all"
>
{{ link.title }}
</NuxtLink>
</div>
<div
class="flex flex-col gap-[.75rem] text-slate-400 w-full"
>
<NuxtLink to="#" class="typo-label-md">
Headphones
<div class="flex flex-col gap-[.75rem] w-full">
<NuxtLink
to="#"
class="typo-label-md font-light text-slate-400 hover:text-slate-500 transition-all"
>
سوالات متدوال
</NuxtLink>
<NuxtLink to="#" class="typo-label-md">
Speakers
<NuxtLink
to="#"
class="typo-label-md font-light text-slate-400 hover:text-slate-500 transition-all"
>
قوانین و مقررات
</NuxtLink>
<NuxtLink to="#" class="typo-label-md">
Charging stations
<NuxtLink
to="#"
class="typo-label-md font-light text-slate-400 hover:text-slate-500 transition-all"
>
گزارش خطا و باگ
</NuxtLink>
<NuxtLink to="#" class="typo-label-md">
Lanterns
<NuxtLink
to="#"
class="typo-label-md font-light text-slate-400 hover:text-slate-500 transition-all"
>
حریم خصوصی
</NuxtLink>
<NuxtLink to="#" class="typo-label-md">
Portable charger
<NuxtLink
to="#"
class="typo-label-md font-light text-slate-400 hover:text-slate-500 transition-all"
>
تماس با ما
</NuxtLink>
</div>
</div>
<div
class="flex items-center justify-between text-white w-full"
>
<span class="typo-label-sm">Privacy Policy</span>
<div class="flex items-center justify-end text-white w-full">
<div class="flex items-center gap-[1rem]">
<NuxtLink to="#" class="flex-center size-[1.5rem]">
<Icon
+61 -48
View File
@@ -3,50 +3,49 @@
import useGetAccount from "~/composables/api/account/useGetAccount";
import { useAuth } from "~/composables/api/auth/useAuth";
// types
type NavLink = {
title: string;
path: string;
};
import { useToast } from "~/composables/global/useToast";
import { NAV_LINKS } from "~/constants";
// state
const { data: account } = useGetAccount();
const route = useRoute();
const { logout } = useAuth();
const router = useRouter();
const nav_links = ref<NavLink[]>([
const { addToast } = useToast();
const { logout, token } = useAuth();
const profileDropdownItems = ref<DropdownItem[]>([
{
title: "خانه",
path: "/",
title: "ورود به پنل کاربری",
icon: "ci:profile",
onClick: () => router.push({ name: "profile" }),
},
{
title: "محصولات",
path: "/products",
},
{
title: "دسته بندی ها",
path: "/category",
},
{
title: "ارتباط با ما",
path: "/contact-us",
},
{
title: "امکانات",
path: "#",
title: "خروج از حساب",
icon: "bi:arrow-bar-left",
itemClass: "!text-red-500",
iconClass: "**:stroke-red-500 **:fill-transparent",
onClick: () => {
logout();
nextTick(() => {
addToast({
message: "با موفقیت از حساب خارج شدید",
options: {
status: "success",
},
});
});
},
},
]);
// queries
const { data: account } = useGetAccount();
// computed
const isHomePage = computed(() => route.path === "/");
// lifecycle
onMounted(() => {});
</script>
<template>
@@ -60,16 +59,28 @@ onMounted(() => {});
>
<div class="flex items-center gap-16">
<div class="flex items-center justify-end gap-[1.5rem]">
<Tooltip v-if="!!account" title="حساب کاربری">
<button
@click="() => logout(true)"
class="size-[1.6rem] flex items-center justify-center relative overflow-hidden rounded-full bg-slate-300"
>
<Avatar
:src="account.profile_photo"
:alt="`${account.first_name} ${account.last_name}`"
/>
</button>
<Tooltip v-if="!!account && !!token" title="حساب کاربری">
<Dropdown :items="profileDropdownItems">
<template #trigger>
<button
class="size-[1.6rem] flex items-center justify-center relative overflow-hidden rounded-full border border-black"
>
<Avatar
:src="account.profile_photo"
:alt="
account.first_name &&
account.last_name
? `${account.first_name.charAt(
0
)} ${account.last_name.charAt(
0
)}`
: ''
"
/>
</button>
</template>
</Dropdown>
</Tooltip>
<Tooltip v-else title="ورود">
<NuxtLink to="/signin" class="flex-center">
@@ -89,20 +100,22 @@ onMounted(() => {});
/>
</NuxtLink>
</Tooltip>
<NuxtLink to="/cart" class="flex-center">
<Icon
name="ci:cart"
size="24px"
class="**:stroke-black"
/>
</NuxtLink>
<Tooltip title="سبد خرید">
<NuxtLink to="/cart" class="flex-center">
<Icon
name="ci:cart"
size="24px"
class="**:stroke-black"
/>
</NuxtLink>
</Tooltip>
</div>
<nav
class="flex-center gap-[2.5rem] typo-label-sm font-light text-black/80"
>
<NuxtLink
v-for="(link, index) in nav_links"
v-for="(link, index) in NAV_LINKS"
:key="index"
:to="link.path"
>
+1 -1
View File
@@ -16,7 +16,7 @@ defineProps<Props>();
</TooltipTrigger>
<TooltipPortal>
<TooltipContent
class="bg-black text-white px-4 py-3 rounded-full text-sm font-iran-yekan-x animate-slide-down-fade"
class="bg-black text-white px-4 py-3 rounded-full text-sm font-iran-yekan-x animate-slide-down-fade z-1000"
:side-offset="5"
>
{{ title }}
+23
View File
@@ -56,3 +56,26 @@ export const PRODUCT_RANGE = {
min: 0,
max: 100_000_000,
};
export const NAV_LINKS = [
{
title: "خانه",
path: "/",
},
{
title: "محصولات",
path: "/products",
},
{
title: "دسته بندی ها",
path: "/category",
},
{
title: "ارتباط با ما",
path: "/contact-us",
},
{
title: "امکانات",
path: "#",
},
];
+29 -18
View File
@@ -3,10 +3,14 @@ export default defineNuxtConfig({
compatibilityDate: "2024-11-01",
ssr: true,
devtools: { enabled: true },
css: ["~/assets/css/tailwind.css", "swiper/css", "animate.css/animate.min.css"],
css: [
"~/assets/css/tailwind.css",
"swiper/css",
"animate.css/animate.min.css",
],
routeRules: {
"/products": { prerender: false, ssr: false }
"/products": { prerender: false, ssr: false },
},
app: {
@@ -15,22 +19,29 @@ export default defineNuxtConfig({
"animate__animated animate__fadeIn animate__faster",
leaveActiveClass:
"animate__animated animate__fadeOut animate__faster",
mode: "out-in"
}
mode: "out-in",
},
layoutTransition: {
enterActiveClass:
"animate__animated animate__fadeIn animate__faster",
leaveActiveClass:
"animate__animated animate__fadeOut animate__faster",
mode: "out-in",
},
},
postcss: {
plugins: {
"@tailwindcss/postcss": {},
autoprefixer: {}
}
autoprefixer: {},
},
},
components: [
{
path: "~/components",
pathPrefix: false
}
pathPrefix: false,
},
],
icon: {
@@ -38,9 +49,9 @@ export default defineNuxtConfig({
customCollections: [
{
prefix: "ci",
dir: "./public/icons"
}
]
dir: "./public/icons",
},
],
},
modules: [
@@ -51,20 +62,20 @@ export default defineNuxtConfig({
"DM Sans": "100..900",
Inter: "100..900",
download: true,
inject: false
}
}
inject: false,
},
},
],
"@nuxt/icon",
"reka-ui/nuxt",
"@vueuse/nuxt",
"@formkit/auto-animate/nuxt",
'@nuxt/test-utils/module'
"@nuxt/test-utils/module",
],
runtimeConfig: {
public: {
API_BASE_URL: "https://api.heymlz.com"
}
}
API_BASE_URL: "https://api.heymlz.com",
},
},
});
+13
View File
@@ -0,0 +1,13 @@
<script setup lang="ts">
// meta
definePageMeta({
middleware: "check-is-logged-in",
});
</script>
<template>
<div></div>
</template>
<style scoped></style>
+13
View File
@@ -0,0 +1,13 @@
<script setup lang="ts">
// meta
definePageMeta({
middleware: "check-is-logged-in",
});
</script>
<template>
<div></div>
</template>
<style scoped></style>
+25 -17
View File
@@ -44,23 +44,23 @@ declare global {
};
type Article = {
"id": number,
"title": string,
"slug": string,
"content": string,
"summery": string,
"created_at": string,
"updated_at": string,
"cover_image": string,
"views": number,
"meta_description": string,
"meta_keywords": string,
"author": {
"full_name": string,
"profile_photo": string
},
"category": number
}
id: number;
title: string;
slug: string;
content: string;
summery: string;
created_at: string;
updated_at: string;
cover_image: string;
views: number;
meta_description: string;
meta_keywords: string;
author: {
full_name: string;
profile_photo: string;
};
category: number;
};
type UserComment = {
id: number;
@@ -108,4 +108,12 @@ declare global {
hour: string;
price: string;
};
type DropdownItem = {
title: string;
icon: string;
itemClass?: string;
iconClass?: string;
onClick?: () => void;
};
}