Merge branch 'main' of https://github.com/Byeto-Company/hossein_por_shop
This commit is contained in:
@@ -1,17 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
// types
|
||||
|
||||
import { useImageColor } from "~/composables/useImageColor";
|
||||
import { useImageColor } from "~/composables/global/useImageColor";
|
||||
|
||||
type Props = {
|
||||
id: number,
|
||||
id: number;
|
||||
category: string;
|
||||
count: number;
|
||||
description: string;
|
||||
picture: string;
|
||||
darkLayer?: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
// props
|
||||
|
||||
@@ -21,7 +20,6 @@ const { id } = toRefs(props);
|
||||
// state
|
||||
|
||||
const { colorObject } = useImageColor(`#category-image-${id.value}`);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -37,10 +35,15 @@ const { colorObject } = useImageColor(`#category-image-${id.value}`);
|
||||
class="bg-linear-to-t from-black/50 to-transparent to-40% absolute z-10 size-full"
|
||||
/>
|
||||
|
||||
<div class="absolute z-20 bottom-0 p-6 flex items-end justify-between w-full">
|
||||
|
||||
<div
|
||||
class="absolute z-20 bottom-0 p-6 flex items-end justify-between w-full"
|
||||
>
|
||||
<div
|
||||
:class="(colorObject?.isLight && !darkLayer) ? 'text-black' : 'text-white'"
|
||||
:class="
|
||||
colorObject?.isLight && !darkLayer
|
||||
? 'text-black'
|
||||
: 'text-white'
|
||||
"
|
||||
class="flex flex-col gap-2"
|
||||
>
|
||||
<div class="typo-s-h-md">
|
||||
@@ -56,9 +59,12 @@ const { colorObject } = useImageColor(`#category-image-${id.value}`);
|
||||
size="24"
|
||||
name="ci:arrow-left"
|
||||
class="mb-1"
|
||||
:class="(colorObject?.isLight && !darkLayer) ? '**:stroke-black' : '**:stroke-white'"
|
||||
:class="
|
||||
colorObject?.isLight && !darkLayer
|
||||
? '**:stroke-black'
|
||||
: '**:stroke-white'
|
||||
"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
// import
|
||||
|
||||
import useGetAccount from "~/composables/api/account/useGetAccount";
|
||||
import { useAuth } from "~/composables/api/auth/useAuth";
|
||||
|
||||
// types
|
||||
|
||||
@@ -19,24 +19,24 @@ const { logout } = useAuth();
|
||||
const nav_links = ref<NavLink[]>([
|
||||
{
|
||||
title: "فروشگاه",
|
||||
path: "#"
|
||||
path: "#",
|
||||
},
|
||||
{
|
||||
title: "دسته بندی ها",
|
||||
path: "#"
|
||||
path: "#",
|
||||
},
|
||||
{
|
||||
title: "جستجو",
|
||||
path: "#"
|
||||
path: "#",
|
||||
},
|
||||
{
|
||||
title: "ارتباط با ما",
|
||||
path: "#"
|
||||
path: "#",
|
||||
},
|
||||
{
|
||||
title: "امکانات",
|
||||
path: "#"
|
||||
}
|
||||
path: "#",
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
@@ -45,16 +45,15 @@ const nav_links = ref<NavLink[]>([
|
||||
<div
|
||||
class="size-full flex items-center justify-between container py-[2.25rem]"
|
||||
>
|
||||
<div v-if="!!account" class="w-2/12 flex items-center justify-start">
|
||||
<div
|
||||
v-if="!!account"
|
||||
class="w-2/12 flex items-center justify-start"
|
||||
>
|
||||
<span class="size-[2rem] bg-black rounded-full"></span>
|
||||
<button @click="() => logout(true)">
|
||||
خروج از وبسایت
|
||||
</button>
|
||||
<button @click="() => logout(true)">خروج از وبسایت</button>
|
||||
</div>
|
||||
|
||||
<div v-else class="text-black">
|
||||
KIR
|
||||
</div>
|
||||
<div v-else class="text-black">KIR</div>
|
||||
|
||||
<nav
|
||||
class="flex-center gap-[2.5rem] w-8/12 typo-label-sm text-slate-500"
|
||||
|
||||
@@ -1,18 +1,68 @@
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
total: number;
|
||||
items: {
|
||||
type: "page" | "not-page";
|
||||
value: number;
|
||||
}[];
|
||||
};
|
||||
|
||||
// props
|
||||
|
||||
defineProps<Props>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PaginationRoot>
|
||||
<PaginationList v-slot="{ items }">
|
||||
<PaginationFirst />
|
||||
<PaginationPrev />
|
||||
<PaginationRoot
|
||||
:total="100"
|
||||
:sibling-count="1"
|
||||
:items-per-page="10"
|
||||
show-edges
|
||||
>
|
||||
<PaginationList
|
||||
v-slot="{ items }"
|
||||
class="flex items-center gap-1 text-stone-700 dark:text-white"
|
||||
>
|
||||
<PaginationFirst
|
||||
class="w-9 h-9 flex items-center justify-center bg-transparent hover:bg-white dark:hover:bg-stone-700/70 transition disabled:opacity-50 rounded-lg"
|
||||
>
|
||||
<Icon name="ci:double-arrow-left" />
|
||||
</PaginationFirst>
|
||||
<PaginationPrev
|
||||
class="w-9 h-9 flex items-center justify-center bg-transparent hover:bg-white dark:hover:bg-stone-700/70 transition mr-4 disabled:opacity-50 rounded-lg"
|
||||
>
|
||||
<Icon name="ci:chevron-left" />
|
||||
</PaginationPrev>
|
||||
<template v-for="(page, index) in items">
|
||||
<PaginationListItem v-if="page.type === 'page'" :key="index" />
|
||||
<PaginationEllipsis v-else :key="page.type" :index="index">
|
||||
<PaginationListItem
|
||||
v-if="page.type === 'page'"
|
||||
:key="index"
|
||||
class="w-9 h-9 border dark:border-stone-800 rounded-lg data-[selected]:!bg-white data-[selected]:shadow-sm data-[selected]:text-blackA11 hover:bg-white dark:hover:bg-stone-700/70 transition"
|
||||
:value="page.value"
|
||||
>
|
||||
{{ page.value }}
|
||||
</PaginationListItem>
|
||||
<PaginationEllipsis
|
||||
v-else
|
||||
:key="page.type"
|
||||
:index="index"
|
||||
class="w-9 h-9 flex items-center justify-center"
|
||||
>
|
||||
…
|
||||
</PaginationEllipsis>
|
||||
</template>
|
||||
<PaginationNext />
|
||||
<PaginationLast />
|
||||
<PaginationNext
|
||||
class="w-9 h-9 flex items-center justify-center bg-transparent hover:bg-white dark:hover:bg-stone-700/70 transition ml-4 disabled:opacity-50 rounded-lg"
|
||||
>
|
||||
<Icon name="ci:chevron-right" />
|
||||
</PaginationNext>
|
||||
<PaginationLast
|
||||
class="w-9 h-9 flex items-center justify-center bg-transparent hover:bg-white dark:hover:bg-stone-700/70 transition disabled:opacity-50 rounded-lg"
|
||||
>
|
||||
<Icon name="ci:double-arrow-right" />
|
||||
</PaginationLast>
|
||||
</PaginationList>
|
||||
</PaginationRoot>
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup>
|
||||
// imports
|
||||
|
||||
import { useToast } from "~/composables/global/useToast";
|
||||
|
||||
// state
|
||||
|
||||
import ToastBox from "~/components/ui/ToastContainer/ToastBox.vue";
|
||||
|
||||
const { toasts } = useToast();
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -16,4 +16,4 @@ const { toasts } = useToast();
|
||||
:message="toast.message"
|
||||
:options="toast.options"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -7,7 +7,11 @@ import { PRODUCT_RANGE } from "~/constants";
|
||||
|
||||
const params = useUrlSearchParams("history");
|
||||
|
||||
const sort_filter = ref(["جدیدترین ها", "گران ترین ها", "ارزان ترین ها"]);
|
||||
const sort_filter = ref([
|
||||
{ title: "جدیدترین ها", value: "newest" },
|
||||
{ title: "گران ترین ها", value: "price" },
|
||||
{ title: "ارزان ترین ها", value: "-price" },
|
||||
]);
|
||||
|
||||
const options = [
|
||||
{
|
||||
@@ -63,15 +67,15 @@ const resetFilters = () => {
|
||||
<button
|
||||
v-for="(sort, index) in sort_filter"
|
||||
:key="index"
|
||||
@click="params.sort = sort"
|
||||
@click="params.sort = sort.value"
|
||||
:class="
|
||||
params.sort == sort
|
||||
params.sort == sort.value
|
||||
? 'bg-black text-white'
|
||||
: 'bg-slate-100'
|
||||
"
|
||||
class="py-1 px-3 cursor-pointer text-nowrap transition-all rounded-full text-sm"
|
||||
>
|
||||
{{ sort }}
|
||||
{{ sort.title }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { API_ENDPOINTS, QUERY_KEYS } from "~/constants";
|
||||
import { useAuth } from "~/composables/api/auth/useAuth";
|
||||
|
||||
// types
|
||||
|
||||
@@ -12,6 +13,7 @@ const useGetAccount = () => {
|
||||
// state
|
||||
|
||||
const { $axios: axios } = useNuxtApp();
|
||||
const { token } = useAuth();
|
||||
|
||||
// methods
|
||||
|
||||
@@ -21,6 +23,7 @@ const useGetAccount = () => {
|
||||
};
|
||||
|
||||
return useQuery({
|
||||
enabled: !!token.value,
|
||||
queryKey: [QUERY_KEYS.account],
|
||||
queryFn: () => handleGetAccount()
|
||||
});
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import useGetAccount from "~/composables/api/account/useGetAccount";
|
||||
|
||||
export const useAuth = () => {
|
||||
|
||||
// state
|
||||
@@ -17,14 +15,6 @@ export const useAuth = () => {
|
||||
if (reload) window.location.reload();
|
||||
};
|
||||
|
||||
// watch
|
||||
|
||||
watch(() => token.value, (newValue) => {
|
||||
token.value = newValue;
|
||||
}, {
|
||||
immediate: true
|
||||
});
|
||||
|
||||
return { token, updateToken, logout };
|
||||
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
// imports
|
||||
|
||||
import { QueryClient, useMutation } from "@tanstack/vue-query";
|
||||
import type { InfiniteData } from "@tanstack/vue-query";
|
||||
import { API_ENDPOINTS, MUTATION_KEYS, QUERY_KEYS } from "~/constants";
|
||||
|
||||
// types
|
||||
|
||||
export type CreateChatMessageRequest = {
|
||||
productId: string | number;
|
||||
new_message: string;
|
||||
};
|
||||
|
||||
export type CreateChatMessageResponse = Chat[]
|
||||
|
||||
const useCreateChatMessage = (queryClient: QueryClient) => {
|
||||
|
||||
// state
|
||||
|
||||
const { $axios: axios } = useNuxtApp();
|
||||
|
||||
// method
|
||||
|
||||
const handleCreateChatMessage = async (variables: CreateChatMessageRequest) => {
|
||||
|
||||
const { data } = await axios.post<CreateChatMessageResponse>(`${API_ENDPOINTS.chat.new_message}/${variables.productId}`, variables);
|
||||
return data;
|
||||
};
|
||||
|
||||
return useMutation({
|
||||
mutationKey: [MUTATION_KEYS.create_chat],
|
||||
mutationFn: (variables: CreateChatMessageRequest) => handleCreateChatMessage(variables),
|
||||
onMutate: (newMessage) => {
|
||||
const prevData = queryClient.getQueriesData({ queryKey: [QUERY_KEYS.chat] });
|
||||
|
||||
queryClient.setQueryData<InfiniteData<ApiPaginated<Chat>>>([QUERY_KEYS.chat], (oldData) => {
|
||||
const lastPage = oldData!.pages[oldData!.pages.length - 1];
|
||||
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
count: lastPage.count,
|
||||
next: lastPage.next,
|
||||
previous: lastPage.previous,
|
||||
results: [
|
||||
{
|
||||
id: Date.now(),
|
||||
content: newMessage.new_message,
|
||||
sender: "user"
|
||||
}
|
||||
]
|
||||
},
|
||||
...oldData!.pages
|
||||
],
|
||||
pageParams: [
|
||||
...oldData!.pageParams,
|
||||
{
|
||||
limit: 10,
|
||||
offset: 0
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
return { prevData: prevData ? prevData[0][1] : undefined };
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
|
||||
queryClient.setQueryData<InfiniteData<ApiPaginated<Chat>>>([QUERY_KEYS.chat], (oldData) => {
|
||||
if (oldData) {
|
||||
const lastPage = oldData!.pages[oldData!.pages.length - 1];
|
||||
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
count: lastPage.count,
|
||||
next: lastPage.next,
|
||||
previous: lastPage.previous,
|
||||
results: {
|
||||
...response[0],
|
||||
id: Date.now()
|
||||
}
|
||||
},
|
||||
...oldData!.pages
|
||||
],
|
||||
pageParams: [
|
||||
...oldData!.pageParams,
|
||||
{
|
||||
limit: 10,
|
||||
offset: 0
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
return oldData;
|
||||
});
|
||||
|
||||
},
|
||||
onError: (err, newMessage, context) => {
|
||||
if (context) {
|
||||
queryClient.setQueryData(
|
||||
[QUERY_KEYS.chat],
|
||||
context.prevData
|
||||
);
|
||||
}
|
||||
},
|
||||
onSettled: (newMessage) => {
|
||||
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.chat] });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default useCreateChatMessage;
|
||||
@@ -0,0 +1,62 @@
|
||||
// imports
|
||||
|
||||
import { useInfiniteQuery } from "@tanstack/vue-query";
|
||||
import { API_ENDPOINTS, QUERY_KEYS } from "~/constants";
|
||||
|
||||
// types
|
||||
|
||||
export type GetBranchResponse = ApiPaginated<Chat>;
|
||||
|
||||
const useGetBranch = (
|
||||
productId: string | number,
|
||||
enabled: Ref<boolean>
|
||||
) => {
|
||||
|
||||
// state
|
||||
|
||||
const { $axios: axios } = useNuxtApp();
|
||||
|
||||
// method
|
||||
|
||||
const handleGetChat = async ({ productId, limit, offset }: {
|
||||
productId: number | string,
|
||||
limit: number,
|
||||
offset: number
|
||||
}) => {
|
||||
|
||||
const { data } = await axios.get<GetBranchResponse>(`${API_ENDPOINTS.chat.messages}/${productId}`, {
|
||||
params: {
|
||||
offset,
|
||||
limit
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoyMTY3ODE2OTAwLCJpYXQiOjE3MzU4MTY5MDAsImp0aSI6ImQwN2E2Y2Y2NzgwZjRlNTE5NWIzOGQxMTAzYzU4NDQ3IiwidXNlcl9pZCI6NX0.slwd7ZSV7nUXEuDTYwwHUOo9ekCefwEEL4kVv2vSTFo`
|
||||
}
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
return useInfiniteQuery({
|
||||
enabled,
|
||||
queryKey: [QUERY_KEYS.chat],
|
||||
initialPageParam: {
|
||||
limit: 10,
|
||||
offset: 0
|
||||
},
|
||||
queryFn: ({ pageParam }) => handleGetChat({
|
||||
limit: pageParam.limit,
|
||||
offset: pageParam.offset,
|
||||
productId: productId
|
||||
}),
|
||||
getNextPageParam: (lastPage, pages) => {
|
||||
if (!lastPage.next) return undefined;
|
||||
|
||||
return {
|
||||
limit: 10,
|
||||
offset: pages.length * 10
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default useGetBranch;
|
||||
@@ -0,0 +1,60 @@
|
||||
// imports
|
||||
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { API_ENDPOINTS, QUERY_KEYS } from "~/constants";
|
||||
|
||||
// types
|
||||
|
||||
export type GetProductsResponse = Product[];
|
||||
|
||||
export type GetProductsFilters = {
|
||||
search: string | undefined;
|
||||
sort: string | undefined;
|
||||
categories: string[] | undefined;
|
||||
price_range: number[] | undefined;
|
||||
has_discount: boolean | undefined;
|
||||
in_stock: boolean | undefined;
|
||||
};
|
||||
|
||||
// composable
|
||||
|
||||
const useGetProducts = (
|
||||
filters: GetProductsFilters,
|
||||
page: ComputedRef<number>
|
||||
) => {
|
||||
// state
|
||||
|
||||
const { $axios: axios } = useNuxtApp();
|
||||
|
||||
// methods
|
||||
|
||||
const handleGetProducts = async ({
|
||||
filters,
|
||||
page,
|
||||
}: {
|
||||
filters: GetProductsFilters;
|
||||
page: number;
|
||||
}) => {
|
||||
const { data } = await axios.get<GetProductsResponse>(
|
||||
`${API_ENDPOINTS.products.get_all}`,
|
||||
{
|
||||
params: {
|
||||
...filters,
|
||||
page,
|
||||
offest: page * 10 - 10,
|
||||
limit: 10,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
return useQuery({
|
||||
staleTime: 60 * 1000,
|
||||
queryKey: [QUERY_KEYS.products, filters, page],
|
||||
queryFn: () => handleGetProducts({ filters, page: page.value }),
|
||||
});
|
||||
};
|
||||
|
||||
export default useGetProducts;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
export const API_ENDPOINTS = {
|
||||
account: {
|
||||
profile : "/accounts/profile",
|
||||
profile: "/accounts/profile",
|
||||
send_otp: "/accounts/send_otp",
|
||||
},
|
||||
product: {
|
||||
get : "/products",
|
||||
get: "/products",
|
||||
},
|
||||
auth: {
|
||||
signin: "/token",
|
||||
@@ -14,11 +14,15 @@ export const API_ENDPOINTS = {
|
||||
messages: "/chat/product",
|
||||
new_message: "/chat/product",
|
||||
},
|
||||
products: {
|
||||
get_all: "/products",
|
||||
},
|
||||
};
|
||||
|
||||
export const QUERY_KEYS = {
|
||||
chat: "chat",
|
||||
product: "product",
|
||||
products: "products",
|
||||
account: "account",
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: "2024-11-01",
|
||||
ssr: false,
|
||||
ssr: true,
|
||||
devtools: { enabled: false },
|
||||
css: ["~/assets/css/tailwind.css", "swiper/css"],
|
||||
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
// import
|
||||
|
||||
import useGetProducts, {
|
||||
type GetProductsFilters,
|
||||
} from "~/composables/api/products/useGetProducts";
|
||||
import { PRODUCT_RANGE } from "~/constants";
|
||||
|
||||
// state
|
||||
|
||||
const params = useUrlSearchParams("history");
|
||||
const route = useRoute();
|
||||
|
||||
const params: GetProductsFilters & { page: number } =
|
||||
useUrlSearchParams("history");
|
||||
|
||||
// computed
|
||||
|
||||
const page = computed(() => (route.query["page"] ? +route.query["page"] : 1));
|
||||
|
||||
// queries
|
||||
|
||||
const { data: products, isLoading: productsIsLoading } = useGetProducts(
|
||||
params,
|
||||
page
|
||||
);
|
||||
|
||||
// life-cycle
|
||||
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
import { helpers, required } from "@vuelidate/validators";
|
||||
import { useVuelidate } from "@vuelidate/core";
|
||||
import useOtp from "~/composables/api/auth/useOtp";
|
||||
import { useTimer } from "~/composables/useTimer";
|
||||
import useSignIn from "~/composables/api/auth/useSignIn";
|
||||
import { definePageMeta } from "#imports";
|
||||
import useGetAccount from "~/composables/api/account/useGetAccount";
|
||||
import { useAuth } from "~/composables/api/auth/useAuth";
|
||||
import { useToast } from "~/composables/global/useToast";
|
||||
import { useTimer } from "~/composables/global/useTimer";
|
||||
|
||||
// types
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import axiosOriginal from "axios";
|
||||
import { useAuth } from "~/composables/api/auth/useAuth";
|
||||
import { API_ENDPOINTS } from "~/constants";
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
|
||||
const config = useRuntimeConfig();
|
||||
const { logout, token } = useAuth();
|
||||
const { token } = useAuth();
|
||||
|
||||
const axios = axiosOriginal.create({
|
||||
baseURL: config.public.API_BASE_URL
|
||||
baseURL: config.public.API_BASE_URL,
|
||||
});
|
||||
|
||||
axios.interceptors.request.use((config) => {
|
||||
|
||||
Reference in New Issue
Block a user