From d1e5c5c364880e6597eb3f8539d2dd6251b0e4bd Mon Sep 17 00:00:00 2001 From: Leonid Chelakhov Date: Tue, 10 Dec 2024 20:30:58 +0300 Subject: [PATCH 1/6] Improvement of General Information Page frontend --- FrontEnd/src/global.css | 8 ++ .../FormComponents/FormComponents.module.css | 59 ++++++++- .../FormComponents/GeneralInfo.jsx | 117 +++++++++++++----- .../SignUp/SignupForm/SignUpFormContent.jsx | 6 +- 4 files changed, 152 insertions(+), 38 deletions(-) diff --git a/FrontEnd/src/global.css b/FrontEnd/src/global.css index 7c91798b9..4e2c4e88b 100644 --- a/FrontEnd/src/global.css +++ b/FrontEnd/src/global.css @@ -159,4 +159,12 @@ --cookie-border-color: #e9e9e9; --cookie-overlay-color: rgba(0, 0, 0, 0.5); --cookie-shadow-color: rgba(0, 0, 0, 0.15); + + /* Tooltip profile page */ + --tooltip-bg-color: #f0f0f0; + --tooltip-text-bg-color: #555; + --tooltip-text-color: #fff; + --tooltip-border-radius: 50%; + --tooltip-text-radius: 5px; + --tooltip-font-color: #000; } diff --git a/FrontEnd/src/pages/ProfilePage/FormComponents/FormComponents.module.css b/FrontEnd/src/pages/ProfilePage/FormComponents/FormComponents.module.css index 1bcaa4f7e..23f127b18 100644 --- a/FrontEnd/src/pages/ProfilePage/FormComponents/FormComponents.module.css +++ b/FrontEnd/src/pages/ProfilePage/FormComponents/FormComponents.module.css @@ -46,6 +46,45 @@ background-color: var(--light-seashell-background); } +.tooltip-inline { + margin-left: 5px; + background-color: var(--tooltip-bg-color); + border-radius: var(--tooltip-border-radius); + width: 16px; + height: 16px; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 12px; + font-weight: bold; + position: relative; + font-family: 'Geologica', sans-serif; + color: var(--tooltip-font-color); + } + + .tooltip-inline .tooltip-text { + visibility: hidden; + background-color: var(--tooltip-text-bg-color); + color: var(--tooltip-text-color); + text-align: center; + border-radius: var(--tooltip-text-radius); + padding: 5px; + position: absolute; + bottom: 150%; + left: 50%; + transform: translateX(-50%); + white-space: nowrap; + opacity: 0; + transition: opacity 0.3s; + font-family: 'Geologica', sans-serif; + } + + .tooltip-inline:hover .tooltip-text { + visibility: visible; + opacity: 1; + } + @media only screen and (min-width: 768px) { .form__container { width: 474px; @@ -75,9 +114,27 @@ } +@media only screen and (max-width: 767px) { + .tooltip-inline { + width: 14px; + height: 14px; + font-size: 10px; + } + + .tooltip-inline .tooltip-text { + padding: 4px; + bottom: auto; + top: 100%; + transform: translateX(-50%) translateY(5px); + white-space: normal; + font-size: 10px; + max-width: 150px; + } +} + @media only screen and (min-width: 1200px) { .form__container { width: 636px; margin-left: 180px; } -} \ No newline at end of file +} diff --git a/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx b/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx index e4a77284e..26a55fdcc 100644 --- a/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx +++ b/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx @@ -1,4 +1,5 @@ import axios from 'axios'; +import { useMemo } from 'react'; import { PropTypes } from 'prop-types'; import { useState, useEffect } from 'react'; import { useContext } from 'react'; @@ -22,7 +23,7 @@ import BanerModeration from './BanerModeration'; import ProfileFormButton from '../UI/ProfileFormButton/ProfileFormButton'; const LABELS = { - name: 'Назва компанії', + name: 'Коротка назва компанії', official_name: 'Юридична назва компанії', edrpou: 'ЄДРПОУ', rnokpp: 'РНОКПП', @@ -41,6 +42,10 @@ const ERRORS = { error: false, message: '', }, + official_name: { + error: false, + message: '', + }, categories: { error: false, message: '', @@ -85,7 +90,7 @@ const GeneralInfo = (props) => { const { setFormIsDirty } = useContext(DirtyFormContext); - const fields = { + const fields = useMemo(() => ({ name: { defaultValue: mainProfile?.name }, official_name: { defaultValue: mainProfile?.official_name ?? null }, edrpou: { defaultValue: mainProfile?.edrpou ?? null }, @@ -98,12 +103,12 @@ const GeneralInfo = (props) => { common_info: { defaultValue: mainProfile?.common_info ?? null }, is_registered: { defaultValue: mainProfile?.is_registered ?? null }, is_startup: { defaultValue: mainProfile?.is_startup ?? null }, - }; + }), [mainProfile]); useEffect(() => { const isDirty = checkFormIsDirty(fields, null, profile); setFormIsDirty(isDirty); - }, [mainProfile, profile]); + }, [mainProfile, profile, fields, setFormIsDirty]); const checkRequiredFields = () => { let isValid = true; @@ -161,31 +166,54 @@ const GeneralInfo = (props) => { const onUpdateField = (e) => { const { value: fieldValue, name: fieldName } = e.target; - const symbolCount = fieldValue.replace(/[\s]/g, '')?.length; - setFormStateErr({ - ...formStateErr, + const symbolCount = fieldValue.replace(/[\s]/g, '').length; + const fieldValidationConfig = { + name: { + minLength: 2, + maxLength: 45, + errorMessage: 'Введіть від 2 до 45 символів.', + }, + official_name: { + minLength: 2, + maxLength: 200, + errorMessage: 'Введіть від 2 до 200 символів.', + }, + }; + setFormStateErr((prev) => ({ + ...prev, [fieldName]: { error: false, message: '' }, - }); - if (fieldName === 'name' && symbolCount < 2) { - setFormStateErr({ - ...formStateErr, - [fieldName]: { error: true, message: 'Введіть від 2 до 100 символів' }, - }); - } - if (fieldName === 'official_name' && symbolCount !== 0 && symbolCount < 2) { - setFormStateErr({ - ...formStateErr, - [fieldName]: { error: true, message: 'Введіть від 2 до 200 символів' }, - }); + })); + if (fieldValidationConfig[fieldName]) { + const { minLength, errorMessage } = fieldValidationConfig[fieldName]; + if (symbolCount !== 0 && symbolCount < minLength) { + setFormStateErr((prev) => ({ + ...prev, + [fieldName]: { + error: true, + message: errorMessage, + }, + })); + } } setProfile((prevState) => { return { ...prevState, [fieldName]: fieldValue }; }); }; + const onBlurHandler = (e) => { const { value: rawFieldValue, name: fieldName } = e.target; const fieldValue = rawFieldValue.replace(/\s{2,}/g, ' ').trim(); + const requiredFields = ['official_name', 'name']; + if (requiredFields.includes(fieldName) && !fieldValue) { + setFormStateErr((prev) => ({ + ...prev, + [fieldName]: { + error: true, + message: 'Це поле є обов’язковим для заповнення.', + }, + })); + } setProfile((prevState) => { return { ...prevState, [fieldName]: fieldValue }; }); @@ -387,19 +415,27 @@ const GeneralInfo = (props) => { toast.error( 'Зміни не можуть бути збережені, перевірте правильність заповнення полів' ); - } else { - const data = defineChanges(fields, profile, null); - try { - const response = await axios.patch( - `${process.env.REACT_APP_BASE_API_URL}/api/profiles/${user.profile_id}`, + return; + } + if (profile.name?.length > 45) { + toast.error('Назва компанії не повинна перевищувати 45 символів.'); + return; + } + if (!profile.official_name?.trim()) { + toast.error('Юридична назва компанії є обов’язковою.'); + return; + } + const data = defineChanges(fields, profile, null); + try { + const response = await axios.patch( + `${process.env.REACT_APP_BASE_API_URL}/api/profiles/${user.profile_id}`, data.profileChanges - ); - profileMutate(response.data); - toast.success('Зміни успішно збережено'); - setFormIsDirty(false); - } catch (error) { - handleError(error); - } + ); + profileMutate(response.data); + toast.success('Зміни успішно збережено'); + setFormIsDirty(false); + } catch (error) { + handleError(error); } }; @@ -415,11 +451,22 @@ const GeneralInfo = (props) => { noValidate >
-
+
+
+ {LABELS.name} +
+ ? + + Назва буде використовуватися на картці компанії + +
+ + } updateHandler={onUpdateField} onBlur={onBlurHandler} error={ @@ -429,8 +476,9 @@ const GeneralInfo = (props) => { } requiredField={true} value={profile.name} - maxLength={100} + maxLength={45} /> +
{ ? formStateErr['official_name']['message'] : null } + requiredField={true} maxLength={200} />
diff --git a/FrontEnd/src/pages/SignUp/SignupForm/SignUpFormContent.jsx b/FrontEnd/src/pages/SignUp/SignupForm/SignUpFormContent.jsx index 60041f01e..71bfc238d 100644 --- a/FrontEnd/src/pages/SignUp/SignupForm/SignUpFormContent.jsx +++ b/FrontEnd/src/pages/SignUp/SignupForm/SignUpFormContent.jsx @@ -37,7 +37,7 @@ export function SignUpFormContentComponent(props) { password: 'Пароль не відповідає вимогам', confirmPassword: 'Паролі не співпадають. Будь ласка, введіть однакові паролі в обидва поля', nameSurnameFieldLength: 'Введіть від 2 до 50 символів', - companyFieldLength: 'Введіть від 2 до 100 символів', + companyFieldLength: 'Введіть від 2 до 45 символів', notAllowedSymbols: 'Поле містить недопустимі символи та/або цифри', maxLength: 'Кількість символів перевищує максимально допустиму (50 символів)', }; @@ -202,8 +202,8 @@ export function SignUpFormContentComponent(props) { message: errorMessageTemplates.companyFieldLength, }, }} - maxLength={100} - tooltip="Назва повинна містити від 2 до 100 символів" + maxLength={45} + tooltip="Назва повинна містити від 2 до 45 символів" tooltipTrigger="focus" error={errors.companyName} onBlur={() => { From 74f46feabdf10570115c4e323949a07233b311f5 Mon Sep 17 00:00:00 2001 From: Leonid Chelakhov Date: Tue, 10 Dec 2024 20:55:00 +0300 Subject: [PATCH 2/6] Changed bg color tooltip --- FrontEnd/src/global.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FrontEnd/src/global.css b/FrontEnd/src/global.css index 4e2c4e88b..7a5412ef6 100644 --- a/FrontEnd/src/global.css +++ b/FrontEnd/src/global.css @@ -162,7 +162,7 @@ /* Tooltip profile page */ --tooltip-bg-color: #f0f0f0; - --tooltip-text-bg-color: #555; + --tooltip-text-bg-color: #000000; --tooltip-text-color: #fff; --tooltip-border-radius: 50%; --tooltip-text-radius: 5px; From c431e5e858125ea3dbe02541018b00f53cae4093 Mon Sep 17 00:00:00 2001 From: Leonid Chelakhov Date: Wed, 11 Dec 2024 13:10:27 +0300 Subject: [PATCH 3/6] added ant designs tooltip --- FrontEnd/src/global.css | 5 +- .../FormComponents/FormComponents.module.css | 46 +------------------ .../FormComponents/GeneralInfo.jsx | 19 ++++---- 3 files changed, 11 insertions(+), 59 deletions(-) diff --git a/FrontEnd/src/global.css b/FrontEnd/src/global.css index 7a5412ef6..e2f4be0ba 100644 --- a/FrontEnd/src/global.css +++ b/FrontEnd/src/global.css @@ -162,9 +162,6 @@ /* Tooltip profile page */ --tooltip-bg-color: #f0f0f0; - --tooltip-text-bg-color: #000000; - --tooltip-text-color: #fff; --tooltip-border-radius: 50%; - --tooltip-text-radius: 5px; - --tooltip-font-color: #000; + --tooltip-font-color: #292E32; } diff --git a/FrontEnd/src/pages/ProfilePage/FormComponents/FormComponents.module.css b/FrontEnd/src/pages/ProfilePage/FormComponents/FormComponents.module.css index 23f127b18..cb039489a 100644 --- a/FrontEnd/src/pages/ProfilePage/FormComponents/FormComponents.module.css +++ b/FrontEnd/src/pages/ProfilePage/FormComponents/FormComponents.module.css @@ -46,7 +46,7 @@ background-color: var(--light-seashell-background); } -.tooltip-inline { +.tooltip-icon { margin-left: 5px; background-color: var(--tooltip-bg-color); border-radius: var(--tooltip-border-radius); @@ -58,32 +58,9 @@ cursor: pointer; font-size: 12px; font-weight: bold; - position: relative; font-family: 'Geologica', sans-serif; color: var(--tooltip-font-color); } - - .tooltip-inline .tooltip-text { - visibility: hidden; - background-color: var(--tooltip-text-bg-color); - color: var(--tooltip-text-color); - text-align: center; - border-radius: var(--tooltip-text-radius); - padding: 5px; - position: absolute; - bottom: 150%; - left: 50%; - transform: translateX(-50%); - white-space: nowrap; - opacity: 0; - transition: opacity 0.3s; - font-family: 'Geologica', sans-serif; - } - - .tooltip-inline:hover .tooltip-text { - visibility: visible; - opacity: 1; - } @media only screen and (min-width: 768px) { .form__container { @@ -111,27 +88,8 @@ background-color: var(--light-seashell-background); display: inherit; } - } - -@media only screen and (max-width: 767px) { - .tooltip-inline { - width: 14px; - height: 14px; - font-size: 10px; - } - - .tooltip-inline .tooltip-text { - padding: 4px; - bottom: auto; - top: 100%; - transform: translateX(-50%) translateY(5px); - white-space: normal; - font-size: 10px; - max-width: 150px; - } -} - + @media only screen and (min-width: 1200px) { .form__container { width: 636px; diff --git a/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx b/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx index 26a55fdcc..a19196883 100644 --- a/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx +++ b/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx @@ -1,9 +1,9 @@ import axios from 'axios'; -import { useMemo } from 'react'; import { PropTypes } from 'prop-types'; import { useState, useEffect } from 'react'; import { useContext } from 'react'; import { toast } from 'react-toastify'; +import { Tooltip } from 'antd'; import useSWR from 'swr'; import { useAuth, useProfile } from '../../../hooks'; import checkFormIsDirty from '../../../utils/checkFormIsDirty'; @@ -90,7 +90,7 @@ const GeneralInfo = (props) => { const { setFormIsDirty } = useContext(DirtyFormContext); - const fields = useMemo(() => ({ + const fields = { name: { defaultValue: mainProfile?.name }, official_name: { defaultValue: mainProfile?.official_name ?? null }, edrpou: { defaultValue: mainProfile?.edrpou ?? null }, @@ -103,12 +103,12 @@ const GeneralInfo = (props) => { common_info: { defaultValue: mainProfile?.common_info ?? null }, is_registered: { defaultValue: mainProfile?.is_registered ?? null }, is_startup: { defaultValue: mainProfile?.is_startup ?? null }, - }), [mainProfile]); + }; useEffect(() => { const isDirty = checkFormIsDirty(fields, null, profile); setFormIsDirty(isDirty); - }, [mainProfile, profile, fields, setFormIsDirty]); + }, [mainProfile, profile]); const checkRequiredFields = () => { let isValid = true; @@ -453,18 +453,15 @@ const GeneralInfo = (props) => {
- {LABELS.name} -
- ? - - Назва буде використовуватися на картці компанії - -
+ + ? + } updateHandler={onUpdateField} From 060ebb381839419a1fd99c571d98832109ca7ec9 Mon Sep 17 00:00:00 2001 From: Leonid Chelakhov Date: Sat, 14 Dec 2024 12:13:56 +0300 Subject: [PATCH 4/6] Improved erorrs messages --- .../FormFields/MultipleSelectChip.module.css | 2 +- .../FormComponents/GeneralInfo.jsx | 40 ++++++++++++------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/FrontEnd/src/pages/ProfilePage/FormComponents/FormFields/MultipleSelectChip.module.css b/FrontEnd/src/pages/ProfilePage/FormComponents/FormFields/MultipleSelectChip.module.css index fb26c5ec2..f99a43c25 100644 --- a/FrontEnd/src/pages/ProfilePage/FormComponents/FormFields/MultipleSelectChip.module.css +++ b/FrontEnd/src/pages/ProfilePage/FormComponents/FormFields/MultipleSelectChip.module.css @@ -47,7 +47,7 @@ flex: 1 0 0; color: var(--red-red-100, #F34444); font-family: var(--font-error); - font-size: 14px; + font-size: 12px; font-style: normal; font-weight: 400; line-height: 22px; diff --git a/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx b/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx index a19196883..baf1f5064 100644 --- a/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx +++ b/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx @@ -114,21 +114,31 @@ const GeneralInfo = (props) => { let isValid = true; const newFormState = {}; for (const key in profile) { - if ( - key in ERRORS && - (!profile[key] || - (Array.isArray(profile[key]) && profile[key]?.length === 0)) - ) { - isValid = false; - newFormState[key] = { - error: true, - message: 'Обов’язкове поле', - }; - } else { - newFormState[key] = { - error: false, - message: '', - }; + if (key in ERRORS) { + if (!profile[key] || (Array.isArray(profile[key]) && profile[key]?.length === 0)) { + isValid = false; + newFormState[key] = { + error: true, + message: 'Це поле є обов’язковим для заповнення.', + }; + } else if (key === 'name' && profile[key].length < 2) { + isValid = false; + newFormState[key] = { + error: true, + message: 'Введіть від 2 до 45 символів.', + }; + } else if (key === 'official_name' && profile[key].length < 2) { + isValid = false; + newFormState[key] = { + error: true, + message: 'Введіть від 2 до 200 символів.', + }; + } else { + newFormState[key] = { + error: false, + message: '', + }; + } } } setFormStateErr({ ...formStateErr, ...newFormState }); From cb1e3c5cc694ab8a4ed3676763fbbad21ff5a030 Mon Sep 17 00:00:00 2001 From: Leonid Chelakhov Date: Sat, 14 Dec 2024 16:39:40 +0300 Subject: [PATCH 5/6] deleted unnecessary code --- .../FormComponents/GeneralInfo.jsx | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx b/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx index baf1f5064..eaa71656f 100644 --- a/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx +++ b/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx @@ -425,30 +425,22 @@ const GeneralInfo = (props) => { toast.error( 'Зміни не можуть бути збережені, перевірте правильність заповнення полів' ); - return; - } - if (profile.name?.length > 45) { - toast.error('Назва компанії не повинна перевищувати 45 символів.'); - return; - } - if (!profile.official_name?.trim()) { - toast.error('Юридична назва компанії є обов’язковою.'); - return; - } - const data = defineChanges(fields, profile, null); - try { - const response = await axios.patch( - `${process.env.REACT_APP_BASE_API_URL}/api/profiles/${user.profile_id}`, + } else { + const data = defineChanges(fields, profile, null); + try { + const response = await axios.patch( + `${process.env.REACT_APP_BASE_API_URL}/api/profiles/${user.profile_id}`, data.profileChanges - ); - profileMutate(response.data); - toast.success('Зміни успішно збережено'); - setFormIsDirty(false); - } catch (error) { - handleError(error); + ); + profileMutate(response.data); + toast.success('Зміни успішно збережено'); + setFormIsDirty(false); + } catch (error) { + handleError(error); + } } }; - + return (

Загальна інформація

From f39733bae19506996dcb7279d08b693c292739de Mon Sep 17 00:00:00 2001 From: Leonid Chelakhov Date: Sat, 14 Dec 2024 16:48:27 +0300 Subject: [PATCH 6/6] deleted trailing-spaces --- FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx b/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx index eaa71656f..d1cbb5e7e 100644 --- a/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx +++ b/FrontEnd/src/pages/ProfilePage/FormComponents/GeneralInfo.jsx @@ -440,7 +440,6 @@ const GeneralInfo = (props) => { } } }; - return (

Загальна інформація