mirror of
https://github.com/myfatemi04/wheelshare-frontend.git
synced 2025-04-17 17:40:16 -04:00
rename some hooks, make events deletable by creator
This commit is contained in:
parent
5d0a25991d
commit
c25d650276
|
@ -1,21 +1,11 @@
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { getEvent } from '../api';
|
import { getEvent } from '../api';
|
||||||
import { IEvent } from '../types';
|
import { IEvent } from '../types';
|
||||||
import UILink from '../UI/UILink';
|
|
||||||
import UISecondaryBox from '../UI/UISecondaryBox';
|
|
||||||
import UISecondaryHeader from '../UI/UISecondaryHeader';
|
|
||||||
import useImmutable from '../useImmutable';
|
import useImmutable from '../useImmutable';
|
||||||
|
import EventContent from './EventContent';
|
||||||
import EventContext from './EventContext';
|
import EventContext from './EventContext';
|
||||||
import EventDetails from './EventDetails';
|
|
||||||
import EventInterestForm from './EventInterestForm';
|
|
||||||
import EventPlaceholder from './EventPlaceholder';
|
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({
|
export default function Event({
|
||||||
id,
|
id,
|
||||||
initial,
|
initial,
|
||||||
|
@ -44,8 +34,6 @@ export default function Event({
|
||||||
return <h1>Event Not Found</h1>;
|
return <h1>Event Not Found</h1>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, group } = event;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EventContext.Provider
|
<EventContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
@ -55,16 +43,7 @@ export default function Event({
|
||||||
tentativeInvites,
|
tentativeInvites,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<UISecondaryBox style={{ width: '35rem', maxWidth: '100vw' }}>
|
<EventContent />
|
||||||
<div style={{ textAlign: 'center' }}>
|
|
||||||
<UISecondaryHeader>{name}</UISecondaryHeader>
|
|
||||||
<span>Created by {event.creator.name}</span>
|
|
||||||
<br />
|
|
||||||
{group && <GroupName group={group} />}
|
|
||||||
</div>
|
|
||||||
<EventDetails />
|
|
||||||
<EventInterestForm />
|
|
||||||
</UISecondaryBox>
|
|
||||||
</EventContext.Provider>
|
</EventContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
83
src/components/Event/EventAdminControls.tsx
Normal file
83
src/components/Event/EventAdminControls.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import { useMe } from '../hooks';
|
||||||
import UIButton from '../UI/UIButton';
|
import UIButton from '../UI/UIButton';
|
||||||
import UILink from '../UI/UILink';
|
import UILink from '../UI/UILink';
|
||||||
import EventContext from './EventContext';
|
import EventContext from './EventContext';
|
||||||
import { useMyCarpool } from './EventHooks';
|
import { useCurrentEventCarpool } from './EventHooks';
|
||||||
|
|
||||||
type CreationStatus = null | 'pending' | 'completed' | 'errored';
|
type CreationStatus = null | 'pending' | 'completed' | 'errored';
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ export default function EventCarpoolCreateButton() {
|
||||||
const [createdCarpoolId, setCreatedCarpoolId] = useState<null | number>(null);
|
const [createdCarpoolId, setCreatedCarpoolId] = useState<null | number>(null);
|
||||||
|
|
||||||
const me = useMe() || { id: 0, name: '' };
|
const me = useMe() || { id: 0, name: '' };
|
||||||
const myCarpool = useMyCarpool();
|
const myCarpool = useCurrentEventCarpool();
|
||||||
|
|
||||||
const createCarpoolCallback = useCallback(async () => {
|
const createCarpoolCallback = useCallback(async () => {
|
||||||
setCreationStatus('pending');
|
setCreationStatus('pending');
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { useMe } from '../hooks';
|
||||||
import { IEvent, IEventSignupComplete } from '../types';
|
import { IEvent, IEventSignupComplete } from '../types';
|
||||||
import useOptimalPath from '../useOptimalPath';
|
import useOptimalPath from '../useOptimalPath';
|
||||||
import EventContext from './EventContext';
|
import EventContext from './EventContext';
|
||||||
import { useMySignup } from './EventHooks';
|
import { useCurrentEventSignup } from './EventHooks';
|
||||||
|
|
||||||
function useMemberLocations(members: IEvent['carpools'][0]['members']) {
|
function useMemberLocations(members: IEvent['carpools'][0]['members']) {
|
||||||
const { event } = useContext(EventContext);
|
const { event } = useContext(EventContext);
|
||||||
|
@ -61,7 +61,7 @@ function CarpoolRow({
|
||||||
|
|
||||||
const { event } = useContext(EventContext);
|
const { event } = useContext(EventContext);
|
||||||
|
|
||||||
const mySignup = useMySignup();
|
const mySignup = useCurrentEventSignup();
|
||||||
|
|
||||||
const memberLocations = useMemberLocations(carpool.members);
|
const memberLocations = useMemberLocations(carpool.members);
|
||||||
|
|
||||||
|
@ -145,13 +145,15 @@ export default function Carpools() {
|
||||||
Click <EmojiPeopleIcon style={{ fontSize: '0.875rem' }} /> to request to
|
Click <EmojiPeopleIcon style={{ fontSize: '0.875rem' }} /> to request to
|
||||||
join a carpool.
|
join a carpool.
|
||||||
</span>
|
</span>
|
||||||
{event.carpools.length>0 ? event.carpools.map((carpool) => (
|
{event.carpools.length > 0
|
||||||
<CarpoolRow
|
? event.carpools.map((carpool) => (
|
||||||
carpool={carpool}
|
<CarpoolRow
|
||||||
key={carpool.id}
|
carpool={carpool}
|
||||||
inCarpoolAlready={alreadyInCarpool}
|
key={carpool.id}
|
||||||
/>
|
inCarpoolAlready={alreadyInCarpool}
|
||||||
)) : "No Carpools"}
|
/>
|
||||||
|
))
|
||||||
|
: 'No Carpools'}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
39
src/components/Event/EventContent.tsx
Normal file
39
src/components/Event/EventContent.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -2,7 +2,41 @@ import { useContext, useDebugValue, useMemo } from 'react';
|
||||||
import { useMe } from '../hooks';
|
import { useMe } from '../hooks';
|
||||||
import EventContext from './EventContext';
|
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;
|
const signups = useContext(EventContext).event.signups;
|
||||||
|
|
||||||
useDebugValue(signups);
|
useDebugValue(signups);
|
||||||
|
@ -10,8 +44,8 @@ export function useSignups() {
|
||||||
return signups;
|
return signups;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useMySignup() {
|
export function useCurrentEventSignup() {
|
||||||
const signups = useSignups();
|
const signups = useCurrentEventSignups();
|
||||||
const me = useMe() || { id: 0, name: '' };
|
const me = useMe() || { id: 0, name: '' };
|
||||||
|
|
||||||
const signup = useMemo(() => signups[me.id] ?? null, [signups, me.id]);
|
const signup = useMemo(() => signups[me.id] ?? null, [signups, me.id]);
|
||||||
|
@ -21,7 +55,7 @@ export function useMySignup() {
|
||||||
return signup;
|
return signup;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useMyCarpool() {
|
export function useCurrentEventCarpool() {
|
||||||
const me = useMe() || { id: 0, name: '' };
|
const me = useMe() || { id: 0, name: '' };
|
||||||
const { event } = useContext(EventContext);
|
const { event } = useContext(EventContext);
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,13 @@ import { IEventSignup } from '../types';
|
||||||
import EventCarpoolCreateButton from './EventCarpoolCreateButton';
|
import EventCarpoolCreateButton from './EventCarpoolCreateButton';
|
||||||
import EventContext from './EventContext';
|
import EventContext from './EventContext';
|
||||||
import pickLatLong from './pickLatLong';
|
import pickLatLong from './pickLatLong';
|
||||||
import { useMySignup } from './EventHooks';
|
import { useCurrentEventSignup } from './EventHooks';
|
||||||
|
|
||||||
function EventSignup({ signup }: { signup: IEventSignup }) {
|
function EventSignup({ signup }: { signup: IEventSignup }) {
|
||||||
const { user } = signup;
|
const { user } = signup;
|
||||||
const me = useMe();
|
const me = useMe();
|
||||||
const { tentativeInvites, event } = useContext(EventContext);
|
const { tentativeInvites, event } = useContext(EventContext);
|
||||||
const mySignup = useMySignup();
|
const mySignup = useCurrentEventSignup();
|
||||||
const myLocation = pickLatLong(mySignup);
|
const myLocation = pickLatLong(mySignup);
|
||||||
const theirLocation = pickLatLong(signup);
|
const theirLocation = pickLatLong(signup);
|
||||||
const eventLocation = pickLatLong(event)!;
|
const eventLocation = pickLatLong(event)!;
|
||||||
|
|
|
@ -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[]> {
|
export async function getEvents(): Promise<IEvent[]> {
|
||||||
return await get('/events');
|
return await get('/events');
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,7 @@ export type IEvent = {
|
||||||
name: string;
|
name: string;
|
||||||
}[];
|
}[];
|
||||||
}[];
|
}[];
|
||||||
|
description: string;
|
||||||
signups: Record<string, IEventSignup>;
|
signups: Record<string, IEventSignup>;
|
||||||
startTime: string; // Datestring
|
startTime: string; // Datestring
|
||||||
duration: number;
|
duration: number;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user