Skip to content

Commit

Permalink
fix: fit & finish profile card plugin (#870)
Browse files Browse the repository at this point in the history
* fix: format plugin elements into card

* fix: clean and refactor Profile Plugin

---------

Co-authored-by: Jason Wesson <[email protected]>
  • Loading branch information
jsnwesson and jsnwesson authored Oct 17, 2023
1 parent 9291ea6 commit 06696f8
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 162 deletions.
3 changes: 3 additions & 0 deletions plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ export {
export {
IFRAME_PLUGIN,
} from './data/constants';
export {
default as PluginErrorBoundary,
} from './PluginErrorBoundary';
264 changes: 102 additions & 162 deletions src/profile/ProfilePluginPage.jsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
/* eslint-disable react/prop-types */
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import { ensureConfig } from '@edx/frontend-platform';
import { AppContext, ErrorBoundary } from '@edx/frontend-platform/react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { AppContext } from '@edx/frontend-platform/react';
import { injectIntl, intlShape, FormattedDate } from '@edx/frontend-platform/i18n';

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTwitter, faFacebook, faLinkedin } from '@fortawesome/free-brands-svg-icons';
import {
ActionRow, Avatar, Card, Hyperlink, Icon,
} from '@edx/paragon';
import { HistoryEdu, VerifiedUser } from '@edx/paragon/icons';

import get from 'lodash.get';

import PluginCountry from './forms/PluginCountry';
import { Plugin } from '../../plugins';

// Actions
import {
fetchProfile,
saveProfile,
saveProfilePhoto,
deleteProfilePhoto,
openForm,
closeForm,
updateDraft,
} from './data/actions';

// Components
import ProfileAvatar from './forms/ProfileAvatar';
import Name from './forms/Name';
import Country from './forms/Country';
import Education from './forms/Education';
import SocialLinks from './forms/SocialLinks';
import Bio from './forms/Bio';
import DateJoined from './DateJoined';
import PageLoading from './PageLoading';

// Selectors
import { profilePageSelector } from './data/selectors';

// i18n
import messages from './ProfilePage.messages';
import eduMessages from './forms/Education.messages';

import withParams from '../utils/hoc';

Expand All @@ -45,143 +45,99 @@ function Fallback() {
);
}

class ProfilePluginPage extends React.Component {
constructor(props, context) {
super(props, context);

this.handleSaveProfilePhoto = this.handleSaveProfilePhoto.bind(this);
this.handleDeleteProfilePhoto = this.handleDeleteProfilePhoto.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handleOpen = this.handleOpen.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
const platformDisplayInfo = {
facebook: {
icon: faFacebook,
name: '',
},
twitter: {
icon: faTwitter,
name: '',
},
linkedin: {
icon: faLinkedin,
name: '',
},
};

class ProfilePluginPage extends React.Component {
componentDidMount() {
this.props.fetchProfile(this.props.params.username);
}

handleSaveProfilePhoto(formData) {
this.props.saveProfilePhoto(this.context.authenticatedUser.username, formData);
}

handleDeleteProfilePhoto() {
this.props.deleteProfilePhoto(this.context.authenticatedUser.username);
}

handleClose(formId) {
this.props.closeForm(formId);
}

handleOpen(formId) {
this.props.openForm(formId);
}

handleSubmit(formId) {
this.props.saveProfile(formId, this.context.authenticatedUser.username);
}

handleChange(name, value) {
this.props.updateDraft(name, value);
}

// Inserted into the DOM in two places (for responsive layout)
renderHeadingLockup() {
const { dateJoined } = this.props;
return (
<ErrorBoundary fallbackComponent={<Fallback />}>
<span data-hj-suppress>
<h1 className="h2 mb-0 font-weight-bold">{this.props.params.username}</h1>
<DateJoined date={dateJoined} />
<hr className="d-none d-md-block" />
</span>
</ErrorBoundary>
);
}

renderContent() {
const {
profileImage,
name,
visibilityName,
country,
visibilityCountry,
levelOfEducation,
visibilityLevelOfEducation,
socialLinks,
visibilitySocialLinks,
bio,
visibilityBio,
isLoadingProfile,
dateJoined,
intl,
} = this.props;

if (isLoadingProfile) {
return <PageLoading srMessage={this.props.intl.formatMessage(messages['profile.loading'])} />;
}

const commonFormProps = {
openHandler: this.handleOpen,
closeHandler: this.handleClose,
submitHandler: this.handleSubmit,
changeHandler: this.handleChange,
};

return (
<Plugin>
<div className="container-fluid">
<div className="row align-items-center pt-4 mb-4 pt-md-0 mb-md-0">
<div className="col-auto col-md-4 col-lg-3">
<div className="d-flex align-items-center d-md-block">
<ProfileAvatar
className="mb-md-3"
src={profileImage.src}
isDefault={profileImage.isDefault}
/>
</div>
</div>
<div className="col pl-0">
<div>
{this.renderHeadingLockup()}
</div>
</div>
</div>
<div className="row">
<div className="col-md-4 col-lg-4">
<Name
name={name}
visibilityName={visibilityName}
formId="name"
{...commonFormProps}
/>
<Country
country={country}
visibilityCountry={visibilityCountry}
formId="country"
{...commonFormProps}
/>
<Education
levelOfEducation={levelOfEducation}
visibilityLevelOfEducation={visibilityLevelOfEducation}
formId="levelOfEducation"
{...commonFormProps}
/>
<SocialLinks
socialLinks={socialLinks}
visibilitySocialLinks={visibilitySocialLinks}
formId="socialLinks"
{...commonFormProps}
/>
</div>
<div className="pt-md-3 col-md-8 col-lg-7 offset-lg-1">
<Bio
bio={bio}
visibilityBio={visibilityBio}
formId="bio"
{...commonFormProps}
/>
</div>
</div>
</div>
<Plugin fallbackComponent={<Fallback />}>
<Card className="mb-2">
<Card.Header
className="pb-5"
subtitle={(
<Hyperlink destination={`http://localhost:1995/u/${this.props.params.username}`}>
View public profile
</Hyperlink>
)}
actions={
(
<ActionRow className="mt-3">
{socialLinks
.filter(({ socialLink }) => Boolean(socialLink))
.map(({ platform, socialLink }) => (
<StaticListItem
key={platform}
name={platformDisplayInfo[platform].name}
url={socialLink}
platform={platform}
/>
))}
</ActionRow>
)
}
/>
<Card.Section className="text-center" muted>
<Avatar
size="xl"
className="profile-plugin-avatar"
src={profileImage.src}
alt="Profile image"
/>
<h1 className="h2 mb-0 font-weight-bold">{this.props.params.username}</h1>
<PluginCountry
country={country}
/>
</Card.Section>
<Card.Footer className="p-0">
<Card.Section className="pgn-icons-cell-vertical">
<Icon src={VerifiedUser} />
<p>
since <FormattedDate value={new Date(dateJoined)} year="numeric" />
</p>
</Card.Section>
<Card.Section className="pgn-icons-cell-vertical">
<Icon src={HistoryEdu} />
<p>
{intl.formatMessage(get(
eduMessages,
`profile.education.levels.${levelOfEducation}`,
eduMessages['profile.education.levels.o'],
))}
</p>
</Card.Section>
</Card.Footer>
</Card>
</Plugin>
);
}
Expand All @@ -195,52 +151,46 @@ class ProfilePluginPage extends React.Component {
}
}

const SocialLink = ({ url, name, platform }) => (
<a href={url} className="font-weight-bold">
<FontAwesomeIcon className="mr-2" icon={platformDisplayInfo[platform].icon} />
{name}
</a>
);

const StaticListItem = ({ url, name, platform }) => (
<ul className="list-inline">
<SocialLink name={name} url={url} platform={platform} />
</ul>
);

ProfilePluginPage.contextType = AppContext;

ProfilePluginPage.propTypes = {
// Account data
dateJoined: PropTypes.string,

// Bio form data
bio: PropTypes.string,
yearOfBirth: PropTypes.number,
visibilityBio: PropTypes.string.isRequired,

// Country form data
country: PropTypes.string,
visibilityCountry: PropTypes.string.isRequired,

// Education form data
levelOfEducation: PropTypes.string,
visibilityLevelOfEducation: PropTypes.string.isRequired,

// Name form data
name: PropTypes.string,
visibilityName: PropTypes.string.isRequired,

// Social links form data
socialLinks: PropTypes.arrayOf(PropTypes.shape({
platform: PropTypes.string,
socialLink: PropTypes.string,
})),
visibilitySocialLinks: PropTypes.string.isRequired,

// Other data we need
profileImage: PropTypes.shape({
src: PropTypes.string,
isDefault: PropTypes.bool,
}),
saveState: PropTypes.oneOf([null, 'pending', 'complete', 'error']),
isLoadingProfile: PropTypes.bool.isRequired,

// Actions
fetchProfile: PropTypes.func.isRequired,
saveProfile: PropTypes.func.isRequired,
saveProfilePhoto: PropTypes.func.isRequired,
deleteProfilePhoto: PropTypes.func.isRequired,
openForm: PropTypes.func.isRequired,
closeForm: PropTypes.func.isRequired,
updateDraft: PropTypes.func.isRequired,

// Router
params: PropTypes.shape({
Expand All @@ -252,26 +202,16 @@ ProfilePluginPage.propTypes = {
};

ProfilePluginPage.defaultProps = {
saveState: null,
profileImage: {},
name: null,
yearOfBirth: null,
levelOfEducation: null,
country: null,
socialLinks: [],
bio: null,
dateJoined: null,
};

export default connect(
profilePageSelector,
{
fetchProfile,
saveProfilePhoto,
deleteProfilePhoto,
saveProfile,
openForm,
closeForm,
updateDraft,
},
)(injectIntl(withParams(ProfilePluginPage)));
Loading

0 comments on commit 06696f8

Please sign in to comment.