rename some hooks, make events deletable by creator

This commit is contained in:
Michael Fatemi 2021-08-16 22:02:59 -04:00
parent 5d0a25991d
commit c25d650276
9 changed files with 189 additions and 40 deletions

View File

@ -1,21 +1,11 @@
import { useCallback, useEffect, useState } from 'react';
import { getEvent } from '../api';
import { IEvent } from '../types';
import UILink from '../UI/UILink';
import UISecondaryBox from '../UI/UISecondaryBox';
import UISecondaryHeader from '../UI/UISecondaryHeader';
import useImmutable from '../useImmutable';
import EventContent from './EventContent';
import EventContext from './EventContext';
import EventDetails from './EventDetails';
import EventInterestForm from './EventInterestForm';
import EventPlaceholder from './EventPlaceholder';
type NotNull<T> = T extends null ? never : T;
function GroupName({ group }: { group: NotNull<IEvent['group']> }) {
return <UILink href={`/groups/${group.id}`}>{group.name}</UILink>;
}
export default function Event({
id,
initial,
@ -44,8 +34,6 @@ export default function Event({
return <h1>Event Not Found</h1>;
}
const { name, group } = event;
return (
<EventContext.Provider
value={{
@ -55,16 +43,7 @@ export default function Event({
tentativeInvites,
}}
>
<UISecondaryBox style={{ width: '35rem', maxWidth: '100vw' }}>
<div style={{ textAlign: 'center' }}>
<UISecondaryHeader>{name}</UISecondaryHeader>
<span>Created by {event.creator.name}</span>
<br />
{group && <GroupName group={group} />}
</div>
<EventDetails />
<EventInterestForm />
</UISecondaryBox>
<EventContent />
</EventContext.Provider>
);
}

View File

@ -0,0 +1,83 @@
import { useCallback, useState } from 'react';
import { deleteEvent } from '../api';
import UIPressable from '../UI/UIPressable';
import { useCurrentEventId } from './EventHooks';
export enum AsyncCallbackStatus {
NONE = 0,
PENDING = 1,
RESOLVED = 2,
REJECTED = 3,
}
export function useAsyncCallback<T, A extends unknown[]>(
callback: (...args: A) => Promise<T>
) {
const [status, setStatus] = useState(AsyncCallbackStatus.NONE);
const cb = useCallback(
(...args: any) => {
setStatus(AsyncCallbackStatus.PENDING);
callback(...args)
.then(() => setStatus(AsyncCallbackStatus.RESOLVED))
.catch(() => setStatus(AsyncCallbackStatus.REJECTED));
},
[callback]
);
const reset = useCallback(() => setStatus(AsyncCallbackStatus.NONE), []);
return [cb as typeof callback, status, reset] as const;
}
export default function EventAdminControls() {
const id = useCurrentEventId();
// const desc = useCurrentEventDescription();
// const descriptionTextareaRef = useRef<HTMLTextAreaElement>(null);
const [onPressDelete, deletionStatus] = useAsyncCallback(
useCallback(() => deleteEvent(id), [id])
);
// const [onSaveDescription, saveDescriptionStatus] = useAsyncCallback(
// useCallback(() => {
// if (!descriptionTextareaRef.current) {
// return Promise.reject('Textarea not ready');
// }
// setEditDescriptionOpen(false);
// return setEventDescription(id, descriptionTextareaRef.current.value);
// }, [id])
// );
// const [editDescriptionOpen, setEditDescriptionOpen] = useState(false);
return (
<div style={{ display: 'flex' }}>
{deletionStatus === AsyncCallbackStatus.NONE ? (
<UIPressable onClick={onPressDelete}>Delete</UIPressable>
) : deletionStatus === AsyncCallbackStatus.PENDING ? (
<span>Deleting...</span>
) : deletionStatus === AsyncCallbackStatus.RESOLVED ? (
<span>Deleted</span>
) : (
<span>Delete failed</span>
)}
{/* {!editDescriptionOpen ? (
<UIPressable onClick={() => setEditDescriptionOpen(true)}>
Edit description
</UIPressable>
) : (
<>
<textarea defaultValue={desc} ref={descriptionTextareaRef} />
<UIPressable onClick={onSaveDescription}>Save</UIPressable>
<UIPressable onClick={() => setEditDescriptionOpen(false)}>
Cancel
</UIPressable>
</>
)} */}
</div>
);
}

View File

@ -5,7 +5,7 @@ import { useMe } from '../hooks';
import UIButton from '../UI/UIButton';
import UILink from '../UI/UILink';
import EventContext from './EventContext';
import { useMyCarpool } from './EventHooks';
import { useCurrentEventCarpool } from './EventHooks';
type CreationStatus = null | 'pending' | 'completed' | 'errored';
@ -16,7 +16,7 @@ export default function EventCarpoolCreateButton() {
const [createdCarpoolId, setCreatedCarpoolId] = useState<null | number>(null);
const me = useMe() || { id: 0, name: '' };
const myCarpool = useMyCarpool();
const myCarpool = useCurrentEventCarpool();
const createCarpoolCallback = useCallback(async () => {
setCreationStatus('pending');

View File

@ -11,7 +11,7 @@ import { useMe } from '../hooks';
import { IEvent, IEventSignupComplete } from '../types';
import useOptimalPath from '../useOptimalPath';
import EventContext from './EventContext';
import { useMySignup } from './EventHooks';
import { useCurrentEventSignup } from './EventHooks';
function useMemberLocations(members: IEvent['carpools'][0]['members']) {
const { event } = useContext(EventContext);
@ -61,7 +61,7 @@ function CarpoolRow({
const { event } = useContext(EventContext);
const mySignup = useMySignup();
const mySignup = useCurrentEventSignup();
const memberLocations = useMemberLocations(carpool.members);
@ -145,13 +145,15 @@ export default function Carpools() {
Click <EmojiPeopleIcon style={{ fontSize: '0.875rem' }} /> to request to
join a carpool.
</span>
{event.carpools.length>0 ? event.carpools.map((carpool) => (
<CarpoolRow
carpool={carpool}
key={carpool.id}
inCarpoolAlready={alreadyInCarpool}
/>
)) : "No Carpools"}
{event.carpools.length > 0
? event.carpools.map((carpool) => (
<CarpoolRow
carpool={carpool}
key={carpool.id}
inCarpoolAlready={alreadyInCarpool}
/>
))
: 'No Carpools'}
</div>
);
}

View File

@ -0,0 +1,39 @@
import UILink from '../UI/UILink';
import UISecondaryBox from '../UI/UISecondaryBox';
import UISecondaryHeader from '../UI/UISecondaryHeader';
import EventAdminControls from './EventAdminControls';
import EventDetails from './EventDetails';
import {
useCurrentEventCreator,
useCurrentEventGroup,
useCurrentEventName,
useIsCurrentEventCreator,
} from './EventHooks';
import EventInterestForm from './EventInterestForm';
export default function EventContent() {
const group = useCurrentEventGroup();
const name = useCurrentEventName();
const creator = useCurrentEventCreator();
const isEventCreator = useIsCurrentEventCreator();
return (
<UISecondaryBox style={{ width: '35rem', maxWidth: '100vw' }}>
<div style={{ textAlign: 'center' }}>
<UISecondaryHeader>{name}</UISecondaryHeader>
<span>
Created by {isEventCreator ? 'you' : creator.name}
{group && (
<>
{' '}
in <UILink href={`/groups/${group.id}`}>{group.name}</UILink>
</>
)}
</span>
</div>
<EventDetails />
<EventInterestForm />
{isEventCreator && <EventAdminControls />}
</UISecondaryBox>
);
}

View File

@ -2,7 +2,41 @@ import { useContext, useDebugValue, useMemo } from 'react';
import { useMe } from '../hooks';
import EventContext from './EventContext';
export function useSignups() {
export function useCurrentEventId() {
const { event } = useContext(EventContext);
return useMemo(() => event.id, [event.id]);
}
export function useCurrentEventName() {
const { event } = useContext(EventContext);
return useMemo(() => event.name, [event.name]);
}
export function useCurrentEventDescription() {
const { event } = useContext(EventContext);
return useMemo(() => event.description, [event.description]);
}
export function useCurrentEventCreator() {
const { event } = useContext(EventContext);
return useMemo(() => event.creator, [event.creator]);
}
export function useCurrentEventGroup() {
const { event } = useContext(EventContext);
return useMemo(() => event.group, [event.group]);
}
export function useIsCurrentEventCreator() {
const creator = useCurrentEventCreator();
const me = useMe();
if (!me) {
return false;
}
return me.id === creator.id;
}
export function useCurrentEventSignups() {
const signups = useContext(EventContext).event.signups;
useDebugValue(signups);
@ -10,8 +44,8 @@ export function useSignups() {
return signups;
}
export function useMySignup() {
const signups = useSignups();
export function useCurrentEventSignup() {
const signups = useCurrentEventSignups();
const me = useMe() || { id: 0, name: '' };
const signup = useMemo(() => signups[me.id] ?? null, [signups, me.id]);
@ -21,7 +55,7 @@ export function useMySignup() {
return signup;
}
export function useMyCarpool() {
export function useCurrentEventCarpool() {
const me = useMe() || { id: 0, name: '' };
const { event } = useContext(EventContext);

View File

@ -7,13 +7,13 @@ import { IEventSignup } from '../types';
import EventCarpoolCreateButton from './EventCarpoolCreateButton';
import EventContext from './EventContext';
import pickLatLong from './pickLatLong';
import { useMySignup } from './EventHooks';
import { useCurrentEventSignup } from './EventHooks';
function EventSignup({ signup }: { signup: IEventSignup }) {
const { user } = signup;
const me = useMe();
const { tentativeInvites, event } = useContext(EventContext);
const mySignup = useMySignup();
const mySignup = useCurrentEventSignup();
const myLocation = pickLatLong(mySignup);
const theirLocation = pickLatLong(signup);
const eventLocation = pickLatLong(event)!;

View File

@ -113,6 +113,17 @@ export async function createEvent({
};
}
export async function setEventDescription(
eventId: number,
description: string
) {
return await post(`/events/${eventId}/update`, { description });
}
export async function deleteEvent(eventId: number) {
return await delete$(`/events/${eventId}`);
}
export async function getEvents(): Promise<IEvent[]> {
return await get('/events');
}

View File

@ -80,6 +80,7 @@ export type IEvent = {
name: string;
}[];
}[];
description: string;
signups: Record<string, IEventSignup>;
startTime: string; // Datestring
duration: number;