mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-09 12:13:10 -05:00
122 lines
2.9 KiB
TypeScript
122 lines
2.9 KiB
TypeScript
import { Mode } from "@prisma/client";
|
|
import type { CSSProperties } from "react";
|
|
import { json, useLocation } from "remix";
|
|
import { z } from "zod";
|
|
|
|
export function makeTitle(endOfTitle?: string) {
|
|
return endOfTitle ? `sendou.ink | ${endOfTitle}` : "sendou.ink";
|
|
}
|
|
|
|
/** Get logged in user from context. Throws with 401 error if no user found. */
|
|
export function requireUser(ctx: any) {
|
|
const user = ctx.user;
|
|
|
|
if (!user) {
|
|
throw json("Log in required", { status: 401 });
|
|
}
|
|
|
|
return user as NonNullable<LoggedInUser>;
|
|
}
|
|
|
|
/** Get logged in user from context. Doesn't throw. */
|
|
export function getUser(ctx: any) {
|
|
const user = ctx.user;
|
|
|
|
return user as LoggedInUser;
|
|
}
|
|
|
|
/** Get link to log in with query param set as current page */
|
|
export function getLogInUrl(location: ReturnType<typeof useLocation>) {
|
|
return `/auth/discord?origin=${encodeURIComponent(
|
|
location.pathname + location.search
|
|
)}`;
|
|
}
|
|
|
|
export function stageNameToImageUrl(name: string) {
|
|
return `/img/stages/${name.replaceAll(" ", "-").toLowerCase()}.webp`;
|
|
}
|
|
|
|
export function modeToImageUrl(mode: Mode) {
|
|
return `/img/modes/${mode}.webp`;
|
|
}
|
|
|
|
/** 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 {
|
|
return schema.parse(Object.fromEntries(await request.formData()));
|
|
} catch (e) {
|
|
if (e instanceof z.ZodError) {
|
|
console.error(e);
|
|
let errorMessage = "Request had following issues: ";
|
|
for (const issue of e.issues) {
|
|
errorMessage += `${issue.message} (path: ${issue.path.join(",")});`;
|
|
}
|
|
throw new Response(errorMessage, { status: 400 });
|
|
}
|
|
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
export function safeJSONParse(value: any) {
|
|
try {
|
|
return JSON.parse(value);
|
|
} catch (e) {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
/** @link https://stackoverflow.com/a/69413184 */
|
|
// @ts-expect-error
|
|
export const assertType = <A, B extends A>() => {};
|
|
|
|
export type LoggedInUser = {
|
|
id: string;
|
|
discordId: string;
|
|
discordAvatar: string;
|
|
} | null;
|
|
|
|
export type Serialized<T> = {
|
|
[P in keyof T]: T[P] extends Date
|
|
? string
|
|
: T[P] extends Date | null
|
|
? string | null
|
|
: Serialized<T[P]>;
|
|
};
|
|
|
|
export type Unpacked<T> = T extends (infer U)[]
|
|
? U
|
|
: T extends (...args: any[]) => infer U
|
|
? U
|
|
: T extends Promise<infer U>
|
|
? U
|
|
: T;
|
|
|
|
export type MyReducerAction<
|
|
K extends string,
|
|
T extends {} | undefined = undefined
|
|
> = T extends {}
|
|
? {
|
|
type: K;
|
|
data: T;
|
|
}
|
|
: { type: K };
|
|
|
|
export interface MyCSSProperties extends CSSProperties {
|
|
"--tournaments-bg"?: string;
|
|
"--tournaments-text"?: string;
|
|
"--tournaments-text-transparent"?: string;
|
|
"--action-section-icon-color"?: string;
|
|
"--brackets-columns"?: number;
|
|
"--brackets-max-matches"?: number;
|
|
"--brackets-bottom-border-length"?: number;
|
|
"--brackets-column-matches"?: number;
|
|
"--tabs-count"?: number;
|
|
}
|