Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#804 [Admin] [Frontend] Admin profile management #815

Merged
merged 24 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions FrontEnd/src/components/BreadCrumbs/BreadCrumbs.jsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,37 @@
import css from './BreadCrumbs.module.css';
import { Link, useNavigate } from 'react-router-dom';
import { PropTypes } from 'prop-types';

import { useAuth } from '../../hooks';

import css from './BreadCrumbs.module.css';

const BreadCrumbs = ({ currentPage }) => {
const navigate = useNavigate();

const { isStaff } = useAuth();
const goBackHandler = () => {
navigate(-1);
};
return (
<div className={css['content']}>
<button
className={css['goback__button']}
type="button"
onClick={goBackHandler}
>
<i className={css['left']}></i>Назад
</button>
<Link className={css['main-page__button']} to="/">
Головна
</Link>
{ !isStaff ? (
<button
className={css['goback__button']}
type="button"
onClick={goBackHandler}
>
<i className={css['left']}></i>Назад
</button>
) : null
}
{!isStaff ? (
<Link className={css['main-page__button']} to="/">
Головна
</Link>
) : (
<Link className={css['main-page__button']} to="/customadmin">
Панель адміністратора
</Link>)
}
<i className={css['right']}></i>
<div className={css['current-page__button']}>{currentPage}</div>
</div>
Expand Down
16 changes: 11 additions & 5 deletions FrontEnd/src/components/Header/Header.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import css from './Header.module.css';
import { useLocation } from 'react-router-dom';

import HeaderMenu from './Menu/HeaderMenu';
import Navbar from './Navbar/Navbar';

import css from './Header.module.css';

function Header(props) {
const { pathname } = useLocation();
const hideMenu = pathname === '/login' || pathname === '/sign-up' || pathname.includes('/customadmin');

return (
<header>
<div className={css['header-content']}>
<Navbar isAuthorized={props.isAuthorized} page={props.page}></Navbar>
<Navbar isAuthorized={props.isAuthorized} page={pathname}></Navbar>

Check warning on line 15 in FrontEnd/src/components/Header/Header.jsx

View workflow job for this annotation

GitHub Actions / Linting

'isAuthorized' is missing in props validation
<div className={css['header-divider']}></div>
{props.page === 'login' || props.page === 'registration' ? null : (
<HeaderMenu />
)}
{hideMenu ? null : (
<HeaderMenu />
)}
<div className={css['header-divider']}></div>
</div>
</header>
Expand Down
4 changes: 2 additions & 2 deletions FrontEnd/src/components/Header/Navbar/Buttons.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import css from './Buttons.module.css';
import { Link } from 'react-router-dom';

function Buttons() {
function Buttons({adminPage}) {

Check warning on line 4 in FrontEnd/src/components/Header/Navbar/Buttons.jsx

View workflow job for this annotation

GitHub Actions / Linting

'adminPage' is missing in props validation
return (
<div className={css['header-buttons-section']}>
<Link className={css['header-login__button']} to="/login">Увійти</Link>
<Link className={css['header-register__button']} to="/sign-up">Зареєструватися</Link>
{!adminPage && <Link className={css['header-register__button']} to="/sign-up">Зареєструватися</Link>}
</div>
);
}
Expand Down
18 changes: 12 additions & 6 deletions FrontEnd/src/components/Header/Navbar/Navbar.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import css from './Navbar.module.css';
import { Link } from 'react-router-dom';

import { useAuth } from '../../../hooks';

import Menu from './Menu';
import SearchBox from './SearchBox';
import Profile from './Profile';
import Buttons from './Buttons';
import { Link } from 'react-router-dom';

function Navbar(props) {
import css from './Navbar.module.css';

function Navbar (props) {
const { isStaff} = useAuth();
const hideMenu = props.page === '/login' || props.page === '/sign-up' || props.page.includes('/customadmin');
return (
<div className={css['navbar-content']}>
<div className={css['navbar-logo__text']}>
<Link to="/">
<Link to="/" target={isStaff ? '_blank' : null}>
<img
className={css['navbar-main-logo']}
src={`${process.env.REACT_APP_PUBLIC_URL}/craftMerge-logo.svg`}
Expand All @@ -18,13 +24,13 @@ function Navbar(props) {
</Link>
</div>
<div className={css['navbar-utility-bar']}>
{props.page === 'login' || props.page === 'registration' ? null : (
{ hideMenu ? null : (
<>
<Menu />
<SearchBox></SearchBox>
</>
)}
{props.isAuthorized === true ? <Profile /> : <Buttons />}
{props.isAuthorized === true ? <Profile /> : <Buttons adminPage={props.page.includes('/customadmin')} />}
</div>
</div>
);
Expand Down
5 changes: 3 additions & 2 deletions FrontEnd/src/components/Header/Navbar/Navbar.module.css
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
.navbar-content {
display: flex;
width: 70%;
padding: 8px 104px;
align-items: center;
justify-content: space-between;
}

.navbar-logo__text{
.navbar-logo__text {
display: flex;
}

.navbar-main-logo{
.navbar-main-logo {
display: flex;
width: 199px;
height: 24px;
Expand Down
12 changes: 6 additions & 6 deletions FrontEnd/src/components/Header/Navbar/Profile.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import css from './Profile.module.css';


function Profile() {
const { user, isAuth, logout } = useAuth();
const { user, isAuth, logout, isStaff } = useAuth();
const navigate = useNavigate();

const navigateToProfile = () => {
Expand All @@ -31,12 +31,12 @@ function Profile() {
className={css['header-profile__avatar']}
src={`${process.env.REACT_APP_PUBLIC_URL}/img/Avatar.png`}
alt="Avatar"
onClick={navigateToProfile}
onClick={!isStaff ? navigateToProfile : null}
/>
<DropdownMenu toggleText="Профіль">
<Link to="/profile/user-info">Профіль</Link>
<button onClick={performLogout}>Вихід</button>
</DropdownMenu>
<DropdownMenu toggleText={isStaff ? 'Адміністратор' : 'Профіль'}>
<Link to={isStaff ? '/customadmin/admin-profile/admin-info' : '/profile/user-info'}>Профіль</Link>
<button onClick={performLogout}>Вихід</button>
</DropdownMenu>
</div>
);
}
Expand Down
1 change: 1 addition & 0 deletions FrontEnd/src/components/Header/Navbar/SearchBox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function SearchBox() {
<div className={css['header-search-form']}>
<input
className={css['header-search-form__input']}
id="search_box"
value={searchTerm}
type="text"
placeholder="Пошук"
Expand Down
17 changes: 17 additions & 0 deletions FrontEnd/src/components/MiniComponents/AdminSubmitButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import classes from './AdminSubmitButton.module.css';

const AdminSubmitButton = ({ disabled }) => {
return (
<div className={classes['admin-submit__container']}>
<button
className={classes['admin-submit__button']}
type="submit"
disabled={disabled}
>
Зберегти зміни
</button>
</div>
);
};

export default AdminSubmitButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.admin-submit__container {
margin-top: 16px;
}

.admin-submit__button {
display: block;
width: 100%;
border-radius: 4px;
border: 1px solid var(--main-button-color);
background: var(--main-button-color);
box-shadow: 0px 2px 0px 0px rgba(0, 0, 0, 0.04);
color: var(--light-button-text-color);
font-feature-settings: 'calt' off;
font-style: normal;
padding: 5px 15px 5px 15px;
font-family: var(--font-main);
font-size: 16px;
font-weight: 600;
line-height: 20px;
letter-spacing: -0.01em;
text-align: center;
cursor: pointer;
}

.admin-submit__button:active {
transform: translateY(2px);
}
13 changes: 8 additions & 5 deletions FrontEnd/src/pages/AdminPage/AdminGlobal.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
:root {
--font__admin-panel: "Inter", sans-serif;
--block-size__admin-panel: 1500px;
--menu-size__admin-panel: 120px;
--block-size__admin-panel: 1512px;
--menu-size__admin-panel: 222px;
--table-size__admin-panel: 1000px;
--font-color__admin-panel: #292E32;
--notification-text-color__admin-panel: #ff4d4f;
--background__admin-panel: #F1FFF7;
--background-butons__admin-panel: #0B6C61;
--button-color__admin-panel: #0B6C61;
}
--background-input__admin-panel: #FFF;
--border-input__admin-panel: #d9d9d9;
--background-butons__admin-panel: #1F9A7C;
--button-color__admin-panel: #1F9A7C;
}
139 changes: 139 additions & 0 deletions FrontEnd/src/pages/AdminPage/AdminProfile/AdminInfo.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import axios from 'axios';
import { toast } from 'react-toastify';
import { useForm } from 'react-hook-form';

import AdminSubmitButton from '../../../components/MiniComponents/AdminSubmitButton';

import classes from './AdminInfo.module.css';

const AdminInfo = ({ user, mutate }) => {
const {
register,
handleSubmit,
setValue,
getValues,
formState: { errors, isDirty },
} = useForm({
defaultValues: {
'name': user.name,
'surname': user.surname,
},
});

const onSubmit = (data) => {
axios.patch(`${process.env.REACT_APP_BASE_API_URL}/api/auth/users/me/`, data)
.then(() => {
toast.success('Зміни успішно збережено');
mutate();
})
.catch((error) => {
console.error(
'Помилка:',
error.response ? error.response.data : error.message
);
if (!error.response || error.response.status !== 401) {
toast.error('Не вдалося зберегти зміни, сталася помилка');
}
});
};

const errorMessageTemplates = {
required: 'Обов’язкове поле',
nameSurnameFieldLength: 'Введіть від 2 до 50 символів',
notAllowedSymbols: 'Поле містить недопустимі символи та/або цифри',
maxLength: 'Кількість символів перевищує максимально допустиму (50 символів)',
};

const validateNameSurname = (value) => {
const allowedSymbolsPattern = /^[a-zA-Zа-щюяьА-ЩЮЯЬїЇіІєЄґҐ'\s]+$/;
const letterCount = (value.match(/[a-zA-Zа-щюяьА-ЩЮЯЬїЇіІєЄґҐ]/g) || [])
.length;
if (!allowedSymbolsPattern.test(value)) {
return errorMessageTemplates.notAllowedSymbols;
}
if (letterCount < 2) {
return errorMessageTemplates.nameSurnameFieldLength;
}
return true;
};

const onBlurHandler = (fieldName) => {
let fieldValue = getValues(fieldName);
if (fieldValue !== undefined && fieldValue !== null) {
fieldValue = fieldValue.replace(/\s{2,}/g, ' ').trim();
setValue(fieldName, fieldValue);
}
};

return (
<div className={classes['admin-info-form']}>
<form onSubmit={handleSubmit(onSubmit)}>
<div className={classes['admin-info-form__container']}>
<div className={classes['admin-info-form__column']}>
<div className={classes['admin-info-form__label']}>
<label className={classes['admin-info-form__label--required']} htmlFor="name">
*
</label>
<label className={classes['admin-info-form__label--text']} htmlFor="name">Ім‘я</label>
</div>
<div className={classes['admin-info-form__field']}>
<input
className={classes['admin-info-form__input']}
id="name"
autoComplete="name"
type="text"
placeholder="Ім‘я"
{...register('name', {
required: errorMessageTemplates.required,
validate: validateNameSurname,
maxLength: {
value: 50,
message: errorMessageTemplates.maxLength
},
})}
onBlur={() => onBlurHandler('name')}
/>
</div>
<div className={classes['admin-info-form__error']}>
{errors.name && errors.name.message}
</div>
</div>
<div className={classes['admin-info-form__column']}>
<div className={classes['admin-info-form__label']}>
<label className={classes['admin-info-form__label--required']} htmlFor="surname">
*
</label>
<label className={classes['admin-info-form__label--text']} htmlFor="surname">
Прізвище
</label>
</div>
<div className={classes['admin-info-form__field']}>
<input
className={classes['admin-info-form__input']}
id="surname"
autoComplete="family-name"
type="text"
placeholder="Прізвище"
{...register('surname', {
required: errorMessageTemplates.required,
validate: validateNameSurname,
maxLength: {
value: 50,
message: errorMessageTemplates.maxLength
},
})}
onBlur={() => onBlurHandler('surname')}
/>
</div>
<div className={classes['admin-info-form__error']}>
{errors.surname && errors.surname.message}
</div>
</div>
<AdminSubmitButton disabled={!isDirty}/>
</div>
</form>
</div>
);
};

export default AdminInfo;
Loading
Loading