komponenty
@ -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
@ -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",
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
22
src/App.jsx
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
23
src/assets/placeholderImage.svg
Normal 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 |
87
src/components/AddJobListing.jsx
Normal 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;
|
||||||
62
src/components/ImageUpload.jsx
Normal 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;
|
||||||
@ -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
@ -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
|
||||||
66
src/components/SelectedSkill.jsx
Normal 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">
|
||||||
|
×
|
||||||
|
</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;
|
||||||
143
src/components/SkillsSelector.jsx
Normal 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;
|
||||||
120
src/components/StepFourJoblisting.jsx
Normal 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> 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
|
||||||
|
<span className="text-[18px]">→</span>{" "}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StepFourJoblisting;
|
||||||
68
src/components/StepOneJoblisting.jsx
Normal 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 <span className='text-[18px]'>→</span> </button>
|
||||||
|
<div className='h-12 w-full'></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StepOneJoblisting
|
||||||
88
src/components/StepThreeJoblisting.jsx
Normal 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> Przejdź do poprzedniego kroku
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StepThreeJoblisting;
|
||||||
178
src/components/StepTwoJoblisting.jsx
Normal 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> 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 <span className='text-[18px]'>→</span> </button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StepTwoJoblisting
|
||||||
16
src/components/TextDivider.jsx
Normal 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
|
||||||
@ -209,7 +209,7 @@ export const linki = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2",
|
"id": "2",
|
||||||
"name": "work/jobpostings",
|
"name": "work/postings",
|
||||||
"title": "Ogłoszenia",
|
"title": "Ogłoszenia",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
BIN
src/icon/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
src/icon/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
src/icon/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src/icon/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 502 B |
BIN
src/icon/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/icon/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
1
src/icon/site.webmanifest
Normal 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"}
|
||||||
@ -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 {
|
||||||
|
|||||||
@ -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]",
|
||||||
|
|||||||