diff --git a/apps/edr-passenger-web/backoffice/src/app/bookings/page.tsx b/apps/edr-passenger-web/backoffice/src/app/bookings/page.tsx index 65960b4d6..ea45fe321 100644 --- a/apps/edr-passenger-web/backoffice/src/app/bookings/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/bookings/page.tsx @@ -37,6 +37,7 @@ function BookingsPageContent() { const [selectedBooking, setSelectedBooking] = useState(null); const [generateTicketBooking, setGenerateTicketBooking] = useState(null); const [generateTicketForm, setGenerateTicketForm] = useState({ paymentReference: '', paymentMethod: '', notes: '' }); + const [generateTicketTouched, setGenerateTicketTouched] = useState({ paymentReference: false, paymentMethod: false }); const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const [bookingToDelete, setBookingToDelete] = useState(null); const [deleteError, setDeleteError] = useState(null); @@ -75,6 +76,7 @@ function BookingsPageContent() { setSelectedBooking(null); setGenerateTicketBooking(null); setGenerateTicketForm({ paymentReference: '', paymentMethod: '', notes: '' }); + setGenerateTicketTouched({ paymentReference: false, paymentMethod: false }); }, }); @@ -276,7 +278,7 @@ function BookingsPageContent() { const actions = [ { label: 'View Details', onClick: (b: any) => setSelectedBooking(b), variant: 'secondary' as const, icon: Eye }, - { label: 'Generate Ticket', onClick: (b: any) => { setGenerateTicketForm({ paymentReference: '', paymentMethod: '', notes: '' }); setGenerateTicketBooking(b); }, variant: 'secondary' as const, icon: Ticket, show: (b: any) => !(b.status === 'CONFIRMED' && b.paymentIntent?.status === 'SUCCEEDED') }, + { label: 'Generate Ticket', onClick: (b: any) => { setGenerateTicketForm({ paymentReference: '', paymentMethod: '', notes: '' }); setGenerateTicketTouched({ paymentReference: false, paymentMethod: false }); setGenerateTicketBooking(b); }, variant: 'secondary' as const, icon: Ticket, show: (b: any) => !(b.status === 'CONFIRMED' && b.paymentIntent?.status === 'SUCCEEDED') }, { label: 'Delete', onClick: (b: any) => { setDeleteError(null); setDeleteCascade(false); setDeleteCascadeChecked(false); setBookingToDelete(b); setDeleteConfirmOpen(true); }, variant: 'danger' as const, icon: Trash2 }, ]; @@ -558,7 +560,7 @@ function BookingsPageContent() { {/* Generate Ticket Modal */} { setGenerateTicketBooking(null); setGenerateTicketForm({ paymentReference: '', paymentMethod: '', notes: '' }); forceConfirmMutation.reset(); }} + onClose={() => { setGenerateTicketBooking(null); setGenerateTicketForm({ paymentReference: '', paymentMethod: '', notes: '' }); setGenerateTicketTouched({ paymentReference: false, paymentMethod: false }); forceConfirmMutation.reset(); }} title="Generate Ticket" size="md" > @@ -573,22 +575,27 @@ function BookingsPageContent() {
- + setGenerateTicketForm(f => ({ ...f, paymentReference: e.target.value }))} + onBlur={() => setGenerateTicketTouched(t => ({ ...t, paymentReference: true }))} /> + {generateTicketTouched.paymentReference && !generateTicketForm.paymentReference && ( +

Payment reference is required

+ )}
- + + {generateTicketTouched.paymentMethod && !generateTicketForm.paymentMethod && ( +

Payment method is required

+ )}
@@ -620,19 +630,23 @@ function BookingsPageContent() {
{ setGenerateTicketBooking(null); setGenerateTicketForm({ paymentReference: '', paymentMethod: '', notes: '' }); forceConfirmMutation.reset(); }} + onClick={() => { setGenerateTicketBooking(null); setGenerateTicketForm({ paymentReference: '', paymentMethod: '', notes: '' }); setGenerateTicketTouched({ paymentReference: false, paymentMethod: false }); forceConfirmMutation.reset(); }} > Cancel forceConfirmMutation.mutate({ - bookingId: generateTicketBooking.id, - data: { - paymentReference: generateTicketForm.paymentReference || undefined, - paymentMethod: generateTicketForm.paymentMethod || undefined, - notes: generateTicketForm.notes || undefined, - }, - })} + onClick={() => { + setGenerateTicketTouched({ paymentReference: true, paymentMethod: true }); + if (!generateTicketForm.paymentReference || !generateTicketForm.paymentMethod) return; + forceConfirmMutation.mutate({ + bookingId: generateTicketBooking.id, + data: { + paymentReference: generateTicketForm.paymentReference, + paymentMethod: generateTicketForm.paymentMethod, + notes: generateTicketForm.notes || undefined, + }, + }); + }} disabled={forceConfirmMutation.isPending} > {forceConfirmMutation.isPending ? 'Generating…' : 'Confirm & Generate Ticket'} diff --git a/apps/edr-passenger-web/portal/src/components/MobileTopBar.tsx b/apps/edr-passenger-web/portal/src/components/MobileTopBar.tsx index 8920b2e80..a58c66958 100644 --- a/apps/edr-passenger-web/portal/src/components/MobileTopBar.tsx +++ b/apps/edr-passenger-web/portal/src/components/MobileTopBar.tsx @@ -1,5 +1,6 @@ import Link from 'next/link'; import Image from 'next/image'; +import ThemeToggle from './ThemeToggle'; // AppSidebar (which carries the logo) is `hidden lg:flex` — invisible on // mobile, so mobile pages had no branding/way back to home at all. This is @@ -19,6 +20,9 @@ export default function MobileTopBar() { priority /> +
+ +
); }