diff --git a/src/constants/translations/en/edit-profile.json b/src/constants/translations/en/edit-profile.json index ad6f56dec..3b5a59d5f 100644 --- a/src/constants/translations/en/edit-profile.json +++ b/src/constants/translations/en/edit-profile.json @@ -41,8 +41,8 @@ "cancelBtn": "Cancel", "submitBtn": "Delete" }, - "mainStudyCategory": "Main Study Category", - "subject": "Subject", + "mainStudyCategory": "Main study category", + "subject": "The category subject(s)", "proficiencyLevels": "Proficiency Levels", "editCategoryBtn": "Edit category", "deleteCategoryBtnDisabledTooltip": "You have opened cooperation(s)/offers with this category.\nPlease close it before deactivation", diff --git a/src/constants/translations/ua/edit-profile.json b/src/constants/translations/ua/edit-profile.json index a800c8cfe..f9aa38909 100644 --- a/src/constants/translations/ua/edit-profile.json +++ b/src/constants/translations/ua/edit-profile.json @@ -42,7 +42,7 @@ "submitBtn": "Видалити" }, "mainStudyCategory": "Основна навчальна категорія", - "subject": "Предмет", + "subject": "Предмет(и) категорії", "proficiencyLevels": "Рівні кваліфікації", "editCategoryBtn": "Редагувати", "deleteCategoryBtnDisabledTooltip": "Ви розпочали співпрацю(і)/пропозицію. з цією категорією.\nБудь ласка, закрийте її перед деактивацією", diff --git a/src/containers/edit-profile/professional-info-tab/professional-category/ProfessionalCategory.styles.ts b/src/containers/edit-profile/professional-info-tab/professional-category/ProfessionalCategory.styles.ts index a2a5be377..53a3ef421 100644 --- a/src/containers/edit-profile/professional-info-tab/professional-category/ProfessionalCategory.styles.ts +++ b/src/containers/edit-profile/professional-info-tab/professional-category/ProfessionalCategory.styles.ts @@ -1,5 +1,6 @@ import { TypographyVariantEnum } from '~/types' import palette from '~/styles/app-theme/app.pallete' +import { alpha, darken } from '@mui/material' const { Body2, Overline, Caption } = TypographyVariantEnum @@ -8,10 +9,9 @@ export const styles = { border: `1px solid ${palette.primary[300]}`, display: 'flex', flexDirection: 'column', - gap: 3, + gap: 2.5, borderRadius: 1, - px: 2, - py: 4 + p: 3 }, createBtnContainer: { my: 3 @@ -33,52 +33,48 @@ export const styles = { }, deleteButton: { svg: { - fill: palette.primary[700] + fill: palette.primary[900], + width: '20px', + height: '20px' }, '&:disabled': { svg: { fill: palette.primary[200] } } } }, - cards: { - display: 'flex', - flexDirection: 'column', - gap: 3 - }, - divider: { - my: 1 - }, - subjects: { - root: { - display: 'flex', - flexDirection: 'column', - columnGap: 3, - rowGap: 2 + subjectChipLabel: (color: string) => ({ + typography: Overline, + fontWeight: '500', + lineHeight: 1.2, + color: darken(color, 0.6) + }), + subjectChip: (color: string) => ({ + backgroundColor: alpha(color, 0.6) + }), + categoryIcon: (color: string) => ({ + width: '24px', + height: '24px', + color: color + }), + description: { + grid: { + m: 0, + display: 'grid', + gridTemplateColumns: { xs: '1fr', md: 'auto 1fr' }, + gridTemplateRows: 'minmax(24px, auto)', + rowGap: 2, + columnGap: 3 }, - item: { - display: 'flex', - flexDirection: 'column', - columnGap: 1, - rowGap: 2 - } - }, - card: { - root: { - m: 0 + label: { + typography: Body2, + color: palette.basic.blueGray, + lineHeight: '24px', + alignSelf: 'start' }, - item: { + value: { + typography: Body2, display: 'flex', - flexDirection: { xs: 'column', md: 'row' }, - m: 0, - columnGap: 3, - rowGap: 1, - label: { - flex: 1, - typography: Overline, - lineHeight: '20px' - }, - value: { - flex: 2.5, - typography: Body2 - } + alignItems: 'center', + flexWrap: 'wrap', + gap: '12px' } } } diff --git a/src/containers/edit-profile/professional-info-tab/professional-category/ProfessionalCategory.tsx b/src/containers/edit-profile/professional-info-tab/professional-category/ProfessionalCategory.tsx index 161d5a1d0..280e15378 100644 --- a/src/containers/edit-profile/professional-info-tab/professional-category/ProfessionalCategory.tsx +++ b/src/containers/edit-profile/professional-info-tab/professional-category/ProfessionalCategory.tsx @@ -1,21 +1,24 @@ import Box from '@mui/material/Box' -import Divider from '@mui/material/Divider' import IconButton from '@mui/material/IconButton' import Typography from '@mui/material/Typography' import Tooltip from '@mui/material/Tooltip' -import { FC } from 'react' +import DeleteIcon from '@mui/icons-material/Delete' +import { FC, ReactNode } from 'react' import { useTranslation } from 'react-i18next' import AppButton from '~/components/app-button/AppButton' +import AppChip from '~/components/app-chip/AppChip' +import { getCategoryIcon } from '~/services/category-icon-service' import { ButtonVariantEnum, ComponentEnum, PositionEnum, UserMainSubject, - OpenProfessionalCategoryModalHandler + OpenProfessionalCategoryModalHandler, + SizeEnum } from '~/types' -import DeleteIcon from '@mui/icons-material/Delete' import useConfirm from '~/hooks/use-confirm' import { styles } from '~/containers/edit-profile/professional-info-tab/professional-category/ProfessionalCategory.styles' +import { getValidatedHexColor } from '~/utils/get-validated-hex-color' interface ProfessionalCategoryProps { item: UserMainSubject @@ -51,31 +54,40 @@ const ProfessionalCategory: FC = ({ }) } - const CardItem = ({ + const handleEditButtonClick = () => { + openProfessionalCategoryModal(item) + } + + const DescriptionItem = ({ label, children }: { label: string - children: string + children: ReactNode }) => { return ( - - + <> + {label}: - + {children} - + ) } + const categoryColor = getValidatedHexColor(item.category.appearance.color) + const CategoryIcon = getCategoryIcon(item.category.appearance.icon) + const Subjects = item.subjects.map((subject) => ( - - - {subject.name} - - + + {subject.name} + )) return ( @@ -83,8 +95,9 @@ const ProfessionalCategory: FC = ({ openProfessionalCategoryModal(item)} - variant={ButtonVariantEnum.ContainedLight} + onClick={handleEditButtonClick} + size={SizeEnum.Medium} + variant={ButtonVariantEnum.Tonal} > {t('editProfilePage.profile.professionalTab.editCategoryBtn')} @@ -100,26 +113,28 @@ const ProfessionalCategory: FC = ({ ) } > - - - - - + + + - - + + {item.category.name} - - - {Subjects} + + + {Subjects} + ) diff --git a/src/types/edit-profile/interfaces/editProfile.interfaces.ts b/src/types/edit-profile/interfaces/editProfile.interfaces.ts index c36cc3ef8..1a03f5eff 100644 --- a/src/types/edit-profile/interfaces/editProfile.interfaces.ts +++ b/src/types/edit-profile/interfaces/editProfile.interfaces.ts @@ -1,5 +1,5 @@ import { - CategoryNameInterface, + CategoryInterface, SubjectNameInterface, UpdatedPhoto } from '~/types/common/common.index' @@ -16,7 +16,7 @@ export interface EditProfileForm } export interface ProfessionalCategory { - category: CategoryNameInterface + category: Pick subjects: SubjectNameInterface[] } diff --git a/src/utils/get-validated-hex-color.tsx b/src/utils/get-validated-hex-color.tsx new file mode 100644 index 000000000..7c8e16ac1 --- /dev/null +++ b/src/utils/get-validated-hex-color.tsx @@ -0,0 +1,11 @@ +import palette from '~/styles/app-theme/app.pallete' + +const HEX_COLOR_PATTERN = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i + +export const getValidatedHexColor = ( + color: string, + fallbackColor: string = palette.success[500] +): string => { + const isValidHex = HEX_COLOR_PATTERN.test(color) + return isValidHex ? color : fallbackColor +} diff --git a/tests/unit/containers/edit-profile/professional-info-tab/professional-category/ProfessionalCategory.spec.jsx b/tests/unit/containers/edit-profile/professional-info-tab/professional-category/ProfessionalCategory.spec.jsx index fb4882ce9..ee7771fb5 100644 --- a/tests/unit/containers/edit-profile/professional-info-tab/professional-category/ProfessionalCategory.spec.jsx +++ b/tests/unit/containers/edit-profile/professional-info-tab/professional-category/ProfessionalCategory.spec.jsx @@ -7,14 +7,28 @@ const mockedHandleDelete = vi.fn() const categoryWithSubjects = { _id: '648850c4fdc2d1a130c24aea', - category: { _id: '64884f21fdc2d1a130c24ac0', name: 'Music' }, + category: { + _id: '64884f21fdc2d1a130c24ac0', + name: 'Music', + appearance: { + color: '#FFD700', + icon: 'MusicNoteIcon' + } + }, subjects: [{ _id: '64885108fdc2d1a130c24af9', name: 'Guitar' }], isDeletionBlocked: false } const blockedCategory = { _id: '648850c4fdc2d1a130c24aea', - category: { _id: '64884f21fdc2d1a130c24ac0', name: 'Music' }, + category: { + _id: '64884f21fdc2d1a130c24ac0', + name: 'Music', + appearance: { + color: '#FFD700', + icon: 'MusicNoteIcon' + } + }, subjects: [{ _id: '64885108fdc2d1a130c24af9', name: 'Guitar' }], isDeletionBlocked: true } diff --git a/tests/unit/utils/get-validated-hex-color.spec.js b/tests/unit/utils/get-validated-hex-color.spec.js new file mode 100644 index 000000000..041b00289 --- /dev/null +++ b/tests/unit/utils/get-validated-hex-color.spec.js @@ -0,0 +1,32 @@ +import { getValidatedHexColor } from '~/utils/get-validated-hex-color' +import palette from '~/styles/app-theme/app.pallete' + +describe('getValidatedHexColor', () => { + it('should return the color if it is a valid 6-digit hex', () => { + const color = '#FFFFFF' + expect(getValidatedHexColor(color)).toBe(color) + }) + + it('should return the color if it is a valid 3-digit hex', () => { + const color = '#FFF' + expect(getValidatedHexColor(color)).toBe(color) + }) + + it('should return the fallback color if the input is not a valid hex color', () => { + const invalidColor = '#WWWWWW' + expect(getValidatedHexColor(invalidColor)).toBe(palette.success[500]) + }) + + it('should return the fallback color if the input is an empty string', () => { + const invalidColor = '' + expect(getValidatedHexColor(invalidColor)).toBe(palette.success[500]) + }) + + it('should return the custom fallback color if the input is not a valid hex color', () => { + const invalidColor = '#WWWWWW' + const customFallbackColor = '#FF5733' + expect(getValidatedHexColor(invalidColor, customFallbackColor)).toBe( + customFallbackColor + ) + }) +})