From 70e4439b6d6523f6bb159f0b0505ce9f642f3bf2 Mon Sep 17 00:00:00 2001 From: Sem Bauke Date: Tue, 8 Apr 2025 10:46:39 +0200 Subject: [PATCH] refactor(profile): update components to use user object instead of individual props (#58680) --- .../show-profile-or-four-oh-four.tsx | 25 +--- .../__snapshots__/profile.test.tsx.snap | 7 +- .../components/profile/components/about.tsx | 40 +++--- .../src/components/profile/components/bio.tsx | 52 ++++---- .../components/profile/components/camper.tsx | 51 ++------ .../profile/components/internet.tsx | 34 ++++-- .../profile/components/portfolio.tsx | 18 ++- client/src/components/profile/profile.tsx | 114 ++---------------- 8 files changed, 117 insertions(+), 224 deletions(-) diff --git a/client/src/client-only-routes/show-profile-or-four-oh-four.tsx b/client/src/client-only-routes/show-profile-or-four-oh-four.tsx index 59c01ace546..278babafdde 100644 --- a/client/src/client-only-routes/show-profile-or-four-oh-four.tsx +++ b/client/src/client-only-routes/show-profile-or-four-oh-four.tsx @@ -7,12 +7,6 @@ import FourOhFour from '../components/FourOhFour'; import Loader from '../components/helpers/loader'; import Profile from '../components/profile/profile'; import { fetchProfileForUser } from '../redux/actions'; - -import { - submitNewAbout, - updateMyPortfolio, - updateMySocials -} from '../redux/settings/actions'; import { usernameSelector, userByNameSelector, @@ -63,23 +57,14 @@ const makeMapStateToProps = const mapDispatchToProps: { fetchProfileForUser: ShowProfileOrFourOhFourProps['fetchProfileForUser']; - submitNewAbout: () => void; - updateMyPortfolio: () => void; - updateMySocials: (formValues: Socials) => void; } = { - fetchProfileForUser, - submitNewAbout, - updateMyPortfolio, - updateMySocials + fetchProfileForUser }; function ShowProfileOrFourOhFour({ requestedUser, maybeUser, fetchProfileForUser, - submitNewAbout, - updateMyPortfolio, - updateMySocials, isSessionUser, showLoading }: ShowProfileOrFourOhFourProps) { @@ -104,13 +89,7 @@ function ShowProfileOrFourOhFour({ ) ) : ( - + ); } diff --git a/client/src/components/profile/__snapshots__/profile.test.tsx.snap b/client/src/components/profile/__snapshots__/profile.test.tsx.snap index 522fc732f30..cdba6c2afb4 100644 --- a/client/src/components/profile/__snapshots__/profile.test.tsx.snap +++ b/client/src/components/profile/__snapshots__/profile.test.tsx.snap @@ -187,17 +187,12 @@ exports[` renders correctly 1`] = ` string -
-
- - -
+ />
diff --git a/client/src/components/profile/components/about.tsx b/client/src/components/profile/components/about.tsx index 953219f683e..d6e53d47510 100644 --- a/client/src/components/profile/components/about.tsx +++ b/client/src/components/profile/components/about.tsx @@ -10,27 +10,32 @@ import type { TFunction } from 'i18next'; import { withTranslation } from 'react-i18next'; import isURL from 'validator/lib/isURL'; +import { connect } from 'react-redux'; import { FullWidthRow } from '../../helpers'; import BlockSaveButton from '../../helpers/form/block-save-button'; import SectionHeader from '../../settings/section-header'; -import type { CamperProps } from './camper'; +import { User } from '../../../redux/prop-types'; +import { submitNewAbout } from '../../../redux/settings/actions'; -type AboutProps = Omit< - CamperProps, - | 'linkedin' - | 'joinDate' - | 'isDonating' - | 'githubProfile' - | 'twitter' - | 'website' - | 'yearsTopContributor' -> & { +type AboutProps = { + user: User; t: TFunction; submitNewAbout: (formValues: FormValues) => void; setIsEditing: (isEditing: boolean) => void; }; -type FormValues = Pick; +type FormValues = { + name: string; + location: string; + picture: string; + about: string; +}; + +const mapDispatchToProps: { + submitNewAbout: () => void; +} = { + submitNewAbout +}; const ShowImageValidationWarning = ({ alertContent @@ -45,14 +50,13 @@ const ShowImageValidationWarning = ({ }; const AboutSettings = ({ + user, t, - name = '', - location = '', - picture = '', - about = '', submitNewAbout, setIsEditing }: AboutProps) => { + const { name = '', location = '', picture = '', about = '' } = user; + const [formValues, setFormValues] = useState({ name, location, @@ -235,4 +239,6 @@ const AboutSettings = ({ AboutSettings.displayName = 'AboutSettings'; -export default withTranslation()(AboutSettings); +export default withTranslation()( + connect(null, mapDispatchToProps)(AboutSettings) +); diff --git a/client/src/components/profile/components/bio.tsx b/client/src/components/profile/components/bio.tsx index 9c142695fad..7cc4d0aeb6d 100644 --- a/client/src/components/profile/components/bio.tsx +++ b/client/src/components/profile/components/bio.tsx @@ -8,25 +8,33 @@ import { import { useTranslation } from 'react-i18next'; import { Button, Spacer } from '@freecodecamp/ui'; import { AvatarRenderer, FullWidthRow } from '../../helpers'; +import { User } from '../../../redux/prop-types'; import { parseDate } from './utils'; import SocialIcons from './social-icons'; -import { type CamperProps } from './camper'; -const Bio = ({ - joinDate, - location, - username, - name, - about, - githubProfile, - linkedin, - twitter, - website, - isDonating, - yearsTopContributor, - picture, - setIsEditing, - isSessionUser -}: CamperProps) => { + +type BioProps = { + user: User; + setIsEditing: (value: boolean) => void; + isSessionUser: boolean; +}; + +const Bio = ({ user, setIsEditing, isSessionUser }: BioProps) => { + const { + joinDate, + location, + username, + name, + about, + githubProfile, + linkedin, + twitter, + website, + isDonating, + yearsTopContributor, + picture, + profileUI: { showAbout, showLocation, showDonation } + } = user; + const { t } = useTranslation(); const isTopContributor = @@ -38,7 +46,7 @@ const Bio = ({
@@ -56,17 +64,17 @@ const Bio = ({ )}
- {name &&

{name}

} + {name && showAbout &&

{name}

} - {about &&

{about}

} + {showAbout &&

{about}

}
- {joinDate && ( + {joinDate && showAbout && (
{parseDate(joinDate, t)}
)} - {location && ( + {location && showLocation && (
{t('profile.from', { location })} diff --git a/client/src/components/profile/components/camper.tsx b/client/src/components/profile/components/camper.tsx index 54b637a8fc6..c03c619e041 100644 --- a/client/src/components/profile/components/camper.tsx +++ b/client/src/components/profile/components/camper.tsx @@ -7,64 +7,35 @@ import SupporterBadgeEmblem from '../../../assets/icons/supporter-badge-emblem'; import TopContibutorBadgeEmblem from '../../../assets/icons/top-contributor-badge-emblem'; import Bio from './bio'; -export type CamperProps = Pick< - User, - | 'about' - | 'githubProfile' - | 'isDonating' - | 'linkedin' - | 'username' - | 'twitter' - | 'yearsTopContributor' - | 'location' - | 'website' - | 'picture' - | 'name' - | 'joinDate' -> & { +export type CamperProps = { + user: User; setIsEditing: (value: boolean) => void; isSessionUser: boolean; }; function Camper({ - name, - username, - location, - picture, - about, - yearsTopContributor, - githubProfile, - isDonating, - joinDate, - linkedin, - twitter, - website, + user, isSessionUser, setIsEditing }: CamperProps): JSX.Element { + const { + isDonating, + yearsTopContributor, + profileUI: { showDonation } + } = user; + const { t } = useTranslation(); const isTopContributor = yearsTopContributor.filter(Boolean).length > 0; return ( <>
- {(isDonating || isTopContributor) && ( + {((isDonating && showDonation) || isTopContributor) && (

{t('profile.badges')}

diff --git a/client/src/components/profile/components/internet.tsx b/client/src/components/profile/components/internet.tsx index 322b8776f55..5220ad42619 100644 --- a/client/src/components/profile/components/internet.tsx +++ b/client/src/components/profile/components/internet.tsx @@ -12,11 +12,14 @@ import { type FormGroupProps } from '@freecodecamp/ui'; +import { connect } from 'react-redux'; import { maybeUrlRE } from '../../../utils'; import { FullWidthRow } from '../../helpers'; import BlockSaveButton from '../../helpers/form/block-save-button'; import SectionHeader from '../../settings/section-header'; +import { User } from '../../../redux/prop-types'; +import { updateMySocials } from '../../../redux/settings/actions'; export interface Socials { githubProfile: string; @@ -25,9 +28,10 @@ export interface Socials { website: string; } -interface InternetProps extends Socials { +interface InternetProps { + user: User; t: TFunction; - updateSocials: (formValues: Socials) => void; + updateMySocials: (formValues: Socials) => void; setIsEditing: (isEditing: boolean) => void; } @@ -40,15 +44,25 @@ function Info({ message }: { message: string }) { return message ? {message} : null; } +const mapDispatchToProps: { + updateMySocials: (formValues: Socials) => void; +} = { + updateMySocials +}; + const InternetSettings = ({ - githubProfile = '', - linkedin = '', - twitter = '', - website = '', + user, t, - updateSocials, + updateMySocials, setIsEditing }: InternetProps) => { + const { + githubProfile = '', + linkedin = '', + twitter = '', + website = '' + } = user; + const [formValues, setFormValues] = useState({ githubProfile, linkedin, @@ -101,7 +115,7 @@ const InternetSettings = ({ e.preventDefault(); if (!isFormPristine() && isFormValid()) { // Only submit the form if is has changed, and if it is valid - updateSocials({ ...formValues }); + updateMySocials({ ...formValues }); } setIsEditing(false); }; @@ -256,4 +270,6 @@ const Check = ({ InternetSettings.displayName = 'InternetSettings'; -export default withTranslation()(InternetSettings); +export default withTranslation()( + connect(null, mapDispatchToProps)(InternetSettings) +); diff --git a/client/src/components/profile/components/portfolio.tsx b/client/src/components/profile/components/portfolio.tsx index 5686a690b19..e4f65d16210 100644 --- a/client/src/components/profile/components/portfolio.tsx +++ b/client/src/components/profile/components/portfolio.tsx @@ -13,6 +13,7 @@ import { } from '@freecodecamp/ui'; import { withTranslation } from 'react-i18next'; import isURL from 'validator/lib/isURL'; +import { connect } from 'react-redux'; import { PortfolioProjectData } from '../../../redux/prop-types'; import { hasProtocolRE } from '../../../utils'; @@ -20,12 +21,13 @@ import { hasProtocolRE } from '../../../utils'; import { FullWidthRow } from '../../helpers'; import BlockSaveButton from '../../helpers/form/block-save-button'; import SectionHeader from '../../settings/section-header'; +import { updateMyPortfolio } from '../../../redux/settings/actions'; type PortfolioProps = { picture?: string; portfolio: PortfolioProjectData[]; t: TFunction; - updatePortfolio: (obj: { portfolio: PortfolioProjectData[] }) => void; + updateMyPortfolio: (obj: { portfolio: PortfolioProjectData[] }) => void; username?: string; setIsEditing: (isEditing: boolean) => void; }; @@ -35,6 +37,12 @@ interface ProfileValidation { message: string; } +const mapDispatchToProps: { + updateMyPortfolio: () => void; +} = { + updateMyPortfolio +}; + function createEmptyPortfolioItem(): PortfolioProjectData { return { id: nanoid(), @@ -54,7 +62,7 @@ const PortfolioSettings = (props: PortfolioProps) => { t, portfolio: initialPortfolio = [], setIsEditing, - updatePortfolio + updateMyPortfolio } = props; const [portfolio, setPortfolio] = useState(initialPortfolio); const [unsavedItemId, setUnsavedItemId] = useState(null); @@ -105,7 +113,7 @@ const PortfolioSettings = (props: PortfolioProps) => { setUnsavedItemId(null); } const portfolioToUpdate = updatedPortfolio || portfolio; - updatePortfolio({ portfolio: portfolioToUpdate }); + updateMyPortfolio({ portfolio: portfolioToUpdate }); setIsEditing(false); }; @@ -384,4 +392,6 @@ const PortfolioSettings = (props: PortfolioProps) => { PortfolioSettings.displayName = 'PortfolioSettings'; -export default withTranslation()(PortfolioSettings); +export default withTranslation()( + connect(null, mapDispatchToProps)(PortfolioSettings) +); diff --git a/client/src/components/profile/profile.tsx b/client/src/components/profile/profile.tsx index fb36b1772e6..73d0ab97d1c 100644 --- a/client/src/components/profile/profile.tsx +++ b/client/src/components/profile/profile.tsx @@ -8,7 +8,7 @@ import Portfolio from './components/portfolio'; import UsernameSettings from './components/username'; import About from './components/about'; -import Internet, { Socials } from './components/internet'; +import Internet from './components/internet'; import { User } from './../../redux/prop-types'; import Timeline from './components/time-line'; import Camper from './components/camper'; @@ -21,9 +21,6 @@ import { PortfolioProjects } from './components/portfolio-projects'; interface ProfileProps { isSessionUser: boolean; user: User; - updateMyPortfolio: () => void; - updateMySocials: (formValues: Socials) => void; - submitNewAbout: () => void; } interface EditModalProps { @@ -31,9 +28,6 @@ interface EditModalProps { isEditing: boolean; isSessionUser: boolean; setIsEditing: (isEditing: boolean) => void; - updateMySocials: (formValues: Socials) => void; - updateMyPortfolio: () => void; - submitNewAbout: () => void; } interface MessageProps { isSessionUser: boolean; @@ -50,27 +44,8 @@ const UserMessage = ({ t }: Pick) => { ); }; -const EditModal = ({ - user, - isEditing, - isSessionUser, - setIsEditing, - updateMyPortfolio, - updateMySocials, - submitNewAbout -}: EditModalProps) => { - const { - portfolio, - username, - about, - location, - name, - picture, - githubProfile, - linkedin, - twitter, - website - } = user; +const EditModal = ({ user, isEditing, setIsEditing }: EditModalProps) => { + const { portfolio, username } = user; const { t } = useTranslation(); return ( setIsEditing(false)} open={isEditing} size='large'> @@ -78,31 +53,11 @@ const EditModal = ({ - + - + - + ); @@ -129,43 +84,22 @@ const Message = ({ isSessionUser, t, username }: MessageProps) => { return ; }; -function UserProfile({ - user, - isSessionUser, - updateMyPortfolio, - updateMySocials, - submitNewAbout -}: ProfileProps): JSX.Element { +function UserProfile({ user, isSessionUser }: ProfileProps): JSX.Element { const [isEditing, setIsEditing] = useState(false); const { profileUI: { - showAbout, showCerts, - showDonation, showHeatMap, - showLocation, - showName, showPoints, showPortfolio, showTimeLine }, calendar, completedChallenges, - githubProfile, - linkedin, - twitter, - website, - name, username, - joinDate, - location, points, - picture, - portfolio, - about, - yearsTopContributor, - isDonating + portfolio } = user; return ( @@ -176,24 +110,10 @@ function UserProfile({ isEditing={isEditing} isSessionUser={isSessionUser} setIsEditing={setIsEditing} - updateMyPortfolio={updateMyPortfolio} - updateMySocials={updateMySocials} - submitNewAbout={submitNewAbout} /> )} @@ -211,13 +131,7 @@ function UserProfile({ ); } -function Profile({ - user, - isSessionUser, - updateMyPortfolio, - updateMySocials, - submitNewAbout -}: ProfileProps): JSX.Element { +function Profile({ user, isSessionUser }: ProfileProps): JSX.Element { const { t } = useTranslation(); const { profileUI: { isLocked }, @@ -238,13 +152,7 @@ function Profile({ )} {showUserProfile && ( - + )} {!isSessionUser && (