diff --git a/src/components/App.tsx b/src/components/App.tsx index 4345575..b8f85bf 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -2,7 +2,7 @@ import { CSSProperties, lazy, Suspense, useEffect, useState } from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { getReceivedInvitationsAndRequests } from './api'; import { useMe } from './hooks'; -import Notifications from './Notifications'; +import Notifications from './Notifications/Notifications'; import { IInvitation } from './types'; import WheelShare from './WheelShare'; import WheelShareLoggedOut from './WheelShareLoggedOut'; diff --git a/src/components/Availability.tsx b/src/components/Availability.tsx deleted file mode 100644 index 91474d1..0000000 --- a/src/components/Availability.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import { CSSProperties } from 'react'; -import { - MouseEventHandler, - ReactEventHandler, - useCallback, - useState, -} from 'react'; - -export type AvailabilityKind = 'going' | 'interested' | 'not-interested'; - -const availabilityNames: Record = { - going: 'Going', - interested: 'Interested', - 'not-interested': 'Not interested', -}; - -const optionStyle: CSSProperties = { - height: '3rem', - backgroundColor: '#e0e0e0', - display: 'flex', - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - cursor: 'pointer', - transition: 'background-color 100ms cubic-bezier', - userSelect: 'none', - position: 'relative', - fontWeight: 'normal', - textTransform: 'uppercase', -}; - -const selectedOptionStyle = { - ...optionStyle, - fontWeight: 600, -}; - -function Option({ - bind, - current, - onSelected, -}: { - bind: AvailabilityKind; - current: AvailabilityKind; - onSelected: (kind: AvailabilityKind) => void; -}) { - const selected = current === bind; - - const select: MouseEventHandler = useCallback( - (event) => { - onSelected(bind); - }, - [onSelected, bind] - ); - - return ( -
- {availabilityNames[bind]} -
- ); -} - -// eslint-disable-next-line -function Availability__old({ - selected, - onSelected: onSelectedInner, -}: { - selected: AvailabilityKind; - onSelected: (kind: AvailabilityKind) => void; -}) { - const [focused, setFocused] = useState(false); - const onSelected = useCallback( - (kind: AvailabilityKind) => { - setFocused(false); - onSelectedInner(kind); - }, - [onSelectedInner] - ); - return ( -
setFocused(false)} - > - {focused ? ( - <> -
- ); -} - -export default function Availability({ - selected, - onSelected: onSelectedInner, -}: { - selected: AvailabilityKind; - onSelected: (kind: AvailabilityKind) => void; -}) { - const onSelected: ReactEventHandler = useCallback( - (event) => { - onSelectedInner( - // @ts-ignore - event.target.value - ); - event.preventDefault(); - }, - [onSelectedInner] - ); - return ( - - ); -} diff --git a/src/components/Carpool.tsx b/src/components/Carpool.tsx index a852c04..66e848e 100644 --- a/src/components/Carpool.tsx +++ b/src/components/Carpool.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { getCarpool } from './api'; import { ICarpool } from './types'; -import UISecondaryBox from './UISecondaryBox'; +import UISecondaryBox from './UI/UISecondaryBox'; function MemberList({ members }: { members: ICarpool['members'] }) { return ( diff --git a/src/components/Event.tsx b/src/components/Event.tsx deleted file mode 100644 index 7d6d5bb..0000000 --- a/src/components/Event.tsx +++ /dev/null @@ -1,409 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from 'react'; -import { - addOrUpdateEventSignup, - getEventSignups, - removeEventSignup, -} from './api'; -import { green, lightgrey } from './colors'; -import { useMe } from './hooks'; -import latlongdist, { R_miles } from './latlongdist'; -import UIButton from './UIButton'; -import UIPlacesAutocomplete from './UIPlacesAutocomplete'; -import UISecondaryBox from './UISecondaryBox'; -import UISecondaryHeader from './UISecondaryHeader'; -import usePlace from './usePlace'; -import useThrottle from './useThrottle'; -import useToggle from './useToggle'; - -export type IEvent = { - id: number; - name: string; - group: string; - formattedAddress: string; - startTime: string; - endTime: string; - latitude: number; - longitude: number; -}; - -function formatStartAndEndTime( - startDatetimeString: string, - endDatetimeString: string -) { - const startDatetime = new Date(startDatetimeString); - const endDatetime = new Date(endDatetimeString); - - if (isNaN(startDatetime.valueOf())) { - console.error('Invalid datetime:', startDatetimeString); - return '(invalid)'; - } - if (isNaN(endDatetime.valueOf())) { - console.error('Invalid datetime:', startDatetimeString); - return '(invalid)'; - } - - const startDateString = startDatetime.toLocaleDateString(); - const endDateString = endDatetime.toLocaleDateString(); - - if (startDateString === endDateString) { - const startTimeString = startDatetime.toLocaleTimeString(); - const endTimeString = endDatetime.toLocaleTimeString(); - return `${startDateString}, ${startTimeString} - ${endTimeString}`; - } else { - return `${startDatetime.toLocaleString()} - ${endDatetime.toLocaleString()}`; - } -} - -function GroupName({ name }: { name: string }) { - return ( - - {name} - - ); -} - -function Details({ - startTime, - endTime, - formattedAddress, -}: { - startTime: string; - endTime: string; - formattedAddress: string; -}) { - return ( -
- - When: - {formatStartAndEndTime(startTime, endTime)} - -
-
- - Where: - {formattedAddress} - -
- ); -} - -export type ICarpool = { - driver: { - id: number; - name: string; - }; - startTime: string; - endTime: string; - extraDistance: number; -}; - -function CarpoolRow({ carpool }: { carpool: ICarpool }) { - const PADDING = '1rem'; - return ( -
-
- {carpool.driver.name} -
- Time:{' '} - - {carpool.startTime} - {carpool.endTime} - -
- Offset from route: {carpool.extraDistance} miles -
-
- Request to join -
-
- ); -} - -const dummyCarpoolData: ICarpool[] = [ - { - driver: { - id: 0, - name: 'Michael Fatemi', - }, - startTime: '10:00', - endTime: '10:10', - extraDistance: 6.9, - }, - { - driver: { - id: 1, - name: 'Joshua Hsueh', - }, - startTime: '10:05', - endTime: '10:10', - extraDistance: 420, - }, -]; -function Carpools({ event }: { event: IEvent }) { - // eslint-disable-next-line - const [carpools, _setCarpools] = useState(dummyCarpoolData); - - return ( -
-

Carpools

- {carpools.map((carpool) => ( - - ))} -
- ); -} - -export type IEventSignup = { - user: { - id: number; - name: number; - }; - placeId: string; - formattedAddress: string; - latitude: number; - longitude: number; -}; - -function Signups({ - event, - signups, - myPlaceId, -}: { - event: IEvent; - signups: IEventSignup[]; - myPlaceId: string | null; -}) { - const PADDING = '1rem'; - const placeDetails = usePlace(myPlaceId); - const locationLongitude = event.latitude; - const locationLatitude = event.longitude; - const me = useMe(); - - return ( -
-

People

- {signups.map(({ latitude, longitude, user }) => { - if (user.id === me?.id) { - return null; - } - let extraDistance = null; - if (placeDetails != null) { - const myLatitude = placeDetails.latitude; - const myLongitude = placeDetails.longitude; - const meToThem = latlongdist( - latitude, - longitude, - locationLongitude, - locationLatitude, - R_miles - ); - const themToLocation = latlongdist( - latitude, - longitude, - myLatitude, - myLongitude, - R_miles - ); - const totalWithThem = meToThem + themToLocation; - const totalWithoutThem = latlongdist( - locationLongitude, - locationLatitude, - myLatitude, - myLongitude, - R_miles - ); - extraDistance = totalWithThem - totalWithoutThem; - } - - return ( -
- {user.name} - {extraDistance ? `: +${extraDistance.toFixed(1)} miles` : ''} -
- Invite to carpool -
-
- ); - })} -
- ); -} - -export default function Event({ event }: { event: IEvent }) { - const { name, group, formattedAddress, startTime, endTime } = event; - const [haveRide, toggleHaveRide] = useToggle(false); - const [placeId, setPlaceId] = useState(null); - const [interested, setInterested] = useState(false); - const [updating, setUpdating] = useState(false); - const [signups, setSignups] = useState([]); - const toggleInterested = useCallback(() => setInterested((i) => !i), []); - const toggleInterestedThrottled = useThrottle(toggleInterested, 500); - const existingSignup = useRef({ - interested: false, - placeId: null as string | null, - eventId: null as number | null, - }); - const me = useMe(); - - useEffect(() => { - const removeSignup = () => { - if (prev.interested) { - removeEventSignup(event.id) - .then(() => { - prev.interested = false; - }) - .finally(() => setUpdating(false)); - } - }; - - const addOrUpdateSignup = () => { - if (!prev.interested) { - addOrUpdateEventSignup(event.id, placeId) - .then(() => { - prev.placeId = placeId; - prev.eventId = event.id; - prev.interested = true; - }) - .finally(() => setUpdating(false)); - } - }; - - const prev = existingSignup.current; - - if (!interested) { - removeSignup(); - } else { - addOrUpdateSignup(); - } - }, [event.id, interested, placeId, updating]); - - useEffect(() => { - getEventSignups(event.id) - .then((signups) => { - setSignups(signups); - for (let signup of signups) { - if (signup.user.id === me?.id) { - setInterested(true); - existingSignup.current.eventId = event.id; - existingSignup.current.placeId = signup.placeId; - existingSignup.current.interested = true; - } - } - }) - .catch(console.error); - }, [event.id, me?.id]); - - return ( - - {name} - -
- - {interested ? 'Interested' : 'Not interested'} - - {interested && ( - <> - { - setPlaceId(placeID); - }} - style={placeId != null ? { border: '2px solid ' + green } : {}} - /> - {false && ( -
- - I don't have any way to get there yet -
- )} - - - - )} - - ); -} diff --git a/src/components/Event/Event.tsx b/src/components/Event/Event.tsx new file mode 100644 index 0000000..14a388a --- /dev/null +++ b/src/components/Event/Event.tsx @@ -0,0 +1,162 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { + addOrUpdateEventSignup, + getEventSignups, + removeEventSignup, +} from '../api'; +import { green, lightgrey } from '../colors'; +import { useMe } from '../hooks'; +import UIButton from '../UI/UIButton'; +import UIPlacesAutocomplete from '../UI/UIPlacesAutocomplete'; +import UISecondaryBox from '../UI/UISecondaryBox'; +import UISecondaryHeader from '../UI/UISecondaryHeader'; +import useThrottle from '../useThrottle'; +import useToggle from '../useToggle'; +import EventCarpools from './EventCarpools'; +import EventDetails from './EventDetails'; +import EventSignups from './EventSignups'; + +export type IEvent = { + id: number; + name: string; + group: string; + formattedAddress: string; + startTime: string; + endTime: string; + latitude: number; + longitude: number; +}; + +function GroupName({ name }: { name: string }) { + return {name}; +} + +export type IEventSignup = { + user: { + id: number; + name: number; + }; + placeId: string; + formattedAddress: string; + latitude: number; + longitude: number; +}; + +export default function Event({ event }: { event: IEvent }) { + const { name, group, formattedAddress, startTime, endTime } = event; + const [haveRide, toggleHaveRide] = useToggle(false); + const [placeId, setPlaceId] = useState(null); + const [interested, setInterested] = useState(false); + const [updating, setUpdating] = useState(false); + const [signups, setSignups] = useState([]); + const toggleInterested = useCallback(() => setInterested((i) => !i), []); + const toggleInterestedThrottled = useThrottle(toggleInterested, 500); + const existingSignup = useRef({ + interested: false, + placeId: null as string | null, + eventId: null as number | null, + }); + const me = useMe(); + + useEffect(() => { + const removeSignup = () => { + if (prev.interested) { + removeEventSignup(event.id) + .then(() => { + prev.interested = false; + }) + .finally(() => setUpdating(false)); + } + }; + + const addOrUpdateSignup = () => { + if (!prev.interested) { + addOrUpdateEventSignup(event.id, placeId) + .then(() => { + prev.placeId = placeId; + prev.eventId = event.id; + prev.interested = true; + }) + .finally(() => setUpdating(false)); + } + }; + + const prev = existingSignup.current; + + if (!interested) { + removeSignup(); + } else { + addOrUpdateSignup(); + } + }, [event.id, interested, placeId, updating]); + + useEffect(() => { + getEventSignups(event.id) + .then((signups) => { + setSignups(signups); + for (let signup of signups) { + if (signup.user.id === me?.id) { + setInterested(true); + existingSignup.current.eventId = event.id; + existingSignup.current.placeId = signup.placeId; + existingSignup.current.interested = true; + } + } + }) + .catch(console.error); + }, [event.id, me?.id]); + + return ( + + {name} + + + + {interested ? 'Interested' : 'Not interested'} + + {interested && ( + <> + { + setPlaceId(placeID); + }} + style={placeId != null ? { border: '2px solid ' + green } : {}} + /> + {false && ( +
+ + I don't have any way to get there yet +
+ )} + + + + )} +
+ ); +} diff --git a/src/components/Event/EventCarpools.tsx b/src/components/Event/EventCarpools.tsx new file mode 100644 index 0000000..f102024 --- /dev/null +++ b/src/components/Event/EventCarpools.tsx @@ -0,0 +1,89 @@ +import { useState } from 'react'; +import { IEvent } from './Event'; + +export type ICarpool = { + driver: { + id: number; + name: string; + }; + startTime: string; + endTime: string; + extraDistance: number; +}; + +function CarpoolRow({ carpool }: { carpool: ICarpool }) { + const PADDING = '1rem'; + return ( +
+
+ {carpool.driver.name} +
+ Time:{' '} + + {carpool.startTime} - {carpool.endTime} + +
+ Offset from route: {carpool.extraDistance} miles +
+
+ Request to join +
+
+ ); +} + +const dummyCarpoolData: ICarpool[] = [ + { + driver: { + id: 0, + name: 'Michael Fatemi', + }, + startTime: '10:00', + endTime: '10:10', + extraDistance: 6.9, + }, + { + driver: { + id: 1, + name: 'Joshua Hsueh', + }, + startTime: '10:05', + endTime: '10:10', + extraDistance: 420, + }, +]; + +export default function Carpools({ event }: { event: IEvent }) { + // eslint-disable-next-line + const [carpools, _setCarpools] = useState(dummyCarpoolData); + + return ( +
+

Carpools

+ {carpools.map((carpool) => ( + + ))} +
+ ); +} diff --git a/src/components/Event/EventDetails.tsx b/src/components/Event/EventDetails.tsx new file mode 100644 index 0000000..4d7e4cc --- /dev/null +++ b/src/components/Event/EventDetails.tsx @@ -0,0 +1,39 @@ +import formatStartAndEndTime from '../dates'; + +export default function Details({ + startTime, + endTime, + formattedAddress, +}: { + startTime: string; + endTime: string; + formattedAddress: string; +}) { + return ( +
+ + When: + {formatStartAndEndTime(startTime, endTime)} + +
+
+ + Where: + {formattedAddress} + +
+ ); +} diff --git a/src/components/Event/EventSignups.tsx b/src/components/Event/EventSignups.tsx new file mode 100644 index 0000000..4b230bd --- /dev/null +++ b/src/components/Event/EventSignups.tsx @@ -0,0 +1,91 @@ +import { useMe } from '../hooks'; +import latlongdist, { R_miles } from '../latlongdist'; +import usePlace from '../usePlace'; +import { IEvent, IEventSignup } from './Event'; + +export default function Signups({ + event, + signups, + myPlaceId, +}: { + event: IEvent; + signups: IEventSignup[]; + myPlaceId: string | null; +}) { + const PADDING = '1rem'; + const placeDetails = usePlace(myPlaceId); + const locationLongitude = event.latitude; + const locationLatitude = event.longitude; + const me = useMe(); + + return ( +
+

People

+ {signups.map(({ latitude, longitude, user }) => { + if (user.id === me?.id) { + return null; + } + let extraDistance = null; + if (placeDetails != null) { + const myLatitude = placeDetails.latitude; + const myLongitude = placeDetails.longitude; + const meToThem = latlongdist( + latitude, + longitude, + locationLongitude, + locationLatitude, + R_miles + ); + const themToLocation = latlongdist( + latitude, + longitude, + myLatitude, + myLongitude, + R_miles + ); + const totalWithThem = meToThem + themToLocation; + const totalWithoutThem = latlongdist( + locationLongitude, + locationLatitude, + myLatitude, + myLongitude, + R_miles + ); + extraDistance = totalWithThem - totalWithoutThem; + } + + return ( +
+ {user.name} + {extraDistance ? `: +${extraDistance.toFixed(1)} miles` : ''} +
+ Invite to carpool +
+
+ ); + })} +
+ ); +} diff --git a/src/components/EventCreator/DaysOfWeekSelector.tsx b/src/components/EventCreator/DaysOfWeekSelector.tsx new file mode 100644 index 0000000..f78ecbc --- /dev/null +++ b/src/components/EventCreator/DaysOfWeekSelector.tsx @@ -0,0 +1,86 @@ +import { Dispatch, SetStateAction, useCallback } from 'react'; +import { toggleBit } from '../bits'; +import { green, lightgrey } from '../colors'; + +const DAY_NAMES = [ + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', +]; + +export default function DaysOfWeekSelector({ + daysOfWeek, + update, + disabled = false, +}: { + daysOfWeek: number; + update: Dispatch>; + disabled?: boolean; +}) { + const toggleDayOfWeek = useCallback( + function (idx: 1 | 2 | 3 | 4 | 5 | 6 | 7) { + update((daysOfWeek) => toggleBit(daysOfWeek, idx)); + }, + [update] + ); + + return ( +
+ {DAY_NAMES.map((name, idx) => { + const mask = 0b1000_0000 >> (idx + 1); + const active = (daysOfWeek & mask) !== 0; + return ( +
{ + if (!disabled) { + toggleDayOfWeek( + // @ts-ignore + idx + 1 + ); + } + }} + key={name} + > + {name.charAt(0)} +
+ ); + })} +
+ ); +} diff --git a/src/components/EventCreator.tsx b/src/components/EventCreator/EventCreator.tsx similarity index 61% rename from src/components/EventCreator.tsx rename to src/components/EventCreator/EventCreator.tsx index 547f5ca..9c4a95b 100644 --- a/src/components/EventCreator.tsx +++ b/src/components/EventCreator/EventCreator.tsx @@ -1,101 +1,18 @@ -import { Dispatch, SetStateAction, useCallback, useState } from 'react'; -import { createEvent } from './api'; -import { toggleBit } from './bits'; -import { green, lightgrey } from './colors'; -import { IGroup } from './Group'; -import UIButton from './UIButton'; -import UIDateInput from './UIDateInput'; -import UIDatetimeInput from './UIDatetimeInput'; -import UIPlacesAutocomplete from './UIPlacesAutocomplete'; -import UISecondaryBox from './UISecondaryBox'; -import UITextInput from './UITextInput'; -import useToggle from './useToggle'; +import { useCallback, useState } from 'react'; +import { createEvent } from '../api'; +import { green, lightgrey } from '../colors'; +import { IGroup } from '../Group'; +import UIButton from '../UI/UIButton'; +import UIDateInput from '../UI/UIDateInput'; +import UIDatetimeInput from '../UI/UIDatetimeInput'; +import UIPlacesAutocomplete from '../UI/UIPlacesAutocomplete'; +import UISecondaryBox from '../UI/UISecondaryBox'; +import UITextInput from '../UI/UITextInput'; +import useToggle from '../useToggle'; +import DaysOfWeekSelector from './DaysOfWeekSelector'; const noop = () => {}; -const DAY_NAMES = [ - 'Sunday', - 'Monday', - 'Tuesday', - 'Wednesday', - 'Thursday', - 'Friday', - 'Saturday', -]; - -function DaysOfWeekSelector({ - daysOfWeek, - update, - disabled = false, -}: { - daysOfWeek: number; - update: Dispatch>; - disabled?: boolean; -}) { - const toggleDayOfWeek = useCallback( - function (idx: 1 | 2 | 3 | 4 | 5 | 6 | 7) { - update((daysOfWeek) => toggleBit(daysOfWeek, idx)); - }, - [update] - ); - - return ( -
- {DAY_NAMES.map((name, idx) => { - const mask = 0b1000_0000 >> (idx + 1); - const active = (daysOfWeek & mask) !== 0; - return ( -
{ - if (!disabled) { - toggleDayOfWeek( - // @ts-ignore - idx + 1 - ); - } - }} - key={name} - > - {name.charAt(0)} -
- ); - })} -
- ); -} - export default function EventCreator({ group }: { group: IGroup }) { const [name, setName] = useState(''); const [startTime, setStartTime] = useState(null); diff --git a/src/components/EventCreatorLink.tsx b/src/components/EventCreator/EventCreatorLink.tsx similarity index 84% rename from src/components/EventCreatorLink.tsx rename to src/components/EventCreator/EventCreatorLink.tsx index f88bbe4..6e47db0 100644 --- a/src/components/EventCreatorLink.tsx +++ b/src/components/EventCreator/EventCreatorLink.tsx @@ -1,6 +1,6 @@ import EventCreator from './EventCreator'; -import { IGroup } from './Group'; -import useToggle from './useToggle'; +import { IGroup } from '../Group'; +import useToggle from '../useToggle'; export default function EventCreatorLink({ group }: { group: IGroup }) { const [open, toggle] = useToggle(false); diff --git a/src/components/EventStream.tsx b/src/components/EventStream.tsx index 16b8f09..9fc94ae 100644 --- a/src/components/EventStream.tsx +++ b/src/components/EventStream.tsx @@ -1,4 +1,4 @@ -import Event, { IEvent } from './Event'; +import Event, { IEvent } from './Event/Event'; export default function EventStream({ events }: { events: IEvent[] }) { return ( diff --git a/src/components/Events.tsx b/src/components/Events.tsx index 136656a..3fe11da 100644 --- a/src/components/Events.tsx +++ b/src/components/Events.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { getEvents } from './api'; -import { IEvent } from './Event'; +import { IEvent } from './Event/Event'; import EventStream from './EventStream'; export default function Events() { diff --git a/src/components/Group.tsx b/src/components/Group.tsx index 008e7eb..779369e 100644 --- a/src/components/Group.tsx +++ b/src/components/Group.tsx @@ -2,11 +2,11 @@ import { useEffect, useState } from 'react'; import { useParams } from 'react-router'; import { Link } from 'react-router-dom'; import { getGroup, getGroupEvents } from './api'; -import { IEvent } from './Event'; -import EventCreatorLink from './EventCreatorLink'; +import { IEvent } from './Event/Event'; +import EventCreatorLink from './EventCreator/EventCreatorLink'; import EventStream from './EventStream'; -import GroupSettingsLink from './GroupSettingsLink'; -import UILink from './UILink'; +import GroupSettingsLink from './GroupSettings/GroupSettingsLink'; +import UILink from './UI/UILink'; export type IGroup = { id: number; diff --git a/src/components/GroupCreator.tsx b/src/components/GroupCreator/GroupCreator.tsx similarity index 86% rename from src/components/GroupCreator.tsx rename to src/components/GroupCreator/GroupCreator.tsx index 660935f..51a2566 100644 --- a/src/components/GroupCreator.tsx +++ b/src/components/GroupCreator/GroupCreator.tsx @@ -1,9 +1,9 @@ import { useCallback, useState } from 'react'; -import { createGroup } from './api'; -import UIButton from './UIButton'; -import UILink from './UILink'; -import UISecondaryBox from './UISecondaryBox'; -import UITextInput from './UITextInput'; +import { createGroup } from '../api'; +import UIButton from '../UI/UIButton'; +import UILink from '../UI/UILink'; +import UISecondaryBox from '../UI/UISecondaryBox'; +import UITextInput from '../UI/UITextInput'; export default function GroupCreator() { const [name, setName] = useState(''); diff --git a/src/components/GroupCreatorLink.tsx b/src/components/GroupCreator/GroupCreatorLink.tsx similarity index 91% rename from src/components/GroupCreatorLink.tsx rename to src/components/GroupCreator/GroupCreatorLink.tsx index e9f1ae5..313e787 100644 --- a/src/components/GroupCreatorLink.tsx +++ b/src/components/GroupCreator/GroupCreatorLink.tsx @@ -1,5 +1,5 @@ import GroupCreator from './GroupCreator'; -import useToggle from './useToggle'; +import useToggle from '../useToggle'; export default function GroupCreatorLink() { const [open, toggle] = useToggle(false); diff --git a/src/components/GroupJoinerLink.tsx b/src/components/GroupJoinerLink.tsx index 65fbe35..c122e38 100644 --- a/src/components/GroupJoinerLink.tsx +++ b/src/components/GroupJoinerLink.tsx @@ -1,9 +1,9 @@ import { useCallback, useEffect, useState } from 'react'; import { joinGroup, resolveCode } from './api'; -import UIButton from './UIButton'; -import UIPressable from './UIPressable'; -import UISecondaryBox from './UISecondaryBox'; -import UITextInput from './UITextInput'; +import UIButton from './UI/UIButton'; +import UIPressable from './UI/UIPressable'; +import UISecondaryBox from './UI/UISecondaryBox'; +import UITextInput from './UI/UITextInput'; import useToggle from './useToggle'; export type GroupPreview = { diff --git a/src/components/GroupSettingsLink.tsx b/src/components/GroupSettings/GroupSettings.tsx similarity index 53% rename from src/components/GroupSettingsLink.tsx rename to src/components/GroupSettings/GroupSettings.tsx index 79cf264..fb1d9ff 100644 --- a/src/components/GroupSettingsLink.tsx +++ b/src/components/GroupSettings/GroupSettings.tsx @@ -1,12 +1,11 @@ -import { useCallback, useState } from 'react'; -import { deleteGroup } from './api'; -import { IGroup } from './Group'; -import UILink from './UILink'; -import UIPressable from './UIPressable'; -import UISecondaryBox from './UISecondaryBox'; -import useToggle from './useToggle'; +import { useState, useCallback } from 'react'; +import { deleteGroup } from '../api'; +import { IGroup } from '../types'; +import UILink from '../UI/UILink'; +import UIPressable from '../UI/UIPressable'; +import UISecondaryBox from '../UI/UISecondaryBox'; -function GroupSettings({ group }: { group: IGroup }) { +export default function GroupSettings({ group }: { group: IGroup }) { const [deletionSuccessful, setDeletionSuccessful] = useState(null); @@ -41,27 +40,3 @@ function GroupSettings({ group }: { group: IGroup }) { ); } - -export default function GroupSettingsLink({ group }: { group: IGroup }) { - const [open, toggle] = useToggle(false); - - return ( -
-
- Settings -
- {open && ( - <> -
- - - )} -
- ); -} diff --git a/src/components/GroupSettings/GroupSettingsLink.tsx b/src/components/GroupSettings/GroupSettingsLink.tsx new file mode 100644 index 0000000..0c992ea --- /dev/null +++ b/src/components/GroupSettings/GroupSettingsLink.tsx @@ -0,0 +1,27 @@ +import { IGroup } from '../Group'; +import useToggle from '../useToggle'; +import GroupSettings from './GroupSettings'; + +export default function GroupSettingsLink({ group }: { group: IGroup }) { + const [open, toggle] = useToggle(false); + + return ( +
+
+ Settings +
+ {open && ( + <> +
+ + + )} +
+ ); +} diff --git a/src/components/GroupList.tsx b/src/components/Groups/GroupList.tsx similarity index 86% rename from src/components/GroupList.tsx rename to src/components/Groups/GroupList.tsx index 0e97ae8..ac031a9 100644 --- a/src/components/GroupList.tsx +++ b/src/components/Groups/GroupList.tsx @@ -1,5 +1,5 @@ -import { IGroup } from './Group'; -import UISecondaryBox from './UISecondaryBox'; +import { IGroup } from '../Group'; +import UISecondaryBox from '../UI/UISecondaryBox'; function GroupListItem({ group }: { group: IGroup }) { return ( diff --git a/src/components/Groups.tsx b/src/components/Groups/Groups.tsx similarity index 77% rename from src/components/Groups.tsx rename to src/components/Groups/Groups.tsx index 3b4bee6..13531c6 100644 --- a/src/components/Groups.tsx +++ b/src/components/Groups/Groups.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from 'react'; -import { getGroups } from './api'; -import { IGroup } from './Group'; -import GroupCreatorLink from './GroupCreatorLink'; -import GroupJoinerLink from './GroupJoinerLink'; +import { getGroups } from '../api'; +import { IGroup } from '../Group'; +import GroupCreatorLink from '../GroupCreator/GroupCreatorLink'; +import GroupJoinerLink from '../GroupJoinerLink'; import GroupList from './GroupList'; export default function Groups() { diff --git a/src/components/Notification.tsx b/src/components/Notifications/Notification.tsx similarity index 94% rename from src/components/Notification.tsx rename to src/components/Notifications/Notification.tsx index 4d4c878..3893ab1 100644 --- a/src/components/Notification.tsx +++ b/src/components/Notifications/Notification.tsx @@ -1,7 +1,7 @@ import { useCallback } from 'react'; -import { acceptInvite, acceptRequest, denyInvite, denyRequest } from './api'; -import { IInvitation } from './types'; -import UIButton from './UIButton'; +import { acceptInvite, acceptRequest, denyInvite, denyRequest } from '../api'; +import { IInvitation } from '../types'; +import UIButton from '../UI/UIButton'; export default function Notification({ notification, diff --git a/src/components/Notifications.tsx b/src/components/Notifications/Notifications.tsx similarity index 92% rename from src/components/Notifications.tsx rename to src/components/Notifications/Notifications.tsx index 5338312..773b58c 100644 --- a/src/components/Notifications.tsx +++ b/src/components/Notifications/Notifications.tsx @@ -1,5 +1,5 @@ import Notification from './Notification'; -import { IInvitation } from './types'; +import { IInvitation } from '../types'; export default function Notifications({ notifications, diff --git a/src/components/UIButton.tsx b/src/components/UI/UIButton.tsx similarity index 100% rename from src/components/UIButton.tsx rename to src/components/UI/UIButton.tsx diff --git a/src/components/UIDateInput.tsx b/src/components/UI/UIDateInput.tsx similarity index 100% rename from src/components/UIDateInput.tsx rename to src/components/UI/UIDateInput.tsx diff --git a/src/components/UIDatetimeInput.tsx b/src/components/UI/UIDatetimeInput.tsx similarity index 100% rename from src/components/UIDatetimeInput.tsx rename to src/components/UI/UIDatetimeInput.tsx diff --git a/src/components/UILink.tsx b/src/components/UI/UILink.tsx similarity index 100% rename from src/components/UILink.tsx rename to src/components/UI/UILink.tsx diff --git a/src/components/UIPlacesAutocomplete.tsx b/src/components/UI/UIPlacesAutocomplete.tsx similarity index 100% rename from src/components/UIPlacesAutocomplete.tsx rename to src/components/UI/UIPlacesAutocomplete.tsx diff --git a/src/components/UIPressable.tsx b/src/components/UI/UIPressable.tsx similarity index 100% rename from src/components/UIPressable.tsx rename to src/components/UI/UIPressable.tsx diff --git a/src/components/UIPrimaryTitle.tsx b/src/components/UI/UIPrimaryTitle.tsx similarity index 100% rename from src/components/UIPrimaryTitle.tsx rename to src/components/UI/UIPrimaryTitle.tsx diff --git a/src/components/UISecondaryBox.tsx b/src/components/UI/UISecondaryBox.tsx similarity index 100% rename from src/components/UISecondaryBox.tsx rename to src/components/UI/UISecondaryBox.tsx diff --git a/src/components/UISecondaryHeader.tsx b/src/components/UI/UISecondaryHeader.tsx similarity index 100% rename from src/components/UISecondaryHeader.tsx rename to src/components/UI/UISecondaryHeader.tsx diff --git a/src/components/UITextInput.tsx b/src/components/UI/UITextInput.tsx similarity index 100% rename from src/components/UITextInput.tsx rename to src/components/UI/UITextInput.tsx diff --git a/src/components/UITimeInput.tsx b/src/components/UI/UITimeInput.tsx similarity index 100% rename from src/components/UITimeInput.tsx rename to src/components/UI/UITimeInput.tsx diff --git a/src/components/WheelShare.tsx b/src/components/WheelShare.tsx index ffdada2..d96e999 100644 --- a/src/components/WheelShare.tsx +++ b/src/components/WheelShare.tsx @@ -1,9 +1,9 @@ import logout from './Authentication/logout'; import Events from './Events'; -import Groups from './Groups'; +import Groups from './Groups/Groups'; import { useMe } from './hooks'; -import UIPressable from './UIPressable'; -import UIPrimaryTitle from './UIPrimaryTitle'; +import UIPressable from './UI/UIPressable'; +import UIPrimaryTitle from './UI/UIPrimaryTitle'; export default function WheelShare() { const { name } = useMe()!; diff --git a/src/components/WheelShareLoggedOut.tsx b/src/components/WheelShareLoggedOut.tsx index a5e0213..ad70b17 100644 --- a/src/components/WheelShareLoggedOut.tsx +++ b/src/components/WheelShareLoggedOut.tsx @@ -1,6 +1,6 @@ import authorizationEndpoint from './Authentication/authorizationEndpoint'; -import UILink from './UILink'; -import UIPrimaryTitle from './UIPrimaryTitle'; +import UILink from './UI/UILink'; +import UIPrimaryTitle from './UI/UIPrimaryTitle'; export default function WheelShareLoggedOut() { return ( diff --git a/src/components/api.ts b/src/components/api.ts index 98c8d86..7043d0e 100644 --- a/src/components/api.ts +++ b/src/components/api.ts @@ -1,4 +1,4 @@ -import { IEventSignup } from './Event'; +import { IEventSignup } from './Event/Event'; import { GroupPreview } from './GroupJoinerLink'; import { IInvitation } from './types'; diff --git a/src/components/dates.ts b/src/components/dates.ts new file mode 100644 index 0000000..8c191b3 --- /dev/null +++ b/src/components/dates.ts @@ -0,0 +1,27 @@ +export default function formatStartAndEndTime( + startDatetimeString: string, + endDatetimeString: string +) { + const startDatetime = new Date(startDatetimeString); + const endDatetime = new Date(endDatetimeString); + + if (isNaN(startDatetime.valueOf())) { + console.error('Invalid datetime:', startDatetimeString); + return '(invalid)'; + } + if (isNaN(endDatetime.valueOf())) { + console.error('Invalid datetime:', startDatetimeString); + return '(invalid)'; + } + + const startDateString = startDatetime.toLocaleDateString(); + const endDateString = endDatetime.toLocaleDateString(); + + if (startDateString === endDateString) { + const startTimeString = startDatetime.toLocaleTimeString(); + const endTimeString = endDatetime.toLocaleTimeString(); + return `${startDateString}, ${startTimeString} - ${endTimeString}`; + } else { + return `${startDatetime.toLocaleString()} - ${endDatetime.toLocaleString()}`; + } +}