From bafc26fb06fc6a1633907356a175fdcf0e473b9b Mon Sep 17 00:00:00 2001
From: Michael Fatemi <myfatemi04@gmail.com>
Date: Tue, 13 Jul 2021 20:48:10 -0400
Subject: [PATCH] add grouphooks, groupprovider (probably will not be used)

---
 src/components/App.tsx                        |  41 +++----
 src/components/EventCreator/EventCreator.tsx  |   4 +-
 .../EventCreator/EventCreatorLink.tsx         |   2 +-
 src/components/Group.tsx                      |   8 +-
 .../GroupSettings/GroupSettingsLink.tsx       |   2 +-
 src/components/Groups/GroupList.tsx           |   2 +-
 src/components/Groups/Groups.tsx              |   2 +-
 src/components/api.ts                         |   4 +-
 src/components/types.ts                       |   1 +
 src/state/EventsProvider.tsx                  |  74 ++++++++++++
 src/state/GroupHooks.tsx                      |   7 ++
 src/state/GroupsProvider.tsx                  | 105 ++++++++++++++++++
 12 files changed, 218 insertions(+), 34 deletions(-)
 create mode 100644 src/state/EventsProvider.tsx
 create mode 100644 src/state/GroupHooks.tsx
 create mode 100644 src/state/GroupsProvider.tsx

diff --git a/src/components/App.tsx b/src/components/App.tsx
index 40b03b2..f7457be 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -1,5 +1,6 @@
 import { CSSProperties, lazy, Suspense } from 'react';
 import { BrowserRouter, Route, Switch } from 'react-router-dom';
+import GroupsProvider from '../state/GroupsProvider';
 import NotificationsProvider from '../state/Notifications/NotificationsProvider';
 import { useMe } from './hooks';
 import WheelShare from './WheelShare';
@@ -24,28 +25,30 @@ export default function App() {
 	const user = useMe();
 	return (
 		<NotificationsProvider>
-			<div style={{ padding: '1rem', maxWidth: '100vw' }}>
-				<div style={style}>
-					<BrowserRouter>
-						<Switch>
-							<Route
-								path="/"
-								exact
-								component={user ? WheelShare : WheelShareLoggedOut}
-							/>
-							<Suspense fallback={null}>
-								<Route path="/groups/:id" component={Group} />
+			<GroupsProvider>
+				<div style={{ padding: '1rem', maxWidth: '100vw' }}>
+					<div style={style}>
+						<BrowserRouter>
+							<Switch>
 								<Route
-									component={Authenticator}
-									path="/auth/:provider/callback"
+									path="/"
+									exact
+									component={user ? WheelShare : WheelShareLoggedOut}
 								/>
-								<Route path="/carpools/:id" component={CarpoolPage} />
-								<Route path="/events/:id" component={EventPage} />
-							</Suspense>
-						</Switch>
-					</BrowserRouter>
+								<Suspense fallback={null}>
+									<Route path="/groups/:id" component={Group} />
+									<Route
+										component={Authenticator}
+										path="/auth/:provider/callback"
+									/>
+									<Route path="/carpools/:id" component={CarpoolPage} />
+									<Route path="/events/:id" component={EventPage} />
+								</Suspense>
+							</Switch>
+						</BrowserRouter>
+					</div>
 				</div>
-			</div>
+			</GroupsProvider>
 		</NotificationsProvider>
 	);
 }
diff --git a/src/components/EventCreator/EventCreator.tsx b/src/components/EventCreator/EventCreator.tsx
index fe9fc91..8da8878 100644
--- a/src/components/EventCreator/EventCreator.tsx
+++ b/src/components/EventCreator/EventCreator.tsx
@@ -1,7 +1,7 @@
 import { useCallback, useState } from 'react';
-import { createEvent } from '../api';
 import { green, lightgrey } from '../../lib/colors';
-import { IGroup } from '../Group';
+import { createEvent } from '../api';
+import { IGroup } from '../types';
 import UIButton from '../UI/UIButton';
 import UIDateInput from '../UI/UIDateInput';
 import UIDatetimeInput from '../UI/UIDatetimeInput';
diff --git a/src/components/EventCreator/EventCreatorLink.tsx b/src/components/EventCreator/EventCreatorLink.tsx
index 6e47db0..9c73eda 100644
--- a/src/components/EventCreator/EventCreatorLink.tsx
+++ b/src/components/EventCreator/EventCreatorLink.tsx
@@ -1,5 +1,5 @@
 import EventCreator from './EventCreator';
-import { IGroup } from '../Group';
+import { IGroup } from '../types';
 import useToggle from '../useToggle';
 
 export default function EventCreatorLink({ group }: { group: IGroup }) {
diff --git a/src/components/Group.tsx b/src/components/Group.tsx
index e120fe8..eaccfb9 100644
--- a/src/components/Group.tsx
+++ b/src/components/Group.tsx
@@ -5,15 +5,9 @@ import { getGroup, getGroupEvents } from './api';
 import EventCreatorLink from './EventCreator/EventCreatorLink';
 import EventStream from './EventStream';
 import GroupSettingsLink from './GroupSettings/GroupSettingsLink';
-import { IEvent } from './types';
+import { IEvent, IGroup } from './types';
 import UILink from './UI/UILink';
 
-export type IGroup = {
-	id: number;
-	events: IEvent[];
-	name: string;
-};
-
 export default function Group() {
 	const { id } = useParams<{ id: string }>();
 	const [loading, setLoading] = useState(true);
diff --git a/src/components/GroupSettings/GroupSettingsLink.tsx b/src/components/GroupSettings/GroupSettingsLink.tsx
index 0c992ea..7b97911 100644
--- a/src/components/GroupSettings/GroupSettingsLink.tsx
+++ b/src/components/GroupSettings/GroupSettingsLink.tsx
@@ -1,4 +1,4 @@
-import { IGroup } from '../Group';
+import { IGroup } from '../types';
 import useToggle from '../useToggle';
 import GroupSettings from './GroupSettings';
 
diff --git a/src/components/Groups/GroupList.tsx b/src/components/Groups/GroupList.tsx
index ac031a9..541e76c 100644
--- a/src/components/Groups/GroupList.tsx
+++ b/src/components/Groups/GroupList.tsx
@@ -1,4 +1,4 @@
-import { IGroup } from '../Group';
+import { IGroup } from '../types';
 import UISecondaryBox from '../UI/UISecondaryBox';
 
 function GroupListItem({ group }: { group: IGroup }) {
diff --git a/src/components/Groups/Groups.tsx b/src/components/Groups/Groups.tsx
index 13531c6..ad72e18 100644
--- a/src/components/Groups/Groups.tsx
+++ b/src/components/Groups/Groups.tsx
@@ -1,6 +1,6 @@
 import { useEffect, useState } from 'react';
 import { getGroups } from '../api';
-import { IGroup } from '../Group';
+import { IGroup } from '../types';
 import GroupCreatorLink from '../GroupCreator/GroupCreatorLink';
 import GroupJoinerLink from '../GroupJoinerLink';
 import GroupList from './GroupList';
diff --git a/src/components/api.ts b/src/components/api.ts
index 7e01c8c..99303bd 100644
--- a/src/components/api.ts
+++ b/src/components/api.ts
@@ -1,5 +1,5 @@
 import { GroupPreview } from './GroupJoinerLink';
-import { IInvitation, IEventSignup, ICarpool, IEvent } from './types';
+import { IInvitation, IEventSignup, ICarpool, IEvent, IGroup } from './types';
 
 const base = process.env.REACT_APP_API_DOMAIN + 'api';
 
@@ -94,7 +94,7 @@ export async function getEvent(id: number): Promise<IEvent> {
 	return await get('/events/' + id);
 }
 
-export async function getGroup(id: number) {
+export async function getGroup(id: number): Promise<IGroup> {
 	return await get('/groups/' + id);
 }
 
diff --git a/src/components/types.ts b/src/components/types.ts
index 0fa7130..ad940cf 100644
--- a/src/components/types.ts
+++ b/src/components/types.ts
@@ -49,6 +49,7 @@ export type ICarpool = {
 export type IGroup = {
 	id: number;
 	name: string;
+	events: IEvent[];
 };
 
 /**
diff --git a/src/state/EventsProvider.tsx b/src/state/EventsProvider.tsx
new file mode 100644
index 0000000..d02c132
--- /dev/null
+++ b/src/state/EventsProvider.tsx
@@ -0,0 +1,74 @@
+import { createContext, ReactNode, useCallback, useState } from 'react';
+import * as immutable from 'immutable';
+import { useMemo } from 'react';
+
+export class EventSignup extends immutable.Record({
+	userId: 0,
+	placeId: '',
+	formattedAddress: '',
+	latitute: 0,
+	longitude: 0,
+}) {}
+
+export class EventState extends immutable.Record({
+	id: 0,
+	name: '',
+	signups: immutable.Map<string, EventSignup>(),
+}) {
+	addSignup(
+		userId: number,
+		placeId: string,
+		formattedAddress: string,
+		latitute: number,
+		longitude: number
+	) {
+		return this.set(
+			'signups',
+			this.signups.set(
+				userId.toString(),
+				new EventSignup({
+					userId,
+					placeId,
+					formattedAddress,
+					latitute,
+					longitude,
+				})
+			)
+		);
+	}
+	setName(name: string) {
+		return this.set('name', name);
+	}
+}
+
+type EventsProps = {
+	events: immutable.Map<number, EventState>;
+	upsertEvent: (event: EventState) => void;
+};
+
+export const EventsContext = createContext<EventsProps>({
+	events: immutable.Map<number, EventState>(),
+	upsertEvent: () => {},
+});
+
+export default function EventsProvider({ children }: { children: ReactNode }) {
+	const [events, setEvents] = useState(immutable.Map<number, EventState>());
+
+	const upsertEvent = useCallback(
+		(event: EventState) => {
+			setEvents(events.set(event.id, event));
+		},
+		[events]
+	);
+
+	const value: EventsProps = useMemo(() => {
+		return {
+			events,
+			upsertEvent,
+		};
+	}, [events, upsertEvent]);
+
+	return (
+		<EventsContext.Provider value={value}>{children}</EventsContext.Provider>
+	);
+}
diff --git a/src/state/GroupHooks.tsx b/src/state/GroupHooks.tsx
new file mode 100644
index 0000000..5a438d8
--- /dev/null
+++ b/src/state/GroupHooks.tsx
@@ -0,0 +1,7 @@
+import { useContext } from 'react';
+import { GroupsContext, GroupState } from './GroupsProvider';
+
+export function useGroup(id: number): GroupState | null {
+	const { groups } = useContext(GroupsContext);
+	return groups.get(id, null);
+}
diff --git a/src/state/GroupsProvider.tsx b/src/state/GroupsProvider.tsx
new file mode 100644
index 0000000..74cde72
--- /dev/null
+++ b/src/state/GroupsProvider.tsx
@@ -0,0 +1,105 @@
+import * as immutable from 'immutable';
+import { ReactNode, useCallback, useMemo, useState } from 'react';
+import { createContext } from 'react';
+import { getGroup as fetchGroup } from '../components/api';
+
+export class GroupState extends immutable.Record({
+	id: 0,
+	name: '',
+	memberIds: immutable.Set<number>(),
+	joinCode: null as string | null,
+}) {
+	setJoinCode(joinCode: string) {
+		return this.set('joinCode', joinCode);
+	}
+	setName(name: string) {
+		return this.set('name', name);
+	}
+	addMember(memberId: number) {
+		return this.set('memberIds', this.memberIds.add(memberId));
+	}
+	removeMember(memberId: number) {
+		return this.set('memberIds', this.memberIds.remove(memberId));
+	}
+	setMemberIds(memberIds: immutable.Set<number>) {
+		return this.set('memberIds', memberIds);
+	}
+}
+
+function u(name: string) {
+	return () => {
+		throw new Error(`${name} is not implemented`);
+	};
+}
+
+type GroupContextProps = {
+	groups: immutable.Map<number, GroupState>;
+	addGroup: (group: GroupState) => void;
+	getGroup: (id: number) => Promise<GroupState | null>;
+	renameGroup: (groupId: number, name: string) => void;
+	generateJoinCode: (groupId: number) => void;
+};
+
+// A React context that provides access to the current user's groups.
+export const GroupsContext = createContext<GroupContextProps>({
+	groups: immutable.Map<number, GroupState>(),
+	addGroup: u('addGroup'),
+	getGroup: u('getGroup'),
+	renameGroup: u('renameGroup'),
+	generateJoinCode: u('generateJoinCode'),
+});
+
+export default function GroupsProvider({ children }: { children: ReactNode }) {
+	// usestates for all the properties in GroupsContext
+	const [groups, setGroups] = useState(immutable.Map<number, GroupState>());
+
+	const addGroup = useCallback((group: GroupState) => {
+		setGroups((groups) => groups.set(group.id, group));
+	}, []);
+
+	const renameGroup = useCallback((groupId: number, name: string) => {
+		setGroups((groups) => groups.setIn([groupId, 'name'], name));
+	}, []);
+
+	const generateJoinCode = useCallback((groupId: number) => {
+		// TODO actually use the API here, this was generated by copilot
+		setGroups((groups) =>
+			groups.setIn(
+				[groupId, 'joinCode'],
+				Math.random().toString(36).substr(2, 5)
+			)
+		);
+	}, []);
+
+	const getGroup = useCallback(
+		async (id: number) => {
+			const group = groups.get(id);
+			if (group) {
+				return group;
+			} else {
+				const group = await fetchGroup(id);
+				const state = new GroupState({
+					id: group.id,
+					name: group.name,
+				});
+				addGroup(state);
+				return state;
+			}
+		},
+		[addGroup, groups]
+	);
+
+	const value: GroupContextProps = useMemo(() => {
+		return {
+			groups,
+			addGroup,
+			getGroup,
+			renameGroup,
+			generateJoinCode,
+		};
+	}, [addGroup, generateJoinCode, getGroup, groups, renameGroup]);
+
+	return (
+		<GroupsContext.Provider value={value}>{children}</GroupsContext.Provider>
+	);
+}