From 98a8e51b7bff725cb7f1e4f8cd0edff5ccbb659c Mon Sep 17 00:00:00 2001
From: Michael Fatemi <myfatemi04@gmail.com>
Date: Thu, 8 Jul 2021 13:51:24 -0400
Subject: [PATCH] Organize

---
 src/components/App.tsx                        |   2 +-
 src/components/Availability.tsx               | 151 -------
 src/components/Carpool.tsx                    |   2 +-
 src/components/Event.tsx                      | 409 ------------------
 src/components/Event/Event.tsx                | 162 +++++++
 src/components/Event/EventCarpools.tsx        |  89 ++++
 src/components/Event/EventDetails.tsx         |  39 ++
 src/components/Event/EventSignups.tsx         |  91 ++++
 .../EventCreator/DaysOfWeekSelector.tsx       |  86 ++++
 .../{ => EventCreator}/EventCreator.tsx       | 107 +----
 .../{ => EventCreator}/EventCreatorLink.tsx   |   4 +-
 src/components/EventStream.tsx                |   2 +-
 src/components/Events.tsx                     |   2 +-
 src/components/Group.tsx                      |   8 +-
 .../{ => GroupCreator}/GroupCreator.tsx       |  10 +-
 .../{ => GroupCreator}/GroupCreatorLink.tsx   |   2 +-
 src/components/GroupJoinerLink.tsx            |   8 +-
 .../GroupSettings.tsx}                        |  39 +-
 .../GroupSettings/GroupSettingsLink.tsx       |  27 ++
 src/components/{ => Groups}/GroupList.tsx     |   4 +-
 src/components/{ => Groups}/Groups.tsx        |   8 +-
 .../{ => Notifications}/Notification.tsx      |   6 +-
 .../{ => Notifications}/Notifications.tsx     |   2 +-
 src/components/{ => UI}/UIButton.tsx          |   0
 src/components/{ => UI}/UIDateInput.tsx       |   0
 src/components/{ => UI}/UIDatetimeInput.tsx   |   0
 src/components/{ => UI}/UILink.tsx            |   0
 .../{ => UI}/UIPlacesAutocomplete.tsx         |   0
 src/components/{ => UI}/UIPressable.tsx       |   0
 src/components/{ => UI}/UIPrimaryTitle.tsx    |   0
 src/components/{ => UI}/UISecondaryBox.tsx    |   0
 src/components/{ => UI}/UISecondaryHeader.tsx |   0
 src/components/{ => UI}/UITextInput.tsx       |   0
 src/components/{ => UI}/UITimeInput.tsx       |   0
 src/components/WheelShare.tsx                 |   6 +-
 src/components/WheelShareLoggedOut.tsx        |   4 +-
 src/components/api.ts                         |   2 +-
 src/components/dates.ts                       |  27 ++
 38 files changed, 576 insertions(+), 723 deletions(-)
 delete mode 100644 src/components/Availability.tsx
 delete mode 100644 src/components/Event.tsx
 create mode 100644 src/components/Event/Event.tsx
 create mode 100644 src/components/Event/EventCarpools.tsx
 create mode 100644 src/components/Event/EventDetails.tsx
 create mode 100644 src/components/Event/EventSignups.tsx
 create mode 100644 src/components/EventCreator/DaysOfWeekSelector.tsx
 rename src/components/{ => EventCreator}/EventCreator.tsx (61%)
 rename src/components/{ => EventCreator}/EventCreatorLink.tsx (84%)
 rename src/components/{ => GroupCreator}/GroupCreator.tsx (86%)
 rename src/components/{ => GroupCreator}/GroupCreatorLink.tsx (91%)
 rename src/components/{GroupSettingsLink.tsx => GroupSettings/GroupSettings.tsx} (53%)
 create mode 100644 src/components/GroupSettings/GroupSettingsLink.tsx
 rename src/components/{ => Groups}/GroupList.tsx (86%)
 rename src/components/{ => Groups}/Groups.tsx (77%)
 rename src/components/{ => Notifications}/Notification.tsx (94%)
 rename src/components/{ => Notifications}/Notifications.tsx (92%)
 rename src/components/{ => UI}/UIButton.tsx (100%)
 rename src/components/{ => UI}/UIDateInput.tsx (100%)
 rename src/components/{ => UI}/UIDatetimeInput.tsx (100%)
 rename src/components/{ => UI}/UILink.tsx (100%)
 rename src/components/{ => UI}/UIPlacesAutocomplete.tsx (100%)
 rename src/components/{ => UI}/UIPressable.tsx (100%)
 rename src/components/{ => UI}/UIPrimaryTitle.tsx (100%)
 rename src/components/{ => UI}/UISecondaryBox.tsx (100%)
 rename src/components/{ => UI}/UISecondaryHeader.tsx (100%)
 rename src/components/{ => UI}/UITextInput.tsx (100%)
 rename src/components/{ => UI}/UITimeInput.tsx (100%)
 create mode 100644 src/components/dates.ts

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<AvailabilityKind, string> = {
-	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<HTMLDivElement> = useCallback(
-		(event) => {
-			onSelected(bind);
-		},
-		[onSelected, bind]
-	);
-
-	return (
-		<div style={selected ? selectedOptionStyle : optionStyle} onClick={select}>
-			{availabilityNames[bind]}
-		</div>
-	);
-}
-
-// 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 (
-		<div
-			style={{
-				display: 'flex',
-				flexDirection: 'column',
-				borderRadius: '0.5rem',
-				overflow: 'hidden',
-				marginTop: '1rem',
-				marginBottom: '1rem',
-			}}
-			tabIndex={0}
-			onBlur={() => setFocused(false)}
-		>
-			{focused ? (
-				<>
-					<Option bind="going" current={selected} onSelected={onSelected} />
-					<Option
-						bind="interested"
-						current={selected}
-						onSelected={onSelected}
-					/>
-					<Option
-						bind="not-interested"
-						current={selected}
-						onSelected={onSelected}
-					/>
-				</>
-			) : (
-				<Option
-					bind={selected}
-					current={selected}
-					onSelected={() => setFocused(true)}
-				/>
-			)}
-		</div>
-	);
-}
-
-export default function Availability({
-	selected,
-	onSelected: onSelectedInner,
-}: {
-	selected: AvailabilityKind;
-	onSelected: (kind: AvailabilityKind) => void;
-}) {
-	const onSelected: ReactEventHandler<HTMLSelectElement> = useCallback(
-		(event) => {
-			onSelectedInner(
-				// @ts-ignore
-				event.target.value
-			);
-			event.preventDefault();
-		},
-		[onSelectedInner]
-	);
-	return (
-		<select
-			value={selected}
-			onChange={onSelected}
-			style={{
-				fontFamily: 'Inter',
-				fontSize: '1rem',
-				border: '0px solid black',
-				borderRadius: '0.5rem',
-				padding: '0.5rem',
-				marginTop: '1rem',
-			}}
-		>
-			<option value="going">Going</option>
-			<option value="interested">Interested</option>
-			<option value="not-interested">Not interested</option>
-		</select>
-	);
-}
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 (
-		<span
-			style={{
-				color: '#303030',
-				textAlign: 'center',
-			}}
-		>
-			{name}
-		</span>
-	);
-}
-
-function Details({
-	startTime,
-	endTime,
-	formattedAddress,
-}: {
-	startTime: string;
-	endTime: string;
-	formattedAddress: string;
-}) {
-	return (
-		<div
-			style={{
-				marginTop: '0.5rem',
-				textAlign: 'left',
-			}}
-		>
-			<span
-				style={{
-					color: '#303030',
-				}}
-			>
-				<b>When: </b>
-				{formatStartAndEndTime(startTime, endTime)}
-			</span>
-			<br />
-			<br />
-			<span
-				style={{
-					color: '#303030',
-				}}
-			>
-				<b>Where: </b>
-				{formattedAddress}
-			</span>
-		</div>
-	);
-}
-
-export type ICarpool = {
-	driver: {
-		id: number;
-		name: string;
-	};
-	startTime: string;
-	endTime: string;
-	extraDistance: number;
-};
-
-function CarpoolRow({ carpool }: { carpool: ICarpool }) {
-	const PADDING = '1rem';
-	return (
-		<div
-			style={{
-				display: 'flex',
-				alignItems: 'center',
-				position: 'relative',
-				padding: PADDING,
-				borderRadius: '0.5rem',
-				border: '1px solid #e0e0e0',
-				marginTop: '0.5rem',
-				marginBottom: '0.5rem',
-			}}
-		>
-			<div>
-				<span style={{ fontWeight: 500 }}>{carpool.driver.name}</span>
-				<br />
-				Time:{' '}
-				<b>
-					{carpool.startTime} - {carpool.endTime}
-				</b>
-				<br />
-				Offset from route: <b>{carpool.extraDistance} miles</b>
-			</div>
-			<div
-				style={{
-					borderRadius: '0.5em',
-					cursor: 'pointer',
-					padding: '0.5em',
-					position: 'absolute',
-					right: PADDING,
-					userSelect: 'none',
-					backgroundColor: '#e0e0e0',
-				}}
-			>
-				Request to join
-			</div>
-		</div>
-	);
-}
-
-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 (
-		<div style={{ display: 'flex', flexDirection: 'column' }}>
-			<h3 style={{ marginBlockEnd: '0' }}>Carpools</h3>
-			{carpools.map((carpool) => (
-				<CarpoolRow carpool={carpool} key={carpool.driver.id} />
-			))}
-		</div>
-	);
-}
-
-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 (
-		<div style={{ display: 'flex', flexDirection: 'column' }}>
-			<h3 style={{ marginBlockEnd: '0' }}>People</h3>
-			{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 (
-					<div
-						style={{
-							display: 'flex',
-							alignItems: 'center',
-							position: 'relative',
-							padding: '1rem',
-							borderRadius: '0.5rem',
-							border: '1px solid #e0e0e0',
-							marginTop: '0.5rem',
-							marginBottom: '0.5rem',
-						}}
-						key={user.id}
-					>
-						<b>{user.name}</b>
-						{extraDistance ? `: +${extraDistance.toFixed(1)} miles` : ''}
-						<div
-							style={{
-								borderRadius: '0.5em',
-								cursor: 'pointer',
-								padding: '0.5em',
-								position: 'absolute',
-								right: PADDING,
-								userSelect: 'none',
-								backgroundColor: '#e0e0e0',
-							}}
-						>
-							Invite to carpool
-						</div>
-					</div>
-				);
-			})}
-		</div>
-	);
-}
-
-export default function Event({ event }: { event: IEvent }) {
-	const { name, group, formattedAddress, startTime, endTime } = event;
-	const [haveRide, toggleHaveRide] = useToggle(false);
-	const [placeId, setPlaceId] = useState<string | null>(null);
-	const [interested, setInterested] = useState(false);
-	const [updating, setUpdating] = useState(false);
-	const [signups, setSignups] = useState<IEventSignup[]>([]);
-	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 (
-		<UISecondaryBox>
-			<UISecondaryHeader>{name}</UISecondaryHeader>
-			<GroupName name={group} />
-			<Details {...{ startTime, endTime, formattedAddress }} />
-			<UIButton
-				onClick={toggleInterestedThrottled}
-				style={{
-					backgroundColor: interested ? green : lightgrey,
-					color: interested ? 'white' : 'black',
-					transition: 'color 0.2s, background-color 0.2s',
-				}}
-			>
-				{interested ? 'Interested' : 'Not interested'}
-			</UIButton>
-			{interested && (
-				<>
-					<UIPlacesAutocomplete
-						placeholder="Pickup and dropoff location"
-						onSelected={(_address, placeID) => {
-							setPlaceId(placeID);
-						}}
-						style={placeId != null ? { border: '2px solid ' + green } : {}}
-					/>
-					{false && (
-						<div
-							style={{
-								display: 'flex',
-								alignItems: 'center',
-								cursor: 'pointer',
-								userSelect: 'none',
-							}}
-							onClick={toggleHaveRide}
-						>
-							<input
-								type="checkbox"
-								style={{
-									borderRadius: '0.5em',
-									width: '2em',
-									height: '2em',
-									margin: '1em',
-								}}
-								checked={haveRide}
-							/>
-							I don't have any way to get there yet
-						</div>
-					)}
-					<Carpools event={event} />
-					<Signups event={event} myPlaceId={placeId} signups={signups} />
-				</>
-			)}
-		</UISecondaryBox>
-	);
-}
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 <span style={{ color: '#303030', textAlign: 'center' }}>{name}</span>;
+}
+
+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<string | null>(null);
+	const [interested, setInterested] = useState(false);
+	const [updating, setUpdating] = useState(false);
+	const [signups, setSignups] = useState<IEventSignup[]>([]);
+	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 (
+		<UISecondaryBox>
+			<UISecondaryHeader>{name}</UISecondaryHeader>
+			<GroupName name={group} />
+			<EventDetails {...{ startTime, endTime, formattedAddress }} />
+			<UIButton
+				onClick={toggleInterestedThrottled}
+				style={{
+					backgroundColor: interested ? green : lightgrey,
+					color: interested ? 'white' : 'black',
+					transition: 'color 0.2s, background-color 0.2s',
+				}}
+			>
+				{interested ? 'Interested' : 'Not interested'}
+			</UIButton>
+			{interested && (
+				<>
+					<UIPlacesAutocomplete
+						placeholder="Pickup and dropoff location"
+						onSelected={(_address, placeID) => {
+							setPlaceId(placeID);
+						}}
+						style={placeId != null ? { border: '2px solid ' + green } : {}}
+					/>
+					{false && (
+						<div
+							style={{
+								display: 'flex',
+								alignItems: 'center',
+								cursor: 'pointer',
+								userSelect: 'none',
+							}}
+							onClick={toggleHaveRide}
+						>
+							<input
+								type="checkbox"
+								style={{
+									borderRadius: '0.5em',
+									width: '2em',
+									height: '2em',
+									margin: '1em',
+								}}
+								checked={haveRide}
+							/>
+							I don't have any way to get there yet
+						</div>
+					)}
+					<EventCarpools event={event} />
+					<EventSignups event={event} myPlaceId={placeId} signups={signups} />
+				</>
+			)}
+		</UISecondaryBox>
+	);
+}
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 (
+		<div
+			style={{
+				display: 'flex',
+				alignItems: 'center',
+				position: 'relative',
+				padding: PADDING,
+				borderRadius: '0.5rem',
+				border: '1px solid #e0e0e0',
+				marginTop: '0.5rem',
+				marginBottom: '0.5rem',
+			}}
+		>
+			<div>
+				<span style={{ fontWeight: 500 }}>{carpool.driver.name}</span>
+				<br />
+				Time:{' '}
+				<b>
+					{carpool.startTime} - {carpool.endTime}
+				</b>
+				<br />
+				Offset from route: <b>{carpool.extraDistance} miles</b>
+			</div>
+			<div
+				style={{
+					borderRadius: '0.5em',
+					cursor: 'pointer',
+					padding: '0.5em',
+					position: 'absolute',
+					right: PADDING,
+					userSelect: 'none',
+					backgroundColor: '#e0e0e0',
+				}}
+			>
+				Request to join
+			</div>
+		</div>
+	);
+}
+
+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 (
+		<div style={{ display: 'flex', flexDirection: 'column' }}>
+			<h3 style={{ marginBlockEnd: '0' }}>Carpools</h3>
+			{carpools.map((carpool) => (
+				<CarpoolRow carpool={carpool} key={carpool.driver.id} />
+			))}
+		</div>
+	);
+}
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 (
+		<div
+			style={{
+				marginTop: '0.5rem',
+				textAlign: 'left',
+			}}
+		>
+			<span
+				style={{
+					color: '#303030',
+				}}
+			>
+				<b>When: </b>
+				{formatStartAndEndTime(startTime, endTime)}
+			</span>
+			<br />
+			<br />
+			<span
+				style={{
+					color: '#303030',
+				}}
+			>
+				<b>Where: </b>
+				{formattedAddress}
+			</span>
+		</div>
+	);
+}
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 (
+		<div style={{ display: 'flex', flexDirection: 'column' }}>
+			<h3 style={{ marginBlockEnd: '0' }}>People</h3>
+			{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 (
+					<div
+						style={{
+							display: 'flex',
+							alignItems: 'center',
+							position: 'relative',
+							padding: '1rem',
+							borderRadius: '0.5rem',
+							border: '1px solid #e0e0e0',
+							marginTop: '0.5rem',
+							marginBottom: '0.5rem',
+						}}
+						key={user.id}
+					>
+						<b>{user.name}</b>
+						{extraDistance ? `: +${extraDistance.toFixed(1)} miles` : ''}
+						<div
+							style={{
+								borderRadius: '0.5em',
+								cursor: 'pointer',
+								padding: '0.5em',
+								position: 'absolute',
+								right: PADDING,
+								userSelect: 'none',
+								backgroundColor: '#e0e0e0',
+							}}
+						>
+							Invite to carpool
+						</div>
+					</div>
+				);
+			})}
+		</div>
+	);
+}
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<SetStateAction<number>>;
+	disabled?: boolean;
+}) {
+	const toggleDayOfWeek = useCallback(
+		function (idx: 1 | 2 | 3 | 4 | 5 | 6 | 7) {
+			update((daysOfWeek) => toggleBit(daysOfWeek, idx));
+		},
+		[update]
+	);
+
+	return (
+		<div
+			style={{
+				display: 'flex',
+				flexDirection: 'row',
+				margin: '1rem auto',
+			}}
+		>
+			{DAY_NAMES.map((name, idx) => {
+				const mask = 0b1000_0000 >> (idx + 1);
+				const active = (daysOfWeek & mask) !== 0;
+				return (
+					<div
+						style={{
+							borderRadius: '100%',
+							cursor: 'pointer',
+							backgroundColor: active
+								? disabled
+									? // lighter version of green
+									  'rgba(96, 247, 96, 0.5)'
+									: green
+								: disabled
+								? // lighter version of lightgrey
+								  'rgba(224, 224, 224, 0.5)'
+								: lightgrey,
+							color: active
+								? 'white'
+								: disabled
+								? 'rgba(0, 0, 0, 0.5)'
+								: 'black',
+							userSelect: 'none',
+							width: '2em',
+							height: '2em',
+							margin: '0.5rem',
+							display: 'flex',
+							flexDirection: 'row',
+							alignItems: 'center',
+							justifyContent: 'center',
+						}}
+						onClick={() => {
+							if (!disabled) {
+								toggleDayOfWeek(
+									// @ts-ignore
+									idx + 1
+								);
+							}
+						}}
+						key={name}
+					>
+						{name.charAt(0)}
+					</div>
+				);
+			})}
+		</div>
+	);
+}
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<SetStateAction<number>>;
-	disabled?: boolean;
-}) {
-	const toggleDayOfWeek = useCallback(
-		function (idx: 1 | 2 | 3 | 4 | 5 | 6 | 7) {
-			update((daysOfWeek) => toggleBit(daysOfWeek, idx));
-		},
-		[update]
-	);
-
-	return (
-		<div
-			style={{
-				display: 'flex',
-				flexDirection: 'row',
-				margin: '1rem auto',
-			}}
-		>
-			{DAY_NAMES.map((name, idx) => {
-				const mask = 0b1000_0000 >> (idx + 1);
-				const active = (daysOfWeek & mask) !== 0;
-				return (
-					<div
-						style={{
-							borderRadius: '100%',
-							cursor: 'pointer',
-							backgroundColor: active
-								? disabled
-									? // lighter version of green
-									  'rgba(96, 247, 96, 0.5)'
-									: green
-								: disabled
-								? // lighter version of lightgrey
-								  'rgba(224, 224, 224, 0.5)'
-								: lightgrey,
-							color: active
-								? 'white'
-								: disabled
-								? 'rgba(0, 0, 0, 0.5)'
-								: 'black',
-							userSelect: 'none',
-							width: '2em',
-							height: '2em',
-							margin: '0.5rem',
-							display: 'flex',
-							flexDirection: 'row',
-							alignItems: 'center',
-							justifyContent: 'center',
-						}}
-						onClick={() => {
-							if (!disabled) {
-								toggleDayOfWeek(
-									// @ts-ignore
-									idx + 1
-								);
-							}
-						}}
-						key={name}
-					>
-						{name.charAt(0)}
-					</div>
-				);
-			})}
-		</div>
-	);
-}
-
 export default function EventCreator({ group }: { group: IGroup }) {
 	const [name, setName] = useState('');
 	const [startTime, setStartTime] = useState<Date | null>(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<boolean | null>(null);
 
@@ -41,27 +40,3 @@ function GroupSettings({ group }: { group: IGroup }) {
 		</UISecondaryBox>
 	);
 }
-
-export default function GroupSettingsLink({ group }: { group: IGroup }) {
-	const [open, toggle] = useToggle(false);
-
-	return (
-		<div>
-			<div
-				style={{
-					cursor: 'pointer',
-					userSelect: 'none',
-				}}
-				onClick={toggle}
-			>
-				Settings
-			</div>
-			{open && (
-				<>
-					<br />
-					<GroupSettings group={group} />
-				</>
-			)}
-		</div>
-	);
-}
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 (
+		<div>
+			<div
+				style={{
+					cursor: 'pointer',
+					userSelect: 'none',
+				}}
+				onClick={toggle}
+			>
+				Settings
+			</div>
+			{open && (
+				<>
+					<br />
+					<GroupSettings group={group} />
+				</>
+			)}
+		</div>
+	);
+}
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()}`;
+	}
+}