refactor(profile): update components to use user object instead of individual props (#58680)

This commit is contained in:
Sem Bauke
2025-04-08 10:46:39 +02:00
committed by GitHub
parent 490cf47e2b
commit 70e4439b6d
8 changed files with 117 additions and 224 deletions
@@ -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({
<FourOhFour />
)
) : (
<Profile
isSessionUser={isSessionUser}
user={requestedUser}
submitNewAbout={submitNewAbout}
updateMyPortfolio={updateMyPortfolio}
updateMySocials={updateMySocials}
/>
<Profile isSessionUser={isSessionUser} user={requestedUser} />
);
}
@@ -187,17 +187,12 @@ exports[`<Profile/> renders correctly 1`] = `
string
</h1>
</div>
<div
class="h-[20px]"
/>
<div
class="profile-meta-container"
>
</div>
/>
<div
class="mx-[-15px] "
>
@@ -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<AboutProps, 'name' | 'location' | 'picture' | 'about'>;
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<FormValues>({
name,
location,
@@ -235,4 +239,6 @@ const AboutSettings = ({
AboutSettings.displayName = 'AboutSettings';
export default withTranslation()(AboutSettings);
export default withTranslation()(
connect(null, mapDispatchToProps)(AboutSettings)
);
@@ -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 = ({
<section className='card card-header'>
<div className='avatar-camper'>
<AvatarRenderer
isDonating={isDonating}
isDonating={isDonating && showDonation}
isTopContributor={isTopContributor}
picture={picture}
/>
@@ -56,17 +64,17 @@ const Bio = ({
</Button>
)}
</div>
{name && <h2>{name}</h2>}
{name && showAbout && <h2>{name}</h2>}
<Spacer size={'s'} />
{about && <p>{about}</p>}
{showAbout && <p>{about}</p>}
<div className='profile-meta-container'>
{joinDate && (
{joinDate && showAbout && (
<div>
<FontAwesomeIcon icon={faCalendar} />
<span>{parseDate(joinDate, t)}</span>
</div>
)}
{location && (
{location && showLocation && (
<div>
<FontAwesomeIcon icon={faLocationDot} />
<span>{t('profile.from', { location })}</span>
@@ -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 (
<>
<div className='bio-container'>
<Bio
joinDate={joinDate}
location={location}
username={username}
name={name}
about={about}
githubProfile={githubProfile}
linkedin={linkedin}
twitter={twitter}
website={website}
isDonating={isDonating}
yearsTopContributor={yearsTopContributor}
picture={picture}
user={user}
setIsEditing={setIsEditing}
isSessionUser={isSessionUser}
/>
</div>
{(isDonating || isTopContributor) && (
{((isDonating && showDonation) || isTopContributor) && (
<FullWidthRow>
<section className='card'>
<h2>{t('profile.badges')}</h2>
@@ -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 ? <HelpBlock>{message}</HelpBlock> : 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<Socials>({
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)
);
@@ -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<string | null>(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)
);
+11 -103
View File
@@ -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<MessageProps, 't'>) => {
);
};
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 (
<Modal onClose={() => setIsEditing(false)} open={isEditing} size='large'>
@@ -78,31 +53,11 @@ const EditModal = ({
<Modal.Body alignment='left'>
<UsernameSettings username={username} setIsEditing={setIsEditing} />
<Spacer size='m' />
<About
about={about}
location={location}
name={name}
picture={picture}
username={username}
submitNewAbout={submitNewAbout}
setIsEditing={setIsEditing}
isSessionUser={isSessionUser}
/>
<About user={user} setIsEditing={setIsEditing} />
<Spacer size='m' />
<Internet
githubProfile={githubProfile}
linkedin={linkedin}
twitter={twitter}
updateSocials={updateMySocials}
setIsEditing={setIsEditing}
website={website}
/>
<Internet user={user} setIsEditing={setIsEditing} />
<Spacer size='m' />
<Portfolio
portfolio={portfolio}
updatePortfolio={updateMyPortfolio}
setIsEditing={setIsEditing}
/>
<Portfolio portfolio={portfolio} setIsEditing={setIsEditing} />
</Modal.Body>
</Modal>
);
@@ -129,43 +84,22 @@ const Message = ({ isSessionUser, t, username }: MessageProps) => {
return <VisitorMessage t={t} username={username} />;
};
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}
/>
)}
<Camper
about={showAbout ? about : ''}
githubProfile={githubProfile}
isDonating={showDonation ? isDonating : false}
joinDate={showAbout ? joinDate : ''}
linkedin={linkedin}
location={showLocation ? location : ''}
name={showName ? name : ''}
picture={picture}
twitter={twitter}
username={username}
website={website}
yearsTopContributor={yearsTopContributor}
user={user}
isSessionUser={isSessionUser}
setIsEditing={setIsEditing}
/>
@@ -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({
<Message username={username} isSessionUser={isSessionUser} t={t} />
)}
{showUserProfile && (
<UserProfile
user={user}
isSessionUser={isSessionUser}
updateMyPortfolio={updateMyPortfolio}
updateMySocials={updateMySocials}
submitNewAbout={submitNewAbout}
/>
<UserProfile user={user} isSessionUser={isSessionUser} />
)}
{!isSessionUser && (
<Row className='text-center'>