Skip to content

Commit ceed8db

Browse files
committed
Add search modal to dashboard
1 parent 0b57d2a commit ceed8db

File tree

3 files changed

+156
-0
lines changed

3 files changed

+156
-0
lines changed

frontend/app/components/Dashboard.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import React, { useEffect, useRef, useState } from "react";
55
import CreateCircleModal from "./CreateCircleModal";
66
import EditModal from "./EditModal";
77
import InviteModal from "./InviteModal";
8+
import SearchModal from "./SearchModal";
89
import { useFetchDashboard } from "../hooks/useFetchDashboard";
910
import { useWebSocketDashboard } from "../hooks/useWebsocketDashboard";
1011
import { Circle, Message, WebSocketMessage } from "../types";
@@ -48,6 +49,17 @@ export default function Dashboard2() {
4849
}
4950
};
5051

52+
// Search chat
53+
const [openSearchModal, setOpenSearchModal] = useState(false);
54+
function handleOpenSearchModal() {
55+
setOpenSearchModal(true);
56+
}
57+
const handleSearchClose = (event: HandleCloseEvent) => {
58+
if (event.target.classList.contains('modal-container')) {
59+
setOpenSearchModal(false);
60+
}
61+
};
62+
5163
// Invite users to circle
5264
const [openInviteModal, setOpenInviteModal] = useState(false);
5365
function handleOpenInviteModal() {
@@ -249,11 +261,30 @@ export default function Dashboard2() {
249261
<EditModal isOpen={openEditModal} setOpen={() => setOpenEditModal(false)} circleId={selectedCircleID} />
250262
</div>
251263
</div>}
264+
{openSearchModal && <div className="modal-container" onClick={handleSearchClose}>
265+
<div className="modal inset-0 bg-black bg-opacity-50 z-50">
266+
<SearchModal isOpen={openSearchModal} setOpen={() => setOpenSearchModal(false)} circleId={selectedCircleID} />
267+
</div>
268+
</div>}
252269
</div>
253270
<div className="chat-container">
254271
<div className="chat-title">
255272
<h1>{circles.find((circle) => circle.id === selectedCircleID)?.name}&nbsp;</h1>
256273
<div className="chat-menu">
274+
<button
275+
className="button-size group disabled:opacity-50 disabled:cursor-not-allowed"
276+
onClick={selectedCircleID !== "" ? handleOpenSearchModal : undefined}
277+
disabled={selectedCircleID === ""}
278+
>
279+
<svg xmlns="http://www.w3.org/2000/svg" className="size-5 opacity-75" viewBox="0 0 30 30" strokeWidth="1" stroke="currentColor" fill="currentColor">
280+
<path d="M 13 3 C 7.4889971 3 3 7.4889971 3 13 C 3 18.511003 7.4889971 23 13 23 C 15.396508 23 17.597385 22.148986 19.322266 20.736328 L 25.292969 26.707031 A 1.0001 1.0001 0 1 0 26.707031 25.292969 L 20.736328 19.322266 C 22.148986 17.597385 23 15.396508 23 13 C 23 7.4889971 18.511003 3 13 3 z M 13 5 C 17.430123 5 21 8.5698774 21 13 C 21 17.430123 17.430123 21 13 21 C 8.5698774 21 5 17.430123 5 13 C 5 8.5698774 8.5698774 5 13 5 z"></path>
281+
</svg>
282+
<span
283+
className="invisible absolute start-1/2 top-full mt-5 -translate-x-1/2 rounded bg-gray-900 px-1.5 py-1.5 text-xs font-medium text-white group-hover:visible"
284+
>
285+
Search Chat
286+
</span>
287+
</button>
257288
<button
258289
className="button-size group disabled:opacity-50 disabled:cursor-not-allowed"
259290
onClick={selectedCircleID !== "" ? handleOpenEditModal : undefined}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.search-modal {
2+
height: 80vh;
3+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { useAuth } from "../context/authContext";
3+
import { User } from "../types";
4+
import "./SearchModal.css"
5+
6+
interface SearchModalProps {
7+
isOpen: boolean;
8+
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
9+
circleId: string;
10+
}
11+
12+
function SearchModal({ isOpen, setOpen, circleId }: SearchModalProps) {
13+
const authContext = useAuth();
14+
if (!authContext) {
15+
throw new Error("useAuth must be used within an AuthProvider");
16+
}
17+
const { getAccessToken } = authContext;
18+
19+
const [users, setUsers] = useState<User[]>([]);
20+
useEffect(() => {
21+
async function fetchInviteUsers() {
22+
const token = await getAccessToken();
23+
const headers = {
24+
'Authorization': `Bearer ${token}`,
25+
};
26+
const body = {
27+
circle_id: circleId,
28+
};
29+
fetch('http://localhost:8000/api/circles/invite', {
30+
method: 'POST',
31+
headers: headers,
32+
body: JSON.stringify(body),
33+
})
34+
.then(async (response) => {
35+
const data = await response.json();
36+
if (!response.ok) {
37+
console.log("Error:", data);
38+
}
39+
else {
40+
console.log("Data:", data);
41+
const mappedUsers = data.map((user: any) => ({
42+
id: user.id,
43+
username: user.username,
44+
checked: false
45+
}));
46+
setUsers(mappedUsers);
47+
}
48+
})
49+
.catch(error => {
50+
console.log(error);
51+
});
52+
}
53+
fetchInviteUsers();
54+
}, [circleId]);
55+
56+
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
57+
e.preventDefault();
58+
const invitedUsers = users.filter(user => user.checked).map(user => user.id);
59+
const token = await getAccessToken();
60+
const headers = {
61+
'Authorization': `Bearer ${token}`,
62+
};
63+
const body = {
64+
circle_id: circleId,
65+
users: invitedUsers,
66+
};
67+
console.log(body);
68+
fetch('http://localhost:8000/api/circles/search', {
69+
method: 'POST',
70+
headers: headers,
71+
body: JSON.stringify(body),
72+
})
73+
.then(async (response) => {
74+
const data = await response.json();
75+
if (!response.ok) {
76+
console.log("Error:", data);
77+
}
78+
else {
79+
console.log("Data:", data);
80+
setOpen(false);
81+
}
82+
})
83+
.catch(error => {
84+
console.log(error);
85+
});
86+
};
87+
88+
const [inviteAll, setInviteAll] = useState(false);
89+
90+
if (!isOpen) return null;
91+
92+
return (
93+
<div className="search-modal mx-auto max-w-screen-xl relative z-10 focus:outline-none">
94+
<form action="#" className="mx-auto mb-4 mt-6 max-w-md space-y-4" onSubmit={handleSubmit}>
95+
<div className="search w-full relative rounded-md">
96+
<input
97+
className="search-input w-full border-gray-200 p-2 text-sm shadow-sm text-black"
98+
placeholder="Search text"
99+
id="circleName"
100+
/>
101+
<svg xmlns="http://www.w3.org/2000/svg"
102+
className="size-11 search-icon hover:cursor-pointer"
103+
fill="hsl(0, 0%, 95%)"
104+
viewBox="0 0 24 24"
105+
stroke="hsl(0, 0%, 95%)"
106+
strokeWidth="1">
107+
<path d="M 9 2 C 5.1458514 2 2 5.1458514 2 9 C 2 12.854149 5.1458514 16 9 16 C 10.747998 16 12.345009 15.348024 13.574219 14.28125 L 14 14.707031 L 14 16 L 20 22 L 22 20 L 16 14 L 14.707031 14 L 14.28125 13.574219 C 15.348024 12.345009 16 10.747998 16 9 C 16 5.1458514 12.854149 2 9 2 z M 9 4 C 11.773268 4 14 6.2267316 14 9 C 14 11.773268 11.773268 14 9 14 C 6.2267316 14 4 11.773268 4 9 C 4 6.2267316 6.2267316 4 9 4 z"></path>
108+
</svg>
109+
</div>
110+
<div className="flex items-center justify-between">
111+
<button
112+
type="submit"
113+
className="inline-block rounded-lg bg-blue-500 px-5 py-3 text-sm font-medium text-white"
114+
>
115+
Submit
116+
</button>
117+
</div>
118+
</form>
119+
</div>
120+
);
121+
}
122+
export default SearchModal;

0 commit comments

Comments
 (0)