Fix chat box and make it responsive
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
// import
|
||||
|
||||
import AiLoading from "~/components/product/ChatBox/AiLoading.vue";
|
||||
import useGetChat from "~/composables/api/chat/useGetChat";
|
||||
import ChatInput from "~/components/product/ChatBox/ChatInput.vue";
|
||||
import { useIsMutating } from "@tanstack/vue-query";
|
||||
@@ -20,6 +19,8 @@ const { isLoggedIn } = useAuth();
|
||||
const route = useRoute();
|
||||
const id = route.params.id as string | number;
|
||||
|
||||
const isMobile = useMediaQuery("(max-width: 480px)");
|
||||
|
||||
const scrollToBottomTimer = ref<NodeJS.Timeout | null>(null);
|
||||
|
||||
const chatContainerEl = ref<HTMLElement | null>(null);
|
||||
@@ -31,26 +32,24 @@ const {
|
||||
isPending: isChatPending,
|
||||
isFetchingNextPage: isNextChatPagePending,
|
||||
hasNextPage: hasMoreChat,
|
||||
fetchNextPage: loadMoreChat
|
||||
fetchNextPage: loadMoreChat,
|
||||
} = useGetChat(id, isOpen);
|
||||
const isCreateMessagePending = useIsMutating({
|
||||
mutationKey: [MUTATION_KEYS.create_chat]
|
||||
mutationKey: [MUTATION_KEYS.create_chat],
|
||||
});
|
||||
|
||||
const canLoadMoreChat = ref(false);
|
||||
|
||||
const isChatScrollLocked = useScrollLock(chatContainerEl);
|
||||
const { y: chatContainerScrollY } = useScroll(chatContainerEl, {
|
||||
behavior: "smooth"
|
||||
behavior: "smooth",
|
||||
});
|
||||
|
||||
useInfiniteScroll(
|
||||
chatContainerEl,
|
||||
async () => {
|
||||
if (hasMoreChat.value && !isChatPending.value) {
|
||||
lastMessageBeforeUpdate.value = chatMessages.value
|
||||
? chatMessages.value[0].id
|
||||
: 0;
|
||||
lastMessageBeforeUpdate.value = chatMessages.value ? chatMessages.value[0].id : 0;
|
||||
await loadMoreChat();
|
||||
}
|
||||
},
|
||||
@@ -58,7 +57,7 @@ useInfiniteScroll(
|
||||
distance: 10,
|
||||
direction: "top",
|
||||
throttle: 1000,
|
||||
canLoadMore: () => canLoadMoreChat.value
|
||||
canLoadMore: () => canLoadMoreChat.value,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -103,8 +102,7 @@ watch(
|
||||
`#message-container-${lastMessageBeforeUpdate.value}`
|
||||
) as HTMLElement;
|
||||
lastChatMessageEl?.scrollIntoView();
|
||||
chatContainerEl.value!.scrollTop =
|
||||
chatContainerEl.value!.scrollTop + scrollTopOld;
|
||||
chatContainerEl.value!.scrollTop = chatContainerEl.value!.scrollTop + scrollTopOld;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -121,28 +119,31 @@ whenever(
|
||||
}, 2000);
|
||||
},
|
||||
{
|
||||
once: true
|
||||
once: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="fade-right-to-left">
|
||||
<Transition :name="isMobile ? 'fade-down' : 'fade-right-to-left'">
|
||||
<div
|
||||
v-if="isOpen"
|
||||
class="fixed z-50 right-8 bottom-8 w-[450px] transition-all duration-500 overflow-hidden h-[700px] rounded-250 shadow-2xl shadow-black/30 pt-[40px] bg-white"
|
||||
class="fixed z-50 max-xs:inset-0 xs:right-8 xs:bottom-8 w-full xs:w-[450px] transition-all duration-500 overflow-hidden h-svh xs:h-[700px] xs:rounded-250 shadow-2xl shadow-black/30 pt-[40px] bg-white"
|
||||
>
|
||||
<CloseButton :disabled="!!isCreateMessagePending" />
|
||||
|
||||
<template v-if="isLoggedIn">
|
||||
<Transition name="zoom" mode="out-in">
|
||||
<Transition
|
||||
name="zoom"
|
||||
mode="out-in"
|
||||
>
|
||||
<div
|
||||
v-if="!isChatPending"
|
||||
class="p-4.5 h-full flex flex-col justify-between gap-4"
|
||||
>
|
||||
<div
|
||||
:style="{
|
||||
maskImage: 'linear-gradient(to top, transparent, black 5%, black, black)'
|
||||
maskImage: 'linear-gradient(to top, transparent, black 5%, black, black)',
|
||||
}"
|
||||
class="hide-scrollbar flex flex-col py-7 gap-6 h-full overflow-y-auto"
|
||||
ref="chatContainerEl"
|
||||
@@ -183,7 +184,11 @@ whenever(
|
||||
v-else
|
||||
class="w-full h-full flex items-center justify-center absolute inset-0"
|
||||
>
|
||||
<AiLoading />
|
||||
<NuxtImg
|
||||
class="size-[250px] drop-shadow-2xl"
|
||||
src="/img/heymlz/heymlz-small-idle.gif"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
@@ -191,12 +196,15 @@ whenever(
|
||||
class="text-black p-6 size-full flex justify-center items-center flex-col"
|
||||
v-else
|
||||
>
|
||||
<NuxtImg class="size-[250px]" src="/img/heymlz/heymlz-loading-1.gif" alt="" />
|
||||
<NuxtImg
|
||||
class="size-[250px] drop-shadow-2xl"
|
||||
src="/img/heymlz/heymlz-small-idle.gif"
|
||||
alt=""
|
||||
/>
|
||||
<div class="flex flex-col gap-4 items-center">
|
||||
<span class="text-center typo-p-xl font-bold">سلام دوست عزیز!</span>
|
||||
<p class="text-center typo-p-md">
|
||||
من میتونم هر سوالی رو درمورد این محصول جواب بدم
|
||||
اگه میخوای شروع کنیم روی دکمه زیر کلیک کن
|
||||
من میتونم هر سوالی رو درمورد این محصول جواب بدم اگه میخوای شروع کنیم روی دکمه زیر کلیک کن
|
||||
</p>
|
||||
</div>
|
||||
<NuxtLink to="/signin">
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
showChatButton: boolean;
|
||||
};
|
||||
|
||||
// props
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
// state
|
||||
|
||||
const isOpen = ref(false);
|
||||
const isMobile = useMediaQuery("(max-width: 480px)");
|
||||
const isWindowScrollLocked = useScrollLock(window);
|
||||
|
||||
// methods
|
||||
|
||||
@@ -14,19 +26,29 @@ provide("isOpen", {
|
||||
closeChat,
|
||||
});
|
||||
|
||||
// watches
|
||||
|
||||
watch([isMobile, isOpen], ([isMobile, isOpen]) => {
|
||||
isWindowScrollLocked.value = isMobile && isOpen;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
v-if="!isOpen"
|
||||
@click="isOpen = !isOpen"
|
||||
class="cursor-pointer z-50 fixed shadow-xl shadow-black/30 right-8 bottom-8 bg-black size-[70px] flex justify-center items-center rounded-full"
|
||||
<Transition
|
||||
name="fade"
|
||||
:duration="150"
|
||||
>
|
||||
<Icon
|
||||
name="streamline:artificial-intelligence-spark"
|
||||
class="**:stroke-white size-[26px]"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
v-if="showChatButton && !isOpen"
|
||||
@click="isOpen = !isOpen"
|
||||
class="cursor-pointer z-50 fixed shadow-xl shadow-black/30 right-8 bottom-8 bg-blue-500 size-[70px] flex justify-center items-center rounded-full"
|
||||
>
|
||||
<Icon
|
||||
name="streamline:artificial-intelligence-spark"
|
||||
class="**:stroke-white size-[26px]"
|
||||
/>
|
||||
</button>
|
||||
</Transition>
|
||||
|
||||
<ChatBoxContainer :isOpen="isOpen" />
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
// types
|
||||
|
||||
import AiLoading from "~/components/product/ChatBox/AiLoading.vue";
|
||||
import useCreateChatMessage from "~/composables/api/chat/useCreateChatMessage";
|
||||
import { useToast } from "~/composables/global/useToast";
|
||||
|
||||
@@ -14,8 +13,7 @@ const { $queryClient: queryClient } = useNuxtApp();
|
||||
|
||||
const { addToast } = useToast();
|
||||
|
||||
const { mutateAsync: createMessage, isPending: isCreatingMessage } =
|
||||
useCreateChatMessage(queryClient);
|
||||
const { mutateAsync: createMessage, isPending: isCreatingMessage } = useCreateChatMessage(queryClient);
|
||||
|
||||
const chatInputEl = ref<HTMLInputElement | null>(null);
|
||||
|
||||
@@ -46,7 +44,10 @@ const sendMessage = async () => {
|
||||
|
||||
<template>
|
||||
<div class="relative">
|
||||
<div id="poda" class="poda-rotate">
|
||||
<div
|
||||
id="poda"
|
||||
class="poda-rotate"
|
||||
>
|
||||
<div
|
||||
class="glow w-full"
|
||||
:class="isCreatingMessage ? '' : '!opacity-0'"
|
||||
@@ -80,11 +81,7 @@ const sendMessage = async () => {
|
||||
<input
|
||||
ref="chatInputEl"
|
||||
:disabled="isCreatingMessage"
|
||||
:placeholder="
|
||||
isCreatingMessage
|
||||
? 'دارم فکر میکنم...'
|
||||
: 'سوال خود را بپرسید'
|
||||
"
|
||||
:placeholder="isCreatingMessage ? 'دارم فکر میکنم...' : 'سوال خود را بپرسید'"
|
||||
type="text"
|
||||
name="text"
|
||||
class="focus:outline-none h-full typo-p-sm w-full border-none"
|
||||
@@ -97,11 +94,11 @@ const sendMessage = async () => {
|
||||
:class="isCreatingMessage ? 'bg-transparent' : 'bg-black'"
|
||||
>
|
||||
<TransitionGroup name="fade-down">
|
||||
<AiLoading
|
||||
<Icon
|
||||
v-if="isCreatingMessage"
|
||||
circle
|
||||
:size="75"
|
||||
class="mb-1"
|
||||
name="svg-spinners:wind-toy"
|
||||
size="24"
|
||||
class="text-black"
|
||||
/>
|
||||
<Icon
|
||||
v-else
|
||||
@@ -188,14 +185,7 @@ const sendMessage = async () => {
|
||||
height: 600px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 0;
|
||||
background-image: conic-gradient(
|
||||
transparent,
|
||||
#998fdc,
|
||||
transparent 10%,
|
||||
transparent 50%,
|
||||
#cf7bba,
|
||||
transparent 60%
|
||||
);
|
||||
background-image: conic-gradient(transparent, #998fdc, transparent 10%, transparent 50%, #cf7bba, transparent 60%);
|
||||
transition: all 2s;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import useGetAccount from '~/composables/api/account/useGetAccount';
|
||||
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
@@ -21,6 +23,7 @@ const emit = defineEmits(["textUpdate"]);
|
||||
// state
|
||||
|
||||
const { $gsap: gsap } = useNuxtApp();
|
||||
const { data: account } = useGetAccount();
|
||||
|
||||
// methods
|
||||
|
||||
@@ -79,26 +82,32 @@ onMounted(() => {
|
||||
>
|
||||
<NuxtImg
|
||||
v-if="reverse"
|
||||
src="/img/heymlz/footer-share.svg"
|
||||
src="/img/heymlz/heymlz-full-body.jpg"
|
||||
class="size-full object-cover absolute"
|
||||
alt="profile"
|
||||
/>
|
||||
<NuxtImg
|
||||
v-else
|
||||
:src="account?.profile_photo ?? ''"
|
||||
class="size-full object-cover absolute"
|
||||
alt="profile"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="rounded-150 px-4 py-3"
|
||||
class="rounded-150 px-4 py-3 whitespace-pre-wrap overflow-hidden"
|
||||
:class="
|
||||
reverse
|
||||
? 'bg-slate-100 text-slate-600'
|
||||
: 'bg-black text-white'
|
||||
"
|
||||
>
|
||||
<p
|
||||
<div
|
||||
v-if="!loadingContent"
|
||||
:id="`chat-message-content-${id}`"
|
||||
class="typo-p-sm font-normal whitespace-pre-wrap"
|
||||
v-html="content"
|
||||
>
|
||||
{{ content }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Icon v-else name="svg-spinners:3-dots-bounce" size="20" />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user