Skip to content

Commit e48bbef

Browse files
authored
Feat/toogle update toggle delete (#541)
* fix: deleted jobs were showing * feat:added toggle update and toggle delete * minor change * feat:added cron job to delete jobs * minor change
1 parent 9ad5881 commit e48bbef

File tree

10 files changed

+288
-101
lines changed

10 files changed

+288
-101
lines changed

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
"seed": "node --import 'data:text/javascript,import { register } from \"node:module\"; import { pathToFileURL } from \"node:url\"; register(\"ts-node/esm\", pathToFileURL(\"./\"));' prisma/seed.ts"
3131
},
3232
"dependencies": {
33-
"100xdevs-job-board": "file:",
3433
"@aws-sdk/client-s3": "^3.645.0",
3534
"@aws-sdk/s3-request-presigner": "^3.645.0",
3635
"@emotion/react": "^11.13.3",
@@ -59,7 +58,6 @@
5958
"@types/lodash": "^4.17.7",
6059
"@types/uuid": "^10.0.0",
6160
"@uidotdev/usehooks": "^2.4.1",
62-
"100xdevs-job-board": "file:",
6361
"bcryptjs": "^2.4.3",
6462
"class-variance-authority": "^0.7.0",
6563
"clsx": "^2.1.1",
@@ -102,6 +100,7 @@
102100
"@types/react-dom": "^18",
103101
"@typescript-eslint/eslint-plugin": "^8.1.0",
104102
"@typescript-eslint/parser": "^8.1.0",
103+
"100xdevs-job-board": "file:",
105104
"eslint": "^8",
106105
"eslint-config-next": "14.2.5",
107106
"husky": "^9.1.4",

prisma/schema.prisma

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ model User {
2929
oauthId String?
3030
3131
blockedByAdmin DateTime?
32-
onBoard Boolean @default(false)
33-
bookmark Bookmark[]
32+
onBoard Boolean @default(false)
33+
bookmark Bookmark[]
3434
}
3535

3636
enum OauthProvider {
@@ -80,21 +80,21 @@ model Job {
8080
maxExperience Int?
8181
isVerifiedJob Boolean @default(false) @map("is_verified_job")
8282
deleted Boolean @default(false)
83+
deletedAt DateTime?
8384
postedAt DateTime @default(now())
8485
updatedAt DateTime @updatedAt
8586
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
86-
bookmark Bookmark[]
87+
bookmark Bookmark[]
8788
}
8889

8990
model Bookmark {
90-
id String @id @default(uuid())
91-
jobId String
92-
userId String
93-
job Job @relation(fields: [jobId],references: [id],onDelete: Cascade)
94-
user User @relation(fields: [userId],references: [id],onDelete: Cascade)
91+
id String @id @default(uuid())
92+
jobId String
93+
userId String
94+
job Job @relation(fields: [jobId], references: [id], onDelete: Cascade)
95+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
9596
}
9697

97-
9898
model Experience {
9999
id Int @id @default(autoincrement())
100100
companyName String

src/actions/corn.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// lib/cron.ts
22
import cron from 'node-cron';
3-
import { updateExpiredJobs } from './job.action';
3+
import { deleteOldDeltedJobs, updateExpiredJobs } from './job.action';
44

55
let cronJobInitialized = false;
66

@@ -12,6 +12,7 @@ export const startCronJob = () => {
1212
cron.schedule('0 0 * * *', async () => {
1313
try {
1414
await updateExpiredJobs();
15+
await deleteOldDeltedJobs();
1516
} catch (error) {
1617
console.error('Error updating expired jobs:', error);
1718
}

src/actions/job.action.ts

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -402,47 +402,85 @@ export const updateJob = withServerActionAsyncCatcher<
402402
).serialize();
403403
});
404404

405-
export const deleteJobById = withServerActionAsyncCatcher<
405+
export const toggleDeleteJobById = withServerActionAsyncCatcher<
406406
DeleteJobByIdSchemaType,
407407
ServerActionReturnType<deletedJob>
408408
>(async (data) => {
409409
const result = deleteJobByIdSchema.parse(data);
410410
const { id } = result;
411-
const deletedJob = await prisma.job.update({
411+
412+
// Fetch the current job's deleted status
413+
const job = await prisma.job.findUnique({
412414
where: {
413415
id: id,
414416
},
415-
data: {
417+
select: {
416418
deleted: true,
419+
deletedAt: true,
420+
},
421+
});
422+
423+
if (!job) {
424+
throw new Error('Job not found');
425+
}
426+
427+
const isNowDeleted = !job.deleted;
428+
const deletedAt = isNowDeleted ? new Date() : null;
429+
430+
const updatedJob = await prisma.job.update({
431+
where: {
432+
id: id,
433+
},
434+
data: {
435+
deleted: isNowDeleted,
436+
deletedAt: deletedAt,
417437
},
418438
});
419-
const deletedJobID = deletedJob.id;
439+
440+
const action = updatedJob.deleted ? 'Deleted' : 'Undeleted';
441+
const deletedJobID = updatedJob.id;
442+
420443
revalidatePath('/manage');
421-
return new SuccessResponse('Job Deleted successfully', 200, {
444+
445+
return new SuccessResponse(`Job ${action} successfully`, 200, {
422446
deletedJobID,
423447
}).serialize();
424448
});
425449

426-
export const approveJob = withAdminServerAction<
450+
export const toggleApproveJob = withAdminServerAction<
427451
ApproveJobSchemaType,
428452
ServerActionReturnType<ApprovedJobID>
429453
>(async (session, data) => {
430454
const result = ApproveJobSchema.safeParse(data);
431455
if (!result.success) {
432456
throw new Error(result.error.errors.toLocaleString());
433457
}
458+
434459
const { id } = result.data;
460+
461+
const job = await prisma.job.findUnique({
462+
where: { id: id },
463+
select: { isVerifiedJob: true },
464+
});
465+
466+
if (!job) {
467+
throw new Error('Job not found');
468+
}
469+
435470
await prisma.job.update({
436471
where: {
437472
id: id,
438473
},
439474
data: {
440-
isVerifiedJob: true,
475+
isVerifiedJob: !job.isVerifiedJob,
441476
},
442477
});
478+
443479
revalidatePath('/manage');
444-
return new SuccessResponse('Job Approved', 200, { jobId: id }).serialize();
480+
const message = job.isVerifiedJob ? 'Job Unapproved' : 'Job Approved';
481+
return new SuccessResponse(message, 200, { jobId: id }).serialize();
445482
});
483+
446484
export async function updateExpiredJobs() {
447485
const currentDate = new Date();
448486

@@ -458,6 +496,18 @@ export async function updateExpiredJobs() {
458496
},
459497
});
460498
}
499+
export const deleteOldDeltedJobs = async () => {
500+
const twoWeeksAgo = new Date(Date.now() - 14 * 24 * 60 * 60 * 1000);
501+
502+
await prisma.job.deleteMany({
503+
where: {
504+
deleted: true,
505+
deletedAt: {
506+
lte: twoWeeksAgo,
507+
},
508+
},
509+
});
510+
};
461511

462512
export async function toggleBookmarkAction(userId: string, jobId: string) {
463513
try {

src/components/DeleteDialog.tsx

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
'use client';
2+
import React, { useState } from 'react';
3+
import { Button } from './ui/button';
4+
import { useToast } from './ui/use-toast';
5+
import { toggleDeleteJobById } from '@/actions/job.action';
6+
import { JobType } from '@/types/jobs.types';
7+
import {
8+
Dialog,
9+
DialogTrigger,
10+
DialogContent,
11+
DialogHeader,
12+
DialogTitle,
13+
DialogDescription,
14+
DialogFooter,
15+
} from './ui/dialog';
16+
import { ArchiveRestore, Trash } from 'lucide-react';
17+
18+
const JobDialog = ({ job }: { job: JobType }) => {
19+
const [dialogOpen, setDialogOpen] = useState(false); // State to manage dialog visibility
20+
const { toast } = useToast();
21+
22+
const handelToggle = async () => {
23+
try {
24+
const result = await toggleDeleteJobById({ id: job.id });
25+
toast({
26+
title: result.message,
27+
variant: 'default',
28+
});
29+
setDialogOpen(false);
30+
} catch (error) {
31+
console.error(error);
32+
toast({ title: 'An Error occurred', variant: 'destructive' });
33+
}
34+
};
35+
36+
return (
37+
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
38+
<DialogTrigger>
39+
{job.deleted ? (
40+
<span
41+
className="mr-5"
42+
role="button"
43+
onClick={() => setDialogOpen(true)}
44+
>
45+
<ArchiveRestore /> {/* Icon for restoring the job */}
46+
</span>
47+
) : (
48+
<span
49+
className="mr-5"
50+
role="button"
51+
onClick={() => setDialogOpen(true)}
52+
>
53+
<Trash /> {/* Icon for deleting the job */}
54+
</span>
55+
)}
56+
</DialogTrigger>
57+
58+
<DialogContent>
59+
<DialogHeader>
60+
<DialogTitle>
61+
{job.deleted
62+
? 'Are you sure you want to Restore?'
63+
: 'Are you absolutely sure?'}
64+
</DialogTitle>
65+
<DialogDescription>
66+
{job.deleted
67+
? 'This action will restore the deleted job.'
68+
: 'This action cannot be undone. This will delete the selected job.'}
69+
</DialogDescription>
70+
</DialogHeader>
71+
72+
<DialogFooter>
73+
<Button
74+
className="mt-2"
75+
variant={job.deleted ? 'secondary' : 'destructive'}
76+
onClick={handelToggle}
77+
>
78+
{job.deleted ? 'Restore' : 'Delete'}
79+
</Button>
80+
</DialogFooter>
81+
</DialogContent>
82+
</Dialog>
83+
);
84+
};
85+
86+
export default JobDialog;

src/components/JobManagementTable.tsx

Lines changed: 10 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,12 @@ import {
66
TableHeader,
77
TableRow,
88
} from './ui/table';
9-
import { Edit, X } from 'lucide-react';
9+
import { Edit } from 'lucide-react';
10+
import { Badge } from '@/components/ui/badge';
11+
1012
import { getAllJobsAdditonalType } from '@/types/jobs.types';
1113
import { ServerActionReturnType } from '@/types/api.types';
12-
import {
13-
Dialog,
14-
DialogContent,
15-
DialogDescription,
16-
DialogFooter,
17-
DialogHeader,
18-
DialogTitle,
19-
DialogTrigger,
20-
} from '@/components/ui/dialog';
14+
2115
import { JobQuerySchemaType } from '@/lib/validators/jobs.validator';
2216
import { DEFAULT_PAGE, JOBS_PER_PAGE } from '@/config/app.config';
2317
import { Pagination, PaginationContent, PaginationItem } from './ui/pagination';
@@ -27,8 +21,8 @@ import {
2721
} from './pagination-client';
2822
import APP_PATHS from '@/config/path.config';
2923
import { PaginationPages } from './ui/paginator';
30-
import ApproveJobButton from './approveJobButton';
31-
import RemoveJobButton from './removeJobButton';
24+
import DeleteDialog from './DeleteDialog';
25+
import ToggleApproveJobButton from './ToggleApproveJobButton';
3226

3327
type props = {
3428
searchParams: JobQuerySchemaType;
@@ -64,42 +58,18 @@ const JobManagementTable = ({ jobs, searchParams }: props) => {
6458
<TableCell>{job?.workMode}</TableCell>
6559
<TableCell>{job?.city}</TableCell>
6660
<TableCell>
67-
{job.isVerifiedJob ? (
68-
job.deleted ? (
69-
<span className="bg-red-400 p-1 rounded-md text-secondary px-3 tracking-wide">
70-
Deleted
71-
</span>
72-
) : (
73-
<span className="bg-green-400 p-1 rounded-md text-secondary px-3 tracking-wide">
74-
Approved
75-
</span>
76-
)
61+
{job.deleted ? (
62+
<Badge className="bg-red-600 text-white">Deleted</Badge>
7763
) : (
78-
<ApproveJobButton jobId={job.id} />
64+
<ToggleApproveJobButton job={job} />
7965
)}
8066
</TableCell>
8167
<TableCell className="text-right w-full flex justify-end">
8268
<span className="mr-5" role="button">
8369
<Edit />
8470
</span>
8571
<span role="button">
86-
<Dialog>
87-
<DialogTrigger>
88-
<X />
89-
</DialogTrigger>
90-
<DialogContent>
91-
<DialogHeader>
92-
<DialogTitle>Are you absolutely sure?</DialogTitle>
93-
<DialogDescription>
94-
This action cannot be undone. This will Delete the
95-
Selected JOB.
96-
</DialogDescription>
97-
</DialogHeader>
98-
<DialogFooter>
99-
<RemoveJobButton jobId={job.id} />
100-
</DialogFooter>
101-
</DialogContent>
102-
</Dialog>
72+
<DeleteDialog job={job} />
10373
</span>
10474
</TableCell>
10575
</TableRow>

0 commit comments

Comments
 (0)