import { useMatches } from '@remix-run/react'; import { useMemo } from 'react'; import type { User } from '~/models/user.server'; export const DEFAULT_COLORS = [ 'dark', 'gray', 'red', 'pink', 'grape', 'violet', 'indigo', 'blue', 'cyan', 'green', 'lime', 'yellow', 'orange', 'teal' ]; export const COLORS_MAP: Record = { 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 = '/'; /** * 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') { return defaultRedirect; } if (!to.startsWith('/') || to.startsWith('//')) { 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 | 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'; } export function useOptionalUser(): User | undefined { const data = useMatchesData('root'); 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.' ); } return maybeUser; } export function validateEmail(email: unknown): email is string { return typeof email === 'string' && email.length > 3 && email.includes('@'); }