mirror of
https://github.com/myfatemi04/wheelshare-frontend.git
synced 2025-04-16 00:50:18 -04:00
make invitation list responsive, add carpool context
This commit is contained in:
parent
16a0ae696f
commit
efd596f80e
6
.env
6
.env
|
@ -1,3 +1,5 @@
|
|||
REACT_APP_API_DOMAIN_=http://localhost:5000/
|
||||
REACT_APP_API_DOMAIN__=https://wheelshare-altbackend-2efyw.ondigitalocean.app/
|
||||
REACT_APP_API_DOMAIN_LOCAL=http://localhost:5000/
|
||||
REACT_APP_API_DOMAIN_DOCN=https://wheelshare-altbackend-2efyw.ondigitalocean.app/
|
||||
REACT_APP_API_DOMAIN_WWW=https://api.wheelshare.app/
|
||||
|
||||
REACT_APP_API_DOMAIN=https://api.wheelshare.app/
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
import EventIcon from '@material-ui/icons/Event';
|
||||
import LocationOnIcon from '@material-ui/icons/LocationOn';
|
||||
import MailOutlineIcon from '@material-ui/icons/MailOutline';
|
||||
import PersonAddIcon from '@material-ui/icons/PersonAdd';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { createContext } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { ICarpool } from '../types';
|
||||
|
||||
import UISecondaryBox from '../UI/UISecondaryBox';
|
||||
import MemberList from './MemberList';
|
||||
import InvitationList from './InvitationList';
|
||||
import UIButton from '../UI/UIButton';
|
||||
import { cancelCarpoolInvite, getCarpool, sendCarpoolInvite } from '../api';
|
||||
import { lightgrey } from '../colors';
|
||||
import { getCarpool } from '../api';
|
||||
import { ICarpool } from '../types';
|
||||
import UIButton from '../UI/UIButton';
|
||||
import UISecondaryBox from '../UI/UISecondaryBox';
|
||||
import useToggle from '../useToggle';
|
||||
import CarpoolDetails from './CarpoolDetails';
|
||||
import InvitationList from './InvitationList';
|
||||
import MemberList from './MemberList';
|
||||
|
||||
export const CarpoolContext = createContext({
|
||||
carpool: null! as ICarpool,
|
||||
sendInvite: (user: { id: number; name: string }) => {
|
||||
console.error('not implemented: sendInvite');
|
||||
},
|
||||
cancelInvite: (user: { id: number; name: string }) => {
|
||||
console.error('not implemented: cancelInvite');
|
||||
},
|
||||
});
|
||||
|
||||
export default function Carpool() {
|
||||
const id = +useParams<{ id: string }>().id;
|
||||
|
@ -26,72 +33,102 @@ export default function Carpool() {
|
|||
|
||||
const [invitationsOpen, toggleInvitationsOpen] = useToggle(false);
|
||||
|
||||
const sendInvite = useCallback(
|
||||
(user: { id: number; name: string }) => {
|
||||
if (carpool) {
|
||||
sendCarpoolInvite(id, user.id)
|
||||
.then(() => {
|
||||
setCarpool(
|
||||
(carpool) =>
|
||||
carpool && {
|
||||
...carpool,
|
||||
invitations: [
|
||||
...carpool.invitations,
|
||||
{ isRequest: false, user },
|
||||
],
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(console.error);
|
||||
} else {
|
||||
console.error(
|
||||
'Trying to send invite when carpool has not been loaded.'
|
||||
);
|
||||
}
|
||||
},
|
||||
[carpool, id]
|
||||
);
|
||||
|
||||
const cancelInvite = useCallback(
|
||||
(user: { id: number; name: string }) => {
|
||||
cancelCarpoolInvite(id, user.id)
|
||||
.then(() => {
|
||||
setCarpool(
|
||||
(carpool) =>
|
||||
carpool && {
|
||||
...carpool,
|
||||
invitations: carpool.invitations.filter(
|
||||
(invite) => invite.user.id !== user.id
|
||||
),
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(console.error);
|
||||
},
|
||||
[id]
|
||||
);
|
||||
|
||||
if (!carpool) {
|
||||
return <>Loading...</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<UISecondaryBox style={{ width: '100%', alignItems: 'center' }}>
|
||||
{carpool ? (
|
||||
<>
|
||||
<h1 style={{ marginBottom: '0rem' }}>{carpool.name}</h1>
|
||||
<h2 style={{ marginBottom: '0rem' }}>{carpool.event.name}</h2>
|
||||
<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 carpool={carpool} />}
|
||||
<div style={{ fontSize: '1.5rem', fontWeight: 400 }}>
|
||||
<CarpoolContext.Provider value={{ carpool, sendInvite, cancelInvite }}>
|
||||
<UISecondaryBox style={{ width: '100%', alignItems: 'center' }}>
|
||||
{carpool ? (
|
||||
<>
|
||||
<h1 style={{ marginBottom: '0rem' }}>{carpool.name}</h1>
|
||||
<h2 style={{ marginBottom: '0rem' }}>{carpool.event.name}</h2>
|
||||
<div
|
||||
style={{
|
||||
color: '#303030',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
margin: '0.5rem 0',
|
||||
}}
|
||||
>
|
||||
<LocationOnIcon style={{ marginRight: '1rem' }} />
|
||||
{carpool.event.formattedAddress}
|
||||
{/* 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>
|
||||
<div
|
||||
style={{
|
||||
color: '#303030',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<EventIcon style={{ marginRight: '1rem' }} />
|
||||
DAWN - DUSK
|
||||
</div>
|
||||
</div>
|
||||
<MemberList members={carpool.members} />
|
||||
</>
|
||||
) : (
|
||||
<h2>Loading</h2>
|
||||
)}
|
||||
</UISecondaryBox>
|
||||
{invitationsOpen && <InvitationList />}
|
||||
<CarpoolDetails carpool={carpool} />
|
||||
<MemberList members={carpool.members} />
|
||||
</>
|
||||
) : (
|
||||
<h2>Loading</h2>
|
||||
)}
|
||||
</UISecondaryBox>
|
||||
</CarpoolContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
31
src/components/Carpool/CarpoolDetails.tsx
Normal file
31
src/components/Carpool/CarpoolDetails.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import EventIcon from '@material-ui/icons/Event';
|
||||
import LocationOnIcon from '@material-ui/icons/LocationOn';
|
||||
|
||||
import { ICarpool } from '../types';
|
||||
|
||||
export default function CarpoolDetails({ carpool }: { carpool: ICarpool }) {
|
||||
return (
|
||||
<div style={{ fontSize: '1.5rem', fontWeight: 400 }}>
|
||||
<div
|
||||
style={{
|
||||
color: '#303030',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<LocationOnIcon style={{ marginRight: '1rem' }} />
|
||||
{carpool.event.formattedAddress}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
color: '#303030',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<EventIcon style={{ marginRight: '1rem' }} />
|
||||
DAWN - DUSK
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
import { createContext, ReactNode, useMemo, useState } from 'react';
|
||||
import * as immutable from 'immutable';
|
||||
import { useEffect } from 'react';
|
||||
import { getCarpool } from '../api';
|
||||
|
||||
class Member extends immutable.Record({
|
||||
id: 0,
|
||||
name: '',
|
||||
}) {}
|
||||
|
||||
class Invitation extends immutable.Record({
|
||||
user: new Member(),
|
||||
isRequest: false,
|
||||
}) {}
|
||||
|
||||
class CarpoolState extends immutable.Record({
|
||||
id: 0,
|
||||
name: '',
|
||||
members: immutable.List<Member>(),
|
||||
invitations: immutable.List<Invitation>(),
|
||||
}) {}
|
||||
|
||||
type Subscriber = (state: CarpoolState) => void;
|
||||
|
||||
class CarpoolSDK {
|
||||
private _state = new CarpoolState();
|
||||
get state() {
|
||||
return this._state;
|
||||
}
|
||||
set state(state: CarpoolState) {
|
||||
this._state = state;
|
||||
}
|
||||
private subscribers: Subscriber[] = [];
|
||||
subscribe(subscriber: Subscriber) {
|
||||
this.subscribers.push(subscriber);
|
||||
return () => {
|
||||
this.subscribers = this.subscribers.filter((s) => s !== subscriber);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const CarpoolContext = createContext({
|
||||
sdk: new CarpoolSDK(),
|
||||
carpool: new CarpoolState(),
|
||||
});
|
||||
|
||||
export default function CarpoolProvider({
|
||||
id,
|
||||
children,
|
||||
}: {
|
||||
id: number;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const [carpool, setCarpool] = useState(new CarpoolState());
|
||||
const sdk = useMemo(() => new CarpoolSDK(), []);
|
||||
|
||||
useEffect(() => {
|
||||
getCarpool(id).then((carpool) => {});
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
const remove = sdk.subscribe(setCarpool);
|
||||
|
||||
return () => remove();
|
||||
}, [sdk]);
|
||||
|
||||
return (
|
||||
<CarpoolContext.Provider value={{ sdk, carpool }}>
|
||||
{children}
|
||||
</CarpoolContext.Provider>
|
||||
);
|
||||
}
|
|
@ -1,19 +1,20 @@
|
|||
import CancelIcon from '@material-ui/icons/Cancel';
|
||||
import PersonAddIcon from '@material-ui/icons/PersonAdd';
|
||||
import { useMemo } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import { getEventSignups } from '../api';
|
||||
import { ICarpool, IEventSignup } from '../types';
|
||||
import { useMe } from '../hooks';
|
||||
import { IEventSignup } from '../types';
|
||||
import { CarpoolContext } from './Carpool';
|
||||
|
||||
function InvitationRow({
|
||||
carpoolId,
|
||||
userId,
|
||||
userName,
|
||||
user,
|
||||
isInvited,
|
||||
}: {
|
||||
carpoolId: number;
|
||||
userId: number;
|
||||
userName: string;
|
||||
user: { id: number; name: string };
|
||||
isInvited: boolean;
|
||||
}) {
|
||||
const { sendInvite, cancelInvite } = useContext(CarpoolContext);
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
|
@ -24,49 +25,65 @@ function InvitationRow({
|
|||
padding: '0.25rem',
|
||||
}}
|
||||
>
|
||||
<span>{userName}</span>
|
||||
<span>{user.name}</span>
|
||||
{isInvited ? (
|
||||
<CancelIcon
|
||||
onClick={() => cancelInvite(user)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
) : (
|
||||
<PersonAddIcon
|
||||
onClick={() => sendInvite(user)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function InvitationList({ carpool }: { carpool: ICarpool }) {
|
||||
export default function InvitationList() {
|
||||
const { carpool } = useContext(CarpoolContext);
|
||||
const me = useMe()!;
|
||||
|
||||
const eventId = carpool.event.id;
|
||||
|
||||
const [availableSignups, setAvailableSignups] =
|
||||
useState<IEventSignup[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
getEventSignups(eventId).then(setAvailableSignups);
|
||||
}, [eventId]);
|
||||
getEventSignups(eventId).then((signups) =>
|
||||
setAvailableSignups(signups.filter((signup) => signup.user.id !== me.id))
|
||||
);
|
||||
}, [eventId, me.id]);
|
||||
|
||||
const existingSignups = useMemo(
|
||||
const invitedUserIDs = useMemo(
|
||||
() =>
|
||||
new Set(
|
||||
carpool.invitations
|
||||
.filter((invitation) => !invitation.isRequest)
|
||||
.map((invitation) => invitation.user.id)
|
||||
),
|
||||
[carpool]
|
||||
[carpool.invitations]
|
||||
);
|
||||
|
||||
const availableSignupsAlreadyInvited = useMemo(
|
||||
() =>
|
||||
availableSignups
|
||||
? availableSignups.filter((signup) =>
|
||||
existingSignups.has(signup.userId)
|
||||
invitedUserIDs.has(signup.user.id)
|
||||
)
|
||||
: null,
|
||||
[availableSignups, existingSignups]
|
||||
[availableSignups, invitedUserIDs]
|
||||
);
|
||||
|
||||
const availableSignupsNotInvited = useMemo(
|
||||
() =>
|
||||
availableSignups
|
||||
? availableSignups.filter(
|
||||
(signup) => !existingSignups.has(signup.userId)
|
||||
(signup) => !invitedUserIDs.has(signup.user.id)
|
||||
)
|
||||
: null,
|
||||
[availableSignups, existingSignups]
|
||||
[availableSignups, invitedUserIDs]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -80,24 +97,20 @@ export default function InvitationList({ carpool }: { carpool: ICarpool }) {
|
|||
>
|
||||
<h1 style={{ marginBottom: '0.25rem' }}>Invite Somebody</h1>
|
||||
{availableSignups === null && 'Loading'}
|
||||
{availableSignupsAlreadyInvited?.map((signup) => (
|
||||
<InvitationRow
|
||||
key={signup.user.id}
|
||||
user={signup.user}
|
||||
isInvited={true}
|
||||
/>
|
||||
))}
|
||||
{availableSignupsNotInvited?.map((signup) => (
|
||||
<InvitationRow
|
||||
key={signup.user.id}
|
||||
userId={signup.user.id}
|
||||
userName={signup.user.name}
|
||||
carpoolId={carpool.id}
|
||||
user={signup.user}
|
||||
isInvited={false}
|
||||
/>
|
||||
))}
|
||||
{availableSignupsAlreadyInvited?.map((signup) => (
|
||||
<InvitationRow
|
||||
key={signup.userId}
|
||||
userId={signup.user.id}
|
||||
userName={signup.user.name}
|
||||
carpoolId={carpool.id}
|
||||
isInvited
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,13 +15,14 @@ async function post(path: string, data: any) {
|
|||
return await res.json();
|
||||
}
|
||||
|
||||
async function delete$(path: string) {
|
||||
async function delete$(path: string, body?: any) {
|
||||
const res = await fetch(base + path, {
|
||||
method: 'delete',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + localStorage.getItem('session_token'),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
return await res.json();
|
||||
}
|
||||
|
@ -176,3 +177,11 @@ export async function createCarpool({
|
|||
}) {
|
||||
return await post('/carpools/', { eventId, name });
|
||||
}
|
||||
|
||||
export async function sendCarpoolInvite(carpoolId: number, userId: number) {
|
||||
return await post('/carpools/' + carpoolId + '/invite', { userId });
|
||||
}
|
||||
|
||||
export async function cancelCarpoolInvite(carpoolId: number, userId: number) {
|
||||
return await delete$('/carpools/' + carpoolId + '/invite', { userId });
|
||||
}
|
||||
|
|
|
@ -33,7 +33,13 @@ export type ICarpool = {
|
|||
id: number;
|
||||
name: string;
|
||||
}[];
|
||||
invitations: IInvitation[];
|
||||
invitations: {
|
||||
user: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
isRequest: boolean;
|
||||
}[];
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -81,7 +87,7 @@ export type IEvent = {
|
|||
|
||||
export type IEventSignup = {
|
||||
eventId: number;
|
||||
userId: number;
|
||||
// userId: number;
|
||||
user: {
|
||||
id: number;
|
||||
name: string;
|
||||
|
|
Loading…
Reference in New Issue
Block a user