Skip to content

Commit 23d6510

Browse files
authored
NoticeBoard (#451)
1 parent 4365487 commit 23d6510

2 files changed

Lines changed: 4 additions & 303 deletions

File tree

Lines changed: 3 additions & 297 deletions
Original file line numberDiff line numberDiff line change
@@ -1,299 +1,5 @@
1-
"use client";
1+
import NoticeBoard from "@/components/Admin/NoticeBoard";
22

3-
import React, { useState, ChangeEvent, FormEvent } from "react";
4-
import { useRouter } from "next/navigation";
5-
6-
interface AnnouncementFormData {
7-
clubname: string;
8-
heading: string;
9-
info: string;
10-
announcelogo: string;
11-
}
12-
13-
export default function Announce(): JSX.Element {
14-
const router = useRouter();
15-
16-
const [group, setGroup] = useState("all");
17-
const [manualEmails, setManualEmails] = useState("");
18-
const [loading, setLoading] = useState(false);
19-
const [errors, setErrors] = useState<string[]>([]);
20-
const [logoPreview, setLogoPreview] = useState("");
21-
22-
const [formData, setFormData] = useState<AnnouncementFormData>({
23-
clubname: "CAMC",
24-
heading: "",
25-
info: "",
26-
announcelogo: "",
27-
});
28-
29-
const handleChange = (
30-
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
31-
) => {
32-
const { name, value } = e.target;
33-
34-
setFormData((prev) => ({
35-
...prev,
36-
[name]: value,
37-
}));
38-
39-
if (errors.length > 0) {
40-
setErrors([]);
41-
}
42-
};
43-
44-
const handleLogoChange = (e: ChangeEvent<HTMLInputElement>) => {
45-
const file = e.target.files?.[0];
46-
47-
if (!file) {
48-
return;
49-
}
50-
51-
const reader = new FileReader();
52-
53-
reader.onloadend = () => {
54-
const imageBase64 = reader.result as string;
55-
56-
setLogoPreview(imageBase64);
57-
58-
setFormData((prev) => ({
59-
...prev,
60-
announcelogo: imageBase64,
61-
}));
62-
};
63-
64-
reader.readAsDataURL(file);
65-
};
66-
67-
const validateForm = () => {
68-
const validationErrors: string[] = [];
69-
70-
if (!formData.heading.trim()) {
71-
validationErrors.push("Announcement heading is required.");
72-
}
73-
74-
if (!formData.info.trim()) {
75-
validationErrors.push("Announcement content is required.");
76-
}
77-
78-
setErrors(validationErrors);
79-
80-
return validationErrors.length === 0;
81-
};
82-
83-
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
84-
e.preventDefault();
85-
86-
if (!validateForm()) {
87-
return;
88-
}
89-
90-
setLoading(true);
91-
setErrors([]);
92-
93-
try {
94-
const emailsArray = manualEmails
95-
.split(",")
96-
.map((email) => email.trim())
97-
.filter((email) => email.length > 0);
98-
99-
const response = await fetch(
100-
`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/v1/announce`,
101-
{
102-
method: "POST",
103-
headers: {
104-
"Content-Type": "application/json",
105-
},
106-
credentials: "include",
107-
body: JSON.stringify({
108-
...formData,
109-
group,
110-
emails: emailsArray,
111-
}),
112-
}
113-
);
114-
115-
const result = await response.json();
116-
117-
if (!response.ok) {
118-
if (result.errors && Array.isArray(result.errors)) {
119-
setErrors(result.errors);
120-
} else {
121-
setErrors([result.message || "Failed to publish announcement."]);
122-
}
123-
124-
return;
125-
}
126-
127-
alert(result.message || "Announcement published successfully.");
128-
129-
setFormData({
130-
clubname: "CAMC",
131-
heading: "",
132-
info: "",
133-
announcelogo: "",
134-
});
135-
136-
setLogoPreview("");
137-
setManualEmails("");
138-
setGroup("all");
139-
140-
router.push("/admin/noticeboardview");
141-
} catch (error: any) {
142-
setErrors([
143-
error?.message || "Something went wrong while publishing announcement.",
144-
]);
145-
} finally {
146-
setLoading(false);
147-
}
148-
};
149-
150-
return (
151-
<div className="min-h-screen bg-slate-50 px-4 py-10">
152-
<div className="mx-auto w-full max-w-2xl rounded-2xl bg-white shadow-lg border border-slate-200">
153-
<div className="flex items-center justify-between border-b border-slate-200 px-8 py-6">
154-
<h1 className="text-2xl font-bold text-slate-900">
155-
Create Announcement
156-
</h1>
157-
158-
<button
159-
type="button"
160-
onClick={() => router.back()}
161-
className="flex h-9 w-9 items-center justify-center rounded-full bg-red-500 text-white hover:bg-red-600 transition"
162-
>
163-
×
164-
</button>
165-
</div>
166-
167-
<form onSubmit={handleSubmit} className="p-8">
168-
{errors.length > 0 && (
169-
<div className="mb-6 rounded-lg border border-red-200 bg-red-50 p-4">
170-
{errors.map((error, index) => (
171-
<p key={index} className="text-sm text-red-600">
172-
{error}
173-
</p>
174-
))}
175-
</div>
176-
)}
177-
178-
<div className="mb-6">
179-
<label className="mb-2 block text-sm font-semibold text-slate-700">
180-
Club Name
181-
</label>
182-
183-
<input
184-
type="text"
185-
value={formData.clubname}
186-
readOnly
187-
className="w-full rounded-lg border border-slate-300 bg-slate-100 px-4 py-3 text-slate-700"
188-
/>
189-
</div>
190-
191-
<div className="mb-6">
192-
<label className="mb-2 block text-sm font-semibold text-slate-700">
193-
Heading
194-
</label>
195-
196-
<input
197-
type="text"
198-
name="heading"
199-
value={formData.heading}
200-
onChange={handleChange}
201-
placeholder="Enter announcement heading"
202-
className="w-full rounded-lg border border-slate-300 px-4 py-3 outline-none focus:border-blue-600"
203-
/>
204-
</div>
205-
206-
<div className="mb-6">
207-
<label className="mb-2 block text-sm font-semibold text-slate-700">
208-
Announcement Content
209-
</label>
210-
211-
<textarea
212-
name="info"
213-
value={formData.info}
214-
onChange={handleChange}
215-
placeholder="Write your announcement here"
216-
rows={6}
217-
className="w-full rounded-lg border border-slate-300 px-4 py-3 outline-none focus:border-blue-600 resize-none"
218-
/>
219-
</div>
220-
221-
<div className="mb-6">
222-
<label className="mb-2 block text-sm font-semibold text-slate-700">
223-
Announcement Logo
224-
</label>
225-
226-
<div className="flex items-center gap-4">
227-
<label className="flex h-24 w-24 cursor-pointer items-center justify-center overflow-hidden rounded-lg border border-slate-300 bg-slate-50">
228-
{logoPreview ? (
229-
<img
230-
src={logoPreview}
231-
alt="Announcement logo"
232-
className="h-full w-full object-cover"
233-
/>
234-
) : (
235-
<span className="text-xs text-slate-400 text-center px-2">
236-
Upload Logo
237-
</span>
238-
)}
239-
240-
<input
241-
type="file"
242-
accept="image/*"
243-
onChange={handleLogoChange}
244-
className="hidden"
245-
/>
246-
</label>
247-
248-
<p className="text-sm text-slate-500">
249-
Optional image that will appear with the announcement.
250-
</p>
251-
</div>
252-
</div>
253-
254-
<div className="mb-6">
255-
<label className="mb-2 block text-sm font-semibold text-slate-700">
256-
Recipient Group
257-
</label>
258-
259-
<select
260-
value={group}
261-
onChange={(e) => setGroup(e.target.value)}
262-
className="w-full rounded-lg border border-slate-300 px-4 py-3 outline-none focus:border-blue-600"
263-
>
264-
<option value="none">None</option>
265-
<option value="all@iiti.ac.in">All Students</option>
266-
<option value="managerplacement@iiti.ac.in">Manager</option>
267-
</select>
268-
</div>
269-
270-
<div className="mb-8">
271-
<label className="mb-2 block text-sm font-semibold text-slate-700">
272-
Extra Emails
273-
</label>
274-
275-
<input
276-
type="text"
277-
value={manualEmails}
278-
onChange={(e) => setManualEmails(e.target.value)}
279-
placeholder="example1@gmail.com, example2@gmail.com"
280-
className="w-full rounded-lg border border-slate-300 px-4 py-3 outline-none focus:border-blue-600"
281-
/>
282-
283-
<p className="mt-2 text-sm text-slate-500">
284-
Enter additional email addresses separated by commas.
285-
</p>
286-
</div>
287-
288-
<button
289-
type="submit"
290-
disabled={loading}
291-
className="w-full rounded-lg bg-blue-700 px-4 py-3 text-white font-semibold hover:bg-blue-800 disabled:cursor-not-allowed disabled:bg-blue-400 transition"
292-
>
293-
{loading ? "Publishing..." : "Publish Announcement"}
294-
</button>
295-
</form>
296-
</div>
297-
</div>
298-
);
3+
export default function Page() {
4+
return <NoticeBoard />;
2995
}

src/components/Sidebar.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ import RecruiterDashboard from "./SideBar/Roles/recruiter";
1111
import FacultyDashboard from "./SideBar/Roles/faculty";
1212
import { jwtDecode } from "jwt-decode";
1313
import TpcMemberDashboard from "./SideBar/Roles/tpcMember";
14-
import NoticeBoard from "./Admin/NoticeBoard";
15-
import NoticeBoardView from "./Admin/NoticeBoardView";
14+
1615
const Sidebar = () => {
1716
const context = useContext(ToggleContext);
1817
const [isAdmin, setIsAdmin] = useState<boolean>(false);
@@ -103,8 +102,6 @@ const Sidebar = () => {
103102
{/* Mobile Role-specific Navigation */}
104103
<div className="space-y-2">
105104
{isAdmin && <AdminDashboard />}
106-
{isAdmin && <NoticeBoard />}
107-
{isAdmin && <NoticeBoardView/>}
108105
{isStudent && <StudentDashboard />}
109106
{isRecruiter && <RecruiterDashboard />}
110107
{isFaculty && <FacultyDashboard />}
@@ -245,8 +242,6 @@ const Sidebar = () => {
245242
<NavButtonGroup loggedIn={isLoggedIn} />
246243
</div>
247244
{isAdmin && <AdminDashboard />}
248-
{isAdmin && <NoticeBoard />}
249-
{isAdmin && <NoticeBoardView/>}
250245
{isStudent && <StudentDashboard />}
251246
{isRecruiter && <RecruiterDashboard />}
252247
{isFaculty && <FacultyDashboard />}

0 commit comments

Comments
 (0)