diff --git a/.gitignore b/.gitignore
index ba44e4d0..82fc4d5c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,3 +41,9 @@ bun.lockb
package-lock.json
yarn.lock
+**/public/sw.js
+**/public/workbox-*.js
+**/public/worker-*.js
+**/public/sw.js.map
+**/public/workbox-*.js.map
+**/public/worker-*.js.map
\ No newline at end of file
diff --git a/next.config.js b/next.config.js
index a715d6bf..376019e0 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,3 +1,4 @@
+// next.config.js
import { fileURLToPath } from 'node:url';
import createJiti from 'jiti';
@@ -20,12 +21,11 @@ const nextConfig = {
remotePatterns: [
{
protocol: 'https',
- hostname: 'job-board.b-cdn.net', // Change this to your CDN domain
+ hostname: 'job-board.b-cdn.net',
},
{
protocol: 'https',
hostname: 'lh3.googleusercontent.com',
- // Change this to your CDN domain
},
{
protocol: 'https',
@@ -39,4 +39,4 @@ const nextConfig = {
},
};
-export default nextConfig; // ES module export
+export default nextConfig;
diff --git a/package.json b/package.json
index c8c006cd..2a7f56c0 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
"@mui/material": "^6.1.3",
"@prisma/client": "5.18.0",
"@radix-ui/react-accordion": "^1.2.0",
+ "@radix-ui/react-alert-dialog": "^1.1.2",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
@@ -79,6 +80,7 @@
"react": "^18",
"react-day-picker": "^8.10.1",
"react-dom": "^18",
+ "react-dropzone": "^14.2.10",
"react-hook-form": "^7.52.2",
"react-icons": "^5.2.1",
"react-quill": "^2.0.0",
diff --git a/prisma/migrations/20241025120951_resume_update_date/migration.sql b/prisma/migrations/20241025120951_resume_update_date/migration.sql
new file mode 100644
index 00000000..6b96159f
--- /dev/null
+++ b/prisma/migrations/20241025120951_resume_update_date/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "User" ADD COLUMN "resumeUpdateDate" TIMESTAMP(3);
diff --git a/prisma/migrations/20241101174959_added_some_user_feilds/migration.sql b/prisma/migrations/20241101174959_added_some_user_feilds/migration.sql
new file mode 100644
index 00000000..f6bac3e5
--- /dev/null
+++ b/prisma/migrations/20241101174959_added_some_user_feilds/migration.sql
@@ -0,0 +1,34 @@
+-- CreateEnum
+CREATE TYPE "DegreeType" AS ENUM ('BTech', 'MTech', 'BCA', 'MCA');
+
+-- CreateEnum
+CREATE TYPE "FieldOfStudyType" AS ENUM ('AI', 'Machine_Learning', 'CS', 'Mechanical');
+
+-- AlterTable
+ALTER TABLE "Project" ADD COLUMN "isFeature" BOOLEAN NOT NULL DEFAULT false;
+
+-- AlterTable
+ALTER TABLE "User" ADD COLUMN "about" TEXT,
+ADD COLUMN "contactEmail" TEXT,
+ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ADD COLUMN "discordLink" TEXT,
+ADD COLUMN "githubLink" TEXT,
+ADD COLUMN "linkedinLink" TEXT,
+ADD COLUMN "portfolioLink" TEXT,
+ADD COLUMN "twitterLink" TEXT;
+
+-- CreateTable
+CREATE TABLE "Education" (
+ "id" SERIAL NOT NULL,
+ "instituteName" TEXT NOT NULL,
+ "degree" "DegreeType" NOT NULL,
+ "fieldOfStudy" "FieldOfStudyType" NOT NULL,
+ "startDate" TIMESTAMP(3) NOT NULL,
+ "endDate" TIMESTAMP(3),
+ "userId" TEXT NOT NULL,
+
+ CONSTRAINT "Education_pkey" PRIMARY KEY ("id")
+);
+
+-- AddForeignKey
+ALTER TABLE "Education" ADD CONSTRAINT "Education_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 64566f13..859197b0 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -8,40 +8,50 @@ datasource db {
}
model User {
- id String @id @default(cuid())
- name String
- email String @unique
- password String?
- avatar String?
- isVerified Boolean @default(false)
- role Role @default(USER)
+ id String @id @default(cuid())
+ name String
+ email String @unique
+ password String?
+ avatar String?
+ isVerified Boolean @default(false)
+ role Role @default(USER)
emailVerified DateTime?
- jobs Job[]
- skills String[]
- experience Experience[]
- project Project[]
- resume String?
-
- oauthProvider OauthProvider?
- oauthId String?
+ jobs Job[]
+ skills String[]
+ experience Experience[]
+ project Project[]
+ resume String?
+ oauthProvider OauthProvider? // Tracks OAuth provider (e.g., 'google')
+ oauthId String?
+ createdAt DateTime @default(now())
blockedByAdmin DateTime?
- onBoard Boolean @default(false)
- bookmark Bookmark[]
- company Company? @relation("UserCompany")
- companyId String? @unique
+ onBoard Boolean @default(false)
+ bookmark Bookmark[]
+ company Company? @relation("UserCompany")
+ companyId String? @unique
+ education Education[]
+ resumeUpdateDate DateTime?
+ contactEmail String?
+ about String?
+ discordLink String?
+ linkedinLink String?
+ twitterLink String?
+ githubLink String?
+ portfolioLink String?
+
}
model Company {
- id String @id @default(cuid())
+ id String @id @default(cuid())
name String
logo String?
website String?
description String?
jobs Job[]
- user User? @relation("UserCompany", fields: [userId], references: [id])
- userId String? @unique
+ user User? @relation("UserCompany", fields: [userId], references: [id])
+ userId String? @unique
}
enum OauthProvider {
@@ -96,21 +106,21 @@ model Job {
postedAt DateTime @default(now())
updatedAt DateTime @updatedAt
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
- company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
- bookmark Bookmark[]
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+ company Company @relation(fields: [companyId], 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)
+ 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())
+ id Int @id @default(autoincrement())
companyName String
designation String
EmploymentType EmployementType
@@ -121,7 +131,18 @@ model Experience {
endDate DateTime?
description String
userId String
- user User @relation(fields: [userId], references: [id])
+ user User @relation(fields: [userId], references: [id])
+}
+
+model Education {
+ id Int @id @default(autoincrement())
+ instituteName String
+ degree DegreeType
+ fieldOfStudy FieldOfStudyType
+ startDate DateTime
+ endDate DateTime?
+ userId String
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model Project {
@@ -134,6 +155,7 @@ model Project {
stack ProjectStack @default(OTHERS)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
+ isFeature Boolean @default(false)
}
enum ProjectStack {
@@ -169,3 +191,17 @@ enum EmployementType {
Internship
Contract
}
+
+enum DegreeType {
+ BTech
+ MTech
+ BCA
+ MCA
+}
+
+enum FieldOfStudyType {
+ AI
+ Machine_Learning
+ CS
+ Mechanical
+}
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 00000000..a4fb0fd7
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,6 @@
+User-agent: *
+Allow: /
+Disallow: /admin/*
+Disallow: /manage/*
+
+Sitemap: https://job.vineet.tech/sitemap.xml
\ No newline at end of file
diff --git a/src/actions/job.action.ts b/src/actions/job.action.ts
index 54b6216e..e5d1b9a2 100644
--- a/src/actions/job.action.ts
+++ b/src/actions/job.action.ts
@@ -158,6 +158,7 @@ export const getAllJobs = withSession<
skills: true,
address: true,
workMode: true,
+ expired: true,
category: true,
minSalary: true,
maxSalary: true,
@@ -221,6 +222,7 @@ export const getRecommendedJobs = withServerActionAsyncCatcher<
maxSalary: true,
postedAt: true,
skills: true,
+ expired: true,
isVerifiedJob: true,
companyLogo: true,
},
@@ -254,6 +256,7 @@ export const getRecommendedJobs = withServerActionAsyncCatcher<
companyLogo: true,
minExperience: true,
maxExperience: true,
+ expired: true,
isVerifiedJob: true,
category: true,
},
@@ -296,6 +299,7 @@ export const getJobById = withServerActionAsyncCatcher<
minExperience: true,
maxExperience: true,
skills: true,
+ expired: true,
address: true,
workMode: true,
hasSalaryRange: true,
@@ -354,6 +358,7 @@ export const getRecentJobs = async () => {
minExperience: true,
maxExperience: true,
skills: true,
+ expired: true,
postedAt: true,
companyLogo: true,
type: true,
@@ -603,6 +608,7 @@ export async function GetBookmarkByUserId() {
minSalary: true,
maxSalary: true,
postedAt: true,
+ expired: true,
companyLogo: true,
},
},
diff --git a/src/actions/user.profile.actions.ts b/src/actions/user.profile.actions.ts
index 51128cda..ad8732a9 100644
--- a/src/actions/user.profile.actions.ts
+++ b/src/actions/user.profile.actions.ts
@@ -1,8 +1,14 @@
'use server';
import prisma from '@/config/prisma.config';
import {
+ aboutMeSchema,
+ AboutMeSchemaType,
addSkillsSchemaType,
expFormSchemaType,
+ profileEducationType,
+ ProfileProjectType,
+ profileSchema,
+ ProfileSchemaType,
projectSchemaType,
UserProfileSchemaType,
} from '@/lib/validators/user.profile.validator';
@@ -13,6 +19,8 @@ import { ErrorHandler } from '@/lib/error';
import { withServerActionAsyncCatcher } from '@/lib/async-catch';
import { ServerActionReturnType } from '@/types/api.types';
import { SuccessResponse } from '@/lib/success';
+import { withSession } from '@/lib/session';
+import { revalidatePath } from 'next/cache';
export const updateUser = async (
email: string,
@@ -39,36 +47,39 @@ export const updateUser = async (
}
};
-export const changePassword = async (
- email: string,
- data: {
- currentPassword: string;
- newPassword: string;
- confirmNewPassword: string;
- }
-) => {
+export const changePassword = async (data: {
+ currentPassword: string;
+ newPassword: string;
+ confirmNewPassword: string;
+}) => {
try {
- const existingUser = await prisma.user.findFirst({
- where: { email: email },
- });
+ const auth = await getServerSession(authOptions);
+
+ if (!auth || !auth?.user?.id)
+ throw new ErrorHandler('Not Authorized', 'UNAUTHORIZED');
+
const { currentPassword, newPassword, confirmNewPassword } = data;
if (!currentPassword || !newPassword || !confirmNewPassword) {
- return { error: 'Password is required' };
+ throw new ErrorHandler('Invalid Data', 'BAD_REQUEST');
}
if (newPassword !== confirmNewPassword) {
- return { error: 'Passwords do not match' };
+ throw new ErrorHandler('Invalid Data', 'BAD_REQUEST');
}
- if (!existingUser) return { error: 'User not found!' };
+ const existingUser = await prisma.user.findFirst({
+ where: { id: auth.user.id },
+ });
+
+ if (!existingUser) throw new ErrorHandler('User Not Found', 'NOT_FOUND');
if (!existingUser.password) {
- return { error: 'User password not found!' };
+ throw new ErrorHandler('Invalid Credientials', 'AUTHENTICATION_FAILED');
}
const matchPassword = await bcryptjs.compare(
currentPassword,
existingUser.password
);
if (!matchPassword) {
- return { error: 'Invalid credentials' };
+ throw new ErrorHandler('Invalid Credientials', 'AUTHENTICATION_FAILED');
}
const hashedPassword = await bcryptjs.hash(newPassword, 10);
@@ -80,9 +91,15 @@ export const changePassword = async (
},
});
- return { success: 'Your password has been successfully updated.' };
- } catch (error) {
- return { error: error };
+ return new SuccessResponse(
+ 'Successfully Password Updated.',
+ 200
+ ).serialize();
+ } catch (_error) {
+ throw new ErrorHandler(
+ 'Something went wrong while changing password.',
+ 'INTERNAL_SERVER_ERROR'
+ );
}
};
@@ -155,6 +172,7 @@ export const addUserSkills = withServerActionAsyncCatcher<
skills: data.skills,
},
});
+ revalidatePath(`/newProfile/${auth.user.id}`);
return new SuccessResponse('Skills updated successfully', 200).serialize();
} catch (_error) {
return new ErrorHandler('Internal server error', 'DATABASE_ERROR');
@@ -177,14 +195,37 @@ export const addUserExperience = withServerActionAsyncCatcher<
userId: auth.user.id,
},
});
+ revalidatePath(`/newProfile/${auth.user.id}`);
return new SuccessResponse(
- 'Experience updated successfully',
+ 'Experience added successfully',
200
).serialize();
} catch (_error) {
return new ErrorHandler('Internal server error', 'DATABASE_ERROR');
}
});
+export const addUserEducation = withServerActionAsyncCatcher<
+ profileEducationType,
+ ServerActionReturnType
+>(async (data) => {
+ const auth = await getServerSession(authOptions);
+
+ if (!auth || !auth?.user?.id)
+ throw new ErrorHandler('Not Authorized', 'UNAUTHORIZED');
+
+ try {
+ await prisma.education.create({
+ data: {
+ ...data,
+ userId: auth.user.id,
+ },
+ });
+ revalidatePath(`/newProfile/${auth.user.id}`);
+ return new SuccessResponse('Education added successfully', 200).serialize();
+ } catch (_error) {
+ return new ErrorHandler('Internal server error', 'DATABASE_ERROR');
+ }
+});
export const addUserProjects = withServerActionAsyncCatcher<
projectSchemaType,
@@ -202,6 +243,7 @@ export const addUserProjects = withServerActionAsyncCatcher<
userId: auth.user.id,
},
});
+ revalidatePath(`/newProfile/${auth.user.id}`);
return new SuccessResponse('Project updated successfully', 200).serialize();
} catch (_error) {
return new ErrorHandler('Internal server error', 'DATABASE_ERROR');
@@ -244,8 +286,10 @@ export const addUserResume = async (resume: string) => {
},
data: {
resume: resume,
+ resumeUpdateDate: new Date(),
},
});
+ revalidatePath(`/newProfile/${auth.user.id}`);
return new SuccessResponse('Resume SuccessFully Uploaded', 200).serialize();
} catch (_error) {
return new ErrorHandler('Internal server error', 'DATABASE_ERROR');
@@ -317,3 +361,302 @@ export const getUserDetails = async () => {
return new ErrorHandler('Internal server error', 'DATABASE_ERROR');
}
};
+
+export const getUserDetailsWithId = async (id: string) => {
+ try {
+ const res = await prisma.user.findFirst({
+ where: {
+ id: id,
+ },
+ select: {
+ name: true,
+ id: true,
+ skills: true,
+ education: true,
+ experience: true,
+ email: true,
+ contactEmail: true,
+ resume: true,
+ avatar: true,
+ about: true,
+ project: true,
+ resumeUpdateDate: true,
+ discordLink: true,
+ githubLink: true,
+ linkedinLink: true,
+ portfolioLink: true,
+ twitterLink: true,
+ },
+ });
+ if (!res) throw new ErrorHandler('User Not Found', 'NOT_FOUND');
+ return new SuccessResponse(
+ 'User SuccessFully Fetched',
+ 200,
+ res
+ ).serialize();
+ } catch (_error) {
+ return new ErrorHandler('Internal server error', 'DATABASE_ERROR');
+ }
+};
+
+export const updateUserDetails = withSession<
+ ProfileSchemaType,
+ ServerActionReturnType
+>(async (session, userData) => {
+ if (!session || !session?.user?.id) {
+ throw new ErrorHandler('Not Authrised', 'UNAUTHORIZED');
+ }
+ const { success, data } = profileSchema.safeParse(userData);
+ if (!success) {
+ throw new ErrorHandler('Invalid Data', 'BAD_REQUEST');
+ }
+
+ if (data) {
+ await prisma.user.update({
+ where: {
+ id: session.user.id,
+ },
+ data: {
+ name: data.name,
+ email: data.email,
+ contactEmail: data.contactEmail,
+ about: data.aboutMe,
+ avatar: data.avatar,
+ discordLink: data.discordLink,
+ linkedinLink: data.linkedinLink,
+ twitterLink: data.twitterLink,
+ portfolioLink: data.portfolioLink,
+ githubLink: data.githubLink,
+ },
+ });
+ revalidatePath(`/newProfile/${session.user.id}`);
+ }
+
+ return new SuccessResponse(
+ 'User profile updated successfully.',
+ 200
+ ).serialize();
+});
+
+export const updateAboutMe = withSession<
+ AboutMeSchemaType,
+ ServerActionReturnType
+>(async (session, userData) => {
+ if (!session || !session?.user?.id) {
+ throw new ErrorHandler('Not Authrised', 'UNAUTHORIZED');
+ }
+ const { success, data } = aboutMeSchema.safeParse(userData);
+ if (!success) {
+ throw new ErrorHandler('Invalid Data', 'BAD_REQUEST');
+ }
+
+ if (data) {
+ await prisma.user.update({
+ where: {
+ id: session.user.id,
+ },
+ data: {
+ about: data.aboutMe,
+ },
+ });
+ }
+ revalidatePath(`/newProfile/${session.user.id}`);
+ return new SuccessResponse('Successfully updated About Me.', 200).serialize();
+});
+
+export const deleteResume = async () => {
+ const auth = await getServerSession(authOptions);
+
+ if (!auth || !auth?.user?.id)
+ throw new ErrorHandler('Not Authorized', 'UNAUTHORIZED');
+ try {
+ // todo: delete file form cdn
+ await prisma.user.update({
+ where: {
+ id: auth.user.id,
+ },
+ data: {
+ resume: null,
+ resumeUpdateDate: null,
+ },
+ });
+ revalidatePath(`/newProfile/${auth.user.id}`);
+ return new SuccessResponse('Resume Deleted Successfully', 200).serialize();
+ } catch (_error) {
+ return new ErrorHandler('Internal server error', 'DATABASE_ERROR');
+ }
+};
+export const deleteProject = async (projectId: number) => {
+ const auth = await getServerSession(authOptions);
+
+ if (!auth || !auth?.user?.id)
+ throw new ErrorHandler('Not Authorized', 'UNAUTHORIZED');
+ try {
+ // todo: delete image file form cdn
+ await prisma.project.delete({
+ where: {
+ id: projectId,
+ },
+ });
+ revalidatePath(`/newProfile/${auth.user.id}`);
+ return new SuccessResponse('Project Deleted Successfully', 200).serialize();
+ } catch (_error) {
+ return new ErrorHandler('Internal server error', 'DATABASE_ERROR');
+ }
+};
+
+export const editProject = withServerActionAsyncCatcher<
+ { data: ProfileProjectType; id: number },
+ ServerActionReturnType
+>(async ({ data, id }) => {
+ const auth = await getServerSession(authOptions);
+
+ if (!auth || !auth?.user?.id)
+ throw new ErrorHandler('Not Authorized', 'UNAUTHORIZED');
+
+ try {
+ await prisma.project.update({
+ where: {
+ id: id,
+ userId: auth.user.id,
+ },
+ data: {
+ ...data,
+ },
+ });
+ revalidatePath(`/newProfile/${auth.user.id}`);
+ return new SuccessResponse('Project updated successfully', 200).serialize();
+ } catch (_error) {
+ return new ErrorHandler('Internal server error', 'DATABASE_ERROR');
+ }
+});
+export const editExperience = withServerActionAsyncCatcher<
+ { data: expFormSchemaType; id: number },
+ ServerActionReturnType
+>(async ({ data, id }) => {
+ const auth = await getServerSession(authOptions);
+
+ if (!auth || !auth?.user?.id)
+ throw new ErrorHandler('Not Authorized', 'UNAUTHORIZED');
+
+ try {
+ await prisma.experience.update({
+ where: {
+ id: id,
+ userId: auth.user.id,
+ },
+ data: {
+ ...data,
+ },
+ });
+ revalidatePath(`/newProfile/${auth.user.id}`);
+ return new SuccessResponse(
+ 'Experience updated successfully',
+ 200
+ ).serialize();
+ } catch (_error) {
+ return new ErrorHandler('Internal server error', 'DATABASE_ERROR');
+ }
+});
+export const editEducation = withServerActionAsyncCatcher<
+ { data: profileEducationType; id: number },
+ ServerActionReturnType
+>(async ({ data, id }) => {
+ const auth = await getServerSession(authOptions);
+
+ if (!auth || !auth?.user?.id)
+ throw new ErrorHandler('Not Authorized', 'UNAUTHORIZED');
+
+ try {
+ await prisma.education.update({
+ where: {
+ id: id,
+ userId: auth.user.id,
+ },
+ data: {
+ ...data,
+ },
+ });
+ revalidatePath(`/newProfile/${auth.user.id}`);
+ return new SuccessResponse(
+ 'Experience updated successfully',
+ 200
+ ).serialize();
+ } catch (_error) {
+ return new ErrorHandler('Internal server error', 'DATABASE_ERROR');
+ }
+});
+
+export const deleteExperience = async (experienceId: number) => {
+ const auth = await getServerSession(authOptions);
+
+ if (!auth || !auth?.user?.id)
+ throw new ErrorHandler('Not Authorized', 'UNAUTHORIZED');
+ try {
+ // todo: delete image file form cdn
+ await prisma.experience.delete({
+ where: {
+ id: experienceId,
+ userId: auth.user.id,
+ },
+ });
+ revalidatePath(`/newProfile/${auth.user.id}`);
+ return new SuccessResponse('Project Deleted Successfully', 200).serialize();
+ } catch (_error) {
+ return new ErrorHandler('Internal server error', 'DATABASE_ERROR');
+ }
+};
+export const deleteEducation = async (educationId: number) => {
+ const auth = await getServerSession(authOptions);
+
+ if (!auth || !auth?.user?.id)
+ throw new ErrorHandler('Not Authorized', 'UNAUTHORIZED');
+ try {
+ // todo: delete image file form cdn
+ await prisma.education.delete({
+ where: {
+ id: educationId,
+ userId: auth.user.id,
+ },
+ });
+ revalidatePath(`/newProfile/${auth.user.id}`);
+ return new SuccessResponse('Project Deleted Successfully', 200).serialize();
+ } catch (_error) {
+ return new ErrorHandler('Internal server error', 'DATABASE_ERROR');
+ }
+};
+export const getUserRecruiters = async () => {
+ const auth = await getServerSession(authOptions);
+
+ if (!auth || !auth?.user?.id || auth?.user?.role !== 'ADMIN')
+ throw new ErrorHandler('Not Authorized', 'UNAUTHORIZED');
+ try {
+ const res = await prisma.user.findMany({
+ where: {
+ role: 'HR',
+ },
+ select: {
+ id: true,
+ email: true,
+ name: true,
+ createdAt: true,
+ _count: {
+ select: {
+ jobs: true,
+ },
+ },
+ company: {
+ select: {
+ name: true,
+ website: true,
+ },
+ },
+ },
+ });
+ return new SuccessResponse('Recruiter SuccessFully Fetched', 200, {
+ recruiters: res,
+ }).serialize();
+ } catch (_error) {
+ return new ErrorHandler('Internal server error', 'DATABASE_ERROR');
+ }
+};
diff --git a/src/app/(auth)/verify-email/[token]/EmailVerificationLinkExpired.tsx b/src/app/(auth)/verify-email/[token]/EmailVerificationLinkExpired.tsx
index a2bf0bc9..4a39b2fa 100644
--- a/src/app/(auth)/verify-email/[token]/EmailVerificationLinkExpired.tsx
+++ b/src/app/(auth)/verify-email/[token]/EmailVerificationLinkExpired.tsx
@@ -39,13 +39,19 @@ export const EmailVerificationLinkExpired = ({ token }: { token: string }) => {
>
{!isEmailSent ? (
-
diff --git a/src/components/auth/forgot-password.tsx b/src/components/auth/forgot-password.tsx
index ad4b8f8b..838a06cb 100644
--- a/src/components/auth/forgot-password.tsx
+++ b/src/components/auth/forgot-password.tsx
@@ -52,7 +52,12 @@ export const ForgotPassword = () => {
}}
/>
-
+
Submit
diff --git a/src/components/auth/reset-password.tsx b/src/components/auth/reset-password.tsx
index 7ffa25f0..0b79f786 100644
--- a/src/components/auth/reset-password.tsx
+++ b/src/components/auth/reset-password.tsx
@@ -78,7 +78,7 @@ export const ResetPassword = () => {
{errorMessage ? (
{errorMessage}
) : null}
-
+
Submit
@@ -114,6 +114,7 @@ export const PasswordInput = ({
type="button"
onClick={togglePasswordVisibility}
className="absolute right-2 top-1/2 transform -translate-y-1/2 focus:outline-none"
+ aria-label="toggle-password"
>
{showPassword ? : }
diff --git a/src/components/auth/signin.tsx b/src/components/auth/signin.tsx
index 76d6f7f1..8afdd818 100644
--- a/src/components/auth/signin.tsx
+++ b/src/components/auth/signin.tsx
@@ -39,8 +39,14 @@ export const Signin = () => {
try {
const response = await signIn('signin', { ...data, redirect: false });
if (!response?.ok) {
+ const errorMessage =
+ response?.error?.includes('User') &&
+ response?.error?.includes('does not exist')
+ ? 'User does not exist'
+ : response?.error || 'Internal server error';
+
return toast({
- title: response?.error || 'Internal server error',
+ title: errorMessage,
variant: 'destructive',
});
}
@@ -48,7 +54,7 @@ export const Signin = () => {
title: 'Login successful! Welcome back!',
variant: 'success',
});
- // const redirect = searchParams.get('next') || APP_PATHS.HOME;
+
const searchParams = new URLSearchParams(window.location.search);
const redirect = searchParams.get('next') || APP_PATHS.HOME;
router.push(redirect);
@@ -107,6 +113,7 @@ export const Signin = () => {
type="submit"
disabled={form.formState.isSubmitting}
className="w-full h-10"
+ aria-label="submit"
>
{form.formState.isSubmitting ? 'Please wait...' : 'Sign In'}
diff --git a/src/components/auth/signup.tsx b/src/components/auth/signup.tsx
index 5232f8b8..72e8b2cc 100644
--- a/src/components/auth/signup.tsx
+++ b/src/components/auth/signup.tsx
@@ -198,6 +198,7 @@ export const Signup = () => {
type="submit"
disabled={form.formState.isSubmitting}
className="w-full h-10"
+ aria-label="submit"
>
{form.formState.isSubmitting ? 'Please wait...' : 'Create Account'}
diff --git a/src/components/auth/social-auth.tsx b/src/components/auth/social-auth.tsx
index 7b7a8b09..8a7d5392 100644
--- a/src/components/auth/social-auth.tsx
+++ b/src/components/auth/social-auth.tsx
@@ -19,6 +19,7 @@ export const GoogleOauthButton = ({ label }: { label: string }) => (
signIn('google');
}}
className="w-full h-10 bg-white border border-gray-300 text-gray-700 font-medium hover:bg-gray-50"
+ aria-label="google-oauth-button"
>
-
- Explore Jobs
-
-
- View Testimonials
-
+
+ Explore Jobs
+
+
+ View Testimonials
+
diff --git a/src/components/job-creation-success.tsx b/src/components/job-creation-success.tsx
index cab36a59..90c3a9a9 100644
--- a/src/components/job-creation-success.tsx
+++ b/src/components/job-creation-success.tsx
@@ -9,7 +9,7 @@ const JobCreateSuccess = ({ isVerifiedJob }: { isVerifiedJob: boolean }) => {
Job created successfully!
{message}
-
Post another job
+
Post another job
);
};
diff --git a/src/components/job-form.tsx b/src/components/job-form.tsx
index 0bb247fa..2a6c2959 100644
--- a/src/components/job-form.tsx
+++ b/src/components/job-form.tsx
@@ -566,6 +566,7 @@ const PostJobForm = () => {
@@ -675,6 +676,7 @@ const PostJobForm = () => {
@@ -751,7 +753,11 @@ const PostJobForm = () => {
-
+
{form.formState.isSubmitting ? 'Please wait...' : 'Create Job'}
diff --git a/src/components/job-landing.tsx b/src/components/job-landing.tsx
index c87df6f8..c931bfb8 100644
--- a/src/components/job-landing.tsx
+++ b/src/components/job-landing.tsx
@@ -27,7 +27,10 @@ export const JobLanding = () => {
-
+
View all jobs
diff --git a/src/components/job.tsx b/src/components/job.tsx
index 5c720c85..31f6153b 100644
--- a/src/components/job.tsx
+++ b/src/components/job.tsx
@@ -84,7 +84,10 @@ export const Job = ({ job }: { job: JobType }) => {
-
+
Apply Now
diff --git a/src/components/pagination-client.tsx b/src/components/pagination-client.tsx
index 69d0e0d1..26881fa9 100644
--- a/src/components/pagination-client.tsx
+++ b/src/components/pagination-client.tsx
@@ -19,9 +19,9 @@ const PaginationPreviousButton = ({
page: (currentPage - PAGE_INCREMENT).toString(),
})
}
+ className=" border dark:bg-slate-400 dark:bg-opacity-5 dark:text-white text-black bg-slate-600 bg-opacity-15 "
aria-disabled={currentPage - PAGE_INCREMENT < PAGE_INCREMENT}
role="button"
- className="aria-disabled:pointer-events-none aria-disabled:text-gray-400 dark:bg-neutral-900 rounded-full bg-neutral-100"
/>
);
};
@@ -43,8 +43,8 @@ const PaginationNextButton = ({
page: (currentPage + PAGE_INCREMENT).toString(),
})
}
+ className=" border dark:bg-slate-400 dark:bg-opacity-5 dark:text-white text-black bg-slate-600 bg-opacity-15"
aria-disabled={currentPage > totalPages - PAGE_INCREMENT}
- className="aria-disabled:pointer-events-none aria-disabled:text-gray-400 dark:bg-neutral-900 rounded-full bg-neutral-100"
/>
);
};
diff --git a/src/components/password-input.tsx b/src/components/password-input.tsx
index 2099ef12..2d0b0721 100644
--- a/src/components/password-input.tsx
+++ b/src/components/password-input.tsx
@@ -25,6 +25,7 @@ export const PasswordInput = ({ placeholder, field }: PasswordInputProps) => {
type="button"
onClick={togglePasswordVisibility}
className="absolute right-2 top-1/2 transform -translate-y-1/2"
+ aria-label="password"
>
{showPassword ? : }
diff --git a/src/components/profile-menu.tsx b/src/components/profile-menu.tsx
index eb34a405..e5dc52a3 100644
--- a/src/components/profile-menu.tsx
+++ b/src/components/profile-menu.tsx
@@ -59,6 +59,7 @@ export function ProfileMenu() {
variant="ghost"
size="icon"
className="focus-visible:outline-0 focus-visible:ring-0 focus-visible:ring-offset-0 py-2 h-8 w-8 px-0"
+ aria-label="profile"
>
Profile
diff --git a/src/components/profile/AboutMe.tsx b/src/components/profile/AboutMe.tsx
new file mode 100644
index 00000000..4c67013e
--- /dev/null
+++ b/src/components/profile/AboutMe.tsx
@@ -0,0 +1,80 @@
+'use client';
+import { SquareUserRound, Pencil } from 'lucide-react';
+import React, { useState } from 'react';
+import { Button } from '@/components/ui/button';
+import SheetWrapper from './sheets/SheetWrapper';
+import { SHEETS } from '@/lib/constant/profile.constant';
+import ProfileEmptyContainers from './emptycontainers/ProfileEmptyContainers';
+import AboutMeForm from './forms/ReadMeForm';
+
+const ProfileAboutMe = ({
+ aboutMe,
+ isOwner,
+}: {
+ aboutMe: string;
+ isOwner: boolean;
+}) => {
+ const [isSheetOpen, setIsSheetOpen] = useState
(false);
+
+ const title =
+ aboutMe.length === 0
+ ? SHEETS.aboutMe.title
+ : SHEETS.aboutMe.title.replace('Add', 'Edit');
+
+ const handleClose = () => {
+ setIsSheetOpen(false);
+ };
+ const handleOpen = () => {
+ setIsSheetOpen(true);
+ };
+
+ return (
+ <>
+
+
About Me
+ {isOwner && (
+
+ Edit
+
+ )}
+
+ {!aboutMe && (
+
+ )}
+ {aboutMe && (
+
+ )}
+ {isOwner && (
+
+
+
+ )}
+ >
+ );
+};
+
+export default ProfileAboutMe;
diff --git a/src/components/profile/ChangePassword.tsx b/src/components/profile/ChangePassword.tsx
index 21edafd1..953d9461 100644
--- a/src/components/profile/ChangePassword.tsx
+++ b/src/components/profile/ChangePassword.tsx
@@ -3,7 +3,6 @@
import { useState, useTransition } from 'react';
import { useForm } from 'react-hook-form';
-import { useSession } from 'next-auth/react';
import { useToast } from '../ui/use-toast';
import { zodResolver } from '@hookform/resolvers/zod';
@@ -28,7 +27,6 @@ import {
import Loader from '../loader';
export const ChangePassword = () => {
- const session = useSession();
const { toast } = useToast();
const { register, watch } = useForm();
@@ -60,14 +58,19 @@ export const ChangePassword = () => {
const handleFormSubmit = async (data: UserPasswordSchemaType) => {
try {
startTransition(() => {
- changePassword(session.data?.user.email as string, data)
+ changePassword(data)
.then((res) => {
- res?.error
- ? toast({
- title: (res.error as string) || 'something went wrong',
- variant: 'destructive',
- })
- : toast({ title: res.success as string, variant: 'success' });
+ res?.status &&
+ toast({
+ title: res.message as string,
+ variant: 'success',
+ });
+ })
+ .catch((error) => {
+ toast({
+ title: error.message as string,
+ variant: 'destructive',
+ });
})
.then(() => {
form.reset();
@@ -158,6 +161,7 @@ export const ChangePassword = () => {
type="button"
onClick={togglePasswordVisibility}
className="absolute right-2 top-1/2 transform -translate-y-1/2"
+ aria-label="password"
>
{showPassword ? : }
@@ -172,6 +176,7 @@ export const ChangePassword = () => {
{isPending ? : 'Save'}
diff --git a/src/components/profile/DeleteAccountDialog.tsx b/src/components/profile/DeleteAccountDialog.tsx
index fee934ef..518929c2 100644
--- a/src/components/profile/DeleteAccountDialog.tsx
+++ b/src/components/profile/DeleteAccountDialog.tsx
@@ -24,7 +24,7 @@ import {
} from '@/lib/validators/user.profile.validator';
import { deleteUser } from '@/actions/user.profile.actions';
-import { Trash, X } from 'lucide-react';
+import { X } from 'lucide-react';
import { FaSpinner } from 'react-icons/fa';
export const DeleteAccountDialog = () => {
@@ -80,12 +80,12 @@ export const DeleteAccountDialog = () => {
) : (
-
+
Submit
)}
diff --git a/src/components/user-multistep-form/add-resume-form.tsx b/src/components/user-multistep-form/add-resume-form.tsx
index 648faba8..45359a03 100644
--- a/src/components/user-multistep-form/add-resume-form.tsx
+++ b/src/components/user-multistep-form/add-resume-form.tsx
@@ -125,6 +125,7 @@ export const AddResume = () => {
type="button"
onClick={clearResumeFile}
className="w-6 h-6 bg-red-500 rounded-full flex items-center justify-center cursor-pointer translate-x-1/2 -translate-y-1/2"
+ aria-label="x"
>
@@ -144,7 +145,7 @@ export const AddResume = () => {
{isLoading ? (
) : (
-
+
Submit
)}
diff --git a/src/components/user-multistep-form/add-skills-form.tsx b/src/components/user-multistep-form/add-skills-form.tsx
index 417f320e..fe57859c 100644
--- a/src/components/user-multistep-form/add-skills-form.tsx
+++ b/src/components/user-multistep-form/add-skills-form.tsx
@@ -66,7 +66,7 @@ export const AddSkills = () => {
{' '}
) : (
-
+
Submit
)}
diff --git a/src/components/user-multistep-form/addExperience-form.tsx b/src/components/user-multistep-form/addExperience-form.tsx
index 5444579f..c9e3f97b 100644
--- a/src/components/user-multistep-form/addExperience-form.tsx
+++ b/src/components/user-multistep-form/addExperience-form.tsx
@@ -270,7 +270,7 @@ export const AddExperience = () => {
{' '}
) : (
-
+
Submit
)}
diff --git a/src/components/user-multistep-form/user-multistep-form.tsx b/src/components/user-multistep-form/user-multistep-form.tsx
index 0535585a..e4e60dc8 100644
--- a/src/components/user-multistep-form/user-multistep-form.tsx
+++ b/src/components/user-multistep-form/user-multistep-form.tsx
@@ -113,7 +113,11 @@ export default function VerticalLinearStepper() {
-
+
Add
@@ -132,6 +136,7 @@ export default function VerticalLinearStepper() {
index === forms.length - 1 ? handleFinish : handleNext
}
sx={{ mt: 1, mr: 1 }}
+ aria-label="next/finish"
>
{index === forms.length - 1 ? 'Finish' : 'Next'}
@@ -139,6 +144,7 @@ export default function VerticalLinearStepper() {
disabled={index === 0}
onClick={handleBack}
sx={{ mt: 1, mr: 1 }}
+ aria-label="back"
>
Back
diff --git a/src/components/userDetails.tsx b/src/components/userDetails.tsx
index 88470d92..c29409f1 100644
--- a/src/components/userDetails.tsx
+++ b/src/components/userDetails.tsx
@@ -7,10 +7,14 @@ const UserDetails = () => {
- Applied to
+
+ Applied to
+
- Update Details
+
+ Update Details
+
@@ -35,14 +39,16 @@ const UserDetails = () => {
- Upload Resume
+
+ Upload Resume{' '}
+
diff --git a/src/config/path.config.ts b/src/config/path.config.ts
index a17b97a2..c5699881 100644
--- a/src/config/path.config.ts
+++ b/src/config/path.config.ts
@@ -5,7 +5,8 @@ const APP_PATHS = {
SIGNUP: '/signup',
RESET_PASSWORD: '/reset-password',
JOBS: '/jobs',
- MANAGE_JOBS: '/manage',
+ MANAGE_RECRUITERS: '/manage/recruiters',
+ MANAGE_JOBS: '/manage/jobs',
CONTACT_US: 'mailto:vineetagarwal.now@gmail.com',
TESTIMONIALS: '#testimonials',
FAQS: '#faq',
diff --git a/src/layouts/footer.tsx b/src/layouts/footer.tsx
index d4b37c23..eaf3f7af 100644
--- a/src/layouts/footer.tsx
+++ b/src/layouts/footer.tsx
@@ -20,6 +20,7 @@ const Footer = () => {
key={i}
href={social.href}
className="p-3 rounded-full border mx-2 hover:bg-slate-50 duration-150 ease-in-out transition"
+ aria-label={`${social.icon}-logo`}
>
diff --git a/src/layouts/header.tsx b/src/layouts/header.tsx
index 2f345a32..9c1bb546 100644
--- a/src/layouts/header.tsx
+++ b/src/layouts/header.tsx
@@ -36,7 +36,7 @@ export const CompanyLogo = () => {
>
{
return (
<>
-