mirror of
https://github.com/myfatemi04/wheelshare-frontend.git
synced 2025-04-16 00:50:18 -04:00
UI improvements
This commit is contained in:
parent
cb7a6c8d05
commit
6529d1c39f
|
@ -1,6 +1,7 @@
|
|||
import { CSSProperties, lazy, Suspense } from 'react';
|
||||
import { BrowserRouter, Route, Switch } from 'react-router-dom';
|
||||
import NotificationsProvider from '../state/Notifications/NotificationsProvider';
|
||||
import Header from './Header/Header';
|
||||
import { useMe } from './hooks';
|
||||
import WheelShare from './WheelShare';
|
||||
import WheelShareLoggedOut from './WheelShareLoggedOut';
|
||||
|
@ -31,11 +32,8 @@ export default function App() {
|
|||
<Switch>
|
||||
{user ? (
|
||||
<NotificationsProvider>
|
||||
<Header />
|
||||
<Route path="/" exact component={WheelShare} />
|
||||
<Route
|
||||
component={Authenticator}
|
||||
path="/auth/:provider/callback"
|
||||
/>
|
||||
<Route path="/carpools/:id" component={CarpoolPage} />
|
||||
<Route path="/events/:id" component={EventPage} />
|
||||
<Route path="/groups/:id" component={Group} />
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
import { useParams } from 'react-router-dom';
|
||||
import Header from '../Header/Header';
|
||||
import Carpool from './Carpool';
|
||||
|
||||
export default function CarpoolPage() {
|
||||
const id = +useParams<{ id: string }>().id;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Carpool id={id} />
|
||||
</>
|
||||
);
|
||||
return <Carpool id={id} />;
|
||||
}
|
||||
|
|
|
@ -1,19 +1,14 @@
|
|||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { green, lightgrey } from '../../lib/colors';
|
||||
import getPlaceDetails from '../../lib/getPlaceDetails';
|
||||
import { addOrUpdateEventSignup, getEvent, removeEventSignup } from '../api';
|
||||
import { useMe } from '../hooks';
|
||||
import { getEvent } from '../api';
|
||||
import { IEvent } from '../types';
|
||||
import UIButton from '../UI/UIButton';
|
||||
import UILink from '../UI/UILink';
|
||||
import UIPlacesAutocomplete from '../UI/UIPlacesAutocomplete';
|
||||
import UISecondaryBox from '../UI/UISecondaryBox';
|
||||
import UISecondaryHeader from '../UI/UISecondaryHeader';
|
||||
import useImmutable from '../useImmutable';
|
||||
import EventCarpools from './EventCarpools';
|
||||
import EventContext from './EventContext';
|
||||
import EventDetails from './EventDetails';
|
||||
import EventSignups from './EventSignups';
|
||||
import EventInterestForm from './EventInterestForm';
|
||||
import EventPlaceholder from './EventPlaceholder';
|
||||
|
||||
function GroupName({ group }: { group: IEvent['group'] }) {
|
||||
return <UILink href={`/groups/${group.id}`}>{group.name}</UILink>;
|
||||
|
@ -26,89 +21,28 @@ export default function Event({
|
|||
id: number;
|
||||
initial?: IEvent;
|
||||
}) {
|
||||
const [event, setEvent] = useImmutable<IEvent>({
|
||||
id,
|
||||
name: '',
|
||||
group: {
|
||||
id: 0,
|
||||
name: '',
|
||||
},
|
||||
signups: {},
|
||||
carpools: [],
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
daysOfWeek: 0,
|
||||
placeId: '',
|
||||
formattedAddress: '',
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
duration: 0,
|
||||
...(initial || {}),
|
||||
});
|
||||
|
||||
const [found, setFound] = useState(false);
|
||||
|
||||
const me = useMe() || { id: 0, name: '' };
|
||||
|
||||
const [event, setEvent] = useImmutable<IEvent | null>(initial ?? null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [tentativeInvites] = useImmutable<Record<number, boolean>>({});
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
getEvent(id).then((e) => {
|
||||
if (e) {
|
||||
setFound(true);
|
||||
setEvent(e);
|
||||
} else {
|
||||
setFound(false);
|
||||
}
|
||||
});
|
||||
setLoading(true);
|
||||
getEvent(id)
|
||||
.then((e) => e && setEvent(e))
|
||||
.finally(() => setLoading(false));
|
||||
}, [id, setEvent]);
|
||||
|
||||
useEffect(refresh, [refresh]);
|
||||
|
||||
const updateSignup = useCallback(
|
||||
async (placeId: string | null) => {
|
||||
await addOrUpdateEventSignup(id, placeId);
|
||||
|
||||
if (placeId) {
|
||||
const details = await getPlaceDetails(placeId);
|
||||
|
||||
event.signups[me.id] = {
|
||||
user: { id: me.id, name: me.name },
|
||||
placeId,
|
||||
...details,
|
||||
};
|
||||
} else {
|
||||
event.signups[me.id] = {
|
||||
user: { id: me.id, name: me.name },
|
||||
placeId: null,
|
||||
latitude: null,
|
||||
longitude: null,
|
||||
formattedAddress: null,
|
||||
};
|
||||
}
|
||||
},
|
||||
[event.signups, id, me.id, me.name]
|
||||
);
|
||||
|
||||
const removeSignup = useCallback(async () => {
|
||||
await removeEventSignup(id);
|
||||
|
||||
if (event.signups[me.id]) {
|
||||
delete event.signups[me.id];
|
||||
}
|
||||
}, [id, me.id, event.signups]);
|
||||
|
||||
const interested = !!event.signups[me.id];
|
||||
|
||||
if (!found) {
|
||||
return (
|
||||
<>
|
||||
<h1>Event Not Found</h1>
|
||||
</>
|
||||
);
|
||||
if (loading) {
|
||||
return <EventPlaceholder />;
|
||||
}
|
||||
|
||||
const { name, group, formattedAddress, startTime, endTime } = event;
|
||||
if (!event) {
|
||||
return <h1>Event Not Found</h1>;
|
||||
}
|
||||
|
||||
const { name, group } = event;
|
||||
|
||||
return (
|
||||
<EventContext.Provider
|
||||
|
@ -124,36 +58,8 @@ export default function Event({
|
|||
<UISecondaryHeader>{name}</UISecondaryHeader>
|
||||
{group && <GroupName group={group} />}
|
||||
</div>
|
||||
<EventDetails {...{ startTime, endTime, formattedAddress }} />
|
||||
<UIButton
|
||||
onClick={interested ? () => removeSignup() : () => updateSignup(null)}
|
||||
style={{
|
||||
backgroundColor: interested ? green : lightgrey,
|
||||
color: interested ? 'white' : 'black',
|
||||
transition: 'color 0.2s, background-color 0.2s',
|
||||
}}
|
||||
>
|
||||
{interested ? 'Interested' : 'Not interested'}
|
||||
</UIButton>
|
||||
{interested && (
|
||||
<>
|
||||
<UIPlacesAutocomplete
|
||||
placeholder="Pickup and dropoff location"
|
||||
onSelected={(_address, placeId) => {
|
||||
updateSignup(placeId);
|
||||
}}
|
||||
style={
|
||||
event.signups[me.id]?.placeId != null
|
||||
? { border: '2px solid ' + green }
|
||||
: {}
|
||||
}
|
||||
placeId={event.signups[me.id]?.placeId}
|
||||
/>
|
||||
<br />
|
||||
<EventCarpools />
|
||||
{event.signups !== null && <EventSignups />}
|
||||
</>
|
||||
)}
|
||||
<EventDetails />
|
||||
<EventInterestForm />
|
||||
</UISecondaryBox>
|
||||
</EventContext.Provider>
|
||||
);
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
import formatStartAndEndTime from '../../lib/dates';
|
||||
import EventIcon from '@material-ui/icons/Event';
|
||||
import LocationOnIcon from '@material-ui/icons/LocationOn';
|
||||
import { useContext } from 'react';
|
||||
import EventContext from './EventContext';
|
||||
|
||||
export default function EventDetails() {
|
||||
const { startTime, endTime, formattedAddress } =
|
||||
useContext(EventContext).event;
|
||||
|
||||
export default function Details({
|
||||
startTime,
|
||||
endTime,
|
||||
formattedAddress,
|
||||
}: {
|
||||
startTime: string;
|
||||
endTime: string | null;
|
||||
formattedAddress: string;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
|
|
|
@ -37,3 +37,7 @@ export function useMyCarpool() {
|
|||
|
||||
return carpool;
|
||||
}
|
||||
|
||||
export function useMutableEvent() {
|
||||
return useContext(EventContext).event;
|
||||
}
|
||||
|
|
84
src/components/Event/EventInterestForm.tsx
Normal file
84
src/components/Event/EventInterestForm.tsx
Normal file
|
@ -0,0 +1,84 @@
|
|||
import { useCallback } from 'react';
|
||||
import { green, lightgrey } from '../../lib/colors';
|
||||
import getPlaceDetails from '../../lib/getPlaceDetails';
|
||||
import { addOrUpdateEventSignup, removeEventSignup } from '../api';
|
||||
import { useMe } from '../hooks';
|
||||
import UIButton from '../UI/UIButton';
|
||||
import UIPlacesAutocomplete from '../UI/UIPlacesAutocomplete';
|
||||
import EventCarpools from './EventCarpools';
|
||||
import { useMutableEvent } from './EventHooks';
|
||||
import EventSignups from './EventSignups';
|
||||
|
||||
export default function EventInterestForm() {
|
||||
const event = useMutableEvent();
|
||||
const me = useMe() || { id: 0, name: '' };
|
||||
|
||||
const updateSignup = useCallback(
|
||||
async (placeId: string | null) => {
|
||||
await addOrUpdateEventSignup(event.id, placeId);
|
||||
|
||||
if (placeId) {
|
||||
const details = await getPlaceDetails(placeId);
|
||||
|
||||
event.signups[me.id] = {
|
||||
user: { id: me.id, name: me.name },
|
||||
placeId,
|
||||
...details,
|
||||
};
|
||||
} else {
|
||||
event.signups[me.id] = {
|
||||
user: { id: me.id, name: me.name },
|
||||
placeId: null,
|
||||
latitude: null,
|
||||
longitude: null,
|
||||
formattedAddress: null,
|
||||
};
|
||||
}
|
||||
},
|
||||
[event.id, event.signups, me.id, me.name]
|
||||
);
|
||||
|
||||
const removeSignup = useCallback(async () => {
|
||||
await removeEventSignup(event.id);
|
||||
|
||||
if (event.signups[me.id]) {
|
||||
delete event.signups[me.id];
|
||||
}
|
||||
}, [event.id, event.signups, me.id]);
|
||||
|
||||
const interested = !!event.signups[me.id];
|
||||
|
||||
return (
|
||||
<>
|
||||
<UIButton
|
||||
onClick={interested ? () => removeSignup() : () => updateSignup(null)}
|
||||
style={{
|
||||
backgroundColor: interested ? green : lightgrey,
|
||||
color: interested ? 'white' : 'black',
|
||||
transition: 'color 0.2s, background-color 0.2s',
|
||||
}}
|
||||
>
|
||||
{interested ? 'Interested' : 'Not interested'}
|
||||
</UIButton>
|
||||
{interested && (
|
||||
<>
|
||||
<UIPlacesAutocomplete
|
||||
placeholder="Pickup and dropoff location"
|
||||
onSelected={(_address, placeId) => {
|
||||
updateSignup(placeId);
|
||||
}}
|
||||
style={
|
||||
event.signups[me.id]?.placeId != null
|
||||
? { border: '2px solid ' + green }
|
||||
: {}
|
||||
}
|
||||
placeId={event.signups[me.id]?.placeId}
|
||||
/>
|
||||
<br />
|
||||
<EventCarpools />
|
||||
{event.signups !== null && <EventSignups />}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,14 +1,8 @@
|
|||
import { useParams } from 'react-router-dom';
|
||||
import Header from '../Header/Header';
|
||||
import Event from './Event';
|
||||
|
||||
export default function EventPage() {
|
||||
const id = +useParams<{ id: string }>().id;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Event id={id} />
|
||||
</>
|
||||
);
|
||||
return <Event id={id} />;
|
||||
}
|
||||
|
|
7
src/components/Event/EventPlaceholder.tsx
Normal file
7
src/components/Event/EventPlaceholder.tsx
Normal file
|
@ -0,0 +1,7 @@
|
|||
import UISecondaryBox from '../UI/UISecondaryBox';
|
||||
|
||||
export default function EventPlaceholder() {
|
||||
return (
|
||||
<UISecondaryBox style={{ height: '10rem' }}>Loading...</UISecondaryBox>
|
||||
);
|
||||
}
|
|
@ -84,7 +84,7 @@ export default function EventCreator({ group }: { group: IGroup }) {
|
|||
]);
|
||||
|
||||
return (
|
||||
<UISecondaryBox style={{ width: '100%', boxSizing: 'border-box' }}>
|
||||
<UISecondaryBox style={{ width: '100%', textAlign: 'center' }}>
|
||||
<h1 style={{ textAlign: 'center', marginBottom: '0.5rem' }}>
|
||||
Create Event
|
||||
</h1>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { useContext } from 'react';
|
||||
import { GroupContext } from '../Group/Group';
|
||||
import UIPressable from '../UI/UIPressable';
|
||||
import useToggle from '../useToggle';
|
||||
import EventCreator from './EventCreator';
|
||||
|
||||
|
@ -8,22 +9,14 @@ export default function EventCreatorLink() {
|
|||
const { group } = useContext(GroupContext);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none',
|
||||
}}
|
||||
onClick={toggle}
|
||||
>
|
||||
Create Event
|
||||
</div>
|
||||
<>
|
||||
<UIPressable onClick={toggle}>Create Event</UIPressable>
|
||||
{open && (
|
||||
<>
|
||||
<br />
|
||||
<EventCreator group={group} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -19,48 +19,50 @@ const DEFAULT_GROUP = (): IGroup => ({
|
|||
export const GroupContext = createContext({ group: DEFAULT_GROUP() });
|
||||
|
||||
export default function Group({ id }: { id: number }) {
|
||||
const [group, setGroup] = useImmutable<IGroup>(DEFAULT_GROUP());
|
||||
const [found, setFound] = useState(false);
|
||||
const [group, setGroup] = useImmutable<IGroup | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
getGroup(id)
|
||||
.then(setGroup)
|
||||
.catch(() => setFound(false));
|
||||
.finally(() => setLoading(false));
|
||||
}, [id, setGroup]);
|
||||
|
||||
return found ? (
|
||||
if (loading) {
|
||||
return <h1>Loading...</h1>;
|
||||
}
|
||||
|
||||
return group ? (
|
||||
<GroupContext.Provider value={{ group }}>
|
||||
<h1>{group.name}</h1>
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
maxWidth: '30rem',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<h1>{group.name}</h1>
|
||||
<UILink href="/">Home</UILink>
|
||||
<br />
|
||||
<br />
|
||||
<GroupMembersLink />
|
||||
<br />
|
||||
<GroupSettingsLink />
|
||||
<br />
|
||||
<EventCreatorLink />
|
||||
<br />
|
||||
|
||||
{group.events.length > 0 ? (
|
||||
<EventStream events={group.events} />
|
||||
) : (
|
||||
<span>
|
||||
There are no events yet. Click 'create event' above to add one!
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<br />
|
||||
|
||||
{group.events.length > 0 ? (
|
||||
<EventStream events={group.events} />
|
||||
) : (
|
||||
<span>
|
||||
There are no events yet. Click 'create event' above to add one!
|
||||
</span>
|
||||
)}
|
||||
</GroupContext.Provider>
|
||||
) : (
|
||||
<>
|
||||
<h1>Group not found</h1>
|
||||
</>
|
||||
<h1>Group not found</h1>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useCallback, useContext } from 'react';
|
||||
import { useCallback, useContext, useState } from 'react';
|
||||
import { lightgrey } from '../../lib/colors';
|
||||
import { generateCode, resetCode } from '../api';
|
||||
import UIButton from '../UI/UIButton';
|
||||
|
@ -7,6 +7,8 @@ import { GroupContext } from './Group';
|
|||
export default function GroupInviteCodeGenerator() {
|
||||
const { group } = useContext(GroupContext);
|
||||
|
||||
const [shown, setShown] = useState(false);
|
||||
|
||||
const generateJoinCode = useCallback(() => {
|
||||
generateCode(group.id).then((code) => {
|
||||
group.joinCode = code;
|
||||
|
@ -22,11 +24,15 @@ export default function GroupInviteCodeGenerator() {
|
|||
if (group.joinCode) {
|
||||
return (
|
||||
<>
|
||||
<span>
|
||||
<span style={{ userSelect: 'none' }}>
|
||||
Join this group with the code{' '}
|
||||
<b>
|
||||
<code>{group.joinCode}</code>
|
||||
</b>
|
||||
<code
|
||||
style={{ userSelect: 'text' }}
|
||||
onClick={() => setShown((shown) => !shown)}
|
||||
>
|
||||
{shown ? group.joinCode : 'XXXXXX'}
|
||||
</code>{' '}
|
||||
(click to show/hide)
|
||||
</span>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<UIButton
|
||||
|
|
|
@ -17,14 +17,15 @@ export default function GroupMembersLink() {
|
|||
{open && (
|
||||
<>
|
||||
<br />
|
||||
<UISecondaryBox>
|
||||
<UISecondaryBox style={{ width: '100%', textAlign: 'center' }}>
|
||||
<h1>Members</h1>
|
||||
|
||||
<GroupInviteCodeGenerator />
|
||||
|
||||
{group.users.map(({ name }) => (
|
||||
<span key={name}>{name}</span>
|
||||
))}
|
||||
<br />
|
||||
|
||||
<GroupInviteCodeGenerator />
|
||||
</UISecondaryBox>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -20,7 +20,7 @@ export default function GroupSettings({ group }: { group: IGroup }) {
|
|||
}, [group.id]);
|
||||
|
||||
return (
|
||||
<UISecondaryBox>
|
||||
<UISecondaryBox style={{ width: '100%', textAlign: 'center' }}>
|
||||
<h1>Settings</h1>
|
||||
{deletionSuccessful !== true && (
|
||||
<UIPressable onClick={onClickedDelete}>Delete Group</UIPressable>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { useContext } from 'react';
|
||||
import UIPressable from '../UI/UIPressable';
|
||||
import useToggle from '../useToggle';
|
||||
import { GroupContext } from './Group';
|
||||
import GroupSettings from './GroupSettings';
|
||||
|
@ -8,22 +9,14 @@ export default function GroupSettingsLink() {
|
|||
const { group } = useContext(GroupContext);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none',
|
||||
}}
|
||||
onClick={toggle}
|
||||
>
|
||||
Settings
|
||||
</div>
|
||||
<>
|
||||
<UIPressable onClick={toggle}>Settings</UIPressable>
|
||||
{open && (
|
||||
<>
|
||||
<br />
|
||||
<GroupSettings group={group} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import ActiveCarpools from './ActiveCarpools/ActiveCarpools';
|
||||
import ActiveEvents from './ActiveEvents/Events';
|
||||
import Groups from './Groups/Groups';
|
||||
import Header from './Header/Header';
|
||||
|
||||
export default function WheelShare() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
|
||||
<Groups />
|
||||
<ActiveCarpools />
|
||||
<ActiveEvents />
|
||||
|
|
Loading…
Reference in New Issue
Block a user