work-timer/app/utils.ts

114 lines
2.7 KiB
TypeScript
Raw Permalink Normal View History

import { useMatches } from '@remix-run/react';
import { useMemo } from 'react';
2023-02-11 03:14:14 +01:00
import type { User } from '~/models/user.server';
2023-02-11 03:14:14 +01:00
export const DEFAULT_COLORS = [
'dark',
'gray',
'red',
'pink',
'grape',
'violet',
'indigo',
'blue',
'cyan',
'green',
'lime',
'yellow',
'orange',
'teal'
];
export const COLORS_MAP: Record<string, string> = {
dark: '#25262b',
gray: '#868e96',
red: '#fa5252',
pink: '#e64980',
grape: '#be4bdb',
violet: '#7950f2',
indigo: '#4c6ef5',
blue: '#228be6',
cyan: '#15aabf',
green: '#12b886',
lime: '#40c057',
yellow: '#82c91e',
orange: '#fab005',
teal: '#fd7e14'
};
export const randomColorName = () =>
DEFAULT_COLORS[Math.floor(Math.random() * DEFAULT_COLORS.length)];
export const randomColor = () => {
const colorName = randomColorName();
return {
name: colorName,
hex: COLORS_MAP[colorName]
};
};
const DEFAULT_REDIRECT = '/';
2023-02-11 03:14:14 +01:00
/**
* This should be used any time the redirect path is user-provided
* (Like the query string on our login/signup pages). This avoids
* open-redirect vulnerabilities.
* @param {string} to The redirect destination
* @param {string} defaultRedirect The redirect to use if the to is unsafe.
*/
export function safeRedirect(
to: FormDataEntryValue | string | null | undefined,
defaultRedirect: string = DEFAULT_REDIRECT
) {
if (!to || typeof to !== 'string') {
2023-02-11 03:14:14 +01:00
return defaultRedirect;
}
if (!to.startsWith('/') || to.startsWith('//')) {
2023-02-11 03:14:14 +01:00
return defaultRedirect;
}
return to;
}
/**
* This base hook is used in other hooks to quickly search for specific data
* across all loader data using useMatches.
* @param {string} id The route id
* @returns {JSON|undefined} The router data or undefined if not found
*/
export function useMatchesData(
id: string
): Record<string, unknown> | undefined {
const matchingRoutes = useMatches();
const route = useMemo(
() => matchingRoutes.find((route) => route.id === id),
[matchingRoutes, id]
);
return route?.data;
}
function isUser(user: any): user is User {
return user && typeof user === 'object' && typeof user.email === 'string';
2023-02-11 03:14:14 +01:00
}
export function useOptionalUser(): User | undefined {
const data = useMatchesData('root');
2023-02-11 03:14:14 +01:00
if (!data || !isUser(data.user)) {
return undefined;
}
return data.user;
}
export function useUser(): User {
const maybeUser = useOptionalUser();
if (!maybeUser) {
throw new Error(
'No user found in root loader, but user is required by useUser. If user is optional, try useOptionalUser instead.'
2023-02-11 03:14:14 +01:00
);
}
return maybeUser;
}
export function validateEmail(email: unknown): email is string {
return typeof email === 'string' && email.length > 3 && email.includes('@');
2023-02-11 03:14:14 +01:00
}