carpool ui revamp

This commit is contained in:
Michael Fatemi 2021-07-16 11:25:09 -04:00
parent a0527855da
commit e27f62701c
8 changed files with 192 additions and 159 deletions

View File

@ -1,16 +1,16 @@
import { createContext, useCallback, useEffect, useMemo } from 'react'; import { createContext, useCallback, useEffect } from 'react';
import { import {
cancelCarpoolInvite, cancelCarpoolInvite,
getCarpool, getCarpool,
leaveCarpool, leaveCarpool,
sendCarpoolInvite, sendCarpoolInvite,
} from '../api'; } from '../api';
import { useMe } from '../hooks';
import { ICarpool } from '../types'; import { ICarpool } from '../types';
import UILink from '../UI/UILink';
import UISecondaryBox from '../UI/UISecondaryBox'; import UISecondaryBox from '../UI/UISecondaryBox';
import useImmutable from '../useImmutable'; import useImmutable from '../useImmutable';
import CarpoolDetails from './CarpoolDetails'; import CarpoolDetails from './CarpoolDetails';
import InvitationsAndRequests from './InvitationsAndRequests'; import CarpoolTopButtons from './CarpoolTopButtons';
import MemberList from './MemberList'; import MemberList from './MemberList';
type CarpoolState = { type CarpoolState = {
@ -35,13 +35,7 @@ export const CarpoolContext = createContext({
}); });
export default function Carpool({ id }: { id: number }) { export default function Carpool({ id }: { id: number }) {
const [carpool, setCarpool] = useImmutable<CarpoolState>({ const [carpool, setCarpool] = useImmutable<CarpoolState | null>(null);
id,
name: '',
event: {} as ICarpool['event'],
members: [],
invitations: {},
});
useEffect(() => { useEffect(() => {
getCarpool(id).then((carpool) => { getCarpool(id).then((carpool) => {
@ -78,16 +72,19 @@ export default function Carpool({ id }: { id: number }) {
const cancelInvite = useCallback( const cancelInvite = useCallback(
(user: { id: number; name: string }) => { (user: { id: number; name: string }) => {
if (!carpool) {
return null;
}
cancelCarpoolInvite(id, user.id) cancelCarpoolInvite(id, user.id)
.then(() => { .then(() => {
delete carpool.invitations[user.id]; delete carpool.invitations[user.id];
}) })
.catch(console.error); .catch(console.error);
}, },
[carpool.invitations, id] [carpool, id]
); );
const eventId = carpool.event.id; const eventId = carpool?.event.id;
const leave = useCallback(() => { const leave = useCallback(() => {
if (eventId) { if (eventId) {
@ -99,13 +96,6 @@ export default function Carpool({ id }: { id: number }) {
} }
}, [eventId, id]); }, [eventId, id]);
const me = useMe();
const isMember = useMemo(
() => carpool.members.some((m) => m.id === me?.id),
[carpool.members, me?.id]
);
if (!carpool) { if (!carpool) {
return <>Loading...</>; return <>Loading...</>;
} }
@ -120,17 +110,13 @@ export default function Carpool({ id }: { id: number }) {
}} }}
> >
<UISecondaryBox style={{ width: '100%', alignItems: 'center' }}> <UISecondaryBox style={{ width: '100%', alignItems: 'center' }}>
{carpool ? ( <h1>{carpool.name}</h1>
<> <UILink href={'/events/' + carpool.event.id}>
<h1 style={{ marginBottom: '0rem' }}>{carpool.name}</h1> {carpool.event.name}
<h2 style={{ marginBottom: '0rem' }}>{carpool.event.name}</h2> </UILink>
{isMember && <InvitationsAndRequests />} <CarpoolTopButtons />
<CarpoolDetails /> <CarpoolDetails />
<MemberList /> <MemberList />
</>
) : (
<h2>Loading</h2>
)}
</UISecondaryBox> </UISecondaryBox>
</CarpoolContext.Provider> </CarpoolContext.Provider>
); );

View File

@ -7,7 +7,7 @@ import { CarpoolContext } from './Carpool';
export default function CarpoolDetails() { export default function CarpoolDetails() {
const { carpool } = useContext(CarpoolContext); const { carpool } = useContext(CarpoolContext);
return ( return (
<div style={{ fontSize: '1.5rem', fontWeight: 400 }}> <div>
<div <div
style={{ style={{
color: '#303030', color: '#303030',

View File

@ -0,0 +1,22 @@
import { useContext, useMemo } from 'react';
import { useMe } from '../hooks';
import { CarpoolContext } from './Carpool';
import CarpoolTopButtonsMembersOnly from './CarpoolTopButtonsMembersOnly';
import CarpoolTopButtonsNonMembersOnly from './CarpoolTopButtonsNonMembersOnly';
export default function CarpoolTopButtons() {
const me = useMe();
const { carpool } = useContext(CarpoolContext);
const members = carpool.members;
const isMember = useMemo(
() => members.some(({ id }) => id === me?.id),
[me?.id, members]
);
if (isMember) {
return <CarpoolTopButtonsMembersOnly />;
} else {
return <CarpoolTopButtonsNonMembersOnly />;
}
}

View File

@ -0,0 +1,43 @@
import MailOutlineIcon from '@material-ui/icons/MailOutline';
import PersonAddIcon from '@material-ui/icons/PersonAdd';
import EventBusyIcon from '@material-ui/icons/EventBusy';
import { useContext } from 'react';
import useToggle from '../useToggle';
import { CarpoolContext } from './Carpool';
import InvitationList from './InvitationList';
const spanStyle = {
padding: '0.5rem',
display: 'flex',
alignItems: 'center',
cursor: 'pointer',
};
export default function CarpoolTopButtonsMembersOnly() {
const [invitationsOpen, toggleInvitationsOpen] = useToggle(false);
const { leave } = useContext(CarpoolContext);
return (
<>
<div
style={{
display: 'flex',
flexDirection: 'row',
margin: '0.5rem 0',
}}
>
<span style={spanStyle} onClick={console.log}>
<MailOutlineIcon style={{ marginRight: '0.5rem' }} /> 1 request
</span>
<span style={spanStyle} onClick={toggleInvitationsOpen}>
<PersonAddIcon style={{ marginRight: '0.5rem' }} /> Invite
</span>
<span style={spanStyle} onClick={leave}>
<EventBusyIcon style={{ marginRight: '0.5rem' }} /> Leave
</span>
</div>
{invitationsOpen && <InvitationList />}
</>
);
}

View File

@ -0,0 +1,90 @@
import { useCallback, useContext } from 'react';
import { lightgrey } from '../../lib/colors';
import { useInvitationState } from '../../state/Notifications/NotificationsHooks';
import { NotificationsContext } from '../../state/Notifications/NotificationsProvider';
import { useMe } from '../hooks';
import UIButton from '../UI/UIButton';
import { CarpoolContext } from './Carpool';
const defaultMe = { id: 0, name: '' };
const greyButtonStyle = {
backgroundColor: lightgrey,
flex: 1,
margin: '0.5rem',
};
export default function CarpoolTopButtonsNonMembersOnly() {
const { carpool } = useContext(CarpoolContext);
const members = carpool.members;
const {
sendCarpoolRequest,
cancelCarpoolRequest,
acceptCarpoolInvite,
denyCarpoolInvite,
} = useContext(NotificationsContext);
const me = useMe() || defaultMe;
const sendRequest = useCallback(() => {
sendCarpoolRequest(carpool.id);
}, [carpool.id, sendCarpoolRequest]);
const cancelRequest = useCallback(() => {
cancelCarpoolRequest(carpool.id);
}, [carpool.id, cancelCarpoolRequest]);
const acceptInvitation = useCallback(() => {
acceptCarpoolInvite(carpool.id).then(() => {
members.push(me);
});
}, [acceptCarpoolInvite, carpool.id, members, me]);
const denyInvitation = useCallback(() => {
denyCarpoolInvite(carpool.id).then(() => {
members.push(me);
});
}, [carpool.id, denyCarpoolInvite, me, members]);
const invitationState = useInvitationState(carpool.id);
return (
<>
{invitationState === 'requested' ? (
<UIButton
style={{ backgroundColor: lightgrey, margin: '0.5rem' }}
onClick={cancelRequest}
>
Cancel request to join
</UIButton>
) : invitationState === 'none' ? (
<UIButton
style={{ backgroundColor: lightgrey, margin: '0.5rem' }}
onClick={sendRequest}
>
Request to join
</UIButton>
) : (
<>
<span style={{ marginTop: '0.5rem' }}>
You've been invited to this carpool!
</span>
<div
style={{
display: 'flex',
width: '100%',
textAlign: 'center',
margin: '0.5rem 0',
}}
>
<UIButton onClick={acceptInvitation} style={greyButtonStyle}>
Accept
</UIButton>
<UIButton onClick={denyInvitation} style={greyButtonStyle}>
Decline
</UIButton>
</div>
</>
)}
</>
);
}

View File

@ -1,49 +0,0 @@
import { lightgrey } from '../../lib/colors';
import UIButton from '../UI/UIButton';
import InvitationList from './InvitationList';
import MailOutlineIcon from '@material-ui/icons/MailOutline';
import PersonAddIcon from '@material-ui/icons/PersonAdd';
import useToggle from '../useToggle';
export default function InvitationsAndRequests() {
const [invitationsOpen, toggleInvitationsOpen] = useToggle(false);
return (
<>
<div
style={{
display: 'flex',
flexDirection: 'row',
margin: '0.5rem 0',
}}
>
{/* Requests */}
<UIButton
style={{
marginRight: '0.25rem',
backgroundColor: lightgrey,
display: 'flex',
alignItems: 'center',
}}
onClick={console.log}
>
<MailOutlineIcon style={{ marginRight: '0.5rem' }} /> 1 request
</UIButton>
{/* Invitations */}
<UIButton
style={{
marginLeft: '0.25rem',
backgroundColor: lightgrey,
display: 'flex',
alignItems: 'center',
}}
onClick={toggleInvitationsOpen}
>
<PersonAddIcon style={{ marginRight: '0.5rem' }} /> Invite
</UIButton>
</div>
{invitationsOpen && <InvitationList />}
</>
);
}

View File

@ -1,12 +1,5 @@
import AccountCircleIcon from '@material-ui/icons/AccountCircle'; import AccountCircleIcon from '@material-ui/icons/AccountCircle';
import { useCallback } from 'react';
import { useMemo } from 'react';
import { useContext } from 'react'; import { useContext } from 'react';
import { useInvitationState } from '../../state/Notifications/NotificationsHooks';
import { NotificationsContext } from '../../state/Notifications/NotificationsProvider';
import { lightgrey } from '../../lib/colors';
import { useMe } from '../hooks';
import UIButton from '../UI/UIButton';
import { CarpoolContext } from './Carpool'; import { CarpoolContext } from './Carpool';
function MemberRow({ member }: { member: { id: number; name: string } }) { function MemberRow({ member }: { member: { id: number; name: string } }) {
@ -18,50 +11,26 @@ function MemberRow({ member }: { member: { id: number; name: string } }) {
); );
} }
function formatOthers(hiddenMemberCount: number) {
if (hiddenMemberCount === 0) {
return '';
}
if (hiddenMemberCount === 1) {
return '1 other...';
}
return `${hiddenMemberCount} others...`;
}
const shownMembersCount = 2; const shownMembersCount = 2;
const defaultMe = { id: 0, name: '' };
export default function MemberList() { export default function MemberList() {
const { leave, carpool } = useContext(CarpoolContext); const { carpool } = useContext(CarpoolContext);
const members = carpool.members; const members = carpool.members;
const membersToShow = members.slice(0, shownMembersCount); const membersToShow = members.slice(0, shownMembersCount);
const hiddenMemberCount = members.length - membersToShow.length; const hiddenMemberCount = members.length - membersToShow.length;
const {
sendCarpoolRequest,
cancelCarpoolRequest,
acceptCarpoolInvite,
denyCarpoolInvite,
} = useContext(NotificationsContext);
const invitationState = useInvitationState(carpool.id);
const me = useMe() || defaultMe;
const sendRequest = useCallback(() => {
sendCarpoolRequest(carpool.id);
}, [carpool.id, sendCarpoolRequest]);
const cancelRequest = useCallback(() => {
cancelCarpoolRequest(carpool.id);
}, [carpool.id, cancelCarpoolRequest]);
const acceptInvitation = useCallback(() => {
acceptCarpoolInvite(carpool.id).then(() => {
members.push(me);
});
}, [acceptCarpoolInvite, carpool.id, members, me]);
const denyInvitation = useCallback(() => {
denyCarpoolInvite(carpool.id).then(() => {
members.push(me);
});
}, [carpool.id, denyCarpoolInvite, me, members]);
const isMember = useMemo(() => {
return members.some(({ id }) => id === me?.id);
}, [me?.id, members]);
return ( return (
<div <div
style={{ style={{
@ -71,48 +40,17 @@ export default function MemberList() {
alignItems: 'center', alignItems: 'center',
}} }}
> >
<h3 style={{ marginBlockEnd: '0' }}>Members</h3> <h2 style={{ marginBlockEnd: '0.5rem' }}>Members</h2>
{members.length > 0 ? ( {members.length > 0 ? (
<div style={{ display: 'flex', flexDirection: 'column' }}> <div style={{ display: 'flex', flexDirection: 'column' }}>
{membersToShow.map((member) => ( {membersToShow.map((member) => (
<MemberRow member={member} key={member.id} /> <MemberRow member={member} key={member.id} />
))} ))}
{hiddenMemberCount > 0 && {formatOthers(hiddenMemberCount)}
(hiddenMemberCount === 1
? hiddenMemberCount + ' other...'
: hiddenMemberCount + ' others...')}
</div> </div>
) : ( ) : (
'This carpool has no members.' 'This carpool has no members.'
)} )}
{isMember ? (
<UIButton onClick={leave} style={{ backgroundColor: lightgrey }}>
Leave
</UIButton>
) : invitationState === 'requested' ? (
<UIButton onClick={cancelRequest}>Cancel request to join</UIButton>
) : invitationState === 'none' ? (
<UIButton onClick={sendRequest}>Request to join</UIButton>
) : (
<span>
You've been invited to this carpool!
<div style={{ display: 'flex', width: '100%', textAlign: 'center' }}>
<UIButton
onClick={acceptInvitation}
style={{ backgroundColor: lightgrey, flex: 1, margin: '0.5rem' }}
>
Accept
</UIButton>
<UIButton
onClick={denyInvitation}
style={{ backgroundColor: lightgrey, flex: 1, margin: '0.5rem' }}
>
Deny
</UIButton>
</div>
</span>
)}
</div> </div>
); );
} }

View File

@ -123,8 +123,11 @@ export async function getGroups(): Promise<IGroup[]> {
return await get('/groups'); return await get('/groups');
} }
export async function deleteGroup(id: number) { export async function deleteGroup(
await delete$('/groups/' + id); id: number
): Promise<{ status: 'success' | 'error' }> {
const { status } = await delete$('/groups/' + id);
return { status };
} }
export async function createGroup(name: string): Promise<{ id: number }> { export async function createGroup(name: string): Promise<{ id: number }> {