mirror of
https://github.com/myfatemi04/wheelshare-frontend.git
synced 2025-04-16 00:50:18 -04:00
integrate useImmutable
This commit is contained in:
parent
0735d3ed3a
commit
a48ad180e0
7
.env
7
.env
|
@ -1,5 +1,2 @@
|
||||||
REACT_APP_API_DOMAIN_LOCAL=http://localhost:5000/
|
REACT_APP_API_LOCAL=http://localhost:5000/
|
||||||
REACT_APP_API_DOMAIN_DOCN=https://wheelshare-altbackend-2efyw.ondigitalocean.app/
|
REACT_APP_API_PROD=https://api.wheelshare.app/
|
||||||
REACT_APP_API_DOMAIN_WWW=https://api.wheelshare.app/
|
|
||||||
|
|
||||||
REACT_APP_API_DOMAIN=https://api.wheelshare.app/
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { BrowserRouter, Route, Switch } from 'react-router-dom';
|
||||||
import GroupsProvider from '../state/GroupsProvider';
|
import GroupsProvider from '../state/GroupsProvider';
|
||||||
import NotificationsProvider from '../state/Notifications/NotificationsProvider';
|
import NotificationsProvider from '../state/Notifications/NotificationsProvider';
|
||||||
import { useMe } from './hooks';
|
import { useMe } from './hooks';
|
||||||
|
import useImmutable from './useImmutable';
|
||||||
import WheelShare from './WheelShare';
|
import WheelShare from './WheelShare';
|
||||||
import WheelShareLoggedOut from './WheelShareLoggedOut';
|
import WheelShareLoggedOut from './WheelShareLoggedOut';
|
||||||
|
|
||||||
|
@ -23,11 +24,20 @@ const style: CSSProperties = {
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const user = useMe();
|
const user = useMe();
|
||||||
|
const [imm] = useImmutable({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: { a: 1, b: 2, c: [0, 1, 2] },
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<NotificationsProvider>
|
<NotificationsProvider>
|
||||||
<GroupsProvider>
|
<GroupsProvider>
|
||||||
<div style={{ padding: '1rem', maxWidth: '100vw' }}>
|
<div style={{ padding: '1rem', maxWidth: '100vw' }}>
|
||||||
<div style={style}>
|
<div style={style}>
|
||||||
|
{JSON.stringify(imm)}
|
||||||
|
{/* Reset button */}
|
||||||
|
<button onClick={() => imm.z.a++}>Increment</button>
|
||||||
|
<button onClick={() => imm.z.c.push(imm.z.c.length)}>Push</button>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<Route
|
||||||
|
|
|
@ -1,11 +1,4 @@
|
||||||
import * as immutable from 'immutable';
|
import { createContext, useCallback, useEffect, useMemo } from 'react';
|
||||||
import {
|
|
||||||
createContext,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import {
|
import {
|
||||||
cancelCarpoolInvite,
|
cancelCarpoolInvite,
|
||||||
getCarpool,
|
getCarpool,
|
||||||
|
@ -13,33 +6,23 @@ import {
|
||||||
sendCarpoolInvite,
|
sendCarpoolInvite,
|
||||||
} from '../api';
|
} from '../api';
|
||||||
import { useMe } from '../hooks';
|
import { useMe } from '../hooks';
|
||||||
|
import { ICarpool } from '../types';
|
||||||
import UISecondaryBox from '../UI/UISecondaryBox';
|
import UISecondaryBox from '../UI/UISecondaryBox';
|
||||||
|
import useImmutable from '../useImmutable';
|
||||||
import CarpoolDetails from './CarpoolDetails';
|
import CarpoolDetails from './CarpoolDetails';
|
||||||
import InvitationsAndRequests from './InvitationsAndRequests';
|
import InvitationsAndRequests from './InvitationsAndRequests';
|
||||||
import MemberList from './MemberList';
|
import MemberList from './MemberList';
|
||||||
|
|
||||||
class CarpoolState extends immutable.Record({
|
type CarpoolState = {
|
||||||
id: 0,
|
id: number;
|
||||||
name: '',
|
name: string;
|
||||||
eventId: -1,
|
event: ICarpool['event'];
|
||||||
event: {
|
members: { id: number; name: string }[];
|
||||||
id: -1,
|
invitations: Record<number, ICarpool['invitations'][0]>;
|
||||||
name: '',
|
};
|
||||||
formattedAddress: '',
|
|
||||||
latitude: 0,
|
|
||||||
longitude: 0,
|
|
||||||
placeId: '',
|
|
||||||
},
|
|
||||||
members: immutable.List<{ id: number; name: string }>(),
|
|
||||||
invitations:
|
|
||||||
immutable.Map<
|
|
||||||
number,
|
|
||||||
{ isRequest: boolean; user: { id: number; name: string } }
|
|
||||||
>(),
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
export const CarpoolContext = createContext({
|
export const CarpoolContext = createContext({
|
||||||
carpool: new CarpoolState(),
|
carpool: {} as CarpoolState,
|
||||||
sendInvite: (user: { id: number; name: string }) => {
|
sendInvite: (user: { id: number; name: string }) => {
|
||||||
console.error('not implemented: sendInvite');
|
console.error('not implemented: sendInvite');
|
||||||
},
|
},
|
||||||
|
@ -52,36 +35,36 @@ export const CarpoolContext = createContext({
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function Carpool({ id }: { id: number }) {
|
export default function Carpool({ id }: { id: number }) {
|
||||||
const [carpool, setCarpool] = useState(new CarpoolState());
|
const [carpool, setCarpool] = useImmutable<CarpoolState>({
|
||||||
|
id,
|
||||||
|
name: '',
|
||||||
|
event: {} as ICarpool['event'],
|
||||||
|
members: [],
|
||||||
|
invitations: {},
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getCarpool(id).then((carpool) => {
|
getCarpool(id).then((carpool) => {
|
||||||
setCarpool(
|
const invitationsMap: Record<number, ICarpool['invitations'][0]> = {};
|
||||||
new CarpoolState({
|
carpool.invitations.forEach((invite) => {
|
||||||
id: carpool.id,
|
invitationsMap[invite.user.id] = invite;
|
||||||
name: carpool.name,
|
});
|
||||||
eventId: carpool.eventId || carpool.event.id,
|
setCarpool({
|
||||||
event: carpool.event,
|
id: carpool.id,
|
||||||
members: immutable.List(carpool.members),
|
name: carpool.name,
|
||||||
invitations: immutable.Map(
|
event: carpool.event,
|
||||||
carpool.invitations.map((invite) => [invite.user.id, invite])
|
members: carpool.members,
|
||||||
),
|
invitations: invitationsMap,
|
||||||
})
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}, [id]);
|
}, [id, setCarpool]);
|
||||||
|
|
||||||
const sendInvite = useCallback(
|
const sendInvite = useCallback(
|
||||||
(user: { id: number; name: string }) => {
|
(user: { id: number; name: string }) => {
|
||||||
if (carpool) {
|
if (carpool) {
|
||||||
sendCarpoolInvite(id, user.id)
|
sendCarpoolInvite(id, user.id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setCarpool((carpool) =>
|
carpool.invitations[user.id] = { isRequest: false, user };
|
||||||
carpool.set(
|
|
||||||
'invitations',
|
|
||||||
carpool.invitations.set(user.id, { isRequest: false, user })
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
} else {
|
} else {
|
||||||
|
@ -97,22 +80,14 @@ export default function Carpool({ id }: { id: number }) {
|
||||||
(user: { id: number; name: string }) => {
|
(user: { id: number; name: string }) => {
|
||||||
cancelCarpoolInvite(id, user.id)
|
cancelCarpoolInvite(id, user.id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setCarpool(
|
delete carpool.invitations[user.id];
|
||||||
(carpool) =>
|
|
||||||
carpool && {
|
|
||||||
...carpool,
|
|
||||||
invitations: carpool.invitations.filter(
|
|
||||||
(invite) => invite.user.id !== user.id
|
|
||||||
),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
},
|
},
|
||||||
[id]
|
[carpool.invitations, id]
|
||||||
);
|
);
|
||||||
|
|
||||||
const eventId = carpool.eventId;
|
const eventId = carpool.event.id;
|
||||||
|
|
||||||
const leave = useCallback(() => {
|
const leave = useCallback(() => {
|
||||||
if (eventId) {
|
if (eventId) {
|
||||||
|
|
|
@ -59,9 +59,8 @@ export default function InvitationList() {
|
||||||
const invitedUserIDs = useMemo(
|
const invitedUserIDs = useMemo(
|
||||||
() =>
|
() =>
|
||||||
new Set(
|
new Set(
|
||||||
carpool.invitations
|
Object.values(carpool.invitations)
|
||||||
.filter((invitation) => !invitation.isRequest)
|
.filter((invitation) => !invitation.isRequest)
|
||||||
.valueSeq()
|
|
||||||
.map((invitation) => invitation.user.id)
|
.map((invitation) => invitation.user.id)
|
||||||
),
|
),
|
||||||
[carpool.invitations]
|
[carpool.invitations]
|
||||||
|
|
|
@ -24,7 +24,7 @@ export default function MemberList() {
|
||||||
const { leave, carpool } = useContext(CarpoolContext);
|
const { leave, 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.size - membersToShow.size;
|
const hiddenMemberCount = members.length - membersToShow.length;
|
||||||
|
|
||||||
const { sendCarpoolRequest, cancelCarpoolRequest } =
|
const { sendCarpoolRequest, cancelCarpoolRequest } =
|
||||||
useContext(NotificationsContext);
|
useContext(NotificationsContext);
|
||||||
|
@ -54,7 +54,7 @@ export default function MemberList() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h3 style={{ marginBlockEnd: '0' }}>Members</h3>
|
<h3 style={{ marginBlockEnd: '0' }}>Members</h3>
|
||||||
{members.size > 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} />
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import * as immutable from 'immutable';
|
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { green, lightgrey } from '../../lib/colors';
|
import { green, lightgrey } from '../../lib/colors';
|
||||||
import {
|
import {
|
||||||
|
@ -14,6 +13,7 @@ import UILink from '../UI/UILink';
|
||||||
import UIPlacesAutocomplete from '../UI/UIPlacesAutocomplete';
|
import UIPlacesAutocomplete from '../UI/UIPlacesAutocomplete';
|
||||||
import UISecondaryBox from '../UI/UISecondaryBox';
|
import UISecondaryBox from '../UI/UISecondaryBox';
|
||||||
import UISecondaryHeader from '../UI/UISecondaryHeader';
|
import UISecondaryHeader from '../UI/UISecondaryHeader';
|
||||||
|
import useImmutable from '../useImmutable';
|
||||||
import useThrottle from '../useThrottle';
|
import useThrottle from '../useThrottle';
|
||||||
import EventCarpools from './EventCarpools';
|
import EventCarpools from './EventCarpools';
|
||||||
import EventContext from './EventContext';
|
import EventContext from './EventContext';
|
||||||
|
@ -49,17 +49,7 @@ export default function Event({
|
||||||
});
|
});
|
||||||
const me = useMe();
|
const me = useMe();
|
||||||
|
|
||||||
const [tentativeInvites, setTentativeInvites] = useState(
|
const [tentativeInvites] = useImmutable<Record<number, boolean>>({});
|
||||||
immutable.Set<number>()
|
|
||||||
);
|
|
||||||
|
|
||||||
const addTentativeInvite = useCallback((userId: number) => {
|
|
||||||
setTentativeInvites((t) => t.add(userId));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const removeTentativeInvite = useCallback((userId: number) => {
|
|
||||||
setTentativeInvites((t) => t.delete(userId));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const refresh = useCallback(() => {
|
const refresh = useCallback(() => {
|
||||||
getEvent(id).then(setEvent);
|
getEvent(id).then(setEvent);
|
||||||
|
@ -142,8 +132,6 @@ export default function Event({
|
||||||
event,
|
event,
|
||||||
refresh,
|
refresh,
|
||||||
default: false,
|
default: false,
|
||||||
addTentativeInvite,
|
|
||||||
removeTentativeInvite,
|
|
||||||
tentativeInvites,
|
tentativeInvites,
|
||||||
signups,
|
signups,
|
||||||
hasCarpool,
|
hasCarpool,
|
||||||
|
|
|
@ -167,7 +167,7 @@ export default function Carpools() {
|
||||||
createCarpool({
|
createCarpool({
|
||||||
name: me.name + "'s Carpool",
|
name: me.name + "'s Carpool",
|
||||||
eventId: event.id,
|
eventId: event.id,
|
||||||
invitedUserIds: tentativeInvites.toArray(),
|
invitedUserIds: Object.keys(tentativeInvites).map(Number),
|
||||||
})
|
})
|
||||||
.then(({ id }) => {
|
.then(({ id }) => {
|
||||||
setCreatedCarpoolId(id);
|
setCreatedCarpoolId(id);
|
||||||
|
@ -180,12 +180,11 @@ export default function Carpools() {
|
||||||
|
|
||||||
const tentativeInviteNames = useMemo(() => {
|
const tentativeInviteNames = useMemo(() => {
|
||||||
if (!signups) return [];
|
if (!signups) return [];
|
||||||
const names = tentativeInvites.map((id) => {
|
const names = Object.keys(tentativeInvites).map((id) => {
|
||||||
const signup = signups[id];
|
const signup = signups[id];
|
||||||
return signup?.user.name;
|
return signup?.user.name;
|
||||||
});
|
});
|
||||||
const nonNull = names.filter((n) => n != null);
|
return names.filter((n) => n != null);
|
||||||
return nonNull.toArray() as string[];
|
|
||||||
}, [tentativeInvites, signups]);
|
}, [tentativeInvites, signups]);
|
||||||
|
|
||||||
let createCarpoolSection;
|
let createCarpoolSection;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
import { IEvent, IEventSignup } from '../types';
|
import { IEvent, IEventSignup } from '../types';
|
||||||
import * as immutable from 'immutable';
|
|
||||||
|
|
||||||
const EventContext = createContext({
|
const EventContext = createContext({
|
||||||
refresh: () => {
|
refresh: () => {
|
||||||
|
@ -9,13 +8,7 @@ const EventContext = createContext({
|
||||||
event: null! as IEvent,
|
event: null! as IEvent,
|
||||||
default: true,
|
default: true,
|
||||||
signups: {} as Record<string, IEventSignup>,
|
signups: {} as Record<string, IEventSignup>,
|
||||||
addTentativeInvite: (id: number) => {
|
tentativeInvites: {} as Record<number, boolean>,
|
||||||
console.error('not implemented: addTentativeInvite');
|
|
||||||
},
|
|
||||||
removeTentativeInvite: (id: number) => {
|
|
||||||
console.error('not implemented: removeTentativeInvite');
|
|
||||||
},
|
|
||||||
tentativeInvites: immutable.Set<number>(),
|
|
||||||
hasCarpool: false,
|
hasCarpool: false,
|
||||||
setHasCarpool: (has: boolean) => {
|
setHasCarpool: (has: boolean) => {
|
||||||
console.error('not implemented: setHasCarpool');
|
console.error('not implemented: setHasCarpool');
|
||||||
|
|
|
@ -21,12 +21,7 @@ function EventSignup({
|
||||||
}) {
|
}) {
|
||||||
const { user, latitude, longitude } = signup;
|
const { user, latitude, longitude } = signup;
|
||||||
const me = useMe();
|
const me = useMe();
|
||||||
const {
|
const { tentativeInvites, hasCarpool } = useContext(EventContext);
|
||||||
addTentativeInvite,
|
|
||||||
removeTentativeInvite,
|
|
||||||
tentativeInvites,
|
|
||||||
hasCarpool,
|
|
||||||
} = useContext(EventContext);
|
|
||||||
|
|
||||||
const extraDistance = useMemo(() => {
|
const extraDistance = useMemo(() => {
|
||||||
if (myPlaceDetails != null && !(latitude === null || longitude === null)) {
|
if (myPlaceDetails != null && !(latitude === null || longitude === null)) {
|
||||||
|
@ -66,10 +61,7 @@ function EventSignup({
|
||||||
myPlaceDetails,
|
myPlaceDetails,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const isTentativelyInvited = useMemo(
|
const isTentativelyInvited = signup.user.id in tentativeInvites;
|
||||||
() => tentativeInvites.has(signup.user.id),
|
|
||||||
[signup.user.id, tentativeInvites]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (user.id === me?.id) {
|
if (user.id === me?.id) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -98,12 +90,12 @@ function EventSignup({
|
||||||
{!hasCarpool &&
|
{!hasCarpool &&
|
||||||
(isTentativelyInvited ? (
|
(isTentativelyInvited ? (
|
||||||
<CancelIcon
|
<CancelIcon
|
||||||
onClick={() => removeTentativeInvite(user.id)}
|
onClick={() => delete tentativeInvites[user.id]}
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<PersonAddIcon
|
<PersonAddIcon
|
||||||
onClick={() => addTentativeInvite(user.id)}
|
onClick={() => (tentativeInvites[user.id] = true)}
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -14,24 +14,44 @@ function createEdgeForObject<T extends PlainJSObject>(
|
||||||
value: T,
|
value: T,
|
||||||
setValue: Dispatch<SetStateAction<T>>
|
setValue: Dispatch<SetStateAction<T>>
|
||||||
): T {
|
): T {
|
||||||
// @ts-expect-error
|
return new Proxy(value, {
|
||||||
const edge: T = {};
|
set: (target, property, value) => {
|
||||||
for (let [key, keyValue] of Object.entries(value)) {
|
setValue((v) => ({ ...v, [property]: value }));
|
||||||
const set = (next: SetStateAction<typeof keyValue>) => {
|
|
||||||
const v = typeof next === 'function' ? next(keyValue) : next;
|
|
||||||
setValue((value) => ({ ...value, [key]: v }));
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.defineProperty(edge, key, {
|
return true;
|
||||||
enumerable: true,
|
},
|
||||||
configurable: false,
|
// @ts-expect-error
|
||||||
get: () => createEdge(keyValue, set),
|
get: (target, property: keyof T) => {
|
||||||
set: (v) => void setValue((value) => ({ ...value, [key]: v })),
|
const keyValue = target[property];
|
||||||
});
|
const set = (next: SetStateAction<typeof keyValue>) => {
|
||||||
}
|
const v = typeof next === 'function' ? next(keyValue) : next;
|
||||||
return edge;
|
setValue((value) => ({ ...value, [property]: v }));
|
||||||
|
};
|
||||||
|
|
||||||
|
return createEdge(keyValue, set);
|
||||||
|
},
|
||||||
|
deleteProperty: (target, property) => {
|
||||||
|
setValue((v) => {
|
||||||
|
const newValue = { ...v };
|
||||||
|
// @ts-ignore
|
||||||
|
delete newValue[property];
|
||||||
|
return newValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const inPlaceArrayOperations = [
|
||||||
|
'fill',
|
||||||
|
'reverse',
|
||||||
|
'push',
|
||||||
|
'pop',
|
||||||
|
'shift',
|
||||||
|
'unshift',
|
||||||
|
];
|
||||||
|
|
||||||
function createEdgeForArray<T extends PlainJS>(
|
function createEdgeForArray<T extends PlainJS>(
|
||||||
value: PlainJSArray<T>,
|
value: PlainJSArray<T>,
|
||||||
setValue: Dispatch<SetStateAction<PlainJSArray<T>>>
|
setValue: Dispatch<SetStateAction<PlainJSArray<T>>>
|
||||||
|
@ -58,7 +78,8 @@ function createEdgeForArray<T extends PlainJS>(
|
||||||
if (typeof property === 'number') {
|
if (typeof property === 'number') {
|
||||||
return target[property];
|
return target[property];
|
||||||
} else {
|
} else {
|
||||||
if (typeof target[property] === 'function') {
|
// @ts-ignore
|
||||||
|
if (inPlaceArrayOperations.includes(property)) {
|
||||||
return function () {
|
return function () {
|
||||||
const newValue = [...value];
|
const newValue = [...value];
|
||||||
const method = newValue[property];
|
const method = newValue[property];
|
||||||
|
@ -67,9 +88,15 @@ function createEdgeForArray<T extends PlainJS>(
|
||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
} else {
|
} else if (typeof target[property] === 'function') {
|
||||||
return target[property];
|
return function () {
|
||||||
|
console.log(target[property]);
|
||||||
|
// @ts-ignore
|
||||||
|
return target[property].apply(target, arguments);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return target[property];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user