mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-02-11 05:59:15 +00:00
initial commit
This commit is contained in:
13
frontend/src/lib/utils/debounce-util.ts
Normal file
13
frontend/src/lib/utils/debounce-util.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export function debounced<T extends (...args: any[]) => void>(func: T, delay: number) {
|
||||
let debounceTimeout: number | undefined;
|
||||
|
||||
return (...args: Parameters<T>) => {
|
||||
if (debounceTimeout !== undefined) {
|
||||
clearTimeout(debounceTimeout);
|
||||
}
|
||||
|
||||
debounceTimeout = setTimeout(() => {
|
||||
func(...args);
|
||||
}, delay);
|
||||
};
|
||||
}
|
||||
36
frontend/src/lib/utils/error-util.ts
Normal file
36
frontend/src/lib/utils/error-util.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { WebAuthnError } from '@simplewebauthn/browser';
|
||||
import { AxiosError } from 'axios';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
export function axiosErrorToast(e: unknown, message: string = 'An unknown error occurred') {
|
||||
if (e instanceof AxiosError) {
|
||||
message = e.response?.data.error || message;
|
||||
}
|
||||
toast.error(message);
|
||||
}
|
||||
|
||||
export function getWebauthnErrorMessage(e: unknown) {
|
||||
const errors = {
|
||||
ERROR_CEREMONY_ABORTED: 'The authentication process was aborted',
|
||||
ERROR_AUTHENTICATOR_GENERAL_ERROR: 'An error occurred with the authenticator',
|
||||
ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT:
|
||||
'The authenticator does not support discoverable credentials',
|
||||
ERROR_AUTHENTICATOR_MISSING_RESIDENT_KEY_SUPPORT:
|
||||
'The authenticator does not support resident keys',
|
||||
ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED: 'This passkey was previously registered',
|
||||
ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG:
|
||||
'The authenticator does not support any of the requested algorithms'
|
||||
};
|
||||
|
||||
let message = 'An unknown error occurred';
|
||||
if (e instanceof WebAuthnError && e.code in errors) {
|
||||
message = errors[e.code as keyof typeof errors];
|
||||
} else if (e instanceof WebAuthnError && e?.message.includes('timed out')) {
|
||||
message = 'The authenticator timed out';
|
||||
} else if (e instanceof AxiosError && e.response?.data.error) {
|
||||
message = e.response?.data.error;
|
||||
} else {
|
||||
console.error(e);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
90
frontend/src/lib/utils/form-util.ts
Normal file
90
frontend/src/lib/utils/form-util.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type FormInput<T> = {
|
||||
value: T;
|
||||
error: string | null;
|
||||
};
|
||||
|
||||
type FormInputs<T> = {
|
||||
[K in keyof T]: FormInput<T[K]>;
|
||||
};
|
||||
|
||||
export function createForm<T extends z.ZodType<any, any>>(schema: T, initialValues: z.infer<T>) {
|
||||
// Create a writable store for the inputs
|
||||
const inputsStore = writable<FormInputs<z.infer<T>>>(initializeInputs(initialValues));
|
||||
|
||||
function initializeInputs(initialValues: z.infer<T>): FormInputs<z.infer<T>> {
|
||||
const inputs: FormInputs<z.infer<T>> = {} as FormInputs<z.infer<T>>;
|
||||
for (const key in initialValues) {
|
||||
if (Object.prototype.hasOwnProperty.call(initialValues, key)) {
|
||||
inputs[key as keyof z.infer<T>] = {
|
||||
value: initialValues[key as keyof z.infer<T>],
|
||||
error: null
|
||||
};
|
||||
}
|
||||
}
|
||||
return inputs;
|
||||
}
|
||||
|
||||
function validate() {
|
||||
let success = true;
|
||||
inputsStore.update((inputs) => {
|
||||
// Extract values from inputs to validate against the schema
|
||||
const values = Object.fromEntries(
|
||||
Object.entries(inputs).map(([key, input]) => [key, input.value])
|
||||
);
|
||||
|
||||
const result = schema.safeParse(values);
|
||||
|
||||
if (!result.success) {
|
||||
success = false;
|
||||
for (const input of Object.keys(inputs)) {
|
||||
const error = result.error.errors.find((e) => e.path[0] === input);
|
||||
if (error) {
|
||||
inputs[input as keyof z.infer<T>].error = error.message;
|
||||
} else {
|
||||
inputs[input as keyof z.infer<T>].error = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const input of Object.keys(inputs)) {
|
||||
inputs[input as keyof z.infer<T>].error = null;
|
||||
}
|
||||
}
|
||||
return inputs;
|
||||
});
|
||||
return success ? data() : null;
|
||||
}
|
||||
|
||||
function data() {
|
||||
let values: z.infer<T> | null = null;
|
||||
inputsStore.subscribe((inputs) => {
|
||||
values = Object.fromEntries(
|
||||
Object.entries(inputs).map(([key, input]) => [key, input.value])
|
||||
) as z.infer<T>;
|
||||
})();
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
function reset() {
|
||||
inputsStore.update((inputs) => {
|
||||
for (const input of Object.keys(inputs)) {
|
||||
inputs[input as keyof z.infer<T>] = {
|
||||
value: initialValues[input as keyof z.infer<T>],
|
||||
error: null
|
||||
};
|
||||
}
|
||||
return inputs;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
schema,
|
||||
inputs: inputsStore,
|
||||
data,
|
||||
validate,
|
||||
reset
|
||||
};
|
||||
}
|
||||
62
frontend/src/lib/utils/style.ts
Normal file
62
frontend/src/lib/utils/style.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { cubicOut } from "svelte/easing";
|
||||
import type { TransitionConfig } from "svelte/transition";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
type FlyAndScaleParams = {
|
||||
y?: number;
|
||||
x?: number;
|
||||
start?: number;
|
||||
duration?: number;
|
||||
};
|
||||
|
||||
export const flyAndScale = (
|
||||
node: Element,
|
||||
params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 }
|
||||
): TransitionConfig => {
|
||||
const style = getComputedStyle(node);
|
||||
const transform = style.transform === "none" ? "" : style.transform;
|
||||
|
||||
const scaleConversion = (
|
||||
valueA: number,
|
||||
scaleA: [number, number],
|
||||
scaleB: [number, number]
|
||||
) => {
|
||||
const [minA, maxA] = scaleA;
|
||||
const [minB, maxB] = scaleB;
|
||||
|
||||
const percentage = (valueA - minA) / (maxA - minA);
|
||||
const valueB = percentage * (maxB - minB) + minB;
|
||||
|
||||
return valueB;
|
||||
};
|
||||
|
||||
const styleToString = (
|
||||
style: Record<string, number | string | undefined>
|
||||
): string => {
|
||||
return Object.keys(style).reduce((str, key) => {
|
||||
if (style[key] === undefined) return str;
|
||||
return str + `${key}:${style[key]};`;
|
||||
}, "");
|
||||
};
|
||||
|
||||
return {
|
||||
duration: params.duration ?? 200,
|
||||
delay: 0,
|
||||
css: (t) => {
|
||||
const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]);
|
||||
const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]);
|
||||
const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]);
|
||||
|
||||
return styleToString({
|
||||
transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`,
|
||||
opacity: t
|
||||
});
|
||||
},
|
||||
easing: cubicOut
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user