mirror of
https://github.com/hanzoai/shadcn.git
synced 2026-10-30 18:22:59 +00:00
* refactor: decouple @hanzo/ui from hanzogui Make @hanzo/ui shadcn-only — no hanzogui/Tamagui coupling. - remove primitives/bases (gui/admin/svelte/vue) re-exports - drop @hanzogui peerDependencies + peerDependenciesMeta entries - add check-no-hanzogui guard, wired into build - fix latent NodeJS.Timeout types to keep the build green * refactor: remove duplicate @hanzo/brand from the monorepo @hanzo/brand is owned by the standalone hanzoai/brand (the npm-canonical source); this monorepo's copy was unused and had drifted. - delete pkg/brand - add check-no-brand-pkg guard (wired into `check`) so it can't reappear * refactor(ui): stop importing from the app; use own cn util pkg/ui pulled `cn` from @/lib/utils (../../app) — an inverted dependency on the consuming app. Point the animation components at pkg/ui's own cn and drop the @/app, @/registry, @/lib tsconfig path aliases. * fix(ui): type errors; stop tracking the root lockfile - ModelCard: lucide-react dropped the Github icon → use Code - drawer: annotate DrawerTrigger/DrawerClose (radix type portability) - untrack root pnpm-lock.yaml; CI → --no-frozen-lockfile * release: bump @hanzo/ui to 5.7.0 * refactor: rename pkg/ → pkgs/, merge packages/ into it standardize on 'pkgs/': move pkg/* and packages/* into pkgs/. Update workspace globs, CI, scripts, and config references accordingly.
77 lines
2.0 KiB
TypeScript
77 lines
2.0 KiB
TypeScript
import { useMatches } from "@remix-run/react";
|
|
import { useMemo } from "react";
|
|
|
|
import type { User } from "~/models/user.server";
|
|
|
|
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<string, unknown> | undefined {
|
|
const matchingRoutes = useMatches();
|
|
const route = useMemo(
|
|
() => matchingRoutes.find((route) => route.id === id),
|
|
[matchingRoutes, id],
|
|
);
|
|
return route?.data as Record<string, unknown>;
|
|
}
|
|
|
|
function isUser(user: unknown): user is User {
|
|
return (
|
|
user != null &&
|
|
typeof user === "object" &&
|
|
"email" in user &&
|
|
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("@");
|
|
}
|