@@ -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'
+ "
/>
-
-
\ No newline at end of file
+
diff --git a/frontend/components/global/Header.vue b/frontend/components/global/Header.vue
index 7ff84ac..07cda45 100644
--- a/frontend/components/global/Header.vue
+++ b/frontend/components/global/Header.vue
@@ -1,8 +1,8 @@
@@ -45,16 +45,15 @@ const nav_links = ref
([
-
+
-
+
-
- KIR
-
+
KIR
diff --git a/frontend/composables/api/account/useGetAccount.ts b/frontend/composables/api/account/useGetAccount.ts
index a7d2bbb..7c2ed83 100644
--- a/frontend/composables/api/account/useGetAccount.ts
+++ b/frontend/composables/api/account/useGetAccount.ts
@@ -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()
});
diff --git a/frontend/composables/api/auth/useAuth.ts b/frontend/composables/api/auth/useAuth.ts
index 831ad40..2fa8493 100644
--- a/frontend/composables/api/auth/useAuth.ts
+++ b/frontend/composables/api/auth/useAuth.ts
@@ -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 };
};
\ No newline at end of file
diff --git a/frontend/composables/api/chat/useCreateChatMessage.ts b/frontend/composables/api/chat/useCreateChatMessage.ts
new file mode 100644
index 0000000..87f5da6
--- /dev/null
+++ b/frontend/composables/api/chat/useCreateChatMessage.ts
@@ -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(`${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>>([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>>([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;
diff --git a/frontend/composables/api/chat/useGetChat.ts b/frontend/composables/api/chat/useGetChat.ts
new file mode 100644
index 0000000..32a6da1
--- /dev/null
+++ b/frontend/composables/api/chat/useGetChat.ts
@@ -0,0 +1,62 @@
+// imports
+
+import { useInfiniteQuery } from "@tanstack/vue-query";
+import { API_ENDPOINTS, QUERY_KEYS } from "~/constants";
+
+// types
+
+export type GetBranchResponse = ApiPaginated;
+
+const useGetBranch = (
+ productId: string | number,
+ enabled: Ref
+) => {
+
+ // state
+
+ const { $axios: axios } = useNuxtApp();
+
+ // method
+
+ const handleGetChat = async ({ productId, limit, offset }: {
+ productId: number | string,
+ limit: number,
+ offset: number
+ }) => {
+
+ const { data } = await axios.get(`${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;
diff --git a/frontend/composables/api/products/useGetProducts.ts b/frontend/composables/api/products/useGetProducts.ts
index e69de29..7828df4 100644
--- a/frontend/composables/api/products/useGetProducts.ts
+++ b/frontend/composables/api/products/useGetProducts.ts
@@ -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
+) => {
+ // state
+
+ const { $axios: axios } = useNuxtApp();
+
+ // methods
+
+ const handleGetProducts = async ({
+ filters,
+ page,
+ }: {
+ filters: GetProductsFilters;
+ page: number;
+ }) => {
+ const { data } = await axios.get(
+ `${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;
diff --git a/frontend/composables/api/global/useBaseUrl.ts b/frontend/composables/global/useBaseUrl.ts
similarity index 100%
rename from frontend/composables/api/global/useBaseUrl.ts
rename to frontend/composables/global/useBaseUrl.ts
diff --git a/frontend/composables/api/global/useImageColor.ts b/frontend/composables/global/useImageColor.ts
similarity index 100%
rename from frontend/composables/api/global/useImageColor.ts
rename to frontend/composables/global/useImageColor.ts
diff --git a/frontend/composables/api/global/useTimer.ts b/frontend/composables/global/useTimer.ts
similarity index 100%
rename from frontend/composables/api/global/useTimer.ts
rename to frontend/composables/global/useTimer.ts
diff --git a/frontend/composables/api/global/useToast.ts b/frontend/composables/global/useToast.ts
similarity index 100%
rename from frontend/composables/api/global/useToast.ts
rename to frontend/composables/global/useToast.ts
diff --git a/frontend/constants/index.ts b/frontend/constants/index.ts
index f7bb85e..7a3cd9f 100644
--- a/frontend/constants/index.ts
+++ b/frontend/constants/index.ts
@@ -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",
};
diff --git a/frontend/nuxt.config.ts b/frontend/nuxt.config.ts
index 8978648..a613368 100644
--- a/frontend/nuxt.config.ts
+++ b/frontend/nuxt.config.ts
@@ -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"],
diff --git a/frontend/pages/products.vue b/frontend/pages/products.vue
index 80985b8..443a4c2 100644
--- a/frontend/pages/products.vue
+++ b/frontend/pages/products.vue
@@ -1,11 +1,28 @@