Files
zeekayandGitHub 5631dbe739 decouple @hanzogui, standardize on 'pkgs/', etc (#228)
* 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.
2026-06-19 15:30:30 -10:00

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("@");
}