Scrim range end as select element for better UX

This commit is contained in:
Kalle 2025-11-26 19:15:06 +02:00
parent f33c5970af
commit 4792982cb7
20 changed files with 171 additions and 49 deletions

View File

@ -13,6 +13,7 @@ import {
errorToastIfFalsy,
parseRequestPayload,
} from "~/utils/remix.server";
import { assertUnreachable } from "~/utils/types";
import { scrimsPage } from "~/utils/urls";
import * as QRepository from "../../sendouq/QRepository.server";
import * as TeamRepository from "../../team/TeamRepository.server";
@ -21,6 +22,7 @@ import { SCRIM } from "../scrims-constants";
import {
type fromSchema,
type newRequestSchema,
type RANGE_END_OPTIONS,
scrimsNewActionSchema,
} from "../scrims-schemas";
import { serializeLutiDiv } from "../scrims-utils";
@ -49,9 +51,13 @@ export const action = async ({ request }: ActionFunctionArgs) => {
}
}
const rangeEndDate = data.rangeEnd
? resolveRangeEndToDate(data.at, data.rangeEnd)
: null;
await ScrimPostRepository.insert({
at: dateToDatabaseTimestamp(data.at),
rangeEnd: data.rangeEnd ? dateToDatabaseTimestamp(data.rangeEnd) : null,
rangeEnd: rangeEndDate ? dateToDatabaseTimestamp(rangeEndDate) : null,
maxDiv: data.divs ? serializeLutiDiv(data.divs.max!) : null,
minDiv: data.divs ? serializeLutiDiv(data.divs.min!) : null,
text: data.postText,
@ -185,3 +191,26 @@ async function validatePickupAllUnbanned(userIds: number[]) {
error: "Pickup includes banned users.",
};
}
function resolveRangeEndToDate(
startDate: Date,
rangeEnd: (typeof RANGE_END_OPTIONS)[number],
): Date {
switch (rangeEnd) {
case "+30min":
return add(startDate, { minutes: 30 });
case "+1hour":
return add(startDate, { hours: 1 });
case "+1.5hours":
return add(startDate, { hours: 1, minutes: 30 });
case "+2hours":
return add(startDate, { hours: 2 });
case "+2.5hours":
return add(startDate, { hours: 2, minutes: 30 });
case "+3hours":
return add(startDate, { hours: 3 });
default: {
assertUnreachable(rangeEnd);
}
}
}

View File

@ -22,6 +22,7 @@ const scrimPostsLoader = wrappedLoader<SerializeFrom<typeof loader>>({
const defaultNewScrimPostArgs: Parameters<typeof newScrimAction>[0] = {
at: new Date(),
rangeEnd: null,
baseVisibility: "PUBLIC",
divs: { min: null, max: null },
from: {

View File

@ -21,6 +21,7 @@ import { loader, type ScrimsNewLoaderData } from "../loaders/scrims.new.server";
import { SCRIM } from "../scrims-constants";
import {
MAX_SCRIM_POST_TEXT_LENGTH,
RANGE_END_OPTIONS,
scrimsNewActionSchema,
} from "../scrims-schemas";
export { loader, action };
@ -69,16 +70,27 @@ export default function NewScrimPage() {
<WithFormField usersTeams={data.teams} />
<DateFormField<FormFields>
size="medium"
label={t("scrims:forms.when.title")}
name="at"
bottomText={t("scrims:forms.when.explanation")}
granularity="minute"
/>
<DateFormField<FormFields>
<SelectFormField<FormFields>
size="medium"
label={t("scrims:forms.rangeEnd.title")}
name="rangeEnd"
bottomText={t("scrims:forms.rangeEnd.explanation")}
granularity="minute"
values={[
{
value: "",
label: t("scrims:forms.rangeEnd.notFlexible"),
},
...RANGE_END_OPTIONS.map((option) => ({
value: option,
label: t(`scrims:forms.rangeEnd.${option}`),
})),
]}
/>
<BaseVisibilityFormField associations={data.associations} />

View File

@ -124,6 +124,15 @@ export const scrimsActionSchema = z.union([
export const MAX_SCRIM_POST_TEXT_LENGTH = 500;
export const RANGE_END_OPTIONS = [
"+30min",
"+1hour",
"+1.5hours",
"+2hours",
"+2.5hours",
"+3hours",
] as const;
export const scrimsNewActionSchema = z
.object({
at: z.preprocess(
@ -152,32 +161,11 @@ export const scrimsNewActionSchema = z
),
),
rangeEnd: z
.preprocess(date, z.date())
.nullish()
.refine(
(date) => {
if (!date) return true;
if (date < sub(new Date(), { days: 1 })) return false;
return true;
},
{
message: "Date can not be in the past",
},
.preprocess(
(val) => (val === "" ? null : val),
z.enum(RANGE_END_OPTIONS).nullable(),
)
.refine(
(date) => {
if (!date) return true;
if (date > add(new Date(), { days: 15 })) return false;
return true;
},
{
message: "Date can not be more than 2 weeks in the future",
},
),
.catch(null),
baseVisibility: associationIdentifierSchema,
notFoundVisibility: z.object({
at: z
@ -258,24 +246,4 @@ export const scrimsNewActionSchema = z
code: z.ZodIssueCode.custom,
});
}
if (post.rangeEnd && post.rangeEnd <= post.at) {
ctx.addIssue({
path: ["rangeEnd"],
message: "End time must be after start time",
code: z.ZodIssueCode.custom,
});
}
if (
post.rangeEnd &&
post.rangeEnd.getTime() - post.at.getTime() > SCRIM.MAX_TIME_RANGE_MS
) {
ctx.addIssue({
path: ["rangeEnd"],
message:
"Time range can not be more than 3 hours. Make separate posts instead",
code: z.ZodIssueCode.custom,
});
}
});

View File

@ -48,6 +48,13 @@
"forms.when.explanation": "",
"forms.rangeEnd.title": "",
"forms.rangeEnd.explanation": "",
"forms.rangeEnd.notFlexible": "",
"forms.rangeEnd.+30min": "",
"forms.rangeEnd.+1hour": "",
"forms.rangeEnd.+1.5hours": "",
"forms.rangeEnd.+2hours": "",
"forms.rangeEnd.+2.5hours": "",
"forms.rangeEnd.+3hours": "",
"forms.text.title": "",
"forms.visibility.title": "",
"forms.visibility.public": "",

View File

@ -48,6 +48,13 @@
"forms.when.explanation": "",
"forms.rangeEnd.title": "",
"forms.rangeEnd.explanation": "",
"forms.rangeEnd.notFlexible": "",
"forms.rangeEnd.+30min": "",
"forms.rangeEnd.+1hour": "",
"forms.rangeEnd.+1.5hours": "",
"forms.rangeEnd.+2hours": "",
"forms.rangeEnd.+2.5hours": "",
"forms.rangeEnd.+3hours": "",
"forms.text.title": "Text",
"forms.visibility.title": "Sichtbarkeit",
"forms.visibility.public": "Öffentlich",

View File

@ -46,8 +46,15 @@
"forms.with.pick-up": "Pick-up",
"forms.when.title": "Start",
"forms.when.explanation": "Leave to default if you want to look for a scrim now",
"forms.rangeEnd.title": "Start time range end",
"forms.rangeEnd.title": "Start time flexibility",
"forms.rangeEnd.explanation": "If set, allow requests for any time between start and end",
"forms.rangeEnd.notFlexible": "Not flexible",
"forms.rangeEnd.+30min": "+30 minutes",
"forms.rangeEnd.+1hour": "+1 hour",
"forms.rangeEnd.+1.5hours": "+1.5 hours",
"forms.rangeEnd.+2hours": "+2 hours",
"forms.rangeEnd.+2.5hours": "+2.5 hours",
"forms.rangeEnd.+3hours": "+3 hours",
"forms.text.title": "Text",
"forms.visibility.title": "Visibility",
"forms.visibility.public": "Public",

View File

@ -48,6 +48,13 @@
"forms.when.explanation": "",
"forms.rangeEnd.title": "",
"forms.rangeEnd.explanation": "",
"forms.rangeEnd.notFlexible": "",
"forms.rangeEnd.+30min": "",
"forms.rangeEnd.+1hour": "",
"forms.rangeEnd.+1.5hours": "",
"forms.rangeEnd.+2hours": "",
"forms.rangeEnd.+2.5hours": "",
"forms.rangeEnd.+3hours": "",
"forms.text.title": "",
"forms.visibility.title": "",
"forms.visibility.public": "",

View File

@ -48,6 +48,13 @@
"forms.when.explanation": "",
"forms.rangeEnd.title": "",
"forms.rangeEnd.explanation": "",
"forms.rangeEnd.notFlexible": "",
"forms.rangeEnd.+30min": "",
"forms.rangeEnd.+1hour": "",
"forms.rangeEnd.+1.5hours": "",
"forms.rangeEnd.+2hours": "",
"forms.rangeEnd.+2.5hours": "",
"forms.rangeEnd.+3hours": "",
"forms.text.title": "",
"forms.visibility.title": "",
"forms.visibility.public": "",

View File

@ -48,6 +48,13 @@
"forms.when.explanation": "",
"forms.rangeEnd.title": "",
"forms.rangeEnd.explanation": "",
"forms.rangeEnd.notFlexible": "",
"forms.rangeEnd.+30min": "",
"forms.rangeEnd.+1hour": "",
"forms.rangeEnd.+1.5hours": "",
"forms.rangeEnd.+2hours": "",
"forms.rangeEnd.+2.5hours": "",
"forms.rangeEnd.+3hours": "",
"forms.text.title": "",
"forms.visibility.title": "",
"forms.visibility.public": "",

View File

@ -48,6 +48,13 @@
"forms.when.explanation": "",
"forms.rangeEnd.title": "",
"forms.rangeEnd.explanation": "",
"forms.rangeEnd.notFlexible": "",
"forms.rangeEnd.+30min": "",
"forms.rangeEnd.+1hour": "",
"forms.rangeEnd.+1.5hours": "",
"forms.rangeEnd.+2hours": "",
"forms.rangeEnd.+2.5hours": "",
"forms.rangeEnd.+3hours": "",
"forms.text.title": "Texte",
"forms.visibility.title": "Visibilité",
"forms.visibility.public": "Public",

View File

@ -48,6 +48,13 @@
"forms.when.explanation": "",
"forms.rangeEnd.title": "",
"forms.rangeEnd.explanation": "",
"forms.rangeEnd.notFlexible": "",
"forms.rangeEnd.+30min": "",
"forms.rangeEnd.+1hour": "",
"forms.rangeEnd.+1.5hours": "",
"forms.rangeEnd.+2hours": "",
"forms.rangeEnd.+2.5hours": "",
"forms.rangeEnd.+3hours": "",
"forms.text.title": "",
"forms.visibility.title": "",
"forms.visibility.public": "",

View File

@ -48,6 +48,13 @@
"forms.when.explanation": "",
"forms.rangeEnd.title": "",
"forms.rangeEnd.explanation": "",
"forms.rangeEnd.notFlexible": "",
"forms.rangeEnd.+30min": "",
"forms.rangeEnd.+1hour": "",
"forms.rangeEnd.+1.5hours": "",
"forms.rangeEnd.+2hours": "",
"forms.rangeEnd.+2.5hours": "",
"forms.rangeEnd.+3hours": "",
"forms.text.title": "",
"forms.visibility.title": "",
"forms.visibility.public": "",

View File

@ -48,6 +48,13 @@
"forms.when.explanation": "",
"forms.rangeEnd.title": "",
"forms.rangeEnd.explanation": "",
"forms.rangeEnd.notFlexible": "",
"forms.rangeEnd.+30min": "",
"forms.rangeEnd.+1hour": "",
"forms.rangeEnd.+1.5hours": "",
"forms.rangeEnd.+2hours": "",
"forms.rangeEnd.+2.5hours": "",
"forms.rangeEnd.+3hours": "",
"forms.text.title": "",
"forms.visibility.title": "",
"forms.visibility.public": "",

View File

@ -48,6 +48,13 @@
"forms.when.explanation": "",
"forms.rangeEnd.title": "",
"forms.rangeEnd.explanation": "",
"forms.rangeEnd.notFlexible": "",
"forms.rangeEnd.+30min": "",
"forms.rangeEnd.+1hour": "",
"forms.rangeEnd.+1.5hours": "",
"forms.rangeEnd.+2hours": "",
"forms.rangeEnd.+2.5hours": "",
"forms.rangeEnd.+3hours": "",
"forms.text.title": "",
"forms.visibility.title": "",
"forms.visibility.public": "",

View File

@ -48,6 +48,13 @@
"forms.when.explanation": "",
"forms.rangeEnd.title": "",
"forms.rangeEnd.explanation": "",
"forms.rangeEnd.notFlexible": "",
"forms.rangeEnd.+30min": "",
"forms.rangeEnd.+1hour": "",
"forms.rangeEnd.+1.5hours": "",
"forms.rangeEnd.+2hours": "",
"forms.rangeEnd.+2.5hours": "",
"forms.rangeEnd.+3hours": "",
"forms.text.title": "",
"forms.visibility.title": "",
"forms.visibility.public": "",

View File

@ -48,6 +48,13 @@
"forms.when.explanation": "",
"forms.rangeEnd.title": "",
"forms.rangeEnd.explanation": "",
"forms.rangeEnd.notFlexible": "",
"forms.rangeEnd.+30min": "",
"forms.rangeEnd.+1hour": "",
"forms.rangeEnd.+1.5hours": "",
"forms.rangeEnd.+2hours": "",
"forms.rangeEnd.+2.5hours": "",
"forms.rangeEnd.+3hours": "",
"forms.text.title": "",
"forms.visibility.title": "",
"forms.visibility.public": "",

View File

@ -48,6 +48,13 @@
"forms.when.explanation": "",
"forms.rangeEnd.title": "",
"forms.rangeEnd.explanation": "",
"forms.rangeEnd.notFlexible": "",
"forms.rangeEnd.+30min": "",
"forms.rangeEnd.+1hour": "",
"forms.rangeEnd.+1.5hours": "",
"forms.rangeEnd.+2hours": "",
"forms.rangeEnd.+2.5hours": "",
"forms.rangeEnd.+3hours": "",
"forms.text.title": "",
"forms.visibility.title": "",
"forms.visibility.public": "",

View File

@ -48,6 +48,13 @@
"forms.when.explanation": "",
"forms.rangeEnd.title": "",
"forms.rangeEnd.explanation": "",
"forms.rangeEnd.notFlexible": "",
"forms.rangeEnd.+30min": "",
"forms.rangeEnd.+1hour": "",
"forms.rangeEnd.+1.5hours": "",
"forms.rangeEnd.+2hours": "",
"forms.rangeEnd.+2.5hours": "",
"forms.rangeEnd.+3hours": "",
"forms.text.title": "",
"forms.visibility.title": "",
"forms.visibility.public": "",

View File

@ -48,6 +48,13 @@
"forms.when.explanation": "",
"forms.rangeEnd.title": "",
"forms.rangeEnd.explanation": "",
"forms.rangeEnd.notFlexible": "",
"forms.rangeEnd.+30min": "",
"forms.rangeEnd.+1hour": "",
"forms.rangeEnd.+1.5hours": "",
"forms.rangeEnd.+2hours": "",
"forms.rangeEnd.+2.5hours": "",
"forms.rangeEnd.+3hours": "",
"forms.text.title": "",
"forms.visibility.title": "",
"forms.visibility.public": "",