This commit is contained in:
marzban-dev
2025-04-16 22:15:25 +03:30
19 changed files with 356 additions and 101 deletions
+32
View File
@@ -325,4 +325,36 @@
.vpd-controls button {
@apply flex justify-center items-center;
}
.pattern {
width: 100%;
height: 100%;
--color: #191a1a10;
background-color: white;
background-image: linear-gradient(
0deg,
transparent 24%,
var(--color) 25%,
var(--color) 26%,
transparent 27%,
transparent 74%,
var(--color) 75%,
var(--color) 76%,
transparent 77%,
transparent
),
linear-gradient(
90deg,
transparent 24%,
var(--color) 25%,
var(--color) 26%,
transparent 27%,
transparent 74%,
var(--color) 75%,
var(--color) 76%,
transparent 77%,
transparent
);
background-size: 55px 55px;
}
}
@@ -90,7 +90,9 @@ const handlePayment = () => {
},
{
onSuccess: (data) => {
window.location.href = data.url;
setTimeout(() => {
window.location.href = data.url;
}, 2000);
},
onError: () => {
addToast({
+8 -2
View File
@@ -8,6 +8,7 @@ import { useImage } from "@vueuse/core";
type Props = {
src: string | null | undefined;
alt: string;
iconClass?: string;
};
// props
@@ -18,7 +19,7 @@ const { src } = toRefs(props);
// state
const { isLoading } = useImage({ src: src.value ?? '' });
const { isLoading } = useImage({ src: src.value ?? "" });
</script>
<template>
@@ -41,7 +42,12 @@ const { isLoading } = useImage({ src: src.value ?? '' });
:delay-ms="600"
>
<div class="size-full rounded-full flex-center">
<Icon name="ci:profile" size="16" class="**:stroke-black" />
<Icon
name="ci:profile"
size="16"
class="**:stroke-black"
:class="iconClass"
/>
</div>
</AvatarFallback>
</template>
+6 -6
View File
@@ -10,7 +10,7 @@
class="flex flex-col gap-4 items-center justify-center relative z-20"
>
<div
class="flex items-center flex-col gap-8 pb-[10px] pt-[80px] lg:py-[150px] justify-center"
class="flex items-center flex-col gap-8 pb-[10px] pt-[80px] lg:pt-[150px] lg:pb-[50px] justify-center"
>
<img
src="/img/heymlz/heymlz-small-idle.gif"
@@ -28,13 +28,13 @@
>
<div class="flex flex-col gap-4 max-w-[300px]">
<h3
class="font-bold text-xl xl:text-3xl max-lg:text-center text-white"
class="font-bold text-lg xl:text-3xl max-lg:text-center text-white"
>
با ما در ارتباط باشید...
</h3>
<p
class="text-md font-thin leading-[175%] mt-4 max-lg:text-center text-slate-300"
class="text-md font-thin leading-[175%] mt-4 max-lg:text-center text-slate-300 max-lg:text-xs"
>
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنگی با
تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان
@@ -77,7 +77,7 @@
<div class="flex flex-col gap-6 max-lg:text-center">
<h3 class="font-bold text-white">لینک های مفید</h3>
<ul
class="flex flex-col gap-4 font-thin text-slate-300"
class="flex flex-col gap-4 font-thin text-slate-300 max-lg:text-xs"
>
<li>از طراحان گرافیک است</li>
<li>تولید نامفهوم</li>
@@ -90,7 +90,7 @@
<div class="flex flex-col gap-6 max-lg:text-center">
<h3 class="font-bold text-white">لینک های مفید</h3>
<ul
class="flex flex-col gap-4 font-thin text-slate-300"
class="flex flex-col gap-4 font-thin text-slate-300 max-lg:text-xs"
>
<li>از طراحان گرافیک است</li>
<li>تولید نامفهوم</li>
@@ -105,7 +105,7 @@
لینک های مفید
</h3>
<ul
class="flex flex-col gap-4 font-thin text-slate-300"
class="flex flex-col gap-4 font-thin text-slate-300 max-lg:text-xs"
>
<li>از طراحان گرافیک است</li>
<li>تولید نامفهوم</li>
+11 -4
View File
@@ -32,7 +32,10 @@ const isHomePage = computed(() => route.path === "/");
<div
class="size-full flex items-center justify-between container h-[65px] lg:h-[85px] shrink-0 grow-0"
>
<button class="md:hidden header-navbar-item" @click="isSideDrawerOpen = true">
<button
class="md:hidden header-navbar-item"
@click="isSideDrawerOpen = true"
>
<Icon name="humbleicons:bars" size="28" />
</button>
<div class="max-md:hidden flex items-center gap-8 lg:gap-16">
@@ -46,6 +49,7 @@ const isHomePage = computed(() => route.path === "/");
>
<Avatar
class="!size-7"
iconClass="header-navbar-item"
:src="account.profile_photo"
:alt="
account.first_name && account.last_name
@@ -61,12 +65,15 @@ const isHomePage = computed(() => route.path === "/");
<NuxtLink to="/signin" class="flex-center">
<Icon
name="ci:profile"
class="**:stroke-black size-5 lg:size-6"
class="**:stroke-black size-5 lg:size-6 header-navbar-item"
/>
</NuxtLink>
</Tooltip>
<Tooltip title="محصولات">
<NuxtLink to="/products" class="flex-center header-navbar-item">
<NuxtLink
to="/products"
class="flex-center header-navbar-item"
>
<Icon
name="ci:search"
class="**:stroke-black size-4.5 lg:size-[21px]"
@@ -106,7 +113,7 @@ const isHomePage = computed(() => route.path === "/");
<div class="header-navbar-item">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 lg:h-6 "
class="h-5 lg:h-6"
fill="none"
viewBox="0 0 220 40"
>
@@ -36,15 +36,12 @@ const highlights = ref<Highlight[]>([
<template>
<section class="w-full border-t-[0.5px] border-slate-200">
<div
class="w-full py-[5rem] gap-8 sm:gap-12 xl:gap-0 container grid grid-cols-2 lg:grid-cols-4"
class="w-full py-[3rem] lg:py-[5rem] gap-8 sm:gap-12 xl:gap-0 container grid grid-cols-2 lg:grid-cols-4"
>
<template v-for="(highlight, index) in highlights" :key="index">
<div class="flex flex-col-center gap-[.75rem] px-5">
<div class="size-[70px] md:size-[100px] flex-center">
<NuxtImg
:src="highlight.icon"
class="w-full"
/>
<NuxtImg :src="highlight.icon" class="w-full" />
</div>
<div class="w-full flex-col-center gap-[.25rem]">
@@ -27,7 +27,9 @@ const in_stock = ref(Boolean(params.in_stock) ?? false);
const sliderValueDebounced = refDebounced(sliderValue, 1000);
const filtersSuccessMessage = ref<{ title: string; status: string } | null>(null);
const filtersSuccessMessage = ref<{ title: string; status: string } | null>(
null
);
// queries
@@ -137,7 +139,7 @@ watch(
? 'bg-black text-white'
: 'bg-slate-100'
"
class="py-1 px-3 cursor-pointer text-nowrap transition-all rounded-full text-sm"
class="py-1 px-3 cursor-pointer text-nowrap transition-all rounded-md text-sm"
>
{{ sort.title }}
</button>
@@ -6,7 +6,7 @@
<Button
end-icon="ci:filter"
variant="outlined"
class="max-lg:size-11 rounded-full max-lg:aspect-square"
class="max-lg:size-11 rounded-xl max-lg:aspect-square lg:py-3.5"
>
<span class="hidden lg:block"> فیلتر محصولات </span>
</Button>
@@ -0,0 +1,32 @@
// imports
import { useQuery } from "@tanstack/vue-query";
import { API_ENDPOINTS, QUERY_KEYS } from "~/constants";
// types
export type GetTransactionResponse = Transaction;
export type GetTransactionRequest = string;
const useGetTransaction = (params: ComputedRef<GetTransactionRequest>) => {
// state
const { $axios: axios } = useNuxtApp();
// methods
const handleGetTransaction = async (tc: GetTransactionRequest) => {
const { data } = await axios.get<GetTransactionResponse>(
`${API_ENDPOINTS.orders.checkout.transaction}/${tc}`
);
return data;
};
return useQuery({
queryKey: [QUERY_KEYS.transaction, params],
queryFn: () => handleGetTransaction(params.value),
});
};
export default useGetTransaction;
@@ -0,0 +1,23 @@
// composables/usePersianDate.ts
import { format, toDate } from "date-fns-jalali";
import { faIR } from "date-fns-jalali/locale";
export default function usePersianDate() {
const formatToPersian = (isoDate: string): string => {
try {
const date = toDate(new Date(isoDate));
const persianDate = format(date, "yyyy/MM/dd", { locale: faIR });
const persianTime = format(date, "HH:mm", { locale: faIR });
return `${persianDate} | ${persianTime}`;
} catch (error) {
return "Invalid date";
}
};
return {
formatToPersian,
};
}
+1
View File
@@ -78,6 +78,7 @@ export const QUERY_KEYS = {
ticket: "ticket",
orders: "orders",
cart: "cart",
transaction: "transaction",
};
export const MUTATION_KEYS = {
+2 -8
View File
@@ -20,17 +20,11 @@ export default defineNuxtConfig({
title: "فروشگاه هی ملز",
},
pageTransition: {
enterActiveClass:
"animate__animated animate__fadeIn animate__faster",
leaveActiveClass:
"animate__animated animate__fadeOut animate__faster",
name: "fade",
mode: "out-in",
},
layoutTransition: {
enterActiveClass:
"animate__animated animate__fadeIn animate__faster",
leaveActiveClass:
"animate__animated animate__fadeOut animate__faster",
name: "fade",
mode: "out-in",
},
},
+1 -1
View File
@@ -37,7 +37,7 @@
"nuxt": "^3.15.4",
"reka-ui": "^1.0.0-alpha.6",
"sanitize-html": "^2.15.0",
"swiper": "^11.2.4",
"swiper": "^11.2.6",
"universal-cookie": "^7.2.2",
"vue": "latest",
"vue-router": "latest",
+1 -1
View File
@@ -100,7 +100,7 @@ whenever(
<Icon
name="svg-spinners:180-ring-with-bg"
size="20"
v-if="!setOrderAddressIsPending"
v-if="setOrderAddressIsPending"
class="pb-0.5"
/>
</div>
+20 -10
View File
@@ -55,27 +55,34 @@ if (response.isError) {
<template>
<div class="container">
<div class="pt-20 pb-8 sm:py-20 flex gap-12 sm:gap-6 justify-between items-center max-sm:flex-col max-sm:items-start">
<span class="typo-h-5 lg:typo-h-4 text-black">دسته بندی ها</span>
<div class="flex items-center gap-4 sm:gap-8 max-sm:w-full">
<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"
>
<div class="flex flex-col items-center lg:items-start w-full">
<h1 class="typo-h-5 lg:typo-h-4 text-black">دسته بندی ها</h1>
</div>
<div
class="w-full flex items-center justify-between lg:justify-end gap-4"
>
<Input
class="max-w-[400px] w-full rounded-full h-[38px] lg:h-[50px]"
variant="outlined"
placeholder="جستجو..."
placeholder="جست و جو محصول ..."
v-model="search"
variant="outlined"
class="!rounded-xl w-full lg:w-8/12"
>
<template #endItem>
<div class="flex items-center gap-1">
<Icon
class="translate-y-[-1px] size-[18px] lg:size-[24px]"
class="translate-y-[-1px] text-[20px] lg:text-[24px]"
name="ci:search"
/>
</div>
</template>
</Input>
<Select
class="whitespace-nowrap max-sm:w-full"
class="shrink-0 max-lg:w-[5rem] lg:w-[6.5rem] py-0.5"
triggerRootClass="!rounded-xl whitespace-nowrap max-sm:w-full shrink-0 "
:options="mainCategoriesMenu"
variant="outlined"
placeholder="انتخاب کنید"
@@ -106,7 +113,10 @@ if (response.isError) {
<div
class="flex-col flex-grow py-32 sm:py-[12rem] gap-6 border-2 border-slate-200 border-dashed size-full rounded-100 flex-center"
>
<Icon name="bi:search" class="**:fill-gray-500 size-[40px] sm:size-[50px]" />
<Icon
name="bi:search"
class="**:fill-gray-500 size-[40px] sm:size-[50px]"
/>
<span class="text-lg text-gray-500">
دسته بندی یافت نشد :(
</span>
+4 -4
View File
@@ -65,13 +65,13 @@ watch(
<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>
<span class="text-xs lg:text-sm">همه</span>
</div>
</div> -->
<h1 class="typo-h-5 lg:typo-h-4">لیست محصولات</h1>
</div>
@@ -82,7 +82,7 @@ watch(
placeholder="جست و جو محصول ..."
v-model="search"
variant="outlined"
class="!rounded-full w-full lg:w-8/12"
class="!rounded-xl w-full lg:w-8/12"
>
<template #endItem>
<div class="flex items-center gap-1">
@@ -97,7 +97,7 @@ watch(
<FilterButton />
<template #fallback>
<Skeleton
class="!size-11 lg:!w-[10.35rem] lg:!h-[3.35rem] shrink-0 !rounded-full"
class="!size-11 lg:!w-[10.35rem] lg:!h-[3.35rem] shrink-0 !rounded-xl"
/>
</template>
</Suspense>
+4 -8
View File
@@ -151,13 +151,7 @@ const resetForm = () => {
<template>
<div class="w-full flex h-svh items-center relative container">
<div
class="bg-[url(/img/pattern-1.png)] -z-10 size-full fixed inset-0 opacity-70"
:style="{
backgroundSize: 150,
mask: 'linear-gradient(to bottom, black 0%, rgba(0,0,0,0.3) 80%)',
}"
/>
<div class="pattern -z-10 size-full fixed inset-0" />
<div
class="flex items-center justify-center flex-col size-full translate-y-[-100px]"
>
@@ -172,7 +166,9 @@ const resetForm = () => {
<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"
>
<h1 class="typo-h-6 sm:typo-h-5 mt-8">شماره خود را وارد کنید</h1>
<h1 class="typo-h-6 sm:typo-h-5 mt-8">
شماره خود را وارد کنید
</h1>
<form @submit.prevent class="max-w-[500px] w-full mt-12">
<Input
+187 -48
View File
@@ -1,75 +1,214 @@
<script setup lang="ts">
// imports
import useGetTransaction from "~/composables/api/orders/useGetTransaction";
import usePersianDate from "~/composables/global/usePersianDate";
// meta
definePageMeta({
layout: "none",
});
// state
const route = useRoute();
const { formatToPersian } = usePersianDate();
// computed
const tracking_code = computed(() => route.query["tc"] as string);
// queries
const {
data: transaction,
isLoading: transactionIsLoading,
suspense,
} = useGetTransaction(tracking_code);
await suspense();
// computed
const statusVariants = computed(() => {
if (transaction.value?.bank_result?.status == "succeeded") {
return {
background_color: "bg-success-500",
text_color: "text-white",
after_background_color:
"bg-success-600/50 shadow-[0px_40px_175px_1px] shadow-success-100",
icon: "bi:check",
title: "تراکنش موفق",
hue_deg: "[filter:_hue-rotate(260deg)] ",
};
} else if (transaction.value?.bank_result?.status == "canceled") {
return {
background_color: "bg-danger-500",
text_color: "text-white",
after_background_color:
"bg-danger-600/50 shadow-[0px_40px_175px_1px] shadow-danger-100",
icon: "bi:x",
title: "تراکنش ناموفق",
hue_deg: "[filter:_hue-rotate(120deg)]",
};
} else {
return {
background_color: "bg-slate-300",
text_color: "text-black",
after_background_color:
"bg-slate-600/50 shadow-[0px_40px_175px_1px] shadow-slate-100",
icon: "bi:question-circle",
title: "تراکنش معلق",
hue_deg: "[filter:_hue-rotate(0deg)]",
};
}
});
const statusTitle = computed(() => {
if (transaction.value?.bank_result?.status == "succeeded") {
return "";
} else if (transaction.value?.bank_result?.status == "canceled") {
return "";
} else {
return "";
}
});
</script>
<template>
<div class="w-full flex-col-center gap-3 h-svh relative">
<div class="w-full flex-col-center container gap-3 h-svh">
<div
class="bg-[url(/img/pattern-1.png)] -z-10 size-full fixed inset-0 opacity-70"
class="pattern -z-10 size-full fixed inset-0 opacity-90"
:style="{
backgroundSize: 150,
mask: 'linear-gradient(to bottom, black 0%, rgba(0,0,0,0.3) 80%)',
}"
/>
<NuxtImg src="/logo/logo-col.png" class="size-44 -mt-12" />
<div class="max-w-[500px] w-full relative">
<NuxtImg
class="aspect-square w-[220px] md:w-[280px] lg:w-[320px] absolute -top-[70px] md:-top-[110px] lg:-top-[138px] left-1/2 -translate-x-1/2 z-10 [filter:_drop-shadow(0px_4px_20px_rgba(0, 0, 0, 0.15))]"
src="/img/heymlz/heymlz-seat.gif"
:class="statusVariants.hue_deg"
/>
<div
class="max-w-[500px] w-full p-6 gap-6 flex flex-col items-center bg-white border shadow-black/10 justify-center border-slate-300 rounded-3xl relative overflow-hidden"
>
<div
class="w-full h-[5rem] bg-success-500 absolute left-0 top-0 flex-center gap-2 text-white"
class="w-full h-5/6 absolute rounded-3xl -z-3 -bottom-1 left-1/2 -translate-x-1/2"
:class="statusVariants.after_background_color"
/>
<div
class="w-full p-5 lg:p-6 gap-5 lg:gap-6 flex flex-col items-center bg-white border shadow-black/10 justify-center overflow-hidden border-slate-300 rounded-3xl relative mt-20 z-1"
>
<Icon name="bi:check" size="28" />
<h1 class="typo-h-6 font-normal">تراکنش موفق</h1>
</div>
<div
class="w-full h-[4rem] lg:h-[5.2rem] absolute left-0 top-0 flex-center gap-2"
:class="[
statusVariants.background_color,
statusVariants.text_color,
]"
>
<Icon
:name="statusVariants.icon"
class="text-2xl lg:text-3xl"
/>
<h1 class="text-lg lg:text-2xl font-normal">
{{ statusVariants.title }}
</h1>
</div>
<div class="w-full flex flex-col gap-5 pt-[5.5rem] p-1">
<div
class="w-full flex flex-row-reverse items-center justify-between"
class="w-full flex flex-col gap-4 lg:gap-5 pt-[4.5rem] lg:pt-[5.5rem] p-1"
>
<span>مبلغ تراكنش </span>
<span>١٢٣ تومان</span>
</div>
<div
class="w-full flex flex-row-reverse items-center justify-between"
>
<span>شماره پيكَيرى </span>
<span>١٢٣ تومان</span>
</div>
<div
class="w-full flex flex-row-reverse items-center justify-between"
>
<span>شماره ارجاع </span>
<span>١٢٣ تومان</span>
</div>
<div
class="w-full flex flex-row-reverse items-center justify-between"
>
<span>تاريخ و ساعت </span>
<span>١٢٣ تومان</span>
</div>
</div>
<div class="w-full flex items-center justify-between gap-5">
<NuxtLink to="/" class="w-full">
<Button
class="w-full rounded-full"
start-icon="ci:left-rotation"
variant="secondary"
>بازگشت به فروشگاه</Button
<div
v-if="transaction?.bank_result?.bank_type"
class="w-full flex flex-row-reverse items-center justify-between max-lg:text-xs"
>
</NuxtLink>
<Button
class="w-full rounded-full bg-success-500 hover:text-success-500 hover:border-success-500 hover:**:!stroke-success-500"
start-icon="ci:share"
variant="primary"
>دانلود فاکتور</Button
<span class="font-medium">درگاه پرداخت</span>
<span class="opacity-50">{{
transaction?.bank_result?.bank_type
}}</span>
</div>
<div
v-if="transaction?.bank_result?.tracking_code"
class="w-full flex flex-row-reverse items-center justify-between max-lg:text-xs"
>
<span class="font-medium">کد پیگیری</span>
<span class="opacity-50 underline"
>#{{
transaction?.bank_result?.tracking_code
}}</span
>
</div>
<div
v-if="transaction?.bank_result?.reference_number"
class="w-full flex flex-row-reverse items-center justify-between max-lg:text-xs"
>
<span class="font-medium">کد ارجاع</span>
<span class="opacity-50 underline"
>#{{
transaction?.bank_result?.reference_number
}}</span
>
</div>
<div
v-if="transaction?.bank_result?.amount"
class="w-full flex flex-row-reverse items-center justify-between max-lg:text-xs"
>
<span class="font-medium">مبلغ</span>
<span class="opacity-50">{{
transaction?.bank_result?.amount
}}</span>
</div>
<div
v-if="transaction?.bank_result?.created_at"
class="w-full flex flex-row-reverse items-center justify-between max-lg:text-xs"
>
<span class="font-medium">تاریخ</span>
<span class="opacity-50">{{
formatToPersian(
transaction?.bank_result?.created_at
)
}}</span>
</div>
<div
v-if="transaction?.bank_result?.response_result"
class="w-full flex flex-row-reverse items-center justify-between max-lg:text-xs"
>
<span class="font-medium">وضعیت پرداخت</span>
<span class="opacity-50">{{
transaction?.bank_result?.status_detail
}}</span>
</div>
</div>
<div
v-if="transaction?.detail"
class="w-full text-center opacity-50 text-sm lg:text-sm leading-[175%]"
>
{{ transaction?.detail }}
</div>
<div
class="w-full flex flex-col-reverse lg:flex-row items-center justify-between gap-4 lg:gap-5"
>
<NuxtLink to="/" class="w-full">
<Button
class="w-full rounded-full max-lg:py-2"
start-icon="ci:left-rotation"
variant="secondary"
>بازگشت به فروشگاه</Button
>
</NuxtLink>
<Button
v-if="transaction?.bank_result?.status == 'succeeded'"
class="w-full rounded-full max-lg:py-2 bg-success-500 hover:text-success-500 hover:border-success-500 hover:**:!stroke-success-500"
start-icon="ci:share"
variant="primary"
>دانلود فاکتور</Button
>
</div>
</div>
</div>
</div>
+14
View File
@@ -290,4 +290,18 @@ declare global {
| "BAHAMTA"
| "BMI";
};
type Transaction = {
detail: string;
bank_result?: {
status: "succeeded" | "canceled" | "pending";
bank_type: string;
tracking_code: string;
amount: string | null;
created_at: string;
response_result: string | null;
reference_number: string | null;
status_detail: string | null;
};
};
}