Compare commits

..

10 Commits

Author SHA1 Message Date
Jakub Kaniecki
dbf7367c53 IZCWRK-2 Poprawa zgodnie z komentarzem od Mariusza 2024-10-08 21:34:27 +02:00
Jakub Kaniecki
c0125af058 Merge branch 'master' into develop 2024-08-31 23:35:29 +02:00
Jakub Kaniecki
45a2c8252e zmiany do mobile, filtrowanie, poprawki
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-27 01:42:21 +02:00
Jakub Kaniecki
97b7e8726d filtry + ciastko
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-09 22:37:54 +02:00
Jakub Kaniecki
256bd8a0ee dodanie nowych komponentow oraz dodanie walidacji propTypes
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-08 20:52:43 +02:00
Jakub Kaniecki
10103fb1a1 fix merge conflicts and merge develop to master
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-08 19:59:36 +02:00
Jakub Kaniecki
7020cdeb4e jakies tam poprawki
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-07 20:33:48 +02:00
Jakub Kaniecki
ee75b0f2eb cicd
All checks were successful
continuous-integration/drone/push Build is passing
2024-06-07 19:41:53 +02:00
Jakub K
b3adb4f8fb zmiana adresow
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2024-03-27 19:13:32 +01:00
Jakub K
2fddcf9ddd zmiana adresow
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-27 19:10:37 +01:00
39 changed files with 13592 additions and 285 deletions

View File

@ -5,9 +5,9 @@ name: default
steps: steps:
- name: build - name: build
commands: commands:
- docker build --no-cache -t izaac-frontend-develop:latest . - docker build --no-cache -t izaac-frontend-master:latest .
- docker tag izaac-frontend:latest registry.izaac.pl:5000/izaac-frontend-develop:latest - docker tag izaac-frontend-master:latest registry.izaac.pl:5000/izaac-frontend-master:latest
- docker push registry.izaac.pl:5000/izaac-frontend-develop:latest - docker push registry.izaac.pl:5000/izaac-frontend-master:latest
- name: delete - name: delete
environment: environment:

View File

@ -31,7 +31,7 @@ spec:
spec: spec:
containers: containers:
- name: izaac-frontend - name: izaac-frontend
image: registry.knck.pl:5000/izaac-frontend-develop:latest image: registry.knck.pl:5000/izaac-frontend-master:latest
ports: ports:
- containerPort: 80 - containerPort: 80
volumeMounts: volumeMounts:

View File

@ -7,7 +7,7 @@
<link rel="icon" href="../icon/favicon.ico"/> <link rel="icon" href="../icon/favicon.ico"/>
<!-- todo ICONs --> <!-- 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 - praca dla inżynierów</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

12489
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,13 +6,14 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", "lint": "eslint -c eslint.config.js --ext .js,.jsx,.ts,.tsx .",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@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",
"@ckeditor/ckeditor5-word-count": "^43.2.0",
"axios": "^0.24.0", "axios": "^0.24.0",
"dompurify": "^3.0.6", "dompurify": "^3.0.6",
"react": "^18.2.0", "react": "^18.2.0",

View File

@ -7,40 +7,33 @@ import { Route, Routes } from "react-router-dom";
import Contact from "./components/Contact"; import Contact from "./components/Contact";
import Mininav from "./components/Mininav"; import Mininav from "./components/Mininav";
import AddJobListing from "./components/AddJobListing"; import AddJobListing from "./components/AddJobListing";
import { useState, useEffect } from "react"; import Cookies from "./components/Cookies";
import axios from "axios";
import EmployerPanel from "./components/EmployerPanel";
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 ${loc}`}> <div className={`bg-white w-full`}>
<div className={`${styles.paddingX} ${styles.flexCenter} border-b-2`}> <div className={`${styles.paddingX} ${styles.flexCenter}`}>
<Mininav /> {/* <Mininav /> */}
<div className={`${styles.boxWidth}`}> <div className={`${styles.boxWidth} `}>
<NavBar /> <NavBar />
</div> </div>
</div> </div>
<div className={`${styles.flexStart}`}> <div className={`${styles.flexStart} mt-18 sm:mt-[4.5rem] md:mt-[6.5rem]`}>
<div className={`${styles.boxWidth2} `}> <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/postings" element={<WorkApp />} /> <Route path="/work/joboffers" element={<WorkApp />} />
<Route path="/contact" element={<Contact />} /> <Route path="/contact" element={<Contact />} />
<Route path="/work/jobposting" element={<AddJobListing />} /> <Route path="/work/addjoboffer" element={<AddJobListing />} />
<Route path="/employerpanel" element={<EmployerPanel />} /> <Route path="/work/joboffers/:id" element={<WorkApp/>} />
{/* <Route path="/employerpanel" element={<EmployerPanel />} /> */}
{/* Add more routes as needed */} {/* Add more routes as needed */}
</Routes> </Routes>
<Cookies />
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import StepOneJoblisting from './StepOneJoblisting'; import StepOneJoblisting from './StepOneJoblisting';
import StepTwoJoblisting from './StepTwoJoblisting'; import StepTwoJoblisting from './StepTwoJoblisting';
import StepThreeJoblisting from './StepThreeJoblisting'; import StepThreeJoblisting from './StepThreeJoblisting';

17
src/components/Button.jsx Normal file
View File

@ -0,0 +1,17 @@
import React from 'react'
const Button = (props) => {
return (
<button
disabled={props.isDiasbled}
type="button"
onClick={props.nextStep}
className={`h-16 md:h-12 w-80 md:w-72 rounded-xl bg-gray-700 font-poppins font-semibold text-[14px] text-white hover:scale-125 duration-300 ${props.isDiasbled ? 'cursor-not-allowed hover:scale-100 duration-300' : ''}`}
>
{props.Text} &nbsp;&nbsp;
<span className="text-[18px]"></span>{" "}
</button>
)
}
export default Button

View File

@ -1,4 +1,4 @@
import React from 'react' import propTypes from 'prop-types'
const Category = (props) => { const Category = (props) => {
return ( return (
@ -15,4 +15,11 @@ const Category = (props) => {
) )
} }
Category.propTypes = {
name: propTypes.string,
func: propTypes.func,
small: propTypes.bool,
last: propTypes.bool
}
export default Category export default Category

View File

@ -0,0 +1,106 @@
import { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { categories } from '../consts';
import { search } from '../assets';
const CategorySelect = props => {
const [inputValue, setInputValue] = useState('');
const [suggestions, setSuggestions] = useState([]);
const [selectedCategoryIds, setSelectedCategoryIds] = useState([]);
useEffect(() => {
console.log(suggestions);
console.log(selectedCategoryIds);
}, [suggestions, selectedCategoryIds]);
useEffect(() => {
if (props.searchQuery && props.searchQuery.categories) {
setSelectedCategoryIds(props.searchQuery.categories);
}
}, [props.searchQuery]);
// useEffect(() =>{
// setSelectedCategoryIds([])
// }, [props.clearSearchQuery])
const handleInputChange = (e) => {
const value = e.target.value;
setInputValue(value);
if (value.length > 0) {
setSuggestions(categories.filter(category =>
category.name.toLowerCase().includes(value.toLowerCase()) &&
!selectedCategoryIds.includes(category.id)
));
} else {
setSuggestions([]);
}
}
const handleSuggestionClick = (suggestion) => {
if (!selectedCategoryIds.includes(suggestion.id)) {
const newSelectedIds = [...selectedCategoryIds, suggestion.id];
setSelectedCategoryIds(newSelectedIds);
props.setSearchQuery(prevState => ({...prevState, categories: newSelectedIds }));
}
setInputValue('');
setSuggestions([]);
}
const handleRemoveCategory = (id) => {
const newSelectedIds = selectedCategoryIds.filter(prevId => prevId !== id);
setSelectedCategoryIds(newSelectedIds);
props.setSearchQuery({ categories: newSelectedIds });
}
return (
<div className='mb-3'>
<p className='text-center'>Branża</p>
<div className='flex flex-wrap gap-y-2 mt-1 '>
{selectedCategoryIds.map(id => {
const category = categories.find(cat => cat.id === id);
return (
<div
key={id}
onClick={() => handleRemoveCategory(id)}
className='min-w-fit w-8 py-1 px-2 mr-3 border-2 hover:bg-gray-300 rounded-xl hover:cursor-pointer '
>
<p className='font-poppins text-sm'>{category.name}
<span className='relative -top-[2px] left-1'>&times;</span>
</p>
</div>
);
})}
</div>
<input
type="text"
className='border-2 rounded-lg px-3 py-1 w-full mt-2'
value={inputValue}
onChange={handleInputChange}
placeholder='Wpisz branżę...'
/>
{inputValue.length >= 1 && suggestions.length > 0 && (
<div className='absolute h-40 w-[90%] overflow-y-auto'>
{suggestions.map(suggestion => (
<div
className='mx-3 p-3 border-2 border-gray-300 bg-gray-100 hover:bg-gray-200 cursor-pointer'
key={suggestion.id}
onClick={() => handleSuggestionClick(suggestion)}
>
{suggestion.name}
</div>
))}
</div>
)}
</div>
)
}
CategorySelect.propTypes = {
setSearchQuery: PropTypes.func.isRequired,
searchQuery: PropTypes.shape({
categories: PropTypes.arrayOf(PropTypes.string)
})
}
export default CategorySelect

View File

@ -0,0 +1,29 @@
import propTypes from 'prop-types'
import { useState } from 'react'
const Cookies = props => {
const handleCookies = () => {
localStorage.setItem('cookies', true)
}
const cookies = localStorage.getItem('cookies')
// if(cookies) return null
const [show, setShow] = useState(true)
if (cookies) return null;
if (!show) return null;
return (
<>
<div className='bg-neutral-900 opacity-[99%] h-24 sm:h-16 w-full fixed bottom-0 z-40 flex justify-center items-center'>
<p className='mx-5 text-justify text-white font-'>Korzystamy z plików cookie, aby zapewnić prawidłowe działanie witryny. </p>
<button className= 'bg-white px-4 py-2 rounded-md ml-4 mr-4 ring-2 ring-dimWhite hover:ring-4 duration-300 font-semibold'
onClick={() => {handleCookies; setShow(!show)}}>
Wyrażenie zgody</button>
</div>
</>
)
}
Cookies.propTypes = {
}
export default Cookies

View File

@ -1,4 +1,4 @@
import React from "react";
import { ogloszenia } from "../consts"; import { ogloszenia } from "../consts";
import ListingSmall from "./ListingSmall"; import ListingSmall from "./ListingSmall";
import { useState } from "react"; import { useState } from "react";

View File

@ -1,30 +1,185 @@
import React from 'react' import { useCallback, useEffect, useMemo } from 'react';
import Search from './Search' import PropTypes from 'prop-types';
import Search from './Search';
import Selector from './Selector';
import RangeSlider from './RangeSlider';
import CategorySelect from './CategorySelect';
import { categories, work_from_home, employment_types } from '../consts';
const Filter = ({ isOpen, searchQuery, setSearchQuery, clearSearchQuery, onClick }) => {
const [min_salary, max_salary] = [0, 20000];
useEffect(() => {
console.log(searchQuery);
}, [searchQuery]);
const Filter = (props) => { const handleRangeChange = useCallback((newMin, newMax) => {
if (!props.isOpen) return null; setSearchQuery(prevState => ({
...prevState,
min_salary: newMin,
max_salary: newMax
}));
}, [setSearchQuery]);
const handleChange = useCallback((e, name) => {
const value = e.target.value;
const numValue = parseInt(value, 10);
setSearchQuery(prevState => {
switch (name) {
case 'name':
return { ...prevState, name: value };
case 'min_salary':
if (value === '' || isNaN(numValue)) {
return { ...prevState, min_salary: '' };
}
return { ...prevState, min_salary: numValue };
case 'max_salary':
if (value === '' || isNaN(numValue)) {
return { ...prevState, max_salary: '' };
}
return { ...prevState, max_salary: numValue };
default:
return prevState;
}
});
}, [setSearchQuery]);
const handleCheckedChange = useCallback((e) => {
const { name, value, checked } = e.target;
setSearchQuery(prevState => {
// Sprawdź, czy dla danej nazwy już istnieje tablica w stanie
if (!prevState[name]) {
prevState[name] = [];
}
if (checked) {
// Jeśli checkbox jest zaznaczony, dodaj wartość do odpowiedniej tablicy
return {
...prevState,
[name]: [...prevState[name], value]
};
} else {
// Jeśli checkbox jest odznaczony, usuń wartość z odpowiedniej tablicy
return {
...prevState,
[name]: prevState[name].filter(item => item !== value)
};
}
});
}, [setSearchQuery]);
const filterButtonClass = useMemo(() => (
'bg-gray-600 text-white py-1 sm:py-3 px-12 mx-auto sm:mx-40 col-span-2 rounded-md font-semibold ' +
'text-xl hover:bg-gray-500 duration-300 hover:scale-110 my-3 sm:my-5 sm:min-h-full'
), []);
return ( return (
<div className={`sticky top-24 z-20 bg-gray-100 flex flex-wrap place-items-center items-center slide-container py-2 ${!props.isOpen ? '' : 'expanded'}`}> <div className={`z-10 flex flex-col bg-dimWhite collapsible px-3 sm:px-4 ${isOpen ? 'expanded border-4' : ''}`}>
<Search label='Wyszukaj ogłoszenie' <div className="grid grid-cols-2 md:grid-cols-3 mb-2">
placeholder='Wpisz nazwę stanowiska...' /> <div className="hidden md:block" />
<Search label='Lokalizacja' <p className="mx-auto mt-3 place-self-center text-center font-poppins font-semibold text-xl text-gray-600">Filtry</p>
placeholder='Wpisz lokalizację...' /> <button
<Search label='Min. wynagrodzenie' className="rounded-xl mt-4 w-32 py-1 bg-slate-400 hover:bg-slate-600 duration-100"
placeholder='Wpisz kwotę minimalną...' /> onClick={clearSearchQuery}
<Search label='Max. wynagrodzenie' >
placeholder='Wpisz kwotę maksymalną...' /> <span className="p-2 text-sm font-bold text-center font-poppins text-white">Wyczyść filtry</span>
</button>
</div>
<Search
label="Wyszukaj ogłoszenie"
placeholder="Wpisz nazwę stanowiska..."
name="name"
type="text"
value={searchQuery.name || ''}
onChange={(e) => handleChange(e, 'name')}
/>
<button className='bg-gray-600 text-white py-3 px-12 col-span-2 <div className="grid grid-cols-2 gap-x-4">
mx-auto rounded-md font-semibold text-xl hover:bg-gray-500 duration-150 <Search
mt-3 label="Min. wynagrodzenie"
' label2="wynagrodzenie"
onClick={props.onClicked} placeholder="Wpisz kwotę minimalną..."
> name="min_salary"
Filtruj type="text"
</button> value={searchQuery.min_salary || ''}
onChange={(e) => handleChange(e, 'min_salary')}
/>
<Search
label="Max. wynagrodzenie"
placeholder="Wpisz kwotę maksymalną..."
name="max_salary"
type="text"
value={searchQuery.max_salary || ''}
onChange={(e) => handleChange(e, 'max_salary')}
/>
<RangeSlider
min={min_salary}
max={max_salary}
minVal={searchQuery.min_salary || min_salary}
maxVal={searchQuery.max_salary || max_salary}
setSearchQuery={setSearchQuery}
onRangeChange={handleRangeChange}
isOpen={isOpen}
/>
</div>
<CategorySelect
setSearchQuery = {setSearchQuery}
searchQuery = {searchQuery}
clearSearchQuery = {clearSearchQuery}
/>
{/* <Selector
value_to_map_from={categories}
name="Kategorie"
inputname="categories"
onChange={handleCheckedChange}
state={searchQuery || {}}
/> */}
{/* <Selector
value_to_map_from={work_from_home}
name="Praca zdalna"
inputname="employment_types"
onChange={handleCheckedChange}
state={searchQuery || {}}
/>
<Selector
value_to_map_from={employment_types}
name="Typ kontraktu"
inputname="employment_types"
onChange={handleCheckedChange}
state={searchQuery || {}}
/> */}
<button className={filterButtonClass} onClick={onClick}>
Filtruj
</button>
</div> </div>
) );
} };
export default Filter Filter.propTypes = {
isOpen: PropTypes.bool.isRequired,
searchQuery: PropTypes.shape({
name: PropTypes.string,
min_salary: PropTypes.number,
max_salary: PropTypes.number,
categories: PropTypes.arrayOf(PropTypes.string),
work_from_home: PropTypes.arrayOf(PropTypes.string),
employment_types: PropTypes.arrayOf(PropTypes.string)
}),
setSearchQuery: PropTypes.func.isRequired,
clearSearchQuery: PropTypes.func.isRequired,
onClick: PropTypes.func.isRequired
};
Filter.defaultProps = {
searchQuery: {
categories: [],
work_from_home: [],
employment_types: []
}
};
export default Filter;

View File

@ -1,4 +1,4 @@
import React from 'react'
import styles from '../style' import styles from '../style'
const Home = () => { const Home = () => {
return ( return (

View File

@ -1,7 +1,7 @@
import React, { useState, useRef, useEffect } from 'react'; import { useState, useRef, useEffect } from 'react';
import { placeholderImage } from '../assets'; import { placeholderImage } from '../assets';
import axios from 'axios'; import axios from 'axios';
import propTypes from 'prop-types';
const ImageUpload = ({setFormData, data}) => { const ImageUpload = ({setFormData, data}) => {
const [imageSrc, setImageSrc] = useState(placeholderImage); const [imageSrc, setImageSrc] = useState(placeholderImage);
@ -83,4 +83,9 @@ const ImageUpload = ({setFormData, data}) => {
); );
}; };
ImageUpload.propTypes = {
setFormData: propTypes.func,
data: propTypes.object
};
export default ImageUpload; export default ImageUpload;

View File

@ -1,14 +1,15 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import axios from 'axios'; import axios from 'axios';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import SkillRender from './SkillRender'; // Ensure this import is correctly pointing to your SkillRender component import SkillRender from './SkillRender';
// eslint-disable-next-line react/prop-types
const JobOfferContent = ({ id, skills }) => { const JobOfferContent = ({ id, skills }) => {
const [jobOffer, setJobOffer] = useState(null); const [jobOffer, setJobOffer] = useState(null);
const fetchJobOfferById = async (jobId) => { const fetchJobOfferById = async (jobId) => {
try { try {
const response = await axios.get(`https://izaac.knck.pl/api/jobposting/joboffers/${jobId}`); const response = await axios.get(`https://izaac.knck.pl/api/jobposting/joboffers/${jobId}/?format=json`);
setJobOffer(response.data); setJobOffer(response.data);
} catch (error) { } catch (error) {
console.error('Failed to fetch job offer:', error); console.error('Failed to fetch job offer:', error);

View File

@ -1,11 +1,12 @@
import React from 'react'
import { IzaacLOGO } from '../assets'
const ListingSmall = ({ id, name, company_name, min_salary, max_salary, require_salary, index, onClick, _class, image }) => { import { IzaacLOGO } from '../assets'
import propTypes from 'prop-types'
const ListingSmall = ({ id, name, company_name, min_salary, max_salary, require_salary, index, onClick, _class, image, selected, blur }) => {
return ( return (
<div <div
key={id} key={id}
className={`flex flex-row items-center gap-4 drop-shadow cursor-pointer mb-2 sm:mr-4 mr-0 text-xl font-bold border rounded-[10px] p-2 sm: px-4 hover:border-l-8 hover:border-zinc-500 duration-300 ${index % 2 === 0 ? 'bg-gray-200' : 'bg-gray-300'} ${_class}`} className={`flex flex-row ml-1 w-[98vw] sm:w-full items-center gap-4 drop-shadow cursor-pointer mb-2 sm:mr-4 mr-4 text-xl font-bold border rounded-[10px] p-2 sm:px-4 hover:border-l-8 hover:border-zinc-500 duration-300 blur-transition ${index % 2 === 0 ? 'bg-gray-200' : 'bg-gray-300'} ${_class} ${selected ? 'border-l-8 border-zinc-500 bg-gray-400' : 'border-l-8 border-transparent'} `}
onClick={onClick} onClick={onClick}
> >
<div className='flex-shrink-0 w-[6rem] h-[6rem] bg-white rounded-lg flex items-center justify-center'> <div className='flex-shrink-0 w-[6rem] h-[6rem] bg-white rounded-lg flex items-center justify-center'>
@ -14,7 +15,7 @@ const ListingSmall = ({ id, name, company_name, min_salary, max_salary, require_
</div> </div>
<div className='flex-grow flex flex-col justify-between'> <div className='flex-grow flex flex-col justify-between'>
<p className='text-base font-bold text-left tracking-wide'>{name}</p> <p className='text-base font-bold text-left tracking-wide'>{name}</p>
<p className='text-sm text-left text-gray-600'>{company_name}</p> <p className='text-xs text-left text-gray-600'>{company_name}</p>
{/* Wyświetlenie wynagrodzenia pod nazwą, jeśli jest wymagane */} {/* Wyświetlenie wynagrodzenia pod nazwą, jeśli jest wymagane */}
{require_salary && ( {require_salary && (
<p className='text-xs font-semibold text-left tracking-widest text-slate-800 mt-2'>{min_salary} - {max_salary} PLN</p> <p className='text-xs font-semibold text-left tracking-widest text-slate-800 mt-2'>{min_salary} - {max_salary} PLN</p>
@ -24,4 +25,19 @@ const ListingSmall = ({ id, name, company_name, min_salary, max_salary, require_
); );
}; };
export default ListingSmall; export default ListingSmall;
ListingSmall.propTypes = {
id: propTypes.number,
name: propTypes.string,
company_name: propTypes.string,
min_salary: propTypes.number,
max_salary: propTypes.number,
require_salary: propTypes.bool,
index: propTypes.number,
onClick: propTypes.func,
_class: propTypes.string,
image: propTypes.string,
selected: propTypes.bool,
blur: propTypes.string
}

View File

@ -1,4 +1,4 @@
import React from 'react' import propTypes from 'prop-types';
import styles from '../style'; import styles from '../style';
const Login = ({isOpen, onClose}) => { const Login = ({isOpen, onClose}) => {
@ -31,4 +31,9 @@ const Login = ({isOpen, onClose}) => {
) )
} }
Login.propTypes = {
isOpen: propTypes.bool,
onClose: propTypes.func
}
export default Login export default Login

View File

@ -1,8 +1,7 @@
import React, { useState } from 'react' import { useState } from 'react'
import Register from './Register'; import Register from './Register';
import Login from './Login'; import Login from './Login';
import { BrowserRouter as Router } from "react-router-dom";
import { Route, Routes } from "react-router-dom";
const Mininav = () => { const Mininav = () => {
const [isPopupOpen, setPopupOpen] = useState(false) const [isPopupOpen, setPopupOpen] = useState(false)
@ -17,7 +16,7 @@ const Mininav = () => {
<> <>
<div className='bg-gray-500 h-5 absolute w-full top-0 flex justify-end items-center z-50'> <div className='bg-gray-500 h-5 absolute w-full top-0 flex justify-end items-center z-50'>
{/* Link otwierający modal */} {/* Link otwierający modal */}
<a href="/employerpanel" className='text-white font-poppins font-semibold text-xs'>Panel pracodawcy</a> {/* <a href="/employerpanel" className='text-white font-poppins font-semibold text-xs'>Panel pracodawcy</a> */}
<button onClick={openPopup} className='text-white font-poppins font-semibold text-xs mx-12'> <button onClick={openPopup} className='text-white font-poppins font-semibold text-xs mx-12'>
Rejestracja Rejestracja
</button> </button>

View File

@ -1,10 +1,10 @@
import React from 'react'
import { linki, linki_home } from '../consts'; import { linki, linki_home } from '../consts';
import { close, search, menu, IzaacLOGO} from '../assets'; 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'; 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,10 +20,10 @@ const NavBar = () => {
const currentLinks = getLinks() const currentLinks = getLinks()
return ( return (
<nav className='w-full flex pt-7 justify-between items-center navbar '> <nav className='fixed top-0 left-0 z-30 w-full flex pt-4 justify-between items-center navbar bg-white px-8 border-b-2 xl:px-64'>
<a href='/' className='font-popins font-semibold text-[24px] text-slate-900'>
<img src={IzaacLOGO} alt="izaac" className='md:w-[200px] w-[120px]'/> <img src={IzaacLOGO} alt="izaac" className='md:w-[200px] w-[120px]'/>
</a>
<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'>
{currentLinks.map((nav, index) => ( {currentLinks.map((nav, index) => (
<li <li
@ -76,5 +76,4 @@ const NavBar = () => {
) )
} }
export default NavBar export default NavBar

View File

@ -0,0 +1,76 @@
import { useRef, useEffect, useCallback } from 'react';
import propTypes from 'prop-types';
const RangeSlider = ({ min, max, minVal, maxVal, setSearchQuery, onRangeChange, isOpen }) => {
const minValRef = useRef(minVal);
const maxValRef = useRef(maxVal);
const range = useRef(null);
const getPercent = useCallback(
(value) => Math.round(((value - min) / (max - min)) * 100),
[min, max]
);
useEffect(() => {
const minPercent = getPercent(minVal === '' ? min : minVal);
const maxPercent = getPercent(maxVal === '' ? max : maxVal);
if (range.current) {
range.current.style.left = `${minPercent}%`;
range.current.style.width = `${maxPercent - minPercent}%`;
}
}, [minVal, maxVal, getPercent, min, max]);
const handleChange = useCallback((event) => {
const { id, value } = event.target;
const numValue = Number(value);
let newMinVal = minVal === '' ? min : minVal;
let newMaxVal = maxVal === '' ? max : maxVal;
if (id === 'range1') {
newMinVal = numValue;
minValRef.current = newMinVal;
} else if (id === 'range2') {
newMaxVal = numValue;
maxValRef.current = newMaxVal;
}
onRangeChange(newMinVal, newMaxVal);
}, [minVal, maxVal, min, max, onRangeChange]);
return (
<div className='col-span-2 my-5 px-8 slider-wrapper'>
<input
id="range1"
type="range"
min={min}
step={100}
max={max}
value={minVal === '' ? min : minVal}
onChange={handleChange}
className={`thumb w-[85%] sm:w-[84%] thumb--left thumb--animatedin thumb--animatedout`}
style={isOpen ? { zIndex: (minVal === '' ? min : minVal) > (maxVal === '' ? max - 100 : maxVal - 100) ? 5 : undefined, } : { display: 'none' }}
/>
<input
id="range2"
type="range"
min={min}
max={max}
step={100}
value={maxVal === '' ? max : maxVal}
onChange={handleChange}
className={`thumb w-[85%] sm:w-[84%] thumb--right thumb--animatedin thumb--animatedout sm`}
style={isOpen ? {} : { display: 'none'}}
/>
<div className="slider" >
<div className="slider__track " />
<div ref={range} className="slider__range" />
</div>
</div>
);
};
export default RangeSlider;

View File

@ -1,5 +1,5 @@
import React from 'react'
import styles from '../style'; import styles from '../style';
import propTypes from 'prop-types';
const Register = ({isOpen, onClose}) => { const Register = ({isOpen, onClose}) => {
if (!isOpen) return null; if (!isOpen) return null;
@ -44,5 +44,8 @@ const Register = ({isOpen, onClose}) => {
</div> </div>
) )
} }
Register.propTypes = {
isOpen: propTypes.bool,
onClose: propTypes.func
}
export default Register export default Register

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import styles from '../style' import propTypes from 'prop-types'
import {employment_types, work_from_home } from '../consts' import {employment_types, work_from_home } from '../consts'
@ -82,7 +82,7 @@ const Salary = ({handleChange, formData, removeFields, setFormData}) => {
className={`h-12 border-2 rounded-xl px-5 mx-4 w-full ${!require_salary ? 'bg-slate-200' : ''} `}/></div> className={`h-12 border-2 rounded-xl px-5 mx-4 w-full ${!require_salary ? 'bg-slate-200' : ''} `}/></div>
<div> <div>
<p className='font-poppins font-semibold text-slate-700 text-center text-[14px] mt-1 mb-2'>Typ kontraktu</p> <p className='font-poppins font-semibold text-slate-700 text-center text-[14px] mt-1 mb-2'>Typ <br className='hidden sm:block md:hidden'/>kontraktu</p>
<select <select
value={formData['employment_type'] || 'default' } value={formData['employment_type'] || 'default' }
name='employmentType' name='employmentType'
@ -96,7 +96,7 @@ const Salary = ({handleChange, formData, removeFields, setFormData}) => {
</select> </select>
</div> </div>
<div> <div>
<p className='font-poppins font-semibold text-slate-700 text-center text-[14px] mt-1 mb-2'>Praca zdalna</p> <p className='font-poppins font-semibold text-slate-700 text-center text-[14px] mt-1 mb-2'>Praca <br className='block md:hidden'/> zdalna</p>
<select <select
value={formData['work_from_home'] || 'default' } value={formData['work_from_home'] || 'default' }
name='workFromHome' name='workFromHome'
@ -112,5 +112,18 @@ const Salary = ({handleChange, formData, removeFields, setFormData}) => {
</div> </div>
) )
} }
Salary.propTypes = {
handleChange: propTypes.func,
formData: propTypes.object,
removeFields: propTypes.func,
setFormData: propTypes.func,
min_salary: propTypes.string,
max_salary: propTypes.string,
require_salary: propTypes.bool,
employment_type: propTypes.string,
work_from_home: propTypes.string
}
export default Salary export default Salary

View File

@ -1,19 +1,62 @@
import React from 'react'
import { useEffect, useState } from 'react'
import propTypes from 'prop-types'
const Search = (props) => { const Search = (props) => {
return ( const [isHovered, setIsHovered] = useState(false)
<div className='grid py-2'> const check_errors = (value, name) => {
<label className="mx-2 text-l font- text-center font-poppins" if ((name === 'max_salary' || name === 'min_salary') && !(value === '' || value === undefined)) {
if (isNaN(parseInt(value))) {
return false;
} else {
return true;
}
} else {
return true;
}
};
useEffect(() => {
console.log(props.value)
}, [props.value])
return (
<div className='grid sm:py-1 '
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<label className="mx-2 text-l text-center ml-3 font-poppins no-spin-buttons"
htmlFor="search"> htmlFor="search">
{props.label} <p className='text-sm'>{props.label}</p>
<span className='text-sm text-red-700'>
{check_errors(props.value, props.name) ? '' : ' *'}
</span>
</label> </label>
<input type="text" <input
placeholder={props.placeholder} type={props._type}
className="border-2 border-gray-300 value={props.value || ''}
bg-white h-8 px-5 pr-16 mx-3 rounded-lg placeholder={props.placeholder}
text-sm focus:outline-none" /> name={props.name}
id={props.name}
onChange={props.onChange}
className={`border-2 ${ props.name === 'min_salary' || props.name === 'max_salary' ? 'w-[12rem] xs:w-[20rem] sm:w-full md:w-full lg:w-full' : '' }
bg-white h-8 px-5 rounded-lg
text-sm focus:outline-none ${check_errors(props.value, props.name) ? 'border-gray-300': 'border-red-700' }`}/>
{isHovered && <div className='text-red-700 text-sm relative'>
{check_errors(props.value, props.name) ? '' : 'Wartość musi być liczbą!'}
</div>}
</div> </div>
) )
} }
Search.propTypes = {
label: propTypes.string,
placeholder: propTypes.string,
name: propTypes.string,
setSearchQuery: propTypes.func,
value: propTypes.string,
_type: propTypes.string,
onChange: propTypes.func
}
export default Search export default Search

View File

@ -1,4 +1,4 @@
import React from 'react'; import propTypes from 'prop-types';
const levelMappings = { const levelMappings = {
'N': 'Nice to have', 'N': 'Nice to have',
@ -53,4 +53,12 @@ const SelectedSkill = ({ skill_name, skillId ,letter, onLevelChange, removeSkill
); );
}; };
SelectedSkill.propTypes = {
skill_name: propTypes.string,
skillId: propTypes.number,
letter: propTypes.string,
onLevelChange: propTypes.func,
removeSkillFromList: propTypes.func,
};
export default SelectedSkill; export default SelectedSkill;

View File

@ -0,0 +1,35 @@
import PropTypes from 'prop-types'
const Selector = (props) => {
return (
<>
<div className='grid grid-cols-2 sm:gap-[1px]'>
<span className='col-span-2 mx-auto text-l font- text-center font-poppins'>{props.name}</span>
{props.value_to_map_from.map((value_to_map_from) => (
<label key={value_to_map_from.id} className='flex items-center justify-start text-sm font-poppins font-semibold text-gray-600'>
<input
type='checkbox'
name={props.inputname}
value={value_to_map_from.id}
onChange={props.onChange}
className='mr-2'
checked={Array.isArray(props.state[props.inputname]) && props.state[props.inputname].includes(value_to_map_from.id)}
/>
{value_to_map_from.name}
</label>
))}
</div>
</>
)
}
Selector.propTypes = {
value_to_map_from: PropTypes.array,
setSearchQuery: PropTypes.func,
name: PropTypes.string,
inputname: PropTypes.string,
onChange: PropTypes.func,
state: PropTypes.object
}
export default Selector

View File

@ -1,4 +1,4 @@
import React from 'react'; import propTypes from 'prop-types';
import SkillRender from './SkillRender'; import SkillRender from './SkillRender';
@ -15,5 +15,9 @@ const SkillsList = ({skillData, skill_names}) => {
</div> </div>
); );
}; };
SkillsList.propTypes = {
skillData: propTypes.array,
skill_names: propTypes.array,
};
export default SkillsList; export default SkillsList;

View File

@ -1,5 +1,4 @@
import React from 'react' import propTypes from 'prop-types';
const levelMappings = { const levelMappings = {
'N': 'Nice to have', 'N': 'Nice to have',
@ -31,7 +30,7 @@ const SkillRender = ({key, skill, level,}) => {
return ( return (
<div className='w-64 h-42 rounded-[15px] bg-slate-200 py-4 m-2 hover:bg-slate-300 drop-shadow-sm' key={key}> <div className='h-42 rounded-[15px] bg-slate-200 py-4 m-2 hover:bg-slate-300 drop-shadow-sm' key={key}>
<p className='text-center font-bold mb-1 px-2'>{skill}</p> <p className='text-center font-bold mb-1 px-2'>{skill}</p>
<div className='h-0.5 mx-auto w-52 mt-2 justify-center bg-dimWhite opacity-60'></div> <div className='h-0.5 mx-auto w-52 mt-2 justify-center bg-dimWhite opacity-60'></div>
@ -42,4 +41,10 @@ const SkillRender = ({key, skill, level,}) => {
) )
}; };
SkillRender.propTypes = {
key: propTypes.number,
skill: propTypes.string,
level: propTypes.string,
};
export default SkillRender; export default SkillRender;

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import propTypes from 'prop-types';
import SelectedSkill from './SelectedSkill'; import SelectedSkill from './SelectedSkill';
import axios from 'axios';
const SkillsSelector = ({ formData, setFormData, skills }) => { const SkillsSelector = ({ formData, setFormData, skills }) => {
const [inputValue, setInputValue] = useState(''); const [inputValue, setInputValue] = useState('');
@ -101,4 +101,10 @@ const SkillsSelector = ({ formData, setFormData, skills }) => {
); );
}; };
SkillsSelector.propTypes = {
formData: propTypes.object.isRequired,
setFormData: propTypes.func.isRequired,
skills: propTypes.array.isRequired
};
export default SkillsSelector; export default SkillsSelector;

View File

@ -1,13 +1,36 @@
import React from "react"; import propTypes from "prop-types";
import { useEffect } from "react";
import styles from "../style"; import styles from "../style";
import TextDivider from "./TextDivider"; import TextDivider from "./TextDivider";
import Button from "./Button";
const StepFourJoblisting = ({ handleChange, formData, nextStep, prevStep }) => { const StepFourJoblisting = ({ handleChange, formData, nextStep, prevStep }) => {
const validateForm = () => {
const { first_name, last_name, contact_email, company_name, vat_number } = formData;
if (!first_name || !last_name || !contact_email || !company_name || !vat_number) {
return true;
};}
useEffect(() => {
validateForm();
}, [formData]);
const validateEmail = (e) => {
const email = e.target.value;
const emailPattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;
if (!emailPattern.test(email)) {
alert("Niepoprawny adres e-mail");
}
}
return ( return (
<div className={`${styles.flexCenter}`}> <div className={`${styles.flexCenter}`}>
<div className="grid grid-cols-5"> <div className="grid grid-cols-5">
<div className="col-span-3 col-start-2 mt-12"> <div className="col-span-3 col-start-2 mt-12">
<TextDivider text={`Podaj dane ogłoszeniodawcy`} /> <TextDivider text={`Podaj dane ogłoszeniodawcy`} />
<div className="grid grid-cols-3"> <div className="grid grid-cols-2">
<div className="col-span-1 mt-4 mx-2"> <div className="col-span-1 mt-4 mx-2">
<div className={`${styles.paragraph} px-4 py-2`}> <div className={`${styles.paragraph} px-4 py-2`}>
Imię Imię
@ -21,8 +44,9 @@ const StepFourJoblisting = ({ handleChange, formData, nextStep, prevStep }) => {
id="first_name" id="first_name"
onChange={handleChange("first_name")} onChange={handleChange("first_name")}
placeholder="Twoje imię..." placeholder="Twoje imię..."
className={`border-b-2 px-4 my-2 ${styles.paragraph}`} className={`px-4 mt-2 ${styles.paragraph}`}
/> />
<div className="bg-black h-0.5 opacity-10"></div>
</div> </div>
<div className="col-span-1 mt-4 mx-2"> <div className="col-span-1 mt-4 mx-2">
<div className={`${styles.paragraph} px-4 py-2`}> <div className={`${styles.paragraph} px-4 py-2`}>
@ -37,8 +61,9 @@ const StepFourJoblisting = ({ handleChange, formData, nextStep, prevStep }) => {
id="last_name" id="last_name"
onChange={handleChange("last_name")} onChange={handleChange("last_name")}
placeholder="Twoje nazwisko..." placeholder="Twoje nazwisko..."
className={`border-b-2 px-4 my-2 ${styles.paragraph}`} className={`px-4 mt-2 ${styles.paragraph}`}
/> />
<div className="bg-black h-0.5 opacity-10"></div>
</div> </div>
<div className="col-span-1 mt-4 mx-2"> <div className="col-span-1 mt-4 mx-2">
<div className={`${styles.paragraph} px-4 py-2`}> <div className={`${styles.paragraph} px-4 py-2`}>
@ -52,16 +77,17 @@ const StepFourJoblisting = ({ handleChange, formData, nextStep, prevStep }) => {
required required
id="contact_email" id="contact_email"
onChange={handleChange("contact_email")} onChange={handleChange("contact_email")}
onBlur={validateEmail}
placeholder="Adres mailowy..." placeholder="Adres mailowy..."
className={`border-b-2 px-4 my-2 ${styles.paragraph}`} className={`px-4 mt-2 ${styles.paragraph}`}
/> />
<div className="bg-black h-0.5 opacity-10"></div>
</div> </div>
<div className="col-span-2 mt-4 mx-2"> <div className="col-span-1 mt-4 mx-2">
<div className={`${styles.paragraph} px-4 py-2`}> <div className={`${styles.paragraph} px-4 py-2`}>
Nazwa firmy Nazwa firmy
<span className={`${styles.paragraph} text-red-700`}>*</span> <span className={`${styles.paragraph} text-red-700`}>*</span>
</div> </div>
<div className="grid grid-cols-2">
<input <input
type="text" type="text"
name="company_name" name="company_name"
@ -70,9 +96,9 @@ const StepFourJoblisting = ({ handleChange, formData, nextStep, prevStep }) => {
onChange={handleChange("company_name")} onChange={handleChange("company_name")}
id="company_name" id="company_name"
placeholder="Wpisz nazwę firmy..." placeholder="Wpisz nazwę firmy..."
className={`border-b-2 px-4 mr-12 my-2 ${styles.paragraph} col-span-2 `} className={`px-4 mt-2 ${styles.paragraph}`}
/> />
</div> <div className="bg-black h-0.5 opacity-10"></div>
</div> </div>
<div className="col-span-1 mt-4 mx-2"> <div className="col-span-1 mt-4 mx-2">
<div className={`${styles.paragraph} px-4 py-2`}> <div className={`${styles.paragraph} px-4 py-2`}>
@ -88,27 +114,25 @@ const StepFourJoblisting = ({ handleChange, formData, nextStep, prevStep }) => {
onChange={handleChange("vat_number")} onChange={handleChange("vat_number")}
id="vat_number" id="vat_number"
placeholder="Wpisz nip..." placeholder="Wpisz nip..."
className={`border-b-2 px-4 my-2 ${styles.paragraph} `} className={`px-4 mt-2 ${styles.paragraph}`}
/> />
<div className="bg-black h-0.5 opacity-10"></div>
</div> </div>
</div> </div>
<div className={`${styles.flexCenter} gap-16 mt-16`}> <div className={`${styles.flexCenter} gap-16 mt-16`}>
<button <button
type="button" type="button"
onClick={prevStep} 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" className="h-16 md:h-12 w-80 md:w-72 rounded-xl bg-gray-700 font-poppins font-semibold text-[14px] text-white hover:scale-125 duration-300"
> >
<span className="text-[18px]"></span>&nbsp;&nbsp;Przejdź do <span className="text-[18px]"></span>&nbsp;&nbsp;Przejdź do <br className="block md:hidden"/>
poprzedniego kroku poprzedniego kroku
</button> </button>
<button
type="button" <Button
onClick={nextStep} isDiasbled={validateForm()}
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" nextStep={nextStep}
> Text="Przejdź do następnego kroku"/>
Przejdź do następnego kroku &nbsp;&nbsp;
<span className="text-[18px]"></span>{" "}
</button>
</div> </div>
</div> </div>
@ -117,4 +141,11 @@ const StepFourJoblisting = ({ handleChange, formData, nextStep, prevStep }) => {
); );
}; };
StepFourJoblisting.propTypes = {
handleChange: propTypes.func,
formData: propTypes.object,
nextStep: propTypes.func,
prevStep: propTypes.func,
};
export default StepFourJoblisting; export default StepFourJoblisting;

View File

@ -1,5 +1,8 @@
import React from "react";
import styles from "../style"; import styles from "../style";
import propTypes from "prop-types";
import { lorem_ipsum, lorem_ipsum_premium, lorem_ipsum_starter } from "../consts";
const StepOneJoblisting = ({ nextStep, handleChange, formData }) => { const StepOneJoblisting = ({ nextStep, handleChange, formData }) => {
// Funkcja do obsługi kliknięcia na div i aktualizacji stanu // Funkcja do obsługi kliknięcia na div i aktualizacji stanu
const handleDivClick = (value) => () => { const handleDivClick = (value) => () => {
@ -7,48 +10,62 @@ const StepOneJoblisting = ({ nextStep, handleChange, formData }) => {
}; };
const activeStyle = const activeStyle =
"h-[500px] w-64 border-4 rounded-xl border-stone-200 bg-gray-200 div-transition scale-125"; "h-[200px] sm:h-[350px] md:h-min w-64 pb-4 border-4 rounded-xl border-stone-200 bg-gray-200 div-transition scale-105 sm:scale-110";
const inactiveStyle = const inactiveStyle =
"h-[500px] w-64 rounded-xl bg-gray-200 border-gray-200 div-transition cursor-pointer hover:bg-gray-300"; "h-[200px] sm:h-[350px] md:h-min w-64 pb-4 rounded-xl bg-gray-200 border-gray-200 div-transition cursor-pointer hover:bg-gray-300";
return ( return (
<> <>
<div <div
className={` grid grid-cols-1 ${styles.paddingX} pb-8 pt-8 gap-1 h-full`} className={`grid grid-cols-1 ${styles.paddingX} pb-8 mt-24 xs:mt-2 xs:pt-8 gap-1 h-full`}
> >
<h1 className={`${styles.heading1} text-center`}> <h1 className={`text-center text-2xl sm:text-[40px] font-bold`}>
Zacznij dodawać ogłoszenie z izaac.pl Zacznij dodawać <br className="block md:hidden" /> ogłoszenie z izaac.pl
</h1> </h1>
<p className={`${styles.paragraph} text-center`}> <p className={`text-center text-xl mt-3`}>
Wybierz pakiet najlepiej odpowiadający Twoim potrzebom Wybierz pakiet najlepiej <br className="block md:hidden" />odpowiadający Twoim potrzebom
</p> </p>
</div> </div>
<div className={` ${styles.flexStart} ${styles.paddingX} gap-16 mt-20`}> <div className="h-[300px] sm:h-[600px]">
{/* Przykładowe divy jako przyciski wyboru */} <div className={` ${styles.flexStart} ${styles.paddingX} gap-3 xs:gap-5 sm:gap-8 mt-6`}>
<div <div
className={ className={
formData.posting_option === "M" ? activeStyle : inactiveStyle formData.posting_option === "M" ? activeStyle : inactiveStyle
} }
onClick={handleDivClick("M")} onClick={handleDivClick("M")}
> >
<p className={`${styles.paragraph} text-center mt-4`}>Starter</p> <p
className={`font-poppins text-l
sm:text-xl text-center mt-4`}>
Starter
</p>
<ul className="hidden md:block">
{lorem_ipsum_starter.map((lorem_ipsum) =>
(<li className="text-sm lista-outside mx-8 text-justify list-inside">{lorem_ipsum}</li>)
)}
</ul>
</div> </div>
<div className="h-[400px] w-0.5 bg-gray-300"></div> <div className="h-[150px] sm:h-[250px] md:h-[300px] w-0.5 bg-gray-300"></div>
<div <div
className={ className={
formData.posting_option === "S" ? activeStyle : inactiveStyle formData.posting_option === "S" ? activeStyle : inactiveStyle
} }
onClick={handleDivClick("S")} onClick={handleDivClick("S")}
> >
<p className={`${styles.paragraph} text-center mt-4`}>Standard</p> <p className={`font-poppins text-l sm:text-xl text-center mt-4`}>Standard</p>
<div className="mt-6"> <div className="mt-2">
<p className={`${styles.paragraph} text-center`}> <p className={`font-poppins text-sm sm:text-[14px] text-center mb-2`}>
najczęsciej wybierany najczęsciej wybierany
</p> </p>
<ul className="hidden md:block">
{lorem_ipsum.map((lorem_ipsum) =>
(<li className="text-sm lista-outside mx-8 text-justify list-inside">{lorem_ipsum}</li>)
)}
</ul>
</div> </div>
</div> </div>
<div className="h-[400px] w-0.5 bg-gray-300"></div> <div className="h-[150px] sm:h-[250px] md:h-[300px] w-0.5 bg-gray-300"></div>
<div <div
className={ className={
formData.posting_option === "P" ? activeStyle : inactiveStyle formData.posting_option === "P" ? activeStyle : inactiveStyle
@ -56,24 +73,35 @@ const StepOneJoblisting = ({ nextStep, handleChange, formData }) => {
onClick={handleDivClick("P")} onClick={handleDivClick("P")}
style={{ cursor: "pointer" }} style={{ cursor: "pointer" }}
> >
<p className={`${styles.paragraph} text-center mt-4`}>Premium</p> <p className={`font-poppins text-l sm:text-xl text-center mt-4`}>Premium</p>
<ul className="hidden md:block">
{lorem_ipsum_premium.map((lorem_ipsum) =>
(<li className="text-sm lista-outside mx-8 text-justify list-inside">{lorem_ipsum}</li>)
)}
</ul>
</div> </div>
</div> </div>
<div className="grid pt-32 items-center justify-center"> </div>
<div className="w-full">
<button <button
type="button" type="button"
onClick={nextStep} onClick={nextStep}
className="h-12 w-72 rounded-xl bg-gray-700 className="absolute inset-x-0 md:-left-4 bottom-[4rem] mx-auto h-12 w-72 rounded-xl bg-gray-700
font-poppins font-semibold text-[14px] text-white font-poppins font-semibold text-[14px] text-white
hover:scale-125 duration-300" hover:scale-125 duration-300"
> >
Przejdź do następnego kroku &nbsp;&nbsp; Przejdź do następnego kroku &nbsp;&nbsp;
<span className="text-[18px]"></span>{" "} <span className="text-[18px]"></span>{" "}
</button> </button>
<div className="h-12 w-full"></div>
</div> </div>
</> </>
); );
}; };
StepOneJoblisting.propTypes = {
nextStep: propTypes.func,
handleChange: propTypes.func,
formData: propTypes.object,
};
export default StepOneJoblisting; export default StepOneJoblisting;

View File

@ -1,12 +1,13 @@
import React, { useEffect } from 'react';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import styles from '../style';
import TextDivider from './TextDivider'; import TextDivider from './TextDivider';
import ListingSmall from './ListingSmall'; import ListingSmall from './ListingSmall';
import { IzaacLOGO } from '../assets' import { IzaacLOGO } from '../assets'
import SkillsList from './SkillList'; import SkillsList from './SkillList';
import PropTypes from 'prop-types';
const StepThreeJoblisting = ({ formData, prevStep, skills, handleSubmit }) => { const StepThreeJoblisting = ({ formData, prevStep, skills, handleSubmit }) => {
// Funkcja do przetwarzania HTML i dodawania klas // Funkcja do przetwarzania HTML i dodawania klas
const processHTML = (htmlString) => { const processHTML = (htmlString) => {
@ -103,4 +104,11 @@ const StepThreeJoblisting = ({ formData, prevStep, skills, handleSubmit }) => {
); );
} }
StepThreeJoblisting.propTypes = {
formData: PropTypes.object,
prevStep: PropTypes.func,
skills: PropTypes.array,
handleSubmit: PropTypes.func
}
export default StepThreeJoblisting; export default StepThreeJoblisting;

View File

@ -1,4 +1,7 @@
import React, { useState } from 'react' import { useEffect, useState } from 'react'
import propTypes from 'prop-types';
import MaximumLength from '@ckeditor/ckeditor5-react'
import { categories, experience_levels } from '../consts';
import styles from '../style' import styles from '../style'
import { CKEditor } from '@ckeditor/ckeditor5-react'; import { CKEditor } from '@ckeditor/ckeditor5-react';
import ClassicEditor from '@ckeditor/ckeditor5-build-classic'; import ClassicEditor from '@ckeditor/ckeditor5-build-classic';
@ -6,17 +9,31 @@ import ImageUpload from './ImageUpload';
import SkillsSelector from './SkillsSelector'; import SkillsSelector from './SkillsSelector';
import Salary from './Salary'; import Salary from './Salary';
import { placeholderImage } from '../assets'; import { placeholderImage } from '../assets';
import Button from './Button';
import { categories, experience_levels } from '../consts';
const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setFormData, removeFields, skills }) => { const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setFormData, removeFields, skills }) => {
const [editorData, setEditorData] = useState(''); const [editorData, setEditorData] = useState('');
const [imageSrc, setImageSrc] = useState(placeholderImage); const [imageSrc, setImageSrc] = useState(placeholderImage);
const handleEditorChange = (event, editor) => { const max_words = 500;
const data = editor.getData(); const countWords = (text) => {
setEditorData(data); return text.split(/\s/).filter(function(n) { return n != '' }).length;
} }
const validateForm2 = () => {
const { name, company_name, localization, content, min_salary, max_salary, work_from_home, employment_type, image, skill_levels } = formData;
if (!name || !company_name || !localization || !content || !min_salary || !max_salary || !work_from_home || !employment_type || !image || !skill_levels) {
return true;
}
}
useEffect(() => {
if (formData.content) {
console.log(countWords(formData.content))
if (countWords(formData.content) > max_words) {
alert('Maksymalna liczba słów wynosi 500')
}
}
}, [formData.content])
const handleNextStep = () => { const handleNextStep = () => {
if (validateForm()) { if (validateForm()) {
console.log(errors) console.log(errors)
@ -29,7 +46,9 @@ const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setForm
const [errors, setErrors] = useState({}); const [errors, setErrors] = useState({});
const validateForm = () => { const validateForm = () => {
let newErrors = {}; let newErrors = {};
if (countWords(formData.content) > max_words) {
newErrors.content = 'Maksymalna liczba słów wynosi 600';
}
// Sprawdź każde wymagane pole // Sprawdź każde wymagane pole
if (!formData.name) { if (!formData.name) {
newErrors.name = 'To pole jest wymagane'; newErrors.name = 'To pole jest wymagane';
@ -136,6 +155,16 @@ const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setForm
editor={ClassicEditor} editor={ClassicEditor}
data={formData['content'] || ''} data={formData['content'] || ''}
required required
// config={{
// plugins: [WordCount],
// toolbar: ['wordCount'],
// wordCount: {
// onUpdate: stats => {
// console.log(stats.words, stats.characters)
// }
// }
// }}
onChange={(event, editor) => { onChange={(event, editor) => {
const data = editor.getData(); const data = editor.getData();
handleChange('content')(data); // Wywołanie zmodyfikowanej funkcji handleChange handleChange('content')(data); // Wywołanie zmodyfikowanej funkcji handleChange
@ -205,24 +234,22 @@ const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setForm
formData={formData}/> formData={formData}/>
</div> </div>
<div className={`${styles.flexCenter} p-20 gap-20`}> <div className={`${styles.flexCenter} py-10 gap-20`}>
<button <button
type="button" type="button"
onClick={prevStep} onClick={prevStep}
className='h-12 w-72 rounded-xl bg-gray-700 className='h-16 md:h-12 w-80 md:w-72 rounded-xl bg-gray-700
font-poppins font-semibold text-[14px] scale-100 font-poppins font-semibold text-[14px] scale-100
text-white hover:scale-125 duration-300'> text-white hover:scale-125 duration-300'>
<span className='text-[18px]'></span>&nbsp;&nbsp; <span className='text-[18px]'></span>&nbsp;&nbsp;
Przejdź do poprzedniego kroku</button> Przejdź do poprzedniego kroku</button>
<button
type="button" <Button
onClick={handleNextStep} isDiasbled={validateForm2()}
className='h-12 w-72 rounded-xl nextStep={handleNextStep}
bg-gray-700 font-poppins Text="Przejdź do następnego kroku"/>
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>
</form> </form>
</div> </div>
@ -231,4 +258,14 @@ const StepTwoJoblisting = ({ nextStep, prevStep, handleChange, formData, setForm
) )
} }
export default StepTwoJoblisting StepTwoJoblisting.propTypes = {
nextStep: propTypes.func,
prevStep: propTypes.func,
handleChange: propTypes.func,
formData: propTypes.object,
setFormData: propTypes.func,
removeFields: propTypes.func,
skills: propTypes.array
}
export default StepTwoJoblisting

View File

@ -1,4 +1,4 @@
import React from 'react'
import TextDivider from './TextDivider' import TextDivider from './TextDivider'
const Success = () => { const Success = () => {

View File

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

View File

@ -2,13 +2,15 @@ import React from "react";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import ListingSmall from "./ListingSmall"; import ListingSmall from "./ListingSmall";
import SkillRender from "./SkillRender"; // import SkillRender from "./SkillRender";
import Search from "./Search"; // import Search from "./Search";
import Filter from "./Filter"; import Filter from "./Filter";
import Category from "./Category"; // import Category from "./Category";
import JobOfferContent from "./JobOfferContent"; import JobOfferContent from "./JobOfferContent";
import { categories } from "../consts"; import { categories } from "../consts";
import axios from "axios"; import axios from "axios";
import { Link, useParams } from "react-router-dom";
function renderCircles(level) { function renderCircles(level) {
let numberOfFilledCircles; let numberOfFilledCircles;
@ -44,11 +46,17 @@ function renderCircles(level) {
} }
const WorkApp = () => { const WorkApp = () => {
let { id } = useParams();
const [isDetailsVisible, setIsDetailsVisible] = useState(false); const [isDetailsVisible, setIsDetailsVisible] = useState(false);
// Czy szczegóły są widoczne // Czy szczegóły są widoczne
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
// Funkcja pobierająca dane z API // useEffect(() => {
// if (!isOpen) {
// setSearchQuery({ name: "", localization: "", min_salary: "", max_salary: "" });
// }}, [isOpen]);
const [ogloszenia, setOgloszenia] = useState([]); const [ogloszenia, setOgloszenia] = useState([]);
const [skills, setSkills] = useState([]); const [skills, setSkills] = useState([]);
@ -64,12 +72,15 @@ const WorkApp = () => {
// console.log(data); // console.log(data);
if (data.length > 0) { if (data.length > 0) {
setSelectedOgloszenie(data[0]); setSelectedOgloszenie(data[0]);
// console.log(data[0]) console.log(data[0])
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
}; };
if (id === undefined) {
id = ogloszenia[0]?.id;
}
const getSkills = async () => { const getSkills = async () => {
try { try {
@ -85,27 +96,41 @@ const WorkApp = () => {
}; };
const [searchQuery, setSearchQuery] = useState({}); const [searchQuery, setSearchQuery] = useState({
min_salary: '',
max_salary: '',
categories: [],
name: ''
});
const clearSearchQuery = () => {
setSearchQuery({
min_salary: '',
max_salary: '',
categories: [],
name: ''
});
getSkills();
getOgloszenia()
setIsOpen(!isOpen)
};
const [selectedOgloszenie, setSelectedOgloszenie] = useState(ogloszenia[0]); const [selectedOgloszenie, setSelectedOgloszenie] = useState(ogloszenia[0]);
useEffect(() => { useEffect(() => {
getOgloszenia(); getOgloszenia();
getSkills(); getSkills();
}, []); }, []);
useEffect(() => {
document.title = `Izaac - ${selectedOgloszenie?.name}`;
}, [selectedOgloszenie?.name]);
const handleOgloszenieClick = (ogloszenie) => { const handleOgloszenieClick = (ogloszenie) => {
setSelectedOgloszenie(ogloszenie); setSelectedOgloszenie(ogloszenie);
setIsDetailsVisible(true); // Pokaż szczegóły na urządzeniach mobilnych setIsDetailsVisible(true); // Pokaż szczegóły na urządzeniach mobilnych
}; };
const prevSlide = () => {
setCurrentSlide(
(prevSlide) => (prevSlide - 1 + categories.length) % categories.length
);
};
const nextSlide = () => {
setCurrentSlide((prevSlide) => (prevSlide + 1) % categories.length);
};
const [currentSlide, setCurrentSlide] = React.useState(0);
// Funkcja do powrotu do listy ogłoszeń // Funkcja do powrotu do listy ogłoszeń
const handleBackToList = () => { const handleBackToList = () => {
setIsDetailsVisible(false); setIsDetailsVisible(false);
@ -125,53 +150,48 @@ const WorkApp = () => {
}, []); }, []);
return ( return (
<section className=" mx-auto bg-white w-full"> <section className={`bg-white`}>
<div className="flex-grow h-5/6 grid grid-cols-10"> <div className="h-[88vh] flex flex-row ">
<div className="px-3 flex col-span-11 sm:flex-wrap flex-shrink-0 items-center place-content-center">
<button onClick={prevSlide} className="lg:hidden mx-6 font-semibold place-self-start self-center leading-5">
Poprzednia <br/> kategoria
</button>
{categories.map((category, index) => (
<div
key={index}
className={`lg:block ${
index === currentSlide ? "block" : "hidden"
} lg:flex flex-grow lg:flex-none flex-basis-0"`}
>
<Category
name={category.name}
last={index === categories.length - 1}
small={isSmallViewport}
func={() => { getOgloszenia({ category: category.id }); }}
/>
</div>
))}
<button onClick={nextSlide} className="lg:hidden font-semibold leading-5 place-self-end self-center mr-6">
Następna <br/> kategoria
</button>
</div>
<div <div
className={`static rounded-xl col-span-11 sm:col-span-4 bg-gray-100 overflow-y-auto px-2 my-4 mx-2 sm:h-[84vh] h-[84vh] ${ className={`relative bg-gray-100 w-full sm:w-[40%]
isDetailsVisible ? "hidden sm:block" : "block" ${isOpen ? 'h-[100vh] overflow-y-hidden' : 'sm:overflow-y-auto'}
}`} sm:h-[89vh] ${isDetailsVisible ? "hidden sm:block" : "block" }`}
> >
<div className="z-10 sticky top-0 col-span-11 sm:cols-span-4 h-24 bg-slate-300 flex items-center place-content-center mb-2"> <div className="z-10 sticky top-[70px] sm:top-0 w-[100vw] sm:w-full">
<h1 className="text-center text-3xl font-poppins font-semibold text-slate-800 py-4 mx-6"> <div className="z-10 w-full fixed sm:sticky top-[4.4rem] sm:top-0 min-h-fit bg-slate-300 flex items-center place-content-center py-1 sm:py-2">
<h1 className="text-center text-xl sm:text-l md:text-2xl font-poppins font-semibold text-slate-800 py-2 mx-2">
Oferty pracy Oferty pracy
</h1> </h1>
<button <button
className="block font-poppins text-white font-semibold className="block font-poppins text-white font-semibold
bg-gray-500 px-4 py-2 mx-6 rounded-md" bg-gray-500 px-4 py-2 mx-6 rounded-md hover:bg-gray-600 duration-100"
onClick={() => setIsOpen(!isOpen)} onClick={() => setIsOpen(!isOpen)}
> >
Pokaż filtry
{!isOpen ? 'Pokaż filtry' : 'Ukryj filtry'}
</button> </button>
</div> </div>
<div className="z-10 sticky top-24 col-span-11 sm:cols-span-4 bg-slate-300 mb-2"> <div className={`z-10 absolute top-[3rem] sm:top-16 w-[100vw] sm:w-full`}>
{isOpen && <Filter isOpen={isOpen} />} <Filter isOpen={isOpen}
searchQuery={searchQuery || {}}
setSearchQuery={setSearchQuery}
categories={categories}
clearSearchQuery={clearSearchQuery}
onClick={()=> {getOgloszenia(searchQuery)
setIsOpen(!isOpen)
}}/>
</div> </div>
</div>
<div className={`sm:mt-2 mr-2 mt-[8rem] noblur ${!isOpen ? '' : 'blur'}`}
>
{ogloszenia.map((ogloszenie, index) => ( {ogloszenia.map((ogloszenie, index) => (
<Link
key={ogloszenie.id}
to={`/work/joboffers/${ogloszenie.id}`} >
<ListingSmall <ListingSmall
key={ogloszenie.id} key={ogloszenie.id}
name={ogloszenie.name} name={ogloszenie.name}
@ -181,14 +201,18 @@ const WorkApp = () => {
require_salary={ogloszenie.require_salary} require_salary={ogloszenie.require_salary}
image={ogloszenie.image} image={ogloszenie.image}
index={index} index={index}
onClick={() => handleOgloszenieClick(ogloszenie)} onClick={() => {handleOgloszenieClick(ogloszenie)}}
selected={selectedOgloszenie?.id === ogloszenie.id}
blur={isOpen ? 'blur' : ''}
/> />
</Link>
))} ))}
</div>
</div> </div>
<div <div
className={`sm:col-span-6 col-span-11 bg-gray-100 sm:h-[84vh] h-[84vh] overflow-y-auto p-4 my-4 ${ className={`absolute sm:relative w-full sm:w-[60%] bg-gray-100 sm:h-[89vh] h-[100vh] top-16 sm:top-0 -left-4 sm:left-0 right-0 overflow-y-auto p-8 sm:p-4 border-separate sm:border-l-8 blur-transition ${
isDetailsVisible ? "block" : "hidden sm:block" isDetailsVisible ? "block" : "hidden sm:block"
}`} } noblur ${isOpen ? 'blur' : ''}`}
> >
{isDetailsVisible && ( {isDetailsVisible && (
<div className="grid grid-cols-6"> <div className="grid grid-cols-6">
@ -202,19 +226,8 @@ const WorkApp = () => {
)} )}
{selectedOgloszenie && ( {selectedOgloszenie && (
<JobOfferContent id={selectedOgloszenie.id} skills={skills} /> <JobOfferContent id={id} skills={skills} />
)} )}
{/* <div className='grid sm:grid-cols-2 md:grid-cols-5 grid-cols-2'>
{Object.entries(selectedOgloszenie.neededSkills).map(([skill, level]) => (
<SkillRender
skill={skill}
level={level}
/>
))}
</div>
<div className='text-slate-800 font-medium mt-4'>
{selectedOgloszenie.tresc}
</div> */}
</div> </div>
</div> </div>
</section> </section>

View File

@ -169,12 +169,12 @@ export const linki = [
}, },
{ {
"id": "2", "id": "2",
"name": "work/postings", "name": "work/joboffers",
"title": "Ogłoszenia", "title": "Ogłoszenia",
}, },
{ {
"id": "3", "id": "3",
"name": "work/jobposting", "name": "work/addjoboffer",
"title": "Dodaj ogłoszenie", "title": "Dodaj ogłoszenie",
}, },
{ {
@ -326,4 +326,35 @@ export const employment_types = [
"id": "INT", "id": "INT",
"name": "Staż", "name": "Staż",
}, },
]
export const lorem_ipsum = [
"Lorem ipsum dolor sit amet",
"consectetur adipiscing elit",
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ",
"Ut enim ad minim veniam",
"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ",
'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. ',
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
]
export const lorem_ipsum_starter = [
"Lorem ipsum dolor sit amet",
"consectetur adipiscing elit",
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ",
"Ut enim ad minim veniam",
"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ",
]
export const lorem_ipsum_premium = [
"Lorem ipsum dolor sit amet",
"consectetur adipiscing elit",
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ",
"Ut enim ad minim veniam",
"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ",
'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. ',
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ",
"Ut enim ad minim veniam",
"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ",
] ]

View File

@ -22,6 +22,27 @@
} }
} }
/* @layer components {
.no-spin-buttons::-webkit-inner-spin-button,
.no-spin-buttons::-webkit-outer-spin-button {
@apply -webkit-appearance-none margin-0;
}
.no-spin-buttons {
@apply -moz-appearance-textfield;
}
} */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Dla Firefox */
input[type="number"] {
-moz-appearance: textfield;
}
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@ -60,11 +81,43 @@
} }
/* Expanded state styles */ /* Expanded state styles */
.expanded { .collapsible {
max-height: fit-content; /* Adjust as needed */ max-height: 0;
opacity: 1; overflow: hidden;
transition-property: all;
transition-timing-function: ease-in-out;
transition-duration: 0.5s;
} }
/* Styl dla elementu, gdy jest rozwinięty */
.collapsible.expanded {
max-height: 1000px; /* Przykładowa maksymalna wysokość, może wymagać dostosowania */
opacity: 1;
transition-property: all;
transition-timing-function: ease-in-out;
transition-duration: 0.5s;
}
.div-transition {
transition-property: filter;
transition-duration: 1s;
transition-timing-function: ease-in-out;
}
.noblur {
filter: blur(0px);
transition-property: filter;
transition-duration: 0.5s;
transition-timing-function: ease-in-out;
}
.blur {
filter: blur(5px);
transition-property: filter;
transition-duration: 0.5s;
transition-timing-function: ease-in-out;
}
.minus-z-index { .minus-z-index {
z-index: -10; z-index: -10;
} }
@ -101,6 +154,10 @@
list-style: inside; list-style: inside;
} }
.lista-outside {
list-style: outside;
}
.sidebar-show { .sidebar-show {
-webkit-animation: slide-top 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both; -webkit-animation: slide-top 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
animation: slide-top 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both; animation: slide-top 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
@ -111,57 +168,146 @@
animation: slide-down 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both; animation: slide-down 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
} }
@-webkit-keyframes slide-top { .slider-wrapper {
0% { display: flex;
-webkit-transform: translateX(600px); justify-content: center;
transform: translateX(600px); align-items: center;
} width: 100%;
100% {
-webkit-transform: translateX(0);
transform: translateX(0);
}
} }
@keyframes slide-top { .slider {
0% { position: relative;
-webkit-transform: translateX(600px); width: 100%;
transform: translateX(600px);
}
100% {
-webkit-transform: translateX(0);
transform: translateX(0);
}
}
@-webkit-keyframes slide-down {
0% {
-webkit-transform: translateX(0);
transform: translateX(0);
}
100% {
-webkit-transform: translateX(600px);
transform: translateX(600px);
}
}
@keyframes slide-down {
0% {
-webkit-transform: translateX(0);
transform: translateX(0);
}
100% {
-webkit-transform: translateX(600px);
transform: translateX(600px);
}
} }
@media (max-width: 768px) { .slider__track,
.details-section { .slider__range,
display: none; .slider__left-value,
} .slider__right-value {
.list-section { position: absolute;
display: block; }
}
.slider__track,
.slider__range {
border-radius: 3px;
max-width: 100%;
width: 100%;
height: 5px;
}
.slider__track {
background-color: #ced4da;
width: 100;
z-index: 1;
}
.slider__range {
background-color: #9fe5e1;
z-index: 2;
}
.slider__left-value,
.slider__right-value {
color: #dee2e6;
font-size: 12px;
margin-top: 20px;
}
.slider__left-value {
left: 6px;
color: black;
}
.slider__right-value {
right: -4px;
color: black;
}
/* Removing the default appearance */
.thumb,
.thumb::-webkit-slider-thumb {
-webkit-appearance: none;
}
.thumb {
pointer-events: none;
position: absolute;
height: 0;
outline: none;
transition-duration: 0.5s;
transition-property: display;
}
.thumb--left {
z-index: 3;
}
.thumb--right {
z-index: 4;
}
/* For Chrome browsers */
.thumb::-webkit-slider-thumb {
background-color: #f1f5f7;
border: none;
border-radius: 50%;
box-shadow: 0 0 1px 1px #ced4da;
cursor: pointer;
height: 18px;
width: 18px;
margin-top: 4px;
pointer-events: all;
position: relative;
}
/* For Firefox browsers */
.thumb::-moz-range-thumb {
background-color: #f1f5f7;
border: none;
border-radius: 50%;
box-shadow: 0 0 1px 1px #ced4da;
cursor: pointer;
height: 18px;
width: 18px;
margin-top: 4px;
pointer-events: all;
position: relative;
}
@-webkit-keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.thumb--animatedin {
animation: fadeIn 0.5s;
}
@-webkit-keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
.thumb--animatedout {
animation: fadeOut 0.5s;
}
.t-width {
width: 84%;
}
.search-width {
width: 16rem;
} }

View File

@ -7,7 +7,7 @@ module.exports = {
colors: { colors: {
primary: "#00040f", primary: "#00040f",
secondary: "#00f6ff", secondary: "#00f6ff",
dimWhite: "rgba(255, 255, 255, 0.7)", dimWhite: "rgba(255, 255, 255, 0.8)",
dimBlue: "rgba(9, 151, 124, 0.1)", dimBlue: "rgba(9, 151, 124, 0.1)",
}, },
fontFamily: { fontFamily: {