komponenty

This commit is contained in:
Jakub K 2023-12-15 23:54:10 +01:00
parent 536fdc63d2
commit 4c6c4cf2da
27 changed files with 1034 additions and 21 deletions

View File

@ -3,7 +3,9 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link href="/dist/output.css" rel="stylesheet"> <link href="/dist/output.css" rel="stylesheet" />
<link rel="icon" href="../icon/favicon.ico"/>
<!-- todo ICONs -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>izaac frontend</title> <title>izaac frontend</title>
</head> </head>

6
package-lock.json generated
View File

@ -11,6 +11,7 @@
"@ckeditor/ckeditor5-build-classic": "^40.0.0", "@ckeditor/ckeditor5-build-classic": "^40.0.0",
"@ckeditor/ckeditor5-build-multi-root": "^40.0.0", "@ckeditor/ckeditor5-build-multi-root": "^40.0.0",
"@ckeditor/ckeditor5-react": "^6.1.0", "@ckeditor/ckeditor5-react": "^6.1.0",
"dompurify": "^3.0.6",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.18.0" "react-router-dom": "^6.18.0"
@ -2069,6 +2070,11 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/dompurify": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.6.tgz",
"integrity": "sha512-ilkD8YEnnGh1zJ240uJsW7AzE+2qpbOUYjacomn3AvJ6J4JhKGSZ2nh4wUIXPZrEPppaCLx5jFe8T89Rk8tQ7w=="
},
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.574", "version": "1.4.574",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.574.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.574.tgz",

View File

@ -13,6 +13,7 @@
"@ckeditor/ckeditor5-build-classic": "^40.0.0", "@ckeditor/ckeditor5-build-classic": "^40.0.0",
"@ckeditor/ckeditor5-build-multi-root": "^40.0.0", "@ckeditor/ckeditor5-build-multi-root": "^40.0.0",
"@ckeditor/ckeditor5-react": "^6.1.0", "@ckeditor/ckeditor5-react": "^6.1.0",
"dompurify": "^3.0.6",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.18.0" "react-router-dom": "^6.18.0"

View File

@ -5,29 +5,35 @@ import Home from './components/Home';
import { BrowserRouter as Router } from 'react-router-dom'; import { BrowserRouter as Router } from 'react-router-dom';
import { Route, Routes } from 'react-router-dom'; import { Route, Routes } from 'react-router-dom';
import Contact from './components/Contact'; import Contact from './components/Contact';
import JobPosting from './components/JobPosting';
import Mininav from './components/Mininav'; import Mininav from './components/Mininav';
import Register from './components/Register'; import AddJobListing from './components/AddJobListing';
function App() { function App() {
const loc = () =>{
if (location.pathname.startsWith('work/jobposting')){
return ""
} else {
return "overflow-hidden"
}
}
return ( return (
<Router> <Router>
<div className='bg-white w-full overflow-hidden'> <div className={`bg-white w-full ${loc}`}>
<div className={`${styles.paddingX} ${styles.flexCenter}`}> <div className={`${styles.paddingX} ${styles.flexCenter} border-b-2`}>
<Mininav /> <Mininav />
<div className={`${styles.boxWidth}`}> <div className={`${styles.boxWidth}`}>
<NavBar /> <NavBar />
</div> </div>
</div> </div>
<div className={`${styles.flexStart}`}> <div className={`${styles.flexStart}`}>
<div className={`${styles.boxWidth2} h-full`}> <div className={`${styles.boxWidth2} `}>
<Routes> <Routes>
<Route path="/home" element={<Home />} /> <Route path="/home" element={<Home />} />
<Route path="/work" element={<WorkApp />} /> <Route path="/work" element={<WorkApp />} />
<Route path="/work/jobpostings" element={<WorkApp />} /> <Route path="/work/postings" element={<WorkApp />} />
<Route path="/contact" element={<Contact />} /> <Route path="/contact" element={<Contact />} />
<Route path="/work/jobposting" element={<JobPosting />} /> <Route path="/work/jobposting" element={<AddJobListing />} />
{/* Add more routes as needed */} {/* Add more routes as needed */}
</Routes> </Routes>
</div> </div>

View File

@ -2,9 +2,11 @@ import close from './close.svg'
import menu from './menu.svg' import menu from './menu.svg'
import search from './search.svg' import search from './search.svg'
import IzaacLOGO from './IzaacLOGO.svg' import IzaacLOGO from './IzaacLOGO.svg'
import placeholderImage from './placeholderImage.svg'
export { export {
close, close,
menu, menu,
search, search,
IzaacLOGO IzaacLOGO,
placeholderImage
} }

View File

@ -0,0 +1,23 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="800.000000pt" height="600.000000pt" viewBox="0 0 800.000000 600.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.16, written by Peter Selinger 2001-2019
</metadata>
<g transform="translate(0.000000,600.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M2780 3873 c-40 -14 -40 -4 -40 -883 0 -808 1 -848 18 -863 17 -16
118 -17 1254 -17 1029 0 1237 2 1247 14 8 10 10 246 9 872 l-3 858 -25 13
c-20 10 -278 13 -1235 12 -665 0 -1217 -3 -1225 -6z m2390 -878 l0 -775 -1160
0 -1160 0 0 775 0 775 1160 0 1160 0 0 -775z"/>
<path d="M3075 3596 c-64 -33 -97 -81 -103 -151 -12 -144 129 -245 260 -187
67 29 101 80 106 157 5 77 -20 133 -77 170 -58 39 -124 43 -186 11z"/>
<path d="M4208 3330 c-14 -10 -129 -161 -257 -335 -128 -173 -233 -315 -235
-315 -2 0 -39 48 -82 108 -103 141 -126 162 -175 162 -31 0 -47 -7 -73 -32
-31 -30 -476 -631 -476 -643 0 -3 493 -5 1096 -5 931 0 1095 2 1091 14 -3 8
-173 242 -378 521 -226 307 -385 515 -404 526 -39 24 -76 24 -107 -1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,87 @@
import React, { useState, useEffect } from 'react'
import StepOneJoblisting from './StepOneJoblisting';
import StepTwoJoblisting from './StepTwoJoblisting';
import StepThreeJoblisting from './StepThreeJoblisting';
import StepFourJoblisting from './StepFourJoblisting'
const AddJobListing = () => {
const [currentStep, setCurrentStep] = useState(1)
const [formData, setFormData] = useState({
'postingOption': 'Opcja 2',
'requireSalary': true
})
const nextStep = () => {
setCurrentStep(currentStep + 1);
};
const prevStep = () => {
setCurrentStep(currentStep - 1);
};
const removeFields = (fieldNames) => {
// Utwórz nowy obiekt, który początkowo jest kopią formData
let newFormData = { ...formData };
// Iteruj przez każde pole do usunięcia
fieldNames.forEach(fieldName => {
// Usuń pole z nowego obiektu danych
delete newFormData[fieldName];
});
// Aktualizuj stan z nowym obiektem
setFormData(newFormData);
};
const handleChange = input => value => {
// Sprawdzenie, czy 'value' jest obiektem zdarzenia
const isEvent = value && value.target;
let newValue;
if (isEvent) {
// Jeśli 'value' jest obiektem zdarzenia, użyj jego właściwości 'value'
newValue = value.target.value;
} else {
// W przeciwnym razie użyj 'value' bezpośrednio
newValue = value;
}
setFormData({ ...formData, [input]: newValue });
console.log('Aktualny stan formularza:', formData);
};
const handleSubmit = () => {
}
useEffect(() => {
console.log('Aktualny stan formularza:', formData);
}, [formData]);
switch (currentStep) {
case 1:
return <StepOneJoblisting
nextStep={nextStep}
handleChange={handleChange}
formData={formData} />;
case 2:
return <StepFourJoblisting
nextStep={nextStep}
formData={formData}
handleChange={handleChange} />;
case 3:
return <StepTwoJoblisting
nextStep={nextStep}
prevStep={prevStep}
removeFields={removeFields}
setFormData={setFormData}
handleChange={handleChange}
formData={formData} />;
case 4:
return <StepThreeJoblisting
prevStep={prevStep}
handleSubmit={handleSubmit}
formData={formData} />;
default:
return <div>Unknown step</div>;
}
};
export default AddJobListing;

View File

@ -0,0 +1,62 @@
import React, { useState, useRef, useEffect } from 'react';
import { IzaacLOGO, placeholderImage } from '../assets'
const ImageUpload = ({setFormData}) => {
const [imageSrc, setImageSrc] = useState(placeholderImage);
const fileInputRef = useRef(); // Referencja do ukrytego inputu plików
const handleImageChange = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
setImageSrc(e.target.result); // Aktualizujemy stan obrazkiem użytkownika
};
reader.readAsDataURL(file);
}
};
const handleDivClick = () => {
fileInputRef.current.click(); // Programowe kliknięcie na ukrytym inpucie plików
};
useEffect(() => {
// Aktualizujemy dane w hooku nadrzędnym za każdym razem, gdy zmienia się imageSrc
if (imageSrc !== placeholderImage) {
setFormData(prevFormData => ({
...prevFormData,
image: imageSrc
}));
}
}, [imageSrc, setFormData]);
return (
<>
<div
onClick={handleDivClick}
className='h-[12rem] rounded-lg border-[0.3rem] border-slate-300'
style={{
backgroundImage: `url(${imageSrc})`,
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
backgroundColor: '#FFFFFF',
}}
>
<input
ref={fileInputRef}
type="file"
id="avatar"
name="avatar"
accept="image/png, image/jpeg"
onChange={handleImageChange}
style={{ display: 'none' }} // Ukryj input plików
/>
</div>
</>
);
};
export default ImageUpload;

View File

@ -4,7 +4,7 @@ import { close, search, menu, IzaacLOGO} from '../assets';
import { useState } from 'react'; import { useState } from 'react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import Mininav from './Mininav';
const NavBar = () => { const NavBar = () => {
const[toggle, setToggle] = useState(false) const[toggle, setToggle] = useState(false)
const[toggleSearch, setToggleSearch] = useState(false) const[toggleSearch, setToggleSearch] = useState(false)
@ -20,8 +20,8 @@ const NavBar = () => {
const currentLinks = getLinks() const currentLinks = getLinks()
return ( return (
<nav className='w-full flex py-6 justify-between items-center navbar '> <nav className='w-full flex pt-7 justify-between items-center navbar '>
<img src={IzaacLOGO} alt="izaac" className='md:w-[200px] w-[120px]'/> <img src={IzaacLOGO} alt="izaac" className='md:w-[200px] w-[120px]'/>
<ul className='list-none sm:flex hidden justify-end items-center flex-1'> <ul className='list-none sm:flex hidden justify-end items-center flex-1'>

127
src/components/Salary.jsx Normal file
View File

@ -0,0 +1,127 @@
import React, { useState, useEffect } from 'react'
import styles from '../style'
const EmploymentTypes = [
'Kontrakt B2B',
'Umowa o pracę',
'Umowa zlecenie',
'Umowa o dzieło',
'Staż',
]
const WorkFromHomes = [
'Praca zdalna',
'Hybrydowo',
'W biurze',
'Inne'
]
const Salary = ({handleChange, formData, removeFields, setFormData}) => {
const [requireSalary, setRequireSalary] = useState(true)
const handleRequireSalary = () => {
setRequireSalary(prevRequireSalary => !prevRequireSalary);
};
useEffect(() => {
setFormData({...formData, 'requireSalary': requireSalary});
}, [requireSalary]);
const [minBigger, setMinBigger] = useState(true);
const handleCheck = () => {
let minSalary = parseInt(formData.minSalary);
let maxSalary = parseInt(formData.maxSalary);
if (minSalary > maxSalary) {
setMinBigger(true)
}
else {
setMinBigger(false)
}
};
useEffect(() => {
handleCheck();
console.log(minBigger)
console.log(formData.minSalary)
console.log(formData.maxSalary)
}, [formData.minSalary, formData.maxSalary]);
useEffect(() => {
})
useEffect( () =>{
if (formData.requireSalary === false) {
removeFields(['minSalary', 'maxSalary']);
setRequireSalary(formData.requireSalary)
}
}, [formData.requireSalary])
return (
<div className='grid grid-cols-4 mt-4 gap-6'>
<div className='col-span-4'>
<input
className='ml-6'
type="checkbox"
checked={!requireSalary}
name="requireSalary"
onChange={handleRequireSalary}
id="" />
<span className='font-poppins font-semibold text-slate-700 text-center text-[14px] mt-1 mb-2'> Nie chce podawać wysokości wynagrodzenia</span>
</div>
<div className='relative'>
<p className='ml-5 font-poppins font-semibold text-slate-700 text-center text-[14px] mt-1 mb-2'>Min. wynagrodzenie</p>
<input
disabled={!requireSalary}
name='minSalary'
value={formData['minSalary'] || '' }
onChange={handleChange('minSalary')}
type='number'
className={`h-12 border-2 rounded-xl px-5 mx-4 w-full ${!requireSalary ? 'bg-slate-200' : ''}`}/>
{minBigger && (
<div className='absolute top-[5rem] left-8 border-2 border-red-500 bg-red-200 rounded-b-xl p-2 opacity-90' >
<p className='font-poppins text-red-700 text-[14px] mt-1 mb-1'>Minimalne wynagrodzenie nie może być większe od maks. wynagrodzenia</p>
</div>
)}
</div>
<div >
<p className='ml-5 font-poppins font-semibold text-slate-700 text-center text-[14px] mt-1 mb-2'>Maks. wynagrodzenie</p>
<input
disabled={!requireSalary}
value={formData['maxSalary'] || '' }
name="maxSalary"
onChange={handleChange('maxSalary')}
type='number'
className={`h-12 border-2 rounded-xl px-5 mx-4 w-full ${!requireSalary ? 'bg-slate-200' : ''} `}/></div>
<div>
<p className='font-poppins font-semibold text-slate-700 text-center text-[14px] mt-1 mb-2'>Typ kontraktu</p>
<select
value={formData['employmentType'] || 'default' }
name='employmentType'
onChange={handleChange('employmentType')}
className='h-12 border-2 rounded-xl px-5 mx-4 w-full '>
<option value={'default'} disabled >Wybierz opcję</option>
{EmploymentTypes.map(EmploymentType => (
<option key={EmploymentType}>{EmploymentType}</option>
))}
</select>
</div>
<div>
<p className='font-poppins font-semibold text-slate-700 text-center text-[14px] mt-1 mb-2'>Praca zdalna</p>
<select
value={formData['workFromHome'] || 'default' }
name='workFromHome'
onChange={handleChange('workFromHome')}
className='h-12 border-2 rounded-xl px-5 mx-4 w-full'>
<option value={'default'} disabled >Wybierz opcję</option>
{WorkFromHomes.map(WorkFromHomes => (
<option key={WorkFromHomes}>{WorkFromHomes}</option>
))}
</select>
</div>
</div>
)
}
export default Salary

View File

@ -0,0 +1,66 @@
import React from 'react';
const levels = ['Nice to have','Podstawowy', 'Średnio zaawansowany', 'Zaawansowany', 'Ekspert'];
function renderCircles(level, handleCircleClick) {
let numberOfFilledCircles;
switch (level) {
case 'Nice to have':
numberOfFilledCircles = 1;
break;
case 'Podstawowy':
numberOfFilledCircles = 2;
break;
case 'Średnio zaawansowany':
numberOfFilledCircles = 3;
break
case 'Zaawansowany':
numberOfFilledCircles = 4;
break;
case 'Ekspert':
numberOfFilledCircles = 5;
break;
default:
numberOfFilledCircles = 1;
}
return (
<>
{[...Array(5)].map((_, index) => (
<div
key={index}
onClick={() => handleCircleClick(index)}
className={`justify-self-center w-3.5 h-3.5 rounded-full ${index < numberOfFilledCircles ? 'bg-red-300 hover:bg-red-700' : 'bg-gray-500 hover:bg-gray-600'}`}
/>
))}
</>
);
};
const SelectedSkill = ({ skill, level, onLevelChange, removeSkillFromList, formData }) => {
const levelss = 'Nice to have';
const handleCircleClick = (levelIndex) => {
const level = levels[levelIndex];
onLevelChange(skill, level);
};
return (
<div className="skill-block">
<div key={skill} className="relative selected-skill bg-slate-200 rounded-2xl h-20 w-56 p-2 mb-5">
<p className='text-slate-700 font-semibold text-[12px] text-center'>{skill}</p>
<button onClick={(e) => {e.preventDefault(); removeSkillFromList(skill)}} className="absolute top-1 right-5 text-black">
&times;
</button>
<div className='h-0.5 w-full bg-dimWhite opacity-60 mt-1'></div>
<div className='grid grid-cols-5 mt-2'>
{renderCircles(level, handleCircleClick)}
</div>
<p className='font-poppins font-semibold text-slate-700 text-center text-[12px] mt-1'>{level}</p>
</div>
</div>
);
};
export default SelectedSkill;

View File

@ -0,0 +1,143 @@
import React, { useState, useEffect } from 'react';
import SelectedSkill from './SelectedSkill';
const initialSkillsList = [
'Inżynieria oprogramowania',
'Analiza danych',
'Projektowanie CAD',
'Automatyka',
'Robotyka',
'Inżynieria materiałowa',
'Inżynieria elektryczna',
'Inżynieria mechaniczna',
'Programowanie mikrokontrolerów',
'Analiza strukturalna',
'Modelowanie 3D',
'Projektowanie układów elektronicznych',
'Programowanie PLC',
'Sztuczna inteligencja',
'Machine learning',
'Inżynieria chemiczna',
'Inżynieria biomedyczna',
'Inżynieria środowiska',
'Automatyka przemysłowa',
'Zarządzanie projektami technicznymi',
'Prototypowanie',
'Przetwarzanie sygnałów',
'Inżynieria systemów',
'Analiza termodynamiczna',
'Fizyka stosowana',
'Inżynieria procesowa',
'Wytwarzanie addytywne',
'Technologia nanomateriałów',
'Projektowanie systemów wbudowanych',
'Inżynieria bezpieczeństwa',
// ... więcej umiejętności ...
];
const SkillsSelector = ({formData, setFormData}) => {
const [inputValue, setInputValue] = useState('');
const [suggestions, setSuggestions] = useState([]);
const [selectedSkills, setSelectedSkills] = useState([]);
const handleInputChange = (e) => {
const value = e.target.value;
setInputValue(value);
if (value.length >= 0) {
setSuggestions(initialSkillsList.filter(skill =>
skill.toLowerCase().includes(value.toLowerCase())));
} else {
setSuggestions([]);
}
};
const handleSuggestionClick = (skill) => {
if (!selectedSkills.includes(skill)) {
setSelectedSkills(prevSkills => [skill, ...prevSkills]);
};
setSkillLevels(prevLevels => ({
...prevLevels,
[skill]: 'Nice to have' // lub inny domyślny poziom
}));
setInputValue('');
setSuggestions([]);
};
const removeSkillFromList = (skillToRemove) => {
setSelectedSkills(prevSkills => prevSkills.filter(skill => skill !== skillToRemove));
setSkillLevels(prevLevels => {
const newLevels = { ...prevLevels };
delete newLevels[skillToRemove];
return newLevels;
});
};
const [skillLevels, setSkillLevels] = useState({});
const handleLevelChange = (skill, newLevel) => {
setSkillLevels(prevLevels => ({ ...prevLevels, [skill]: newLevel }));
};
useEffect(() => {
// Aktualizacja formData, aby zawierała skillLevels
setFormData(prevFormData => ({
...prevFormData,
skillLevels: skillLevels
}));
}, [skillLevels]);
useEffect(() => {
// Jeśli formData zawiera już wybrane umiejętności i ich poziomy
if (formData.skillLevels) {
setSkillLevels(formData.skillLevels);
// Ustaw wybrane umiejętności na podstawie kluczy w formData.skillLevels
setSelectedSkills(Object.keys(formData.skillLevels));
}
}, []);
useEffect(() => {
console.log('Zaktualizowane poziomy umiejętności:', skillLevels);
}, [skillLevels, ]);
return (
<div className='relative mt-3 grid grid-cols-5'>
<div className='col-span-5 grid mb-4'>
<div>
<input
type="text"
value={inputValue}
onChange={handleInputChange}
placeholder="Wpisz umiejętność"
className="selected-skill bg-slate-200 rounded-2xl h-20 w-56 py-2 px-4 text-slate-700 font-semibold text-[12px] col-span-3"
/>
</div>
{inputValue.length >= 1 && suggestions.length > 0 && (
<div className="absolute border-4 w-56 top-16 z-10 overflow-y-auto overflow-x-hidden m" >
{suggestions.map(suggestion => (
<div
key={suggestion}
onClick={() => handleSuggestionClick(suggestion)}
className="w-56 h-max px-3 py-1 bg-slate-300 border-b-2 cursor-pointer"
>
<p className='text-slate-700 font-semibold text-[12px]'> {suggestion}</p>
</div>
))}
</div>
)}
</div>
{/* Lista wybranych umiejętności z możliwością określenia poziomu */}
{selectedSkills.map(skill => (
<SelectedSkill
key={skill}
skill={skill}
level={skillLevels[skill] || 'Nice to have'} // Domyślny poziom, jeśli nie ustawiony
onLevelChange={handleLevelChange}
removeSkillFromList={removeSkillFromList}
/>
))}
</div>
);
};
export default SkillsSelector;

View File

@ -0,0 +1,120 @@
import React from "react";
import styles from "../style";
import TextDivider from "./TextDivider";
const StepFourJoblisting = ({ handleChange, formData, nextStep, prevStep }) => {
return (
<div className={`${styles.flexCenter}`}>
<div className="grid grid-cols-5">
<div className="col-span-3 col-start-2 mt-12">
<TextDivider text={`Podaj dane ogłoszeniodawcy`} />
<div className="grid grid-cols-3">
<div className="col-span-1 mt-4 mx-2">
<div className={`${styles.paragraph} px-4 py-2`}>
Imię
<span className={`${styles.paragraph} text-red-700`}>*</span>
</div>
<input
type="text"
name="first_name"
value={formData["first_name"] || ""}
required
id="first_name"
onChange={handleChange("first_name")}
placeholder="Twoje imię..."
className={`border-b-2 px-4 my-2 ${styles.paragraph}`}
/>
</div>
<div className="col-span-1 mt-4 mx-2">
<div className={`${styles.paragraph} px-4 py-2`}>
Nazwisko
<span className={`${styles.paragraph} text-red-700`}>*</span>
</div>
<input
type="text"
name="lastname"
value={formData["lastname"] || ""}
required
id="lastname"
onChange={handleChange("lastname")}
placeholder="Twoje nazwisko..."
className={`border-b-2 px-4 my-2 ${styles.paragraph}`}
/>
</div>
<div className="col-span-1 mt-4 mx-2">
<div className={`${styles.paragraph} px-4 py-2`}>
Adres e-mail
<span className={`${styles.paragraph} text-red-700`}>*</span>
</div>
<input
type="email"
name="email"
value={formData["email"] || ""}
required
id="email"
onChange={handleChange("email")}
placeholder="Adres mailowy..."
className={`border-b-2 px-4 my-2 ${styles.paragraph}`}
/>
</div>
<div className="col-span-2 mt-4 mx-2">
<div className={`${styles.paragraph} px-4 py-2`}>
Nazwa firmy
<span className={`${styles.paragraph} text-red-700`}>*</span>
</div>
<div className="grid grid-cols-2">
<input
type="text"
name="company_name"
value={formData["company_name"] || ""}
required
onChange={handleChange("company_name")}
id="company_name"
placeholder="Wpisz nazwę firmy..."
className={`border-b-2 px-4 mr-12 my-2 ${styles.paragraph} col-span-2 `}
/>
</div>
</div>
<div className="col-span-1 mt-4 mx-2">
<div className={`${styles.paragraph} px-4 py-2`}>
Nazwa firmy
<span className={`${styles.paragraph} text-red-700`}>*</span>
</div>
<input
type="number"
name="vat_number"
value={formData["vat_number"] || ""}
required
maxLength="10"
onChange={handleChange("vat_number")}
id="vat_number"
placeholder="Wpisz nip..."
className={`border-b-2 px-4 my-2 ${styles.paragraph} `}
/>
</div>
</div>
<div className={`${styles.flexCenter} gap-16 mt-16`}>
<button
type="button"
onClick={prevStep}
className="h-12 w-72 rounded-xl bg-gray-700 font-poppins font-semibold text-[14px] scale-100 text-white hover:scale-125 duration-300"
>
<span className="text-[18px]"></span>&nbsp;&nbsp;Przejdź do
poprzedniego kroku
</button>
<button
type="button"
onClick={nextStep}
className="h-12 w-72 rounded-xl bg-gray-700 font-poppins font-semibold text-[14px] scale-100 text-white hover:scale-125 duration-300"
>
Przejdź do następnego kroku &nbsp;&nbsp;
<span className="text-[18px]"></span>{" "}
</button>
</div>
</div>
</div>
</div>
);
};
export default StepFourJoblisting;

View File

@ -0,0 +1,68 @@
import React from 'react'
import styles from '../style'
const StepOneJoblisting = ({ nextStep, handleChange, formData }) => {
// Funkcja do obsługi kliknięcia na div i aktualizacji stanu
const handleDivClick = (value) => () => {
handleChange('postingOption')({ target: { value: value } });
};
const activeStyle = "h-[500px] w-64 border-4 rounded-xl border-stone-200 bg-gray-200 div-transition scale-125";
const inactiveStyle = "h-[500px] w-64 rounded-xl bg-gray-200 border-gray-200 div-transition cursor-pointer hover:bg-gray-300"; // Dodaj tę samą klasę przejścia tutaj
return (
<form>
<div className={` grid grid-cols-1 ${styles.paddingX} pb-8 pt-8 gap-1 h-full`}>
<h1 className={`${styles.heading1} text-center`}>Zacznij dodawać ogłoszenie z izaac.pl</h1>
<p className={`${styles.paragraph} text-center`}>Wybierz pakiet najlepiej odpowiadający Twoim potrzebom</p>
</div>
<div className={` ${styles.flexStart} ${styles.paddingX} gap-16 mt-20`}>
{/* Przykładowe divy jako przyciski wyboru */}
<div
className={formData.postingOption === 'Opcja 1' ? activeStyle : inactiveStyle}
onClick={handleDivClick('Opcja 1')}
>
<p className={`${styles.paragraph} text-center mt-4`}>Starter</p>
</div>
<div className='h-[400px] w-0.5 bg-gray-300'>
</div>
<div
className={formData.postingOption === 'Opcja 2' ? activeStyle : inactiveStyle}
onClick={handleDivClick('Opcja 2')}
>
<p className={`${styles.paragraph} text-center mt-4`}>Standard</p>
<div className='mt-6'>
<p className={`${styles.paragraph} text-center`}>najczęsciej wybierany</p>
</div>
</div>
<div className='h-[400px] w-0.5 bg-gray-300'>
</div>
<div
className={formData.postingOption === 'Opcja 3' ? activeStyle : inactiveStyle}
onClick={handleDivClick('Opcja 3')}
style={{ cursor: 'pointer' }}
>
<p className={`${styles.paragraph} text-center mt-4`}>Premium</p>
</div>
</div>
<div className='grid pt-32 items-center justify-center'>
<button
type="button"
onClick={nextStep}
className='h-12 w-72 rounded-xl bg-gray-700 font-poppins font-semibold text-[14px] text-white hover:scale-125 duration-300'>Przejdź do następnego kroku &nbsp;&nbsp;<span className='text-[18px]'></span> </button>
<div className='h-12 w-full'></div>
</div>
</form>
);
};
export default StepOneJoblisting

View File

@ -0,0 +1,88 @@
import React from 'react';
import DOMPurify from 'dompurify';
import styles from '../style';
import TextDivider from './TextDivider';
const StepThreeJoblisting = ({ formData, prevStep }) => {
// Funkcja do przetwarzania HTML i dodawania klas
const processHTML = (htmlString) => {
const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, 'text/html');
// Dodaj klasy do tagów h1, h2, ..., h6
doc.querySelectorAll('h1, h2, h3, h4, h5, h6').forEach(tag => {
tag.classList.add('no-tailwindcss-base');
});
// Dodaj klasy do tagów ol, ul
doc.querySelectorAll('ol, ul').forEach(tag => {
tag.classList.add('no-tailwindcss-base');
});
doc.querySelectorAll('li').forEach(tag => {tag.classList.add('lista')})
return doc.body.innerHTML;
};
// Sanituj i przetwórz dane
const cleanAndProcessData = (userInput) => {
const sanitizedHTML = DOMPurify.sanitize(userInput);
return processHTML(sanitizedHTML);
};
// Przykład renderowania przetworzonej treści jako komponentów React
const renderContent = (htmlString) => {
return <div dangerouslySetInnerHTML={{ __html: cleanAndProcessData(htmlString) }} />;
};
return (
<div className='text-center'>
<p className='mt-8 text-[24px] font-poppins font-bold '>
Wybrałeś opcję {formData.postingOption}
</p>
<TextDivider text={`Tak będzie wyglądało Twoje ogłoszenie na liście ogłoszeń`} />
<div className={`grid grid-cols-4`}>
<div
className={`job-listing col-start-2 col-end-4 grid grid-cols-4 drop-shadow cursor-pointer mb-2 sm:mr-4 mr-0 text-xl font-bold border rounded-[10px] p-2 hover:border-l-8 hover:border-zinc-500 duration-300 bg-gray-200`}
>
<p className='col-span-3 text-xl text-left tracking-wide'>F.H.U. Januszex</p>
<p className='place-self-end text-[16px] font-semibold tracking-widest text-slate-800'>12000 PLN - 15000 PLN</p>
<p className='col-span-4 text-xl text-left tracking-wide'>Starszy Inżynier ds. ciągłości produkcji</p>
</div>
<div
className={`col-start-2 col-end-4 grid grid-cols-4 drop-shadow cursor-pointer mb-2 sm:mr-4 mr-0 text-xl font-bold border rounded-[10px] p-2 hover:border-l-8 hover:border-zinc-500 duration-300 bg-gray-200`}
>
<p className='col-span-3 text-base text-left tracking-wide'>{cleanAndProcessData(formData.company_name)}</p>
{
formData.requireSalary && (<p className='place-self-end text-[16px] font-semibold tracking-widest text-slate-800'>{cleanAndProcessData(formData.minSalary)} PLN - {cleanAndProcessData(formData.maxSalary)} PLN</p> )
}
{
!formData.requireSalary && (<p className='place-self-end text-xl text-slate-800'></p> )
}
<p className='col-span-4 text-xl text-left'>{cleanAndProcessData(formData.jobtitle)}</p>
</div>
<div
className={`job-listing2 col-start-2 col-end-4 grid grid-cols-4 drop-shadow cursor-pointer mb-2 sm:mr-4 mr-0 text-xl font-bold border rounded-[10px] p-2 hover:border-l-8 hover:border-zinc-500 duration-300 bg-gray-200`}
>
<p className='col-span-3 text-xl text-left tracking-wide'>Papieżeks Sp. z o. o.</p>
<p className='place-self-end text-[16px] font-semibold tracking-widest text-slate-800'>9000 PLN - 12000 PLN</p>
<p className='col-span-4 text-xl text-left tracking-wide'>Konstruktor - inżynier mechanik</p>
</div>
</div>
<TextDivider text={`Tak będzie się prezentowała treść Twojego ogłoszenia`} />
<div className='grid grid-cols-5 mt-4 mb-4'>
<div className='col-span-3 col-start-2 bg-gray-100 text-justify text-[18px] leading-[1.75rem] p-4 rounded-xl'>
{renderContent(formData.content)}
</div>
</div>
<button
type="button"
onClick={prevStep}
className='h-12 w-72 rounded-xl bg-gray-700 font-poppins font-semibold text-[14px] text-white hover:scale-125 duration-300 mb-5'>
<span className='text-[18px]'></span>&nbsp;&nbsp;Przejdź do poprzedniego kroku
</button>
</div>
);
}
export default StepThreeJoblisting;

View File

@ -0,0 +1,178 @@
import React, { useState } from 'react'
import styles from '../style'
import { CKEditor } from '@ckeditor/ckeditor5-react';
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
import ImageUpload from './ImageUpload';
import SkillsSelector from './SkillsSelector';
import Salary from './Salary';
const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setFormData, removeFields }) => {
const [editorData, setEditorData] = useState('');
const handleEditorChange = (event, editor) => {
const data = editor.getData();
setEditorData(data);
}
const handleNextStep = () => {
if (validateForm()) {
console.log(errors)
nextStep(); // Przejście do następnego kroku
} else {
alert('Proszę wypełnić wszystkie wymagane pola.'); // Wyświetl komunikat o błędzie
}
};
const errorStyleInput = 'border-red-600'
const [errors, setErrors] = useState({});
const validateForm = () => {
let newErrors = {};
// Sprawdź każde wymagane pole
if (!formData.jobtitle) {
newErrors.jobtitle = 'To pole jest wymagane';
}
if (!formData.company_name) {
newErrors.company_name = 'To pole jest wymagane';
}
if (!formData.localization) {
newErrors.localization = 'To pole jest wymagane';
}
if (!formData.content) {
newErrors.content = 'To pole jest wymagane';
}
console.log(formData.requireSalary)
if (formData.requireSalary === true)
{
console.log(formData.requireSalary)
if (!formData.minSalary) {
newErrors.minSalary = 'To pole jest wymagane';
}
if (!formData.maxSalary) {
newErrors.maxSalary = 'To pole jest wymagane';
}
}
if (!formData.employmentType) {
newErrors.employmentType = 'To pole jest wymagane';
}
if (!formData.employmentType) {
newErrors.employmentType = 'To pole jest wymagane';
}
if (!formData.image) {
newErrors.image = 'To pole jest wymagane';
}
if (Object.keys(formData.skillLevels).length === 0) {
newErrors.skillLevels = 'To pole jest wymagane';
}
setErrors(newErrors);
// Zwróć true, jeśli nie ma błędów
return Object.keys(newErrors).length === 0;
};
return (
<>
<div className='grid grid-cols-3'>
<div className='sm:px-20 px-16 sm:pt-3 pt-6 pb-6 xl:col-span-3 col-span-3'>
<span className='justify-center'>
<p className={`${styles.heading1} text-center`}>Dodajesz ogłoszenie z izaac.pl</p>
</span>
</div>
</div>
<div className='no-tailwindcss-base'>
<div className='mx-auto max-w-none h-[70vh] w-[60vw]'>
<form action="">
<div className='grid mb-4'>
<div className='grid grid-cols-3 items-center'>
<div className={`${styles.paragraph} px-4 py-2`}>Nazwa ogłoszenia<span className={`${styles.paragraph} text-red-700`}>*</span></div>
<input type="text"
name="jobtitle"
value={formData['jobtitle'] || ''}
required
id="jobtitle"
onChange={handleChange('jobtitle')}
placeholder='Wpisz nazwę stanowiska...'
className={`border-b-2 px-4 my-2 ${styles.paragraph} col-span-2 ${errors.jobtitle ? errorStyleInput : ''}`}/>
<div className={`${styles.paragraph} px-4 py-2 `}>Strona internetowa</div>
<input type="text"
name="webpage"
value={formData['webpage'] || ''}
onChange={handleChange('webpage')}
id="webpage"
placeholder='Wpisz adres strony internetowej...'
className={`border-b-2 px-4 my-2 ${styles.paragraph} col-span-2`}/>
<div className={`${styles.paragraph} px-4 py-2 `}>Adres siedziby<span className={`${styles.paragraph} text-red-700`}>*</span></div>
<input type="text"
name="localization"
required
value={formData['localization'] || ''}
onChange={handleChange('localization')}
id="localization"
placeholder='Wpisz adres siedziby...'
className={`border-b-2 px-4 my-2 ${styles.paragraph} col-span-2`}/>
<div className={`${styles.paragraph} px-4 py-2 `}>Logo firmy<span className={`${styles.paragraph} text-red-700`}>*</span></div>
<div className='mt-6 col-span-2'>
<ImageUpload
setFormData={setFormData}
/>
</div>
</div>
</div>
<div className='w-full border-b-2'></div>
<div className={`${styles.paragraph} px-4 py-2`}>Treść ogłoszenia <span className={`${styles.paragraph} text-red-700`}>*</span></div>
<CKEditor
editor={ClassicEditor}
data={formData['content'] || ''}
required
onChange={(event, editor) => {
const data = editor.getData();
handleChange('content')(data); // Wywołanie zmodyfikowanej funkcji handleChange
}}
onReady={ editor => {
// You can store the "editor" and use when it is needed.
// console.log( 'Editor is ready to use!', editor );
} }
onBlur={ ( event, editor ) => {
// console.log( 'Blur.', editor );
} }
onFocus={ ( event, editor ) => {
// console.log( 'Focus.', editor );
} }
/>
<div className='grid grid-cols-3 items-center mt-4'>
<span className={`${styles.paragraph} col-span-3 px-4`}>Dodaj wybrane umiejętności <span className={`${styles.paragraph} text-red-700`}>*</span></span>
<div className='col-span-3'>
<SkillsSelector
formData={formData}
setFormData={setFormData}
/>
</div>
</div>
<div className='w-full border-b-2'></div>
<div className={`${styles.paragraph} px-4 py-2`}>Wynagrodzenie <span className={`${styles.paragraph} text-red-700`}>*</span></div>
<div className=''>
<Salary
removeFields={removeFields}
setFormData={setFormData}
handleChange={handleChange}
formData={formData}/>
</div>
<div className={`${styles.flexCenter} p-20 gap-20`}>
<button
type="button"
onClick={prevStep}
className='h-12 w-72 rounded-xl bg-gray-700 font-poppins font-semibold text-[14px] scale-100 text-white hover:scale-125 duration-300'><span className='text-[18px]'></span>&nbsp;&nbsp;Przejdź do poprzedniego kroku</button>
<button
type="button"
onClick={handleNextStep}
className='h-12 w-72 rounded-xl bg-gray-700 font-poppins font-semibold text-[14px] scale-100 text-white hover:scale-125 duration-300'>Przejdź do następnego kroku &nbsp;&nbsp;<span className='text-[18px]'></span> </button>
</div>
</form>
</div>
</div>
</>
)
}
export default StepTwoJoblisting

View File

@ -0,0 +1,16 @@
import React from 'react'
const TextDivider = ({text }) => {
return (
<div className='flex items-center justify-center mt-4 mb-6'>
<div className='h-0.5 bg-black w-[14rem] opacity-30'></div>
<p className='text-[24px] mx-8 font-semibold'>
{text}
</p>
<div className='h-0.5 bg-black w-[14rem] opacity-30'></div>
</div>
)
}
export default TextDivider

View File

@ -209,7 +209,7 @@ export const linki = [
}, },
{ {
"id": "2", "id": "2",
"name": "work/jobpostings", "name": "work/postings",
"title": "Ogłoszenia", "title": "Ogłoszenia",
}, },
{ {

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
src/icon/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

BIN
src/icon/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
src/icon/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

View File

@ -25,6 +25,7 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
:root { :root {
--black-gradient: linear-gradient( --black-gradient: linear-gradient(
144.39deg, 144.39deg,
@ -38,20 +39,35 @@
scroll-behavior: smooth; scroll-behavior: smooth;
} }
.editor-container { .editor-container {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
padding-left: 4rem; padding-left: 12rem;
padding-right: 4rem; padding-right: 12rem;
; ;
} }
.div-transition {
transition: all 0.3s ease;
scale: 100%;
}
.ck-editor__editable { .ck-editor__editable {
min-height: 30rem; min-height: 12rem;
max-height: 12rem;
} }
.ck.ck-editor {
min-height: 140rem; .job-listing {
-webkit-mask-image: linear-gradient(to top, black 0%, transparent 100%);
mask-image: linear-gradient(to top, black 0%, transparent 100%);
}
.job-listing2 {
-webkit-mask-image: linear-gradient(to bottom, black 0%, transparent 100%);
mask-image: linear-gradient(to bottom, black 0%, transparent 100%);
}
.lista {
list-style: inside;
} }
.sidebar-show { .sidebar-show {

View File

@ -1,6 +1,7 @@
const styles = { const styles = {
boxWidth: "xl:max-w-[1280px] w-full", boxWidth: "xl:max-w-[1280px] w-full",
boxWidth2: "w-full", boxWidth2: "w-full",
heading1: "font-poppins font-semibold xs:text-[40px] text-[40px] text-black xs:leading-[76.8px] leading-[66.8px] w-full",
heading2: "font-poppins font-semibold xs:text-[48px] text-[40px] text-black xs:leading-[76.8px] leading-[66.8px] w-full", heading2: "font-poppins font-semibold xs:text-[48px] text-[40px] text-black xs:leading-[76.8px] leading-[66.8px] w-full",
paragraph: "font-poppins font-normal text-black text-[18px] leading-[30.8px]", paragraph: "font-poppins font-normal text-black text-[18px] leading-[30.8px]",