Edit event full flow

This commit is contained in:
Kalle 2022-07-31 14:16:03 +03:00
parent beb8e73436
commit 7d7f771c19
8 changed files with 54 additions and 18 deletions

View File

@ -9,8 +9,8 @@ Calendar
- [x] Implement tags
- [x] Add initial tags.json
- [x] Add tags to the seed
- [ ] Page to add a new event
- [ ] Edit page
- [x] Page to add a new event
- [x] Edit page
- [ ] E2E test form controls (adding/removing date, adding/removing tags) etc.
- [ ] E2E test browsing events + event page
- [ ] E2E test adding and editing event
@ -27,7 +27,7 @@ Calendar
- [x] Calendar new title
- [x] Sort tags so that they are always in same order
- [x] Maybe make bracket URL mandatory?
- [ ] components render twice on state change?
- [x] components render twice on state change?
- [ ] If week has no events show some text + don't show time zone text
- [x] flatMap -> React.fragment
- [x] Event page layout shift with time not taking space before mount (only if one day)

View File

@ -1,6 +1,11 @@
import type * as plusSuggestions from "~/db/models/plusSuggestions.server";
import { monthsVotingRange } from "./modules/plus-server";
import type { PlusSuggestion, User, UserWithPlusTier } from "./db/types";
import type {
CalendarEvent,
PlusSuggestion,
User,
UserWithPlusTier,
} from "./db/types";
import { allTruthy } from "./utils/arrays";
import { ADMIN_DISCORD_ID, LOHI_TOKEN_HEADER_NAME } from "./constants";
import invariant from "tiny-invariant";
@ -254,3 +259,14 @@ function isBadgeManager({
export function canEditBadgeManagers(user?: IsAdminUser) {
return isAdmin(user);
}
interface CanEditCalendarEventArgs {
user?: Pick<User, "id" | "discordId">;
event: Pick<CalendarEvent, "authorId">;
}
export function canEditCalendarEvent({
user,
event,
}: CanEditCalendarEventArgs) {
return adminOverride(user)(user?.id === event.authorId);
}

View File

@ -16,12 +16,14 @@ import { LinkButton } from "~/components/Button";
import { Main } from "~/components/Main";
import { db } from "~/db";
import { useIsMounted } from "~/hooks/useIsMounted";
import { useUser } from "~/modules/auth";
import { i18next } from "~/modules/i18n";
import { canEditCalendarEvent } from "~/permissions";
import styles from "~/styles/calendar-event.css";
import { databaseTimestampToDate } from "~/utils/dates";
import { notFoundIfFalsy } from "~/utils/remix";
import { discordFullName, makeTitle } from "~/utils/strings";
import { resolveBaseUrl } from "~/utils/urls";
import { calendarEditPage, resolveBaseUrl } from "~/utils/urls";
import { actualNumber, id } from "~/utils/zod";
import { Tags } from "./components/Tags";
import allTags from "./tags.json";
@ -60,12 +62,13 @@ export const loader = async ({ params, request }: LoaderArgs) => {
};
export default function CalendarEventPage() {
const user = useUser();
const { event } = useLoaderData<typeof loader>();
const { i18n } = useTranslation();
const isMounted = useIsMounted();
return (
<Main className="stack lg">
<Main className="stack md">
<section className="stack sm">
<div className="event__times">
{event.startTimes.map((startTime, i) => (
@ -99,7 +102,7 @@ export default function CalendarEventPage() {
<h2>{event.name}</h2>
<Tags tags={event.tags} />
</div>
<div className="stack horizontal sm">
<div className="stack horizontal sm flex-wrap">
{event.discordUrl ? (
<LinkButton
to={event.discordUrl}
@ -118,6 +121,11 @@ export default function CalendarEventPage() {
>
{resolveBaseUrl(event.bracketUrl)}
</LinkButton>
{canEditCalendarEvent({ user, event }) && (
<LinkButton tiny to={calendarEditPage(event.eventId)}>
Edit
</LinkButton>
)}
</div>
</div>
</section>

View File

@ -1,3 +1,4 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { Button } from "~/components/Button";
import { CrossIcon } from "~/components/icons/Cross";
@ -19,8 +20,8 @@ export function Tags({
return (
<ul className="calendar__event__tags">
{tags.map((tag) => (
<>
<li key={tag} style={{ backgroundColor: allTags[tag].color }}>
<React.Fragment key={tag}>
<li style={{ backgroundColor: allTags[tag].color }}>
{t(`tag.name.${tag}`)}
{onDelete && (
<Button
@ -33,7 +34,7 @@ export function Tags({
/>
)}
</li>
</>
</React.Fragment>
))}
</ul>
);

View File

@ -42,7 +42,11 @@ import {
removeDuplicates,
safeJSONParse,
} from "~/utils/zod";
import { parseRequestFormData } from "~/utils/remix";
import {
badRequestIfFalsy,
parseRequestFormData,
validate,
} from "~/utils/remix";
import {
databaseTimestampToDate,
dateToDatabaseTimestamp,
@ -52,6 +56,7 @@ import { calendarEventPage } from "~/utils/urls";
import { makeTitle } from "~/utils/strings";
import { i18next } from "~/modules/i18n";
import type { UseDataFunctionReturn } from "@remix-run/react/dist/components";
import { canEditCalendarEvent } from "~/permissions";
const MIN_DATE = new Date(Date.UTC(2015, 4, 28));
@ -137,10 +142,10 @@ export const action: ActionFunction = async ({ request }) => {
badges: data.badges ?? [],
};
if (data.eventToEditId) {
const eventToEdit = db.calendarEvents.findById(data.eventToEditId);
if (eventToEdit?.authorId !== user.id) {
throw new Response(null, { status: 401 });
}
const eventToEdit = badRequestIfFalsy(
db.calendarEvents.findById(data.eventToEditId)
);
validate(canEditCalendarEvent({ user, event: eventToEdit }), 401);
db.calendarEvents.update({
eventId: data.eventToEditId,

View File

@ -74,6 +74,10 @@
justify-content: center;
}
.flex-wrap {
flex-wrap: wrap;
}
.all-unset {
all: unset;
}

View File

@ -38,10 +38,10 @@ export async function parseRequestFormData<T extends z.ZodTypeAny>({
}
}
/** Asserts condition is truthy. Throws a new `Response` with status code 400 and given message if falsy. */
/** Asserts condition is truthy. Throws a new `Response` with given status code if falsy. */
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- same format as TS docs: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions
export function validate(condition: any): asserts condition {
export function validate(condition: any, status = 400): asserts condition {
if (condition) return;
throw new Response(null, { status: 400 });
throw new Response(null, { status });
}

View File

@ -25,6 +25,8 @@ export const badgePage = (badgeId: number) => `${BADGES_PAGE}/${badgeId}`;
export const plusSuggestionPage = (tier?: string | number) =>
`/plus/suggestions${tier ? `?tier=${tier}` : ""}`;
export const calendarEventPage = (eventId: number) => `/calendar/${eventId}`;
export const calendarEditPage = (eventId: number) =>
`/calendar/new?eventId=${eventId}`;
export const badgeUrl = ({
code,