Update comment flow

This commit is contained in:
marzban-dev
2026-05-14 21:18:44 +03:30
parent b57d58aa92
commit 6796213ccf
9 changed files with 168 additions and 30 deletions
+122 -21
View File
@@ -3,8 +3,17 @@
import useGetComments from "~/composables/api/product/useGetComments";
import useCreateComment from "~/composables/api/product/useCreateComment";
import useRateProduct from "~/composables/api/product/useRateProduct";
import { useAuth } from "~/composables/api/auth/useAuth";
// props
type Props = {
product: Product;
};
const props = defineProps<Props>();
// state
const route = useRoute();
@@ -13,25 +22,66 @@ const id = route.params.id as string | undefined;
const page = ref(1);
const { token } = useAuth();
const userTitle = ref("");
const userComment = ref("");
const selectedRating = ref(5);
const showMoreComments = ref(false);
const { data: comments, refetch: refetchComments } = useGetComments(id, page);
const { mutateAsync: createComment, isPending: isCreateCommentPending } = useCreateComment(id);
const { mutateAsync: rateProduct, isPending: isRateProductPending } = useRateProduct(props.product.slug);
watch(
() => props.product.user_rating,
(newUserRating) => {
if (token.value && newUserRating !== null) {
selectedRating.value = newUserRating;
} else {
selectedRating.value = 5;
}
},
{ immediate: true }
);
const canSubmitComment = computed(() => {
return userTitle.value.trim().length > 0 && userComment.value.trim().length > 0;
});
const hasUserRated = computed(() => {
return token.value && props.product.user_rating !== null;
});
// methods
const submitComment = async () => {
if (userComment.value.length > 3) {
await createComment({
content: userComment.value,
});
userComment.value = "";
await refetchComments();
if (!canSubmitComment.value) {
return;
}
const promises: Promise<any>[] = [
createComment({
title: userTitle.value,
content: userComment.value,
}),
];
// Only submit rating if user hasn't rated before
if (!hasUserRated.value) {
promises.push(
rateProduct({
rating: selectedRating.value,
})
);
}
await Promise.all(promises);
userTitle.value = "";
userComment.value = "";
selectedRating.value = 5;
await refetchComments();
};
// computed
@@ -52,20 +102,69 @@ const limitedComments = computed(() => {
>
<div class="flex relative gap-8 my-42 container max-lg:flex-col">
<div
class="sticky top-0 flex flex-col gap-6 lg:min-w-[400px] h-fit bg-white p-8 rounded-xl border-[0.5px] border-slate-200"
class="sticky top-0 flex flex-col gap-6 lg:min-w-100 h-fit bg-white p-8 rounded-xl border-[0.5px] border-slate-200"
>
<h3 class="typo-h-6 max-sm:text-xl md:typo-h-5 lg:typo-h-4">نظرات کاربران</h3>
<div class="flex flex-col gap-2">
<Rating :rate="2" />
<!-- <div class="flex flex-col gap-2">
<Rating :rate="props.product.rating" />
<span class="typo-p-sm"> بر اساس {{ comments?.count }} نظر </span>
</div>
</div> -->
<form
@submit.prevent="submitComment"
class="flex flex-col gap-6"
>
<div v-if="hasUserRated" class="flex flex-col gap-3 px-4 py-3 bg-white rounded-lg border border-slate-300">
<div class="flex flex-col gap-2">
<span class="typo-p-xs text-slate-700 font-semibold">امتیاز قبلی شما</span>
<div class="flex items-center gap-1">
<button
v-for="star in 5"
:key="`prev-${star}`"
type="button"
disabled
class="size-9 rounded-full flex-center cursor-not-allowed opacity-75"
:class="star <= (props.product.user_rating || 0) ? 'bg-amber-50' : 'bg-slate-50'"
>
<Icon
name="ci:star-solid"
class="size-5"
:class="star <= (props.product.user_rating || 0) ? '**:fill-yellow-500' : '**:fill-slate-300'"
/>
</button>
<span class="typo-p-sm font-semibold text-slate-600 mr-2">{{ props.product.user_rating }}</span>
</div>
</div>
</div>
<div v-else class="flex flex-col gap-2">
<span class="typo-p-sm text-slate-500">امتیاز شما</span>
<div class="flex items-center gap-1">
<button
v-for="star in 5"
:key="star"
type="button"
class="size-9 rounded-full flex-center transition-colors"
:class="star <= selectedRating ? 'bg-amber-50' : 'bg-slate-50'"
:disabled="!token || isCreateCommentPending || isRateProductPending"
@click="selectedRating = star"
>
<Icon
name="ci:star-solid"
class="size-5"
:class="star <= selectedRating ? '**:fill-yellow-500' : '**:fill-slate-300'"
/>
</button>
<span class="typo-p-sm font-semibold text-slate-500 mr-2">{{ selectedRating }}</span>
</div>
</div>
<Input
v-model="userTitle"
:disabled="!token"
placeholder="عنوان نظر را بنویسید..."
variant="outlined"
/>
<textarea
:disabled="!token"
class="w-full min-h-[125px] resize-none sm:min-h-[200px] field-sizing-content rounded-xl bg-white p-4 border border-slate-200 placeholder:text-xs lg:placeholder:text-sm placeholder:font-normal"
class="w-full min-h-31.25 resize-none sm:min-h-50 field-sizing-content rounded-xl bg-white p-4 border border-slate-300 placeholder:text-xs lg:placeholder:text-sm placeholder:font-normal"
v-model="userComment"
placeholder="نظر خود را بنویسید..."
/>
@@ -73,10 +172,10 @@ const limitedComments = computed(() => {
v-if="token"
type="submit"
class="rounded-full w-full"
:loading="isCreateCommentPending"
:disabled="isCreateCommentPending"
:loading="isCreateCommentPending || isRateProductPending"
:disabled="isCreateCommentPending || isRateProductPending || !canSubmitComment"
>
نظر بنویسید
ثبت نظر
</Button>
<NuxtLink
v-else
@@ -95,10 +194,11 @@ const limitedComments = computed(() => {
<Comment
v-for="comment in limitedComments"
:key="comment.id"
title=""
:title="comment.title"
:content="comment.content"
:date="comment.timestamp"
:username="'منصور مرزبان'"
:username="comment.user"
:rate="product.user_rating ?? 0"
/>
<div
@@ -109,6 +209,7 @@ const limitedComments = computed(() => {
v-if="showMoreComments"
:total="comments.count"
:items="comments.results.map((item, i) => ({ type: 'page', value: i }))"
:per-page="3"
/>
<Button
v-else
@@ -123,15 +224,15 @@ const limitedComments = computed(() => {
</div>
<div
v-else
class="h-[400px] lg:flex-grow w-full border-[0.5px] flex-col-center border-slate-200 bg-white rounded-xl"
class="h-100 lg:grow w-full border-[0.5px] flex-col-center border-slate-200 bg-white rounded-xl"
>
<NuxtImg
src="/img/heymlz/heymlz-contact-us.gif"
loading="lazy"
fetch-priority="low"
class="w-[200px] lg:w-[300px] translate-y-[-25px]"
class="w-50 lg:w-75 -translate-y-6.25"
/>
<span class="text-xl text-black font-semibold translate-y-[-25px]"> هیچ نظری ثبت نشده است </span>
<span class="text-xl text-black font-semibold -translate-y-6.25"> هیچ نظری ثبت نشده است </span>
</div>
</div>
</div>