Skip to content

Commit e3a12e6

Browse files
committed
Refined Share/Delete dropdown menu UI and increase title length
1 parent 85647c4 commit e3a12e6

File tree

2 files changed

+156
-48
lines changed

2 files changed

+156
-48
lines changed

backend/routes/ai.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ router.post("/chat", authMiddleware, async (req, res) => {
143143
data: {
144144
id: conversationId,
145145
userId,
146-
title: data.message.slice(0, 20) + "...",
146+
title: data.message.slice(0, 35) + "...",
147147
type: "CONVERSATION",
148148
externalId: conversationId
149149
}

frontend/components/ui/ui-structure.tsx

Lines changed: 155 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,45 @@ import {
1919
MagnifyingGlassIcon,
2020
ShareFatIcon,
2121
TrashIcon,
22+
DotsThreeIcon,
2223
} from "@phosphor-icons/react";
24+
import { EllipsisIcon } from "lucide-react";
2325
import { toast } from "sonner";
2426
import { usePathname, useRouter } from "next/navigation";
2527
import { Logo } from "../svgs/logo";
2628
import { useUser } from "@/hooks/useUser";
2729
import Link from "next/link";
2830
import { useExecutionContext } from "@/contexts/execution-context";
31+
import {
32+
Dialog,
33+
DialogDescription,
34+
DialogContent,
35+
DialogTrigger,
36+
DialogTitle,
37+
DialogHeader,
38+
DialogClose,
39+
DialogFooter,
40+
} from "@/components/ui/dialog";
41+
import {
42+
DropdownMenu,
43+
DropdownMenuContent,
44+
DropdownMenuItem,
45+
DropdownMenuTrigger,
46+
} from "@/components/ui/dropdown-menu";
47+
import { Input } from "@/components/ui/input";
2948
import { Execution } from "@/hooks/useExecution";
3049
import { BACKEND_URL, cn } from "@/lib/utils";
3150

3251
export function UIStructure() {
3352
const [uiExecutions, setUiExecutions] = useState<Execution[]>([]);
53+
const [openMenuId, setOpenMenuId] = useState<string | null>(null);
3454
const [hoverChatId, setHoverChatId] = useState<string>("");
3555
const [isAppsDialogOpen, setIsAppsDialogOpen] = useState(false);
56+
const [isSearchDialogOpen, setIsSearchDialogOpen] = useState(false);
3657
const [searchTerm, setSearchTerm] = useState<string>("");
3758
const { executions, loading, createNewExecution } = useExecutionContext();
3859
const router = useRouter();
39-
60+
4061
const pathname = usePathname();
4162
const currentConversationId = pathname.split("/").pop();
4263

@@ -48,28 +69,30 @@ export function UIStructure() {
4869
} else {
4970
setUiExecutions(
5071
executions.filter((execution) =>
51-
(execution.title ?? "").toLowerCase().includes(term)
52-
)
72+
(execution.title ?? "").toLowerCase().includes(term),
73+
),
5374
);
5475
}
5576
}
5677
}, [executions, searchTerm]);
5778

58-
const handleDeleteExecution = async (executionId: string) => {
59-
try {
60-
await fetch(`${BACKEND_URL}/ai/chat/${executionId}`, {
61-
method: "DELETE",
62-
headers: {
63-
"Content-Type": "application/json",
64-
"Authorization": `Bearer ${localStorage.getItem("token")}`
65-
}
66-
});
67-
setUiExecutions(executions.filter((execution) => execution.id !== executionId));
68-
toast.success("Chat deleted successfully");
69-
} catch (error) {
70-
console.error("Error deleting chat:", error);
71-
}
72-
};
79+
const handleDeleteExecution = async (executionId: string) => {
80+
try {
81+
await fetch(`${BACKEND_URL}/ai/chat/${executionId}`, {
82+
method: "DELETE",
83+
headers: {
84+
"Content-Type": "application/json",
85+
Authorization: `Bearer ${localStorage.getItem("token")}`,
86+
},
87+
});
88+
setUiExecutions(
89+
executions.filter((execution) => execution.id !== executionId),
90+
);
91+
toast.success("Chat deleted successfully");
92+
} catch (error) {
93+
console.error("Error deleting chat:", error);
94+
}
95+
};
7396

7497
// Available AI Apps
7598
const availableApps = [
@@ -91,7 +114,7 @@ export function UIStructure() {
91114
return (
92115
<Sidebar className={`border py-2 pl-2`}>
93116
<SidebarContent className="h-full justify-between">
94-
<SidebarGroup className="flex flex-col gap-4">
117+
<SidebarGroup className="flex flex-col gap-8">
95118
<SidebarHeader className="sticky top-0 !p-0">
96119
<div className="flex w-full flex-col items-center gap-2 rounded-lg">
97120
<div className="flex w-full items-center gap-2 rounded-lg p-1 text-lg justify-between">
@@ -112,14 +135,62 @@ export function UIStructure() {
112135
</Button>
113136
</div>
114137

115-
<div className="relative flex items-center p-2 gap-2 bg-zinc-400/10 px-2 rounded-xl border border-black/10 dark:border-zinc-400/8 inset-shadow-sm cursor-pointer hover:bg-orange-200/10 dark:hover:bg-orange-100/10 dark:hover:[&>*]:text-orange-100 hover:[&>*]:text-orange-400">
116-
<MagnifyingGlassIcon className="text-foreground" size={18} />
117-
<p className="rounded-none text-sm border-none bg-transparent px-0 shadow-none ring-0 focus-visible:ring-0 dark:bg-transparent">
118-
Search for chats
119-
</p>
120-
</div>
138+
<Dialog
139+
open={isSearchDialogOpen}
140+
onOpenChange={setIsSearchDialogOpen}
141+
>
142+
<DialogTrigger className="relative flex items-center p-2 gap-2 bg-zinc-400/10 px-2 rounded-xl border border-black/10 dark:border-zinc-400/8 inset-shadow-sm cursor-pointer hover:bg-orange-200/10 dark:hover:bg-orange-100/10 dark:hover:[&>*]:text-orange-100 hover:[&>*]:text-orange-400">
143+
<MagnifyingGlassIcon className="text-foreground" size={18} />
144+
<p className="rounded-none text-sm border-none bg-transparent px-0 shadow-none ring-0 focus-visible:ring-0 dark:bg-transparent">
145+
Search for chats
146+
</p>
147+
</DialogTrigger>
121148

149+
<DialogContent className="sm:max-w-[35rem] rounded-xl p-0 overflow-auto gap-0 border-none">
150+
<DialogHeader className="bg-transparent border-b dark:border-b-orange-100/5">
151+
<Input
152+
placeholder="Search for chats"
153+
value={searchTerm}
154+
onChange={(e) => setSearchTerm(e.target.value)}
155+
className="border-none rounded-none text-sm rounded-tl-xl rounded-tr-xl shadow-none focus-visible:ring-0 bg-transparent dark:bg-transparent px-4 py-6"
156+
/>
157+
</DialogHeader>
158+
<div className="grid min-h-[8rem] max-h-[15rem] overflow-auto pb-3">
159+
<div className="flex flex-col px-2 justify-center">
160+
{uiExecutions.length === 0 ? (
161+
<div className="flex items-center justify-center">
162+
<p className="text-xs">No chat found!</p>
163+
</div>
164+
) : (
165+
<div>
166+
<DialogTitle className="font-normal tracking-wider text-xs text-orange-100/50 p-2">
167+
Chats
168+
</DialogTitle>
169+
{uiExecutions.map((execution: Execution) => (
170+
<div key={execution.id} className="">
171+
<div
172+
className={`p-2 rounded-lg hover:bg-orange-100/10 hover:text-orange-100`}
173+
onMouseEnter={() => setHoverChatId(execution.id)}
174+
onMouseLeave={() => setHoverChatId("")}
175+
onClick={() => {
176+
router.push(`/ask/${execution.id}`);
177+
setIsSearchDialogOpen(false);
178+
}}
179+
>
180+
<div className="flex w-full items-center justify-between text-sm">
181+
<span className="">{execution.title}</span>
182+
</div>
183+
</div>
184+
</div>
185+
))}
186+
</div>
187+
)}
188+
</div>
189+
</div>
190+
</DialogContent>
191+
</Dialog>
122192
</SidebarHeader>
193+
123194
<SidebarGroupContent>
124195
<SidebarMenu className="w-full p-0">
125196
{loading
@@ -133,29 +204,67 @@ export function UIStructure() {
133204
: uiExecutions.map((execution: Execution) => (
134205
<SidebarMenuItem key={execution.id}>
135206
<SidebarMenuButton
136-
className={`group relative w-full text-left hover:bg-primary/20 transition ${execution.id === currentConversationId ? "bg-primary/20" : ""}`}
207+
className={`group relative font-normal cursor-pointer rounded-xl w-full active:bg-transparent active:text-inherit hover:bg-orange-300/20 hover:text-orange-400 dark:hover:text-orange-100 dark:hover:bg-orange-100/10 ${execution.id === currentConversationId ? "bg-orange-300/20 text-orange-400 dark:text-orange-100 dark:bg-orange-100/10" : ""}`}
137208
onMouseEnter={() => setHoverChatId(execution.id)}
138209
onMouseLeave={() => setHoverChatId("")}
139210
onClick={() => router.push(`/ask/${execution.id}`)}
140211
>
141212
<div className="flex w-full items-center justify-between">
142-
<span className="z-[-1] cursor-pointer truncate">
213+
<span className="cursor-pointer truncate">
143214
{execution.title}
144215
</span>
145-
<div
146-
className={cn(
147-
"absolute top-0 right-0 z-[5] h-full w-12 rounded-r-md blur-[2em]",
148-
execution.id === hoverChatId && "bg-primary"
149-
)}
150-
/>
151216

152-
<div
153-
className={cn(
154-
"absolute top-1/2 -right-16 z-[10] flex h-full -translate-y-1/2 items-center justify-center gap-1.5 rounded-r-md bg-transparent px-1 backdrop-blur-xl transition-all duration-200 ease-in-out",
155-
execution.id === hoverChatId && "group-hover:right-0"
156-
)}
157-
>
158-
<div
217+
<div className="realtive group flex items-center">
218+
<DropdownMenu
219+
open={openMenuId === execution.id}
220+
onOpenChange={(isOpen: boolean) =>
221+
setOpenMenuId(isOpen ? execution.id : null)
222+
}
223+
>
224+
<DropdownMenuTrigger asChild>
225+
<button
226+
className={cn(
227+
"opacity-0 transition-opacity",
228+
{
229+
"opacity-100":
230+
execution.id === hoverChatId ||
231+
openMenuId === execution.id,
232+
},
233+
)}
234+
>
235+
<EllipsisIcon className="size-5 text-white" />
236+
</button>
237+
</DropdownMenuTrigger>
238+
239+
<DropdownMenuContent className="border-none rounded-xl">
240+
<DropdownMenuItem
241+
className="focus:bg-orange-300/20 focus:text-orange-400 darK:focus:bg-orange-100/10 dark:focus:text-orange-100 rounded-xl"
242+
onClick={() => {
243+
const shareLink = `${process.env.NEXT_PUBLIC_APP_URL}/ask/${execution.id}`;
244+
navigator.clipboard.writeText(shareLink);
245+
toast.success(
246+
"Share link copied to clipboard",
247+
);
248+
setOpenMenuId(null);
249+
}}
250+
>
251+
<ShareFatIcon className="focus:text-orange-400 dark:text-orange-100" />
252+
Share
253+
</DropdownMenuItem>
254+
255+
<DropdownMenuItem
256+
onClick={() => {
257+
handleDeleteExecution(execution.id);
258+
setOpenMenuId(null);
259+
}}
260+
className="focus:bg-red-400/10 text-red-400 rounded-xl focus:text-red-400"
261+
>
262+
<TrashIcon className="text-red-400" />
263+
Delete
264+
</DropdownMenuItem>
265+
</DropdownMenuContent>
266+
</DropdownMenu>
267+
{/* <div
159268
className="flex items-center justify-center rounded-md"
160269
onClick={(e) => {
161270
e.preventDefault();
@@ -170,9 +279,9 @@ export function UIStructure() {
170279
weight="fill"
171280
className="hover:text-foreground size-4"
172281
/>
173-
</div>
282+
</div> */}
174283

175-
<div
284+
{/* <div
176285
className="flex items-center justify-center rounded-md"
177286
onClick={(e) => {
178287
e.stopPropagation();
@@ -183,7 +292,7 @@ export function UIStructure() {
183292
weight={"bold"}
184293
className="hover:text-foreground size-4"
185294
/>
186-
</div>
295+
</div> */}
187296
</div>
188297
</div>
189298
</SidebarMenuButton>
@@ -193,7 +302,7 @@ export function UIStructure() {
193302
</SidebarGroupContent>
194303
</SidebarGroup>
195304

196-
{/* <SidebarFooter className="sticky bottom-0 flex flex-col gap-2 w-full p-3 bg-background">
305+
{/* <SidebarFooter className="sticky bottom-0 flex flex-col gap-2 w-full p-3 bg-background">
197306
{!isUserLoading && !user && (
198307
<Link href="/auth">
199308
<Button variant="secondary" className="w-full" size="lg">
@@ -247,11 +356,10 @@ export function UIStructure() {
247356
</DialogContent>
248357
</Dialog> */}
249358

250-
251359
<SidebarFooter className="sticky bottom-0 flex flex-col gap-2 w-full p-3 bg-transparent">
252360
<NavSetting />
253361

254-
{/* {user && (
362+
{/* {user && (
255363
<Dialog open={isAppsDialogOpen} onOpenChange={setIsAppsDialogOpen}>
256364
<DialogTrigger asChild>
257365
<Button variant="secondary" className="w-full " size="lg">
@@ -298,7 +406,7 @@ export function UIStructure() {
298406
</Dialog>
299407
)} */}
300408

301-
{/* {user && (
409+
{/* {user && (
302410
<Button
303411
variant="destructive"
304412
className="w-full text-sm text-white h-10 px-3.5 rounded-xl inset-shadow-sm inset-shadow-white/60 font-medium border border-black/4 outline-0"

0 commit comments

Comments
 (0)