diff --git a/app/components/Input.tsx b/app/components/Input.tsx
index fb6b3a8b5..87faaf30b 100644
--- a/app/components/Input.tsx
+++ b/app/components/Input.tsx
@@ -2,17 +2,21 @@ import clsx from "clsx";
export function Input({
name,
- leftAddon,
className,
+ minLength,
+ maxLength,
+ leftAddon,
}: {
name: string;
- leftAddon?: string;
className?: string;
+ minLength?: number;
+ maxLength?: number;
+ leftAddon?: string;
}) {
return (
);
}
diff --git a/app/constants.ts b/app/constants.ts
index 246ddd23d..7cfe351b7 100644
--- a/app/constants.ts
+++ b/app/constants.ts
@@ -2,9 +2,16 @@ export const TWEET_LENGTH_MAX_LENGTH = 280;
export const DISCORD_MESSAGE_MAX_LENGTH = 2000;
export const USER_BIO_MAX_LENGTH = DISCORD_MESSAGE_MAX_LENGTH;
+
export const PlUS_SUGGESTION_FIRST_COMMENT_MAX_LENGTH = 500;
export const PlUS_SUGGESTION_COMMENT_MAX_LENGTH = TWEET_LENGTH_MAX_LENGTH;
+
+export const CALENDAR_EVENT_NAME_MIN_LENGTH = 2;
+export const CALENDAR_EVENT_NAME_MAX_LENGTH = 100;
export const CALENDAR_EVENT_DESCRIPTION_MAX_LENGTH = DISCORD_MESSAGE_MAX_LENGTH;
+export const CALENDAR_EVENT_DISCORD_INVITE_CODE_MAX_LENGTH = 50;
+export const CALENDAR_EVENT_BRACKET_URL_MAX_LENGTH = 100;
+export const CALENDAR_EVENT_MAX_AMOUNT_OF_DATES = 5;
export const PLUS_TIERS = [1, 2, 3];
diff --git a/app/routes/calendar/new.tsx b/app/routes/calendar/new.tsx
index ec6257b9c..6439a31f5 100644
--- a/app/routes/calendar/new.tsx
+++ b/app/routes/calendar/new.tsx
@@ -7,9 +7,21 @@ import type {
CalendarEvent,
CalendarEventTag,
} from "~/db/types";
-import { CALENDAR_EVENT_DESCRIPTION_MAX_LENGTH } from "~/constants";
+import {
+ CALENDAR_EVENT_BRACKET_URL_MAX_LENGTH,
+ CALENDAR_EVENT_DESCRIPTION_MAX_LENGTH,
+ CALENDAR_EVENT_DISCORD_INVITE_CODE_MAX_LENGTH,
+ CALENDAR_EVENT_MAX_AMOUNT_OF_DATES,
+ CALENDAR_EVENT_NAME_MAX_LENGTH,
+ CALENDAR_EVENT_NAME_MIN_LENGTH,
+} from "~/constants";
import { Button } from "~/components/Button";
-import { json, type LinksFunction, type LoaderArgs } from "@remix-run/node";
+import {
+ json,
+ type ActionFunction,
+ type LinksFunction,
+ type LoaderArgs,
+} from "@remix-run/node";
import styles from "~/styles/calendar-new.css";
import clsx from "clsx";
import { useTranslation } from "react-i18next";
@@ -22,11 +34,70 @@ import { Tags } from "./components/Tags";
import { db } from "~/db";
import { requireUser } from "~/modules/auth";
import { Badge } from "~/components/Badge";
+import { z } from "zod";
+import {
+ date,
+ falsyToNull,
+ id,
+ processMany,
+ removeDuplicates,
+ safeJSONParse,
+} from "~/utils/zod";
+import { parseRequestFormData } from "~/utils/remix";
+import { dateToYearMonthDayHourMinuteString } from "~/utils/dates";
+
+const MIN_DATE = new Date(Date.UTC(2015, 4, 28));
+
+const MAX_DATE = new Date();
+MAX_DATE.setFullYear(MAX_DATE.getFullYear() + 1);
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: styles }];
};
+const newCalendarEventActionSchema = z.object({
+ name: z
+ .string()
+ .min(CALENDAR_EVENT_NAME_MIN_LENGTH)
+ .max(CALENDAR_EVENT_NAME_MAX_LENGTH),
+ description: z.preprocess(
+ falsyToNull,
+ z.string().max(CALENDAR_EVENT_DESCRIPTION_MAX_LENGTH).nullable()
+ ),
+ dates: z.preprocess(
+ safeJSONParse,
+ z
+ .array(z.preprocess(date, z.date().min(MIN_DATE).max(MAX_DATE)))
+ .min(1)
+ .max(CALENDAR_EVENT_MAX_AMOUNT_OF_DATES)
+ ),
+ bracketUrl: z.string().url().max(CALENDAR_EVENT_BRACKET_URL_MAX_LENGTH),
+ discordInviteCode: z.preprocess(
+ falsyToNull,
+ z.string().max(CALENDAR_EVENT_DISCORD_INVITE_CODE_MAX_LENGTH).nullable()
+ ),
+ tags: z.preprocess(
+ processMany(safeJSONParse, removeDuplicates),
+ z
+ .array(z.string().refine((val) => Object.keys(allTags).includes(val)))
+ .nullable()
+ ),
+ badges: z.preprocess(
+ processMany(safeJSONParse, removeDuplicates),
+ z.array(id).nullable()
+ ),
+});
+
+export const action: ActionFunction = async ({ request }) => {
+ const user = await requireUser(request);
+ const data = await parseRequestFormData({
+ request,
+ schema: newCalendarEventActionSchema,
+ });
+
+ return null;
+};
+
export const handle = {
i18n: "calendar",
};
@@ -64,7 +135,12 @@ function NameInput() {
-
+
);
}
@@ -110,6 +186,13 @@ function DatesInput() {
return (
);
}
@@ -200,6 +294,7 @@ function BracketUrlInput() {
function TagsAdder() {
const { t } = useTranslation("calendar");
const [tags, setTags] = React.useState([]);
+ const id = React.useId();
const tagsForSelect = (
Object.keys(allTags) as Array
@@ -207,10 +302,15 @@ function TagsAdder() {
return (
+
0 ? tags : null)}
+ />
-
+