From 4a0697c09f9828bcbbebe53dbb0178937d53bc38 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Sat, 17 May 2025 14:08:06 -0700 Subject: [PATCH] Dry up drawers (#27) --- src/actions/auditLogs.ts | 4 +- .../(authenticated)/audit-logs/AuditLogs.tsx | 48 +++---- src/app/(authenticated)/usage/TaskDetails.tsx | 135 +++++++++--------- src/components/audit-logs/AuditLogCard.tsx | 49 +++---- src/components/audit-logs/AuditLogDetails.tsx | 4 +- src/components/audit-logs/AuditLogDrawer.tsx | 31 ++++ src/components/audit-logs/AuditLogEntry.tsx | 34 ++--- src/components/audit-logs/index.ts | 1 + src/components/layout/Logo.tsx | 9 +- src/components/layout/ThemeSwitcher.tsx | 10 +- src/components/ui/skeleton.tsx | 6 +- src/components/usage/Metric.tsx | 8 +- src/components/usage/UsageCard.tsx | 26 ++-- src/db/schema.ts | 4 +- 14 files changed, 195 insertions(+), 174 deletions(-) create mode 100644 src/components/audit-logs/AuditLogDrawer.tsx diff --git a/src/actions/auditLogs.ts b/src/actions/auditLogs.ts index 765aeb30ba..03d3c9f685 100644 --- a/src/actions/auditLogs.ts +++ b/src/actions/auditLogs.ts @@ -5,7 +5,7 @@ import { eq, gte, and, desc } from 'drizzle-orm'; import { db } from '@/db'; import { auditLogs } from '@/db/schema'; import { logger } from '@/lib/server/logger'; -import { AuditLogType } from '@/db/schema'; +import { AuditLog } from '@/db/schema'; /** * getAuditLogs @@ -20,7 +20,7 @@ export const getAuditLogs = async ({ orgId?: string | null; limit?: number; nRecentDays?: number; -}): Promise => { +}): Promise => { if (!orgId) { return []; } diff --git a/src/app/(authenticated)/audit-logs/AuditLogs.tsx b/src/app/(authenticated)/audit-logs/AuditLogs.tsx index c04c6d726e..6ec2aa8165 100644 --- a/src/app/(authenticated)/audit-logs/AuditLogs.tsx +++ b/src/app/(authenticated)/audit-logs/AuditLogs.tsx @@ -4,7 +4,7 @@ import { useState } from 'react'; import { useOrganization } from '@clerk/nextjs'; import { useQuery } from '@tanstack/react-query'; -import type { AuditLogType } from '@/db/schema'; +import type { AuditLog } from '@/db/schema'; import { timePeriods, type TimePeriod } from '@/schemas'; import { getAuditLogs } from '@/actions/auditLogs'; import { @@ -14,20 +14,16 @@ import { CardTitle, CardDescription, CardContent, - Drawer, - DrawerContent, - DrawerHeader, - DrawerTitle, + Skeleton, } from '@/components/ui'; -import { AuditLogDetails } from '@/components/audit-logs/AuditLogDetails'; -import { AuditLogEntry } from '@/components/audit-logs/AuditLogEntry'; +import { AuditLogEntry, AuditLogDrawer } from '@/components/audit-logs'; export const AuditLogs = () => { const { organization } = useOrganization(); const [timePeriod, setTimePeriod] = useState(7); - const [selectedLog, setSelectedLog] = useState(null); + const [selectedLog, setSelectedLog] = useState(null); - const { data: logs = [], isLoading } = useQuery({ + const { data: logs = [], isPending } = useQuery({ queryKey: ['auditLogs', organization?.id, timePeriod], queryFn: () => getAuditLogs({ @@ -60,37 +56,33 @@ export const AuditLogs = () => { ))} -
- {isLoading ? ( -
-

Loading...

-
+
+ {isPending ? ( + <> + + + + ) : logs.length > 0 ? ( - logs.map((log: AuditLogType) => ( + logs.map((log: AuditLog) => ( setSelectedLog(log)} + onClick={(log: AuditLog) => setSelectedLog(log)} /> )) ) : ( -
-

- No activity found -

+
+ No activity found.
)}
- setSelectedLog(null)}> - - - Activity Details - - {selectedLog && } - - + setSelectedLog(null)} + /> ); }; diff --git a/src/app/(authenticated)/usage/TaskDetails.tsx b/src/app/(authenticated)/usage/TaskDetails.tsx index 436d59b50e..799dabafd3 100644 --- a/src/app/(authenticated)/usage/TaskDetails.tsx +++ b/src/app/(authenticated)/usage/TaskDetails.tsx @@ -17,79 +17,72 @@ export const TaskDetails = ({ task ? ( -
- - Task Details - -
-
-
- Task ID: - {task.id} -
-
- Date: - - {task.date.toLocaleString()} - -
-
- - Developer: - - {task.developerName} -
-
- Model: - {task.modelName} -
-
- Tokens: - - {task.tokensConsumed.toLocaleString()} - -
-
- Cost: - ${task.cost.toFixed(2)} -
-
- Status: - - {task.status.charAt(0).toUpperCase() + task.status.slice(1)} - -
+ + Task Details + +
+
+
+ Task ID: + {task.id}
-

Conversation

-
- {task.conversation.map((message) => ( -
-
- {message.role.charAt(0).toUpperCase() + - message.role.slice(1)}{' '} - •{message.timestamp.toLocaleTimeString()} -
-
{message.content}
+
+ Date: + {task.date.toLocaleString()} +
+
+ Developer: + {task.developerName} +
+
+ Model: + {task.modelName} +
+
+ Tokens: + + {task.tokensConsumed.toLocaleString()} + +
+
+ Cost: + ${task.cost.toFixed(2)} +
+
+ Status: + + {task.status.charAt(0).toUpperCase() + task.status.slice(1)} + +
+
+

Conversation

+
+ {task.conversation.map((message) => ( +
+
+ {message.role.charAt(0).toUpperCase() + message.role.slice(1)}{' '} + •{message.timestamp.toLocaleTimeString()}
- ))} -
+
{message.content}
+
+ ))}
diff --git a/src/components/audit-logs/AuditLogCard.tsx b/src/components/audit-logs/AuditLogCard.tsx index d43be077ba..8b4b011884 100644 --- a/src/components/audit-logs/AuditLogCard.tsx +++ b/src/components/audit-logs/AuditLogCard.tsx @@ -7,7 +7,7 @@ import { useOrganization } from '@clerk/nextjs'; import { useQuery } from '@tanstack/react-query'; import { ArrowRightIcon } from 'lucide-react'; -import type { AuditLogType } from '@/db/schema'; +import type { AuditLog } from '@/db/schema'; import { getAuditLogs } from '@/actions/auditLogs'; import { Card, @@ -15,21 +15,20 @@ import { CardTitle, CardDescription, CardContent, - Drawer, - DrawerContent, - DrawerHeader, - DrawerTitle, + Skeleton, } from '@/components/ui'; import { Button as EnhancedButton } from '@/components/ui/ecosystem'; -import { AuditLogDetails, AuditLogEntry } from '@/components/audit-logs'; +import { AuditLogEntry } from '@/components/audit-logs'; + +import { AuditLogDrawer } from './AuditLogDrawer'; export function AuditLogCard() { - const [selectedLog, setSelectedLog] = useState(null); + const [selectedLog, setSelectedLog] = useState(null); const { organization } = useOrganization(); const path = usePathname(); - const { data: logs = [], isLoading } = useQuery({ + const { data: logs = [], isPending } = useQuery({ queryKey: ['auditLogs', organization?.id, 5], queryFn: () => getAuditLogs({ orgId: organization?.id, limit: 5 }), enabled: !!organization?.id, @@ -61,37 +60,33 @@ export function AuditLogCard() { )} -
- {isLoading ? ( -
-

Loading...

-
+
+ {isPending ? ( + <> + + + + ) : logs.length > 0 ? ( - logs.map((log: AuditLogType) => ( + logs.map((log: AuditLog) => ( setSelectedLog(log)} + onClick={(log: AuditLog) => setSelectedLog(log)} /> )) ) : ( -
-

- No recent activity -

+
+ No recent activity.
)}
- setSelectedLog(null)}> - - - Activity Details - - {selectedLog && } - - + setSelectedLog(null)} + /> ); } diff --git a/src/components/audit-logs/AuditLogDetails.tsx b/src/components/audit-logs/AuditLogDetails.tsx index ef930487bb..450e28672a 100644 --- a/src/components/audit-logs/AuditLogDetails.tsx +++ b/src/components/audit-logs/AuditLogDetails.tsx @@ -3,10 +3,10 @@ import { ArrowRight, Calendar, Clock, User } from 'lucide-react'; import Link from 'next/link'; -import type { AuditLogType } from '@/db/schema'; +import type { AuditLog } from '@/db/schema'; type AuditLogDetailsProps = { - log: AuditLogType; + log: AuditLog; }; const formatValue = (value: unknown) => { diff --git a/src/components/audit-logs/AuditLogDrawer.tsx b/src/components/audit-logs/AuditLogDrawer.tsx new file mode 100644 index 0000000000..33fb7c15e4 --- /dev/null +++ b/src/components/audit-logs/AuditLogDrawer.tsx @@ -0,0 +1,31 @@ +import type { AuditLog } from '@/db/schema'; +import { + Drawer, + DrawerContent, + DrawerHeader, + DrawerTitle, +} from '@/components/ui'; +import { AuditLogDetails } from '@/components/audit-logs'; + +type AuditLogDrawerProps = { + selectedLog: AuditLog | null; + onClose: () => void; +}; + +export const AuditLogDrawer = ({ + selectedLog, + onClose, +}: AuditLogDrawerProps) => { + return ( + + + + Activity Details + +
+ {selectedLog && } +
+
+
+ ); +}; diff --git a/src/components/audit-logs/AuditLogEntry.tsx b/src/components/audit-logs/AuditLogEntry.tsx index adf0004f4f..f354924d1a 100644 --- a/src/components/audit-logs/AuditLogEntry.tsx +++ b/src/components/audit-logs/AuditLogEntry.tsx @@ -1,12 +1,11 @@ -import { Settings, Sliders, Users } from 'lucide-react'; import { formatDistance } from 'date-fns'; +import { Settings, Sliders, Users } from 'lucide-react'; -import { type AuditLogType, AuditLogTargetType } from '@/db/schema'; -import { cn } from '@/lib/utils'; +import { type AuditLog, AuditLogTargetType } from '@/db/schema'; type AuditLogEntryProps = { - log: AuditLogType; - onClick: (log: AuditLogType) => void; + log: AuditLog; + onClick: (log: AuditLog) => void; }; const getIconByType = (type: AuditLogTargetType) => { @@ -23,24 +22,21 @@ const getIconByType = (type: AuditLogTargetType) => { }; export const AuditLogEntry = ({ log, onClick }: AuditLogEntryProps) => ( - +
); diff --git a/src/components/audit-logs/index.ts b/src/components/audit-logs/index.ts index a504098059..a5fdfb5d49 100644 --- a/src/components/audit-logs/index.ts +++ b/src/components/audit-logs/index.ts @@ -1,3 +1,4 @@ export { AuditLogCard } from './AuditLogCard'; export { AuditLogDetails } from './AuditLogDetails'; +export { AuditLogDrawer } from './AuditLogDrawer'; export { AuditLogEntry } from './AuditLogEntry'; diff --git a/src/components/layout/Logo.tsx b/src/components/layout/Logo.tsx index 4133bd4ce6..144f65accd 100644 --- a/src/components/layout/Logo.tsx +++ b/src/components/layout/Logo.tsx @@ -22,9 +22,11 @@ export const Logo = ({ const { resolvedTheme } = useTheme(); const [fill, setFill] = useState('#000'); - useEffect(() => { - setFill(resolvedTheme === 'dark' ? '#fff' : '#000'); - }, [resolvedTheme]); + // Fixes hydration error. + useEffect( + () => setFill(resolvedTheme === 'dark' ? '#fff' : '#000'), + [resolvedTheme], + ); return ( { useEffect(() => { const element = ref.current; + const isHopping = element !== null && element.classList.contains('animate-hop'); diff --git a/src/components/layout/ThemeSwitcher.tsx b/src/components/layout/ThemeSwitcher.tsx index 47226d02b6..43e68dae30 100644 --- a/src/components/layout/ThemeSwitcher.tsx +++ b/src/components/layout/ThemeSwitcher.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useEffect, useState } from 'react'; import { useTheme } from 'next-themes'; import { Moon, Sun } from 'lucide-react'; @@ -7,11 +8,18 @@ import { Switch } from '@/components/ui'; export function ThemeSwitcher() { const { resolvedTheme, setTheme } = useTheme(); + const [mode, setMode] = useState<'light' | 'dark'>('light'); + + // Fixes hydration error. + useEffect( + () => setMode(resolvedTheme === 'dark' ? 'dark' : 'light'), + [resolvedTheme], + ); return (
setTheme(checked ? 'dark' : 'light')} />
diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx index 02535dc4f5..6d066899e5 100644 --- a/src/components/ui/skeleton.tsx +++ b/src/components/ui/skeleton.tsx @@ -4,7 +4,11 @@ function Skeleton({ className, ...props }: React.ComponentProps<'div'>) { return (
); diff --git a/src/components/usage/Metric.tsx b/src/components/usage/Metric.tsx index f2b2bcc387..033df39976 100644 --- a/src/components/usage/Metric.tsx +++ b/src/components/usage/Metric.tsx @@ -1,17 +1,17 @@ -import { Skeleton } from '../ui'; import { formatNumber } from '@/lib/formatters'; +import { Skeleton } from '@/components/ui'; type MetricProps = { label: string; value?: number | string; - isLoading: boolean; + isPending: boolean; }; -export const Metric = ({ label, value, isLoading }: MetricProps) => ( +export const Metric = ({ label, value, isPending }: MetricProps) => (
{label}
- {isLoading ? ( + {isPending ? (
diff --git a/src/components/usage/UsageCard.tsx b/src/components/usage/UsageCard.tsx index 985bb1a7ad..c940ec5add 100644 --- a/src/components/usage/UsageCard.tsx +++ b/src/components/usage/UsageCard.tsx @@ -33,7 +33,7 @@ export const UsageCard = () => { const path = usePathname(); - const usage = useQuery({ + const { data: usage = {}, isPending } = useQuery({ queryKey: ['usage', orgId, timePeriod], queryFn: () => getUsage({ orgId, timePeriod }), }); @@ -79,34 +79,30 @@ export const UsageCard = () => {
diff --git a/src/db/schema.ts b/src/db/schema.ts index f2f120cb05..c426c8fc0e 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -26,4 +26,6 @@ export enum AuditLogTargetType { MEMBER_CHANGE = 3, // TODO: Currently no logs of this type are collected. } -export type AuditLogType = typeof auditLogs.$inferSelect; +export type AuditLog = typeof auditLogs.$inferSelect & { + targetType: AuditLogTargetType; +};