mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
refactor(profile): update components to use user object instead of individual props (#58680)
This commit is contained in:
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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'>
|
||||
|
||||
Reference in New Issue
Block a user