nextjs-demo

next.js demo using react 19 rc

git clone https://9o.is/git/nextjs-demo.git

EventsPage.tsx

(2398B)


      1 "use client"
      2 
      3 import { Checkbox, ComboBox, Select, Loading, Alert, Form, FormDataMap, Button } from '../../headless'
      4 import { useNumberFormatter } from '../../hooks'
      5 import { SHEvent } from './useSHEvents'
      6 import { FilteredSHEventsInput, useFilteredSHEvents } from './useFilteredSHEvents'
      7 import { invariant, isSortType, SORT_TYPES } from '../../../lib'
      8 import { Suspense, use, useTransition } from 'react'
      9 import { eventsAction } from './eventsAction'
     10 
     11 type EventsProps = {
     12   events: Promise<SHEvent[]>
     13   cities: Promise<string[]>
     14 }
     15 
     16 export function EventsPage({ events, cities }: EventsProps) {
     17   const [isPending, startTransition] = useTransition()
     18   // const { events, filterSHEvents } = useFilteredSHEvents(loadedEvents)
     19   
     20   return (
     21     <Alert.Context>
     22       <Alert />
     23       <Form aria-label='Filter Events' action={eventsAction} validator={validator}>
     24         <ComboBox label="City" name="city" options={[]} />
     25         <Select label="Sort by price" name="priceSort" options={SORT_TYPES} />
     26         <Checkbox label="Find Cheapest" name="cheapest" />
     27         <Button type="submit" disabled={false}>Search</Button>
     28         <Form.Error>{(error) => error}</Form.Error>
     29       </Form>
     30       <Alert.ErrorBoundary fallback="Failed to get list of events">
     31         <Suspense fallback={<Loading />}>
     32           <EventList events={events} />
     33         </Suspense>
     34       </Alert.ErrorBoundary>
     35     </Alert.Context>
     36   )
     37 }
     38 
     39 type EventListProps = {
     40   events: Promise<SHEvent[]>
     41 }
     42 
     43 function EventList({ events }: EventListProps) {
     44   const priceFormatter = useNumberFormatter({ 
     45     style: 'currency', 
     46     currency: 'USD',
     47     maximumFractionDigits: 0
     48   })
     49   
     50   return (
     51     <ul>
     52       {use(events).map(({ id, city, price }) => (
     53         <li key={id}>
     54           <div>City: {city}</div>
     55           <div>Price: {priceFormatter.format(price)}</div>
     56         </li>
     57       ))}
     58     </ul>
     59   )
     60 }
     61 
     62 
     63 function validator ({ city, priceSort, cheapest }: FormDataMap): FilteredSHEventsInput {
     64   invariant(
     65     typeof city === 'string' && city.length < 255, 
     66     "City is invalid"
     67   )
     68   
     69   invariant(
     70     cheapest === undefined || cheapest === 'on', 
     71     "Find cheapest input is invalid"
     72   )
     73   
     74   invariant(
     75     priceSort === '' || (typeof priceSort === 'string' && isSortType(priceSort)),
     76     "Sort by price is invalid"
     77   )
     78 
     79   return { 
     80     city, 
     81     priceSort: priceSort || undefined, 
     82     cheapest: cheapest === 'on', 
     83   }
     84 }