Files
webMontime/src/components/booking/BookingReviewHost.vue
T
2026-06-29 19:32:19 +03:30

218 lines
6.2 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<AppModal
:open="reviewOpen"
:title="`امتیاز به ${activeReview?.businessTitle ?? 'کسب‌وکار'}`"
subtitle="از ۱ تا ۵ ستاره — تجربه خود را با ما به اشتراک بگذارید"
@close="dismissReview"
>
<div class="space-y-6">
<p class="text-center text-sm text-muted">
خدمت: {{ activeReview?.serviceTitle ?? '—' }}
</p>
<StarRating v-model="rating" />
<template v-if="activeReview?.tipsEnabled">
<div class="rounded-2xl border border-amber-500/25 bg-amber-50/80 p-4 dark:bg-amber-500/10">
<h3 class="text-sm font-bold text-text">حسن انجام کار</h3>
<p class="mt-2 text-sm leading-7 text-muted">
در صورتی که از خدمات ارائه‌شده توسط
<span class="font-semibold text-text">{{ activeReview?.businessTitle }}</span>
رضایت دارید، می‌توانید مبلغی را به‌عنوان قدردانی اختصاص دهید.
</p>
</div>
<p v-if="error" class="text-sm text-red-600">{{ error }}</p>
<div class="flex flex-col gap-2 sm:flex-row">
<AppButton
class="flex-1"
variant="outline"
:disabled="!rating || submitting"
@click="submitWithoutTip"
>
تمایل ندارم
</AppButton>
<AppButton
class="flex-1"
:disabled="!rating || submitting"
@click="openTipModal"
>
تمایل دارم
</AppButton>
</div>
</template>
<template v-else>
<p v-if="error" class="text-sm text-red-600">{{ error }}</p>
<AppButton
class="w-full"
:disabled="!rating || submitting"
:loading="submitting"
@click="submitWithoutTip"
>
ثبت امتیاز
</AppButton>
</template>
</div>
</AppModal>
<AppModal
:open="tipOpen"
title="حسن انجام کار"
:subtitle="`مبلغ قدردانی برای ${activeReview?.businessTitle ?? 'کسب‌وکار'}`"
@close="closeTipModal"
>
<form class="space-y-4" @submit.prevent="submitTip">
<AppInput
id="tipAmount"
v-model="tipAmountInput"
label="مبلغ (تومان)"
inputmode="numeric"
ltr
placeholder="مثلاً ۵۰٬۰۰۰"
required
/>
<p v-if="tipError" class="text-sm text-red-600">{{ tipError }}</p>
<AppButton type="submit" class="w-full" :loading="tipSubmitting" :disabled="!tipAmountToman">
پرداخت
</AppButton>
</form>
</AppModal>
</template>
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue';
import {
fetchPendingReviews,
initiateBookingTip,
submitBookingReview,
type PendingBookingReview,
} from '@/api/bookings';
import { ApiError } from '@/api/client';
import StarRating from '@/components/booking/StarRating.vue';
import AppButton from '@/components/ui/AppButton.vue';
import AppInput from '@/components/ui/AppInput.vue';
import AppModal from '@/components/ui/AppModal.vue';
import { useAuthStore } from '@/stores/auth';
import { parsePriceInput } from '@/utils/format';
const auth = useAuthStore();
const pending = ref<PendingBookingReview[]>([]);
const activeReview = ref<PendingBookingReview | null>(null);
const reviewOpen = ref(false);
const tipOpen = ref(false);
const rating = ref(0);
const error = ref('');
const submitting = ref(false);
const tipAmountInput = ref('');
const tipError = ref('');
const tipSubmitting = ref(false);
const tipAmountToman = computed(() => parsePriceInput(tipAmountInput.value));
async function loadPending() {
if (!auth.isLoggedIn || auth.isBusinessOwner) return;
try {
pending.value = await fetchPendingReviews();
if (!activeReview.value && pending.value.length) {
activeReview.value = pending.value[0] ?? null;
reviewOpen.value = true;
}
} catch {
/* ignore */
}
}
function resetForm() {
rating.value = 0;
error.value = '';
tipAmountInput.value = '';
tipError.value = '';
}
function dismissReview() {
reviewOpen.value = false;
resetForm();
if (pending.value.length > 1) {
pending.value = pending.value.slice(1);
activeReview.value = pending.value[0] ?? null;
if (activeReview.value) {
reviewOpen.value = true;
}
} else {
activeReview.value = null;
pending.value = [];
}
}
function openTipModal() {
if (!rating.value) return;
tipOpen.value = true;
}
function closeTipModal() {
tipOpen.value = false;
tipError.value = '';
}
async function submitWithoutTip() {
if (!activeReview.value || !rating.value) return;
submitting.value = true;
error.value = '';
try {
await submitBookingReview(activeReview.value.bookingId, rating.value);
reviewOpen.value = false;
resetForm();
pending.value = pending.value.filter(
(item) => item.bookingId !== activeReview.value?.bookingId,
);
activeReview.value = pending.value[0] ?? null;
if (activeReview.value) {
reviewOpen.value = true;
}
} catch (e) {
error.value = e instanceof ApiError ? e.message : 'ثبت امتیاز ناموفق بود.';
} finally {
submitting.value = false;
}
}
async function submitTip() {
if (!activeReview.value || !rating.value || !tipAmountToman.value) return;
const bookingId = activeReview.value.bookingId;
tipSubmitting.value = true;
tipError.value = '';
try {
await submitBookingReview(bookingId, rating.value);
const result = await initiateBookingTip(bookingId, tipAmountToman.value);
tipOpen.value = false;
reviewOpen.value = false;
resetForm();
pending.value = pending.value.filter((item) => item.bookingId !== bookingId);
activeReview.value = pending.value[0] ?? null;
if (result.mock) {
if (activeReview.value) reviewOpen.value = true;
return;
}
window.location.href = result.startUrl;
} catch (e) {
tipError.value = e instanceof ApiError ? e.message : 'پرداخت ناموفق بود.';
} finally {
tipSubmitting.value = false;
}
}
watch(
() => auth.isLoggedIn,
(loggedIn) => {
if (loggedIn) void loadPending();
},
);
onMounted(() => {
void loadPending();
});
</script>