mirror of
https://github.com/documenso/documenso.git
synced 2025-11-16 09:41:35 +10:00
**Description:** - Updated mobile header with respect to latest designs <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added a new `showText` property to the `MenuSwitcher` component to control text visibility. - Added a `textSectionClassName` property to the `AvatarWithText` component for conditional text section styling. - Updated the `CommandDialog` and `DialogContent` components with new positioning and styling properties. - **Style Updates** - Adjusted text size responsiveness in the `Hero` component for various screen sizes. - Modified text truncation and input styling in the `Widget` component. - Changed the width of the `SheetContent` element in `MobileNavigation` and adjusted footer layout. - **Documentation** - Added instructions for certificate placement in `SIGNING.md`. - **Refactor** - Standardized type imports across various components and utilities for improved type checking. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Adithya Krishna <adithya@documenso.com> Signed-off-by: Adithya Krishna <aadithya794@gmail.com> Co-authored-by: David Nguyen <davidngu28@gmail.com>
234 lines
7.9 KiB
TypeScript
234 lines
7.9 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
import { usePathname } from 'next/navigation';
|
|
|
|
import { CheckCircle2, ChevronsUpDown, Plus, Settings2 } from 'lucide-react';
|
|
import { signOut } from 'next-auth/react';
|
|
|
|
import { TEAM_MEMBER_ROLE_MAP, TEAM_URL_REGEX } from '@documenso/lib/constants/teams';
|
|
import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin';
|
|
import type { GetTeamsResponse } from '@documenso/lib/server-only/team/get-teams';
|
|
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
|
|
import { canExecuteTeamAction } from '@documenso/lib/utils/teams';
|
|
import type { User } from '@documenso/prisma/client';
|
|
import { trpc } from '@documenso/trpc/react';
|
|
import { cn } from '@documenso/ui/lib/utils';
|
|
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
|
|
import { Button } from '@documenso/ui/primitives/button';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from '@documenso/ui/primitives/dropdown-menu';
|
|
|
|
export type MenuSwitcherProps = {
|
|
user: User;
|
|
teams: GetTeamsResponse;
|
|
};
|
|
|
|
export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProps) => {
|
|
const pathname = usePathname();
|
|
|
|
const isUserAdmin = isAdmin(user);
|
|
|
|
const { data: teamsQueryResult } = trpc.team.getTeams.useQuery(undefined, {
|
|
initialData: initialTeamsData,
|
|
});
|
|
|
|
const teams = teamsQueryResult && teamsQueryResult.length > 0 ? teamsQueryResult : null;
|
|
|
|
const isPathTeamUrl = (teamUrl: string) => {
|
|
if (!pathname || !pathname.startsWith(`/t/`)) {
|
|
return false;
|
|
}
|
|
|
|
return pathname.split('/')[2] === teamUrl;
|
|
};
|
|
|
|
const selectedTeam = teams?.find((team) => isPathTeamUrl(team.url));
|
|
|
|
const formatAvatarFallback = (teamName?: string) => {
|
|
if (teamName !== undefined) {
|
|
return teamName.slice(0, 1).toUpperCase();
|
|
}
|
|
|
|
return user.name ? extractInitials(user.name) : user.email.slice(0, 1).toUpperCase();
|
|
};
|
|
|
|
const formatSecondaryAvatarText = (team?: typeof selectedTeam) => {
|
|
if (!team) {
|
|
return 'Personal Account';
|
|
}
|
|
|
|
if (team.ownerUserId === user.id) {
|
|
return 'Owner';
|
|
}
|
|
|
|
return TEAM_MEMBER_ROLE_MAP[team.currentTeamMember.role];
|
|
};
|
|
|
|
/**
|
|
* Formats the redirect URL so we can switch between documents and templates page
|
|
* seemlessly between teams and personal accounts.
|
|
*/
|
|
const formatRedirectUrlOnSwitch = (teamUrl?: string) => {
|
|
const baseUrl = teamUrl ? `/t/${teamUrl}/` : '/';
|
|
|
|
const currentPathname = (pathname ?? '/').replace(TEAM_URL_REGEX, '');
|
|
|
|
if (currentPathname === '/templates') {
|
|
return `${baseUrl}templates`;
|
|
}
|
|
|
|
return baseUrl;
|
|
};
|
|
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button
|
|
data-testid="menu-switcher"
|
|
variant="none"
|
|
className="relative flex h-12 flex-row items-center px-0 py-2 ring-0 focus:outline-none focus-visible:border-0 focus-visible:ring-0 focus-visible:ring-transparent md:px-2"
|
|
>
|
|
<AvatarWithText
|
|
avatarFallback={formatAvatarFallback(selectedTeam?.name)}
|
|
primaryText={selectedTeam ? selectedTeam.name : user.name}
|
|
secondaryText={formatSecondaryAvatarText(selectedTeam)}
|
|
rightSideComponent={
|
|
<ChevronsUpDown className="text-muted-foreground ml-auto h-4 w-4" />
|
|
}
|
|
textSectionClassName="hidden lg:flex"
|
|
/>
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
|
|
<DropdownMenuContent
|
|
className={cn('z-[60] ml-6 w-full md:ml-0', teams ? 'min-w-[20rem]' : 'min-w-[12rem]')}
|
|
align="end"
|
|
forceMount
|
|
>
|
|
{teams ? (
|
|
<>
|
|
<DropdownMenuLabel>Personal</DropdownMenuLabel>
|
|
|
|
<DropdownMenuItem asChild>
|
|
<Link href={formatRedirectUrlOnSwitch()}>
|
|
<AvatarWithText
|
|
avatarFallback={formatAvatarFallback()}
|
|
primaryText={user.name}
|
|
secondaryText={formatSecondaryAvatarText()}
|
|
rightSideComponent={
|
|
!pathname?.startsWith(`/t/`) && (
|
|
<CheckCircle2 className="ml-auto fill-black text-white dark:fill-white dark:text-black" />
|
|
)
|
|
}
|
|
/>
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
|
|
<DropdownMenuSeparator className="mt-2" />
|
|
|
|
<DropdownMenuLabel>
|
|
<div className="flex flex-row items-center justify-between">
|
|
<p>Teams</p>
|
|
|
|
<div className="flex flex-row space-x-2">
|
|
<DropdownMenuItem asChild>
|
|
<Button
|
|
title="Manage teams"
|
|
variant="ghost"
|
|
className="text-muted-foreground flex h-5 w-5 items-center justify-center p-0"
|
|
asChild
|
|
>
|
|
<Link href="/settings/teams">
|
|
<Settings2 className="h-4 w-4" />
|
|
</Link>
|
|
</Button>
|
|
</DropdownMenuItem>
|
|
|
|
<DropdownMenuItem asChild>
|
|
<Button
|
|
title="Create team"
|
|
variant="ghost"
|
|
className="text-muted-foreground flex h-5 w-5 items-center justify-center p-0"
|
|
asChild
|
|
>
|
|
<Link href="/settings/teams?action=add-team">
|
|
<Plus className="h-4 w-4" />
|
|
</Link>
|
|
</Button>
|
|
</DropdownMenuItem>
|
|
</div>
|
|
</div>
|
|
</DropdownMenuLabel>
|
|
|
|
<div className="custom-scrollbar max-h-[40vh] overflow-auto">
|
|
{teams.map((team) => (
|
|
<DropdownMenuItem asChild key={team.id}>
|
|
<Link href={formatRedirectUrlOnSwitch(team.url)}>
|
|
<AvatarWithText
|
|
avatarFallback={formatAvatarFallback(team.name)}
|
|
primaryText={team.name}
|
|
secondaryText={formatSecondaryAvatarText(team)}
|
|
rightSideComponent={
|
|
isPathTeamUrl(team.url) && (
|
|
<CheckCircle2 className="ml-auto fill-black text-white dark:fill-white dark:text-black" />
|
|
)
|
|
}
|
|
/>
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
))}
|
|
</div>
|
|
</>
|
|
) : (
|
|
<DropdownMenuItem className="text-muted-foreground px-4 py-2" asChild>
|
|
<Link
|
|
href="/settings/teams?action=add-team"
|
|
className="flex items-center justify-between"
|
|
>
|
|
Create team
|
|
<Plus className="ml-2 h-4 w-4" />
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
)}
|
|
|
|
<DropdownMenuSeparator />
|
|
|
|
{isUserAdmin && (
|
|
<DropdownMenuItem className="text-muted-foreground px-4 py-2" asChild>
|
|
<Link href="/admin">Admin panel</Link>
|
|
</DropdownMenuItem>
|
|
)}
|
|
|
|
<DropdownMenuItem className="text-muted-foreground px-4 py-2" asChild>
|
|
<Link href="/settings/profile">User settings</Link>
|
|
</DropdownMenuItem>
|
|
|
|
{selectedTeam &&
|
|
canExecuteTeamAction('MANAGE_TEAM', selectedTeam.currentTeamMember.role) && (
|
|
<DropdownMenuItem className="text-muted-foreground px-4 py-2" asChild>
|
|
<Link href={`/t/${selectedTeam.url}/settings/`}>Team settings</Link>
|
|
</DropdownMenuItem>
|
|
)}
|
|
|
|
<DropdownMenuItem
|
|
className="text-destructive/90 hover:!text-destructive px-4 py-2"
|
|
onSelect={async () =>
|
|
signOut({
|
|
callbackUrl: '/',
|
|
})
|
|
}
|
|
>
|
|
Sign Out
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
};
|