From 27f0dc0833356d41e0a046d4f900e0274733401b Mon Sep 17 00:00:00 2001 From: Julian Weng Date: Thu, 10 Oct 2024 18:25:25 -0400 Subject: [PATCH] Disable editing ticket count in cart while waiting for server response for previous edit, propogate unsuccessful edit attempts to form state, clarify date range shorthand in ticket card --- frontend/components/Tickets/CartTickets.tsx | 40 ++++++++++++++++----- frontend/components/Tickets/TicketCard.tsx | 18 ++++++---- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/frontend/components/Tickets/CartTickets.tsx b/frontend/components/Tickets/CartTickets.tsx index edab5ce80..5020ad192 100644 --- a/frontend/components/Tickets/CartTickets.tsx +++ b/frontend/components/Tickets/CartTickets.tsx @@ -114,13 +114,14 @@ export interface CartTicketsProps { * @param tickets - Original array of tickets * @returns Array of tickets condensed into unique types */ -const combineTickets = (tickets: EventTicket[]): CountedEventTicket[] => +const combineTickets = (tickets: EventTicket[]): CountedEventTicketStatus[] => Object.values( tickets.reduce( (acc, ticket) => ({ ...acc, [`${ticket.event.id}_${ticket.type}`]: { ...ticket, + pendingEdit: false, count: (acc[`${ticket.event.id}_${ticket.type}`]?.count ?? 0) + 1, }, }), @@ -182,12 +183,17 @@ const useCheckout = (paid: boolean) => { } } +interface CountedEventTicketStatus extends CountedEventTicket { + pendingEdit: boolean +} + const CartTickets: React.FC = ({ tickets, soldOut }) => { const navigate = useRouter() - const [removeModal, setRemoveModal] = useState( - null, - ) - const [countedTickets, setCountedTickets] = useState([]) + const [removeModal, setRemoveModal] = + useState(null) + const [countedTickets, setCountedTickets] = useState< + CountedEventTicketStatus[] + >([]) const atLeastOnePaid = tickets.some((ticket) => parseFloat(ticket.price) > 0) const { @@ -225,12 +231,25 @@ const CartTickets: React.FC = ({ tickets, soldOut }) => { checkout() } - function handleUpdateTicket(ticket: CountedEventTicket, newCount?: number) { + function handleUpdateTicket( + ticket: CountedEventTicketStatus, + newCount?: number, + propogateCount?: (count: number) => void, + ) { let reqPromise if (!ticket.count || newCount === ticket.count) { return } + + function flipPendingEdit(value: boolean) { + setCountedTickets( + countedTickets.map((t) => + t.id === ticket.id ? { ...t, pendingEdit: value } : t, + ), + ) + } + flipPendingEdit(true) if (newCount && newCount > ticket.count) { reqPromise = doApiRequest(`/events/${ticket.event.id}/add_to_cart/`, { method: 'POST', @@ -268,8 +287,11 @@ const CartTickets: React.FC = ({ tickets, soldOut }) => { toast.error(res.detail, { style: { color: WHITE }, }) + propogateCount?.(ticket.count ?? 0) + flipPendingEdit(false) return } + flipPendingEdit(false) toast.success(res.detail) // TODO: a less naive approach to updating the cart setCountedTickets( @@ -365,9 +387,9 @@ const CartTickets: React.FC = ({ tickets, soldOut }) => { ticket={ticket} hideActions removable - editable - onChange={(count) => { - handleUpdateTicket(ticket, count) + editable={!ticket.pendingEdit} + onChange={(count, propogateCount) => { + handleUpdateTicket(ticket, count, propogateCount) }} onRemove={() => { setRemoveModal(ticket) diff --git a/frontend/components/Tickets/TicketCard.tsx b/frontend/components/Tickets/TicketCard.tsx index 2d88b6337..1986c919a 100644 --- a/frontend/components/Tickets/TicketCard.tsx +++ b/frontend/components/Tickets/TicketCard.tsx @@ -132,7 +132,8 @@ export const TicketCard = ({ indexProps?: TicketCardIndexProps onRemove?: () => void - onChange?: (count: number) => void + // Optimistically update the ticket count but revert if server request fails + onChange?: (count: number, propogateCount: (number) => void) => void onClick?: () => void viewModal?: (type: ModalType) => void @@ -196,7 +197,10 @@ export const TicketCard = ({ onKeyDown={(e) => { if (e.key === 'Enter') { setIsEditMode(false) - onChange?.(parseInt(e.currentTarget.value ?? ticket.count)) + onChange?.( + parseInt(e.currentTarget.value ?? ticket.count), + setTicketCount, + ) } }} /> @@ -256,12 +260,12 @@ export const TicketCard = ({ css={css` font-size: 12px; position: absolute; - right: 12px; - top: 12px; + right: 8px; + top: 8px; `} > - {datetimeData.dayDuration < 0 ? '-' : '+'}{' '} - {Math.abs(datetimeData.dayDuration)} + {datetimeData.dayDuration < 0 ? '-' : '+'} + {Math.abs(datetimeData.dayDuration)}d )} @@ -318,7 +322,7 @@ export const TicketCard = ({ `} onClick={() => { if (isEditMode) { - onChange?.(ticketCount || ticket.count!) + onChange?.(ticketCount || ticket.count!, setTicketCount) } setIsEditMode(!isEditMode) }}