awaiting_payment

This commit is contained in:
2026-06-29 15:49:13 +03:30
parent ddc24612cb
commit 54e2d31a51
11 changed files with 363 additions and 15 deletions
+14 -1
View File
@@ -65,9 +65,17 @@
</div>
<div
v-if="canConfirm || canCancel"
v-if="canConfirm || canCancel || canPay"
class="mt-auto flex flex-wrap gap-2 border-t border-border pt-3"
>
<AppButton
v-if="canPay"
class="!w-auto flex-1 px-3"
:loading="acting"
@click="emit('pay', bookings)"
>
پرداخت
</AppButton>
<AppButton
v-if="canConfirm"
class="!w-auto flex-1 px-3"
@@ -106,11 +114,13 @@ const props = defineProps<{
showCustomer: boolean;
canConfirm: boolean;
canCancel: boolean;
canPay: boolean;
}>();
const emit = defineEmits<{
confirm: [bookings: Booking[]];
cancel: [bookings: Booking[]];
pay: [bookings: Booking[]];
}>();
const primary = computed(() => props.bookings[0]!);
@@ -139,6 +149,9 @@ function statusClassFor(status: BookingStatus | 'mixed') {
if (status === 'confirmed' || status === 'completed') {
return 'bg-emerald-500/15 text-emerald-700 dark:text-emerald-400';
}
if (status === 'awaiting_payment') {
return 'bg-sky-500/15 text-sky-700 dark:text-sky-400';
}
if (status === 'pending') {
return 'bg-amber-500/15 text-amber-700 dark:text-amber-400';
}
+23 -4
View File
@@ -16,9 +16,9 @@
</div>
<div>
<h2 class="text-2xl font-bold text-text">رزرو شما با موفقیت ثبت شد!</h2>
<h2 class="text-2xl font-bold text-text">{{ title }}</h2>
<p class="mt-2 text-sm text-muted">
جزئیات رزرو برای شما پیامک و در بخش رزروها ذخیره شد.
{{ subtitle }}
</p>
</div>
@@ -61,7 +61,10 @@
</div>
<div class="flex flex-col items-stretch gap-2 sm:flex-row sm:flex-wrap sm:items-center sm:justify-center sm:gap-3">
<AppButton @click="emit('view-bookings')">
<AppButton v-if="showPayButton" @click="emit('pay')">
پرداخت آنلاین
</AppButton>
<AppButton :variant="showPayButton ? 'outline' : 'primary'" @click="emit('view-bookings')">
مشاهده رزروها
</AppButton>
<AppButton variant="outline" @click="emit('add-to-calendar')">
@@ -81,22 +84,38 @@
</template>
<script setup lang="ts">
import { computed } from 'vue';
import AppButton from '@/components/ui/AppButton.vue';
import AppIcon from '@/components/ui/AppIcon.vue';
defineProps<{
const props = defineProps<{
serviceTitle: string;
dateLabel: string;
timeLabel: string;
durationLabel: string;
address?: string | null;
bookingRef: string;
successMode?: 'pending_approval' | 'awaiting_payment';
showPayButton?: boolean;
}>();
const title = computed(() =>
props.successMode === 'awaiting_payment'
? 'رزرو تأیید شد — آماده پرداخت'
: 'درخواست رزرو ثبت شد',
);
const subtitle = computed(() =>
props.successMode === 'awaiting_payment'
? 'برای نهایی شدن رزرو، پرداخت آنلاین را انجام دهید.'
: 'درخواست شما برای صاحب کسب‌وکار ارسال شد. پس از تأیید، لینک پرداخت در اپ و پیامک برای شما ارسال می‌شود.',
);
const emit = defineEmits<{
'view-bookings': [];
'add-to-calendar': [];
'go-home': [];
pay: [];
}>();
const confettiDots = [
+34 -2
View File
@@ -17,6 +17,7 @@
@view-bookings="emit('view-bookings')"
@add-to-calendar="downloadCalendar"
@go-home="emit('go-home')"
@pay="goToPayment"
/>
<template v-else>
@@ -123,7 +124,7 @@
placeholder="در صورت نیاز توضیحی بنویسید..."
/>
<p class="mt-4 text-sm text-muted">
پرداخت در محل انجام می‌شود. نیازی به پرداخت آنلاین نیست.
{{ paymentInfoText }}
</p>
</div>
@@ -223,6 +224,7 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { createBooking, createBookingBatch, fetchAvailableSlots } from '@/api/bookings';
import { fetchBusinessServiceClasses } from '@/api/business-service-classes';
import { ApiError } from '@/api/client';
@@ -275,8 +277,13 @@ const emit = defineEmits<{
}>();
const auth = useAuthStore();
const router = useRouter();
const currentStep = ref(1);
const paymentInfoText = computed(() =>
props.business.autoApproveBookings
? 'پس از ثبت نهایی، مستقیماً به صفحه پرداخت آنلاین هدایت می‌شوید.'
: 'پس از ثبت، درخواست شما بررسی می‌شود. در صورت تأیید صاحب کسب‌وکار، لینک پرداخت آنلاین برای شما در اپ و پیامک ارسال می‌شود.',
);
const phase = ref<'booking' | 'success'>('booking');
const selectedClassId = ref('');
const selectedServiceIds = ref<string[]>([]);
@@ -405,6 +412,11 @@ const successDetails = computed(() => {
durationLabel: selectedDurationLabel.value ?? '—',
address: props.business.address,
bookingRef: createdBooking.value.id.slice(0, 8).toUpperCase(),
successMode:
createdBooking.value.status === 'awaiting_payment'
? 'awaiting_payment'
: 'pending_approval',
showPayButton: createdBooking.value.status === 'awaiting_payment',
};
});
@@ -581,6 +593,17 @@ async function submitBooking() {
});
createdBooking.value = bookings[0] ?? null;
}
if (
createdBooking.value?.status === 'awaiting_payment' &&
createdBooking.value.paymentToken &&
props.business.autoApproveBookings
) {
await router.push({
path: '/app/bookings/pay',
query: { token: createdBooking.value.paymentToken },
});
return;
}
phase.value = 'success';
} catch (e) {
bookingError.value = e instanceof ApiError ? e.message : 'ثبت رزرو ناموفق بود.';
@@ -589,6 +612,15 @@ async function submitBooking() {
}
}
function goToPayment() {
const token = createdBooking.value?.paymentToken;
if (!token) return;
void router.push({
path: '/app/bookings/pay',
query: { token },
});
}
function downloadCalendar() {
if (!createdBooking.value) return;
const start = new Date(createdBooking.value.scheduledAt);