+
تمام دسته ها
@@ -39,7 +55,8 @@ defineProps();
diff --git a/frontend/components/global/ServiceHighlights.vue b/frontend/components/global/ServiceHighlights.vue
index 7ea8f5b..fa7cf11 100644
--- a/frontend/components/global/ServiceHighlights.vue
+++ b/frontend/components/global/ServiceHighlights.vue
@@ -34,27 +34,29 @@ const highlights = ref
([
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
{{ highlight.title }}
-
- {{ highlight.description }}
-
+
+ {{ highlight.description }}
+
+
-
-
-
+
+
+
diff --git a/frontend/components/home/LatestStories.vue b/frontend/components/home/LatestStories.vue
index 616251d..a9df01b 100644
--- a/frontend/components/home/LatestStories.vue
+++ b/frontend/components/home/LatestStories.vue
@@ -12,7 +12,7 @@ const {} = toRefs(props);
-
+
مقالات اخیر سایت
diff --git a/frontend/components/product/ChatBox/AiLoading.vue b/frontend/components/product/ChatBox/AiLoading.vue
new file mode 100644
index 0000000..6dba1e2
--- /dev/null
+++ b/frontend/components/product/ChatBox/AiLoading.vue
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/components/product/ChatBox/ChatBoxContainer.vue b/frontend/components/product/ChatBox/ChatBoxContainer.vue
new file mode 100644
index 0000000..00af74d
--- /dev/null
+++ b/frontend/components/product/ChatBox/ChatBoxContainer.vue
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/components/product/ChatBox/ChatInput.vue b/frontend/components/product/ChatBox/ChatInput.vue
new file mode 100644
index 0000000..5f1b73c
--- /dev/null
+++ b/frontend/components/product/ChatBox/ChatInput.vue
@@ -0,0 +1,305 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/components/product/ChatBox/ChatMessage.vue b/frontend/components/product/ChatBox/ChatMessage.vue
new file mode 100644
index 0000000..6a1db5c
--- /dev/null
+++ b/frontend/components/product/ChatBox/ChatMessage.vue
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+ {{ content }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/components/product/ChatBox/CloseButton.vue b/frontend/components/product/ChatBox/CloseButton.vue
new file mode 100644
index 0000000..af2c23f
--- /dev/null
+++ b/frontend/components/product/ChatBox/CloseButton.vue
@@ -0,0 +1,34 @@
+
+
+
+
+
+ چت بات هوش مصنوعی
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/components/ui/ToastContainer/ToastBox.vue b/frontend/components/ui/ToastContainer/ToastBox.vue
index 152a043..d274de6 100644
--- a/frontend/components/ui/ToastContainer/ToastBox.vue
+++ b/frontend/components/ui/ToastContainer/ToastBox.vue
@@ -1,17 +1,21 @@
-
- {{ message }}
+
+
+ {{ message }}
-
+
- {{ description }}
+ {{ options.description }}
diff --git a/frontend/components/ui/ToastContainer/index.vue b/frontend/components/ui/ToastContainer/index.vue
index 266d557..d05e921 100644
--- a/frontend/components/ui/ToastContainer/index.vue
+++ b/frontend/components/ui/ToastContainer/index.vue
@@ -14,6 +14,6 @@ const { toasts } = useToast();
:key="toast.id"
:id="toast.id"
:message="toast.message"
- :description="toast.description"
+ :options="toast.options"
/>
\ No newline at end of file
diff --git a/frontend/composables/api/auth/useOtp.ts b/frontend/composables/api/auth/useOtp.ts
index 7909ccc..4c7e806 100644
--- a/frontend/composables/api/auth/useOtp.ts
+++ b/frontend/composables/api/auth/useOtp.ts
@@ -13,7 +13,7 @@ export type OtpRequest = {
// methods
export const handleOtp = async (variables: OtpRequest) => {
- const { data } = await axios.post(`${API_ENDPOINTS.account.send_otp}`, variables);
+ const { data } = await axios.post(`${API_ENDPOINTS.account.send_otp}`, variables);
return data;
};
diff --git a/frontend/composables/api/auth/useSignIn.ts b/frontend/composables/api/auth/useSignIn.ts
index 3ace4f1..3f5e4b8 100644
--- a/frontend/composables/api/auth/useSignIn.ts
+++ b/frontend/composables/api/auth/useSignIn.ts
@@ -14,7 +14,7 @@ export type SignInRequest = {
// methods
export const handleSignIn = async (variables: SignInRequest) => {
- const { data } = await axios.post(`${API_ENDPOINTS.auth.signin}/`, variables);
+ const { data } = await axios.post(`${API_ENDPOINTS.auth.signin}/`, variables);
return data;
};
diff --git a/frontend/composables/api/chat/useCreateChatMessage.ts b/frontend/composables/api/chat/useCreateChatMessage.ts
new file mode 100644
index 0000000..ee0d7d7
--- /dev/null
+++ b/frontend/composables/api/chat/useCreateChatMessage.ts
@@ -0,0 +1,116 @@
+// imports
+
+import { QueryClient, useMutation } from "@tanstack/vue-query";
+import type { InfiniteData } from "@tanstack/vue-query";
+import axios from "~/configs/axios.config";
+import { API_ENDPOINTS, MUTATION_KEYS, QUERY_KEYS } from "~/constants";
+
+// types
+
+export type CreateChatMessageRequest = {
+ productId: string | number;
+ new_message: string;
+};
+
+export type CreateChatMessageResponse = Chat[]
+
+// methods
+
+export const handleCreateChatMessage = async (variables: CreateChatMessageRequest) => {
+ const { data } = await axios.post(`${API_ENDPOINTS.chat.new_message}/${variables.productId}`, variables, {
+ headers: {
+ Authorization: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoyMTY3ODE2OTAwLCJpYXQiOjE3MzU4MTY5MDAsImp0aSI6ImQwN2E2Y2Y2NzgwZjRlNTE5NWIzOGQxMTAzYzU4NDQ3IiwidXNlcl9pZCI6NX0.slwd7ZSV7nUXEuDTYwwHUOo9ekCefwEEL4kVv2vSTFo`
+ }
+ });
+ return data;
+};
+
+// composable
+
+const useCreateChatMessage = (queryClient: QueryClient) => {
+ 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/useImageColor.ts b/frontend/composables/useImageColor.ts
new file mode 100644
index 0000000..11c88e6
--- /dev/null
+++ b/frontend/composables/useImageColor.ts
@@ -0,0 +1,32 @@
+import type { FastAverageColorResult } from "fast-average-color";
+import { FastAverageColor } from "fast-average-color";
+
+export const useImageColor = (img: string) => {
+ const fac = new FastAverageColor();
+ const colorObject = ref();
+ const isPending = ref(false);
+
+ const extractImageColor = async () => {
+ isPending.value = true;
+
+ const imageEl = document.querySelector(img) as HTMLImageElement;
+
+ try {
+ const color = await fac.getColorAsync(imageEl);
+ isPending.value = false;
+ colorObject.value = color;
+ } catch (e) {
+ isPending.value = false;
+ }
+ };
+
+ onMounted(() => {
+ extractImageColor();
+ });
+
+ return {
+ colorObject,
+ extractImageColor,
+ isPending
+ };
+};
\ No newline at end of file
diff --git a/frontend/composables/useToast.ts b/frontend/composables/useToast.ts
index 95bec80..4649bac 100644
--- a/frontend/composables/useToast.ts
+++ b/frontend/composables/useToast.ts
@@ -1,23 +1,22 @@
+export type ToastOptions = {
+ description?: string;
+ duration?: number;
+ status?: "success" | "error" | "info" | "warning",
+}
+
type Toast = {
id: number;
message: string;
- description?: string;
- duration?: number;
-}
-
-type Props = {
- message: string,
- description?: string,
- options?: Omit
+ options?: ToastOptions
}
const toasts = ref([]);
export function useToast() {
- const addToast = ({ message, description, options = {} }: Props) => {
+ const addToast = ({ message, options = {} }: Omit) => {
const id = Date.now();
- toasts.value.push({ id, message, description, ...options });
+ toasts.value.push({ id, message, options });
};
const destroyToast = (id: number) => {
diff --git a/frontend/constants/index.ts b/frontend/constants/index.ts
index 51d3b72..91b533f 100644
--- a/frontend/constants/index.ts
+++ b/frontend/constants/index.ts
@@ -1,33 +1,41 @@
export const API_ENDPOINTS = {
- account : {
- send_otp : "/accounts/send_otp",
+ account: {
+ send_otp: "/accounts/send_otp"
},
auth: {
signin: "/token",
- logout: "/accounts/logout",
+ logout: "/accounts/logout"
+ },
+ chat: {
+ messages: "/chat/product",
+ new_message: "/chat/product"
}
};
export const QUERY_KEYS = {
+ chat: "chat",
+};
+export const MUTATION_KEYS = {
+ create_chat: "create_chat",
};
export const FILE_FORMATS = [
{
ext: [".jpg", ".jpeg", ".png", ".svg", ".bmp", ".webp", ".gif", ".tiff", ".heif", ".raw"],
- icon: "bi:file-earmark-image",
+ icon: "bi:file-earmark-image"
},
{
ext: [".mp4", ".mkv", ".avi", ".mov", ".wmv", ".flv", ".webm", ".mpeg", ".3gp", ".mts"],
- icon: "bi:file-earmark-play",
+ icon: "bi:file-earmark-play"
},
{
ext: [".zip", ".rar", ".tar.gz", ".dmg", ".7z", ".bz2", ".tar", ".gz", ".xz"],
- icon: "bi:file-earmark-zip",
+ icon: "bi:file-earmark-zip"
},
{
ext: [".mp3", ".wav", ".aac", ".flac", ".ogg", ".wma", ".alac", ".m4a", ".opus", ".aiff"],
- icon: "bi:file-earmark-music",
+ icon: "bi:file-earmark-music"
},
{
ext: [
@@ -45,9 +53,9 @@ export const FILE_FORMATS = [
".sh",
".md",
".rust",
- ".pl",
+ ".pl"
],
- icon: "bi:file-earmark-code",
+ icon: "bi:file-earmark-code"
},
{
ext: [
@@ -63,28 +71,28 @@ export const FILE_FORMATS = [
".tsv",
".ods",
".db",
- ".sqlite",
+ ".sqlite"
],
- icon: "bi:file-earmark-spreadsheet",
+ icon: "bi:file-earmark-spreadsheet"
},
{
ext: [".pdf", ".epub", ".xps", ".djvu", ".cbz", ".cbt"],
- icon: "bi:file-earmark-pdf",
+ icon: "bi:file-earmark-pdf"
},
{
ext: [".ppt", ".pptx", ".odp", ".pps", ".ppsx", ".key"],
- icon: "bi:file-earmark-slides",
+ icon: "bi:file-earmark-slides"
},
{
ext: [".txt", ".doc", ".docx", ".odt", ".rtf", ".wps", ".wpd"],
- icon: "bi:file-earmark-text",
+ icon: "bi:file-earmark-text"
},
{
ext: [".lock", ".lck"],
- icon: "bi:file-earmark-lock",
+ icon: "bi:file-earmark-lock"
},
{
ext: [".exe", ".bin", ".elf", ".dll", ".iso", ".dmg", ".swf", ".o", ".obg", ".pe", ".app"],
- icon: "bi:file-earmark-binary",
- },
+ icon: "bi:file-earmark-binary"
+ }
];
\ No newline at end of file
diff --git a/frontend/nuxt.config.ts b/frontend/nuxt.config.ts
index d863918..1ef28ca 100644
--- a/frontend/nuxt.config.ts
+++ b/frontend/nuxt.config.ts
@@ -47,7 +47,7 @@ export default defineNuxtConfig({
runtimeConfig: {
public: {
- API_BASE_URL: "http://38.60.202.91:8000",
+ API_BASE_URL: "http://38.60.202.91:8001",
},
},
});
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 76a2eee..eb9b3b8 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -14,6 +14,7 @@
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"axios": "^1.7.9",
+ "fast-average-color": "^9.4.0",
"gsap": "^3.12.5",
"nuxt": "^3.14.1592",
"reka-ui": "^1.0.0-alpha.6",
@@ -5162,6 +5163,15 @@
"ufo": "^1.1.2"
}
},
+ "node_modules/fast-average-color": {
+ "version": "9.4.0",
+ "resolved": "https://registry.npmjs.org/fast-average-color/-/fast-average-color-9.4.0.tgz",
+ "integrity": "sha512-bvM8vV6YwK07dPbzFz77zJaBcfF6ABVfgNwaxVgXc2G+o0e/tzLCF9WU8Ryp1r0Nkk6JuJNsWCzbb4cLOMlB+Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index f1154d8..cb7f655 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -19,6 +19,7 @@
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"axios": "^1.7.9",
+ "fast-average-color": "^9.4.0",
"gsap": "^3.12.5",
"nuxt": "^3.14.1592",
"reka-ui": "^1.0.0-alpha.6",
diff --git a/frontend/pages/category/index.vue b/frontend/pages/category/index.vue
new file mode 100644
index 0000000..9fa1104
--- /dev/null
+++ b/frontend/pages/category/index.vue
@@ -0,0 +1,68 @@
+
+
+
+ دسته بندی ها
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/pages/product/[id].vue b/frontend/pages/product/[id].vue
index 056cfac..8d4d78a 100644
--- a/frontend/pages/product/[id].vue
+++ b/frontend/pages/product/[id].vue
@@ -1,4 +1,6 @@
-
+
diff --git a/frontend/pages/signin/index.vue b/frontend/pages/signin/index.vue
index 73d33f8..33f65fb 100644
--- a/frontend/pages/signin/index.vue
+++ b/frontend/pages/signin/index.vue
@@ -57,12 +57,22 @@ const sendOtpHandler = async () => {
phone: `0${loginInfo.value.phone}`
});
- addToast({ message: "کد برای شما ارسال شد" });
+ addToast({
+ message: "کد برای شما ارسال شد",
+ options: {
+ status: "success"
+ }
+ });
showOtp.value = true;
} catch (e) {
- addToast({ message: "مشکلی پیش آمده" });
+ addToast({
+ message: "مشکلی پیش آمده",
+ options: {
+ status: "error"
+ }
+ });
}
}
};
diff --git a/frontend/plugins/gsap.client.ts b/frontend/plugins/gsap.client.ts
index e23323b..328cba3 100644
--- a/frontend/plugins/gsap.client.ts
+++ b/frontend/plugins/gsap.client.ts
@@ -1,10 +1,11 @@
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import { ScrollToPlugin } from 'gsap/ScrollToPlugin'
+import { TextPlugin } from "gsap/TextPlugin";
export default defineNuxtPlugin(() => {
if (process.client) {
- gsap.registerPlugin(ScrollTrigger, ScrollToPlugin)
+ gsap.registerPlugin(ScrollTrigger, ScrollToPlugin, TextPlugin)
}
return {
diff --git a/frontend/public/img/ai-loading-2.gif b/frontend/public/img/ai-loading-2.gif
new file mode 100644
index 0000000..d2242eb
Binary files /dev/null and b/frontend/public/img/ai-loading-2.gif differ
diff --git a/frontend/public/img/ai-loading.gif b/frontend/public/img/ai-loading.gif
new file mode 100644
index 0000000..70f2fb1
Binary files /dev/null and b/frontend/public/img/ai-loading.gif differ
diff --git a/frontend/public/img/product-4.jpg b/frontend/public/img/product-4.jpg
new file mode 100644
index 0000000..1d92658
Binary files /dev/null and b/frontend/public/img/product-4.jpg differ
diff --git a/frontend/public/img/product-5.jpg b/frontend/public/img/product-5.jpg
new file mode 100644
index 0000000..2cacba8
Binary files /dev/null and b/frontend/public/img/product-5.jpg differ
diff --git a/frontend/public/img/test1.jpg b/frontend/public/img/test1.jpg
new file mode 100644
index 0000000..a09bb75
Binary files /dev/null and b/frontend/public/img/test1.jpg differ
diff --git a/frontend/types/global.d.ts b/frontend/types/global.d.ts
index bc70bd1..f0df3d0 100644
--- a/frontend/types/global.d.ts
+++ b/frontend/types/global.d.ts
@@ -7,4 +7,11 @@ declare global {
previous: string | null;
results: T[];
};
+
+ type Chat = {
+ id: number,
+ sender: "ai" | "user",
+ content: string
+ }
+
}