diff --git a/src/components/App.tsx b/src/components/App.tsx index f098788..40b03b2 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 NotificationsProvider from '../state/Notifications/NotificationsProvider'; import { useMe } from './hooks'; import WheelShare from './WheelShare'; import WheelShareLoggedOut from './WheelShareLoggedOut'; @@ -22,27 +23,29 @@ const style: CSSProperties = { export default function App() { const user = useMe(); return ( -
-
- - - - - + +
+
+ + - - - - - + + + + + + + + +
-
+ ); } diff --git a/src/components/Carpool/MemberList.tsx b/src/components/Carpool/MemberList.tsx index aac57ad..31a2150 100644 --- a/src/components/Carpool/MemberList.tsx +++ b/src/components/Carpool/MemberList.tsx @@ -1,6 +1,11 @@ import AccountCircleIcon from '@material-ui/icons/AccountCircle'; +import { useCallback } from 'react'; +import { useMemo } from 'react'; import { useContext } from 'react'; +import { useInvitationState } from '../../state/Notifications/NotificationsHooks'; +import { NotificationsContext } from '../../state/Notifications/NotificationsProvider'; import { lightgrey } from '../colors'; +import { useMe } from '../hooks'; import UIButton from '../UI/UIButton'; import { CarpoolContext } from './Carpool'; @@ -21,9 +26,26 @@ export default function MemberList({ name: string; }[]; }) { - const { leave } = useContext(CarpoolContext); + const { leave, carpool } = useContext(CarpoolContext); const membersToShow = members.slice(0, 2); const hiddenMemberCount = members.length - membersToShow.length; + const me = useMe()!; + + const isMember = useMemo(() => { + return members.some(({ id }) => id === me.id); + }, [me.id, members]); + + const { sendCarpoolRequest, cancelCarpoolRequest } = + useContext(NotificationsContext); + const invitationState = useInvitationState(carpool.id); + + const sendRequest = useCallback(() => { + sendCarpoolRequest(carpool.id); + }, [carpool.id, sendCarpoolRequest]); + + const cancelRequest = useCallback(() => { + cancelCarpoolRequest(carpool.id); + }, [carpool.id, cancelCarpoolRequest]); return (
- Leave - + + {isMember ? ( + + Leave + + ) : invitationState === 'requested' ? ( + Cancel request to join + ) : invitationState === 'none' ? ( + Request to join + ) : ( + + You've been invited, we need to make it so you can accept the invite + + )}
); } diff --git a/src/components/api.ts b/src/components/api.ts index 0b26b31..7e01c8c 100644 --- a/src/components/api.ts +++ b/src/components/api.ts @@ -193,3 +193,11 @@ export async function cancelCarpoolInvite(carpoolId: number, userId: number) { export async function leaveCarpool(carpoolId: number) { return await post(`/carpools/${carpoolId}/leave`, {}); } + +export async function sendCarpoolRequest(carpoolId: number) { + return await post('/carpools/' + carpoolId + '/request', {}); +} + +export async function cancelCarpoolRequest(carpoolId: number) { + return await delete$('/carpools/' + carpoolId + '/request'); +} diff --git a/src/state/Notifications/NotificationsHooks.tsx b/src/state/Notifications/NotificationsHooks.tsx new file mode 100644 index 0000000..5959238 --- /dev/null +++ b/src/state/Notifications/NotificationsHooks.tsx @@ -0,0 +1,21 @@ +import { useMemo } from 'react'; +import { useContext } from 'react'; +import { NotificationsContext } from './NotificationsProvider'; + +export function useInvitationState( + carpoolId: number +): 'invited' | 'requested' | 'none' { + const notifications = useContext(NotificationsContext); + + const invited = useMemo( + () => notifications.invitedCarpoolIds.has(carpoolId), + [carpoolId, notifications.invitedCarpoolIds] + ); + + const requested = useMemo( + () => notifications.requestedCarpoolIds.has(carpoolId), + [carpoolId, notifications.requestedCarpoolIds] + ); + + return invited ? 'invited' : requested ? 'requested' : 'none'; +} diff --git a/src/state/Notifications/NotificationsProvider.tsx b/src/state/Notifications/NotificationsProvider.tsx new file mode 100644 index 0000000..be3d119 --- /dev/null +++ b/src/state/Notifications/NotificationsProvider.tsx @@ -0,0 +1,54 @@ +import { createContext, ReactNode, useCallback, useState } from 'react'; +import * as immutable from 'immutable'; +import * as api from '../../components/api'; + +export const NotificationsContext = createContext({ + invitedCarpoolIds: immutable.Set(), + requestedCarpoolIds: immutable.Set(), + + sendCarpoolRequest: (carpoolId: number) => + console.error('not implemented: sendCarpoolRequest'), + + cancelCarpoolRequest: (carpoolId: number) => + console.error('not implemented: cancelCarpoolRequest'), +}); + +export default function NotificationsProvider({ + children, +}: { + children: ReactNode; +}) { + // eslint-disable-next-line + const [invitedCarpoolIds, _setInvitedCarpoolIds] = useState( + immutable.Set() + ); + + const [requestedCarpoolIds, setRequestedCarpoolIds] = useState( + immutable.Set() + ); + + const sendCarpoolRequest = useCallback((carpoolId: number) => { + api + .sendCarpoolRequest(carpoolId) + .then(() => setRequestedCarpoolIds((ids) => ids.add(carpoolId))); + }, []); + + const cancelCarpoolRequest = useCallback((carpoolId: number) => { + api + .cancelCarpoolRequest(carpoolId) + .then(() => setRequestedCarpoolIds((ids) => ids.delete(carpoolId))); + }, []); + + return ( + + {children} + + ); +}