mirror of
https://github.com/supabase/supabase.git
synced 2026-10-11 04:15:04 +03:00
format
This commit is contained in:
1 parent
f070beb6e6
commit
295c002882
8 files changed
+63
-35
No files matched your search
@@ -5,10 +5,7 @@ import { getMockAvailability, shouldUseMock } from '../../mock'
|
||||
const HUBSPOT_BASE =
|
||||
'https://api.hubspot.com/scheduler/v3/meetings/meeting-links/book/availability-page'
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ slug: string }> }
|
||||
) {
|
||||
export async function GET(request: NextRequest, { params }: { params: Promise<{ slug: string }> }) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const timezone = searchParams.get('timezone') || 'America/New_York'
|
||||
|
||||
@@ -30,10 +27,7 @@ export async function GET(
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text()
|
||||
return NextResponse.json(
|
||||
{ error: 'HubSpot API error', detail: text },
|
||||
{ status: res.status }
|
||||
)
|
||||
return NextResponse.json({ error: 'HubSpot API error', detail: text }, { status: res.status })
|
||||
}
|
||||
|
||||
const data = await res.json()
|
||||
|
||||
@@ -4,10 +4,7 @@ import { getMockBookingInfo, shouldUseMock } from '../mock'
|
||||
|
||||
const HUBSPOT_BASE = 'https://api.hubspot.com/scheduler/v3/meetings/meeting-links/book'
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ slug: string }> }
|
||||
) {
|
||||
export async function GET(request: NextRequest, { params }: { params: Promise<{ slug: string }> }) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const timezone = searchParams.get('timezone') || 'America/New_York'
|
||||
const monthOffset = parseInt(searchParams.get('monthOffset') || '0', 10)
|
||||
@@ -30,10 +27,7 @@ export async function GET(
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text()
|
||||
return NextResponse.json(
|
||||
{ error: 'HubSpot API error', detail: text },
|
||||
{ status: res.status }
|
||||
)
|
||||
return NextResponse.json({ error: 'HubSpot API error', detail: text }, { status: res.status })
|
||||
}
|
||||
|
||||
const data = await res.json()
|
||||
|
||||
@@ -10,7 +10,10 @@ export function shouldUseMock(): boolean {
|
||||
}
|
||||
|
||||
/** Generate mock time slots for the next 30 days, excluding weekends */
|
||||
function generateMockSlots(timezone: string, monthOffset: number): Record<string, Array<{ startMillisUtc: number; endMillisUtc: number }>> {
|
||||
function generateMockSlots(
|
||||
timezone: string,
|
||||
monthOffset: number
|
||||
): Record<string, Array<{ startMillisUtc: number; endMillisUtc: number }>> {
|
||||
const now = new Date()
|
||||
const target = new Date(now.getFullYear(), now.getMonth() + monthOffset, 1)
|
||||
const daysInMonth = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate()
|
||||
|
||||
@@ -67,7 +67,9 @@ export default function CalendarPicker({
|
||||
disabled={!hasPrev}
|
||||
className={cn(
|
||||
'p-2 rounded-md text-foreground-lighter transition-colors',
|
||||
hasPrev ? 'hover:bg-surface-300/50 hover:text-foreground' : 'opacity-30 cursor-not-allowed'
|
||||
hasPrev
|
||||
? 'hover:bg-surface-300/50 hover:text-foreground'
|
||||
: 'opacity-30 cursor-not-allowed'
|
||||
)}
|
||||
aria-label="Previous month"
|
||||
>
|
||||
@@ -80,7 +82,9 @@ export default function CalendarPicker({
|
||||
disabled={!hasNext}
|
||||
className={cn(
|
||||
'p-2 rounded-md text-foreground-lighter transition-colors',
|
||||
hasNext ? 'hover:bg-surface-300/50 hover:text-foreground' : 'opacity-30 cursor-not-allowed'
|
||||
hasNext
|
||||
? 'hover:bg-surface-300/50 hover:text-foreground'
|
||||
: 'opacity-30 cursor-not-allowed'
|
||||
)}
|
||||
aria-label="Next month"
|
||||
>
|
||||
@@ -133,7 +137,13 @@ export default function CalendarPicker({
|
||||
function ChevronLeftIcon() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 12L6 8L10 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path
|
||||
d="M10 12L6 8L10 4"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -141,7 +151,13 @@ function ChevronLeftIcon() {
|
||||
function ChevronRightIcon() {
|
||||
return (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 4L10 8L6 12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path
|
||||
d="M6 4L10 8L6 12"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -30,7 +30,13 @@ export default function Confirmation({ confirmation, timezone }: ConfirmationPro
|
||||
return (
|
||||
<div className="w-full text-center flex flex-col items-center gap-4">
|
||||
<div className="w-12 h-12 rounded-full bg-brand-500/10 flex items-center justify-center">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M20 6L9 17L4 12"
|
||||
stroke="currentColor"
|
||||
|
||||
@@ -12,7 +12,10 @@ async function parseErrorResponse(res: Response, fallback: string): Promise<stri
|
||||
}
|
||||
|
||||
function wrapNetworkError(err: unknown): never {
|
||||
if (err instanceof TypeError && (err.message === 'Failed to fetch' || err.message === 'Load failed')) {
|
||||
if (
|
||||
err instanceof TypeError &&
|
||||
(err.message === 'Failed to fetch' || err.message === 'Load failed')
|
||||
) {
|
||||
throw new Error('Unable to connect. Please check your internet connection and try again.')
|
||||
}
|
||||
throw err
|
||||
@@ -37,10 +40,7 @@ export async function fetchBookingInfo(
|
||||
return res.json()
|
||||
}
|
||||
|
||||
export async function fetchAvailability(
|
||||
slug: string,
|
||||
timezone: string
|
||||
): Promise<BookingInfo> {
|
||||
export async function fetchAvailability(slug: string, timezone: string): Promise<BookingInfo> {
|
||||
const params = new URLSearchParams({ timezone })
|
||||
let res: Response
|
||||
try {
|
||||
|
||||
@@ -68,4 +68,11 @@ export interface BookingConfirmation {
|
||||
isOffline: boolean
|
||||
}
|
||||
|
||||
export type SchedulerStep = 'loading' | 'date-select' | 'time-select' | 'form' | 'submitting' | 'confirmed' | 'error'
|
||||
export type SchedulerStep =
|
||||
| 'loading'
|
||||
| 'date-select'
|
||||
| 'time-select'
|
||||
| 'form'
|
||||
| 'submitting'
|
||||
| 'confirmed'
|
||||
| 'error'
|
||||
@@ -1,7 +1,13 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
|
||||
import { bookMeeting, fetchBookingInfo } from './api'
|
||||
import type { BookingConfirmation, BookingInfo, BookingRequest, SchedulerStep, TimeSlot } from './types'
|
||||
import type {
|
||||
BookingConfirmation,
|
||||
BookingInfo,
|
||||
BookingRequest,
|
||||
SchedulerStep,
|
||||
TimeSlot,
|
||||
} from './types'
|
||||
|
||||
/** How many months to prefetch (current + 3 ahead) */
|
||||
const MONTHS_TO_FETCH = 4
|
||||
@@ -59,9 +65,7 @@ export function useScheduler(slug: string, enabled = true) {
|
||||
setState((s) => ({ ...s, step: 'loading', error: null }))
|
||||
|
||||
const results = await Promise.all(
|
||||
Array.from({ length: MONTHS_TO_FETCH }, (_, i) =>
|
||||
fetchBookingInfo(slug, effectiveTz, i)
|
||||
)
|
||||
Array.from({ length: MONTHS_TO_FETCH }, (_, i) => fetchBookingInfo(slug, effectiveTz, i))
|
||||
)
|
||||
|
||||
const cache: Record<number, BookingInfo> = {}
|
||||
@@ -69,8 +73,7 @@ export function useScheduler(slug: string, enabled = true) {
|
||||
cache[i] = results[i]
|
||||
}
|
||||
|
||||
const defaultDuration =
|
||||
results[0]?.customParams?.availableDurations?.[0] ?? 1800000
|
||||
const defaultDuration = results[0]?.customParams?.availableDurations?.[0] ?? 1800000
|
||||
|
||||
setMonthCache(cache)
|
||||
setState((s) => ({
|
||||
@@ -134,7 +137,12 @@ export function useScheduler(slug: string, enabled = true) {
|
||||
}, [])
|
||||
|
||||
const submitBooking = useCallback(
|
||||
async (form: { firstName: string; lastName: string; email: string; guestEmails?: string[] }) => {
|
||||
async (form: {
|
||||
firstName: string
|
||||
lastName: string
|
||||
email: string
|
||||
guestEmails?: string[]
|
||||
}) => {
|
||||
if (!state.selectedSlot) return
|
||||
|
||||
setState((s) => ({ ...s, step: 'submitting' }))
|
||||
|
||||
Reference in new issue
Block a user