generated from 202306-NEA-DZ-FEW/capstone-template
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #133 from 202306-NEA-DZ-FEW/fixes/careers
feat(career page): create individual job page
- Loading branch information
Showing
32 changed files
with
1,570 additions
and
705 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { useTranslation } from "next-i18next"; | ||
import React from "react"; | ||
|
||
export default function Jobs({ onEdit, onDelete, job, title }) { | ||
const handleEditClick = () => { | ||
// Trigger the onEdit function with the entire job object | ||
onEdit(job); | ||
}; | ||
|
||
const handleDeleteClick = async () => { | ||
try { | ||
// Trigger the onDelete function with the job ID | ||
await onDelete(job); | ||
} catch (error) { | ||
console.log("im a joooob", job); | ||
console.error("Error deleting job:", error); | ||
} | ||
}; | ||
|
||
const { t } = useTranslation("common"); | ||
|
||
return ( | ||
<div className='flex flex-col font-poppins text-NeutralBlack w-[1080px] '> | ||
<div className='flex bg-slate-200 mt-3 mx-4 py-2 px-2 space-x-2 rounded-md hover:shadow-[4.0px_8.0px_8.0px_rgba(0,0,0,0.30)] duration-500 '> | ||
<h3 className='flex-1'>{title}</h3> | ||
<button | ||
onClick={handleEditClick} | ||
className='w-28 h-6 rounded-md self-end text-base font-poppins font-regular bg-Accent text-NeutralBlack hover:bg-[#879AB8] hover:text-NeutralWhite hover:scale-105 duration-500' | ||
> | ||
{t("Edit")} | ||
</button> | ||
<button | ||
onClick={handleDeleteClick} | ||
className='w-28 h-6 rounded-md self-end text-base font-poppins font-regular bg-Accent text-NeutralBlack hover:bg-[#879AB8] hover:text-NeutralWhite hover:scale-105 duration-500' | ||
> | ||
{t("Delete")} | ||
</button> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
import axios from "axios"; | ||
import format from "date-fns/format"; | ||
import { doc, setDoc } from "firebase/firestore"; | ||
import dynamic from "next/dynamic"; | ||
import { useTranslation } from "next-i18next"; | ||
import { useEffect, useMemo, useRef, useState } from "react"; | ||
import { toast } from "react-toastify"; | ||
import React from "react"; | ||
|
||
import "react-toastify/dist/ReactToastify.css"; | ||
|
||
import { db } from "@/util/firebase"; | ||
|
||
// TextEditor is dynamically loaded | ||
export default function JobsEdit({ job }) { | ||
const TextEditor = useMemo(() => { | ||
return dynamic(() => import("@/components/AdminDashboard/TextEditor"), { | ||
loading: () => <p>loading...</p>, | ||
ssr: false, | ||
}); | ||
}, []); | ||
|
||
const { t } = useTranslation("common"); | ||
// State to manage form data and other UI elements | ||
const [editorContent, setEditorContent] = useState(""); | ||
const [formData, setFormData] = useState({ | ||
title: "", | ||
department: "", | ||
description: "", | ||
location: "", | ||
datePosted: format(new Date(), "yyyy-MM-dd"), | ||
id: uniqueID(), | ||
}); | ||
const fileInputRef = useRef(null); | ||
|
||
// useEffect to populate form data when 'job' prop changes | ||
useEffect(() => { | ||
if (job) { | ||
setFormData({ | ||
title: job.title || "", | ||
department: job.department || "", | ||
location: job.location || "", | ||
datePosted: job.datePosted || format(new Date(), "yyyy-MM-dd"), | ||
id: job.id || uniqueID(), | ||
}); | ||
setEditorContent(job.description || ""); | ||
} | ||
}, [job]); | ||
|
||
// Function to generate a unique ID | ||
function uniqueID() { | ||
return ( | ||
Math.random().toString(36).substring(2) + Date.now().toString(36) | ||
); | ||
} | ||
|
||
// Maximum character limits for input fields | ||
const MAX_TITLE_LENGTH = 40; | ||
const MAX_DEPARTMENT_LENGTH = 40; | ||
const MAX_LOCATION_LENGTH = 30; | ||
|
||
// Function to check if the form is valid before submission | ||
const isFormValid = () => { | ||
const isNonEmptyString = (value) => { | ||
return value && value.trim() !== ""; | ||
}; | ||
|
||
// Check if all required fields are filled | ||
const requiredFieldsFilled = | ||
isNonEmptyString(formData.title) && | ||
isNonEmptyString(formData.location) && | ||
isNonEmptyString(formData.department) && | ||
isNonEmptyString(formData.description); // Check for non-empty description | ||
formData.description !== null && formData.description !== "<p><br></p>"; | ||
|
||
return requiredFieldsFilled; | ||
}; | ||
|
||
// Function to display a toast notification for incomplete form | ||
const showToast = () => { | ||
toast.error("Please fill in all required fields!", { | ||
position: toast.POSITION.TOP_CENTER, | ||
}); | ||
}; | ||
|
||
// Function to handle form submission | ||
const handleSubmit = async (e) => { | ||
e.preventDefault(); | ||
if (!isFormValid()) { | ||
showToast(); | ||
return; | ||
} | ||
try { | ||
await setDoc(doc(db, "jobs", formData.id), formData); | ||
} catch (err) { | ||
console.error("Error adding the job", err); | ||
} | ||
// Clear the form fields after submission | ||
setFormData({ | ||
title: "", | ||
department: "", | ||
location: "", | ||
datePosted: format(new Date(), "yyyy-MM-dd"), | ||
id: uniqueID(), | ||
}); | ||
setEditorContent(""); | ||
if (fileInputRef.current) { | ||
fileInputRef.current.value = ""; | ||
} | ||
}; | ||
|
||
// Function to handle changes in input fields | ||
const handleChange = (e) => { | ||
const { name, value } = e.target; | ||
|
||
// Check for input name and restrict character length accordingly | ||
let truncatedValue = value; | ||
switch (name) { | ||
case "title": | ||
if (value.length > MAX_TITLE_LENGTH) { | ||
truncatedValue = value.substring(0, MAX_TITLE_LENGTH); | ||
} | ||
break; | ||
case "department": | ||
if (value.length > MAX_DEPARTMENT_LENGTH) { | ||
truncatedValue = value.substring(0, MAX_DEPARTMENT_LENGTH); | ||
} | ||
break; | ||
case "location": | ||
if (value.length > MAX_LOCATION_LENGTH) { | ||
truncatedValue = value.substring(0, MAX_LOCATION_LENGTH); | ||
} | ||
break; | ||
default: | ||
break; | ||
} | ||
setFormData({ | ||
...formData, | ||
[name]: truncatedValue, | ||
}); | ||
}; | ||
|
||
// Function to handle changes in the editor content | ||
|
||
const handleChangeEditor = (content) => { | ||
setEditorContent(content); | ||
setFormData((prevFormData) => ({ | ||
...prevFormData, | ||
description: content, | ||
})); | ||
}; | ||
|
||
// Function to handle resetting form fields | ||
const handleReset = () => { | ||
setFormData({ | ||
title: "", | ||
department: "", | ||
location: "", | ||
date: format(new Date(), "yyyy-MM-dd"), | ||
id: uniqueID(), | ||
}); | ||
setEditorContent(""); | ||
if (fileInputRef.current) { | ||
fileInputRef.current.value = ""; | ||
} | ||
}; | ||
|
||
return ( | ||
<div className='flex flex-col md:flex-row md:w-full mx-6'> | ||
<form onSubmit={handleSubmit} className='flex flex-col'> | ||
{/* Title */} | ||
<div className='mb-4 space-y-1 text-xl flex flex-col'> | ||
<label htmlFor='Title'>{t("Title")}</label> | ||
<input | ||
type='text' | ||
id='Title' | ||
className='w-full rounded-md border border-gray-300 bg-white py-3 outline-none' | ||
value={formData.title} | ||
onChange={handleChange} | ||
name='title' | ||
/> | ||
</div> | ||
{/* Department */} | ||
<div className='mb-4 space-y-1 text-xl flex flex-col'> | ||
<label htmlFor='Department'>{t("Department")}</label> | ||
<input | ||
type='text' | ||
id='Department' | ||
className='w-full rounded-md border border-gray-300 bg-white py-3 outline-none' | ||
value={formData.department} | ||
onChange={handleChange} | ||
name='department' | ||
/> | ||
</div> | ||
{/* Location */} | ||
<div className='mb-4 space-y-1 text-xl flex flex-col'> | ||
<label htmlFor='Location'>{t("Location")}</label> | ||
<input | ||
type='text' | ||
id='Location' | ||
className='w-full rounded-md border border-gray-300 bg-white py-3 outline-none' | ||
value={formData.location} | ||
onChange={handleChange} | ||
name='location' | ||
/> | ||
</div> | ||
{/* Reset and Submit buttons */} | ||
<div className='flex flex-row'> | ||
<button | ||
onClick={handleReset} | ||
className='w-32 h-10 self-start ml-4 mt-20 rounded-md text-base font-poppins font-regular bg-Accent text-NeutralBlack hover:bg-[#879AB8] hover:text-NeutralWhite hover:scale-105 duration-500' | ||
> | ||
{t("Reset")} | ||
</button> | ||
<button | ||
onClick={handleSubmit} | ||
className='w-32 h-10 self-start ml-4 mt-20 rounded-md text-base font-poppins font-regular bg-Accent text-NeutralBlack hover:bg-[#879AB8] hover:text-NeutralWhite hover:scale-105 duration-500' | ||
> | ||
{t("Submit")} | ||
</button> | ||
</div> | ||
</form> | ||
{/* Text Editor */} | ||
<div className='pl-4 mb-4 h-fit space-y-1 text-xl flex flex-col'> | ||
<span className='text-NeutralBlack'>{t("Body")}</span> | ||
<TextEditor | ||
value={editorContent} | ||
onChange={handleChangeEditor} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Import useTranslation for mocking | ||
import renderer from "react-test-renderer"; | ||
|
||
import Jobs from "../Jobs"; | ||
|
||
jest.mock("next-i18next", () => ({ | ||
// Mocking the useTranslation hook | ||
useTranslation: () => ({ t: (key) => key }), | ||
})); | ||
|
||
it("renders correctly", () => { | ||
// Mock blog object with a title property | ||
const mockJob = { title: "Test Job" /* other properties */ }; | ||
|
||
// Mock onDelete and onEdit functions | ||
const mockOnDelete = jest.fn(); | ||
const mockOnEdit = jest.fn(); | ||
|
||
const tree = renderer | ||
.create( | ||
<Jobs onEdit={mockOnEdit} onDelete={mockOnDelete} job={mockJob} /> | ||
) | ||
.toJSON(); | ||
|
||
expect(tree).toMatchSnapshot(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import renderer from "react-test-renderer"; | ||
|
||
import JobsEdit from "../JobsEdit"; | ||
|
||
it("renders correctly", () => { | ||
const tree = renderer.create(<JobsEdit />).toJSON(); | ||
expect(tree).toMatchSnapshot(); | ||
}); |
Oops, something went wrong.