Skip to content

Commit

Permalink
Merge pull request #133 from 202306-NEA-DZ-FEW/fixes/careers
Browse files Browse the repository at this point in the history
feat(career page): create individual job page
  • Loading branch information
GhaniBahri authored Nov 22, 2023
2 parents e24acb0 + f130d40 commit a2ea12d
Show file tree
Hide file tree
Showing 32 changed files with 1,570 additions and 705 deletions.
Binary file added public/assets/CareersOffice.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/CareersPage.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/assets/RequirementPage.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 33 additions & 1 deletion public/locales/ar/common.json

Large diffs are not rendered by default.

39 changes: 33 additions & 6 deletions public/locales/en/common.json

Large diffs are not rendered by default.

41 changes: 34 additions & 7 deletions public/locales/fr/common.json

Large diffs are not rendered by default.

Empty file.
41 changes: 41 additions & 0 deletions src/components/AdminDashboard/Jobs.jsx
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>
);
}
233 changes: 233 additions & 0 deletions src/components/AdminDashboard/JobsEdit.jsx
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>
);
}
36 changes: 19 additions & 17 deletions src/components/AdminDashboard/ReceivedEmails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const ReceivedEmails = ({ emails: initialEmails }) => {
const [emailSubject, setEmailSubject] = useState("");
const [isSendingEmail, setIsSendingEmail] = useState(false);
const [selectedEmailId, setSelectedEmailId] = useState(null);
const [currentTab, setCurrentTab] = useState("all");
const [currentTab, setCurrentTab] = useState("notArchived");
const [emails, setEmails] = useState(initialEmails); // Store emails in state

const handleSendEmailClick = (emailId) => {
Expand Down Expand Up @@ -98,13 +98,13 @@ const ReceivedEmails = ({ emails: initialEmails }) => {
<div className='flex space-x-4 mb-4'>
<button
className={`py-2 px-4 ${
currentTab === "all"
currentTab === "notArchived"
? "bg-blue-500 text-white"
: "bg-gray-300 text-gray-700"
} rounded`}
onClick={() => setCurrentTab("all")}
onClick={() => setCurrentTab("notArchived")}
>
All
Not Archived
</button>
<button
className={`py-2 px-4 ${
Expand All @@ -118,13 +118,13 @@ const ReceivedEmails = ({ emails: initialEmails }) => {
</button>
<button
className={`py-2 px-4 ${
currentTab === "notArchived"
currentTab === "all"
? "bg-blue-500 text-white"
: "bg-gray-300 text-gray-700"
} rounded`}
onClick={() => setCurrentTab("notArchived")}
onClick={() => setCurrentTab("all")}
>
Not Archived
All
</button>
</div>
<table className='w-full border-collapse border border-gray-300'>
Expand Down Expand Up @@ -155,18 +155,20 @@ const ReceivedEmails = ({ emails: initialEmails }) => {
<td className='py-2 px-4 border-b'>
{email.data.Details}
</td>
<td className='py-2 px-4 border-b flex flex-row'>
<button
className='mr-2 bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 focus:outline-none focus:bg-blue-600'
onClick={() =>
handleSendEmailClick(email.id)
}
>
Respond
</button>
<td className='py-2 px-4 border-b flex flex-row justify-end'>
{!email.data.archived && (
<button
className='mr-2 bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 focus:outline-none focus:bg-blue-600'
onClick={() =>
handleSendEmailClick(email.id)
}
>
Respond
</button>
)}
{email.data.archived ? (
<button
className='bg-gray-300 text-gray-700 px-4 py-2 rounded hover:bg-gray-400 focus:outline-none focus:bg-gray-400'
className='bg-gray-300 text-gray-700 px-4 py-2 rounded hover:bg-gray-400 focus:outline-none focus:bg-gray-400 '
onClick={() =>
handleDelete(
email.id,
Expand Down
26 changes: 26 additions & 0 deletions src/components/AdminDashboard/__test__/Jobs.test.js
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();
});
8 changes: 8 additions & 0 deletions src/components/AdminDashboard/__test__/JobsEdit.test.js
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();
});
Loading

0 comments on commit a2ea12d

Please sign in to comment.