From 849dccb3e9268e3a652f14bc3003cf26a8cb2937 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Mon, 16 Jun 2025 10:42:52 -0400 Subject: [PATCH] Use a task modal instead of a drawer (#103) --- src/app/(authenticated)/usage/Messages.tsx | 10 +- src/app/(authenticated)/usage/TaskDrawer.tsx | 114 --------- src/app/(authenticated)/usage/TaskModal.tsx | 57 +++++ src/app/(authenticated)/usage/Usage.tsx | 6 +- .../task-sharing/SharedTaskView.tsx | 82 +------ src/components/task-sharing/TaskDetails.tsx | 116 +++++++++ src/components/task-sharing/index.ts | 1 + src/components/ui/dialog.tsx | 224 +++++++++++------- 8 files changed, 329 insertions(+), 281 deletions(-) delete mode 100644 src/app/(authenticated)/usage/TaskDrawer.tsx create mode 100644 src/app/(authenticated)/usage/TaskModal.tsx create mode 100644 src/components/task-sharing/TaskDetails.tsx diff --git a/src/app/(authenticated)/usage/Messages.tsx b/src/app/(authenticated)/usage/Messages.tsx index 2bee83dd82..07329aff16 100644 --- a/src/app/(authenticated)/usage/Messages.tsx +++ b/src/app/(authenticated)/usage/Messages.tsx @@ -19,17 +19,17 @@ export const Messages = ({ messages }: MessagesProps) => { ); return ( -
+
{conversation.map((message) => (
-
-
+
+
{message.name}
·
{message.timestamp}
@@ -40,7 +40,7 @@ export const Messages = ({ messages }: MessagesProps) => {
)}
-
+
{message.text}
diff --git a/src/app/(authenticated)/usage/TaskDrawer.tsx b/src/app/(authenticated)/usage/TaskDrawer.tsx deleted file mode 100644 index f4211f0460..0000000000 --- a/src/app/(authenticated)/usage/TaskDrawer.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { X } from 'lucide-react'; - -import type { TaskWithUser } from '@/actions/analytics'; -import { getMessages } from '@/actions/analytics'; -import { canShareTask } from '@/actions/taskSharing'; -import { useOrganizationSettings } from '@/hooks/useOrganizationSettings'; -import { formatCurrency, formatNumber } from '@/lib/formatters'; -import { generateFallbackTitle } from '@/lib/task-utils'; -import { QueryKey } from '@/types/react-query'; -import { Drawer, DrawerContent, Button } from '@/components/ui'; -import { ShareButton } from '@/components/task-sharing/ShareButton'; - -import { Status } from './Status'; -import { Messages } from './Messages'; - -type TaskDrawerProps = { - task: TaskWithUser; - onClose: () => void; -}; - -export const TaskDrawer = ({ task, onClose }: TaskDrawerProps) => { - const { data: messages } = useQuery({ - queryKey: ['messages', task.taskId], - queryFn: () => getMessages(task.taskId), - }); - - const { data: orgSettings } = useOrganizationSettings(); - - const { data: sharePermission } = useQuery({ - queryKey: [QueryKey.CanShareTask, task.taskId], - queryFn: () => canShareTask(task.taskId), - enabled: !!task.taskId, - }); - - const isTaskSharingEnabled = - orgSettings?.cloudSettings?.enableTaskSharing ?? false; - - const canUserShareThisTask = sharePermission?.canShare ?? false; - - return ( - - -
-
-
- {isTaskSharingEnabled && canUserShareThisTask && ( - - )} - -
-

- {task.title || generateFallbackTitle(task)} -

-
-
- Developer - {task.user.name} -
-
- Provider - - {task.provider} - -
-
- Model - - {task.model} - -
- {task.mode && ( -
- Mode - {task.mode} -
- )} -
- Tokens - - {formatNumber(task.tokens)} - -
-
- Cost - - {formatCurrency(task.cost)} - -
-
- Date - - {new Date(task.timestamp * 1000).toLocaleString()} - -
-
- Status - -
-
- {typeof messages !== 'undefined' && messages.length > 0 && ( - <> -

Conversation

- - - )} -
-
-
-
- ); -}; diff --git a/src/app/(authenticated)/usage/TaskModal.tsx b/src/app/(authenticated)/usage/TaskModal.tsx new file mode 100644 index 0000000000..bbe52e2402 --- /dev/null +++ b/src/app/(authenticated)/usage/TaskModal.tsx @@ -0,0 +1,57 @@ +import { useQuery } from '@tanstack/react-query'; + +import type { TaskWithUser } from '@/actions/analytics'; +import { getMessages } from '@/actions/analytics'; +import { canShareTask } from '@/actions/taskSharing'; +import { useOrganizationSettings } from '@/hooks/useOrganizationSettings'; +import { QueryKey } from '@/types/react-query'; +import { Dialog, DialogContentLarge } from '@/components/ui'; +import { ShareButton } from '@/components/task-sharing/ShareButton'; +import { TaskDetails } from '@/components/task-sharing/TaskDetails'; + +type TaskModalProps = { + task: TaskWithUser; + open: boolean; + onClose: () => void; +}; + +export const TaskModal = ({ task, open, onClose }: TaskModalProps) => { + const { data: messages = [] } = useQuery({ + queryKey: ['messages', task.taskId], + queryFn: () => getMessages(task.taskId), + enabled: open && !!task.taskId, + }); + + const { data: orgSettings } = useOrganizationSettings(); + + const { data: sharePermission } = useQuery({ + queryKey: [QueryKey.CanShareTask, task.taskId], + queryFn: () => canShareTask(task.taskId), + enabled: open && !!task.taskId, + }); + + const isTaskSharingEnabled = + orgSettings?.cloudSettings?.enableTaskSharing ?? false; + + const canUserShareThisTask = sharePermission?.canShare ?? false; + + const headerActions = ( + <> + {isTaskSharingEnabled && canUserShareThisTask && ( + + )} + + ); + + return ( + + + + + + ); +}; diff --git a/src/app/(authenticated)/usage/Usage.tsx b/src/app/(authenticated)/usage/Usage.tsx index be0848a588..16d61aea5e 100644 --- a/src/app/(authenticated)/usage/Usage.tsx +++ b/src/app/(authenticated)/usage/Usage.tsx @@ -12,7 +12,7 @@ import { type Filter, type ViewMode, viewModes } from './types'; import { Developers } from './Developers'; import { Models } from './Models'; import { Tasks } from './Tasks'; -import { TaskDrawer } from './TaskDrawer'; +import { TaskModal } from './TaskModal'; type UsageProps = { userRole?: 'admin' | 'member'; @@ -89,7 +89,9 @@ export const Usage = ({ userRole = 'admin', currentUserId }: UsageProps) => { )}
- {task && setTask(null)} />} + {task && ( + setTask(null)} /> + )} ); }; diff --git a/src/components/task-sharing/SharedTaskView.tsx b/src/components/task-sharing/SharedTaskView.tsx index aa2d363362..a33819e6fc 100644 --- a/src/components/task-sharing/SharedTaskView.tsx +++ b/src/components/task-sharing/SharedTaskView.tsx @@ -1,10 +1,6 @@ import type { TaskWithUser, Message } from '@/actions/analytics'; import type { SharedByUser } from '@/types/task-sharing'; -import { formatCurrency, formatNumber } from '@/lib/formatters'; -import { generateFallbackTitle } from '@/lib/task-utils'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui'; -import { Status } from '@/app/(authenticated)/usage/Status'; -import { Messages } from '@/app/(authenticated)/usage/Messages'; +import { TaskDetails } from './TaskDetails'; type SharedTaskViewProps = { task: TaskWithUser; @@ -19,75 +15,13 @@ export const SharedTaskView = ({ sharedBy, sharedAt, }: SharedTaskViewProps) => { - const taskTitle = task.title || generateFallbackTitle(task); - return ( -
- {/* Task Header */} - - -
-
- - {taskTitle} - -

- Shared by {sharedBy.name} • {sharedAt.toLocaleDateString()} -

-
-
- - - Shared - -
-
-
- -
-
-

Developer

-

{task.user.name}

-
-
-

Model

-

{task.model}

-
-
-

Provider

-

{task.provider}

-
-
-

Tokens

-

{formatNumber(task.tokens)}

-
-
-

Cost

-

{formatCurrency(task.cost)}

-
-
-
-
- - {/* Conversation */} - {messages.length > 0 ? ( - - - Conversation - - - - - - ) : ( - - -

- No conversation messages are available for this task. -

-
-
- )} -
+ ); }; diff --git a/src/components/task-sharing/TaskDetails.tsx b/src/components/task-sharing/TaskDetails.tsx new file mode 100644 index 0000000000..8f764ce08a --- /dev/null +++ b/src/components/task-sharing/TaskDetails.tsx @@ -0,0 +1,116 @@ +import type { TaskWithUser, Message } from '@/actions/analytics'; +import type { SharedByUser } from '@/types/task-sharing'; +import { formatCurrency, formatNumber } from '@/lib/formatters'; +import { generateFallbackTitle } from '@/lib/task-utils'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui'; +import { Status } from '@/app/(authenticated)/usage/Status'; +import { Messages } from '@/app/(authenticated)/usage/Messages'; + +type TaskDetailsProps = { + task: TaskWithUser; + messages: Message[]; + sharedBy?: SharedByUser; + sharedAt?: Date; + showSharedInfo?: boolean; + headerActions?: React.ReactNode; +}; + +export const TaskDetails = ({ + task, + messages, + sharedBy, + sharedAt, + showSharedInfo = false, + headerActions, +}: TaskDetailsProps) => { + const taskTitle = task.title || generateFallbackTitle(task); + + return ( +
+ {/* Task Header */} + + +
+
+ + {taskTitle} + + {showSharedInfo && sharedBy && sharedAt && ( +

+ Shared by {sharedBy.name} • {sharedAt.toLocaleDateString()} +

+ )} +
+
+ + {showSharedInfo && ( + + Shared + + )} + {headerActions} +
+
+
+ +
+
+

Developer

+

{task.user.name}

+
+
+

Model

+

{task.model}

+
+
+

Provider

+

{task.provider}

+
+
+

Tokens

+

{formatNumber(task.tokens)}

+
+
+

Cost

+

{formatCurrency(task.cost)}

+
+
+ {task.mode && ( +
+
+ Mode + {task.mode} +
+
+ )} +
+
+ Date + {new Date(task.timestamp * 1000).toLocaleString()} +
+
+
+
+ + {/* Conversation */} + {messages.length > 0 ? ( + + + Conversation + + + + + + ) : ( + + +

+ No conversation messages are available for this task. +

+
+
+ )} +
+ ); +}; diff --git a/src/components/task-sharing/index.ts b/src/components/task-sharing/index.ts index 91322d4faf..533ce651b8 100644 --- a/src/components/task-sharing/index.ts +++ b/src/components/task-sharing/index.ts @@ -1,2 +1,3 @@ export * from './ShareButton'; export * from './SharedTaskView'; +export * from './TaskDetails'; diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx index 39a5187e68..417bd99b67 100644 --- a/src/components/ui/dialog.tsx +++ b/src/components/ui/dialog.tsx @@ -1,110 +1,162 @@ -"use client" +'use client'; -import * as React from "react" -import * as DialogPrimitive from "@radix-ui/react-dialog" -import { XIcon } from "lucide-react" +import * as React from 'react'; +import * as DialogPrimitive from '@radix-ui/react-dialog'; +import { XIcon } from 'lucide-react'; -import { cn } from "@/lib/utils" +import { cn } from '@/lib/utils'; -function Dialog({ ...props }: React.ComponentProps) { - return +function Dialog({ + ...props +}: React.ComponentProps) { + return ; } -function DialogTrigger({ ...props }: React.ComponentProps) { - return +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return ; } -function DialogPortal({ ...props }: React.ComponentProps) { - return +function DialogPortal({ + ...props +}: React.ComponentProps) { + return ; } -function DialogClose({ ...props }: React.ComponentProps) { - return +function DialogClose({ + ...props +}: React.ComponentProps) { + return ; } -function DialogOverlay({ className, ...props }: React.ComponentProps) { - return ( - - ) +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); } -function DialogContent({ className, children, ...props }: React.ComponentProps) { - return ( - - - - {children} - - - Close - - - - ) +function DialogContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + {children} + + + Close + + + + ); } -function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) +function DialogContentLarge({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + Close + +
{children}
+
+
+ ); } -function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { - return ( -
- ) +function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); } -function DialogTitle({ className, ...props }: React.ComponentProps) { - return ( - - ) +function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); } -function DialogDescription({ className, ...props }: React.ComponentProps) { - return ( - - ) +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); } export { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogOverlay, - DialogPortal, - DialogTitle, - DialogTrigger, -} + Dialog, + DialogClose, + DialogContent, + DialogContentLarge, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +};