mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-07-24 12:11:56 -05:00
104 lines
2.5 KiB
TypeScript
104 lines
2.5 KiB
TypeScript
import { json } from "remix";
|
||
import invariant from "tiny-invariant";
|
||
import { useLocation } from "remix";
|
||
import type { CSSProperties } from "react";
|
||
|
||
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
|
||
)}`;
|
||
}
|
||
|
||
/** Get fields from `request.formData()`. Throw an error if the formData doesn't contain a requested field. */
|
||
export async function formDataFromRequest<T extends string>({
|
||
request,
|
||
fields,
|
||
}: {
|
||
request: Request;
|
||
fields: T[];
|
||
}): Promise<Record<T, string>> {
|
||
const formData = await request.formData();
|
||
let result: Partial<Record<T, string>> = {};
|
||
|
||
for (const field of fields) {
|
||
const value = formData.get(field);
|
||
|
||
invariant(typeof value === "string", `Expected ${field} to be string`);
|
||
|
||
result[field] = value;
|
||
}
|
||
|
||
return result as Record<T, string>;
|
||
}
|
||
|
||
/** @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;
|
||
}
|