Skip to content

Commit 44faa9e

Browse files
[dev] [Itsnotaka] daniel/ui (#1915)
* feat(ui)(sidebar): remove old sidebar components and update dependencies * feat(ui): enhance organization switcher with dropdown menu and sorting options * revert(ui): sheet ui * fix(ui): type fix * fix: tooltip leak --------- Co-authored-by: Daniel Fu <[email protected]>
1 parent dbf2dec commit 44faa9e

File tree

21 files changed

+1555
-814
lines changed

21 files changed

+1555
-814
lines changed

apps/app/src/actions/sidebar.ts

Lines changed: 0 additions & 24 deletions
This file was deleted.

apps/app/src/app/(app)/[orgId]/layout.tsx

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { AnimatedLayout } from '@/components/animated-layout';
1+
import { AppSidebar } from '@/components/app-sidebar';
22
import { CheckoutCompleteDialog } from '@/components/dialogs/checkout-complete-dialog';
33
import { Header } from '@/components/header';
44
import { AssistantSheet } from '@/components/sheets/assistant-sheet';
5-
import { Sidebar } from '@/components/sidebar';
65
import { TriggerTokenProvider } from '@/components/trigger-token-provider';
7-
import { SidebarProvider } from '@/context/sidebar-context';
86
import { auth } from '@/utils/auth';
7+
import { SidebarInset, SidebarProvider } from '@comp/ui/sidebar';
98
import { db, Role } from '@db';
109
import dynamic from 'next/dynamic';
1110
import { cookies, headers } from 'next/headers';
@@ -15,7 +14,6 @@ import { ConditionalOnboardingTracker } from './components/ConditionalOnboarding
1514
import { ConditionalPaddingWrapper } from './components/ConditionalPaddingWrapper';
1615
import { DynamicMinHeight } from './components/DynamicMinHeight';
1716

18-
// Helper to safely parse comma-separated roles string
1917
function parseRolesString(rolesStr: string | null | undefined): Role[] {
2018
if (!rolesStr) return [];
2119
return rolesStr
@@ -38,10 +36,9 @@ export default async function Layout({
3836
const { orgId: requestedOrgId } = await params;
3937

4038
const cookieStore = await cookies();
41-
const isCollapsed = cookieStore.get('sidebar-collapsed')?.value === 'true';
42-
let publicAccessToken = cookieStore.get('publicAccessToken')?.value || undefined;
39+
const defaultOpen = cookieStore.get('sidebar_state')?.value !== 'false';
40+
const publicAccessToken = cookieStore.get('publicAccessToken')?.value || undefined;
4341

44-
// Check if user has access to this organization
4542
const session = await auth.api.getSession({
4643
headers: await headers(),
4744
});
@@ -51,13 +48,11 @@ export default async function Layout({
5148
return redirect('/auth');
5249
}
5350

54-
// First check if the organization exists and load access flags
5551
const organization = await db.organization.findUnique({
5652
where: { id: requestedOrgId },
5753
});
5854

5955
if (!organization) {
60-
// Organization doesn't exist
6156
return redirect('/auth/not-found');
6257
}
6358

@@ -70,7 +65,6 @@ export default async function Layout({
7065
});
7166

7267
if (!member) {
73-
// User doesn't have access to this organization
7468
return redirect('/auth/unauthorized');
7569
}
7670

@@ -82,12 +76,10 @@ export default async function Layout({
8276
return redirect('/no-access');
8377
}
8478

85-
// If this org is not accessible on current plan, redirect to upgrade
8679
if (!organization.hasAccess) {
8780
return redirect(`/upgrade/${organization.id}`);
8881
}
8982

90-
// If onboarding is required and user isn't already on onboarding, redirect
9183
if (!organization.onboardingCompleted) {
9284
return redirect(`/onboarding/${organization.id}`);
9385
}
@@ -103,8 +95,9 @@ export default async function Layout({
10395
triggerJobId={onboarding?.triggerJobId || undefined}
10496
initialToken={publicAccessToken || undefined}
10597
>
106-
<SidebarProvider initialIsCollapsed={isCollapsed}>
107-
<AnimatedLayout sidebar={<Sidebar organization={organization} />} isCollapsed={isCollapsed}>
98+
<SidebarProvider defaultOpen={defaultOpen}>
99+
<AppSidebar organization={organization} />
100+
<SidebarInset>
108101
{onboarding?.triggerJobId && <ConditionalOnboardingTracker onboarding={onboarding} />}
109102
<Header organizationId={organization.id} />
110103
<ConditionalPaddingWrapper>
@@ -114,7 +107,7 @@ export default async function Layout({
114107
<Suspense fallback={null}>
115108
<CheckoutCompleteDialog orgId={organization.id} />
116109
</Suspense>
117-
</AnimatedLayout>
110+
</SidebarInset>
118111
<HotKeys />
119112
</SidebarProvider>
120113
</TriggerTokenProvider>

apps/app/src/app/providers.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { JwtTokenManager } from '@/components/auth/jwt-token-manager';
44
import { env } from '@/env.mjs';
55
import { AnalyticsProvider } from '@comp/analytics';
66
import { Toaster } from '@comp/ui/sooner';
7+
import { TooltipProvider } from '@comp/ui/tooltip';
78
import { GoogleTagManager } from '@next/third-parties/google';
89
import {
910
defaultShouldDehydrateQuery,
@@ -86,9 +87,11 @@ export function Providers({ children, session }: ProviderProps) {
8687
userId={session?.user?.id ?? undefined}
8788
userEmail={session?.user?.email ?? undefined}
8889
>
89-
<JwtTokenManager />
90-
{children}
91-
<Toaster richColors />
90+
<TooltipProvider delayDuration={0}>
91+
<JwtTokenManager />
92+
{children}
93+
<Toaster richColors />
94+
</TooltipProvider>
9295
</AnalyticsProvider>
9396
</ThemeProvider>
9497
</QueryClientProvider>

apps/app/src/components/animated-layout.tsx

Lines changed: 0 additions & 25 deletions
This file was deleted.
Lines changed: 31 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@ import { getOrganizations } from '@/data/getOrganizations';
44
import { auth } from '@/utils/auth';
55
import { GetObjectCommand } from '@aws-sdk/client-s3';
66
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
7-
import { cn } from '@comp/ui/cn';
7+
import {
8+
Sidebar,
9+
SidebarContent,
10+
SidebarFooter,
11+
SidebarHeader,
12+
SidebarRail,
13+
} from '@comp/ui/sidebar';
814
import { db, type Organization, Role } from '@db';
9-
import { cookies, headers } from 'next/headers';
15+
import { headers } from 'next/headers';
1016
import { MainMenu } from './main-menu';
1117
import { OrganizationSwitcher } from './organization-switcher';
1218
import { SidebarCollapseButton } from './sidebar-collapse-button';
1319
import { SidebarLogo } from './sidebar-logo';
1420

15-
// Helper to safely parse comma-separated roles string
1621
function parseRolesString(rolesStr: string | null | undefined): Role[] {
1722
if (!rolesStr) return [];
1823
return rolesStr
@@ -21,18 +26,9 @@ function parseRolesString(rolesStr: string | null | undefined): Role[] {
2126
.filter((r) => r in Role) as Role[];
2227
}
2328

24-
export async function Sidebar({
25-
organization,
26-
collapsed = false,
27-
}: {
28-
organization: Organization | null;
29-
collapsed?: boolean;
30-
}) {
31-
const cookieStore = await cookies();
32-
const isCollapsed = collapsed || cookieStore.get('sidebar-collapsed')?.value === 'true';
29+
export async function AppSidebar({ organization }: { organization?: Organization | null }) {
3330
const { organizations } = await getOrganizations();
3431

35-
// Generate logo URLs for all organizations
3632
const logoUrls: Record<string, string> = {};
3733
if (s3Client && APP_AWS_ORG_ASSETS_BUCKET) {
3834
await Promise.all(
@@ -52,7 +48,6 @@ export async function Sidebar({
5248
);
5349
}
5450

55-
// Check feature flags for menu items
5651
const session = await auth.api.getSession({
5752
headers: await headers(),
5853
});
@@ -78,41 +73,38 @@ export async function Sidebar({
7873
if (member?.role) {
7974
const roles = parseRolesString(member.role);
8075
hasAuditorRole = roles.includes(Role.auditor);
81-
// Only hide tabs if auditor is their ONLY role
82-
// If they have multiple roles (e.g., "owner, auditor" or "admin, auditor"), show tabs
8376
isOnlyAuditor = hasAuditorRole && roles.length === 1;
8477
}
8578
}
8679

8780
return (
88-
<div className="bg-card flex h-full flex-col gap-0 overflow-x-clip">
89-
<div className="flex flex-col gap-2 p-4">
90-
<div className={cn('flex items-center justify-start', isCollapsed && 'justify-center')}>
91-
<SidebarLogo isCollapsed={isCollapsed} />
92-
</div>
93-
<div className="mt-2 flex flex-col gap-2">
81+
<Sidebar collapsible="icon" variant="sidebar" side="left">
82+
<SidebarHeader>
83+
<SidebarLogo />
84+
<div className="mt-2">
9485
<OrganizationSwitcher
9586
organizations={organizations}
96-
organization={organization}
97-
isCollapsed={isCollapsed}
87+
organization={organization ?? null}
9888
logoUrls={logoUrls}
9989
/>
100-
<MainMenu
101-
organizationId={organization?.id ?? ''}
102-
organization={organization}
103-
isCollapsed={isCollapsed}
104-
isQuestionnaireEnabled={isQuestionnaireEnabled}
105-
isTrustNdaEnabled={isTrustNdaEnabled}
106-
hasAuditorRole={hasAuditorRole}
107-
isOnlyAuditor={isOnlyAuditor}
108-
/>
10990
</div>
110-
</div>
111-
<div className="flex-1" />
91+
</SidebarHeader>
92+
93+
<SidebarContent className="px-2">
94+
<MainMenu
95+
organizationId={organization?.id ?? ''}
96+
organization={organization ?? null}
97+
isQuestionnaireEnabled={isQuestionnaireEnabled}
98+
isTrustNdaEnabled={isTrustNdaEnabled}
99+
hasAuditorRole={hasAuditorRole}
100+
isOnlyAuditor={isOnlyAuditor}
101+
/>
102+
</SidebarContent>
112103

113-
<div className="flex justify-center py-2">
114-
<SidebarCollapseButton isCollapsed={isCollapsed} />
115-
</div>
116-
</div>
104+
<SidebarFooter>
105+
<SidebarCollapseButton />
106+
</SidebarFooter>
107+
<SidebarRail />
108+
</Sidebar>
117109
);
118110
}

apps/app/src/components/header.tsx

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
import { getFeatureFlags } from '@/app/posthog';
21
import { UserMenu } from '@/components/user-menu';
3-
import { getOrganizations } from '@/data/getOrganizations';
4-
import { auth } from '@/utils/auth';
52
import { Skeleton } from '@comp/ui/skeleton';
6-
import { headers } from 'next/headers';
3+
import { SidebarTrigger } from '@comp/ui/sidebar';
74
import { Suspense } from 'react';
85
import { AssistantButton } from './ai/chat-button';
9-
import { MobileMenu } from './mobile-menu';
106
import { NotificationBell } from './notifications/notification-bell';
117

128
export async function Header({
@@ -16,29 +12,9 @@ export async function Header({
1612
organizationId?: string;
1713
hideChat?: boolean;
1814
}) {
19-
const { organizations } = await getOrganizations();
20-
21-
// Check feature flags for menu items
22-
const session = await auth.api.getSession({
23-
headers: await headers(),
24-
});
25-
let isQuestionnaireEnabled = false;
26-
let isTrustNdaEnabled = false;
27-
if (session?.user?.id) {
28-
const flags = await getFeatureFlags(session.user.id);
29-
isQuestionnaireEnabled = flags['ai-vendor-questionnaire'] === true;
30-
isTrustNdaEnabled =
31-
flags['is-trust-nda-enabled'] === true || flags['is-trust-nda-enabled'] === 'true';
32-
}
33-
3415
return (
3516
<header className="border/40 sticky top-0 z-10 flex items-center justify-between border-b px-4 py-2 backdrop-blur-sm bg-card">
36-
<MobileMenu
37-
organizations={organizations}
38-
organizationId={organizationId}
39-
isQuestionnaireEnabled={isQuestionnaireEnabled}
40-
isTrustNdaEnabled={isTrustNdaEnabled}
41-
/>
17+
<SidebarTrigger className="md:hidden" />
4218

4319
{!hideChat && <AssistantButton />}
4420

0 commit comments

Comments
 (0)