{"version":3,"sources":["../../src/hooks/useMyBookings.ts"],"names":[],"mappings":"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","file":"useMyBookings.mjs","sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n  resolveSchedulingBasePath,\n  schedulingGet,\n  schedulingPost,\n  SchedulingClientError,\n} from '../lib/schedulingClient';\nimport type { SchedulingMyBookingRow } from '../types/scheduling';\n\nexport interface UseMyBookingsOptions {\n  /** Override the API base path. Default: auto-detect via resolveSchedulingBasePath(). */\n  apiBase?: string;\n  /**\n   * If true, the hook will not auto-load on mount — the consumer calls\n   * `refresh()` explicitly. Useful when the account page is gated by an auth\n   * status that's still resolving and the initial fetch shouldn't fire until\n   * the user is known-signed-in.\n   */\n  skipInitialLoad?: boolean;\n  /**\n   * Called once with the canceled row id whenever a `cancel(id)` succeeds.\n   * Fires after the optimistic update has been committed.\n   */\n  onCancelled?: (bookingId: string) => void;\n}\n\nexport interface UseMyBookingsResult {\n  basePath: string;\n  /** Bookings whose `starts_at` is in the future. Sorted soonest-first. */\n  upcoming: SchedulingMyBookingRow[];\n  /** Bookings whose `starts_at` is in the past. Sorted newest-first. */\n  past: SchedulingMyBookingRow[];\n  /** True while the initial / refresh fetch is in flight. */\n  loading: boolean;\n  /** The id currently being canceled, or null. */\n  cancelling: string | null;\n  /** Last error string. Cleared on the next successful op. */\n  error: string | null;\n\n  refresh: () => Promise<void>;\n  /**\n   * Cancel a booking via POST /bookings/manage. Optimistically removes the row\n   * from `upcoming` and reverts on failure.\n   */\n  cancel: (bookingId: string) => Promise<void>;\n}\n\ninterface MyBookingsResponse {\n  bookings?: SchedulingMyBookingRow[];\n  count?: number;\n}\n\nfunction partition(\n  rows: SchedulingMyBookingRow[],\n  nowMs: number,\n): { upcoming: SchedulingMyBookingRow[]; past: SchedulingMyBookingRow[] } {\n  const upcoming: SchedulingMyBookingRow[] = [];\n  const past: SchedulingMyBookingRow[] = [];\n  for (const b of rows) {\n    const t = Date.parse(b.starts_at);\n    // Treat unparseable dates as past so they don't get stuck \"upcoming.\"\n    if (Number.isFinite(t) && t >= nowMs) {\n      upcoming.push(b);\n    } else {\n      past.push(b);\n    }\n  }\n  upcoming.sort((a, b) => (a.starts_at < b.starts_at ? -1 : a.starts_at > b.starts_at ? 1 : 0));\n  past.sort((a, b) => (a.starts_at < b.starts_at ? 1 : a.starts_at > b.starts_at ? -1 : 0));\n  return { upcoming, past };\n}\n\nexport function useMyBookings(opts: UseMyBookingsOptions = {}): UseMyBookingsResult {\n  const basePath = useMemo(\n    () => opts.apiBase ?? resolveSchedulingBasePath(),\n    [opts.apiBase],\n  );\n\n  const [rows, setRows] = useState<SchedulingMyBookingRow[]>([]);\n  const [loading, setLoading] = useState(false);\n  const [cancelling, setCancelling] = useState<string | null>(null);\n  const [error, setError] = useState<string | null>(null);\n\n  // Snapshot the latest onCancelled so callers don't have to memoize it.\n  const onCancelledRef = useRef(opts.onCancelled);\n  onCancelledRef.current = opts.onCancelled;\n\n  const refresh = useCallback(async () => {\n    setLoading(true);\n    setError(null);\n    try {\n      const data = await schedulingGet<MyBookingsResponse>(basePath, '/my-bookings');\n      setRows(Array.isArray(data?.bookings) ? data.bookings : []);\n    } catch (err) {\n      const message =\n        err instanceof SchedulingClientError ? err.message\n        : err instanceof Error ? err.message\n        : 'Failed to load bookings.';\n      setError(message);\n    } finally {\n      setLoading(false);\n    }\n  }, [basePath]);\n\n  useEffect(() => {\n    if (opts.skipInitialLoad) return;\n    refresh();\n  }, [refresh, opts.skipInitialLoad]);\n\n  const cancel = useCallback(\n    async (bookingId: string) => {\n      // Find the row + its token. If we don't have the row in state we can't\n      // cancel it (the manage endpoint is token-gated, not session-gated).\n      const target = rows.find((r) => r.id === bookingId);\n      if (!target) {\n        setError('Booking not found.');\n        return;\n      }\n      if (!target.reschedule_token) {\n        setError('This booking can no longer be cancelled.');\n        return;\n      }\n\n      // Optimistic: drop the row from local state and remember the previous\n      // list so we can revert on failure.\n      const previous = rows;\n      setRows((prev) => prev.filter((r) => r.id !== bookingId));\n      setCancelling(bookingId);\n      setError(null);\n      try {\n        await schedulingPost(basePath, '/bookings/manage', {\n          token: target.reschedule_token,\n          action: 'cancel',\n        });\n        onCancelledRef.current?.(bookingId);\n        // Re-fetch so the row reappears under \"past\" with status=cancelled.\n        await refresh();\n      } catch (err) {\n        // Revert the optimistic removal.\n        setRows(previous);\n        const message =\n          err instanceof SchedulingClientError ? err.message\n          : err instanceof Error ? err.message\n          : 'Cancellation failed.';\n        setError(message);\n      } finally {\n        setCancelling(null);\n      }\n    },\n    [basePath, rows, refresh],\n  );\n\n  // Recompute upcoming/past whenever rows change. `now` is captured at render\n  // — fine for the typical \"open the page, look at bookings\" path; long-lived\n  // sessions can call refresh() to re-bucket if a booking just transitioned.\n  const { upcoming, past } = useMemo(() => partition(rows, Date.now()), [rows]);\n\n  return {\n    basePath,\n    upcoming,\n    past,\n    loading,\n    cancelling,\n    error,\n    refresh,\n    cancel,\n  };\n}\n"]}