Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: created the bookmark functionality #518

Merged
merged 10 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

-- DropForeignKey
ALTER TABLE "Job" DROP CONSTRAINT "Job_userId_fkey";

Expand Down
14 changes: 14 additions & 0 deletions prisma/migrations/20241019104716_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- CreateTable
CREATE TABLE "Bookmark" (
"id" TEXT NOT NULL,
"jobId" TEXT NOT NULL,
"userId" TEXT NOT NULL,

CONSTRAINT "Bookmark_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "Bookmark" ADD CONSTRAINT "Bookmark_jobId_fkey" FOREIGN KEY ("jobId") REFERENCES "Job"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Bookmark" ADD CONSTRAINT "Bookmark_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
13 changes: 12 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ model User {
oauthId String?

blockedByAdmin DateTime?
onBoard Boolean @default(false)
onBoard Boolean @default(false)
bookmark Bookmark[]
}

enum OauthProvider {
Expand Down Expand Up @@ -82,8 +83,18 @@ model Job {
postedAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
bookmark Bookmark[]
}

model Bookmark {
id String @id @default(uuid())
jobId String
userId String
job Job @relation(fields: [jobId],references: [id],onDelete: Cascade)
user User @relation(fields: [userId],references: [id],onDelete: Cascade)
}


model Experience {
id Int @id @default(autoincrement())
companyName String
Expand Down
154 changes: 154 additions & 0 deletions src/actions/job.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,3 +450,157 @@ export async function updateExpiredJobs() {
},
});
}

export async function toggleBookmarkAction(userId: string, jobId: string) {
try {
if (!userId || !jobId) throw new Error('User or Post is missing');

const checkForUser = await prisma.user.findFirst({
where: { id: userId },
});

if (!checkForUser)
throw new ErrorHandler(
'User with this email does not exist',
'BAD_REQUEST'
);

const checkForBookmark = await prisma.bookmark.findFirst({
where: {
jobId: jobId,
userId: userId,
},
});

if (checkForBookmark) {
const deletedBookmark = await prisma.bookmark.delete({
where: {
id: checkForBookmark.id,
},
});

return {
status: 201,
message: 'Bookmark Deleted Successfully',
data: deletedBookmark,
};
}

const createNewBookmark = await prisma.bookmark.create({
data: {
jobId: jobId,
userId: userId,
},
});

return {
status: 200,
message: 'Bookmarked Successfully',
data: createNewBookmark,
};
} catch (error) {
return {
status: 404,
message: (error as Error).message,
data: null,
};
}
}

export async function GetBookmarkByUserId() {
try {
const auth = await getServerSession(authOptions);

if (!auth || !auth?.user?.id)
throw new ErrorHandler('Not Authrised', 'UNAUTHORIZED');

const userId = auth.user.id;

const getUserBookmarks = await prisma.bookmark.findMany({
where: {
userId: userId,
},

select: {
job: {
select: {
id: true,
type: true,
title: true,
description: true,
companyName: true,
city: true,
companyBio: true,
hasExperiencerange: true,
minExperience: true,
maxExperience: true,
hasExpiryDate: true,
expiryDate: true,
skills: true,
address: true,
workMode: true,
category: true,
minSalary: true,
maxSalary: true,
postedAt: true,
companyLogo: true,
},
},
},
});

if (!getUserBookmarks || getUserBookmarks.length === 0)
throw new Error('No Bookmarked Job found');

return {
status: 200,
message: 'Bookmarks fetched ',
data: getUserBookmarks,
};
} catch (error) {
return {
status: 404,
message: (error as Error).message,
data: null,
};
}
}

export async function GetUserBookmarksId() {
try {
const auth = await getServerSession(authOptions);

if (!auth || !auth?.user?.id)
throw new ErrorHandler('Not Authrised', 'UNAUTHORIZED');

const userId = auth.user.id;

const getUserBookmarks = await prisma.user.findFirst({
where: {
id: userId,
},

select: {
bookmark: {
select: {
jobId: true,
},
},
},
});

if (!getUserBookmarks) throw new Error('No Bookmarked Job found');

return {
status: 200,
message: 'Bookmarks fetched ',
data: getUserBookmarks.bookmark,
};
} catch (error) {
return {
status: 404,
message: (error as Error).message,
data: null,
};
}
}
4 changes: 4 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -407,3 +407,7 @@ html {
html {
scroll-behavior: smooth;
}

.no-scrollbar::-webkit-scrollbar {
display: none;
}
71 changes: 71 additions & 0 deletions src/app/profile/bookmarks/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use client';

import { GetBookmarkByUserId } from '@/actions/job.action';
import BookmarkCardSkeleton from '@/components/BookmarkCardSkeletion';
import JobCard from '@/components/Jobcard';

import { JobType } from '@/types/jobs.types';
import { useEffect, useState } from 'react';

export default function BookmarkPage() {
const [loading, setLoading] = useState(true);
const [errorNoPost, setErrorNoPost] = useState(false);
const [bookmarkedJobs, setBookmarkedJobs] = useState<
{
job: JobType;
}[]
>();

useEffect(() => {
const getBookmark = async () => {
setLoading(true);
try {
const response = await GetBookmarkByUserId();
if (response.status !== 200 || !response.data) {
return setErrorNoPost(true);
}
setBookmarkedJobs(response.data);
} finally {
setLoading(false);
}
};
getBookmark();
}, []);

return (
<div className="md:container flex flex-col w-full">
<div className="flex justify-between items-center">
<span>Bookmarks</span>
</div>

{loading ? (
<div className="space-y-4 w-full">
{[1, 2, 3, 4, 5].map((e: number) => (
<BookmarkCardSkeleton key={e} />
))}
</div>
) : (
<>
{errorNoPost ? (
<div className="w-full h-screen flex items-center justify-center text-white">
<p>No Bookmarks found</p>
</div>
) : (
<div className="h-full overflow-y-scroll no-scrollbar flex flex-col gap-8 my-3">
{bookmarkedJobs?.map(({ job }, index) => {
return (
<JobCard
job={job}
key={index}
className="w-full"
isBookmarked={true}
/>
);
})}
</div>
)}
</>
)}
</div>
);
}
2 changes: 1 addition & 1 deletion src/app/profile/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const EditProfilePage = () => {
const router = useRouter();
const session = useSession();
useEffect(() => {
if (session.status !== 'loading' && session.status === 'unauthenticated')
if (session.status === 'unauthenticated')
router.push(`${APP_PATHS.SIGNIN}?redirectTo=/profile`);
}, [session.status, router]);
const user = session.data?.user;
Expand Down
2 changes: 1 addition & 1 deletion src/app/profile/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const ProfileLayout = ({ children }: { children: React.ReactNode }) => {
return (
<div className="container flex max-md:flex-col md:gap-5 w-full relative">
<Sidebar />
<div className="flex px-2 w-full overflow-y-auto md:max-h-[73vh] lg:h-full md:border md:rounded-xl md:pt-6 ">
<div className="flex px-2 w-full overflow-y-auto md:max-h-[73vh] lg:h-full md:border md:rounded-xl md:pt-6 no-scrollbar">
{children}
</div>
</div>
Expand Down
28 changes: 28 additions & 0 deletions src/components/BookmarkCardSkeletion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Card, CardContent } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';

export default function BookmarkCardSkeleton() {
return (
<Card className=" border shadow-sm ">
<CardContent className="p-6">
<div className="flex flex-col sm:flex-row items-start gap-4">
<Skeleton className="w-12 h-12 rounded-full " />
<div className="flex-1 space-y-2">
<Skeleton className="w-48 h-6 " />
<Skeleton className="w-32 h-4 " />
<div className="flex flex-wrap gap-4">
<Skeleton className="w-20 h-6 " />
<Skeleton className="w-20 h-6 " />
<Skeleton className="w-32 h-6 " />
</div>
<div className="flex flex-wrap gap-2">
{[1, 2, 3, 4, 5, 6].map((skill) => (
<Skeleton key={skill} className="w-16 h-4 " />
))}
</div>
</div>
</div>
</CardContent>
</Card>
);
}
Loading
Loading