Files
zeekay 9d66337dec Show the brand as the Hanzo eSign wordmark alone, under hanzo.ai's bar
The wordmark was an SVG that guessed its own width, and clipped the name
to "Hanzo Sigı". It is now text: NEXT_PUBLIC_APP_NAME, "Hanzo eSign" by
default, measured by the browser and sized by the caller, with no mark in
front of it anywhere. The mobile menu and the public profile header drew
the upstream Documenso logo; the emails and the sealed PDF's certificate
and audit log drew it too, from static/logo.png, which is now the
wordmark set in Zen.

The app header is hanzo.ai's: the wordmark at the left, quiet nav, pill
controls at the right, a hairline under it. A visitor without a session
sees the same bar with the wordmark alone, and a footer with the
copyright and hanzo.ai's terms and privacy policy, on sign-in, the other
unauthenticated pages, a recipient's completion and result pages and
public profiles. The signing pages keep their own bar, which now shows
the wordmark at every width.
2026-10-20 12:47:20 -07:00

122 lines
3.6 KiB
TypeScript

import type { HTMLAttributes } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { motion } from 'framer-motion';
import { AnimatePresence } from 'framer-motion';
import { Search } from 'lucide-react';
import { Link, useLocation } from 'react-router';
import { useSession } from '@hanzo/esign-lib/client-only/providers/session';
import { isPersonalLayout } from '@hanzo/esign-lib/utils/organisations';
import { cn } from '@hanzo/esign-ui/lib/utils';
import { Button } from '@hanzo/esign-ui/primitives/button';
import { useOptionalCurrentTeam } from '~/providers/team';
export type AppNavDesktopProps = HTMLAttributes<HTMLDivElement> & {
setIsCommandMenuOpen: (value: boolean) => void;
};
export const AppNavDesktop = ({
className,
setIsCommandMenuOpen,
...props
}: AppNavDesktopProps) => {
const { _ } = useLingui();
const { organisations } = useSession();
const { pathname } = useLocation();
const [modifierKey, setModifierKey] = useState(() => 'Ctrl');
const currentTeam = useOptionalCurrentTeam();
useEffect(() => {
const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown';
const isMacOS = /Macintosh|Mac\s+OS\s+X/i.test(userAgent);
setModifierKey(isMacOS ? '⌘' : 'Ctrl');
}, []);
const menuNavigationLinks = useMemo(() => {
let teamUrl = currentTeam?.url || null;
if (!teamUrl && isPersonalLayout(organisations)) {
teamUrl = organisations[0].teams[0]?.url || null;
}
if (!teamUrl) {
return [];
}
return [
{
href: `/t/${teamUrl}/documents`,
label: msg`Documents`,
},
{
href: `/t/${teamUrl}/templates`,
label: msg`Templates`,
},
];
}, [currentTeam, organisations]);
return (
<div
className={cn(
'ml-8 hidden flex-1 items-center gap-x-12 md:flex md:justify-between',
className,
)}
{...props}
>
<div>
<AnimatePresence>
{menuNavigationLinks.length > 0 && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="flex items-baseline gap-x-6"
>
{menuNavigationLinks.map(({ href, label }) => (
<Link
key={href}
to={href}
className={cn(
'rounded-full text-[13px] font-medium leading-5 text-muted-foreground ring-offset-background transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
{
'text-foreground': pathname?.startsWith(href),
},
)}
>
{_(label)}
</Link>
))}
</motion.div>
)}
</AnimatePresence>
</div>
<Button
variant="outline"
className="flex h-9 w-full max-w-80 items-center justify-between pl-3 pr-1.5 font-normal text-muted-foreground"
onClick={() => setIsCommandMenuOpen(true)}
>
<div className="flex items-center text-[13px]">
<Search className="mr-2 h-4 w-4" />
<Trans>Search</Trans>
</div>
<div>
<div className="flex items-center rounded-full bg-muted px-2 py-0.5 text-xs tracking-wider text-muted-foreground">
{modifierKey}+K
</div>
</div>
</Button>
</div>
);
};