tipsEnabled
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user