profilePage

This commit is contained in:
Николай Полтщук 2022-12-28 09:45:26 +03:00
parent d81110d7d5
commit cabd01ca43
10 changed files with 531 additions and 138 deletions

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" style="height: 100%">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
@ -14,7 +14,7 @@
<link rel="shortcut icon" href="favicon.ico" /> <link rel="shortcut icon" href="favicon.ico" />
</head> </head>
<body> <body style="min-height: 100%">
<!-- <noscript>You need to enable JavaScript to run this app.</noscript> --> <!-- <noscript>You need to enable JavaScript to run this app.</noscript> -->
<div id="root"></div> <div id="root"></div>
<!-- <script type="text/javascript"> <!-- <script type="text/javascript">

View File

@ -6,6 +6,9 @@ import {
selectCurrentCandidate, selectCurrentCandidate,
auth auth
} from '../../redux/outstaffingSlice' } from '../../redux/outstaffingSlice'
import {getRole} from '../../redux/roleSlice'
import {useState} from 'react'
import {createMarkup} from "../../helper";
import arrow from '../../images/right-arrow.png' import arrow from '../../images/right-arrow.png'
import rectangle from '../../images/rectangle_secondPage.png' import rectangle from '../../images/rectangle_secondPage.png'
import Sidebar from '../CandidateSidebar/CandidateSidebar' import Sidebar from '../CandidateSidebar/CandidateSidebar'
@ -17,8 +20,6 @@ import {fetchGet} from '../../server/server'
import {Footer} from '../Footer/Footer' import {Footer} from '../Footer/Footer'
import './candidate.scss' import './candidate.scss'
import {getRole} from '../../redux/roleSlice'
import {useState} from 'react'
const Candidate = () => { const Candidate = () => {
const history = useHistory(); const history = useHistory();
@ -79,10 +80,6 @@ const Candidate = () => {
return styles return styles
}; };
function createMarkup(text) {
return {__html: text.split('</p>').join('</p>')}
}
const {header, img, classes} = setStyles(); const {header, img, classes} = setStyles();
return ( return (

View File

@ -1,11 +1,59 @@
import React from 'react'; import React, {useEffect, useState} from 'react';
import {LogoutButton} from "../LogoutButton/LogoutButton"; import {auth, getProfileInfo, setProfileInfo} from "../../redux/outstaffingSlice";
import {useDispatch, useSelector} from "react-redux";
import {Loader} from '../Loader/Loader'
import {getRole} from "../../redux/roleSlice";
import {useHistory} from "react-router-dom";
import {fetchGet} from "../../server/server";
import './profileHeader.scss'
export const ProfileHeader = () => { export const ProfileHeader = () => {
const [isLoggingOut, setIsLoggingOut] = useState(false);
const dispatch = useDispatch();
const userRole = useSelector(getRole);
const profileInfo = useSelector(getProfileInfo)
const history = useHistory();
useEffect(() => {
fetchGet({
link: `${process.env.REACT_APP_API_URL}/api/profile/${localStorage.getItem('cardId')}`,
}).then((profileInfo) => {
dispatch(setProfileInfo(profileInfo))
})
}, [])
return( return(
<header className='profileHeader'> <header className='profileHeader'>
<h2 className='profileHeader__title'>Аутстаффинг <span>Personal Profile</span></h2> <div className='profileHeader__head'>
<LogoutButton/> <div className='profileHeader__container'>
<h2 className='profileHeader__title'>itguild.<span>для разработчиков</span></h2>
<button onClick={() => {
setIsLoggingOut(true);
localStorage.clear();
dispatch(auth(false));
setIsLoggingOut(false);
history.push(userRole === 'ROLE_DEV' ? '/authdev' : '/auth')
}}
className='profileHeader__logout'>
{isLoggingOut ? <Loader/> : 'Выйти'}{' '}
</button>
</div>
</div>
<div className='profileHeader__info'>
<div className='profileHeader__container'>
<nav className='profileHeader__nav'>
<a className='active'>Резюме</a>
<a>Отчетность</a>
<a>Трекер</a>
<a>Выплаты</a>
<a>Настройки</a>
</nav>
<div className='profileHeader__personalInfo'>
<h3 className='profileHeader__personalInfoName'>{profileInfo.fio}</h3>
<img src={profileInfo.photo} className='profileHeader__personalInfoAvatar' />
</div>
</div>
</div>
</header> </header>
) )
} }

View File

@ -1,98 +1,276 @@
.profile { .profile {
background: #F1F1F1;
height: 100%;
min-height: 100vh;
&__container { &__container {
max-width: 1140px; max-width: 1160px;
margin: 0 auto; padding: 0 10px;
margin: 20px auto;
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
&Header {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-top: 60px;
&__title {
text-align: center;
font-family: "GT Eesti Pro Display", sans-serif;
font-size: 5em;
font-weight: 700;
font-style: normal;
letter-spacing: normal;
line-height: 77.81px;
span {
color: #52b709;
}
}
.logout-button {
top: auto;
}
}
&__content { &__content {
display: flex; display: flex;
margin-top: 20px; flex-direction: column;
}
&__info { &__title {
display: flex; font-weight: 700;
flex-direction: column; font-size: 22px;
align-items: center; line-height: 32px;
padding-left: 45px; margin-bottom: 0;
span {
color: #52B709;
} }
} }
&__sideBar { &__back {
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
padding: 10px; column-gap: 30px;
border: 2px solid whitesmoke; margin-top: 20px;
max-width: 34%; cursor: pointer;
row-gap: 12px;
&__position { p {
font-size: 3rem; margin-bottom: 0;
font-family: "GT Eesti Pro Display", sans-serif; font-size: 14px;
font-weight: 700; line-height: 32px;
font-style: normal; font-weight: 500;
letter-spacing: normal;
line-height: 36px;
text-align: center;
} }
}
&__experience { &__info {
font-family: "GT Eesti Pro Display", sans-serif; min-height: 128px;
font-size: 18px; background: white;
font-weight: normal; border-radius: 12px;
font-style: normal; margin-top: 30px;
letter-spacing: normal; display: flex;
line-height: 36px; align-items: center;
margin-top: 20px; padding: 0 130px 0 45px;
display: flex; justify-content: space-between;
flex-direction: column; }
span { &__person {
font-size: 30px; display: flex;
font-weight: 700; align-items: center;
text-align: center; column-gap: 45px;
}
}
&-name {
font-size: 30px;
font-weight: 700;
text-align: center;
}
} }
&__avatar { &__avatar {
width: 180px; width: 88px;
height: 180px; height: 88px;
border-radius: 100px; border-radius: 100px;
} }
&__name {
font-weight: 500;
font-size: 16px;
line-height: 32px;
position: relative;
&:after {
content: '';
position: absolute;
background: #52B709;
border-radius: 12px;
width: 70%;
height: 8px;
bottom: -14px;
left: 0;
}
}
&__git {
background: #52B709;
border-radius: 44px;
width: 177px;
height: 50px;
font-weight: 500;
font-size: 16px;
line-height: 32px;
color: white;
border: none;
}
&__skills {
background: #FFFFFF;
border-radius: 12px;
margin-top: 35px;
}
&__sections__head {
display: flex;
min-height: 69px;
background: #E1FCCF;
border-radius: 12px 12px 0px 0px;
align-items: center;
padding: 0 35px 0 50px;
justify-content: space-between;
h3 {
font-style: normal;
font-size: 18px;
line-height: 32px;
}
button {
background: #FFFFFF;
border-radius: 44px;
border: none;
min-width: 190px;
height: 42px;
font-weight: 500;
font-size: 14px;
line-height: 32px;
}
}
.skills__section {
&__items {
padding: 25px 35px 25px 50px;
&__wrapper {
max-width: 630px;
display: flex;
flex-wrap: wrap;
column-gap: 5px;
.skill_item {
font-weight: 700;
font-size: 16px;
line-height: 32px;
white-space: nowrap;
text-transform: uppercase;
}
}
}
}
&__experience {
display: flex;
flex-direction: column;
row-gap: 20px;
margin-top: 30px;
.experience {
&__block {
background: #FFFFFF;
border-radius: 12px;
}
&__content {
padding: 15px 35px 15px 50px;
h2 {
font-weight: 700;
font-size: 16px;
line-height: 32px;
margin-bottom: 0;
}
p {
font-weight: 400;
font-size: 16px;
line-height: 32px;
margin-bottom: 0;
}
}
}
}
&__sectionGit {
margin-top: 25px;
&Items {
margin-top: 25px;
display: flex;
flex-wrap: wrap;
row-gap: 20px;
column-gap: 25px;
justify-content: space-between;
.gitItem {
width: 48%;
display: flex;
align-items: center;
justify-content: space-between;
background: #FFFFFF;
border-radius: 12px;
padding: 35px 30px 30px 45px;
&__info {
display: flex;
flex-direction: column;
max-width: 350px;
width: 100%;
&__about {
display: flex;
align-items: center;
column-gap: 15px;
}
&__name {
h4 {
font-weight: 700;
font-size: 18px;
line-height: 32px;
margin-bottom: 0;
}
p {
font-weight: 300;
font-size: 16px;
line-height: 32px;
margin-bottom: 0;
}
}
&__specification {
margin-top: 30px;
display: flex;
align-items: center;
padding-left: 10px;
column-gap: 25px;
span {
background: #D4F123;
border-radius: 12px;
max-width: 260px;
width: 100%;
height: 8px;
}
p {
margin-bottom: 0;
font-weight: 400;
font-size: 14px;
line-height: 32px;
}
}
}
&__link {
border-radius: 50%;
background: #DDEEC6;
width: 48px;
height: 48px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
}
}
}
.container {
max-width: 1160px;
padding: 0 10px;
margin-top: 70px;
}
} }

View File

@ -0,0 +1,83 @@
.profileHeader {
width: 100%;
display: flex;
flex-direction: column;
&__head {
background: #E1FCCF;
}
&__container {
max-width: 1160px;
padding: 0 10px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
min-height: 50px;
}
&__title {
font-weight: 700;
font-size: 20px;
line-height: 32px;
margin-bottom: 0;
span {
color: #52B709;
}
}
&__logout {
background: none;
border: none;
font-weight: 500;
font-size: 16px;
line-height: 32px;
color: #000000;
}
&__info {
background: #FFFFFF;
}
&__nav {
display: flex;
column-gap: 30px;
.active {
color: #000000 !important;
}
a {
text-decoration: none;
cursor: pointer;
font-weight: 700;
font-size: 18px;
line-height: 32px;
color: #807777 !important;
}
}
&__personalInfo {
display: flex;
column-gap: 20px;
align-items: center;
&Name {
margin-bottom: 0;
font-size: 12px;
line-height: 32px;
max-width: 150px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
&Avatar {
width: 37px;
height: 37px;
border-radius: 100px;
}
}
}

14
src/helper.js Normal file
View File

@ -0,0 +1,14 @@
export function createMarkup(text) {
return {__html: text.split('</p>').join('</p>')}
}
export function transformHtml(text) {
let startHtml = {__html: text.split('<h2>').join('<br><h2>').split('<br>')}
startHtml = startHtml.__html.filter((el)=> {
return (el != null && el != "" || el === 0)
})
const finalHtml = startHtml.map((item) => {
return `<div class='experience__block'><div class="profile__sections__head"><h3>Описание опыта работы</h3><button>Редактировать раздел</button></div><div class="experience__content">${item.split('<h3>')[0]}</div></div>`
})
return {__html: finalHtml.join('')}
}

BIN
src/images/arrowRight.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

BIN
src/images/gitItemImg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,70 +1,142 @@
import React, {useEffect, useState} from 'react'; import React, {useState} from 'react';
import { ProfileHeader } from "../components/Profile/ProfileHeader"; import { ProfileHeader } from "../components/Profile/ProfileHeader";
import { setProfileInfo, getProfileInfo } from "../redux/outstaffingSlice"; import { getProfileInfo } from "../redux/outstaffingSlice";
import {useDispatch, useSelector} from "react-redux"; import { useSelector } from "react-redux";
import { fetchGet } from '../../src/server/server' import {transformHtml} from "../helper";
import {Link} from "react-router-dom"; import { Footer } from '../components/Footer/Footer'
import arrow from "../images/right-arrow.png";
import rightArrow from "../images/arrowRight.png"
import gitImgItem from "../images/gitItemImg.png"
import './../components/Profile/profile.scss' import './../components/Profile/profile.scss'
export const Profile = () => { export const Profile = () => {
const dispatch = useDispatch();
const profileInfo = useSelector(getProfileInfo) const profileInfo = useSelector(getProfileInfo)
useEffect(() => { const [openGit, setOpenGit] = useState(false);
fetchGet({
link: `${process.env.REACT_APP_API_URL}/api/profile/get-main-data?user_id=${localStorage.getItem('id')}`,
}).then((profileInfo) => {
dispatch(setProfileInfo(profileInfo))
})
}, [dispatch, localStorage.getItem('id')])
return( return(
<div className='profile'> <div className='profile'>
<ProfileHeader/>
<div className='profile__container'> <div className='profile__container'>
<ProfileHeader/>
<div className='profile__content'> <div className='profile__content'>
<div className='profile__sideBar'> <h2 className='profile__title'>Ваше резюме {openGit && <span>- Git</span>}</h2>
<h2 className='profile__sideBar__position'>{profileInfo.position_name} {profileInfo.specification}</h2> {openGit && <div className='profile__back' onClick={() => setOpenGit(false)}>
<img className='profile__avatar' src={profileInfo.photo} /> <img src={arrow} alt='arrow'/>
<span className='profile__sideBar-name'>{profileInfo.fio}</span> <p>Вернуться</p>
<p className='profile__sideBar__experience'>Опыт работы: <span>{profileInfo.years_of_exp}года</span></p> </div>}
</div> <div className='profile__info'>
<div className='profile__content__info'> <div className='profile__person'>
<div className="works__body"> <img src={profileInfo.photo} className='profile__avatar' />
<div className="works__item item-works"> <p className='profile__name'>{profileInfo.fio} {profileInfo.specification}</p>
<div className="item-works__body">
<Link to="/" className="item-works__link">Vuetifyis.com</Link>
<div className="item-works__text">Forked from peluprvi/vuetifyjs.com <br/> Vuetifyjs.com
documentation
</div>
<div className="item-works__mark">Angular</div>
</div>
</div>
<div className="works__item item-works">
<div className="item-works__body">
<Link to="/" className="item-works__link">Vuetifyis.com</Link>
<div className="item-works__text">Forked from peluprvi/vuetifyjs.com <br/> Vuetifyjs.com
documentation
</div>
<div className="item-works__mark">Angular</div>
</div>
</div>
<div className="works__item item-works">
<div className="item-works__body">
<Link to="/" className="item-works__link">Vuetifyis.com</Link>
<div className="item-works__text">Forked from peluprvi/vuetifyjs.com <br/> Vuetifyjs.com
documentation
</div>
<div className="item-works__mark item-works__mark_yellow">Laravel</div>
</div>
</div>
</div> </div>
<Link to={`/ProfileCalendar`}> {!openGit &&
<button className='candidate-sidebar__select'> <button className='profile__git' onClick={()=> setOpenGit(true)}>Git</button>
Отчёты }
</button>
</Link>
</div> </div>
</div> </div>
{!openGit &&
<div className='profile__skills skills__section'>
<div className='profile__sections__head'>
<h3>Основной стек</h3>
<button>Редактировать раздел</button>
</div>
<div className='skills__section__items'>
<div className='skills__section__items__wrapper'>
{profileInfo.skillValues && profileInfo.skillValues.map((skill) => {
return <span key={skill.id} className='skill_item'>{skill.skill.name}</span>
})}
</div>
</div>
</div>
}
{profileInfo.vc_text && !openGit &&
<div className='profile__experience' dangerouslySetInnerHTML={transformHtml(profileInfo.vc_text)}>
</div>
}
{openGit &&
<div className='profile__sectionGit'>
<div className='profile__sections__head'>
<h3>Страница портфолио кода разработчика</h3>
<button>Редактировать раздел</button>
</div>
<div className='profile__sectionGitItems'>
<div className='profile__sectionGitItem gitItem'>
<div className='gitItem__info'>
<div className='gitItem__info__about'>
<img src={gitImgItem} alt='gitImg' />
<div className='gitItem__info__name'>
<h4>cybershop-api</h4>
<p>Реактивная социальная сеть</p>
</div>
</div>
<div className='gitItem__info__specification'>
<span></span>
<p>JavaScript</p>
</div>
</div>
<a className='gitItem__link'>
<img src={rightArrow} alt='arrowRight' />
</a>
</div>
<div className='profile__sectionGitItem gitItem'>
<div className='gitItem__info'>
<div className='gitItem__info__about'>
<img src={gitImgItem} alt='gitImg' />
<div className='gitItem__info__name'>
<h4>cybershop-api</h4>
<p>Реактивная социальная сеть</p>
</div>
</div>
<div className='gitItem__info__specification'>
<span></span>
<p>JavaScript</p>
</div>
</div>
<a className='gitItem__link'>
<img src={rightArrow} alt='arrowRight' />
</a>
</div>
<div className='profile__sectionGitItem gitItem'>
<div className='gitItem__info'>
<div className='gitItem__info__about'>
<img src={gitImgItem} alt='gitImg' />
<div className='gitItem__info__name'>
<h4>cybershop-api</h4>
<p>Реактивная социальная сеть</p>
</div>
</div>
<div className='gitItem__info__specification'>
<span></span>
<p>JavaScript</p>
</div>
</div>
<a className='gitItem__link'>
<img src={rightArrow} alt='arrowRight' />
</a>
</div>
<div className='profile__sectionGitItem gitItem'>
<div className='gitItem__info'>
<div className='gitItem__info__about'>
<img src={gitImgItem} alt='gitImg' />
<div className='gitItem__info__name'>
<h4>cybershop-api</h4>
<p>Реактивная социальная сеть</p>
</div>
</div>
<div className='gitItem__info__specification'>
<span></span>
<p>JavaScript</p>
</div>
</div>
<a className='gitItem__link'>
<img src={rightArrow} alt='arrowRight' />
</a>
</div>
</div>
</div>
}
</div> </div>
<Footer/>
</div> </div>
) )
} }

View File

@ -49,6 +49,7 @@ export const fetchAuth = async ({
response.json().then((resJSON) => { response.json().then((resJSON) => {
localStorage.setItem('auth_token', resJSON.access_token) localStorage.setItem('auth_token', resJSON.access_token)
localStorage.setItem('id', resJSON.id) localStorage.setItem('id', resJSON.id)
localStorage.setItem('cardId', resJSON.card_id)
localStorage.setItem( localStorage.setItem(
'access_token_expired_at', 'access_token_expired_at',
resJSON.access_token_expired_at resJSON.access_token_expired_at