Skip to content

Commit 6fb83d6

Browse files
Merge pull request #22 from junaiddshaukat/fix-carrers-page
fix: carrers page issues and enhance filters
2 parents 8350aef + db2d4f4 commit 6fb83d6

File tree

3 files changed

+112
-20
lines changed

3 files changed

+112
-20
lines changed

app/admin/dashboard/jobs/page.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,6 @@ export default function JobsAdminPage() {
157157
if (!formData.title.trim()) return 'Title is required';
158158
if (!formData.company.trim()) return 'Company is required';
159159
if (!formData.description.trim()) return 'Description is required';
160-
if (!formData.applyUrl.trim() && !formData.applyEmail.trim()) return 'Provide apply URL or apply email';
161160
return '';
162161
};
163162

@@ -242,7 +241,7 @@ export default function JobsAdminPage() {
242241
<DialogContent className="max-w-2xl w-[calc(100vw-1.5rem)] sm:w-full max-h-[90vh] overflow-hidden p-4 sm:p-6 grid-rows-[auto,1fr]">
243242
<DialogHeader>
244243
<DialogTitle>{editingId ? 'Edit Job' : 'Create Job'}</DialogTitle>
245-
<DialogDescription>Fill in the job details. At least one apply method is required.</DialogDescription>
244+
<DialogDescription>Fill in the job details. Apply URL/Email are optional.</DialogDescription>
246245
</DialogHeader>
247246

248247
<form onSubmit={handleSubmit} className="flex flex-col min-h-0">

app/carrers/page.tsx

Lines changed: 111 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,12 @@ export default function CarrersPage() {
3737
const [loading, setLoading] = useState(true);
3838
const [search, setSearch] = useState('');
3939
const [audience, setAudience] = useState<'all' | 'Students' | 'Professionals' | 'Both'>('all');
40+
const [workplace, setWorkplace] = useState<'all' | 'Remote' | 'Hybrid' | 'Onsite'>('all');
41+
const [employment, setEmployment] = useState<'all' | 'Internship' | 'Full-time' | 'Part-time' | 'Contract'>('all');
4042
const [applyOpen, setApplyOpen] = useState(false);
4143
const [selectedJob, setSelectedJob] = useState<JobDoc | null>(null);
44+
const [detailsOpen, setDetailsOpen] = useState(false);
45+
const [detailsJob, setDetailsJob] = useState<JobDoc | null>(null);
4246

4347
useEffect(() => {
4448
fetch('/api/jobs')
@@ -51,6 +55,8 @@ export default function CarrersPage() {
5155
const q = search.trim().toLowerCase();
5256
return jobs.filter((j) => {
5357
if (audience !== 'all' && (j.audience || 'Both') !== audience) return false;
58+
if (workplace !== 'all' && (j.workplaceType || 'Remote') !== workplace) return false;
59+
if (employment !== 'all' && (j.employmentType || 'Full-time') !== employment) return false;
5460
if (!q) return true;
5561
return (
5662
j.title?.toLowerCase().includes(q) ||
@@ -59,13 +65,18 @@ export default function CarrersPage() {
5965
(j.tags || []).join(' ').toLowerCase().includes(q)
6066
);
6167
});
62-
}, [jobs, search, audience]);
68+
}, [jobs, search, audience, workplace, employment]);
6369

6470
const openApply = (job: JobDoc) => {
6571
setSelectedJob(job);
6672
setApplyOpen(true);
6773
};
6874

75+
const openDetails = (job: JobDoc) => {
76+
setDetailsJob(job);
77+
setDetailsOpen(true);
78+
};
79+
6980
const copyToClipboard = async (text: string) => {
7081
try {
7182
await navigator.clipboard.writeText(text);
@@ -79,7 +90,7 @@ export default function CarrersPage() {
7990
<div className="max-w-3xl">
8091
<h1 className="text-3xl sm:text-4xl font-bold">Careers</h1>
8192
<p className="text-muted-foreground mt-3">
82-
Hand-picked opportunities for students and professionals from our community.
93+
Hand-picked opportunities for students and professionals across Dev Weekends and Dev Weekends partner companies.
8394
</p>
8495
</div>
8596

@@ -101,6 +112,29 @@ export default function CarrersPage() {
101112
<SelectItem value="Both">Both</SelectItem>
102113
</SelectContent>
103114
</Select>
115+
<Select value={workplace} onValueChange={(v) => setWorkplace(v as any)}>
116+
<SelectTrigger className="w-[180px]">
117+
<SelectValue placeholder="Workplace" />
118+
</SelectTrigger>
119+
<SelectContent>
120+
<SelectItem value="all">All Workplaces</SelectItem>
121+
<SelectItem value="Remote">Remote</SelectItem>
122+
<SelectItem value="Hybrid">Hybrid</SelectItem>
123+
<SelectItem value="Onsite">Onsite</SelectItem>
124+
</SelectContent>
125+
</Select>
126+
<Select value={employment} onValueChange={(v) => setEmployment(v as any)}>
127+
<SelectTrigger className="w-[180px]">
128+
<SelectValue placeholder="Employment" />
129+
</SelectTrigger>
130+
<SelectContent>
131+
<SelectItem value="all">All Employment</SelectItem>
132+
<SelectItem value="Internship">Internship</SelectItem>
133+
<SelectItem value="Full-time">Full-time</SelectItem>
134+
<SelectItem value="Part-time">Part-time</SelectItem>
135+
<SelectItem value="Contract">Contract</SelectItem>
136+
</SelectContent>
137+
</Select>
104138
<div className="flex-1" />
105139
<Button asChild variant="outline">
106140
<Link href="https://linktr.ee/DevWeekends" target="_blank" rel="noopener noreferrer">
@@ -159,9 +193,14 @@ export default function CarrersPage() {
159193
) : null}
160194
</CardContent>
161195
<CardFooter className="pt-0">
162-
<Button className="w-full" onClick={() => openApply(job)}>
163-
Apply
164-
</Button>
196+
<div className="w-full flex flex-col sm:flex-row gap-2">
197+
<Button className="w-full" variant="outline" onClick={() => openDetails(job)}>
198+
Read more
199+
</Button>
200+
<Button className="w-full" onClick={() => openApply(job)}>
201+
Apply
202+
</Button>
203+
</div>
165204
</CardFooter>
166205
</Card>
167206
);
@@ -170,6 +209,73 @@ export default function CarrersPage() {
170209
)}
171210
</div>
172211

212+
<Dialog
213+
open={detailsOpen}
214+
onOpenChange={(open) => {
215+
setDetailsOpen(open);
216+
if (!open) setDetailsJob(null);
217+
}}
218+
>
219+
<DialogContent className="w-[calc(100vw-1.5rem)] sm:w-full max-w-2xl max-h-[90vh] overflow-hidden p-4 sm:p-6 grid-rows-[auto,1fr]">
220+
<DialogHeader>
221+
<DialogTitle>Job Details</DialogTitle>
222+
<DialogDescription>
223+
{detailsJob ? (
224+
<>
225+
{detailsJob.title} @ {detailsJob.company}
226+
</>
227+
) : (
228+
'Job details'
229+
)}
230+
</DialogDescription>
231+
</DialogHeader>
232+
233+
<div className="min-h-0 overflow-y-auto pr-1 space-y-4">
234+
<div className="text-sm text-muted-foreground space-y-1">
235+
<div>
236+
<span className="text-foreground font-semibold">Company:</span>{' '}
237+
<span className="text-foreground">{detailsJob?.company || ''}</span>
238+
</div>
239+
<div>
240+
<span className="text-foreground font-semibold">Location:</span>{' '}
241+
<span>{detailsJob?.location || 'Not specified'}</span>
242+
</div>
243+
<div>
244+
<span className="text-foreground font-semibold">Deadline:</span>{' '}
245+
<span>
246+
{detailsJob?.deadline ? new Date(detailsJob.deadline).toLocaleDateString() : 'Not specified'}
247+
</span>
248+
</div>
249+
</div>
250+
251+
<div className="space-y-2">
252+
<div className="text-sm font-semibold">Description</div>
253+
<div className="text-sm whitespace-pre-wrap">{detailsJob?.description || ''}</div>
254+
</div>
255+
256+
<div className="space-y-2">
257+
<div className="text-sm font-semibold">Requirements</div>
258+
<div className="text-sm whitespace-pre-wrap">
259+
{detailsJob?.requirements?.trim() ? detailsJob.requirements : 'Not provided'}
260+
</div>
261+
</div>
262+
263+
{Array.isArray(detailsJob?.tags) && detailsJob!.tags!.length > 0 ? (
264+
<div className="space-y-2">
265+
<div className="text-sm font-semibold">Tags</div>
266+
<div className="flex flex-wrap gap-1">
267+
{detailsJob!.tags!.map((t, idx) => (
268+
<Badge key={`details-${detailsJob!._id}-${idx}`} variant="outline">
269+
{t}
270+
</Badge>
271+
))}
272+
</div>
273+
</div>
274+
) : null}
275+
</div>
276+
</DialogContent>
277+
</Dialog>
278+
173279
<Dialog
174280
open={applyOpen}
175281
onOpenChange={(open) => {

models/Job.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,6 @@ const jobSchema = new Schema(
2626
{ timestamps: true }
2727
);
2828

29-
// Basic safety: don't allow jobs without any apply method
30-
jobSchema.pre('validate', function (next: (err?: any) => void) {
31-
// @ts-expect-error - mongoose doc typing
32-
const applyUrl = (this.applyUrl || '').toString().trim();
33-
// @ts-expect-error - mongoose doc typing
34-
const applyEmail = (this.applyEmail || '').toString().trim();
35-
if (!applyUrl && !applyEmail) {
36-
// @ts-expect-error - mongoose doc typing
37-
this.invalidate('applyUrl', 'Either applyUrl or applyEmail is required');
38-
}
39-
next();
40-
});
41-
4229
export const Job = models.Job || mongoose.model('Job', jobSchema);
4330

4431

0 commit comments

Comments
 (0)