Skip to content

Commit 0cd8017

Browse files
committed
Merge branch 'main' into DEVT-54-Augment-notes-LLM-response
2 parents 6587725 + e31dedd commit 0cd8017

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1904
-1159
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"use client";
2+
3+
import { deleteChatroom } from "@/app/chatrooms/actions";
4+
import { Button } from "@/shared/components/ui/button";
5+
import { Trash2 } from "lucide-react";
6+
import { useRouter } from "next/navigation";
7+
import { useState } from "react";
8+
9+
export default function DeleteChatroomButton({
10+
classroomId,
11+
chatroomId,
12+
assistantId,
13+
}: {
14+
classroomId: number;
15+
chatroomId: string;
16+
assistantId: string | null;
17+
}) {
18+
const [isDeleting, setIsDeleting] = useState(false);
19+
const router = useRouter();
20+
21+
const handleDelete = async () => {
22+
if (confirm("Are you sure you want to delete this chatroom?")) {
23+
setIsDeleting(true);
24+
try {
25+
await deleteChatroom(chatroomId, assistantId);
26+
router.push(`/classrooms/${classroomId}/chatrooms`);
27+
} catch (error) {
28+
console.error("Error deleting chatroom:", error);
29+
alert("Failed to delete chatroom. Please try again.");
30+
} finally {
31+
setIsDeleting(false);
32+
}
33+
}
34+
};
35+
36+
return (
37+
<Button
38+
size={"sm"}
39+
onClick={handleDelete}
40+
disabled={isDeleting}
41+
variant={"destructive"}
42+
>
43+
<Trash2 />
44+
{isDeleting ? "Deleting..." : "Delete"}
45+
</Button>
46+
);
47+
}
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
"use client";
2+
3+
import { Check, Plus } from "lucide-react";
4+
5+
import { Button } from "@shared/components/ui/button";
6+
import {
7+
Command,
8+
CommandEmpty,
9+
CommandGroup,
10+
CommandInput,
11+
CommandItem,
12+
CommandList,
13+
} from "@shared/components/ui/command";
14+
import {
15+
Dialog,
16+
DialogContent,
17+
DialogDescription,
18+
DialogFooter,
19+
DialogHeader,
20+
DialogTitle,
21+
} from "@shared/components/ui/dialog";
22+
import { useEffect, useState } from "react";
23+
import {
24+
Avatar,
25+
AvatarFallback,
26+
AvatarImage,
27+
} from "@/shared/components/ui/avatar";
28+
import { createClient } from "@/shared/utils/supabase/client";
29+
import { toast } from "sonner";
30+
import { inviteUserToChatroom } from "../../actions";
31+
32+
interface ChatroomMembers {
33+
chatroom_id: string;
34+
created_at: string;
35+
id: number;
36+
is_active: boolean;
37+
member_id: number;
38+
Classroom_Members: {
39+
id: number;
40+
user_id: string;
41+
classroom_id: number;
42+
Users: {
43+
id: string;
44+
full_name: string | null;
45+
avatar_url: string | null;
46+
};
47+
};
48+
}
49+
50+
interface ClassroomMember {
51+
id: number;
52+
user_id: string;
53+
classroom_id: number;
54+
Users: {
55+
email: string | null;
56+
full_name: string | null;
57+
avatar_url: string | null;
58+
};
59+
}
60+
61+
export function InviteChatroomButton({
62+
chatroomId,
63+
classroomId,
64+
chatroomMembers,
65+
}: {
66+
chatroomId: string;
67+
classroomId: number;
68+
chatroomMembers: ChatroomMembers[];
69+
}) {
70+
const [open, setOpen] = useState(false);
71+
const [selectedUsers, setSelectedUsers] = useState<ClassroomMember[]>([]);
72+
const [classroomInvitees, setClassroomInvitees] = useState<ClassroomMember[]>(
73+
[]
74+
);
75+
76+
// get a list of Classroom_Members id
77+
const currentMemberIds = chatroomMembers.map(
78+
(member) => member.Classroom_Members.id
79+
);
80+
81+
useEffect(() => {
82+
async function fetchClassroomMembers() {
83+
const supabase = createClient();
84+
85+
const { data, error } = await supabase
86+
.from("Classroom_Members")
87+
.select(
88+
`
89+
id,
90+
user_id,
91+
classroom_id,
92+
Users (
93+
email,
94+
full_name,
95+
avatar_url
96+
)
97+
`
98+
)
99+
.eq("classroom_id", classroomId);
100+
101+
if (error) {
102+
console.error("Error fetching classroom members:", error);
103+
} else {
104+
setClassroomInvitees(data as ClassroomMember[]);
105+
}
106+
}
107+
108+
if (open) {
109+
fetchClassroomMembers();
110+
}
111+
}, [classroomId, currentMemberIds, open]);
112+
113+
const handleInviteUsers = async () => {
114+
if (selectedUsers.length === 0) return;
115+
116+
try {
117+
const invitePromises = selectedUsers.map((invitee) =>
118+
inviteUserToChatroom(chatroomId, invitee.Users.email || "")
119+
);
120+
121+
await Promise.all(invitePromises);
122+
123+
setOpen(false);
124+
setSelectedUsers([]);
125+
toast.success("Successfully invited user(s) to chatroom");
126+
} catch (error) {
127+
if (error instanceof Error) {
128+
toast.error(error.message);
129+
}
130+
}
131+
};
132+
133+
return (
134+
<>
135+
<Button size="sm" onClick={() => setOpen(true)}>
136+
<Plus />
137+
Invite
138+
</Button>
139+
<Dialog
140+
open={open}
141+
onOpenChange={(isOpen) => {
142+
setOpen(isOpen);
143+
if (!isOpen) {
144+
setSelectedUsers([]);
145+
}
146+
}}
147+
>
148+
<DialogContent className="gap-0 p-0 outline-none">
149+
<DialogHeader className="px-4 pb-4 pt-5">
150+
<DialogTitle>New chatroom Member</DialogTitle>
151+
<DialogDescription>
152+
Invite a user to this chatroom. You can only invite members of
153+
current classroom.
154+
</DialogDescription>
155+
</DialogHeader>
156+
<Command className="overflow-hidden rounded-t-none border-t bg-transparent">
157+
<CommandInput placeholder="Search member..." />
158+
<CommandList>
159+
<CommandEmpty>No members found.</CommandEmpty>
160+
<CommandGroup className="p-2">
161+
{classroomInvitees.map((user) => (
162+
<CommandItem
163+
key={user.id.toString()}
164+
className="flex items-center px-2"
165+
onSelect={() => {
166+
if (
167+
selectedUsers.some(
168+
(selected) => selected.id === user.id
169+
)
170+
) {
171+
setSelectedUsers(
172+
selectedUsers.filter(
173+
(selectedUser) => selectedUser.id !== user.id
174+
)
175+
);
176+
} else {
177+
setSelectedUsers([...selectedUsers, user]);
178+
}
179+
}}
180+
>
181+
<Avatar>
182+
<AvatarImage
183+
src={user.Users.avatar_url || ""}
184+
alt="Image"
185+
/>
186+
<AvatarFallback>
187+
{user.Users.full_name?.[0] || "U"}
188+
</AvatarFallback>
189+
</Avatar>
190+
<div className="ml-2">
191+
<p className="text-sm font-medium leading-none">
192+
{user.Users.full_name || "Unknown"}
193+
</p>
194+
<p className="text-sm text-muted-foreground">
195+
{user.Users.email || "No email"}
196+
</p>
197+
</div>
198+
{selectedUsers.some(
199+
(selected) => selected.id === user.id
200+
) ? (
201+
<Check className="ml-auto flex h-5 w-5 text-primary" />
202+
) : null}
203+
</CommandItem>
204+
))}
205+
</CommandGroup>
206+
</CommandList>
207+
</Command>
208+
<DialogFooter className="flex items-center border-t p-4 sm:justify-between">
209+
{selectedUsers.length > 0 ? (
210+
<div className="flex -space-x-2 overflow-hidden">
211+
{selectedUsers.map((user) => (
212+
<Avatar
213+
key={user.Users.email}
214+
className="inline-block border-2 border-background"
215+
>
216+
<AvatarImage src={user.Users.avatar_url!} />
217+
<AvatarFallback>{user.Users.full_name![0]}</AvatarFallback>
218+
</Avatar>
219+
))}
220+
</div>
221+
) : (
222+
<p className="text-sm text-muted-foreground">
223+
Select users to add to this thread.
224+
</p>
225+
)}
226+
<Button
227+
disabled={selectedUsers.length <= 0}
228+
onClick={handleInviteUsers}
229+
>
230+
Continue
231+
</Button>
232+
</DialogFooter>
233+
</DialogContent>
234+
</Dialog>
235+
</>
236+
);
237+
}
238+
239+
export default InviteChatroomButton;

app/chatrooms/[chatroomId]/components/leave-chatroom-button.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22

33
import { useState } from "react";
44
import { useRouter } from "next/navigation";
5-
import { leaveChatroom } from "../../actions";
5+
import { leaveChatroom } from "@/app/chatrooms/actions";
6+
import { Button } from "@/shared/components/ui/button";
7+
import { DoorOpen } from "lucide-react";
68

79
export default function LeaveChatroomButton({
10+
classroomId,
811
chatroomId,
912
}: {
13+
classroomId: number;
1014
chatroomId: string;
1115
}) {
1216
const [isLeaving, setIsLeaving] = useState(false);
@@ -17,7 +21,7 @@ export default function LeaveChatroomButton({
1721
setIsLeaving(true);
1822
try {
1923
await leaveChatroom(chatroomId);
20-
router.push("/chatrooms");
24+
router.push(`/classrooms/${classroomId}/chatrooms`);
2125
} catch (error) {
2226
console.error("Error leaving chatroom:", error);
2327
alert("Failed to leave chatroom. Please try again.");
@@ -28,12 +32,14 @@ export default function LeaveChatroomButton({
2832
};
2933

3034
return (
31-
<button
35+
<Button
36+
size={"sm"}
3237
onClick={handleLeave}
3338
disabled={isLeaving}
34-
className="rounded bg-red-600 px-4 py-2 text-white transition-colors hover:bg-red-700 disabled:bg-gray-400"
39+
variant={"destructive"}
3540
>
36-
{isLeaving ? "Leaving..." : "Leave Chatroom"}
37-
</button>
41+
<DoorOpen />
42+
{isLeaving ? "Leaving..." : "Leave"}
43+
</Button>
3844
);
3945
}

0 commit comments

Comments
 (0)