new changes
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
// import
|
// import
|
||||||
|
|
||||||
import type { ToastOptions } from "~/composables/global/useToast";
|
import type { ToastOptions } from "~/composables/global/useToast";
|
||||||
@@ -9,9 +8,9 @@ import { useToast } from "~/composables/global/useToast";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: number;
|
id: number;
|
||||||
message: string,
|
message: string;
|
||||||
options: ToastOptions
|
options: ToastOptions;
|
||||||
}
|
};
|
||||||
|
|
||||||
// props
|
// props
|
||||||
|
|
||||||
@@ -24,7 +23,7 @@ const { destroyToast } = useToast();
|
|||||||
|
|
||||||
const open = ref(true);
|
const open = ref(true);
|
||||||
|
|
||||||
// method
|
// methods
|
||||||
|
|
||||||
const onSwipeEnd = () => {
|
const onSwipeEnd = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -41,37 +40,39 @@ const statusIcon = computed(() => {
|
|||||||
case "success":
|
case "success":
|
||||||
return {
|
return {
|
||||||
name: "duo-icons:check-circle",
|
name: "duo-icons:check-circle",
|
||||||
class: "**:fill-success-500 [filter:drop-shadow(0_0_10px_var(--color-success-500))]"
|
class: "**:fill-success-500 [filter:drop-shadow(0_0_10px_var(--color-success-500))]",
|
||||||
};
|
};
|
||||||
case "error":
|
case "error":
|
||||||
return {
|
return {
|
||||||
name: "duo-icons:alert-triangle",
|
name: "duo-icons:alert-triangle",
|
||||||
class: "**:fill-danger-500 [filter:drop-shadow(0_0_10px_var(--color-danger-500))]"
|
class: "**:fill-danger-500 [filter:drop-shadow(0_0_10px_var(--color-danger-500))]",
|
||||||
};
|
};
|
||||||
case "info":
|
case "info":
|
||||||
return {
|
return {
|
||||||
name: "duo-icons:info",
|
name: "duo-icons:info",
|
||||||
class: "**:fill-cyan-500 [filter:drop-shadow(0_0_10px_var(--color-cyan-500))]"
|
class: "**:fill-cyan-500 [filter:drop-shadow(0_0_10px_var(--color-cyan-500))]",
|
||||||
};
|
};
|
||||||
case "warning":
|
case "warning":
|
||||||
return {
|
return {
|
||||||
name: "duo-icons:alert-octagon",
|
name: "duo-icons:alert-octagon",
|
||||||
class: "**:fill-warning-500 [filter:drop-shadow(0_0_10px_var(--color-warning-500))]"
|
class: "**:fill-warning-500 [filter:drop-shadow(0_0_10px_var(--color-warning-500))]",
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
name: "duo-icons:info",
|
name: "duo-icons:info",
|
||||||
class: "**:fill-slate-500 [filter:drop-shadow(0_0_10px_var(--color-slate-500))]"
|
class: "**:fill-slate-500 [filter:drop-shadow(0_0_10px_var(--color-slate-500))]",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// watch
|
// watch
|
||||||
|
|
||||||
watch(() => open.value, (value) => {
|
watch(
|
||||||
if (!value) onSwipeEnd();
|
() => open.value,
|
||||||
});
|
(value) => {
|
||||||
|
if (!value) onSwipeEnd();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// lifecycle
|
// lifecycle
|
||||||
|
|
||||||
@@ -80,7 +81,6 @@ onMounted(() => {
|
|||||||
open.value = false;
|
open.value = false;
|
||||||
}, options.value.duration ?? 4000);
|
}, options.value.duration ?? 4000);
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -92,7 +92,7 @@ onMounted(() => {
|
|||||||
:class="options.description ? 'rounded-150' : 'rounded-full'"
|
:class="options.description ? 'rounded-150' : 'rounded-full'"
|
||||||
>
|
>
|
||||||
<ToastTitle
|
<ToastTitle
|
||||||
:class="[ { 'mb-1.5' : options.description } ]"
|
:class="[{ 'mb-1.5': options.description }]"
|
||||||
class="[grid-area:_title] font-medium text-slate-600 text-sm flex items-center gap-2"
|
class="[grid-area:_title] font-medium text-slate-600 text-sm flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<Icon :name="statusIcon.name" :class="statusIcon.class" size="24" />
|
<Icon :name="statusIcon.name" :class="statusIcon.class" size="24" />
|
||||||
@@ -106,4 +106,4 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</ToastDescription>
|
</ToastDescription>
|
||||||
</ToastRoot>
|
</ToastRoot>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
// types
|
// types
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
selectedSlide: number;
|
selectedSlide: number;
|
||||||
slides: ProductImage[]
|
slides: ProductImage[];
|
||||||
}
|
};
|
||||||
|
|
||||||
// props
|
// props
|
||||||
|
|
||||||
@@ -23,17 +22,18 @@ const selectedSlideDetail = computed(() => {
|
|||||||
})!;
|
})!;
|
||||||
});
|
});
|
||||||
|
|
||||||
// method
|
// methods
|
||||||
|
|
||||||
const changeSlide = (id: number) => {
|
const changeSlide = (id: number) => {
|
||||||
emit("update:selectedSlide", id);
|
emit("update:selectedSlide", id);
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col relative gap-6">
|
<div class="flex flex-col relative gap-6">
|
||||||
<div class="bg-white brightness-[97%] w-full relative aspect-square overflow-hidden rounded-200">
|
<div
|
||||||
|
class="bg-white brightness-[97%] w-full relative aspect-square overflow-hidden rounded-200"
|
||||||
|
>
|
||||||
<Transition name="zoom" mode="out-in">
|
<Transition name="zoom" mode="out-in">
|
||||||
<img
|
<img
|
||||||
:key="selectedSlideDetail.id"
|
:key="selectedSlideDetail.id"
|
||||||
@@ -47,7 +47,11 @@ const changeSlide = (id: number) => {
|
|||||||
<div
|
<div
|
||||||
@click="changeSlide(slide.id)"
|
@click="changeSlide(slide.id)"
|
||||||
v-for="slide in slides"
|
v-for="slide in slides"
|
||||||
:class="selectedSlide === slide.id ? '!ring-black' : 'ring-transparent'"
|
:class="
|
||||||
|
selectedSlide === slide.id
|
||||||
|
? '!ring-black'
|
||||||
|
: 'ring-transparent'
|
||||||
|
"
|
||||||
class="active:scale-95 hover:ring-slate-200 transition-all cursor-pointer brightness-[97%] bg-white aspect-square ring-2 ring-offset-4 rounded-200 w-full overflow-hidden relative"
|
class="active:scale-95 hover:ring-slate-200 transition-all cursor-pointer brightness-[97%] bg-white aspect-square ring-2 ring-offset-4 rounded-200 w-full overflow-hidden relative"
|
||||||
:key="slide.id"
|
:key="slide.id"
|
||||||
>
|
>
|
||||||
@@ -59,4 +63,4 @@ const changeSlide = (id: number) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -29,17 +29,17 @@ const {
|
|||||||
isPending: isChatPending,
|
isPending: isChatPending,
|
||||||
isFetchingNextPage: isNextChatPagePending,
|
isFetchingNextPage: isNextChatPagePending,
|
||||||
hasNextPage: hasMoreChat,
|
hasNextPage: hasMoreChat,
|
||||||
fetchNextPage: loadMoreChat
|
fetchNextPage: loadMoreChat,
|
||||||
} = useGetChat(id, isOpen);
|
} = useGetChat(id, isOpen);
|
||||||
const isCreateMessagePending = useIsMutating({
|
const isCreateMessagePending = useIsMutating({
|
||||||
mutationKey: [MUTATION_KEYS.create_chat]
|
mutationKey: [MUTATION_KEYS.create_chat],
|
||||||
});
|
});
|
||||||
|
|
||||||
const canLoadMoreChat = ref(false);
|
const canLoadMoreChat = ref(false);
|
||||||
|
|
||||||
const isChatScrollLocked = useScrollLock(chatContainerEl);
|
const isChatScrollLocked = useScrollLock(chatContainerEl);
|
||||||
const { y: chatContainerScrollY } = useScroll(chatContainerEl, {
|
const { y: chatContainerScrollY } = useScroll(chatContainerEl, {
|
||||||
behavior: "smooth"
|
behavior: "smooth",
|
||||||
});
|
});
|
||||||
|
|
||||||
useInfiniteScroll(
|
useInfiniteScroll(
|
||||||
@@ -56,11 +56,11 @@ useInfiniteScroll(
|
|||||||
distance: 10,
|
distance: 10,
|
||||||
direction: "top",
|
direction: "top",
|
||||||
throttle: 1000,
|
throttle: 1000,
|
||||||
canLoadMore: () => canLoadMoreChat.value
|
canLoadMore: () => canLoadMoreChat.value,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// method
|
// methods
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
chatContainerScrollY.value = chatContainerEl.value?.scrollHeight ?? 0;
|
chatContainerScrollY.value = chatContainerEl.value?.scrollHeight ?? 0;
|
||||||
@@ -116,7 +116,7 @@ whenever(
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
once: true
|
once: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
@@ -131,16 +131,15 @@ whenever(
|
|||||||
|
|
||||||
<template v-if="isLoggedIn">
|
<template v-if="isLoggedIn">
|
||||||
<Transition name="zoom" mode="out-in">
|
<Transition name="zoom" mode="out-in">
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="!isChatPending"
|
v-if="!isChatPending"
|
||||||
class="p-4.5 h-full flex flex-col justify-between gap-4"
|
class="p-4.5 h-full flex flex-col justify-between gap-4"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:style="{
|
:style="{
|
||||||
maskImage:
|
maskImage:
|
||||||
'linear-gradient(to top, transparent, black 5%, black, black)',
|
'linear-gradient(to top, transparent, black 5%, black, black)',
|
||||||
}"
|
}"
|
||||||
class="hide-scrollbar flex flex-col py-7 gap-6 h-full overflow-y-auto"
|
class="hide-scrollbar flex flex-col py-7 gap-6 h-full overflow-y-auto"
|
||||||
ref="chatContainerEl"
|
ref="chatContainerEl"
|
||||||
>
|
>
|
||||||
@@ -148,7 +147,10 @@ whenever(
|
|||||||
v-if="hasMoreChat"
|
v-if="hasMoreChat"
|
||||||
class="py-2 flex items-center justify-center"
|
class="py-2 flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<Icon name="svg-spinners:3-dots-fade" size="24" />
|
<Icon
|
||||||
|
name="svg-spinners:3-dots-fade"
|
||||||
|
size="24"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
v-for="(message, index) in chatMessages"
|
v-for="(message, index) in chatMessages"
|
||||||
@@ -173,7 +175,6 @@ whenever(
|
|||||||
<ChatInput />
|
<ChatInput />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="w-full h-full flex items-center justify-center absolute inset-0"
|
class="w-full h-full flex items-center justify-center absolute inset-0"
|
||||||
@@ -182,7 +183,10 @@ whenever(
|
|||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</template>
|
</template>
|
||||||
<div class="text-black p-4.5 size-full flex justify-center items-center" v-else>
|
<div
|
||||||
|
class="text-black p-4.5 size-full flex justify-center items-center"
|
||||||
|
v-else
|
||||||
|
>
|
||||||
Please sign in first
|
Please sign in first
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,24 +1,21 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
// state
|
// state
|
||||||
|
|
||||||
const isOpen = ref(false);
|
const isOpen = ref(false);
|
||||||
|
|
||||||
// method
|
// methods
|
||||||
|
|
||||||
const closeChat = () => isOpen.value = false;
|
const closeChat = () => (isOpen.value = false);
|
||||||
|
|
||||||
// provide-inject
|
// provide-inject
|
||||||
|
|
||||||
provide("isOpen", {
|
provide("isOpen", {
|
||||||
isOpen,
|
isOpen,
|
||||||
closeChat
|
closeChat,
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
v-if="!isOpen"
|
v-if="!isOpen"
|
||||||
@click="isOpen = !isOpen"
|
@click="isOpen = !isOpen"
|
||||||
@@ -32,5 +29,4 @@ provide("isOpen", {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ChatBoxContainer :isOpen="isOpen" />
|
<ChatBoxContainer :isOpen="isOpen" />
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
// types
|
// types
|
||||||
|
|
||||||
import AiLoading from "~/components/product/ChatBox/AiLoading.vue";
|
import AiLoading from "~/components/product/ChatBox/AiLoading.vue";
|
||||||
@@ -12,12 +11,12 @@ const { $queryClient: queryClient } = useNuxtApp();
|
|||||||
|
|
||||||
const { addToast } = useToast();
|
const { addToast } = useToast();
|
||||||
|
|
||||||
const { mutateAsync: createMessage, isPending: isCreatingMessage } = useCreateChatMessage(queryClient);
|
const { mutateAsync: createMessage, isPending: isCreatingMessage } =
|
||||||
|
useCreateChatMessage(queryClient);
|
||||||
|
|
||||||
const chatInputEl = ref<HTMLInputElement | null>(null);
|
const chatInputEl = ref<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
|
// methods
|
||||||
// method
|
|
||||||
|
|
||||||
const sendMessage = async () => {
|
const sendMessage = async () => {
|
||||||
const value = chatInputEl.value!.value;
|
const value = chatInputEl.value!.value;
|
||||||
@@ -28,20 +27,18 @@ const sendMessage = async () => {
|
|||||||
|
|
||||||
await createMessage({
|
await createMessage({
|
||||||
new_message: value,
|
new_message: value,
|
||||||
productId: 1
|
productId: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
addToast({
|
addToast({
|
||||||
message: "مشکلی پیش آمده",
|
message: "مشکلی پیش آمده",
|
||||||
options: {
|
options: {
|
||||||
status: "error"
|
status: "error",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -71,12 +68,20 @@ const sendMessage = async () => {
|
|||||||
|
|
||||||
<form
|
<form
|
||||||
class="transition-all duration-200 relative flex items-center gap-4 w-full shadow-sm rounded-full h-[56px] border pe-3 ps-4"
|
class="transition-all duration-200 relative flex items-center gap-4 w-full shadow-sm rounded-full h-[56px] border pe-3 ps-4"
|
||||||
:class="isCreatingMessage ? 'border-transparent shadow-black/10 bg-white/85 backdrop-blur-xl' : 'border-slate-200 shadow-transparent bg-white'"
|
:class="
|
||||||
|
isCreatingMessage
|
||||||
|
? 'border-transparent shadow-black/10 bg-white/85 backdrop-blur-xl'
|
||||||
|
: 'border-slate-200 shadow-transparent bg-white'
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
ref="chatInputEl"
|
ref="chatInputEl"
|
||||||
:disabled="isCreatingMessage"
|
:disabled="isCreatingMessage"
|
||||||
:placeholder="isCreatingMessage ? 'دارم فکر میکنم...' : 'سوال خود را بپرسید'"
|
:placeholder="
|
||||||
|
isCreatingMessage
|
||||||
|
? 'دارم فکر میکنم...'
|
||||||
|
: 'سوال خود را بپرسید'
|
||||||
|
"
|
||||||
type="text"
|
type="text"
|
||||||
name="text"
|
name="text"
|
||||||
class="focus:outline-none h-full typo-p-sm w-full border-none"
|
class="focus:outline-none h-full typo-p-sm w-full border-none"
|
||||||
@@ -89,13 +94,21 @@ const sendMessage = async () => {
|
|||||||
:class="isCreatingMessage ? 'bg-transparent' : 'bg-black'"
|
:class="isCreatingMessage ? 'bg-transparent' : 'bg-black'"
|
||||||
>
|
>
|
||||||
<TransitionGroup name="fade-down">
|
<TransitionGroup name="fade-down">
|
||||||
<AiLoading v-if="isCreatingMessage" circle :size="75" class="mb-1" />
|
<AiLoading
|
||||||
<Icon v-else name="iconamoon:send-light" class="absolute rotate-180 **:stroke-white" />
|
v-if="isCreatingMessage"
|
||||||
|
circle
|
||||||
|
:size="75"
|
||||||
|
class="mb-1"
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
v-else
|
||||||
|
name="iconamoon:send-light"
|
||||||
|
class="absolute rotate-180 **:stroke-white"
|
||||||
|
/>
|
||||||
</TransitionGroup>
|
</TransitionGroup>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -303,4 +316,4 @@ const sendMessage = async () => {
|
|||||||
transform: translate(-50%, -50%) rotate(443deg);
|
transform: translate(-50%, -50%) rotate(443deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
// types
|
// types
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: number,
|
id: number;
|
||||||
reverse?: boolean,
|
reverse?: boolean;
|
||||||
content: string,
|
content: string;
|
||||||
isLast?: boolean,
|
isLast?: boolean;
|
||||||
loadingContent?: boolean
|
loadingContent?: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
// props
|
// props
|
||||||
|
|
||||||
@@ -23,20 +22,24 @@ const emit = defineEmits(["textUpdate"]);
|
|||||||
|
|
||||||
const { $gsap: gsap } = useNuxtApp();
|
const { $gsap: gsap } = useNuxtApp();
|
||||||
|
|
||||||
// method
|
// methods
|
||||||
|
|
||||||
const showMessage = () => {
|
const showMessage = () => {
|
||||||
gsap.fromTo(`#message-container-${id.value}`, {
|
gsap.fromTo(
|
||||||
rotateX: -50,
|
`#message-container-${id.value}`,
|
||||||
translateY: -40,
|
{
|
||||||
opacity: 0
|
rotateX: -50,
|
||||||
}, {
|
translateY: -40,
|
||||||
rotateX: 0,
|
opacity: 0,
|
||||||
translateY: 0,
|
},
|
||||||
opacity: 1,
|
{
|
||||||
duration: 0.5,
|
rotateX: 0,
|
||||||
ease: "expo.out"
|
translateY: 0,
|
||||||
});
|
opacity: 1,
|
||||||
|
duration: 0.5,
|
||||||
|
ease: "expo.out",
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// lifecycle
|
// lifecycle
|
||||||
@@ -47,19 +50,22 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (reverse.value && isLast.value) {
|
if (reverse.value && isLast.value) {
|
||||||
gsap.fromTo(`#chat-message-content-${id.value}`, {
|
gsap.fromTo(
|
||||||
text: "",
|
`#chat-message-content-${id.value}`,
|
||||||
duration: 2.5,
|
{
|
||||||
ease: "none"
|
text: "",
|
||||||
}, {
|
duration: 2.5,
|
||||||
text: { value: content.value, rtl: false },
|
ease: "none",
|
||||||
duration: 2.5,
|
},
|
||||||
ease: "none",
|
{
|
||||||
onUpdate: () => emit("textUpdate")
|
text: { value: content.value, rtl: false },
|
||||||
});
|
duration: 2.5,
|
||||||
|
ease: "none",
|
||||||
|
onUpdate: () => emit("textUpdate"),
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -81,7 +87,11 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="rounded-150 px-4 py-3"
|
class="rounded-150 px-4 py-3"
|
||||||
:class="reverse ? 'bg-slate-100 text-slate-600' : 'bg-black text-white'"
|
:class="
|
||||||
|
reverse
|
||||||
|
? 'bg-slate-100 text-slate-600'
|
||||||
|
: 'bg-black text-white'
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
v-if="!loadingContent"
|
v-if="!loadingContent"
|
||||||
@@ -91,12 +101,8 @@ onMounted(() => {
|
|||||||
{{ content }}
|
{{ content }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Icon
|
<Icon v-else name="svg-spinners:3-dots-bounce" size="20" />
|
||||||
v-else
|
|
||||||
name="svg-spinners:3-dots-bounce"
|
|
||||||
size="20"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
// import
|
// import
|
||||||
|
|
||||||
import useGetComments from "~/composables/api/product/useGetComments";
|
import useGetComments from "~/composables/api/product/useGetComments";
|
||||||
@@ -16,15 +15,16 @@ const page = ref(1);
|
|||||||
const { token } = useAuth();
|
const { token } = useAuth();
|
||||||
const userComment = ref("");
|
const userComment = ref("");
|
||||||
|
|
||||||
const { data: comments, refetch : refetchComments } = useGetComments(id, page);
|
const { data: comments, refetch: refetchComments } = useGetComments(id, page);
|
||||||
const { mutateAsync: createComment, isPending: isCreateCommentPending } = useCreateComment(id);
|
const { mutateAsync: createComment, isPending: isCreateCommentPending } =
|
||||||
|
useCreateComment(id);
|
||||||
|
|
||||||
// method
|
// methods
|
||||||
|
|
||||||
const submitComment = async () => {
|
const submitComment = async () => {
|
||||||
if (userComment.value.length > 3) {
|
if (userComment.value.length > 3) {
|
||||||
await createComment({
|
await createComment({
|
||||||
content: userComment.value
|
content: userComment.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
userComment.value = "";
|
userComment.value = "";
|
||||||
@@ -32,23 +32,25 @@ const submitComment = async () => {
|
|||||||
await refetchComments();
|
await refetchComments();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="bg-slate-50">
|
<section class="bg-slate-50">
|
||||||
<div class="flex relative gap-8 my-42 container">
|
<div class="flex relative gap-8 my-42 container">
|
||||||
<div class="sticky top-0 flex flex-col gap-6 min-w-[400px] max-h-min bg-white p-8 rounded-xl border-[0.5px] border-slate-200">
|
<div
|
||||||
<h3 class="typo-h-3">
|
class="sticky top-0 flex flex-col gap-6 min-w-[400px] max-h-min bg-white p-8 rounded-xl border-[0.5px] border-slate-200"
|
||||||
نظرات کاربران
|
>
|
||||||
</h3>
|
<h3 class="typo-h-3">نظرات کاربران</h3>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<Rating />
|
<Rating />
|
||||||
<span class="typo-p-sm">
|
<span class="typo-p-sm">
|
||||||
بر اساس {{ comments?.count }} نظر
|
بر اساس {{ comments?.count }} نظر
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<form @submit.prevent="submitComment" class="flex flex-col gap-6">
|
<form
|
||||||
|
@submit.prevent="submitComment"
|
||||||
|
class="flex flex-col gap-6"
|
||||||
|
>
|
||||||
<textarea
|
<textarea
|
||||||
:disabled="!token"
|
:disabled="!token"
|
||||||
class="w-full min-h-[200px] field-sizing-content rounded-xl bg-white p-4 border border-slate-200"
|
class="w-full min-h-[200px] field-sizing-content rounded-xl bg-white p-4 border border-slate-200"
|
||||||
@@ -65,10 +67,7 @@ const submitComment = async () => {
|
|||||||
نظر بنویسید
|
نظر بنویسید
|
||||||
</Button>
|
</Button>
|
||||||
<NuxtLink v-else to="/signin">
|
<NuxtLink v-else to="/signin">
|
||||||
<Button
|
<Button type="button" class="rounded-full w-full">
|
||||||
type="button"
|
|
||||||
class="rounded-full w-full"
|
|
||||||
>
|
|
||||||
وارد شوید
|
وارد شوید
|
||||||
</Button>
|
</Button>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
@@ -90,7 +89,6 @@ const submitComment = async () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user