Files
golunteer/client/src/lib.ts
2025-01-29 19:42:32 +00:00

222 lines
5.0 KiB
TypeScript

import { DateFormatter as IntlDateFormatter } from "@internationalized/date";
import zustand, { User } from "./Zustand";
import { Availability } from "./app/admin/(availabilities)/AvailabilityEditor";
export type AllString<T> = { [K in keyof T]: string };
type QueryParams = Record<string, string | { toString(): string }>;
type Body = object | string | number | boolean;
export type APICallResult<T> = Omit<Response, "json"> & {
json: () => Promise<T>;
};
export async function apiCall<K>(
method: "GET",
api: string,
query?: QueryParams,
): Promise<APICallResult<K>>;
export async function apiCall<K>(
method: "POST" | "PATCH" | "PUT",
api: string,
query?: QueryParams,
body?: Body,
): Promise<APICallResult<K>>;
export async function apiCall<K>(
method: "DELETE",
api: string,
query?: QueryParams,
body?: Body,
): Promise<APICallResult<K>>;
export async function apiCall<K>(
method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE",
api: string,
query?: QueryParams,
body?: Body,
): Promise<APICallResult<K>> {
let url = window.origin + "/api/" + api;
if (query) {
const urlsearchparams = new URLSearchParams(
Object.fromEntries(
Object.entries(query).map(([key, value]): [string, string] => {
if (typeof value !== "string") {
return [key, value.toString()];
} else {
return [key, value];
}
}),
),
);
url += "?" + urlsearchparams.toString();
}
const response = await fetch(url, {
headers: {
"Content-Type": `${getContentType(typeof body)}; charset=UTF-8`,
},
credentials: "include",
method,
body: prepareBody(body),
});
return response;
}
function getContentType(type: string): string {
switch (type) {
case "object":
return "application/json";
case "string":
case "number":
case "bigint":
case "boolean":
return "text/plain";
default:
return "application/octet-stream";
}
}
function prepareBody(
body: object | number | string | boolean | undefined,
): BodyInit | undefined {
switch (typeof body) {
case "object":
return JSON.stringify(body);
case "undefined":
return undefined;
default:
return body.toString();
}
}
export function classNames(...classNames: (string | undefined)[]): string;
export function classNames(classNames: string[]): string;
export function classNames(classNames: Record<string, boolean>): string;
export function classNames(
classNames: string[] | Record<string, boolean> | string | undefined,
...rest: (string | undefined)[]
) {
// if rest isn't undefined, use the rest values
if (rest !== undefined) {
return [classNames, ...rest].join(" ");
// if classnames is undefined too, return an empty string
} else if (classNames === undefined) {
return "";
// if classnames is an array, join it
} else if (Array.isArray(classNames)) {
return classNames.join(" ");
// if classnames is an object, join it based on the value
} else {
return Object.entries(classNames)
.filter(([, value]) => value)
.map(([classString]) => classString)
.join(" ");
}
}
export class DateFormatter {
private formatter;
constructor(locale: string, options?: Intl.DateTimeFormatOptions) {
this.formatter = new IntlDateFormatter(locale, options);
}
format(dt: Date) {
return this.formatter.format(dt);
}
}
export function validatePassword(password: string): string[] {
const errors = [];
if (password.length < 12) {
errors.push("Password must be 12 characters or more");
} else if (password.length > 64) {
errors.push("Password must be 64 characters or short");
}
return errors;
}
export interface Task {
taskID: number | undefined;
taskName: string;
enabled: boolean;
}
export async function getTask(name: string): Promise<Task | undefined> {
// get the tasks
const tasks = await getTasks();
return tasks.find((t) => t.taskName === name);
}
export async function getTasks(): Promise<Task[]> {
// check wether it is cached in zustand
const state = zustand.getState();
if (!!state.tasks) {
return state.tasks;
} else {
const result = await apiCall<Task[]>("GET", "tasks");
if (result.ok) {
const tasks = (await result.json()) as Task[];
state.patch({ tasks: tasks });
return tasks;
} else {
return [];
}
}
}
export async function getAvailabilities(): Promise<Availability[]> {
// check wether it is cached in zustand
const state = zustand.getState();
if (!!state.availabilities) {
return state.availabilities;
} else {
const result = await apiCall<Availability[]>("GET", "availabilities");
if (result.ok) {
const availabilities = await result.json();
state.patch({ availabilities });
return availabilities;
} else {
return [];
}
}
}
export async function getUsers(): Promise<User[]> {
// check wether it is cached in zustand
const state = zustand.getState();
if (!!state.users) {
return state.users;
} else {
const result = await apiCall<User[]>("GET", "users");
if (result.ok) {
const users = await result.json();
state.patch({ users });
return users;
} else {
return [];
}
}
}