mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-30 23:27:55 -05:00
can edit and delete events
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { createRouter } from "pages/api/trpc/[trpc]";
|
||||
import { throwIfNotLoggedIn } from "utils/api";
|
||||
import { eventSchema } from "utils/validators/event";
|
||||
import * as z from "zod";
|
||||
import service from "./service";
|
||||
|
||||
const plusApi = createRouter()
|
||||
@@ -15,5 +16,26 @@ const plusApi = createRouter()
|
||||
const user = throwIfNotLoggedIn(ctx.user);
|
||||
return service.addEvent({ input, userId: user.id });
|
||||
},
|
||||
})
|
||||
.mutation("editEvent", {
|
||||
input: z.object({ event: eventSchema, eventId: z.number() }),
|
||||
resolve({ ctx, input }) {
|
||||
const user = throwIfNotLoggedIn(ctx.user);
|
||||
console.log("input", input);
|
||||
return service.editEvent({
|
||||
...input,
|
||||
userId: user.id,
|
||||
});
|
||||
},
|
||||
})
|
||||
.mutation("deleteEvent", {
|
||||
input: z.object({ eventId: z.number() }),
|
||||
resolve({ ctx, input }) {
|
||||
const user = throwIfNotLoggedIn(ctx.user);
|
||||
return service.deleteEvent({
|
||||
eventId: input.eventId,
|
||||
userId: user.id,
|
||||
});
|
||||
},
|
||||
});
|
||||
export default plusApi;
|
||||
|
||||
@@ -7,12 +7,14 @@ import { useMyTheme } from "hooks/common";
|
||||
import { Fragment, useState } from "react";
|
||||
import { trpc } from "utils/trpc";
|
||||
import EventInfo from "./EventInfo";
|
||||
import { EventModal } from "./EventModal";
|
||||
import { EventModal, FormData } from "./EventModal";
|
||||
|
||||
export default function CalendarPage() {
|
||||
const { gray } = useMyTheme();
|
||||
const events = trpc.useQuery(["calendar.events"], { enabled: false });
|
||||
const [modalIsOpen, setModalIsOpen] = useState(false);
|
||||
const [eventToEdit, setEventToEdit] = useState<
|
||||
boolean | (FormData & { id: number })
|
||||
>(false);
|
||||
const [filter, setFilter] = useState("");
|
||||
|
||||
let lastPrintedDate: [number, number, Date] | null = null;
|
||||
@@ -22,13 +24,13 @@ export default function CalendarPage() {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<Button size="sm" onClick={() => setModalIsOpen(true)}>
|
||||
<Button size="sm" onClick={() => setEventToEdit(true)}>
|
||||
<Trans>Add event</Trans>
|
||||
</Button>
|
||||
{modalIsOpen && (
|
||||
{eventToEdit && (
|
||||
<EventModal
|
||||
onClose={() => setModalIsOpen(false)}
|
||||
event={false}
|
||||
onClose={() => setEventToEdit(false)}
|
||||
event={typeof eventToEdit === "boolean" ? undefined : eventToEdit}
|
||||
refetchQuery={events.refetch}
|
||||
/>
|
||||
)}
|
||||
@@ -67,7 +69,22 @@ export default function CalendarPage() {
|
||||
</Box>
|
||||
)}
|
||||
<div>
|
||||
<EventInfo event={event} />
|
||||
<EventInfo
|
||||
event={event}
|
||||
edit={() =>
|
||||
setEventToEdit({
|
||||
...event,
|
||||
date: event.date.toISOString(),
|
||||
// TODO: remove this if later other event types than tournament are allowed
|
||||
// currently in the validator we accept the properties as if you can only submit
|
||||
// tournaments but database is prepared to accept other kind of events
|
||||
// this makes TS freak out a bit
|
||||
discordInviteUrl: event.discordInviteUrl!,
|
||||
tags: event.tags as any,
|
||||
format: event.format as any,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
@@ -26,12 +26,12 @@ import { EVENT_FORMATS, TAGS } from "../utils";
|
||||
|
||||
interface EventInfoProps {
|
||||
event: Unpacked<Events>;
|
||||
edit: () => void;
|
||||
}
|
||||
|
||||
const TournamentInfo = ({ event }: EventInfoProps) => {
|
||||
const TournamentInfo = ({ event, edit }: EventInfoProps) => {
|
||||
const { secondaryBgColor, gray, themeColorShade } = useMyTheme();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const poster = event.poster;
|
||||
|
||||
const [user] = useUser();
|
||||
@@ -164,7 +164,7 @@ const TournamentInfo = ({ event }: EventInfoProps) => {
|
||||
<Button
|
||||
leftIcon={<FiEdit />}
|
||||
size="sm"
|
||||
onClick={() => setShowModal(true)}
|
||||
onClick={edit}
|
||||
variant="outline"
|
||||
>
|
||||
Edit event
|
||||
|
||||
@@ -30,7 +30,7 @@ import * as z from "zod";
|
||||
import { EVENT_FORMATS } from "../utils";
|
||||
import TagsSelector from "./TagsSelector";
|
||||
|
||||
type FormData = z.infer<typeof eventSchema>;
|
||||
export type FormData = z.infer<typeof eventSchema>;
|
||||
|
||||
export function EventModal({
|
||||
onClose,
|
||||
@@ -38,14 +38,16 @@ export function EventModal({
|
||||
refetchQuery,
|
||||
}: {
|
||||
onClose: () => void;
|
||||
event: boolean;
|
||||
event?: { id: number } & FormData;
|
||||
refetchQuery: () => void;
|
||||
}) {
|
||||
const toast = useToast();
|
||||
const { i18n } = useLingui();
|
||||
const { handleSubmit, errors, register, watch, control } = useForm<FormData>({
|
||||
resolver: zodResolver(eventSchema),
|
||||
defaultValues: event,
|
||||
});
|
||||
|
||||
const addEvent = trpc.useMutation("calendar.addEvent", {
|
||||
onSuccess() {
|
||||
toast(getToastOptions(t`Event added`, "success"));
|
||||
@@ -57,19 +59,39 @@ export function EventModal({
|
||||
toast(getToastOptions(error.message, "error"));
|
||||
},
|
||||
});
|
||||
const editEvent = trpc.useMutation("calendar.editEvent", {
|
||||
onSuccess() {
|
||||
toast(getToastOptions(t`Event updated`, "success"));
|
||||
refetchQuery();
|
||||
onClose();
|
||||
},
|
||||
onError(error) {
|
||||
console.log({ error });
|
||||
toast(getToastOptions(error.message, "error"));
|
||||
},
|
||||
});
|
||||
const deleteEvent = trpc.useMutation("calendar.deleteEvent", {
|
||||
onSuccess() {
|
||||
toast(getToastOptions(t`Event deleted`, "success"));
|
||||
refetchQuery();
|
||||
onClose();
|
||||
},
|
||||
onError(error) {
|
||||
console.log({ error });
|
||||
toast(getToastOptions(error.message, "error"));
|
||||
},
|
||||
});
|
||||
|
||||
const watchDescription = watch("description", /*team.bio*/ "");
|
||||
const watchDescription = watch("description", event?.description ?? "");
|
||||
|
||||
const onSubmit = async (values: FormData) => {
|
||||
console.log("jaa");
|
||||
addEvent.mutate(values);
|
||||
console.log({ values });
|
||||
event
|
||||
? editEvent.mutate({ event: values, eventId: event.id })
|
||||
: addEvent.mutate(values);
|
||||
};
|
||||
|
||||
const onDelete = async () => {
|
||||
console.log("delete");
|
||||
};
|
||||
|
||||
console.log({ errors });
|
||||
const onDelete = (eventId: number) => deleteEvent.mutate({ eventId });
|
||||
|
||||
return (
|
||||
<Modal isOpen onClose={onClose} size="xl" closeOnOverlayClick={false}>
|
||||
@@ -91,9 +113,10 @@ export function EventModal({
|
||||
variant="outline"
|
||||
color="red.500"
|
||||
mb={6}
|
||||
//isLoading={deleting}
|
||||
isLoading={deleteEvent.status === "loading"}
|
||||
onClick={async () => {
|
||||
if (window.confirm(t`Delete the event?`)) await onDelete();
|
||||
if (window.confirm(t`Delete the event?`))
|
||||
onDelete(event.id);
|
||||
}}
|
||||
>
|
||||
<Trans>Delete event</Trans>
|
||||
@@ -128,19 +151,18 @@ export function EventModal({
|
||||
</FormLabel>
|
||||
|
||||
<FormControl isInvalid={!!errors.date}>
|
||||
{/* <Controller
|
||||
<Controller
|
||||
name="date"
|
||||
control={control}
|
||||
defaultValue={new Date()}
|
||||
render={({ onChange, value }) => {
|
||||
return <DatePicker date={value} onChange={onChange} />;
|
||||
}}
|
||||
/> */}
|
||||
<Input
|
||||
type="datetime-local"
|
||||
name="date"
|
||||
ref={register}
|
||||
min={new Date().toISOString()}
|
||||
defaultValue={new Date().toISOString()}
|
||||
render={({ onChange, value }) => (
|
||||
<Input
|
||||
type="datetime-local"
|
||||
value={value.substring(0, 16)}
|
||||
onChange={onChange}
|
||||
min={new Date().toISOString()}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<FormHelperText>
|
||||
<Trans>Input the time in your local time zone:</Trans>{" "}
|
||||
@@ -219,7 +241,10 @@ export function EventModal({
|
||||
<Button
|
||||
mr={3}
|
||||
type="submit"
|
||||
isLoading={addEvent.status === "loading"}
|
||||
isLoading={
|
||||
addEvent.status === "loading" ||
|
||||
editEvent.status === "loading"
|
||||
}
|
||||
>
|
||||
<Trans>Save</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -34,7 +34,7 @@ const TagsSelector: React.FC<TagsSelectorProps> = (props) => {
|
||||
label: i18n._(tag.name),
|
||||
value: tag.code,
|
||||
}))}
|
||||
//value={getValue()}
|
||||
value={props.value?.map((value) => ({ value, label: getLabel(value) }))}
|
||||
setValue={props.setValue}
|
||||
isClearable
|
||||
isMulti
|
||||
@@ -45,6 +45,11 @@ const TagsSelector: React.FC<TagsSelectorProps> = (props) => {
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
function getLabel(value: string) {
|
||||
const tag = TAGS.find(({ code }) => code === value);
|
||||
return tag?.name;
|
||||
}
|
||||
};
|
||||
|
||||
export default TagsSelector;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { httpError } from "@trpc/server";
|
||||
import prisma from "prisma/client";
|
||||
import { ADMIN_ID } from "utils/constants";
|
||||
import { userBasicSelection } from "utils/prisma";
|
||||
import { eventSchema } from "utils/validators/event";
|
||||
import * as z from "zod";
|
||||
|
||||
@@ -7,8 +10,20 @@ export type Events = Prisma.PromiseReturnType<typeof events>;
|
||||
|
||||
const events = () => {
|
||||
return prisma.calendarEvent.findMany({
|
||||
select: {
|
||||
date: true,
|
||||
description: true,
|
||||
discordInviteUrl: true,
|
||||
eventUrl: true,
|
||||
format: true,
|
||||
id: true,
|
||||
name: true,
|
||||
tags: true,
|
||||
poster: {
|
||||
select: userBasicSelection,
|
||||
},
|
||||
},
|
||||
where: { date: { gt: new Date() } },
|
||||
include: { poster: true },
|
||||
orderBy: { date: "asc" },
|
||||
});
|
||||
};
|
||||
@@ -30,7 +45,52 @@ const addEvent = async ({
|
||||
});
|
||||
};
|
||||
|
||||
const editEvent = async ({
|
||||
event,
|
||||
userId,
|
||||
eventId,
|
||||
}: {
|
||||
event: z.infer<typeof eventSchema>;
|
||||
eventId: number;
|
||||
userId: number;
|
||||
}) => {
|
||||
const existingEvent = await prisma.calendarEvent.findFirst({
|
||||
where: { AND: [{ id: eventId }, { posterId: userId }] },
|
||||
});
|
||||
if (!existingEvent || userId === ADMIN_ID) {
|
||||
throw httpError.badRequest("no event to edit");
|
||||
}
|
||||
|
||||
return prisma.calendarEvent.update({
|
||||
where: { id: eventId },
|
||||
data: {
|
||||
...event,
|
||||
isTournament: true,
|
||||
date: new Date(event.date),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const deleteEvent = async ({
|
||||
userId,
|
||||
eventId,
|
||||
}: {
|
||||
eventId: number;
|
||||
userId: number;
|
||||
}) => {
|
||||
const existingEvent = await prisma.calendarEvent.findFirst({
|
||||
where: { AND: [{ id: eventId }, { posterId: userId }] },
|
||||
});
|
||||
if (!existingEvent || userId === ADMIN_ID) {
|
||||
throw httpError.badRequest("no event to delete");
|
||||
}
|
||||
|
||||
return prisma.calendarEvent.delete({ where: { id: eventId } });
|
||||
};
|
||||
|
||||
export default {
|
||||
events,
|
||||
addEvent,
|
||||
editEvent,
|
||||
deleteEvent,
|
||||
};
|
||||
|
||||
@@ -258,7 +258,7 @@ const ProfileModal: React.FC<Props> = ({ onClose, user }) => {
|
||||
name="weaponPool"
|
||||
control={control}
|
||||
defaultValue={[]}
|
||||
render={({ onChange, value, name }) => (
|
||||
render={({ onChange, value }) => (
|
||||
<WeaponSelector
|
||||
value={value}
|
||||
setValue={onChange}
|
||||
|
||||
Reference in New Issue
Block a user