Update comment flow
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -61,7 +61,7 @@ watch(
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
@@ -72,7 +72,7 @@ watch(
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -136,7 +136,7 @@ watch(
|
||||
<span class="max-sm:hidden"> تخفیف درصد </span>
|
||||
</div>
|
||||
</div>
|
||||
<Rating :rate="3" />
|
||||
<Rating :rate="product!.rating" />
|
||||
</div>
|
||||
</div>
|
||||
<Slider
|
||||
@@ -238,7 +238,7 @@ watch(
|
||||
<div class="flex items-center gap-6 flex-wrap">
|
||||
<ProductVariant
|
||||
@click="selectedVariantId = variant.id"
|
||||
v-for="variant in product!.variants.filter(p => p.color === selectedColor)"
|
||||
v-for="variant in product!.variants.filter((p) => p.color === selectedColor)"
|
||||
:key="variant.id"
|
||||
:variantDetail="variant"
|
||||
:isSelected="selectedVariantId === variant.id"
|
||||
|
||||
Reference in New Issue
Block a user