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_LOCAL=http://localhost:5000/
|
||||||
REACT_APP_API_DOMAIN__=https://wheelshare-altbackend-2efyw.ondigitalocean.app/
|
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/
|
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 MailOutlineIcon from '@material-ui/icons/MailOutline';
|
||||||
import PersonAddIcon from '@material-ui/icons/PersonAdd';
|
import PersonAddIcon from '@material-ui/icons/PersonAdd';
|
||||||
|
import { createContext } from 'react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { cancelCarpoolInvite, getCarpool, sendCarpoolInvite } from '../api';
|
||||||
import { ICarpool } from '../types';
|
|
||||||
|
|
||||||
import UISecondaryBox from '../UI/UISecondaryBox';
|
|
||||||
import MemberList from './MemberList';
|
|
||||||
import InvitationList from './InvitationList';
|
|
||||||
import UIButton from '../UI/UIButton';
|
|
||||||
import { lightgrey } from '../colors';
|
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 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() {
|
export default function Carpool() {
|
||||||
const id = +useParams<{ id: string }>().id;
|
const id = +useParams<{ id: string }>().id;
|
||||||
|
@ -26,72 +33,102 @@ export default function Carpool() {
|
||||||
|
|
||||||
const [invitationsOpen, toggleInvitationsOpen] = useToggle(false);
|
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 (
|
return (
|
||||||
<UISecondaryBox style={{ width: '100%', alignItems: 'center' }}>
|
<CarpoolContext.Provider value={{ carpool, sendInvite, cancelInvite }}>
|
||||||
{carpool ? (
|
<UISecondaryBox style={{ width: '100%', alignItems: 'center' }}>
|
||||||
<>
|
{carpool ? (
|
||||||
<h1 style={{ marginBottom: '0rem' }}>{carpool.name}</h1>
|
<>
|
||||||
<h2 style={{ marginBottom: '0rem' }}>{carpool.event.name}</h2>
|
<h1 style={{ marginBottom: '0rem' }}>{carpool.name}</h1>
|
||||||
<div
|
<h2 style={{ marginBottom: '0rem' }}>{carpool.event.name}</h2>
|
||||||
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 }}>
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
color: '#303030',
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
flexDirection: 'row',
|
||||||
|
margin: '0.5rem 0',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LocationOnIcon style={{ marginRight: '1rem' }} />
|
{/* Requests */}
|
||||||
{carpool.event.formattedAddress}
|
<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>
|
||||||
<div
|
{invitationsOpen && <InvitationList />}
|
||||||
style={{
|
<CarpoolDetails carpool={carpool} />
|
||||||
color: '#303030',
|
<MemberList members={carpool.members} />
|
||||||
display: 'flex',
|
</>
|
||||||
alignItems: 'center',
|
) : (
|
||||||
}}
|
<h2>Loading</h2>
|
||||||
>
|
)}
|
||||||
<EventIcon style={{ marginRight: '1rem' }} />
|
</UISecondaryBox>
|
||||||
DAWN - DUSK
|
</CarpoolContext.Provider>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<MemberList members={carpool.members} />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<h2>Loading</h2>
|
|
||||||
)}
|
|
||||||
</UISecondaryBox>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
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 { useMemo } from 'react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import { getEventSignups } from '../api';
|
import { getEventSignups } from '../api';
|
||||||
import { ICarpool, IEventSignup } from '../types';
|
import { useMe } from '../hooks';
|
||||||
|
import { IEventSignup } from '../types';
|
||||||
|
import { CarpoolContext } from './Carpool';
|
||||||
|
|
||||||
function InvitationRow({
|
function InvitationRow({
|
||||||
carpoolId,
|
user,
|
||||||
userId,
|
|
||||||
userName,
|
|
||||||
isInvited,
|
isInvited,
|
||||||
}: {
|
}: {
|
||||||
carpoolId: number;
|
user: { id: number; name: string };
|
||||||
userId: number;
|
|
||||||
userName: string;
|
|
||||||
isInvited: boolean;
|
isInvited: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
const { sendInvite, cancelInvite } = useContext(CarpoolContext);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
@ -24,49 +25,65 @@ function InvitationRow({
|
||||||
padding: '0.25rem',
|
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>
|
</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 eventId = carpool.event.id;
|
||||||
|
|
||||||
const [availableSignups, setAvailableSignups] =
|
const [availableSignups, setAvailableSignups] =
|
||||||
useState<IEventSignup[] | null>(null);
|
useState<IEventSignup[] | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getEventSignups(eventId).then(setAvailableSignups);
|
getEventSignups(eventId).then((signups) =>
|
||||||
}, [eventId]);
|
setAvailableSignups(signups.filter((signup) => signup.user.id !== me.id))
|
||||||
|
);
|
||||||
|
}, [eventId, me.id]);
|
||||||
|
|
||||||
const existingSignups = useMemo(
|
const invitedUserIDs = useMemo(
|
||||||
() =>
|
() =>
|
||||||
new Set(
|
new Set(
|
||||||
carpool.invitations
|
carpool.invitations
|
||||||
.filter((invitation) => !invitation.isRequest)
|
.filter((invitation) => !invitation.isRequest)
|
||||||
.map((invitation) => invitation.user.id)
|
.map((invitation) => invitation.user.id)
|
||||||
),
|
),
|
||||||
[carpool]
|
[carpool.invitations]
|
||||||
);
|
);
|
||||||
|
|
||||||
const availableSignupsAlreadyInvited = useMemo(
|
const availableSignupsAlreadyInvited = useMemo(
|
||||||
() =>
|
() =>
|
||||||
availableSignups
|
availableSignups
|
||||||
? availableSignups.filter((signup) =>
|
? availableSignups.filter((signup) =>
|
||||||
existingSignups.has(signup.userId)
|
invitedUserIDs.has(signup.user.id)
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
[availableSignups, existingSignups]
|
[availableSignups, invitedUserIDs]
|
||||||
);
|
);
|
||||||
|
|
||||||
const availableSignupsNotInvited = useMemo(
|
const availableSignupsNotInvited = useMemo(
|
||||||
() =>
|
() =>
|
||||||
availableSignups
|
availableSignups
|
||||||
? availableSignups.filter(
|
? availableSignups.filter(
|
||||||
(signup) => !existingSignups.has(signup.userId)
|
(signup) => !invitedUserIDs.has(signup.user.id)
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
[availableSignups, existingSignups]
|
[availableSignups, invitedUserIDs]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -80,24 +97,20 @@ export default function InvitationList({ carpool }: { carpool: ICarpool }) {
|
||||||
>
|
>
|
||||||
<h1 style={{ marginBottom: '0.25rem' }}>Invite Somebody</h1>
|
<h1 style={{ marginBottom: '0.25rem' }}>Invite Somebody</h1>
|
||||||
{availableSignups === null && 'Loading'}
|
{availableSignups === null && 'Loading'}
|
||||||
|
{availableSignupsAlreadyInvited?.map((signup) => (
|
||||||
|
<InvitationRow
|
||||||
|
key={signup.user.id}
|
||||||
|
user={signup.user}
|
||||||
|
isInvited={true}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
{availableSignupsNotInvited?.map((signup) => (
|
{availableSignupsNotInvited?.map((signup) => (
|
||||||
<InvitationRow
|
<InvitationRow
|
||||||
key={signup.user.id}
|
key={signup.user.id}
|
||||||
userId={signup.user.id}
|
user={signup.user}
|
||||||
userName={signup.user.name}
|
|
||||||
carpoolId={carpool.id}
|
|
||||||
isInvited={false}
|
isInvited={false}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{availableSignupsAlreadyInvited?.map((signup) => (
|
|
||||||
<InvitationRow
|
|
||||||
key={signup.userId}
|
|
||||||
userId={signup.user.id}
|
|
||||||
userName={signup.user.name}
|
|
||||||
carpoolId={carpool.id}
|
|
||||||
isInvited
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,13 +15,14 @@ async function post(path: string, data: any) {
|
||||||
return await res.json();
|
return await res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function delete$(path: string) {
|
async function delete$(path: string, body?: any) {
|
||||||
const res = await fetch(base + path, {
|
const res = await fetch(base + path, {
|
||||||
method: 'delete',
|
method: 'delete',
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: 'Bearer ' + localStorage.getItem('session_token'),
|
Authorization: 'Bearer ' + localStorage.getItem('session_token'),
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
|
body: body ? JSON.stringify(body) : undefined,
|
||||||
});
|
});
|
||||||
return await res.json();
|
return await res.json();
|
||||||
}
|
}
|
||||||
|
@ -176,3 +177,11 @@ export async function createCarpool({
|
||||||
}) {
|
}) {
|
||||||
return await post('/carpools/', { eventId, name });
|
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;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
}[];
|
}[];
|
||||||
invitations: IInvitation[];
|
invitations: {
|
||||||
|
user: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
isRequest: boolean;
|
||||||
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -81,7 +87,7 @@ export type IEvent = {
|
||||||
|
|
||||||
export type IEventSignup = {
|
export type IEventSignup = {
|
||||||
eventId: number;
|
eventId: number;
|
||||||
userId: number;
|
// userId: number;
|
||||||
user: {
|
user: {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user