diff --git a/src/components/Carpool/CarpoolRouteEstimator.tsx b/src/components/Carpool/CarpoolRouteEstimator.tsx index f241ea3..7f513de 100644 --- a/src/components/Carpool/CarpoolRouteEstimator.tsx +++ b/src/components/Carpool/CarpoolRouteEstimator.tsx @@ -1,7 +1,7 @@ import { useContext, useMemo } from 'react'; import { Location } from '../../lib/estimateoptimalpath'; import getDistance from '../../lib/getdistance'; -import { IEventSignupComplete } from '../types'; +import { IEventSignupWithLocation } from '../types'; import useOptimalPath from '../useOptimalPath'; import { CarpoolContext } from './Carpool'; import useSignups from './useSignups'; @@ -21,7 +21,7 @@ export default function CarpoolRouteEstimator() { () => signups.filter( (signup) => signup.latitude !== null - ) as IEventSignupComplete[], + ) as IEventSignupWithLocation[], [signups] ); diff --git a/src/components/Event/EventCarpools.tsx b/src/components/Event/EventCarpools.tsx index 09c2262..d59ef40 100644 --- a/src/components/Event/EventCarpools.tsx +++ b/src/components/Event/EventCarpools.tsx @@ -8,7 +8,7 @@ import { useSendCarpoolRequest, } from '../../state/Notifications/NotificationsHooks'; import { useMe } from '../hooks'; -import { IEvent, IEventSignupComplete } from '../types'; +import { IEvent, IEventSignupWithLocation } from '../types'; import useOptimalPath from '../useOptimalPath'; import EventContext from './EventContext'; import { useCurrentEventSignup } from './EventHooks'; @@ -33,7 +33,7 @@ function useMemberLocations(members: IEvent['carpools'][0]['members']) { longitude: signup.longitude, }; }) - .filter(Boolean) as IEventSignupComplete[], + .filter(Boolean) as IEventSignupWithLocation[], [members, signups] ); } diff --git a/src/components/Event/EventInterestForm.tsx b/src/components/Event/EventInterestForm.tsx index b506f23..3c62779 100644 --- a/src/components/Event/EventInterestForm.tsx +++ b/src/components/Event/EventInterestForm.tsx @@ -1,10 +1,11 @@ -import { useCallback, useEffect, useRef } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { green, lightgrey } from '../../lib/colors'; import getPlaceDetails from '../../lib/getPlaceDetails'; import { addOrUpdateEventSignup, removeEventSignup } from '../api'; import { useMe } from '../hooks'; import UIButton from '../UI/UIButton'; import UIPlacesAutocomplete from '../UI/UIPlacesAutocomplete'; +import UITextInput from '../UI/UITextInput'; import EventCarpools from './EventCarpools'; import { useMutableEvent } from './EventHooks'; import EventSignups from './EventSignups'; @@ -14,6 +15,9 @@ export default function EventInterestForm() { const me = useMe() || { id: 0, name: '' }; const placeIdRef = useRef(null); const canDriveRef = useRef(false); + const [note, setNote] = useState(''); + const [noteSaved, setNoteSaved] = useState(true); + const noteUpdateTimerRef = useRef(null); { const signup = event.signups[me.id]; @@ -22,34 +26,48 @@ export default function EventInterestForm() { placeIdRef.current = signup?.placeId ?? null; canDriveRef.current = signup?.canDrive ?? false; }, [signup?.canDrive, signup?.placeId]); + + useEffect(() => { + setNote(signup?.note || ''); + }, [signup?.note]); } - const updateSignup = useCallback(async () => { - const placeId = placeIdRef.current; - const canDrive = canDriveRef.current; + const updateSignup = useCallback( + async (note: string) => { + const placeId = placeIdRef.current; + const canDrive = canDriveRef.current; - await addOrUpdateEventSignup(event.id, placeIdRef.current, canDrive); - - if (placeId) { - const details = await getPlaceDetails(placeId); - - event.signups[me.id] = { - user: { id: me.id, name: me.name }, - placeId, - ...details, + await addOrUpdateEventSignup( + event.id, + placeIdRef.current, canDrive, - }; - } else { - event.signups[me.id] = { - user: { id: me.id, name: me.name }, - placeId: null, - latitude: null, - longitude: null, - formattedAddress: null, - canDrive, - }; - } - }, [event.id, event.signups, me.id, me.name]); + note + ); + + if (placeId) { + const details = await getPlaceDetails(placeId); + + event.signups[me.id] = { + user: { id: me.id, name: me.name }, + placeId, + ...details, + canDrive, + note, + }; + } else { + event.signups[me.id] = { + user: { id: me.id, name: me.name }, + placeId: null, + latitude: null, + longitude: null, + formattedAddress: null, + canDrive, + note, + }; + } + }, + [event.id, event.signups, me.id, me.name] + ); const removeSignup = useCallback(async () => { await removeEventSignup(event.id); @@ -59,6 +77,20 @@ export default function EventInterestForm() { } }, [event.id, event.signups, me.id]); + const updateNote = useCallback( + (newNote: string) => { + setNote(newNote); + setNoteSaved(false); + if (noteUpdateTimerRef.current) { + clearTimeout(noteUpdateTimerRef.current); + } + noteUpdateTimerRef.current = setTimeout(() => { + updateSignup(newNote).then(() => setNoteSaved(true)); + }, 1000); + }, + [updateSignup] + ); + const interested = !!event.signups[me.id]; const canDrive = !!event.signups[me.id]?.canDrive; @@ -72,7 +104,7 @@ export default function EventInterestForm() { ? () => removeSignup() : () => { placeIdRef.current = null; - updateSignup(); + updateSignup(note); } } style={{ @@ -91,7 +123,7 @@ export default function EventInterestForm() { { canDriveRef.current = !canDriveRef.current; - updateSignup(); + updateSignup(note); }} style={{ backgroundColor: canDrive ? green : lightgrey, @@ -105,7 +137,7 @@ export default function EventInterestForm() { placeholder="Pickup and dropoff location" onSelected={(_address, placeId) => { placeIdRef.current = placeId; - updateSignup(); + updateSignup(note); }} style={ event.signups[me.id]?.placeId != null @@ -115,6 +147,15 @@ export default function EventInterestForm() { placeId={event.signups[me.id]?.placeId} />
+ + Note (e.g. "Monday, Tuesday, Wednesday") + + +
{event.signups !== null && } diff --git a/src/components/UI/UITextInput.tsx b/src/components/UI/UITextInput.tsx index 2de7134..2d9e463 100644 --- a/src/components/UI/UITextInput.tsx +++ b/src/components/UI/UITextInput.tsx @@ -1,4 +1,5 @@ -import { CSSProperties, useCallback } from 'react'; +import { forwardRef } from 'react'; +import { CSSProperties, ForwardedRef, useCallback } from 'react'; const baseStyle = { marginTop: '0.5em', @@ -9,23 +10,27 @@ const baseStyle = { border: '0px', }; -export default function UITextInput({ - value, - disabled = false, - onChangeText, - style, -}: { - value: string; - disabled?: boolean; - onChangeText: (text: string) => void; - style?: CSSProperties; -}) { +function UITextInput( + { + value, + disabled = false, + onChangeText, + style, + }: { + value?: string; + disabled?: boolean; + onChangeText?: (text: string) => void; + style?: CSSProperties; + }, + ref: ForwardedRef +) { const onChange = useCallback( - (e) => onChangeText(e.target.value), + (e) => onChangeText?.(e.target.value), [onChangeText] ); return ( ); } + +export default forwardRef(UITextInput); diff --git a/src/components/api.ts b/src/components/api.ts index 80a59ee..9f41b14 100644 --- a/src/components/api.ts +++ b/src/components/api.ts @@ -75,9 +75,10 @@ export async function getEventSignups( export async function addOrUpdateEventSignup( eventId: number, placeId: string | null, - canDrive: boolean + canDrive: boolean, + note: string ) { - await post(`/events/${eventId}/signup`, { placeId, canDrive }); + await post(`/events/${eventId}/signup`, { placeId, canDrive, note }); } export async function removeEventSignup(eventId: number) { diff --git a/src/components/types.ts b/src/components/types.ts index 292fe18..e00c037 100644 --- a/src/components/types.ts +++ b/src/components/types.ts @@ -92,24 +92,23 @@ export type IEvent = { longitude: number; }; -export type IEventSignupComplete = { +export type IEventSignupBase = { user: { id: number; name: string; }; canDrive: boolean; + note: string; +}; + +export type IEventSignupWithLocation = IEventSignupBase & { placeId: string; formattedAddress: string; latitude: number; longitude: number; }; -export type IEventSignupIncomplete = { - user: { - id: number; - name: string; - }; - canDrive: boolean; +export type IEventSignupWithoutLocation = IEventSignupBase & { placeId: null; formattedAddress: null; latitude: null; @@ -120,7 +119,9 @@ export type IEventSignupIncomplete = { * Model EventSignup */ -export type IEventSignup = IEventSignupComplete | IEventSignupIncomplete; +export type IEventSignup = + | IEventSignupWithLocation + | IEventSignupWithoutLocation; export type IInvitation = { user: { diff --git a/src/components/useOptimalPath.ts b/src/components/useOptimalPath.ts index b81b7e8..e650b6d 100644 --- a/src/components/useOptimalPath.ts +++ b/src/components/useOptimalPath.ts @@ -1,9 +1,9 @@ import { useDebugValue, useMemo } from 'react'; import estimateOptimalPath, { Path } from '../lib/estimateoptimalpath'; -import { ICarpool, IEventSignupComplete } from './types'; +import { ICarpool, IEventSignupWithLocation } from './types'; export default function useOptimalPath( - members: IEventSignupComplete[], + members: IEventSignupWithLocation[], destination: ICarpool['event'] ) { const path = useMemo(() => { @@ -38,7 +38,7 @@ export default function useOptimalPath( } return prev; - }, null! as { path: Path; distance: number }); + }, null! as { path: Path; distance: number }); return path; }, [destination, members]); diff --git a/src/lib/estimateoptimalpath.ts b/src/lib/estimateoptimalpath.ts index dc8e992..9e0de78 100644 --- a/src/lib/estimateoptimalpath.ts +++ b/src/lib/estimateoptimalpath.ts @@ -1,4 +1,4 @@ -import { ICarpool, IEventSignupComplete } from '../components/types'; +import { ICarpool, IEventSignupWithLocation } from '../components/types'; import getDistance from './getdistance'; export type Location = { @@ -13,9 +13,9 @@ export type Path = { }; export default function estimateOptimalPath( - path: Path + path: Path ): { - path: Path; + path: Path; distance: number; } { const { from, to, waypoints } = path; @@ -49,7 +49,7 @@ export default function estimateOptimalPath( path: { from, to, - waypoints: newWaypoints as IEventSignupComplete[], + waypoints: newWaypoints as IEventSignupWithLocation[], }, distance: getDistance(from, ...sequence, to), };