diff --git a/src/components/NewUI/Event.tsx b/src/components/NewUI/Event.tsx
index b6804cc..3caef51 100644
--- a/src/components/NewUI/Event.tsx
+++ b/src/components/NewUI/Event.tsx
@@ -1,4 +1,6 @@
-import { useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
+import { post } from './api';
+import { green, lightgrey } from './colors';
 import latlongdist, { R_miles } from './latlongdist';
 import UIButton from './UIButton';
 import UIPlacesAutocomplete from './UIPlacesAutocomplete';
@@ -8,9 +10,6 @@ import usePlace from './usePlace';
 import useThrottle from './useThrottle';
 import useToggle from './useToggle';
 
-const green = '#60f760';
-const lightgrey = '#e0e0e0';
-
 export type IEvent = {
 	id: number;
 	name: string;
@@ -18,6 +17,8 @@ export type IEvent = {
 	formattedAddress: string;
 	startTime: string;
 	endTime: string;
+	latitude: number;
+	longitude: number;
 };
 
 function formatStartAndEndTime(
@@ -206,13 +207,13 @@ const dummyPeopleData: IPerson[] = [
 		longitude: 10.12,
 	},
 ];
-function People({ event, placeId }: { event: IEvent; placeId: string }) {
+function People({ event, placeId }: { event: IEvent; placeId: string | null }) {
 	const PADDING = '1rem';
 	// eslint-disable-next-line
 	const [people, setPeople] = useState(dummyPeopleData);
 	const placeDetails = usePlace(placeId);
-	const myLatitude = 10;
-	const myLongitude = 10;
+	const locationLongitude = event.latitude;
+	const locationLatitude = event.longitude;
 
 	return (
 		<div style={{ display: 'flex', flexDirection: 'column' }}>
@@ -220,28 +221,28 @@ function People({ event, placeId }: { event: IEvent; placeId: string }) {
 			{people.map(({ name, latitude, longitude, id }) => {
 				let extraDistance = null;
 				if (placeDetails != null) {
-					const locationLatitude = placeDetails.latitude;
-					const locationLongitude = placeDetails.longitude;
+					const myLatitude = placeDetails.latitude;
+					const myLongitude = placeDetails.longitude;
 					const meToThem = latlongdist(
 						latitude,
 						longitude,
-						myLatitude,
-						myLongitude,
+						locationLongitude,
+						locationLatitude,
 						R_miles
 					);
 					const themToLocation = latlongdist(
 						latitude,
 						longitude,
-						locationLatitude,
-						locationLongitude,
+						myLatitude,
+						myLongitude,
 						R_miles
 					);
 					const totalWithThem = meToThem + themToLocation;
 					const totalWithoutThem = latlongdist(
+						locationLongitude,
+						locationLatitude,
 						myLatitude,
 						myLongitude,
-						locationLatitude,
-						locationLongitude,
 						R_miles
 					);
 					extraDistance = totalWithThem - totalWithoutThem;
@@ -285,9 +286,47 @@ function People({ event, placeId }: { event: IEvent; placeId: string }) {
 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!);
+	const [placeId, setPlaceId] = useState<string | null>(null);
 	const [interested, toggleInterested] = useToggle(false);
+	const [updating, setUpdating] = useState(false);
 	const toggleInterestedThrottled = useThrottle(toggleInterested, 500);
+	const existingSignup = useRef({
+		interested: false,
+		placeId: null as string | null,
+		eventId: null as number | null,
+	});
+
+	useEffect(() => {
+		const prev = existingSignup.current;
+		if (prev.interested === false && interested === false) {
+			return;
+		}
+
+		if (
+			(prev.interested === true && interested === false) ||
+			(interested === true && prev.placeId !== null && placeId === null)
+		) {
+			fetch(`http://localhost:5000/api/events/${event.id}/signup`, {
+				method: 'delete',
+			}).finally(() => setUpdating(false));
+			prev.interested = false;
+			return;
+		}
+
+		if (
+			interested === true &&
+			(prev.placeId !== placeId || prev.eventId !== event.id)
+		) {
+			prev.placeId = placeId;
+			prev.eventId = event.id;
+			prev.interested = true;
+
+			post(`/events/${event.id}/signup`, {
+				placeId,
+			}).finally(() => setUpdating(false));
+			return;
+		}
+	}, [event.id, interested, placeId, updating]);
 
 	return (
 		<UISecondaryBox>
diff --git a/src/components/NewUI/EventCreator.tsx b/src/components/NewUI/EventCreator.tsx
index 6a2f6af..2b1a2f0 100644
--- a/src/components/NewUI/EventCreator.tsx
+++ b/src/components/NewUI/EventCreator.tsx
@@ -1,14 +1,101 @@
-import { useCallback, useState } from 'react';
+import { Dispatch, SetStateAction, useCallback, useState } from 'react';
 import { post } 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';
 
 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);
@@ -17,22 +104,44 @@ export default function EventCreator({ group }: { group: IGroup }) {
 	const [creating, setCreating] = useState(false);
 	const [createdEventId, setCreatedEventId] = useState(-1);
 
+	const [recurring, toggleRecurring] = useToggle(false);
+	const [daysOfWeek, setDaysOfWeek] = useState(0);
+	const [endDate, setEndDate] = useState<Date | null>(null);
+
+	const durationIsNegative =
+		endTime && startTime && endTime.getTime() < startTime.getTime();
+
 	const buttonEnabled =
 		name.length > 0 &&
 		startTime != null &&
 		endTime != null &&
 		placeId != null &&
+		(!recurring || daysOfWeek || endDate !== null) &&
+		!durationIsNegative &&
 		!creating;
 
 	const createEvent = useCallback(() => {
 		if (!creating) {
+			if (startTime === null) {
+				console.warn(
+					'Tried to create an event where the start time was unspecified.'
+				);
+				return;
+			}
+
+			const duration =
+				endTime !== null ? (endTime.getTime() - startTime.getTime()) / 60 : 0;
+
 			setCreating(true);
+
 			post('/events', {
 				name,
 				startTime,
-				endTime,
+				duration,
+				endDate,
 				groupId: group.id,
 				placeId,
+				daysOfWeek,
 			})
 				.then((response) => response.json())
 				.then(({ id }) => {
@@ -40,7 +149,16 @@ export default function EventCreator({ group }: { group: IGroup }) {
 				})
 				.finally(() => setCreating(false));
 		}
-	}, [creating, name, startTime, endTime, group.id, placeId]);
+	}, [
+		creating,
+		name,
+		startTime,
+		endTime,
+		group.id,
+		placeId,
+		daysOfWeek,
+		endDate,
+	]);
 
 	return (
 		<UISecondaryBox style={{ width: '100%', boxSizing: 'border-box' }}>
@@ -63,6 +181,34 @@ export default function EventCreator({ group }: { group: IGroup }) {
 					setPlaceId(placeId);
 				}}
 			/>
+			<UIButton
+				onClick={toggleRecurring}
+				style={{
+					backgroundColor: recurring ? green : lightgrey,
+					color: recurring ? 'white' : 'black',
+					transition: 'color 0.2s, background-color 0.2s',
+					marginBottom: '1.5rem',
+				}}
+			>
+				Recurring event
+			</UIButton>
+			{recurring && (
+				<>
+					Days of week
+					<DaysOfWeekSelector
+						daysOfWeek={daysOfWeek}
+						update={setDaysOfWeek}
+						disabled={creating}
+					/>
+					Date of last occurence
+					<UIDateInput onChangedDate={setEndDate} disabled={creating} />
+				</>
+			)}
+			{durationIsNegative && (
+				<span style={{ marginTop: '1rem' }}>
+					The start time can't be after the end time.
+				</span>
+			)}
 			{createdEventId === -1 ? (
 				<UIButton
 					onClick={buttonEnabled ? createEvent : noop}
diff --git a/src/components/NewUI/UIDateInput.tsx b/src/components/NewUI/UIDateInput.tsx
new file mode 100644
index 0000000..8780274
--- /dev/null
+++ b/src/components/NewUI/UIDateInput.tsx
@@ -0,0 +1,44 @@
+import { useCallback } from 'react';
+
+const baseStyle = {
+	marginTop: '0.5em',
+	padding: '0.5em',
+	fontFamily: 'Inter',
+	fontSize: '1.25rem',
+	borderRadius: '0.5em',
+	border: '0px',
+};
+
+export default function UIDateInput({
+	onChangedDate,
+	disabled = false,
+}: {
+	onChangedDate: (date: Date | null) => void;
+	disabled?: boolean;
+}) {
+	const onChange = useCallback(
+		(e) => {
+			// YYYY-MM-DD or "" (empty string)
+			const dateAsString = e.target.value as string;
+			if (typeof dateAsString !== 'string' || dateAsString.length === 0) {
+				onChangedDate(null);
+			}
+			const year = dateAsString.slice(0, 4);
+			const month = dateAsString.slice(5, 7);
+			const day = dateAsString.slice(8, 10);
+
+			// Midnight in the timezone of the user
+			const date = new Date(`${year}-${month}-${day}T00:00:00`);
+			onChangedDate(date);
+		},
+		[onChangedDate]
+	);
+	return (
+		<input
+			style={baseStyle}
+			type="date"
+			disabled={disabled}
+			onChange={onChange}
+		/>
+	);
+}
diff --git a/src/components/NewUI/api.ts b/src/components/NewUI/api.ts
index 81168bf..10f5123 100644
--- a/src/components/NewUI/api.ts
+++ b/src/components/NewUI/api.ts
@@ -7,3 +7,12 @@ export function post(path: string, data: any) {
 		},
 	});
 }
+
+export function delete$(path: string, data: any) {
+	return fetch('http://localhost:5000/api' + path, {
+		method: 'delete',
+		headers: {
+			'Content-Type': 'application/json',
+		},
+	});
+}
diff --git a/src/components/NewUI/bits.ts b/src/components/NewUI/bits.ts
new file mode 100644
index 0000000..a4959f4
--- /dev/null
+++ b/src/components/NewUI/bits.ts
@@ -0,0 +1,23 @@
+export function setBit(n: number, idx: number, active: boolean) {
+	if (idx < 1 || idx > 7 || !isFinite(idx)) {
+		throw new Error('invalid idx. idx must be from 1 - 7.');
+	}
+
+	const mask = 0b1000_0000 >> idx;
+
+	if (active) {
+		return n | mask;
+	} else {
+		return n & ~mask;
+	}
+}
+
+export function toggleBit(n: number, idx: number) {
+	if (idx < 1 || idx > 7 || !isFinite(idx)) {
+		throw new Error('invalid idx. idx must be from 1 - 7.');
+	}
+
+	const mask = 0b1000_0000 >> idx;
+
+	return n ^ mask;
+}
diff --git a/src/components/NewUI/colors.ts b/src/components/NewUI/colors.ts
new file mode 100644
index 0000000..c69ddb8
--- /dev/null
+++ b/src/components/NewUI/colors.ts
@@ -0,0 +1,2 @@
+export const green = '#60f760';
+export const lightgrey = '#e0e0e0';
diff --git a/src/components/NewUI/usePlace.ts b/src/components/NewUI/usePlace.ts
index ffbc0fb..9d17cff 100644
--- a/src/components/NewUI/usePlace.ts
+++ b/src/components/NewUI/usePlace.ts
@@ -2,7 +2,7 @@ import { useState, useEffect, useCallback } from 'react';
 import { getPlaceDetails, PlaceDetails } from '../../api/google';
 import useThrottle from './useThrottle';
 
-export default function usePlace(placeId: string) {
+export default function usePlace(placeId: string | null) {
 	const [placeDetails, setPlaceDetails] = useState<PlaceDetails | null>(null);
 
 	const updatePlaceDetails = useCallback(() => {