mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-05 20:56:13 -05:00
* Got something going * Style overwrites * width != height * More playing with lines * Migrations * Start bracket initial * Unhardcode stage generation params * Link to match page * Matches page initial * Support directly adding seed to map list generator * Add docs * Maps in matches page * Add invariant about tie breaker map pool * Fix PICNIC lacking tie breaker maps * Only link in bracket when tournament has started * Styled tournament roster inputs * Prefer IGN in tournament match page * ModeProgressIndicator * Some conditional rendering * Match action initial + better error display * Persist bestOf in DB * Resolve best of ahead of time * Move brackets-manager to core * Score reporting works * Clear winner on score report * ModeProgressIndicator: highlight winners * Fix inconsistent input * Better text when submitting match * mapCountPlayedInSetWithCertainty that works * UNDO_REPORT_SCORE implemented * Permission check when starting tournament * Remove IGN from upsert * View match results page * Source in DB * Match page waiting for teams * Move tournament bracket to feature folder * REOPEN_MATCH initial * Handle proper resetting of match * Inline bracket-manager * Syncify * Transactions * Handle match is locked gracefully * Match page auto refresh * Fix match refresh called "globally" * Bracket autoupdate * Move fillWithNullTillPowerOfTwo to utils with testing * Fix map lists not visible after tournament started * Optimize match events * Show UI while in progress to members * Fix start tournament alert not being responsive * Teams can check in * Fix map list 400 * xxx -> TODO * Seeds page * Remove map icons for team page * Don't display link to seeds after tournament has started * Admin actions initial * Change captain admin action * Make all hooks ts * Admin actions functioning * Fix validate error not displaying in CatchBoundary * Adjust validate args order * Remove admin loader * Make delete team button menancing * Only include checked in teams to bracket * Optimize to.id route loads * Working show map list generator toggle * Update full tournaments flow * Make full tournaments work with many start times * Handle undefined in crud * Dynamic stage banner * Handle default strat if map list generation fails * Fix crash on brackets if less than 2 teams * Add commented out test for reference * Add TODO * Add players from team during register * TrustRelationship * Prefers not to host feature * Last before merge * Rename some vars * More renames
135 lines
3.7 KiB
TypeScript
135 lines
3.7 KiB
TypeScript
import { z } from "zod";
|
|
import { type TFunction, type Namespace } from "react-i18next";
|
|
import { type RouteMatch } from "@remix-run/react";
|
|
import type navItems from "~/components/layout/nav-items.json";
|
|
|
|
export function notFoundIfFalsy<T>(value: T | null | undefined): T {
|
|
if (!value) throw new Response(null, { status: 404 });
|
|
|
|
return value;
|
|
}
|
|
|
|
export function notFoundIfNullLike<T>(value: T | null | undefined): T {
|
|
if (value === null || value === undefined)
|
|
throw new Response(null, { status: 404 });
|
|
|
|
return value;
|
|
}
|
|
|
|
export function badRequestIfFalsy<T>(value: T | null | undefined): T {
|
|
if (!value) throw new Response(null, { status: 400 });
|
|
|
|
return value;
|
|
}
|
|
|
|
/** Parse formData of a request with the given schema. Throws HTTP 400 response if fails. */
|
|
export async function parseRequestFormData<T extends z.ZodTypeAny>({
|
|
request,
|
|
schema,
|
|
}: {
|
|
request: Request;
|
|
schema: T;
|
|
}): Promise<z.infer<T>> {
|
|
try {
|
|
// False alarm
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
return schema.parse(formDataToObject(await request.formData()));
|
|
} catch (e) {
|
|
if (e instanceof z.ZodError) {
|
|
console.error(e);
|
|
throw new Response(JSON.stringify(e), { status: 400 });
|
|
}
|
|
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
export async function safeParseRequestFormData<T extends z.ZodTypeAny>({
|
|
request,
|
|
schema,
|
|
}: {
|
|
request: Request;
|
|
schema: T;
|
|
}): Promise<
|
|
{ success: true; data: z.infer<T> } | { success: false; errors: string[] }
|
|
> {
|
|
const parsed = schema.safeParse(formDataToObject(await request.formData()));
|
|
|
|
// this implementation is somewhat redundant but it's the only way I got types to work nice
|
|
if (!parsed.success) {
|
|
return {
|
|
success: false,
|
|
errors: parsed.error.errors.map((error) => error.message),
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: parsed.data,
|
|
};
|
|
}
|
|
|
|
function formDataToObject(formData: FormData) {
|
|
const result: Record<string, string | string[]> = {};
|
|
|
|
for (const [key, value] of formData.entries()) {
|
|
const newValue = String(value);
|
|
const existingValue = result[key];
|
|
|
|
if (Array.isArray(existingValue)) {
|
|
existingValue.push(newValue);
|
|
} else if (typeof existingValue === "string") {
|
|
result[key] = [existingValue, newValue];
|
|
} else {
|
|
result[key] = newValue;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/** 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,
|
|
message?: string,
|
|
status = 400
|
|
): asserts condition {
|
|
if (condition) return;
|
|
|
|
throw new Response(
|
|
message ? JSON.stringify({ validationError: message }) : undefined,
|
|
{
|
|
status,
|
|
}
|
|
);
|
|
}
|
|
|
|
export type Breadcrumb =
|
|
| { imgPath: string; type: "IMAGE"; href: string }
|
|
| { text: string; type: "TEXT"; href: string };
|
|
|
|
/**
|
|
* Our custom type for route handles - the keys are defined by us or
|
|
* libraries that parse them.
|
|
*
|
|
* Can be set per route using `export const handle: SendouRouteHandle = { };`
|
|
* Can be accessed for all currently active routes via the `useMatches()` hook.
|
|
*/
|
|
export type SendouRouteHandle = {
|
|
/** The i18n translation files used for this route, via remix-i18next */
|
|
i18n?: Namespace;
|
|
|
|
/**
|
|
* A function that returns the breadcrumb text that should be displayed in
|
|
* the <Breadcrumb> component
|
|
*/
|
|
breadcrumb?: (args: {
|
|
match: RouteMatch;
|
|
t: TFunction<"common", undefined>;
|
|
}) => Breadcrumb | Array<Breadcrumb> | undefined;
|
|
|
|
/** The name of a navItem that is active on this route. See nav-items.json */
|
|
navItemName?: (typeof navItems)[number]["name"];
|
|
};
|