Skip to content

Instantly share code, notes, and snippets.

@Vetrivel-VP
Last active July 22, 2024 06:02
Show Gist options
  • Save Vetrivel-VP/0199de6318e70f6caf3ed3299ae7b7c1 to your computer and use it in GitHub Desktop.
Save Vetrivel-VP/0199de6318e70f6caf3ed3299ae7b7c1 to your computer and use it in GitHub Desktop.
Job Portal Helpers
------------------------------------------------------------------------------------------------------------------------
Google Ai Studio Scripts
/*
* Install the Generative AI SDK
*
* $ npm install @google/generative-ai
*
* See the getting started guide for more information
* https://ai.google.dev/gemini-api/docs/get-started/node
*/
const {
GoogleGenerativeAI,
HarmCategory,
HarmBlockThreshold,
} = require("@google/generative-ai");
// const apiKey = process.env.GEMINI_API_KEY;
const genAI = new GoogleGenerativeAI("Your_Api_key");
const model = genAI.getGenerativeModel({
model: "gemini-1.5-flash",
});
const generationConfig = {
temperature: 1,
topP: 0.95,
topK: 64,
maxOutputTokens: 8192,
responseMimeType: "text/plain",
};
async function getGenerativeAIResponse(prompt: string) {
const safetySettings = [
{
category: HarmCategory.HARM_CATEGORY_HARASSMENT,
threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
},
{
category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
},
{
category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
},
{
category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
},
];
const chatSession = model.startChat({
generationConfig,
safetySettings,
history: [],
});
const result = await chatSession.sendMessage(prompt);
console.log(result.response.text());
return result.response.text().trim().replace(/```/g, "");
}
export default getGenerativeAIResponse;
------------------------------------------------------------------------------------------------------------------------
Custom Prompts
const job_description = `Could you please draft a job requirements document for the position of ${rollname}? The job description should include roles & responsibilities, key features, and details about the role. The required skills should include proficiency in ${skills}. Additionally, you can list any optional skill related to job. Thanks!`;
const job_short_description = `Could you craft a concise job description for a ${prompt} position in fewer than 400 characters?`;
const job_tags = `Generate an array of top 10 keywords related to the job profession "${prompt}". These keywords should encompass various aspects of the profession, including skills, responsibilities, tools, and technologies commonly associated with it. Aim for a diverse set of keywords that accurately represent the breadth of the profession. Your output should be a list/array of keywords. Just return me the array alone.`;
const company_why_join_us = `Create a compelling "Why join us" content piece for ${rollname}. Highlight the unique opportunities, benefits, and experiences that ${rollname} offers to its users. Emphasize the platform's value proposition, such as access to a vast music library, personalized recommendations, exclusive content, community features, and career opportunities for musicians and creators. Tailor the content to attract potential users and illustrate why ${rollname} stands out among other music streaming platforms.`;
const company_overview = `Generate an overview content about ${rollname}. Include information about its history, purpose, features, user base, and impact on the industry. Focus on providing a comprehensive yet concise summary suitable for readers unfamiliar with the platform.`;
------------------------------------------------------------------------------------------------------------------------
Insertin Category Into MongoDB - Script (seed.ts)
const { PrismaClient } = require("@prisma/client");
const database = new PrismaClient();
const main = async () => {
try {
await database.category.createMany({
data: [
{ name: "Software Development" },
{ name: "Web Development" },
{ name: "Mobile App Development" },
{ name: "Data Science" },
{ name: "Machine Learning" },
{ name: "Artificial Intelligence" },
{ name: "UI/UX Design" },
{ name: "Product Management" },
{ name: "Project Management" },
{ name: "Quality Assurance" },
{ name: "DevOps" },
{ name: "Cybersecurity" },
{ name: "Cloud Computing" },
{ name: "Database Administration" },
{ name: "Network Engineering" },
{ name: "Business Analysis" },
{ name: "Sales" },
{ name: "Marketing" },
{ name: "Customer Support" },
{ name: "Human Resources" },
{ name: "Finance" },
{ name: "Accounting" },
{ name: "Legal" },
],
});
console.log("Success");
} catch (error) {
console.log(`Error on seeding the database categories : ${error}`);
}
};
main();
------------------------------------------------------------------------------------------------------------------------
Attachements Form - attachments-form.tsx
"use client";
import { AttachmentsUploads } from "@/components/attachments-uploads";
import { ImageUpload } from "@/components/image-upload";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { zodResolver } from "@hookform/resolvers/zod";
import { Job, Attachment } from "@prisma/client";
import axios from "axios";
import { File, ImageIcon, Pencil, X } from "lucide-react";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useForm } from "react-hook-form";
import toast from "react-hot-toast";
import { z } from "zod";
interface AttachmentsFormProps {
initialData: Job & { attachments: Attachment[] };
jobId: string;
}
const formSchema = z.object({
attachments: z.object({ url: z.string(), name: z.string() }).array(),
});
export const AttachmentsForm = ({
initialData,
jobId,
}: AttachmentsFormProps) => {
const [isEditing, setIsEditing] = useState(false);
const router = useRouter();
// Assuming initialData is available and has type of any
const initialAttachments = Array.isArray(initialData?.attachments)
? initialData.attachments.map((attachment: any) => {
if (
typeof attachment === "object" &&
attachment !== null &&
"url" in attachment &&
"name" in attachment
) {
return { url: attachment.url, name: attachment.name };
}
return { url: "", name: "" }; // Provide default values if the shape is incorrect
})
: [];
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
attachments: initialAttachments,
},
});
const { isSubmitting, isValid } = form.formState;
const onSubmit = async (values: z.infer<typeof formSchema>) => {
console.log(values);
try {
const response = await axios.post(
`/api/jobs/${jobId}/attachments`,
values
);
toast.success("Job Attachment updated");
toggleEditing();
router.refresh();
} catch (error) {
console.log((error as Error)?.message);
toast.error("Something went wrong");
}
};
const toggleEditing = () => setIsEditing((current) => !current);
return (
<div className="mt-6 border bg-neutral-100 rounded-md p-4">
<div className="font-medium flex items-center justify-between">
Job Attachments
<Button onClick={toggleEditing} variant={"ghost"}>
{isEditing ? (
<>Cancel</>
) : (
<>
<Pencil className="w-4 h-4 mr-2" />
Edit
</>
)}
</Button>
</div>
{/* display the attachments if not editing */}
{!isEditing && (
<>
{initialData.attachments.map((item) => (
<div
key={item.url}
className="p-3 w-full bg-purple-100 border-purple-200 border text-purple-700 rounded-md flex items-center"
>
<File className="w-4 h-4 mr-2 " />
<p className="text-xs w-full truncate">{item.name}</p>
<Button
variant={"ghost"}
size={"icon"}
className="p-1"
onClick={() => {}}
type="button"
>
<X className="w-4 h4" />
</Button>
</div>
))}
</>
)}
{/* on editing mode display the input */}
{isEditing && (
<Form {...form}>
"use client";
import { AttachmentsUploads } from "@/components/attachments-uploads";
import { ImageUpload } from "@/components/image-upload";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { zodResolver } from "@hookform/resolvers/zod";
import { Job, Attachment } from "@prisma/client";
import axios from "axios";
import { File, ImageIcon, Loader2, Pencil, X } from "lucide-react";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useForm } from "react-hook-form";
import toast from "react-hot-toast";
import { z } from "zod";
interface AttachmentsFormProps {
initialData: Job & { attachments: Attachment[] };
jobId: string;
}
const formSchema = z.object({
attachments: z.object({ url: z.string(), name: z.string() }).array(),
});
export const AttachmentsForm = ({
initialData,
jobId,
}: AttachmentsFormProps) => {
const [isEditing, setIsEditing] = useState(false);
const [deletingId, setDeletingId] = useState<string | null>(null);
const router = useRouter();
// Assuming initialData is available and has type of any
const initialAttachments = Array.isArray(initialData?.attachments)
? initialData.attachments.map((attachment: any) => {
if (
typeof attachment === "object" &&
attachment !== null &&
"url" in attachment &&
"name" in attachment
) {
return { url: attachment.url, name: attachment.name };
}
return { url: "", name: "" }; // Provide default values if the shape is incorrect
})
: [];
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
attachments: initialAttachments,
},
});
const { isSubmitting, isValid } = form.formState;
const onSubmit = async (values: z.infer<typeof formSchema>) => {
console.log(values);
try {
const response = await axios.post(
`/api/jobs/${jobId}/attachments`,
values
);
toast.success("Job Attachment updated");
toggleEditing();
router.refresh();
} catch (error) {
console.log((error as Error)?.message);
toast.error("Something went wrong");
}
};
const toggleEditing = () => setIsEditing((current) => !current);
const onDelete = async (attachment: Attachment) => {
try {
setDeletingId(attachment.id);
await axios.delete(`/api/jobs/${jobId}/attachments/${attachment.id}`);
toast.success("Attachment Removed");
router.refresh();
} catch (error) {
console.log((error as Error)?.message);
toast.error("Something went wrong");
}
};
return (
<div className="mt-6 border bg-neutral-100 rounded-md p-4">
<div className="font-medium flex items-center justify-between">
Job Attachments
<Button onClick={toggleEditing} variant={"ghost"}>
{isEditing ? (
<>Cancel</>
) : (
<>
<Pencil className="w-4 h-4 mr-2" />
Edit
</>
)}
</Button>
</div>
{/* display the attachments if not editing */}
{!isEditing && (
<div className="space-y-2">
{initialData.attachments.map((item) => (
<div
key={item.url}
className="p-3 w-full bg-purple-100 border-purple-200 border text-purple-700 rounded-md flex items-center"
>
<File className="w-4 h-4 mr-2 " />
<p className="text-xs w-full truncate">{item.name}</p>
{deletingId === item.id && (
<Button
variant={"ghost"}
size={"icon"}
className="p-1"
type="button"
>
<Loader2 className="h-4 w-4 animate-spin" />
</Button>
)}
{deletingId !== item.id && (
<Button
variant={"ghost"}
size={"icon"}
className="p-1"
onClick={() => {
onDelete(item);
}}
type="button"
>
<X className="w-4 h4" />
</Button>
)}
</div>
))}
</div>
)}
{/* on editing mode display the input */}
{isEditing && (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-4 mt-4"
>
<FormField
control={form.control}
name="attachments"
render={({ field }) => (
<FormItem>
<FormControl>
<AttachmentsUploads
value={field.value}
disabled={isSubmitting}
onChange={(attachments) => {
if (attachments) {
onSubmit({ attachments });
}
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex items-center gap-x-2">
<Button disabled={!isValid || isSubmitting} type="submit">
Save
</Button>
</div>
</form>
</Form>
)}
</div>
);
};
------------------------------------------------------------------------------------------------------------------------
Attachements API Route - /api/jobs/[jobId]/attachments/route.ts
import { db } from "@/lib/db";
import { auth } from "@clerk/nextjs/server";
import { Attachment } from "@prisma/client";
import { NextResponse } from "next/server";
export const POST = async (
req: Request,
{ params }: { params: { jobId: string } }
) => {
try {
const { userId } = auth();
const { jobId } = params;
if (!userId) {
return new NextResponse("Un-Authorized", { status: 401 });
}
if (!jobId) {
return new NextResponse("ID Is missing", { status: 401 });
}
const { attachments } = await req.json();
if (
!attachments ||
!Array.isArray(attachments) ||
attachments.length === 0
) {
return new NextResponse("Invalid Attachment Format", { status: 400 });
}
const createdAttachments: Attachment[] = [];
for (const attachment of attachments) {
const { url, name } = attachment;
// check the attachment with the same url is already exists for this jobid
const existingAttachment = await db.attachment.findFirst({
where: {
jobId,
url,
},
});
if (existingAttachment) {
// skip the insertion
console.log(
`Attachment with URL ${url} already exists for jobId ${jobId}`
);
continue;
}
// create a new attachment
const createdAttachment = await db.attachment.create({
data: {
url,
name,
jobId,
},
});
createdAttachments.push(createdAttachment);
}
return NextResponse.json(createdAttachments);
} catch (error) {
console.log(`[JOB_ATTACHMENT_POST] : ${error}`);
return new NextResponse("Internal Server Error", { status: 500 });
}
};
------------------------------------------------------------------------------------------------------------------------
Attachement Delete Route - /api/jobs/[jobId]/attachments/[attachmentId]/route.ts
import { storage } from "@/config/firebase.config";
import { db } from "@/lib/db";
import { auth } from "@clerk/nextjs/server";
import { deleteObject, ref } from "firebase/storage";
import { NextResponse } from "next/server";
export const DELETE = async (
req: Request,
{ params }: { params: { jobId: string; attachmentId: string } }
) => {
try {
const { userId } = auth();
const { jobId, attachmentId } = params;
if (!userId) {
return new NextResponse("Un-Authorized", { status: 401 });
}
if (!jobId) {
return new NextResponse("ID Is missing", { status: 401 });
}
const attachment = await db.attachment.findUnique({
where: {
id: attachmentId,
},
});
if (!attachment || attachment.jobId !== params.jobId) {
return new NextResponse("Attachment not found", { status: 404 });
}
// delete from the firebase storage
const storageRef = ref(storage, attachment.url);
await deleteObject(storageRef);
// delete from mongodb
await db.attachment.delete({
where: {
id: attachmentId,
},
});
return NextResponse.json({ message: "Attachment deleted successfully" });
} catch (error) {
console.log(`[JOB_DELETE] : ${error}`);
return new NextResponse("Internal Server Error", { status: 500 });
}
};
------------------------------------------------------------------------------------------------------------------------
Attachement Uploads - attachments-uploads.tsx
"use client";
import { storage } from "@/config/firebase.config";
import {
deleteObject,
getDownloadURL,
ref,
uploadBytesResumable,
} from "firebase/storage";
import { File, FilePlus, ImagePlus, Trash, X } from "lucide-react";
import Image from "next/image";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import { Button } from "./ui/button";
import { url } from "inspector";
interface AttachmentsUploadsProps {
disabled?: boolean;
onChange: (value: { url: string; name: string }[]) => void;
value: { url: string; name: string }[];
}
export const AttachmentsUploads = ({
disabled,
onChange,
value,
}: AttachmentsUploadsProps) => {
const [isMounted, setIsMounted] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [progress, setProgress] = useState<number>(0);
useEffect(() => {
setIsMounted(true);
}, []);
if (!isMounted) {
return null;
}
const onUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const files: File[] = Array.from(e.target.files || []);
setIsLoading(true);
// array to store newly uploaded urls
const newUrls: { url: string; name: string }[] = [];
// counter to keep track the uploaded files
let completedFiles = 0;
files.forEach((file: File) => {
const uploadTask = uploadBytesResumable(
ref(storage, `Attachments/${Date.now()}-${file.name}`),
file,
{ contentType: file.type }
);
uploadTask.on(
"state_changed",
(snapshot) => {
setProgress((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
},
(error) => {
toast.error(error.message);
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((downloadurl) => {
// store this url
newUrls.push({ url: downloadurl, name: file.name });
// increase the count of the counter
completedFiles++;
// check the files are uploaded or not
if (completedFiles === files.length) {
setIsLoading(false);
onChange([...value, ...newUrls]);
}
});
}
);
});
};
return (
<div>
<div className="w-full h-40 bg-purple-100 p-2 flex items-center justify-center">
{isLoading ? (
<>
<p>{`${progress.toFixed(2)}%`}</p>
</>
) : (
<>
<label className="w-full h-full flex items-center justify-center">
<div className="flex gap-2 items-center justify-center cursor-pointer ">
<FilePlus className="w-3 h-3 mr-2" />
<p>Add a file</p>
</div>
<input
type="file"
accept=".jpg,.jpeg,.png,.gif,.bmp,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.rtf,.odt"
multiple
className="w-0 h-0"
onChange={onUpload}
/>
</label>
</>
)}
</div>
</div>
);
};
------------------------------------------------------------------------------------------------------------------------
DateFilterData - date-filter.tsx
const DateFilter = () => {
const data = [
{ value: "today", label: "Today" },
{ value: "yesterday", label: "Yesterday" },
{ value: "thisWeek", label: "This Week" },
{ value: "lastWeek", label: "Last Week" },
{ value: "thisMonth", label: "This Month" },
];
------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------
Filters data
const shiftTimingsData = [
{
value: "full-time",
label: "Full Time",
},
{
value: "part-time",
label: "Part Time",
},
{
value: "contract",
label: "Contract",
},
];
const workingModesData = [
{
value: "remote",
label: "Remote",
},
{
value: "hybrid",
label: "Hybrid",
},
{
value: "office",
label: "Office",
},
];
const experienceData = [
{
value: "0",
label: "Fresher",
},
{
value: "2",
label: "0-2 years",
},
{
value: "3",
label: "2-4 years",
},
{
value: "5",
label: "5+ years",
},
];
------------------------------------------------------------------------------------------------------------------------
resume-form.tsx
"use client";
import { AttachmentsUploads } from "@/components/attachments-uploads";
import { ImageUpload } from "@/components/image-upload";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
import { zodResolver } from "@hookform/resolvers/zod";
import { Job, Attachment, UserProfile, Resumes } from "@prisma/client";
import axios from "axios";
import {
File,
ImageIcon,
Loader2,
Pencil,
PlusCircle,
ShieldCheck,
ShieldX,
X,
} from "lucide-react";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useForm } from "react-hook-form";
import toast from "react-hot-toast";
import { z } from "zod";
interface ResumeFormProps {
initialData: (UserProfile & { resumes: Resumes[] }) | null;
userId: string;
}
const formSchema = z.object({
resumes: z.object({ url: z.string(), name: z.string() }).array(),
});
export const ResumeForm = ({ initialData, userId }: ResumeFormProps) => {
const [isEditing, setIsEditing] = useState(false);
const [deletingId, setDeletingId] = useState<string | null>(null);
const [isActiveResumeId, setIsActiveResumeId] = useState<string | null>(null);
const router = useRouter();
// Assuming initialData is available and has type of any
const initialResumes = Array.isArray(initialData?.resumes)
? initialData.resumes.map((resume: any) => {
if (
typeof resume === "object" &&
resume !== null &&
"url" in resume &&
"name" in resume
) {
return { url: resume.url, name: resume.name };
}
return { url: "", name: "" }; // Provide default values if the shape is incorrect
})
: [];
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
resumes: initialResumes,
},
});
const { isSubmitting, isValid } = form.formState;
const onSubmit = async (values: z.infer<typeof formSchema>) => {
console.log(values, userId);
try {
const response = await axios.post(`/api/users/${userId}/resumes`, values);
toast.success("Resume updated");
toggleEditing();
router.refresh();
} catch (error) {
console.log((error as Error)?.message);
toast.error("Something went wrong");
}
};
const toggleEditing = () => setIsEditing((current) => !current);
const onDelete = async (resume: Resumes) => {
try {
setDeletingId(resume.id);
if (initialData?.activeResumeId === resume.id) {
toast.error("Can't Delete the active resume");
return;
}
await axios.delete(`/api/users/${userId}/resumes/${resume.id}`);
toast.success("Resume Removed");
router.refresh();
} catch (error) {
console.log((error as Error)?.message);
toast.error("Something went wrong");
} finally {
setDeletingId(null);
}
};
const setActiveResumeId = async (resumeId: string) => {
setIsActiveResumeId(resumeId);
const response = await axios.patch(`/api/users/${userId}`, {
activeResumeId: resumeId,
});
toast.success("Resume Activated");
router.refresh();
try {
} catch (error) {
console.log((error as Error)?.message);
toast.error("Something went wrong");
} finally {
setIsActiveResumeId(null);
}
};
return (
<div className="mt-6 border flex-1 w-full rounded-md p-4">
<div className="font-medium flex items-center justify-between">
Your Resumes
<Button onClick={toggleEditing} variant={"ghost"}>
{isEditing ? (
<>Cancel</>
) : (
<>
<PlusCircle className="w-4 h-4 mr-2" />
Add a file
</>
)}
</Button>
</div>
{/* display the attachments if not editing */}
{!isEditing && (
<div className="space-y-2">
{initialData?.resumes.map((item) => (
<div className="grid grid-cols-12 gap-2">
<div
key={item.url}
className="p-3 w-full bg-purple-100 border-purple-200 border text-purple-700 rounded-md flex items-center col-span-10"
>
<File className="w-4 h-4 mr-2 " />
<p className="text-xs w-full truncate">{item.name}</p>
{deletingId === item.id && (
<Button
variant={"ghost"}
size={"icon"}
className="p-1"
type="button"
>
<Loader2 className="h-4 w-4 animate-spin" />
</Button>
)}
{deletingId !== item.id && (
<Button
variant={"ghost"}
size={"icon"}
className="p-1"
onClick={() => {
onDelete(item);
}}
type="button"
>
<X className="w-4 h4" />
</Button>
)}
</div>
<div className="col-span-2 flex items-center justify-start gap-2">
{isActiveResumeId === item.id ? (
<>
<div className="flex items-center justify-center w-full">
<Loader2 className="w-4 h-4 animate-spin" />
</div>
</>
) : (
<>
<Button
variant={"ghost"}
className={cn(
"flex items-center justify-center",
initialData.activeResumeId === item.id
? "text-emerald-500"
: "text-red-500"
)}
onClick={() => setActiveResumeId(item.id)}
>
<p>
{initialData.activeResumeId === item.id
? "Live"
: "Activate"}
</p>
{initialData.activeResumeId === item.id ? (
<ShieldCheck className="w-4 h-4 ml-2" />
) : (
<ShieldX className="w-4 h-4 ml-2" />
)}
</Button>
</>
)}
</div>
</div>
))}
</div>
)}
{/* on editing mode display the input */}
{isEditing && (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-4 mt-4"
>
<FormField
control={form.control}
name="resumes"
render={({ field }) => (
<FormItem>
<FormControl>
<AttachmentsUploads
value={field.value}
disabled={isSubmitting}
onChange={(resumes) => {
if (resumes) {
onSubmit({ resumes });
}
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex items-center gap-x-2">
<Button disabled={!isValid || isSubmitting} type="submit">
Save
</Button>
</div>
</form>
</Form>
)}
</div>
);
};
------------------------------------------------------------------------------------------------------------------------
/api/users/[userId]/route.ts
import { db } from "@/lib/db";
import { auth } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";
export const PATCH = async (req: Request) => {
try {
const { userId } = auth();
const values = await req.json();
if (!userId) {
return new NextResponse("Un-Authorized", { status: 401 });
}
let profile = await db.userProfile.findUnique({
where: {
userId,
},
});
let userProfile;
if (profile) {
userProfile = await db.userProfile.update({
where: {
userId,
},
data: {
...values,
},
});
} else {
userProfile = await db.userProfile.create({
data: {
userId,
...values,
},
});
}
return NextResponse.json(userProfile);
} catch (error) {
console.log(`[JOB_PATCH] : ${error}`);
return new NextResponse("Internal Server Error", { status: 500 });
}
};
------------------------------------------------------------------------------------------------------------------------
/api/users/[userId]/resumes/route.ts
import { db } from "@/lib/db";
import { auth } from "@clerk/nextjs/server";
import { Attachment, Resumes } from "@prisma/client";
import { NextResponse } from "next/server";
export const POST = async (req: Request) => {
try {
const { userId } = auth();
if (!userId) {
return new NextResponse("Un-Authorized", { status: 401 });
}
const { resumes } = await req.json();
if (!resumes || !Array.isArray(resumes) || resumes.length === 0) {
return new NextResponse("Invalid Resume Format", { status: 400 });
}
const createdResumes: Resumes[] = [];
for (const resume of resumes) {
const { url, name } = resume;
// check the resume with the same url is already exists for this resumeId
const existingresume = await db.resumes.findFirst({
where: {
userProfileId: userId,
url,
},
});
if (existingresume) {
// skip the insertion
console.log(
`Resume with URL ${url} already exists for resumeId ${userId}`
);
continue;
}
// create a new resume
const createdResume = await db.resumes.create({
data: {
url,
name,
userProfileId: userId,
},
});
createdResumes.push(createdResume);
}
return NextResponse.json(createdResumes);
} catch (error) {
console.log(`[USER_RESUME_POST] : ${error}`);
return new NextResponse("Internal Server Error", { status: 500 });
}
};
------------------------------------------------------------------------------------------------------------------------
/api/users/[userId]/resumes/[resumeId]route.ts
import { storage } from "@/config/firebase.config";
import { db } from "@/lib/db";
import { auth } from "@clerk/nextjs/server";
import { deleteObject, ref } from "firebase/storage";
import { NextResponse } from "next/server";
export const DELETE = async (
req: Request,
{ params }: { params: { resumeId: string } }
) => {
try {
const { userId } = auth();
const { resumeId } = params;
if (!userId) {
return new NextResponse("Un-Authorized", { status: 401 });
}
const resume = await db.resumes.findUnique({
where: {
id: resumeId,
},
});
if (!resume || resume.id !== resumeId) {
return new NextResponse("resume not found", { status: 404 });
}
// delete from the firebase storage
const storageRef = ref(storage, resume.url);
await deleteObject(storageRef);
// delete from mongodb
await db.resumes.delete({
where: {
id: resumeId,
},
});
return NextResponse.json({ message: "Resume deleted successfully" });
} catch (error) {
console.log(`[RESUME_DELETE] : ${error}`);
return new NextResponse("Internal Server Error", { status: 500 });
}
};
------------------------------------------------------------------------------------------------------------------------
import {
Code,
Monitor,
Smartphone,
BarChart,
Cpu,
Brain,
Palette,
Box,
Clipboard,
Shield,
Terminal,
Lock,
Cloud,
Database,
Globe,
FileText,
DollarSign,
CreditCard,
Headphones,
Users,
Currency,
Scale,
LucideIcon,
} from "lucide-react";
export type IconName =
| "Software Development"
| "Web Development"
| "Mobile App Development"
| "Data Science"
| "Machine Learning"
| "Artificial Intelligence"
| "UI/UX Design"
| "Product Management"
| "Project Management"
| "Quality Assurance"
| "DevOps"
| "Cybersecurity"
| "Cloud Computing"
| "Database Administration"
| "Network Engineering"
| "Business Analysis"
| "Sales"
| "Marketing"
| "Customer Support"
| "Human Resources"
| "Finance"
| "Accounting"
| "Legal";
export const iconMapping: Record<IconName, LucideIcon> = {
"Software Development": Code,
"Web Development": Monitor,
"Mobile App Development": Smartphone,
"Data Science": BarChart,
"Machine Learning": Cpu,
"Artificial Intelligence": Brain,
"UI/UX Design": Palette,
"Product Management": Box,
"Project Management": Clipboard,
"Quality Assurance": Shield,
DevOps: Terminal,
Cybersecurity: Lock,
"Cloud Computing": Cloud,
"Database Administration": Database,
"Network Engineering": Globe,
"Business Analysis": FileText,
Sales: DollarSign,
Marketing: CreditCard,
"Customer Support": Headphones,
"Human Resources": Users,
Finance: Currency,
Accounting: CreditCard,
Legal: Scale,
};
------------------------------------------------------------------------------------------------------------------------
footer.tsx
"use client";
import { Logo } from "@/app/(dashboard)/_components/logo";
import Box from "./box";
import { Facebook, Linkedin, Twitter, Youtube } from "lucide-react";
import Link from "next/link";
import { Card, CardDescription, CardTitle } from "./ui/card";
import Image from "next/image";
import { Separator } from "./ui/separator";
const menuOne = [
{ href: "#", label: "About Us" },
{ href: "#", label: "Careers" },
{ href: "#", label: "Employer home" },
{ href: "#", label: "Sitemap" },
{ href: "#", label: "Credits" },
];
export const Footer = () => {
return (
<Box className="h-72 p-4 items-start flex-col">
<div className="w-full grid grid-cols-1 md:grid-cols-2 xl:grid-cols-5 gap-6">
{/* first section */}
<Box className="flex-col items-start gap-6">
<div className="flex items-center gap-3">
<Logo />
<h2 className="text-xl font-semibold text-muted-foreground">
WorkNow
</h2>
</div>
<p className="font-semibold text-base">Connect with us</p>
<div className="flex items-center gap-6 w-full">
<Link href={"www.facebook.com"}>
<Facebook className="w-5 h-5 text-muted-foreground hover:text-purple-500 hover:scale-125 transition-all" />
</Link>
<Link href={"www.facebook.com"}>
<Twitter className="w-5 h-5 text-muted-foreground hover:text-purple-500 hover:scale-125 transition-all" />
</Link>
<Link href={"www.facebook.com"}>
<Linkedin className="w-5 h-5 text-muted-foreground hover:text-purple-500 hover:scale-125 transition-all" />
</Link>
<Link href={"www.facebook.com"}>
<Youtube className="w-5 h-5 text-muted-foreground hover:text-purple-500 hover:scale-125 transition-all" />
</Link>
</div>
</Box>
{/* second */}
<Box className="flex-col items-start justify-between gap-y-4 ml-4">
{menuOne.map((item) => (
<Link key={item.label} href={item.href}>
<p className="text-sm font-sans text-neutral-500 hover:text-purple-500">
{item.label}
</p>
</Link>
))}
</Box>
<Box className="flex-col items-start justify-between gap-y-4 ml-4">
{menuOne.map((item) => (
<Link key={item.label} href={item.href}>
<p className="text-sm font-sans text-neutral-500 hover:text-purple-500">
{item.label}
</p>
</Link>
))}
</Box>
<Card className="p-6 col-span-2">
<CardTitle className="text-base">Apply on the go</CardTitle>
<CardDescription>
Get real-time job updates on our App
</CardDescription>
<Link href={"#"}>
<div className="w-full relative overflow-hidden h-16">
<Image
src={"/img/play-apple-store.png"}
fill
className="w-full h-full object-contain"
alt="Play Store & Apple Store"
/>
</div>
</Link>
</Card>
</div>
<Separator />
<Box className="justify-center p-4 text-sm text-muted-foreground">
All rights reserved &copy; 2024
</Box>
</Box>
);
};
------------------------------------------------------------------------------------------------------------------------
const PURPLE_COLORS = [
"#8a2be2", // Blue Violet
"#9932cc", // Dark Orchid
"#a020f0", // Purple
"#9370db", // Medium Purple
"#ba55d3", // Medium Orchid
"#8b008b", // Dark Magenta
"#800080", // Purple
"#9400d3", // Dark Violet
"#9932cc", // Dark Orchid
"#800080", // Purple
];
------------------------------------------------------------------------------------------------------------------------
list-item.tsx
"use client";
import { cn } from "@/lib/utils";
import { Check } from "lucide-react";
interface ListItemProps {
category: any;
onSelect: (category: any) => void;
isChecked: boolean;
}
export const ListItem = ({ category, onSelect, isChecked }: ListItemProps) => {
return (
<div
className="flex items-center px-2 py-1 cursor-pointer hover:bg-gray-50 text-muted-foreground hover:text-primary"
onClick={() => onSelect(category)}
>
<Check
className={cn(
"ml-auto h-4 w-4",
isChecked ? "opacity-100" : "opacity-0"
)}
/>
<p className="w-full truncate text-sm whitespace-nowrap">
{category.label}
</p>
</div>
);
};
------------------------------------------------------------------------------------------------------------------------
combo-box.tsx
"use client";
import * as React from "react";
import { Check, ChevronsUpDown, Search } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ListItem } from "./list-item";
interface ComboboxProps {
options: { label: string; value: string }[];
value?: string;
onChange: (value: string) => void;
heading: string;
}
export const Combobox = ({
options,
value,
onChange,
heading,
}: ComboboxProps) => {
const [open, setOpen] = React.useState(false);
const [searchTerm, setSearchTerm] = React.useState("");
const [filtered, setFiltered] = React.useState<
{ label: string; value: string }[]
>([]);
const handleSearchTerm = (e: any) => {
setSearchTerm(e.target.value);
setFiltered(
options.filter((item) =>
item.label.toLowerCase().includes(searchTerm.toLowerCase())
)
);
};
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-full justify-between"
>
{value
? options.find((option) => option.value === value)?.label
: "Select option..."}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0 md:min-w-96">
<Command>
<div className="w-full px-2 py-1 flex items-center border rounded-md border-gray-100">
<Search className="mr-2 h-4 w-4 min-w-4" />
<input
type="text"
placeholder="Search category"
onChange={handleSearchTerm}
className="flex-1 w-full outline-none text-sm py-1"
/>
</div>
<CommandList>
<CommandGroup heading={heading}>
{searchTerm === "" ? (
options.map((option) => (
<ListItem
key={option.value}
category={option}
onSelect={() => {
onChange(option.value === value ? "" : option.value);
setOpen(false);
}}
isChecked={option?.value === value}
/>
))
) : filtered.length > 0 ? (
filtered.map((option) => (
<ListItem
key={option.value}
category={option}
onSelect={() => {
onChange(option.value === value ? "" : option.value);
setOpen(false);
}}
isChecked={option?.value === value}
/>
))
) : (
<CommandEmpty>No Category Found</CommandEmpty>
)}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment