diff --git a/src/components/orders/OrdersListView.module.scss b/src/components/orders/OrdersListView.module.scss index 8182558..de15d0c 100644 --- a/src/components/orders/OrdersListView.module.scss +++ b/src/components/orders/OrdersListView.module.scss @@ -71,13 +71,21 @@ background: $color-surface; color: inherit; text-decoration: none; - transition: border-color 0.15s ease; + transition: + border-color 0.15s ease, + opacity 0.28s ease, + transform 0.28s ease; &:hover { border-color: $color-primary; } } +.orderExiting .order { + opacity: 0; + transform: translateY(-8px) scale(0.98); +} + .orderHeader { display: flex; justify-content: space-between; diff --git a/src/components/orders/OrdersListView.tsx b/src/components/orders/OrdersListView.tsx index 95b8b54..f2db595 100644 --- a/src/components/orders/OrdersListView.tsx +++ b/src/components/orders/OrdersListView.tsx @@ -1,4 +1,5 @@ import { Link } from '@tanstack/react-router' +import { useEffect, useRef, useState } from 'react' import type { CustomerOrderSummary } from '#/domain/ordering.ts' import { formatPrice } from '#/lib/format.ts' @@ -9,6 +10,10 @@ type OrdersListViewProps = { orders: Array } +// Must match the CSS transition duration on .order in +// OrdersListView.module.scss. +const EXIT_DURATION_MS = 280 + const OrdersIcon = () => ( ) .join(', ') -export const OrdersListView = ({ orders }: OrdersListViewProps) => ( -
-

Your orders

- {orders.length === 0 ? ( -
- -

No orders yet

-

- Your placed orders will show up here. -

- - Browse the menu - -
- ) : ( - + )} + + ) +} diff --git a/src/integrations/better-auth/header-user.tsx b/src/integrations/better-auth/header-user.tsx index 1f1a359..cb267d3 100644 --- a/src/integrations/better-auth/header-user.tsx +++ b/src/integrations/better-auth/header-user.tsx @@ -153,27 +153,7 @@ const BetterAuthHeader = () => { clearBasket() void authClient.signOut({ fetchOptions: { - onSuccess: () => { - // Cross-fades the now-cleared Orders list out (and - // the empty state in) instead of popping instantly — - // each order card already carries its own - // `view-transition-name` (see OrdersListView), so - // this reuses that for a proper per-card exit. - if ('startViewTransition' in document) { - // Rejects if the browser can't actually run the - // transition right now (e.g. the tab isn't - // visible) — router.invalidate() still runs either - // way, so there's nothing to handle beyond not - // leaving this as an unhandled rejection. - const transition = document.startViewTransition(() => - router.invalidate(), - ) - transition.ready.catch(() => {}) - transition.finished.catch(() => {}) - } else { - void router.invalidate() - } - }, + onSuccess: () => void router.invalidate(), }, }) }} diff --git a/src/routes/orders/index.tsx b/src/routes/orders/index.tsx index 3d9d2bf..303ad97 100644 --- a/src/routes/orders/index.tsx +++ b/src/routes/orders/index.tsx @@ -1,5 +1,5 @@ import { createFileRoute } from '@tanstack/react-router' -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { OrdersListView } from '#/components/orders/OrdersListView.tsx' import type { CustomerOrderSummary } from '#/domain/ordering.ts' @@ -25,7 +25,16 @@ const OrdersRoute = () => { void getGuestOrders({ data: { refs } }).then(setGuestOrders) }, [accountOrders]) - const orders = accountOrders ?? guestOrders + const resolvedOrders = accountOrders ?? guestOrders + + // Signing out clears both sources for a moment (the account loader + // reruns and comes back null, guest refs were just wiped) before the + // guest lookup above resolves to `[]` — keep showing the last known + // list through that gap instead of unmounting OrdersListView, so its + // own exit animation (see OrdersListView) can actually play. + const lastKnownOrders = useRef | null>(null) + if (resolvedOrders !== null) lastKnownOrders.current = resolvedOrders + const orders = resolvedOrders ?? lastKnownOrders.current if (orders === null) return null return