Organize application according to semantic modules. (#153)
This commit is contained in:
@@ -1,67 +1,167 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect, Provider } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { IntlProvider, injectIntl, intlShape } from 'react-intl';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { ConnectedRouter } from 'connected-react-router';
|
||||
import { sendTrackEvent } from '@edx/frontend-analytics';
|
||||
import SiteHeader from '@edx/frontend-component-site-header';
|
||||
import SiteFooter from '@edx/frontend-component-footer';
|
||||
import { fetchUserAccount, UserAccountApiService } from '@edx/frontend-auth';
|
||||
|
||||
import apiClient from '../config/apiClient';
|
||||
import { getLocale, getMessages } from '@edx/frontend-i18n'; // eslint-disable-line
|
||||
import SiteHeader from './common/SiteHeader';
|
||||
import ConnectedProfilePage from './ProfilePage';
|
||||
|
||||
import FooterLogo from '../../assets/edx-footer.png';
|
||||
import { PageLoading, fetchUserAccount } from '../common';
|
||||
import { ConnectedProfilePage } from '../profile';
|
||||
|
||||
import FooterLogo from '../assets/edx-footer.png';
|
||||
import HeaderLogo from '../assets/logo.svg';
|
||||
import ErrorPage from './ErrorPage';
|
||||
import NotFoundPage from './NotFoundPage';
|
||||
import PageLoading from './common/PageLoading';
|
||||
|
||||
import messages from './App.messages';
|
||||
|
||||
|
||||
function PageContent({
|
||||
ready,
|
||||
configuration,
|
||||
username,
|
||||
avatar,
|
||||
intl,
|
||||
}) {
|
||||
if (!ready) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
|
||||
const mainMenu = [
|
||||
{
|
||||
type: 'item',
|
||||
href: `${process.env.MARKETING_SITE_BASE_URL}/course`,
|
||||
content: intl.formatMessage(messages['siteheader.links.courses']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: `${process.env.MARKETING_SITE_BASE_URL}/course?program=all`,
|
||||
content: intl.formatMessage(messages['siteheader.links.programs']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: `${process.env.MARKETING_SITE_BASE_URL}/schools-partners`,
|
||||
content: intl.formatMessage(messages['siteheader.links.schools']),
|
||||
},
|
||||
];
|
||||
const userMenu = [
|
||||
{
|
||||
type: 'item',
|
||||
href: `${process.env.LMS_BASE_URL}`,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.dashboard']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: `${process.env.BASE_URL}/u/${username}`,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.profile']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: `${process.env.LMS_BASE_URL}/account/settings`,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.account.settings']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: process.env.LOGOUT_URL,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.logout']),
|
||||
},
|
||||
];
|
||||
const loggedOutItems = [
|
||||
{
|
||||
type: 'item',
|
||||
href: `${process.env.LMS_BASE_URL}/login`,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.login']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: `${process.env.LMS_BASE_URL}/register`,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.register']),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SiteHeader
|
||||
logo={HeaderLogo}
|
||||
loggedIn
|
||||
username={username}
|
||||
avatar={avatar}
|
||||
logoAltText={configuration.SITE_NAME}
|
||||
logoDestination={configuration.MARKETING_SITE_BASE_URL}
|
||||
mainMenu={mainMenu}
|
||||
userMenu={userMenu}
|
||||
loggedOutItems={loggedOutItems}
|
||||
/>
|
||||
<main>
|
||||
<Switch>
|
||||
<Route path="/u/:username" component={ConnectedProfilePage} />
|
||||
<Route path="/error" component={ErrorPage} />
|
||||
<Route path="/notfound" component={NotFoundPage} />
|
||||
<Route path="*" component={NotFoundPage} />
|
||||
</Switch>
|
||||
</main>
|
||||
<SiteFooter
|
||||
siteName={configuration.SITE_NAME}
|
||||
siteLogo={FooterLogo}
|
||||
marketingSiteBaseUrl={configuration.MARKETING_SITE_BASE_URL}
|
||||
supportUrl={configuration.SUPPORT_URL}
|
||||
contactUrl={configuration.CONTACT_URL}
|
||||
openSourceUrl={configuration.OPEN_SOURCE_URL}
|
||||
termsOfServiceUrl={configuration.TERMS_OF_SERVICE_URL}
|
||||
privacyPolicyUrl={configuration.PRIVACY_POLICY_URL}
|
||||
facebookUrl={configuration.FACEBOOK_URL}
|
||||
twitterUrl={configuration.TWITTER_URL}
|
||||
youTubeUrl={configuration.YOU_TUBE_URL}
|
||||
linkedInUrl={configuration.LINKED_IN_URL}
|
||||
googlePlusUrl={configuration.GOOGLE_PLUS_URL}
|
||||
redditUrl={configuration.REDDIT_URL}
|
||||
appleAppStoreUrl={configuration.APPLE_APP_STORE_URL}
|
||||
googlePlayUrl={configuration.GOOGLE_PLAY_URL}
|
||||
handleAllTrackEvents={sendTrackEvent}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
PageContent.propTypes = {
|
||||
username: PropTypes.string.isRequired,
|
||||
avatar: PropTypes.string,
|
||||
ready: PropTypes.bool,
|
||||
configuration: PropTypes.shape({
|
||||
SITE_NAME: PropTypes.string.isRequired,
|
||||
MARKETING_SITE_BASE_URL: PropTypes.string.isRequired,
|
||||
SUPPORT_URL: PropTypes.string.isRequired,
|
||||
CONTACT_URL: PropTypes.string.isRequired,
|
||||
OPEN_SOURCE_URL: PropTypes.string.isRequired,
|
||||
TERMS_OF_SERVICE_URL: PropTypes.string.isRequired,
|
||||
PRIVACY_POLICY_URL: PropTypes.string.isRequired,
|
||||
FACEBOOK_URL: PropTypes.string.isRequired,
|
||||
TWITTER_URL: PropTypes.string.isRequired,
|
||||
YOU_TUBE_URL: PropTypes.string.isRequired,
|
||||
LINKED_IN_URL: PropTypes.string.isRequired,
|
||||
GOOGLE_PLUS_URL: PropTypes.string.isRequired,
|
||||
REDDIT_URL: PropTypes.string.isRequired,
|
||||
APPLE_APP_STORE_URL: PropTypes.string.isRequired,
|
||||
GOOGLE_PLAY_URL: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
PageContent.defaultProps = {
|
||||
ready: false,
|
||||
avatar: null,
|
||||
};
|
||||
|
||||
const IntlPageContent = injectIntl(PageContent);
|
||||
|
||||
class App extends Component {
|
||||
componentDidMount() {
|
||||
const { username } = this.props;
|
||||
const userAccountApiService = new UserAccountApiService(apiClient, process.env.LMS_BASE_URL);
|
||||
this.props.fetchUserAccount(userAccountApiService, username);
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
if (!this.props.ready) {
|
||||
return <PageLoading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SiteHeader />
|
||||
<main>
|
||||
<Switch>
|
||||
<Route path="/u/:username" component={ConnectedProfilePage} />
|
||||
<Route path="/error" component={ErrorPage} />
|
||||
<Route path="/notfound" component={NotFoundPage} />
|
||||
<Route path="*" component={NotFoundPage} />
|
||||
</Switch>
|
||||
</main>
|
||||
<SiteFooter
|
||||
siteName={process.env.SITE_NAME}
|
||||
siteLogo={FooterLogo}
|
||||
marketingSiteBaseUrl={process.env.MARKETING_SITE_BASE_URL}
|
||||
supportUrl={process.env.SUPPORT_URL}
|
||||
contactUrl={process.env.CONTACT_URL}
|
||||
openSourceUrl={process.env.OPEN_SOURCE_URL}
|
||||
termsOfServiceUrl={process.env.TERMS_OF_SERVICE_URL}
|
||||
privacyPolicyUrl={process.env.PRIVACY_POLICY_URL}
|
||||
facebookUrl={process.env.FACEBOOK_URL}
|
||||
twitterUrl={process.env.TWITTER_URL}
|
||||
youTubeUrl={process.env.YOU_TUBE_URL}
|
||||
linkedInUrl={process.env.LINKED_IN_URL}
|
||||
googlePlusUrl={process.env.GOOGLE_PLUS_URL}
|
||||
redditUrl={process.env.REDDIT_URL}
|
||||
appleAppStoreUrl={process.env.APPLE_APP_STORE_URL}
|
||||
googlePlayUrl={process.env.GOOGLE_PLAY_URL}
|
||||
handleAllTrackEvents={sendTrackEvent}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
this.props.fetchUserAccount(username);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -69,7 +169,12 @@ class App extends Component {
|
||||
<IntlProvider locale={getLocale()} messages={getMessages()}>
|
||||
<Provider store={this.props.store}>
|
||||
<ConnectedRouter history={this.props.history}>
|
||||
{this.renderContent()}
|
||||
<IntlPageContent
|
||||
ready={this.props.ready}
|
||||
configuration={this.props.configuration}
|
||||
username={this.props.username}
|
||||
avatar={this.props.avatar}
|
||||
/>
|
||||
</ConnectedRouter>
|
||||
</Provider>
|
||||
</IntlProvider>
|
||||
@@ -80,13 +185,32 @@ class App extends Component {
|
||||
App.propTypes = {
|
||||
fetchUserAccount: PropTypes.func.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
avatar: PropTypes.string,
|
||||
store: PropTypes.object.isRequired, // eslint-disable-line
|
||||
history: PropTypes.object.isRequired, // eslint-disable-line
|
||||
ready: PropTypes.bool,
|
||||
configuration: PropTypes.shape({
|
||||
SITE_NAME: PropTypes.string.isRequired,
|
||||
MARKETING_SITE_BASE_URL: PropTypes.string.isRequired,
|
||||
SUPPORT_URL: PropTypes.string.isRequired,
|
||||
CONTACT_URL: PropTypes.string.isRequired,
|
||||
OPEN_SOURCE_URL: PropTypes.string.isRequired,
|
||||
TERMS_OF_SERVICE_URL: PropTypes.string.isRequired,
|
||||
PRIVACY_POLICY_URL: PropTypes.string.isRequired,
|
||||
FACEBOOK_URL: PropTypes.string.isRequired,
|
||||
TWITTER_URL: PropTypes.string.isRequired,
|
||||
YOU_TUBE_URL: PropTypes.string.isRequired,
|
||||
LINKED_IN_URL: PropTypes.string.isRequired,
|
||||
GOOGLE_PLUS_URL: PropTypes.string.isRequired,
|
||||
REDDIT_URL: PropTypes.string.isRequired,
|
||||
APPLE_APP_STORE_URL: PropTypes.string.isRequired,
|
||||
GOOGLE_PLAY_URL: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
App.defaultProps = {
|
||||
ready: false,
|
||||
avatar: null,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
@@ -94,6 +218,10 @@ const mapStateToProps = state => ({
|
||||
// An error means that we tried to load the user account and failed,
|
||||
// which also means we're ready to display something.
|
||||
ready: state.userAccount.loaded || state.userAccount.error != null,
|
||||
configuration: state.configuration,
|
||||
avatar: state.userAccount.profileImage.hasImage
|
||||
? state.userAccount.profileImage.imageUrlMedium
|
||||
: null,
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
||||
@@ -1,46 +1,51 @@
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { Button } from '@edx/paragon';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import apiClient from '../config/apiClient';
|
||||
|
||||
export default class ErrorPage extends Component {
|
||||
componentDidMount() {}
|
||||
|
||||
render() {
|
||||
const { username } = apiClient.getAuthenticationState().authentication;
|
||||
|
||||
return (
|
||||
<div className="container-fluid py-5 justify-content-center align-items-start text-center">
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<p className="my-0 py-5 text-muted">
|
||||
<FormattedMessage
|
||||
id="profile.error.message.text"
|
||||
defaultMessage="An unexpected error occurred. Please click the button below to return to your profile and try again."
|
||||
description="error message when an unexpected error occurs"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<Link to={`/u/${username}`}>
|
||||
<Button
|
||||
buttonType="primary"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="profile.error.button.text"
|
||||
defaultMessage="Return to Your Profile"
|
||||
description="text for button that navigates back to your profile page after an error has occured"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
function ErrorPage({ username }) {
|
||||
return (
|
||||
<div className="container-fluid py-5 justify-content-center align-items-start text-center">
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<p className="my-0 py-5 text-muted">
|
||||
<FormattedMessage
|
||||
id="profile.error.message.text"
|
||||
defaultMessage="An unexpected error occurred. Please click the button below to return to your profile and try again."
|
||||
description="error message when an unexpected error occurs"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<Link to={`/u/${username}`}>
|
||||
<Button
|
||||
buttonType="primary"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="profile.error.button.text"
|
||||
defaultMessage="Return to Your Profile"
|
||||
description="text for button that navigates back to your profile page after an error has occured"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ErrorPage.propTypes = {
|
||||
username: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
username: state.authentication.username,
|
||||
}),
|
||||
{},
|
||||
)(ErrorPage);
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default class NotFoundPage extends Component {
|
||||
componentDidMount() {}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="container-fluid d-flex py-5 justify-content-center align-items-start text-center">
|
||||
<p
|
||||
className="my-0 py-5 text-muted"
|
||||
style={{ maxWidth: '32em' }}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="profile.notfound.message"
|
||||
defaultMessage="The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again."
|
||||
description="error message when a page does not exist"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default function NotFoundPage() {
|
||||
return (
|
||||
<div className="container-fluid d-flex py-5 justify-content-center align-items-start text-center">
|
||||
<p className="my-0 py-5 text-muted" style={{ maxWidth: '32em' }}>
|
||||
<FormattedMessage
|
||||
id="profile.notfound.message"
|
||||
defaultMessage="The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again."
|
||||
description="error message when a page does not exist"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,356 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StatusAlert, Hyperlink } from '@edx/paragon';
|
||||
import { connect } from 'react-redux';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n'; // eslint-disable-line
|
||||
|
||||
// Analytics
|
||||
import { sendTrackingLogEvent } from '@edx/frontend-analytics';
|
||||
|
||||
// Actions
|
||||
import {
|
||||
fetchProfile,
|
||||
saveProfile,
|
||||
saveProfilePhoto,
|
||||
deleteProfilePhoto,
|
||||
openForm,
|
||||
closeForm,
|
||||
updateDraft,
|
||||
} from '../actions/ProfileActions';
|
||||
|
||||
// Components
|
||||
import ProfileAvatar from './ProfilePage/ProfileAvatar';
|
||||
import Name from './ProfilePage/Name';
|
||||
import Country from './ProfilePage/Country';
|
||||
import PreferredLanguage from './ProfilePage/PreferredLanguage';
|
||||
import Education from './ProfilePage/Education';
|
||||
import SocialLinks from './ProfilePage/SocialLinks';
|
||||
import Bio from './ProfilePage/Bio';
|
||||
import Certificates from './ProfilePage/Certificates';
|
||||
import AgeMessage from './ProfilePage/AgeMessage';
|
||||
import DateJoined from './ProfilePage/DateJoined';
|
||||
import PageLoading from './common/PageLoading';
|
||||
import Banner from './common/Banner';
|
||||
import { profilePageSelector } from '../selectors/ProfilePageSelector';
|
||||
|
||||
// Configuration
|
||||
import { configuration } from '../config/environment';
|
||||
|
||||
// i18n
|
||||
import messages from './ProfilePage.messages';
|
||||
|
||||
export class ProfilePage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchProfile(this.props.match.params.username);
|
||||
sendTrackingLogEvent('edx.profile.viewed', {
|
||||
username: this.props.match.params.username,
|
||||
});
|
||||
}
|
||||
|
||||
handleSaveProfilePhoto(formData) {
|
||||
this.props.saveProfilePhoto(this.props.username, formData);
|
||||
}
|
||||
|
||||
handleDeleteProfilePhoto() {
|
||||
this.props.deleteProfilePhoto(this.props.username);
|
||||
}
|
||||
|
||||
handleClose(formId) {
|
||||
this.props.closeForm(formId);
|
||||
}
|
||||
|
||||
handleOpen(formId) {
|
||||
this.props.openForm(formId);
|
||||
}
|
||||
|
||||
handleSubmit(formId) {
|
||||
this.props.saveProfile(formId);
|
||||
}
|
||||
|
||||
handleChange(name, value) {
|
||||
this.props.updateDraft(name, value);
|
||||
}
|
||||
|
||||
// Inserted into the DOM in two places (for responsive layout)
|
||||
renderViewMyRecordsButton() {
|
||||
if (!this.props.isAuthenticatedUserProfile) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Hyperlink className="btn btn-primary" href={configuration.VIEW_MY_RECORDS_URL} target="_blank">
|
||||
{this.props.intl.formatMessage(messages['profile.viewMyRecords'])}
|
||||
</Hyperlink>
|
||||
);
|
||||
}
|
||||
|
||||
// Inserted into the DOM in two places (for responsive layout)
|
||||
renderHeadingLockup() {
|
||||
const { username, dateJoined } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h1 className="h2 mb-0 font-weight-bold">{username}</h1>
|
||||
<DateJoined date={dateJoined} />
|
||||
<hr className="d-none d-md-block" />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
renderPhotoUploadErrorMessage() {
|
||||
const { photoUploadError } = this.props;
|
||||
|
||||
if (photoUploadError === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-md-4 col-lg-3">
|
||||
<StatusAlert alertType="danger" dialog={photoUploadError.userMessage} dismissible={false} open />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
profileImage,
|
||||
name,
|
||||
visibilityName,
|
||||
country,
|
||||
visibilityCountry,
|
||||
levelOfEducation,
|
||||
visibilityLevelOfEducation,
|
||||
socialLinks,
|
||||
draftSocialLinksByPlatform,
|
||||
visibilitySocialLinks,
|
||||
languageProficiencies,
|
||||
visibilityLanguageProficiencies,
|
||||
visibilityCourseCertificates,
|
||||
bio,
|
||||
visibilityBio,
|
||||
requiresParentalConsent,
|
||||
isAuthenticatedUserProfile,
|
||||
isLoadingProfile,
|
||||
} = this.props;
|
||||
|
||||
if (isLoadingProfile) return <PageLoading />;
|
||||
|
||||
const commonFormProps = {
|
||||
openHandler: this.handleOpen,
|
||||
closeHandler: this.handleClose,
|
||||
submitHandler: this.handleSubmit,
|
||||
changeHandler: this.handleChange,
|
||||
};
|
||||
|
||||
const shouldShowAgeMessage = requiresParentalConsent && isAuthenticatedUserProfile;
|
||||
|
||||
return (
|
||||
<div className="profile-page">
|
||||
<Banner />
|
||||
<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}
|
||||
onSave={this.handleSaveProfilePhoto}
|
||||
onDelete={this.handleDeleteProfilePhoto}
|
||||
savePhotoState={this.props.savePhotoState}
|
||||
isEditable={this.props.isAuthenticatedUserProfile && !requiresParentalConsent}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col pl-0">
|
||||
<div className="d-md-none">
|
||||
{this.renderHeadingLockup()}
|
||||
</div>
|
||||
<div className="d-none d-md-block float-right">
|
||||
{this.renderViewMyRecordsButton()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{this.renderPhotoUploadErrorMessage()}
|
||||
<div className="row">
|
||||
<div className="col-md-4 col-lg-4">
|
||||
<div className="d-none d-md-block mb-4">
|
||||
{this.renderHeadingLockup()}
|
||||
</div>
|
||||
<div className="d-md-none mb-4">
|
||||
{this.renderViewMyRecordsButton()}
|
||||
</div>
|
||||
<Name
|
||||
name={name}
|
||||
visibilityName={visibilityName}
|
||||
formId="name"
|
||||
{...commonFormProps}
|
||||
/>
|
||||
<Country
|
||||
country={country}
|
||||
visibilityCountry={visibilityCountry}
|
||||
formId="country"
|
||||
{...commonFormProps}
|
||||
/>
|
||||
<PreferredLanguage
|
||||
languageProficiencies={languageProficiencies}
|
||||
visibilityLanguageProficiencies={visibilityLanguageProficiencies}
|
||||
formId="languageProficiencies"
|
||||
{...commonFormProps}
|
||||
/>
|
||||
<Education
|
||||
levelOfEducation={levelOfEducation}
|
||||
visibilityLevelOfEducation={visibilityLevelOfEducation}
|
||||
formId="levelOfEducation"
|
||||
{...commonFormProps}
|
||||
/>
|
||||
<SocialLinks
|
||||
socialLinks={socialLinks}
|
||||
draftSocialLinksByPlatform={draftSocialLinksByPlatform}
|
||||
visibilitySocialLinks={visibilitySocialLinks}
|
||||
formId="socialLinks"
|
||||
{...commonFormProps}
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-md-3 col-md-8 col-lg-7 offset-lg-1">
|
||||
{shouldShowAgeMessage ? <AgeMessage accountURL="#account" /> : null}
|
||||
<Bio
|
||||
bio={bio}
|
||||
visibilityBio={visibilityBio}
|
||||
formId="bio"
|
||||
{...commonFormProps}
|
||||
/>
|
||||
<Certificates
|
||||
visibilityCourseCertificates={visibilityCourseCertificates}
|
||||
formId="certificates"
|
||||
{...commonFormProps}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ProfilePage.propTypes = {
|
||||
// Account data
|
||||
username: PropTypes.string,
|
||||
requiresParentalConsent: PropTypes.bool,
|
||||
dateJoined: PropTypes.string,
|
||||
isAuthenticatedUserProfile: PropTypes.bool.isRequired,
|
||||
|
||||
// Bio form data
|
||||
bio: PropTypes.string,
|
||||
visibilityBio: PropTypes.string.isRequired,
|
||||
|
||||
// Certificates form data
|
||||
courseCertificates: PropTypes.arrayOf(PropTypes.shape({
|
||||
title: PropTypes.string,
|
||||
})),
|
||||
visibilityCourseCertificates: PropTypes.string.isRequired,
|
||||
|
||||
// Country form data
|
||||
country: PropTypes.string,
|
||||
visibilityCountry: PropTypes.string.isRequired,
|
||||
|
||||
// Education form data
|
||||
levelOfEducation: PropTypes.string,
|
||||
visibilityLevelOfEducation: PropTypes.string.isRequired,
|
||||
|
||||
// Language proficiency form data
|
||||
languageProficiencies: PropTypes.arrayOf(PropTypes.shape({
|
||||
code: PropTypes.string.isRequired,
|
||||
})),
|
||||
visibilityLanguageProficiencies: 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,
|
||||
})),
|
||||
draftSocialLinksByPlatform: PropTypes.objectOf(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']),
|
||||
savePhotoState: PropTypes.oneOf([null, 'pending', 'complete', 'error']),
|
||||
isLoadingProfile: PropTypes.bool.isRequired,
|
||||
|
||||
// Page state helpers
|
||||
photoUploadError: PropTypes.objectOf(PropTypes.string),
|
||||
|
||||
// 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
|
||||
match: PropTypes.shape({
|
||||
params: PropTypes.shape({
|
||||
username: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
|
||||
// i18n
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
ProfilePage.defaultProps = {
|
||||
saveState: null,
|
||||
savePhotoState: null,
|
||||
photoUploadError: {},
|
||||
profileImage: {},
|
||||
name: null,
|
||||
username: null,
|
||||
levelOfEducation: null,
|
||||
country: null,
|
||||
socialLinks: [],
|
||||
draftSocialLinksByPlatform: {},
|
||||
bio: null,
|
||||
languageProficiencies: [],
|
||||
courseCertificates: null,
|
||||
requiresParentalConsent: null,
|
||||
dateJoined: null,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
profilePageSelector,
|
||||
{
|
||||
fetchProfile,
|
||||
saveProfilePhoto,
|
||||
deleteProfilePhoto,
|
||||
saveProfile,
|
||||
openForm,
|
||||
closeForm,
|
||||
updateDraft,
|
||||
},
|
||||
)(injectIntl(ProfilePage));
|
||||
@@ -1,11 +0,0 @@
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.viewMyRecords': {
|
||||
id: 'profile.viewMyRecords',
|
||||
defaultMessage: 'View My Records',
|
||||
description: 'A link to go view my academic records',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -1,113 +0,0 @@
|
||||
/* eslint-disable global-require */
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { Provider } from 'react-redux';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
|
||||
import * as analytics from '@edx/frontend-analytics';
|
||||
import ConnectedProfilePage from './ProfilePage';
|
||||
|
||||
|
||||
const mockStore = configureMockStore();
|
||||
const storeMocks = {
|
||||
loadingApp: require('./__mocks__/loadingApp.mockStore.js'),
|
||||
viewOwnProfile: require('./__mocks__/viewOwnProfile.mockStore.js'),
|
||||
viewOtherProfile: require('./__mocks__/viewOtherProfile.mockStore.js'),
|
||||
savingEditedBio: require('./__mocks__/savingEditedBio.mockStore.js'),
|
||||
};
|
||||
const requiredProfilePageProps = {
|
||||
isAuthenticatedUserProfile: true,
|
||||
fetchProfile: () => {},
|
||||
saveProfile: () => {},
|
||||
saveProfilePhoto: () => {},
|
||||
deleteProfilePhoto: () => {},
|
||||
openField: () => {},
|
||||
closeField: () => {},
|
||||
match: { params: { username: 'staff' } },
|
||||
};
|
||||
|
||||
|
||||
describe('<ProfilePage />', () => {
|
||||
describe('Renders correctly in various states', () => {
|
||||
it('app loading', () => {
|
||||
analytics.sendTrackingLogEvent = jest.fn();
|
||||
const tree = renderer
|
||||
.create((
|
||||
<IntlProvider locale="en">
|
||||
<Provider store={mockStore(storeMocks.loadingApp)}>
|
||||
<ConnectedProfilePage {...requiredProfilePageProps} />
|
||||
</Provider>
|
||||
</IntlProvider>
|
||||
))
|
||||
.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('viewing own profile', () => {
|
||||
analytics.sendTrackingLogEvent = jest.fn();
|
||||
const tree = renderer
|
||||
.create((
|
||||
<IntlProvider locale="en">
|
||||
<Provider store={mockStore(storeMocks.viewOwnProfile)}>
|
||||
<ConnectedProfilePage {...requiredProfilePageProps} />
|
||||
</Provider>
|
||||
</IntlProvider>
|
||||
))
|
||||
.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('viewing other profile', () => {
|
||||
analytics.sendTrackingLogEvent = jest.fn();
|
||||
const tree = renderer
|
||||
.create((
|
||||
<IntlProvider locale="en">
|
||||
<Provider store={mockStore(storeMocks.viewOtherProfile)}>
|
||||
<ConnectedProfilePage {...requiredProfilePageProps} />
|
||||
</Provider>
|
||||
</IntlProvider>
|
||||
))
|
||||
.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('while saving an edited bio', () => {
|
||||
analytics.sendTrackingLogEvent = jest.fn();
|
||||
const tree = renderer
|
||||
.create((
|
||||
<IntlProvider locale="en">
|
||||
<Provider store={mockStore(storeMocks.savingEditedBio)}>
|
||||
<ConnectedProfilePage {...requiredProfilePageProps} />
|
||||
</Provider>
|
||||
</IntlProvider>
|
||||
))
|
||||
.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('handles analytics', () => {
|
||||
it('calls sendTrackingLogEvent when mounting', () => {
|
||||
analytics.sendTrackingLogEvent = jest.fn();
|
||||
mount((
|
||||
<IntlProvider locale="en">
|
||||
<Provider store={mockStore(storeMocks.loadingApp)}>
|
||||
<ConnectedProfilePage
|
||||
{...requiredProfilePageProps}
|
||||
match={{ params: { username: 'test-username' } }}
|
||||
/>
|
||||
</Provider>
|
||||
</IntlProvider>
|
||||
));
|
||||
|
||||
expect(analytics.sendTrackingLogEvent.mock.calls.length).toBe(1);
|
||||
expect(analytics.sendTrackingLogEvent.mock.calls[0][0]).toEqual('edx.profile.viewed');
|
||||
expect(analytics.sendTrackingLogEvent.mock.calls[0][1]).toEqual({
|
||||
username: 'test-username',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
import React from 'react';
|
||||
import { StatusAlert } from '@edx/paragon';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { configuration } from '../../config/environment';
|
||||
|
||||
const { ACCOUNT_SETTINGS_URL } = configuration;
|
||||
|
||||
function AgeMessage() {
|
||||
return (
|
||||
<StatusAlert
|
||||
alertType="info"
|
||||
dialog={
|
||||
<React.Fragment>
|
||||
<FormattedMessage
|
||||
id="profile.age.headline"
|
||||
defaultMessage="Your profile cannot be shared."
|
||||
description="error message"
|
||||
tagName="h6"
|
||||
/>
|
||||
<FormattedMessage
|
||||
id="profile.age.details"
|
||||
defaultMessage="To share your profile with other edX learners, you must confirm that you are over the age of 13."
|
||||
description="error message"
|
||||
tagName="p"
|
||||
/>
|
||||
<a href={ACCOUNT_SETTINGS_URL}>
|
||||
<FormattedMessage
|
||||
id="profile.age.set.date"
|
||||
defaultMessage="Set your date of birth"
|
||||
description="label on a link to set birthday"
|
||||
/>
|
||||
</a>
|
||||
</React.Fragment>
|
||||
}
|
||||
dismissible={false}
|
||||
open
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default AgeMessage;
|
||||
@@ -1,157 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n'; // eslint-disable-line
|
||||
import { ValidationFormGroup } from '@edx/paragon';
|
||||
|
||||
import messages from './Bio.messages';
|
||||
|
||||
// Components
|
||||
import FormControls from './elements/FormControls';
|
||||
import EditableItemHeader from './elements/EditableItemHeader';
|
||||
import EmptyContent from './elements/EmptyContent';
|
||||
import SwitchContent from './elements/SwitchContent';
|
||||
|
||||
// Selectors
|
||||
import { editableFormSelector } from '../../selectors/ProfilePageSelector';
|
||||
|
||||
class Bio extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleClose = this.handleClose.bind(this);
|
||||
this.handleOpen = this.handleOpen.bind(this);
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
const { name, value } = e.target;
|
||||
this.props.changeHandler(name, value);
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
this.props.submitHandler(this.props.formId);
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
this.props.closeHandler(this.props.formId);
|
||||
}
|
||||
|
||||
handleOpen() {
|
||||
this.props.openHandler(this.props.formId);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
formId, bio, visibilityBio, editMode, saveState, error, intl,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<SwitchContent
|
||||
className="mb-5"
|
||||
expression={editMode}
|
||||
cases={{
|
||||
editing: (
|
||||
<div role="dialog" aria-labelledby={`${formId}-label`}>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<ValidationFormGroup
|
||||
for={formId}
|
||||
invalid={error !== null}
|
||||
invalidMessage={error}
|
||||
>
|
||||
<label className="edit-section-header" htmlFor={formId}>
|
||||
{intl.formatMessage(messages['profile.bio.about.me'])}
|
||||
</label>
|
||||
<textarea
|
||||
className="form-control"
|
||||
id={formId}
|
||||
name={formId}
|
||||
value={bio}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</ValidationFormGroup>
|
||||
<FormControls
|
||||
visibilityId="visibilityBio"
|
||||
saveState={saveState}
|
||||
visibility={visibilityBio}
|
||||
cancelHandler={this.handleClose}
|
||||
changeHandler={this.handleChange}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
),
|
||||
editable: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader
|
||||
content={intl.formatMessage(messages['profile.bio.about.me'])}
|
||||
showEditButton
|
||||
onClickEdit={this.handleOpen}
|
||||
showVisibility={visibilityBio !== null}
|
||||
visibility={visibilityBio}
|
||||
/>
|
||||
<p className="lead">{bio}</p>
|
||||
</React.Fragment>
|
||||
),
|
||||
empty: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader content={intl.formatMessage(messages['profile.bio.about.me'])} />
|
||||
<EmptyContent onClick={this.handleOpen}>
|
||||
<FormattedMessage
|
||||
id="profile.bio.empty"
|
||||
defaultMessage="Add a short bio"
|
||||
description="instructions when the user hasn't written an About Me"
|
||||
/>
|
||||
</EmptyContent>
|
||||
</React.Fragment>
|
||||
),
|
||||
static: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader content={intl.formatMessage(messages['profile.bio.about.me'])} />
|
||||
<p className="lead">{bio}</p>
|
||||
</React.Fragment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Bio.propTypes = {
|
||||
// It'd be nice to just set this as a defaultProps...
|
||||
// except the class that comes out on the other side of react-redux's
|
||||
// connect() method won't have it anymore. Static properties won't survive
|
||||
// through the higher order function.
|
||||
formId: PropTypes.string.isRequired,
|
||||
|
||||
// From Selector
|
||||
bio: PropTypes.string,
|
||||
visibilityBio: PropTypes.oneOf(['private', 'all_users']),
|
||||
editMode: PropTypes.oneOf(['editing', 'editable', 'empty', 'static']),
|
||||
saveState: PropTypes.string,
|
||||
error: PropTypes.string,
|
||||
|
||||
// Actions
|
||||
changeHandler: PropTypes.func.isRequired,
|
||||
submitHandler: PropTypes.func.isRequired,
|
||||
closeHandler: PropTypes.func.isRequired,
|
||||
openHandler: PropTypes.func.isRequired,
|
||||
|
||||
// i18n
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
Bio.defaultProps = {
|
||||
editMode: 'static',
|
||||
saveState: null,
|
||||
bio: null,
|
||||
visibilityBio: 'private',
|
||||
error: null,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
editableFormSelector,
|
||||
{},
|
||||
)(injectIntl(Bio));
|
||||
@@ -1,11 +0,0 @@
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.bio.about.me': {
|
||||
id: 'profile.bio.about.me',
|
||||
defaultMessage: 'About Me',
|
||||
description: 'A section of a user profile',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -1,228 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedDate, FormattedMessage } from 'react-intl';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n'; // eslint-disable-line
|
||||
import { Hyperlink } from '@edx/paragon';
|
||||
import { connect } from 'react-redux';
|
||||
import get from 'lodash.get';
|
||||
|
||||
import messages from './Certificates.messages';
|
||||
|
||||
// Components
|
||||
import FormControls from './elements/FormControls';
|
||||
import EditableItemHeader from './elements/EditableItemHeader';
|
||||
import SwitchContent from './elements/SwitchContent';
|
||||
|
||||
// Assets
|
||||
import microMastersSVG from '../../../assets/micro-masters.svg';
|
||||
import professionalCertificateSVG from '../../../assets/professional-certificate.svg';
|
||||
import verifiedCertificateSVG from '../../../assets/verified-certificate.svg';
|
||||
|
||||
// Selectors
|
||||
import { certificatesSelector } from '../../selectors/ProfilePageSelector';
|
||||
|
||||
class Certificates extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleClose = this.handleClose.bind(this);
|
||||
this.handleOpen = this.handleOpen.bind(this);
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
const { name, value } = e.target;
|
||||
this.props.changeHandler(name, value);
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
this.props.submitHandler(this.props.formId);
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
this.props.closeHandler(this.props.formId);
|
||||
}
|
||||
|
||||
handleOpen() {
|
||||
this.props.openHandler(this.props.formId);
|
||||
}
|
||||
|
||||
renderCertificate({
|
||||
certificateType, courseDisplayName, courseOrganization, modifiedDate, downloadUrl,
|
||||
}) {
|
||||
const { intl } = this.props;
|
||||
const certificateIllustration = ((type) => {
|
||||
switch (type) {
|
||||
case 'Professional Certificate':
|
||||
return professionalCertificateSVG;
|
||||
case 'MicroMasters Certificate':
|
||||
return microMastersSVG;
|
||||
case 'Verified Certificate':
|
||||
return verifiedCertificateSVG;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})(certificateType);
|
||||
|
||||
return (
|
||||
<div key={downloadUrl} className="col col-sm-6 d-flex align-items-stretch">
|
||||
<div className="card mb-4 certificate flex-grow-1">
|
||||
<div
|
||||
className="certificate-type-illustration"
|
||||
style={{ backgroundImage: `url(${certificateIllustration})` }}
|
||||
/>
|
||||
<div className="card-body d-flex flex-column">
|
||||
<div className="card-title">
|
||||
<p className="small mb-0">
|
||||
{intl.formatMessage(get(
|
||||
messages,
|
||||
`profile.certificates.types.${certificateType}`,
|
||||
messages['profile.certificates.types.unknown'],
|
||||
))}
|
||||
</p>
|
||||
<h4 className="certificate-title">{courseDisplayName}</h4>
|
||||
</div>
|
||||
<p className="small mb-0">
|
||||
<FormattedMessage
|
||||
id="profile.certificate.organization.label"
|
||||
defaultMessage="From"
|
||||
/>
|
||||
</p>
|
||||
<p className="h6 mb-4">{courseOrganization}</p>
|
||||
<div className="flex-grow-1" />
|
||||
<p className="small mb-2">
|
||||
<FormattedMessage
|
||||
id="profile.certificate.completion.date.label"
|
||||
defaultMessage="Completed on {date}"
|
||||
values={{
|
||||
date: <FormattedDate value={new Date(modifiedDate)} />,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<div>
|
||||
<Hyperlink href={downloadUrl} className="btn btn-outline-primary" target="_blank">
|
||||
{intl.formatMessage(messages['profile.certificates.view.certificate'])}
|
||||
</Hyperlink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderCertificates() {
|
||||
if (this.props.certificates === null || this.props.certificates.length === 0) {
|
||||
return (<FormattedMessage
|
||||
id="profile.no.certificates"
|
||||
defaultMessage="You don't have any certificates yet."
|
||||
description="displays when user has no course completion certificates"
|
||||
/>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="row align-items-stretch">{this.props.certificates.map(certificate => this.renderCertificate(certificate))}</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
visibilityCourseCertificates, editMode, saveState, intl,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<SwitchContent
|
||||
className="mb-4"
|
||||
expression={editMode}
|
||||
cases={{
|
||||
editing: (
|
||||
<div role="dialog" aria-labelledby="course-certificates-label">
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<EditableItemHeader
|
||||
headingId="course-certificates-label"
|
||||
content={intl.formatMessage(messages['profile.certificates.my.certificates'])}
|
||||
/>
|
||||
<FormControls
|
||||
visibilityId="visibilityCourseCertificates"
|
||||
saveState={saveState}
|
||||
visibility={visibilityCourseCertificates}
|
||||
cancelHandler={this.handleClose}
|
||||
changeHandler={this.handleChange}
|
||||
/>
|
||||
{this.renderCertificates()}
|
||||
</form>
|
||||
</div>
|
||||
),
|
||||
editable: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader
|
||||
content={intl.formatMessage(messages['profile.certificates.my.certificates'])}
|
||||
showEditButton
|
||||
onClickEdit={this.handleOpen}
|
||||
showVisibility={visibilityCourseCertificates !== null}
|
||||
visibility={visibilityCourseCertificates}
|
||||
/>
|
||||
{this.renderCertificates()}
|
||||
</React.Fragment>
|
||||
),
|
||||
empty: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader
|
||||
content={intl.formatMessage(messages['profile.certificates.my.certificates'])}
|
||||
showEditButton
|
||||
onClickEdit={this.handleOpen}
|
||||
showVisibility={visibilityCourseCertificates !== null}
|
||||
visibility={visibilityCourseCertificates}
|
||||
/>
|
||||
{this.renderCertificates()}
|
||||
</React.Fragment>
|
||||
),
|
||||
static: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader content={intl.formatMessage(messages['profile.certificates.my.certificates'])} />
|
||||
{this.renderCertificates()}
|
||||
</React.Fragment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Certificates.propTypes = {
|
||||
// It'd be nice to just set this as a defaultProps...
|
||||
// except the class that comes out on the other side of react-redux's
|
||||
// connect() method won't have it anymore. Static properties won't survive
|
||||
// through the higher order function.
|
||||
formId: PropTypes.string.isRequired,
|
||||
|
||||
// From Selector
|
||||
certificates: PropTypes.arrayOf(PropTypes.shape({
|
||||
title: PropTypes.string,
|
||||
})),
|
||||
visibilityCourseCertificates: PropTypes.oneOf(['private', 'all_users']),
|
||||
editMode: PropTypes.oneOf(['editing', 'editable', 'empty', 'static']),
|
||||
saveState: PropTypes.string,
|
||||
|
||||
// Actions
|
||||
changeHandler: PropTypes.func.isRequired,
|
||||
submitHandler: PropTypes.func.isRequired,
|
||||
closeHandler: PropTypes.func.isRequired,
|
||||
openHandler: PropTypes.func.isRequired,
|
||||
|
||||
// i18n
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
Certificates.defaultProps = {
|
||||
editMode: 'static',
|
||||
saveState: null,
|
||||
visibilityCourseCertificates: 'private',
|
||||
certificates: null,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
certificatesSelector,
|
||||
{},
|
||||
)(injectIntl(Certificates));
|
||||
@@ -1,31 +0,0 @@
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.certificates.my.certificates': {
|
||||
id: 'profile.certificates.my.certificates',
|
||||
defaultMessage: 'My Certificates',
|
||||
description: 'A section of a user profile',
|
||||
},
|
||||
'profile.certificates.view.certificate': {
|
||||
id: 'profile.certificates.view.certificate',
|
||||
defaultMessage: 'View Certificate',
|
||||
description: 'A call to action to view a certificate',
|
||||
},
|
||||
'profile.certificates.types.verified': {
|
||||
id: 'profile.certificates.types.verified',
|
||||
defaultMessage: 'Verified Certificate',
|
||||
description: 'A type of certificate a user may have earned',
|
||||
},
|
||||
'profile.certificates.types.professional': {
|
||||
id: 'profile.certificates.types.professional',
|
||||
defaultMessage: 'Professional Certificate',
|
||||
description: 'A type of certificate a user may have earned',
|
||||
},
|
||||
'profile.certificates.types.unknown': {
|
||||
id: 'profile.certificates.types.unknown',
|
||||
defaultMessage: 'Certificate',
|
||||
description: 'The string to display when a certificate is of an unknown type',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -1,177 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n'; // eslint-disable-line
|
||||
import { ValidationFormGroup } from '@edx/paragon';
|
||||
|
||||
import messages from './Country.messages';
|
||||
|
||||
// Components
|
||||
import FormControls from './elements/FormControls';
|
||||
import EditableItemHeader from './elements/EditableItemHeader';
|
||||
import EmptyContent from './elements/EmptyContent';
|
||||
import SwitchContent from './elements/SwitchContent';
|
||||
|
||||
// Selectors
|
||||
import { countrySelector } from '../../selectors/ProfilePageSelector';
|
||||
|
||||
class Country extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleClose = this.handleClose.bind(this);
|
||||
this.handleOpen = this.handleOpen.bind(this);
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
const {
|
||||
name,
|
||||
value,
|
||||
} = e.target;
|
||||
this.props.changeHandler(name, value);
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
this.props.submitHandler(this.props.formId);
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
this.props.closeHandler(this.props.formId);
|
||||
}
|
||||
|
||||
handleOpen() {
|
||||
this.props.openHandler(this.props.formId);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
formId,
|
||||
country,
|
||||
visibilityCountry,
|
||||
editMode,
|
||||
saveState,
|
||||
error,
|
||||
intl,
|
||||
sortedCountries,
|
||||
countryMessages,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<SwitchContent
|
||||
className="mb-5"
|
||||
expression={editMode}
|
||||
cases={{
|
||||
editing: (
|
||||
<div role="dialog" aria-labelledby={`${formId}-label`}>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<ValidationFormGroup
|
||||
for={formId}
|
||||
invalid={error !== null}
|
||||
invalidMessage={error}
|
||||
>
|
||||
<label className="edit-section-header" htmlFor={formId}>
|
||||
{intl.formatMessage(messages['profile.country.label'])}
|
||||
</label>
|
||||
<select
|
||||
className="form-control"
|
||||
type="select"
|
||||
id={formId}
|
||||
name={formId}
|
||||
value={country}
|
||||
onChange={this.handleChange}
|
||||
>
|
||||
{sortedCountries.map(({ code, name }) => (
|
||||
<option key={code} value={code}>{name}</option>
|
||||
))}
|
||||
</select>
|
||||
</ValidationFormGroup>
|
||||
<FormControls
|
||||
visibilityId="visibilityCountry"
|
||||
saveState={saveState}
|
||||
visibility={visibilityCountry}
|
||||
cancelHandler={this.handleClose}
|
||||
changeHandler={this.handleChange}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
),
|
||||
editable: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader
|
||||
content={intl.formatMessage(messages['profile.country.label'])}
|
||||
showEditButton
|
||||
onClickEdit={this.handleOpen}
|
||||
showVisibility={visibilityCountry !== null}
|
||||
visibility={visibilityCountry}
|
||||
/>
|
||||
<p className="h5">{countryMessages[country]}</p>
|
||||
</React.Fragment>
|
||||
),
|
||||
empty: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader
|
||||
content={intl.formatMessage(messages['profile.country.label'])}
|
||||
/>
|
||||
<EmptyContent onClick={this.handleOpen}>
|
||||
{intl.formatMessage(messages['profile.country.empty'])}
|
||||
</EmptyContent>
|
||||
</React.Fragment>
|
||||
),
|
||||
static: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader
|
||||
content={intl.formatMessage(messages['profile.country.label'])}
|
||||
/>
|
||||
<p className="h5">{countryMessages[country]}</p>
|
||||
</React.Fragment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Country.propTypes = {
|
||||
// It'd be nice to just set this as a defaultProps...
|
||||
// except the class that comes out on the other side of react-redux's
|
||||
// connect() method won't have it anymore. Static properties won't survive
|
||||
// through the higher order function.
|
||||
formId: PropTypes.string.isRequired,
|
||||
|
||||
// From Selector
|
||||
country: PropTypes.string,
|
||||
visibilityCountry: PropTypes.oneOf(['private', 'all_users']),
|
||||
editMode: PropTypes.oneOf(['editing', 'editable', 'empty', 'static']),
|
||||
saveState: PropTypes.string,
|
||||
error: PropTypes.string,
|
||||
sortedCountries: PropTypes.arrayOf(PropTypes.shape({
|
||||
code: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
})).isRequired,
|
||||
countryMessages: PropTypes.objectOf(PropTypes.string).isRequired,
|
||||
|
||||
// Actions
|
||||
changeHandler: PropTypes.func.isRequired,
|
||||
submitHandler: PropTypes.func.isRequired,
|
||||
closeHandler: PropTypes.func.isRequired,
|
||||
openHandler: PropTypes.func.isRequired,
|
||||
|
||||
// i18n
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
Country.defaultProps = {
|
||||
editMode: 'static',
|
||||
saveState: null,
|
||||
country: null,
|
||||
visibilityCountry: 'private',
|
||||
error: null,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
countrySelector,
|
||||
{},
|
||||
)(injectIntl(Country));
|
||||
@@ -1,16 +0,0 @@
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.country.label': {
|
||||
id: 'profile.country.label',
|
||||
defaultMessage: 'Location',
|
||||
description: 'The label for a country in a user profile.',
|
||||
},
|
||||
'profile.country.empty': {
|
||||
id: 'profile.country.empty',
|
||||
defaultMessage: 'Add location',
|
||||
description: 'The affordance to add country location to a user’s profile.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -1,29 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage, FormattedDate } from 'react-intl';
|
||||
|
||||
function DateJoined({ date }) {
|
||||
if (date == null) return null;
|
||||
|
||||
return (
|
||||
<p className="mb-0">
|
||||
<FormattedMessage
|
||||
id="profile.datejoined.member.since"
|
||||
defaultMessage="Member since {year}"
|
||||
description="A label for how long the user has been a member"
|
||||
values={{
|
||||
year: <FormattedDate value={new Date(date)} year="numeric" />,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
DateJoined.propTypes = {
|
||||
date: PropTypes.string,
|
||||
};
|
||||
DateJoined.defaultProps = {
|
||||
date: null,
|
||||
};
|
||||
|
||||
export default DateJoined;
|
||||
@@ -1,186 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n'; // eslint-disable-line
|
||||
import get from 'lodash.get';
|
||||
import { ValidationFormGroup } from '@edx/paragon';
|
||||
|
||||
import messages from './Education.messages';
|
||||
|
||||
// Components
|
||||
import FormControls from './elements/FormControls';
|
||||
import EditableItemHeader from './elements/EditableItemHeader';
|
||||
import EmptyContent from './elements/EmptyContent';
|
||||
import SwitchContent from './elements/SwitchContent';
|
||||
|
||||
// Constants
|
||||
import EDUCATION_LEVELS from '../../constants/education';
|
||||
|
||||
// Selectors
|
||||
import { editableFormSelector } from '../../selectors/ProfilePageSelector';
|
||||
|
||||
class Education extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleClose = this.handleClose.bind(this);
|
||||
this.handleOpen = this.handleOpen.bind(this);
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
const {
|
||||
name,
|
||||
value,
|
||||
} = e.target;
|
||||
this.props.changeHandler(name, value);
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
this.props.submitHandler(this.props.formId);
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
this.props.closeHandler(this.props.formId);
|
||||
}
|
||||
|
||||
handleOpen() {
|
||||
this.props.openHandler(this.props.formId);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
formId, levelOfEducation, visibilityLevelOfEducation, editMode, saveState, error, intl,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<SwitchContent
|
||||
className="mb-5"
|
||||
expression={editMode}
|
||||
cases={{
|
||||
editing: (
|
||||
<div role="dialog" aria-labelledby={`${formId}-label`}>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<ValidationFormGroup
|
||||
for={formId}
|
||||
invalid={error !== null}
|
||||
invalidMessage={error}
|
||||
>
|
||||
<label className="edit-section-header" htmlFor={formId}>
|
||||
{intl.formatMessage(messages['profile.education.education'])}
|
||||
</label>
|
||||
<select
|
||||
className="form-control"
|
||||
id={formId}
|
||||
name={formId}
|
||||
value={levelOfEducation}
|
||||
onChange={this.handleChange}
|
||||
>
|
||||
{EDUCATION_LEVELS.map(level => (
|
||||
<option key={level} value={level}>
|
||||
{intl.formatMessage(get(
|
||||
messages,
|
||||
`profile.education.levels.${level}`,
|
||||
messages['profile.education.levels.o'],
|
||||
))}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</ValidationFormGroup>
|
||||
<FormControls
|
||||
visibilityId="visibilityLevelOfEducation"
|
||||
saveState={saveState}
|
||||
visibility={visibilityLevelOfEducation}
|
||||
cancelHandler={this.handleClose}
|
||||
changeHandler={this.handleChange}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
),
|
||||
editable: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader
|
||||
content={intl.formatMessage(messages['profile.education.education'])}
|
||||
showEditButton
|
||||
onClickEdit={this.handleOpen}
|
||||
showVisibility={visibilityLevelOfEducation !== null}
|
||||
visibility={visibilityLevelOfEducation}
|
||||
/>
|
||||
<p className="h5">
|
||||
{intl.formatMessage(get(
|
||||
messages,
|
||||
`profile.education.levels.${levelOfEducation}`,
|
||||
messages['profile.education.levels.o'],
|
||||
))}
|
||||
</p>
|
||||
</React.Fragment>
|
||||
),
|
||||
empty: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader content={intl.formatMessage(messages['profile.education.education'])} />
|
||||
<EmptyContent onClick={this.handleOpen}>
|
||||
<FormattedMessage
|
||||
id="profile.education.empty"
|
||||
defaultMessage="Add education"
|
||||
description="instructions when the user doesn't have their level of education set"
|
||||
/>
|
||||
</EmptyContent>
|
||||
</React.Fragment>
|
||||
),
|
||||
static: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader content={intl.formatMessage(messages['profile.education.education'])} />
|
||||
<p className="h5">
|
||||
{intl.formatMessage(get(
|
||||
messages,
|
||||
`profile.education.levels.${levelOfEducation}`,
|
||||
messages['profile.education.levels.o'],
|
||||
))}
|
||||
</p>
|
||||
</React.Fragment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Education.propTypes = {
|
||||
// It'd be nice to just set this as a defaultProps...
|
||||
// except the class that comes out on the other side of react-redux's
|
||||
// connect() method won't have it anymore. Static properties won't survive
|
||||
// through the higher order function.
|
||||
formId: PropTypes.string.isRequired,
|
||||
|
||||
// From Selector
|
||||
levelOfEducation: PropTypes.string,
|
||||
visibilityLevelOfEducation: PropTypes.oneOf(['private', 'all_users']),
|
||||
editMode: PropTypes.oneOf(['editing', 'editable', 'empty', 'static']),
|
||||
saveState: PropTypes.string,
|
||||
error: PropTypes.string,
|
||||
|
||||
// Actions
|
||||
changeHandler: PropTypes.func.isRequired,
|
||||
submitHandler: PropTypes.func.isRequired,
|
||||
closeHandler: PropTypes.func.isRequired,
|
||||
openHandler: PropTypes.func.isRequired,
|
||||
|
||||
// i18n
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
Education.defaultProps = {
|
||||
editMode: 'static',
|
||||
saveState: null,
|
||||
levelOfEducation: null,
|
||||
visibilityLevelOfEducation: 'private',
|
||||
error: null,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
editableFormSelector,
|
||||
{},
|
||||
)(injectIntl(Education));
|
||||
@@ -1,56 +0,0 @@
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.education.education': {
|
||||
id: 'profile.education.education',
|
||||
defaultMessage: 'Education',
|
||||
description: 'A section of a user profile',
|
||||
},
|
||||
'profile.education.levels.p': {
|
||||
id: 'profile.education.levels.p',
|
||||
defaultMessage: 'Doctorate',
|
||||
description: 'Selected by the user if their highest level of education is a doctorate degree.',
|
||||
},
|
||||
'profile.education.levels.m': {
|
||||
id: 'profile.education.levels.m',
|
||||
defaultMessage: "Master's or professional degree",
|
||||
description: "Selected by the user if their highest level of education is a master's or professional degree from a college or university.",
|
||||
},
|
||||
'profile.education.levels.b': {
|
||||
id: 'profile.education.levels.b',
|
||||
defaultMessage: "Bachelor's Degree",
|
||||
description: "Selected by the user if their highest level of education is a four year college or university bachelor's degree.",
|
||||
},
|
||||
'profile.education.levels.a': {
|
||||
id: 'profile.education.levels.a',
|
||||
defaultMessage: "Associate's degree",
|
||||
description: "Selected by the user if their highest level of education is an associate's degree. 1-2 years of college or university.",
|
||||
},
|
||||
'profile.education.levels.hs': {
|
||||
id: 'profile.education.levels.hs',
|
||||
defaultMessage: 'Secondary/high school',
|
||||
description: 'Selected by the user if their highest level of education is secondary or high school. 9-12 years of education.',
|
||||
},
|
||||
'profile.education.levels.jhs': {
|
||||
id: 'profile.education.levels.jhs',
|
||||
defaultMessage: 'Junior secondary/junior high/middle school',
|
||||
description: 'Selected by the user if their highest level of education is junior or middle school. 6-8 years of education.',
|
||||
},
|
||||
'profile.education.levels.el': {
|
||||
id: 'profile.education.levels.el',
|
||||
defaultMessage: 'Elementary/primary school',
|
||||
description: 'Selected by the user if their highest level of education is elementary or primary school. 1-5 years of education.',
|
||||
},
|
||||
'profile.education.levels.none': {
|
||||
id: 'profile.education.levels.none',
|
||||
defaultMessage: 'No formal education',
|
||||
description: 'Selected by the user to describe their education.',
|
||||
},
|
||||
'profile.education.levels.o': {
|
||||
id: 'profile.education.levels.o',
|
||||
defaultMessage: 'Other education',
|
||||
description: 'Selected by the user if they have a type of education not described by the other choices.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -1,157 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n'; // eslint-disable-line
|
||||
|
||||
import messages from './Name.messages';
|
||||
|
||||
// Components
|
||||
import FormControls from './elements/FormControls';
|
||||
import EditableItemHeader from './elements/EditableItemHeader';
|
||||
import EmptyContent from './elements/EmptyContent';
|
||||
import SwitchContent from './elements/SwitchContent';
|
||||
|
||||
// Selectors
|
||||
import { editableFormSelector } from '../../selectors/ProfilePageSelector';
|
||||
|
||||
class Name extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleClose = this.handleClose.bind(this);
|
||||
this.handleOpen = this.handleOpen.bind(this);
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
const {
|
||||
name,
|
||||
value,
|
||||
} = e.target;
|
||||
this.props.changeHandler(name, value);
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
this.props.submitHandler(this.props.formId);
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
this.props.closeHandler(this.props.formId);
|
||||
}
|
||||
|
||||
handleOpen() {
|
||||
this.props.openHandler(this.props.formId);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
formId, name, visibilityName, editMode, saveState, intl,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<SwitchContent
|
||||
className="mb-5"
|
||||
expression={editMode}
|
||||
cases={{
|
||||
editing: (
|
||||
<div role="dialog" aria-labelledby={`${formId}-label`}>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<div className="form-group">
|
||||
<EditableItemHeader content={intl.formatMessage(messages['profile.name.full.name'])} />
|
||||
{/*
|
||||
This isn't a mistake - the name field should not be editable. But if it were,
|
||||
you'd find the original code got deleted in the commit which added this comment.
|
||||
-djoy
|
||||
TODO: Relatedly, the plumbing for editing the name field is still in place.
|
||||
Once we're super sure we don't want it back, you could delete the name props and
|
||||
such to fully get rid of it.
|
||||
*/}
|
||||
<p className="h5">{name}</p>
|
||||
<small className="form-text text-muted" id={`${formId}-help-text`}>
|
||||
{intl.formatMessage(messages['profile.name.details'])}
|
||||
</small>
|
||||
</div>
|
||||
<FormControls
|
||||
visibilityId="visibilityName"
|
||||
saveState={saveState}
|
||||
visibility={visibilityName}
|
||||
cancelHandler={this.handleClose}
|
||||
changeHandler={this.handleChange}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
),
|
||||
editable: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader
|
||||
content={intl.formatMessage(messages['profile.name.full.name'])}
|
||||
showEditButton
|
||||
onClickEdit={this.handleOpen}
|
||||
showVisibility={visibilityName !== null}
|
||||
visibility={visibilityName}
|
||||
/>
|
||||
<p className="h5">{name}</p>
|
||||
<small className="form-text text-muted">
|
||||
{intl.formatMessage(messages['profile.name.details'])}
|
||||
</small>
|
||||
</React.Fragment>
|
||||
),
|
||||
empty: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader content={intl.formatMessage(messages['profile.name.full.name'])} />
|
||||
<EmptyContent onClick={this.handleOpen}>
|
||||
{intl.formatMessage(messages['profile.name.empty'])}
|
||||
</EmptyContent>
|
||||
<small className="form-text text-muted">
|
||||
{intl.formatMessage(messages['profile.name.details'])}
|
||||
</small>
|
||||
</React.Fragment>
|
||||
),
|
||||
static: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader content={intl.formatMessage(messages['profile.name.full.name'])} />
|
||||
<p className="h5">{name}</p>
|
||||
</React.Fragment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Name.propTypes = {
|
||||
// It'd be nice to just set this as a defaultProps...
|
||||
// except the class that comes out on the other side of react-redux's
|
||||
// connect() method won't have it anymore. Static properties won't survive
|
||||
// through the higher order function.
|
||||
formId: PropTypes.string.isRequired,
|
||||
|
||||
// From Selector
|
||||
name: PropTypes.string,
|
||||
visibilityName: PropTypes.oneOf(['private', 'all_users']),
|
||||
editMode: PropTypes.oneOf(['editing', 'editable', 'empty', 'static']),
|
||||
saveState: PropTypes.string,
|
||||
|
||||
// Actions
|
||||
changeHandler: PropTypes.func.isRequired,
|
||||
submitHandler: PropTypes.func.isRequired,
|
||||
closeHandler: PropTypes.func.isRequired,
|
||||
openHandler: PropTypes.func.isRequired,
|
||||
|
||||
// i18n
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
Name.defaultProps = {
|
||||
editMode: 'static',
|
||||
saveState: null,
|
||||
name: null,
|
||||
visibilityName: 'private',
|
||||
};
|
||||
|
||||
export default connect(
|
||||
editableFormSelector,
|
||||
{},
|
||||
)(injectIntl(Name));
|
||||
@@ -1,21 +0,0 @@
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.name.full.name': {
|
||||
id: 'profile.name.full.name',
|
||||
defaultMessage: 'Full Name',
|
||||
description: 'A section of a user profile',
|
||||
},
|
||||
'profile.name.details': {
|
||||
id: 'profile.name.details',
|
||||
defaultMessage: 'This is the name that appears in your account and on your certificates.',
|
||||
description: 'Describes the area for a user to update their name.',
|
||||
},
|
||||
'profile.name.empty': {
|
||||
id: 'profile.name.empty',
|
||||
defaultMessage: 'Add name',
|
||||
description: 'The affordance to add a name to a user’s profile.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -1,191 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n'; // eslint-disable-line
|
||||
import { ValidationFormGroup } from '@edx/paragon';
|
||||
|
||||
import messages from './PreferredLanguage.messages';
|
||||
|
||||
// Components
|
||||
import FormControls from './elements/FormControls';
|
||||
import EditableItemHeader from './elements/EditableItemHeader';
|
||||
import EmptyContent from './elements/EmptyContent';
|
||||
import SwitchContent from './elements/SwitchContent';
|
||||
|
||||
// Selectors
|
||||
import { preferredLanguageSelector } from '../../selectors/ProfilePageSelector';
|
||||
|
||||
class PreferredLanguage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleClose = this.handleClose.bind(this);
|
||||
this.handleOpen = this.handleOpen.bind(this);
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
const {
|
||||
name,
|
||||
value: rawValue,
|
||||
} = e.target;
|
||||
let value = rawValue;
|
||||
// Restructure the data.
|
||||
// We deconstruct our value prop in render() so this
|
||||
// changes our data's shape back to match what came in
|
||||
if (name === this.props.formId) {
|
||||
value = [{ code: rawValue }];
|
||||
}
|
||||
|
||||
this.props.changeHandler(name, value);
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
this.props.submitHandler(this.props.formId);
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
this.props.closeHandler(this.props.formId);
|
||||
}
|
||||
|
||||
handleOpen() {
|
||||
this.props.openHandler(this.props.formId);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
formId,
|
||||
languageProficiencies,
|
||||
visibilityLanguageProficiencies,
|
||||
editMode,
|
||||
saveState,
|
||||
error,
|
||||
intl,
|
||||
sortedLanguages,
|
||||
languageMessages,
|
||||
} = this.props;
|
||||
|
||||
const value = languageProficiencies.length ? languageProficiencies[0].code : '';
|
||||
|
||||
return (
|
||||
<SwitchContent
|
||||
className="mb-5"
|
||||
expression={editMode}
|
||||
cases={{
|
||||
editing: (
|
||||
<div role="dialog" aria-labelledby={`${formId}-label`}>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<ValidationFormGroup
|
||||
for={formId}
|
||||
invalid={error !== null}
|
||||
invalidMessage={error}
|
||||
>
|
||||
<label className="edit-section-header" htmlFor={formId}>
|
||||
{intl.formatMessage(messages['profile.preferredlanguage.label'])}
|
||||
</label>
|
||||
<select
|
||||
id={formId}
|
||||
name={formId}
|
||||
className="form-control"
|
||||
value={value}
|
||||
onChange={this.handleChange}
|
||||
>
|
||||
{sortedLanguages.map(({ code, name }) => (
|
||||
<option key={code} value={code}>{name}</option>
|
||||
))}
|
||||
</select>
|
||||
</ValidationFormGroup>
|
||||
<FormControls
|
||||
visibilityId="visibilityLanguageProficiencies"
|
||||
saveState={saveState}
|
||||
visibility={visibilityLanguageProficiencies}
|
||||
cancelHandler={this.handleClose}
|
||||
changeHandler={this.handleChange}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
),
|
||||
editable: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader
|
||||
content={intl.formatMessage(messages['profile.preferredlanguage.label'])}
|
||||
showEditButton
|
||||
onClickEdit={this.handleOpen}
|
||||
showVisibility={visibilityLanguageProficiencies !== null}
|
||||
visibility={visibilityLanguageProficiencies}
|
||||
/>
|
||||
<p className="h5">{languageMessages[value]}</p>
|
||||
</React.Fragment>
|
||||
),
|
||||
empty: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader
|
||||
content={intl.formatMessage(messages['profile.preferredlanguage.label'])}
|
||||
/>
|
||||
<EmptyContent onClick={this.handleOpen}>
|
||||
{intl.formatMessage(messages['profile.preferredlanguage.empty'])}
|
||||
</EmptyContent>
|
||||
</React.Fragment>
|
||||
),
|
||||
static: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader
|
||||
content={intl.formatMessage(messages['profile.preferredlanguage.label'])}
|
||||
/>
|
||||
<p className="h5">{languageMessages[value]}</p>
|
||||
</React.Fragment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PreferredLanguage.propTypes = {
|
||||
// It'd be nice to just set this as a defaultProps...
|
||||
// except the class that comes out on the other side of react-redux's
|
||||
// connect() method won't have it anymore. Static properties won't survive
|
||||
// through the higher order function.
|
||||
formId: PropTypes.string.isRequired,
|
||||
|
||||
// From Selector
|
||||
languageProficiencies: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.shape({ code: PropTypes.string })),
|
||||
// TODO: ProfilePageSelector should supply null values
|
||||
// instead of empty strings when no value exists
|
||||
PropTypes.oneOf(['']),
|
||||
]),
|
||||
visibilityLanguageProficiencies: PropTypes.oneOf(['private', 'all_users']),
|
||||
editMode: PropTypes.oneOf(['editing', 'editable', 'empty', 'static']),
|
||||
saveState: PropTypes.string,
|
||||
error: PropTypes.string,
|
||||
sortedLanguages: PropTypes.arrayOf(PropTypes.shape({
|
||||
code: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
})).isRequired,
|
||||
languageMessages: PropTypes.objectOf(PropTypes.string).isRequired,
|
||||
|
||||
// Actions
|
||||
changeHandler: PropTypes.func.isRequired,
|
||||
submitHandler: PropTypes.func.isRequired,
|
||||
closeHandler: PropTypes.func.isRequired,
|
||||
openHandler: PropTypes.func.isRequired,
|
||||
|
||||
// i18n
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
PreferredLanguage.defaultProps = {
|
||||
editMode: 'static',
|
||||
saveState: null,
|
||||
languageProficiencies: [],
|
||||
visibilityLanguageProficiencies: 'private',
|
||||
error: null,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
preferredLanguageSelector,
|
||||
{},
|
||||
)(injectIntl(PreferredLanguage));
|
||||
@@ -1,16 +0,0 @@
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.preferredlanguage.empty': {
|
||||
id: 'profile.preferredlanguage.empty',
|
||||
defaultMessage: 'Add language',
|
||||
description: 'Instructions when the user doesn’t have a preferred language set.',
|
||||
},
|
||||
'profile.preferredlanguage.label': {
|
||||
id: 'profile.preferredlanguage.label',
|
||||
defaultMessage: 'Primary Language Spoken',
|
||||
description: 'The label for a user’s primary spoken language.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -1,173 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Dropdown } from '@edx/paragon';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n'; // eslint-disable-line
|
||||
|
||||
import { ReactComponent as DefaultAvatar } from '../../assets/avatar.svg';
|
||||
|
||||
import messages from './ProfileAvatar.messages';
|
||||
|
||||
class ProfileAvatar extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.fileInput = React.createRef();
|
||||
this.form = React.createRef();
|
||||
|
||||
this.onClickUpload = this.onClickUpload.bind(this);
|
||||
this.onClickDelete = this.onClickDelete.bind(this);
|
||||
this.onChangeInput = this.onChangeInput.bind(this);
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
}
|
||||
|
||||
onClickUpload() {
|
||||
this.fileInput.current.click();
|
||||
}
|
||||
|
||||
onClickDelete() {
|
||||
this.props.onDelete();
|
||||
}
|
||||
|
||||
onChangeInput() {
|
||||
this.onSubmit();
|
||||
}
|
||||
|
||||
onSubmit(e) {
|
||||
if (e) e.preventDefault();
|
||||
this.props.onSave(new FormData(this.form.current));
|
||||
this.form.current.reset();
|
||||
}
|
||||
|
||||
renderPending() {
|
||||
return (
|
||||
<div
|
||||
className="position-absolute w-100 h-100 d-flex justify-content-center align-items-center rounded-circle"
|
||||
style={{ backgroundColor: 'rgba(0,0,0,.65)' }}
|
||||
>
|
||||
<div className="spinner-border text-primary" role="status" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderMenuContent() {
|
||||
if (this.props.isDefault) {
|
||||
return (
|
||||
<Button
|
||||
className="text-white btn-block btn-sm btn-link"
|
||||
onClick={this.onClickUpload}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="profile.profileavatar.upload-button"
|
||||
defaultMessage="Upload Photo"
|
||||
description="Upload photo button"
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
buttonType="primary"
|
||||
title={(
|
||||
<FormattedMessage
|
||||
id="profile.profileavatar.change-button"
|
||||
defaultMessage="Change"
|
||||
description="Change photo button"
|
||||
/>
|
||||
)}
|
||||
menuItems={[
|
||||
(
|
||||
<button className="dropdown-item" onClick={this.onClickUpload}>
|
||||
<FormattedMessage
|
||||
id="profile.profileavatar.upload-button"
|
||||
defaultMessage="Upload Photo"
|
||||
description="Upload photo button"
|
||||
/>
|
||||
</button>
|
||||
),
|
||||
(
|
||||
<button className="dropdown-item" onClick={this.onClickDelete}>
|
||||
<FormattedMessage
|
||||
id="profile.profileavatar.remove.button"
|
||||
defaultMessage="Remove"
|
||||
description="Remove photo button"
|
||||
/>
|
||||
</button>
|
||||
),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderMenu() {
|
||||
if (!this.props.isEditable) return null;
|
||||
|
||||
return (
|
||||
<div className="profile-avatar-menu-container">
|
||||
{this.renderMenuContent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderAvatar() {
|
||||
const { intl } = this.props;
|
||||
|
||||
return this.props.isDefault ? (
|
||||
<DefaultAvatar className="text-muted" role="img" aria-hidden focusable="false" viewBox="0 0 24 24" />
|
||||
) : (
|
||||
<img
|
||||
className="w-100 h-100 d-block rounded-circle overflow-hidden"
|
||||
style={{ objectFit: 'cover' }}
|
||||
alt={intl.formatMessage(messages['profile.image.alt.attribute'])}
|
||||
src={this.props.src}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="profile-avatar-wrap position-relative">
|
||||
<div className="profile-avatar rounded-circle bg-light">
|
||||
{this.props.savePhotoState === 'pending' ? this.renderPending() : this.renderMenu() }
|
||||
{this.renderAvatar()}
|
||||
</div>
|
||||
<form
|
||||
ref={this.form}
|
||||
onSubmit={this.onSubmit}
|
||||
encType="multipart/form-data"
|
||||
>
|
||||
{/* The name of this input must be 'file' */}
|
||||
<input
|
||||
className="d-none form-control-file"
|
||||
ref={this.fileInput}
|
||||
type="file"
|
||||
name="file"
|
||||
id="photo-file"
|
||||
onChange={this.onChangeInput}
|
||||
accept=".jpg, .jpeg, .png"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default injectIntl(ProfileAvatar);
|
||||
|
||||
ProfileAvatar.propTypes = {
|
||||
src: PropTypes.string,
|
||||
isDefault: PropTypes.bool,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
savePhotoState: PropTypes.oneOf([null, 'pending', 'complete', 'error']),
|
||||
isEditable: PropTypes.bool,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
ProfileAvatar.defaultProps = {
|
||||
src: null,
|
||||
isDefault: true,
|
||||
savePhotoState: null,
|
||||
isEditable: false,
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.image.alt.attribute': {
|
||||
id: 'profile.image.alt.attribute',
|
||||
defaultMessage: 'profile avatar',
|
||||
description: 'Alt attribute for a profile photo',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -1,352 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StatusAlert } from '@edx/paragon';
|
||||
import { connect } from 'react-redux';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faTwitter, faFacebook, faLinkedin } from '@fortawesome/free-brands-svg-icons';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n'; // eslint-disable-line
|
||||
import classNames from 'classnames';
|
||||
|
||||
import messages from './SocialLinks.messages';
|
||||
|
||||
// Components
|
||||
import FormControls from './elements/FormControls';
|
||||
import EditableItemHeader from './elements/EditableItemHeader';
|
||||
import EmptyContent from './elements/EmptyContent';
|
||||
import SwitchContent from './elements/SwitchContent';
|
||||
|
||||
// Selectors
|
||||
import { editableFormSelector } from '../../selectors/ProfilePageSelector';
|
||||
|
||||
const platformDisplayInfo = {
|
||||
facebook: {
|
||||
icon: faFacebook,
|
||||
name: 'Facebook',
|
||||
},
|
||||
twitter: {
|
||||
icon: faTwitter,
|
||||
name: 'Twitter',
|
||||
},
|
||||
linkedin: {
|
||||
icon: faLinkedin,
|
||||
name: 'LinkedIn',
|
||||
},
|
||||
};
|
||||
|
||||
class SocialLinks extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleClose = this.handleClose.bind(this);
|
||||
this.handleOpen = this.handleOpen.bind(this);
|
||||
}
|
||||
|
||||
handleChange(e) {
|
||||
const { name, value } = e.target;
|
||||
|
||||
// The social links are a bit special. If we're updating them, we need to merge them
|
||||
// with any existing social link drafts, essentially sending a fresh copy of the whole
|
||||
// data structure back to the reducer. This helps the reducer stay simple and keeps
|
||||
// special cases out of it, concentrating them here, where they began.
|
||||
if (name !== 'visibilitySocialLinks') {
|
||||
this.props.changeHandler(
|
||||
'socialLinks',
|
||||
this.mergeWithDrafts({
|
||||
platform: name,
|
||||
// If it's an empty string, send it as null.
|
||||
// The empty string is just for the input. We want nulls.
|
||||
socialLink: value,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
this.props.changeHandler(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
mergeWithDrafts(newSocialLink) {
|
||||
const knownPlatforms = ['twitter', 'facebook', 'linkedin'];
|
||||
const updated = [];
|
||||
knownPlatforms.forEach((platform) => {
|
||||
if (newSocialLink.platform === platform) {
|
||||
updated.push(newSocialLink);
|
||||
} else if (this.props.draftSocialLinksByPlatform[platform] !== undefined) {
|
||||
updated.push(this.props.draftSocialLinksByPlatform[platform]);
|
||||
}
|
||||
});
|
||||
return updated;
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
this.props.submitHandler(this.props.formId);
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
this.props.closeHandler(this.props.formId);
|
||||
}
|
||||
|
||||
handleOpen() {
|
||||
this.props.openHandler(this.props.formId);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
socialLinks, visibilitySocialLinks, editMode, saveState, error, intl,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<SwitchContent
|
||||
className="mb-5"
|
||||
expression={editMode}
|
||||
cases={{
|
||||
empty: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader content={intl.formatMessage(messages['profile.sociallinks.social.links'])} />
|
||||
<ul className="list-unstyled">
|
||||
{socialLinks.map(({ platform }) => (
|
||||
<EmptyListItem
|
||||
key={platform}
|
||||
onClick={this.handleOpen}
|
||||
name={platformDisplayInfo[platform].name}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</React.Fragment>
|
||||
),
|
||||
static: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader
|
||||
content={intl.formatMessage(messages['profile.sociallinks.social.links'])}
|
||||
/>
|
||||
<ul className="list-unstyled">
|
||||
{socialLinks
|
||||
.filter(({ socialLink }) => Boolean(socialLink))
|
||||
.map(({ platform, socialLink }) => (
|
||||
<StaticListItem
|
||||
key={platform}
|
||||
name={platformDisplayInfo[platform].name}
|
||||
url={socialLink}
|
||||
platform={platform}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</React.Fragment>
|
||||
),
|
||||
editable: (
|
||||
<React.Fragment>
|
||||
<EditableItemHeader
|
||||
content={intl.formatMessage(messages['profile.sociallinks.social.links'])}
|
||||
showEditButton
|
||||
onClickEdit={this.handleOpen}
|
||||
showVisibility={visibilitySocialLinks !== null}
|
||||
visibility={visibilitySocialLinks}
|
||||
/>
|
||||
<ul className="list-unstyled">
|
||||
{socialLinks.map(({ platform, socialLink }) => (
|
||||
<EditableListItem
|
||||
key={platform}
|
||||
platform={platform}
|
||||
name={platformDisplayInfo[platform].name}
|
||||
url={socialLink}
|
||||
onClickEmptyContent={this.handleOpen}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</React.Fragment>
|
||||
),
|
||||
editing: (
|
||||
<div role="dialog" aria-labelledby="social-links-label">
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<EditableItemHeader
|
||||
headingId="social-links-label"
|
||||
content={intl.formatMessage(messages['profile.sociallinks.social.links'])}
|
||||
/>
|
||||
{/* TODO: Replace this alert with per-field errors. Needs API update. */}
|
||||
<div id="social-error-feedback">
|
||||
{error !== null ? <StatusAlert alertType="danger" dialog={error} dismissible={false} open /> : null}
|
||||
</div>
|
||||
<ul className="list-unstyled">
|
||||
{socialLinks.map(({ platform, socialLink }) => (
|
||||
<EditingListItem
|
||||
key={platform}
|
||||
name={platformDisplayInfo[platform].name}
|
||||
platform={platform}
|
||||
value={socialLink}
|
||||
/* TODO: Per-field errors: error={error !== null ? error[platform] : null} */
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<FormControls
|
||||
visibilityId="visibilitySocialLinks"
|
||||
saveState={saveState}
|
||||
visibility={visibilitySocialLinks}
|
||||
cancelHandler={this.handleClose}
|
||||
changeHandler={this.handleChange}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SocialLinks.propTypes = {
|
||||
// It'd be nice to just set this as a defaultProps...
|
||||
// except the class that comes out on the other side of react-redux's
|
||||
// connect() method won't have it anymore. Static properties won't survive
|
||||
// through the higher order function.
|
||||
formId: PropTypes.string.isRequired,
|
||||
|
||||
// From Selector
|
||||
socialLinks: PropTypes.arrayOf(PropTypes.shape({
|
||||
platform: PropTypes.string,
|
||||
socialLink: PropTypes.string,
|
||||
})).isRequired,
|
||||
draftSocialLinksByPlatform: PropTypes.objectOf(PropTypes.shape({
|
||||
platform: PropTypes.string,
|
||||
socialLink: PropTypes.string,
|
||||
})),
|
||||
visibilitySocialLinks: PropTypes.oneOf(['private', 'all_users']),
|
||||
editMode: PropTypes.oneOf(['editing', 'editable', 'empty', 'static']),
|
||||
saveState: PropTypes.string,
|
||||
error: PropTypes.string,
|
||||
|
||||
// Actions
|
||||
changeHandler: PropTypes.func.isRequired,
|
||||
submitHandler: PropTypes.func.isRequired,
|
||||
closeHandler: PropTypes.func.isRequired,
|
||||
openHandler: PropTypes.func.isRequired,
|
||||
|
||||
// i18n
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
SocialLinks.defaultProps = {
|
||||
editMode: 'static',
|
||||
saveState: null,
|
||||
draftSocialLinksByPlatform: {},
|
||||
visibilitySocialLinks: 'private',
|
||||
error: null,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
editableFormSelector,
|
||||
{},
|
||||
)(injectIntl(SocialLinks));
|
||||
|
||||
function SocialLink({ url, name, platform }) {
|
||||
return (
|
||||
<a href={url} className="font-weight-bold">
|
||||
<FontAwesomeIcon className="mr-2" icon={platformDisplayInfo[platform].icon} />
|
||||
{name}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
SocialLink.propTypes = {
|
||||
url: PropTypes.string.isRequired,
|
||||
platform: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
function EditableListItem({
|
||||
url, platform, onClickEmptyContent, name,
|
||||
}) {
|
||||
const linkDisplay = url ? (
|
||||
<SocialLink name={name} url={url} platform={platform} />
|
||||
) : (
|
||||
<EmptyContent onClick={onClickEmptyContent}>Add {name}</EmptyContent>
|
||||
);
|
||||
|
||||
return <li className="form-group">{linkDisplay}</li>;
|
||||
}
|
||||
|
||||
EditableListItem.propTypes = {
|
||||
url: PropTypes.string,
|
||||
platform: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
onClickEmptyContent: PropTypes.func,
|
||||
};
|
||||
EditableListItem.defaultProps = {
|
||||
url: null,
|
||||
onClickEmptyContent: null,
|
||||
};
|
||||
|
||||
function EditingListItem({
|
||||
platform, name, value, onChange, error,
|
||||
}) {
|
||||
return (
|
||||
<li className="form-group">
|
||||
<label htmlFor={`social-${platform}`}>{name}</label>
|
||||
<input
|
||||
className={classNames('form-control', { 'is-invalid': Boolean(error) })}
|
||||
type="text"
|
||||
id={`social-${platform}`}
|
||||
name={platform}
|
||||
value={value || ''}
|
||||
onChange={onChange}
|
||||
aria-describedby="social-error-feedback"
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
EditingListItem.propTypes = {
|
||||
platform: PropTypes.string.isRequired,
|
||||
value: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
error: PropTypes.string,
|
||||
};
|
||||
|
||||
EditingListItem.defaultProps = {
|
||||
value: null,
|
||||
error: null,
|
||||
};
|
||||
|
||||
function EmptyListItem({ onClick, name }) {
|
||||
return (
|
||||
<li className="mb-4">
|
||||
<EmptyContent onClick={onClick}>
|
||||
<FormattedMessage
|
||||
id="profile.sociallinks.add"
|
||||
defaultMessage="Add {network}"
|
||||
values={{
|
||||
network: name,
|
||||
}}
|
||||
description="{network} is the name of a social network such as Facebook or Twitter"
|
||||
/>
|
||||
</EmptyContent>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
EmptyListItem.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
function StaticListItem({ name, url, platform }) {
|
||||
return (
|
||||
<li className="mb-2">
|
||||
<SocialLink name={name} url={url} platform={platform} />
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
StaticListItem.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
url: PropTypes.string,
|
||||
platform: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
StaticListItem.defaultProps = {
|
||||
url: null,
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.sociallinks.social.links': {
|
||||
id: 'profile.sociallinks.social.links',
|
||||
defaultMessage: 'Social Links',
|
||||
description: 'A section of a user profile',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -1,40 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n'; // eslint-disable-line
|
||||
import { Button } from '@edx/paragon';
|
||||
|
||||
import messages from './EditButton.messages';
|
||||
|
||||
function EditButton({
|
||||
onClick, className, style, intl,
|
||||
}) {
|
||||
return (
|
||||
<Button
|
||||
className={classNames('btn-sm btn-link', className)}
|
||||
onClick={onClick}
|
||||
style={style}
|
||||
>
|
||||
<FontAwesomeIcon className="mr-1" icon={faPencilAlt} />
|
||||
{intl.formatMessage(messages['profile.editbutton.edit'])}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export default injectIntl(EditButton);
|
||||
|
||||
EditButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object, // eslint-disable-line
|
||||
|
||||
// i18n
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
EditButton.defaultProps = {
|
||||
className: null,
|
||||
style: null,
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.editbutton.edit': {
|
||||
id: 'profile.editbutton.edit',
|
||||
defaultMessage: 'Edit',
|
||||
description: 'A button label',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -1,47 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import EditButton from './EditButton';
|
||||
import { Visibility } from './Visibility';
|
||||
|
||||
|
||||
function EditableItemHeader({
|
||||
content,
|
||||
showVisibility,
|
||||
visibility,
|
||||
showEditButton,
|
||||
onClickEdit,
|
||||
headingId,
|
||||
}) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="editable-item-header mb-2">
|
||||
<h2 className="edit-section-header" id={headingId}>
|
||||
{content}
|
||||
{showEditButton ? <EditButton style={{ marginTop: '-.35rem' }} className="float-right px-0" onClick={onClickEdit} /> : null}
|
||||
</h2>
|
||||
{showVisibility ? <p className="mb-0"><Visibility to={visibility} /></p> : null}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditableItemHeader;
|
||||
|
||||
EditableItemHeader.propTypes = {
|
||||
onClickEdit: PropTypes.func,
|
||||
showVisibility: PropTypes.bool,
|
||||
showEditButton: PropTypes.bool,
|
||||
content: PropTypes.node,
|
||||
visibility: PropTypes.oneOf(['private', 'all_users']),
|
||||
headingId: PropTypes.string,
|
||||
};
|
||||
|
||||
EditableItemHeader.defaultProps = {
|
||||
onClickEdit: () => {},
|
||||
showVisibility: false,
|
||||
showEditButton: false,
|
||||
content: '',
|
||||
visibility: 'private',
|
||||
headingId: null,
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
function EmptyContent({ children, onClick, showPlusIcon }) {
|
||||
return (
|
||||
<div>
|
||||
{onClick ? (
|
||||
<button
|
||||
className="pl-0 text-left btn btn-link"
|
||||
onClick={onClick}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter') onClick(); }}
|
||||
tabIndex={0}
|
||||
>
|
||||
{showPlusIcon ? <FontAwesomeIcon size="xs" className="mr-2" icon={faPlus} /> : null}
|
||||
{children}
|
||||
</button>
|
||||
) : children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default EmptyContent;
|
||||
|
||||
EmptyContent.propTypes = {
|
||||
onClick: PropTypes.func,
|
||||
children: PropTypes.node,
|
||||
showPlusIcon: PropTypes.bool,
|
||||
};
|
||||
|
||||
EmptyContent.defaultProps = {
|
||||
onClick: null,
|
||||
children: null,
|
||||
showPlusIcon: true,
|
||||
};
|
||||
@@ -1,77 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, StatefulButton } from '@edx/paragon';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n'; // eslint-disable-line
|
||||
|
||||
import messages from './FormControls.messages';
|
||||
|
||||
import { VisibilitySelect } from './Visibility';
|
||||
|
||||
function FormControls({
|
||||
cancelHandler, changeHandler, visibility, visibilityId, saveState, intl,
|
||||
}) {
|
||||
// Eliminate error/failed state for save button
|
||||
const buttonState = saveState === 'error' ? null : saveState;
|
||||
|
||||
return (
|
||||
<div className="d-flex flex-row-reverse flex-wrap justify-content-end align-items-center">
|
||||
<div className="form-group d-flex flex-wrap">
|
||||
<label className="col-form-label" htmlFor={visibilityId}>
|
||||
{intl.formatMessage(messages['profile.formcontrols.who.can.see'])}
|
||||
</label>
|
||||
<VisibilitySelect
|
||||
id={visibilityId}
|
||||
className="d-flex align-items-center"
|
||||
type="select"
|
||||
name={visibilityId}
|
||||
value={visibility}
|
||||
onChange={changeHandler}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group flex-shrink-0 mr-auto">
|
||||
<StatefulButton
|
||||
type="submit"
|
||||
className="btn-primary"
|
||||
state={buttonState}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['profile.formcontrols.button.save']),
|
||||
pending: intl.formatMessage(messages['profile.formcontrols.button.saving']),
|
||||
complete: intl.formatMessage(messages['profile.formcontrols.button.saved']),
|
||||
}}
|
||||
onClick={(e) => {
|
||||
// Swallow clicks if the state is pending.
|
||||
// We do this instead of disabling the button to prevent
|
||||
// it from losing focus (disabled elements cannot have focus).
|
||||
// Disabling it would causes upstream issues in focus management.
|
||||
// Swallowing the onSubmit event on the form would be better, but
|
||||
// we would have to add that logic for every field given our
|
||||
// current structure of the application.
|
||||
if (buttonState === 'pending') e.preventDefault();
|
||||
}}
|
||||
disabledStates={[]}
|
||||
/>
|
||||
<Button className="btn-link" onClick={cancelHandler}>
|
||||
{intl.formatMessage(messages['profile.formcontrols.button.cancel'])}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default injectIntl(FormControls);
|
||||
|
||||
FormControls.propTypes = {
|
||||
saveState: PropTypes.oneOf([null, 'pending', 'complete', 'error']),
|
||||
visibility: PropTypes.oneOf(['private', 'all_users']),
|
||||
visibilityId: PropTypes.string.isRequired,
|
||||
cancelHandler: PropTypes.func.isRequired,
|
||||
changeHandler: PropTypes.func.isRequired,
|
||||
|
||||
// i18n
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
FormControls.defaultProps = {
|
||||
visibility: 'private',
|
||||
saveState: null,
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.formcontrols.who.can.see': {
|
||||
id: 'profile.formcontrols.who.can.see',
|
||||
defaultMessage: 'Who can see this:',
|
||||
description: 'What users can see this area?',
|
||||
},
|
||||
'profile.formcontrols.button.cancel': {
|
||||
id: 'profile.formcontrols.button.cancel',
|
||||
defaultMessage: 'Cancel',
|
||||
description: 'A button label',
|
||||
},
|
||||
'profile.formcontrols.button.save': {
|
||||
id: 'profile.formcontrols.button.save',
|
||||
defaultMessage: 'Save',
|
||||
description: 'A button label',
|
||||
},
|
||||
'profile.formcontrols.button.saving': {
|
||||
id: 'profile.formcontrols.button.saving',
|
||||
defaultMessage: 'Saving',
|
||||
description: 'A button label',
|
||||
},
|
||||
'profile.formcontrols.button.saved': {
|
||||
id: 'profile.formcontrols.button.saved',
|
||||
defaultMessage: 'Saved',
|
||||
description: 'A button label',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -1,65 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { TransitionReplace } from '@edx/paragon';
|
||||
|
||||
|
||||
const onChildExit = (htmlNode) => {
|
||||
// If the leaving child has focus, take control and redirect it
|
||||
if (htmlNode.contains(document.activeElement)) {
|
||||
// Get the newly entering sibling.
|
||||
// It's the previousSibling, but not for any explicit reason. So checking for both.
|
||||
const enteringChild = htmlNode.previousSibling || htmlNode.nextSibling;
|
||||
|
||||
// There's no replacement, do nothing.
|
||||
if (!enteringChild) return;
|
||||
|
||||
// Get all the focusable elements in the entering child and focus the first one
|
||||
const focusableElements = enteringChild.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
||||
if (focusableElements.length) {
|
||||
focusableElements[0].focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function SwitchContent({ expression, cases, className }) {
|
||||
const getContent = (caseKey) => {
|
||||
if (cases[caseKey]) {
|
||||
if (typeof cases[caseKey] === 'string') {
|
||||
return getContent(cases[caseKey]);
|
||||
}
|
||||
return React.cloneElement(cases[caseKey], { key: caseKey });
|
||||
} else if (cases.default) {
|
||||
if (typeof cases.default === 'string') {
|
||||
return getContent(cases.default);
|
||||
}
|
||||
React.cloneElement(cases.default, { key: 'default' });
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<TransitionReplace
|
||||
className={className}
|
||||
onChildExit={onChildExit}
|
||||
>
|
||||
{getContent(expression)}
|
||||
</TransitionReplace>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
SwitchContent.propTypes = {
|
||||
expression: PropTypes.string,
|
||||
cases: PropTypes.objectOf(PropTypes.node).isRequired,
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
SwitchContent.defaultProps = {
|
||||
expression: null,
|
||||
className: null,
|
||||
};
|
||||
|
||||
|
||||
export default SwitchContent;
|
||||
@@ -1,80 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n'; // eslint-disable-line
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faEyeSlash, faEye } from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
import messages from './Visibility.messages';
|
||||
|
||||
|
||||
function Visibility({ to, intl }) {
|
||||
const icon = to === 'private' ? faEyeSlash : faEye;
|
||||
const label = to === 'private' ?
|
||||
intl.formatMessage(messages['profile.visibility.who.just.me']) :
|
||||
intl.formatMessage(messages['profile.visibility.who.everyone']);
|
||||
|
||||
return (
|
||||
<span className="ml-auto small text-muted">
|
||||
<FontAwesomeIcon icon={icon} /> {label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
Visibility.propTypes = {
|
||||
to: PropTypes.oneOf(['private', 'all_users']),
|
||||
|
||||
// i18n
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
Visibility.defaultProps = {
|
||||
to: 'private',
|
||||
};
|
||||
|
||||
|
||||
function VisibilitySelect({ intl, className, ...props }) {
|
||||
const { value } = props;
|
||||
const icon = value === 'private' ? faEyeSlash : faEye;
|
||||
|
||||
return (
|
||||
<span className={className}>
|
||||
<span className="d-inline-block ml-1 mr-2" style={{ width: '1.5rem' }}>
|
||||
<FontAwesomeIcon icon={icon} />
|
||||
</span>
|
||||
<select className="d-inline-block w-auto form-control" {...props}>
|
||||
<option key="private" value="private">
|
||||
{intl.formatMessage(messages['profile.visibility.who.just.me'])}
|
||||
</option>
|
||||
<option key="all_users" value="all_users">
|
||||
{intl.formatMessage(messages['profile.visibility.who.everyone'])}
|
||||
</option>
|
||||
</select>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
VisibilitySelect.propTypes = {
|
||||
id: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
value: PropTypes.oneOf(['private', 'all_users']),
|
||||
onChange: PropTypes.func,
|
||||
|
||||
// i18n
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
VisibilitySelect.defaultProps = {
|
||||
id: null,
|
||||
className: null,
|
||||
name: 'visibility',
|
||||
value: null,
|
||||
onChange: null,
|
||||
};
|
||||
|
||||
const intlVisibility = injectIntl(Visibility);
|
||||
const intlVisibilitySelect = injectIntl(VisibilitySelect);
|
||||
|
||||
|
||||
export {
|
||||
intlVisibility as Visibility,
|
||||
intlVisibilitySelect as VisibilitySelect,
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
'profile.visibility.who.just.me': {
|
||||
id: 'profile.visibility.who.just.me',
|
||||
defaultMessage: 'Just me',
|
||||
description: 'What users can see this area?',
|
||||
},
|
||||
'profile.visibility.who.everyone': {
|
||||
id: 'profile.visibility.who.everyone',
|
||||
defaultMessage: 'Everyone on edX',
|
||||
description: 'What users can see this area?',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -1,42 +0,0 @@
|
||||
module.exports = {
|
||||
authentication: {
|
||||
userId: 9,
|
||||
username: 'staff'
|
||||
},
|
||||
userAccount: {
|
||||
loading: false,
|
||||
error: null,
|
||||
username: null,
|
||||
email: null,
|
||||
bio: null,
|
||||
name: null,
|
||||
country: null,
|
||||
socialLinks: null,
|
||||
profileImage: {
|
||||
imageUrlMedium: null,
|
||||
imageUrlLarge: null
|
||||
},
|
||||
levelOfEducation: null
|
||||
},
|
||||
profilePage: {
|
||||
errors: {},
|
||||
saveState: null,
|
||||
savePhotoState: null,
|
||||
currentlyEditingField: null,
|
||||
account: {
|
||||
socialLinks: []
|
||||
},
|
||||
preferences: {},
|
||||
courseCertificates: [],
|
||||
drafts: {},
|
||||
isLoadingProfile: true
|
||||
},
|
||||
router: {
|
||||
location: {
|
||||
pathname: '/u/verified',
|
||||
search: '',
|
||||
hash: ''
|
||||
},
|
||||
action: 'POP'
|
||||
}
|
||||
};
|
||||
@@ -1,138 +0,0 @@
|
||||
module.exports = {
|
||||
authentication: {
|
||||
userId: 9,
|
||||
username: 'staff'
|
||||
},
|
||||
userAccount: {
|
||||
loading: false,
|
||||
error: null,
|
||||
username: 'staff',
|
||||
email: 'staff@example.com',
|
||||
bio: 'This is my bio',
|
||||
name: 'Lemon Seltzer',
|
||||
country: 'ME',
|
||||
socialLinks: [
|
||||
{
|
||||
platform: 'facebook',
|
||||
socialLink: 'https://www.facebook.com/aloha'
|
||||
},
|
||||
{
|
||||
platform: 'twitter',
|
||||
socialLink: 'https://www.twitter.com/ALOHA'
|
||||
}
|
||||
],
|
||||
profileImage: {
|
||||
imageUrlFull: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_500.jpg?v=1552495012',
|
||||
imageUrlLarge: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_120.jpg?v=1552495012',
|
||||
imageUrlMedium: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_50.jpg?v=1552495012',
|
||||
imageUrlSmall: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_30.jpg?v=1552495012',
|
||||
hasImage: true
|
||||
},
|
||||
levelOfEducation: 'el',
|
||||
mailingAddress: null,
|
||||
extendedProfile: [],
|
||||
dateJoined: '2017-06-07T00:44:23Z',
|
||||
accomplishmentsShared: false,
|
||||
isActive: true,
|
||||
yearOfBirth: 1901,
|
||||
goals: null,
|
||||
languageProficiencies: [
|
||||
{
|
||||
code: 'yo'
|
||||
}
|
||||
],
|
||||
courseCertificates: null,
|
||||
requiresParentalConsent: false,
|
||||
secondaryEmail: null,
|
||||
timeZone: null,
|
||||
gender: null,
|
||||
accountPrivacy: 'custom'
|
||||
},
|
||||
profilePage: {
|
||||
errors: {},
|
||||
saveState: 'pending',
|
||||
savePhotoState: null,
|
||||
currentlyEditingField: 'bio',
|
||||
account: {
|
||||
mailingAddress: null,
|
||||
profileImage: {
|
||||
imageUrlFull: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_500.jpg?v=1552495012',
|
||||
imageUrlLarge: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_120.jpg?v=1552495012',
|
||||
imageUrlMedium: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_50.jpg?v=1552495012',
|
||||
imageUrlSmall: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_30.jpg?v=1552495012',
|
||||
hasImage: true
|
||||
},
|
||||
extendedProfile: [],
|
||||
dateJoined: '2017-06-07T00:44:23Z',
|
||||
accomplishmentsShared: false,
|
||||
email: 'staff@example.com',
|
||||
username: 'staff',
|
||||
bio: 'This is my bio',
|
||||
isActive: true,
|
||||
yearOfBirth: 1901,
|
||||
goals: null,
|
||||
languageProficiencies: [
|
||||
{
|
||||
code: 'yo'
|
||||
}
|
||||
],
|
||||
courseCertificates: null,
|
||||
requiresParentalConsent: false,
|
||||
name: 'Lemon Seltzer',
|
||||
secondaryEmail: null,
|
||||
country: 'ME',
|
||||
socialLinks: [
|
||||
{
|
||||
platform: 'facebook',
|
||||
socialLink: 'https://www.facebook.com/aloha'
|
||||
},
|
||||
{
|
||||
platform: 'twitter',
|
||||
socialLink: 'https://www.twitter.com/ALOHA'
|
||||
}
|
||||
],
|
||||
timeZone: null,
|
||||
levelOfEducation: 'el',
|
||||
gender: null,
|
||||
accountPrivacy: 'custom'
|
||||
},
|
||||
preferences: {
|
||||
visibilityUserLocation: 'all_users',
|
||||
visibilitySocialLinks: 'all_users',
|
||||
visibilityCertificates: 'private',
|
||||
visibilityLevelOfEducation: 'private',
|
||||
visibilityCourseCertificates: 'all_users',
|
||||
prefLang: 'en',
|
||||
visibilityBio: 'all_users',
|
||||
visibilityName: 'private',
|
||||
visibilityLanguageProficiencies: 'all_users',
|
||||
visibilityCountry: 'all_users',
|
||||
accountPrivacy: 'custom'
|
||||
},
|
||||
courseCertificates: [
|
||||
{
|
||||
username: 'staff',
|
||||
status: 'downloadable',
|
||||
courseDisplayName: 'edX Demonstration Course',
|
||||
grade: '0.89',
|
||||
courseId: 'course-v1:edX+DemoX+Demo_Course',
|
||||
courseOrganization: 'edX',
|
||||
modifiedDate: '2019-03-04T19:31:39.930255Z',
|
||||
isPassing: true,
|
||||
downloadUrl: 'http://www.example.com/',
|
||||
certificateType: 'verified',
|
||||
createdDate: '2019-03-04T19:31:39.896806Z'
|
||||
}
|
||||
],
|
||||
drafts: {},
|
||||
isLoadingProfile: false
|
||||
},
|
||||
router: {
|
||||
location: {
|
||||
pathname: '/u/staff',
|
||||
search: '',
|
||||
hash: ''
|
||||
},
|
||||
action: 'POP'
|
||||
}
|
||||
};
|
||||
@@ -1,99 +0,0 @@
|
||||
module.exports = {
|
||||
authentication: {
|
||||
userId: 9,
|
||||
username: 'staff'
|
||||
},
|
||||
userAccount: {
|
||||
loading: false,
|
||||
error: null,
|
||||
username: 'staff',
|
||||
email: 'staff@example.com',
|
||||
bio: 'This is my bio',
|
||||
name: 'Lemon Seltzer',
|
||||
country: 'ME',
|
||||
socialLinks: [
|
||||
{
|
||||
platform: 'facebook',
|
||||
socialLink: 'https://www.facebook.com/aloha'
|
||||
},
|
||||
{
|
||||
platform: 'twitter',
|
||||
socialLink: 'https://www.twitter.com/ALOHA'
|
||||
}
|
||||
],
|
||||
profileImage: {
|
||||
imageUrlFull: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_500.jpg?v=1552495012',
|
||||
imageUrlLarge: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_120.jpg?v=1552495012',
|
||||
imageUrlMedium: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_50.jpg?v=1552495012',
|
||||
imageUrlSmall: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_30.jpg?v=1552495012',
|
||||
hasImage: true
|
||||
},
|
||||
levelOfEducation: 'el',
|
||||
mailingAddress: null,
|
||||
extendedProfile: [],
|
||||
dateJoined: '2017-06-07T00:44:23Z',
|
||||
accomplishmentsShared: false,
|
||||
isActive: true,
|
||||
yearOfBirth: 1901,
|
||||
goals: null,
|
||||
languageProficiencies: [
|
||||
{
|
||||
code: 'yo'
|
||||
}
|
||||
],
|
||||
courseCertificates: null,
|
||||
requiresParentalConsent: false,
|
||||
secondaryEmail: null,
|
||||
timeZone: null,
|
||||
gender: null,
|
||||
accountPrivacy: 'custom'
|
||||
},
|
||||
profilePage: {
|
||||
errors: {},
|
||||
saveState: null,
|
||||
savePhotoState: null,
|
||||
currentlyEditingField: null,
|
||||
account: {
|
||||
mailingAddress: null,
|
||||
profileImage: {
|
||||
imageUrlFull: 'http://localhost:18000/static/images/profiles/default_500.png',
|
||||
imageUrlLarge: 'http://localhost:18000/static/images/profiles/default_120.png',
|
||||
imageUrlMedium: 'http://localhost:18000/static/images/profiles/default_50.png',
|
||||
imageUrlSmall: 'http://localhost:18000/static/images/profiles/default_30.png',
|
||||
hasImage: false
|
||||
},
|
||||
extendedProfile: [],
|
||||
dateJoined: '2017-06-07T00:44:19Z',
|
||||
accomplishmentsShared: false,
|
||||
email: 'verified@example.com',
|
||||
username: 'verified',
|
||||
bio: null,
|
||||
isActive: true,
|
||||
yearOfBirth: null,
|
||||
goals: null,
|
||||
languageProficiencies: [],
|
||||
courseCertificates: null,
|
||||
requiresParentalConsent: true,
|
||||
name: '',
|
||||
secondaryEmail: null,
|
||||
country: null,
|
||||
socialLinks: [],
|
||||
timeZone: null,
|
||||
levelOfEducation: null,
|
||||
gender: null,
|
||||
accountPrivacy: 'private'
|
||||
},
|
||||
preferences: {},
|
||||
courseCertificates: [],
|
||||
drafts: {},
|
||||
isLoadingProfile: false
|
||||
},
|
||||
router: {
|
||||
location: {
|
||||
pathname: '/u/verified',
|
||||
search: '',
|
||||
hash: ''
|
||||
},
|
||||
action: 'POP'
|
||||
}
|
||||
};
|
||||
@@ -1,138 +0,0 @@
|
||||
module.exports = {
|
||||
authentication: {
|
||||
userId: 9,
|
||||
username: 'staff'
|
||||
},
|
||||
userAccount: {
|
||||
loading: false,
|
||||
error: null,
|
||||
username: 'staff',
|
||||
email: 'staff@example.com',
|
||||
bio: 'This is my bio',
|
||||
name: 'Lemon Seltzer',
|
||||
country: 'ME',
|
||||
socialLinks: [
|
||||
{
|
||||
platform: 'facebook',
|
||||
socialLink: 'https://www.facebook.com/aloha'
|
||||
},
|
||||
{
|
||||
platform: 'twitter',
|
||||
socialLink: 'https://www.twitter.com/ALOHA'
|
||||
}
|
||||
],
|
||||
profileImage: {
|
||||
imageUrlFull: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_500.jpg?v=1552495012',
|
||||
imageUrlLarge: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_120.jpg?v=1552495012',
|
||||
imageUrlMedium: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_50.jpg?v=1552495012',
|
||||
imageUrlSmall: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_30.jpg?v=1552495012',
|
||||
hasImage: true
|
||||
},
|
||||
levelOfEducation: 'el',
|
||||
mailingAddress: null,
|
||||
extendedProfile: [],
|
||||
dateJoined: '2017-06-07T00:44:23Z',
|
||||
accomplishmentsShared: false,
|
||||
isActive: true,
|
||||
yearOfBirth: 1901,
|
||||
goals: null,
|
||||
languageProficiencies: [
|
||||
{
|
||||
code: 'yo'
|
||||
}
|
||||
],
|
||||
courseCertificates: null,
|
||||
requiresParentalConsent: false,
|
||||
secondaryEmail: null,
|
||||
timeZone: null,
|
||||
gender: null,
|
||||
accountPrivacy: 'custom'
|
||||
},
|
||||
profilePage: {
|
||||
errors: {},
|
||||
saveState: null,
|
||||
savePhotoState: null,
|
||||
currentlyEditingField: null,
|
||||
account: {
|
||||
mailingAddress: null,
|
||||
profileImage: {
|
||||
imageUrlFull: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_500.jpg?v=1552495012',
|
||||
imageUrlLarge: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_120.jpg?v=1552495012',
|
||||
imageUrlMedium: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_50.jpg?v=1552495012',
|
||||
imageUrlSmall: 'http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_30.jpg?v=1552495012',
|
||||
hasImage: true
|
||||
},
|
||||
extendedProfile: [],
|
||||
dateJoined: '2017-06-07T00:44:23Z',
|
||||
accomplishmentsShared: false,
|
||||
email: 'staff@example.com',
|
||||
username: 'staff',
|
||||
bio: 'This is my bio',
|
||||
isActive: true,
|
||||
yearOfBirth: 1901,
|
||||
goals: null,
|
||||
languageProficiencies: [
|
||||
{
|
||||
code: 'yo'
|
||||
}
|
||||
],
|
||||
courseCertificates: null,
|
||||
requiresParentalConsent: false,
|
||||
name: 'Lemon Seltzer',
|
||||
secondaryEmail: null,
|
||||
country: 'ME',
|
||||
socialLinks: [
|
||||
{
|
||||
platform: 'facebook',
|
||||
socialLink: 'https://www.facebook.com/aloha'
|
||||
},
|
||||
{
|
||||
platform: 'twitter',
|
||||
socialLink: 'https://www.twitter.com/ALOHA'
|
||||
}
|
||||
],
|
||||
timeZone: null,
|
||||
levelOfEducation: 'el',
|
||||
gender: null,
|
||||
accountPrivacy: 'custom'
|
||||
},
|
||||
preferences: {
|
||||
visibilityUserLocation: 'all_users',
|
||||
visibilitySocialLinks: 'all_users',
|
||||
visibilityCertificates: 'private',
|
||||
visibilityLevelOfEducation: 'private',
|
||||
visibilityCourseCertificates: 'all_users',
|
||||
prefLang: 'en',
|
||||
visibilityBio: 'all_users',
|
||||
visibilityName: 'private',
|
||||
visibilityLanguageProficiencies: 'all_users',
|
||||
visibilityCountry: 'all_users',
|
||||
accountPrivacy: 'custom'
|
||||
},
|
||||
courseCertificates: [
|
||||
{
|
||||
username: 'staff',
|
||||
status: 'downloadable',
|
||||
courseDisplayName: 'edX Demonstration Course',
|
||||
grade: '0.89',
|
||||
courseId: 'course-v1:edX+DemoX+Demo_Course',
|
||||
courseOrganization: 'edX',
|
||||
modifiedDate: '2019-03-04T19:31:39.930255Z',
|
||||
isPassing: true,
|
||||
downloadUrl: 'http://www.example.com/',
|
||||
certificateType: 'verified',
|
||||
createdDate: '2019-03-04T19:31:39.896806Z'
|
||||
}
|
||||
],
|
||||
drafts: {},
|
||||
isLoadingProfile: false
|
||||
},
|
||||
router: {
|
||||
location: {
|
||||
pathname: '/u/staff',
|
||||
search: '',
|
||||
hash: ''
|
||||
},
|
||||
action: 'POP'
|
||||
}
|
||||
};
|
||||
@@ -1,2276 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ProfilePage /> Renders correctly in various states app loading 1`] = `
|
||||
<div>
|
||||
<div
|
||||
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
|
||||
/>
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center flex-column"
|
||||
style={
|
||||
Object {
|
||||
"height": "50vh",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="spinner-border text-primary"
|
||||
role="status"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<ProfilePage /> Renders correctly in various states viewing other profile 1`] = `
|
||||
<div
|
||||
className="profile-page"
|
||||
>
|
||||
<div
|
||||
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
|
||||
/>
|
||||
<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"
|
||||
>
|
||||
<div
|
||||
className="profile-avatar-wrap position-relative"
|
||||
>
|
||||
<div
|
||||
className="profile-avatar rounded-circle bg-light"
|
||||
>
|
||||
<IconMock
|
||||
aria-hidden={true}
|
||||
className="text-muted"
|
||||
focusable="false"
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
/>
|
||||
</div>
|
||||
<form
|
||||
encType="multipart/form-data"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<input
|
||||
accept=".jpg, .jpeg, .png"
|
||||
className="d-none form-control-file"
|
||||
id="photo-file"
|
||||
name="file"
|
||||
onChange={[Function]}
|
||||
type="file"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="col pl-0"
|
||||
>
|
||||
<div
|
||||
className="d-md-none"
|
||||
>
|
||||
<h1
|
||||
className="h2 mb-0 font-weight-bold"
|
||||
>
|
||||
verified
|
||||
</h1>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span>
|
||||
Member since
|
||||
<span>
|
||||
2017
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<hr
|
||||
className="d-none d-md-block"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="d-none d-md-block float-right"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="row"
|
||||
>
|
||||
<div
|
||||
className="col-md-4 col-lg-4"
|
||||
>
|
||||
<div
|
||||
className="d-none d-md-block mb-4"
|
||||
>
|
||||
<h1
|
||||
className="h2 mb-0 font-weight-bold"
|
||||
>
|
||||
verified
|
||||
</h1>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span>
|
||||
Member since
|
||||
<span>
|
||||
2017
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<hr
|
||||
className="d-none d-md-block"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="d-md-none mb-4"
|
||||
/>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-5"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-5"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-5"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-5"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-5"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="pt-md-3 col-md-8 col-lg-7 offset-lg-1"
|
||||
>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-5"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-4"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<ProfilePage /> Renders correctly in various states viewing own profile 1`] = `
|
||||
<div
|
||||
className="profile-page"
|
||||
>
|
||||
<div
|
||||
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
|
||||
/>
|
||||
<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"
|
||||
>
|
||||
<div
|
||||
className="profile-avatar-wrap position-relative"
|
||||
>
|
||||
<div
|
||||
className="profile-avatar rounded-circle bg-light"
|
||||
>
|
||||
<div
|
||||
className="profile-avatar-menu-container"
|
||||
>
|
||||
<div
|
||||
className="dropdown"
|
||||
>
|
||||
<button
|
||||
aria-expanded={false}
|
||||
aria-haspopup="true"
|
||||
className="btn dropdown-toggle btn-primary"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Change
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
aria-hidden={true}
|
||||
aria-label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Change"
|
||||
description="Change photo button"
|
||||
id="profile.profileavatar.change-button"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
className="dropdown-menu"
|
||||
role="menu"
|
||||
>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
>
|
||||
<span>
|
||||
Upload Photo
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
>
|
||||
<span>
|
||||
Remove
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
alt="profile avatar"
|
||||
className="w-100 h-100 d-block rounded-circle overflow-hidden"
|
||||
src="http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_500.jpg?v=1552495012"
|
||||
style={
|
||||
Object {
|
||||
"objectFit": "cover",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<form
|
||||
encType="multipart/form-data"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<input
|
||||
accept=".jpg, .jpeg, .png"
|
||||
className="d-none form-control-file"
|
||||
id="photo-file"
|
||||
name="file"
|
||||
onChange={[Function]}
|
||||
type="file"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="col pl-0"
|
||||
>
|
||||
<div
|
||||
className="d-md-none"
|
||||
>
|
||||
<h1
|
||||
className="h2 mb-0 font-weight-bold"
|
||||
>
|
||||
staff
|
||||
</h1>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span>
|
||||
Member since
|
||||
<span>
|
||||
2017
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<hr
|
||||
className="d-none d-md-block"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="d-none d-md-block float-right"
|
||||
>
|
||||
<a
|
||||
className="btn btn-primary"
|
||||
href="undefined/records"
|
||||
onClick={[Function]}
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
View My Records
|
||||
<span>
|
||||
|
||||
<span
|
||||
aria-hidden={false}
|
||||
aria-label="Opens in a new window"
|
||||
className="fa fa-external-link"
|
||||
title="Opens in a new window"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="row"
|
||||
>
|
||||
<div
|
||||
className="col-md-4 col-lg-4"
|
||||
>
|
||||
<div
|
||||
className="d-none d-md-block mb-4"
|
||||
>
|
||||
<h1
|
||||
className="h2 mb-0 font-weight-bold"
|
||||
>
|
||||
staff
|
||||
</h1>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span>
|
||||
Member since
|
||||
<span>
|
||||
2017
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<hr
|
||||
className="d-none d-md-block"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="d-md-none mb-4"
|
||||
>
|
||||
<a
|
||||
className="btn btn-primary"
|
||||
href="undefined/records"
|
||||
onClick={[Function]}
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
View My Records
|
||||
<span>
|
||||
|
||||
<span
|
||||
aria-hidden={false}
|
||||
aria-label="Opens in a new window"
|
||||
className="fa fa-external-link"
|
||||
title="Opens in a new window"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-5"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="editable-item-header mb-2"
|
||||
>
|
||||
<h2
|
||||
className="edit-section-header"
|
||||
id={null}
|
||||
>
|
||||
Full Name
|
||||
<button
|
||||
className="btn btn-sm btn-link float-right px-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"marginTop": "-.35rem",
|
||||
}
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</h2>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span
|
||||
className="ml-auto small text-muted"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-eye-slash fa-w-20 "
|
||||
data-icon="eye-slash"
|
||||
data-prefix="far"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 640 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M634 471L36 3.51A16 16 0 0 0 13.51 6l-10 12.49A16 16 0 0 0 6 41l598 467.49a16 16 0 0 0 22.49-2.49l10-12.49A16 16 0 0 0 634 471zM296.79 146.47l134.79 105.38C429.36 191.91 380.48 144 320 144a112.26 112.26 0 0 0-23.21 2.47zm46.42 219.07L208.42 260.16C210.65 320.09 259.53 368 320 368a113 113 0 0 0 23.21-2.46zM320 112c98.65 0 189.09 55 237.93 144a285.53 285.53 0 0 1-44 60.2l37.74 29.5a333.7 333.7 0 0 0 52.9-75.11 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64c-36.7 0-71.71 7-104.63 18.81l46.41 36.29c18.94-4.3 38.34-7.1 58.22-7.1zm0 288c-98.65 0-189.08-55-237.93-144a285.47 285.47 0 0 1 44.05-60.19l-37.74-29.5a333.6 333.6 0 0 0-52.89 75.1 32.35 32.35 0 0 0 0 29.19C89.72 376.41 197.08 448 320 448c36.7 0 71.71-7.05 104.63-18.81l-46.41-36.28C359.28 397.2 339.89 400 320 400z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Just me
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
className="h5"
|
||||
>
|
||||
Lemon Seltzer
|
||||
</p>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
>
|
||||
This is the name that appears in your account and on your certificates.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-5"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="editable-item-header mb-2"
|
||||
>
|
||||
<h2
|
||||
className="edit-section-header"
|
||||
id={null}
|
||||
>
|
||||
Location
|
||||
<button
|
||||
className="btn btn-sm btn-link float-right px-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"marginTop": "-.35rem",
|
||||
}
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</h2>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span
|
||||
className="ml-auto small text-muted"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-eye fa-w-18 "
|
||||
data-icon="eye"
|
||||
data-prefix="far"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 576 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Everyone on edX
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
className="h5"
|
||||
>
|
||||
Montenegro
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-5"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="editable-item-header mb-2"
|
||||
>
|
||||
<h2
|
||||
className="edit-section-header"
|
||||
id={null}
|
||||
>
|
||||
Primary Language Spoken
|
||||
<button
|
||||
className="btn btn-sm btn-link float-right px-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"marginTop": "-.35rem",
|
||||
}
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</h2>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span
|
||||
className="ml-auto small text-muted"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-eye fa-w-18 "
|
||||
data-icon="eye"
|
||||
data-prefix="far"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 576 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Everyone on edX
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
className="h5"
|
||||
>
|
||||
Yoruba
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-5"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="editable-item-header mb-2"
|
||||
>
|
||||
<h2
|
||||
className="edit-section-header"
|
||||
id={null}
|
||||
>
|
||||
Education
|
||||
<button
|
||||
className="btn btn-sm btn-link float-right px-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"marginTop": "-.35rem",
|
||||
}
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</h2>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span
|
||||
className="ml-auto small text-muted"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-eye-slash fa-w-20 "
|
||||
data-icon="eye-slash"
|
||||
data-prefix="far"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 640 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M634 471L36 3.51A16 16 0 0 0 13.51 6l-10 12.49A16 16 0 0 0 6 41l598 467.49a16 16 0 0 0 22.49-2.49l10-12.49A16 16 0 0 0 634 471zM296.79 146.47l134.79 105.38C429.36 191.91 380.48 144 320 144a112.26 112.26 0 0 0-23.21 2.47zm46.42 219.07L208.42 260.16C210.65 320.09 259.53 368 320 368a113 113 0 0 0 23.21-2.46zM320 112c98.65 0 189.09 55 237.93 144a285.53 285.53 0 0 1-44 60.2l37.74 29.5a333.7 333.7 0 0 0 52.9-75.11 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64c-36.7 0-71.71 7-104.63 18.81l46.41 36.29c18.94-4.3 38.34-7.1 58.22-7.1zm0 288c-98.65 0-189.08-55-237.93-144a285.47 285.47 0 0 1 44.05-60.19l-37.74-29.5a333.6 333.6 0 0 0-52.89 75.1 32.35 32.35 0 0 0 0 29.19C89.72 376.41 197.08 448 320 448c36.7 0 71.71-7.05 104.63-18.81l-46.41-36.28C359.28 397.2 339.89 400 320 400z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Just me
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
className="h5"
|
||||
>
|
||||
Elementary/primary school
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-5"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="editable-item-header mb-2"
|
||||
>
|
||||
<h2
|
||||
className="edit-section-header"
|
||||
id={null}
|
||||
>
|
||||
Social Links
|
||||
<button
|
||||
className="btn btn-sm btn-link float-right px-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"marginTop": "-.35rem",
|
||||
}
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</h2>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span
|
||||
className="ml-auto small text-muted"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-eye fa-w-18 "
|
||||
data-icon="eye"
|
||||
data-prefix="far"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 576 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Everyone on edX
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<ul
|
||||
className="list-unstyled"
|
||||
>
|
||||
<li
|
||||
className="form-group"
|
||||
>
|
||||
<a
|
||||
className="font-weight-bold"
|
||||
href="https://www.twitter.com/ALOHA"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-twitter fa-w-16 mr-2"
|
||||
data-icon="twitter"
|
||||
data-prefix="fab"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Twitter
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className="form-group"
|
||||
>
|
||||
<a
|
||||
className="font-weight-bold"
|
||||
href="https://www.facebook.com/aloha"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-facebook fa-w-14 mr-2"
|
||||
data-icon="facebook"
|
||||
data-prefix="fab"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 448 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M448 56.7v398.5c0 13.7-11.1 24.7-24.7 24.7H309.1V306.5h58.2l8.7-67.6h-67v-43.2c0-19.6 5.4-32.9 33.5-32.9h35.8v-60.5c-6.2-.8-27.4-2.7-52.2-2.7-51.6 0-87 31.5-87 89.4v49.9h-58.4v67.6h58.4V480H24.7C11.1 480 0 468.9 0 455.3V56.7C0 43.1 11.1 32 24.7 32h398.5c13.7 0 24.8 11.1 24.8 24.7z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Facebook
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className="form-group"
|
||||
>
|
||||
<div>
|
||||
<button
|
||||
className="pl-0 text-left btn btn-link"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
tabIndex={0}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-plus fa-w-14 fa-xs mr-2"
|
||||
data-icon="plus"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 448 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Add
|
||||
LinkedIn
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pt-md-3 col-md-8 col-lg-7 offset-lg-1"
|
||||
>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-5"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="editable-item-header mb-2"
|
||||
>
|
||||
<h2
|
||||
className="edit-section-header"
|
||||
id={null}
|
||||
>
|
||||
About Me
|
||||
<button
|
||||
className="btn btn-sm btn-link float-right px-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"marginTop": "-.35rem",
|
||||
}
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</h2>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span
|
||||
className="ml-auto small text-muted"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-eye fa-w-18 "
|
||||
data-icon="eye"
|
||||
data-prefix="far"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 576 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Everyone on edX
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
className="lead"
|
||||
>
|
||||
This is my bio
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-4"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="editable-item-header mb-2"
|
||||
>
|
||||
<h2
|
||||
className="edit-section-header"
|
||||
id={null}
|
||||
>
|
||||
My Certificates
|
||||
<button
|
||||
className="btn btn-sm btn-link float-right px-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"marginTop": "-.35rem",
|
||||
}
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</h2>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span
|
||||
className="ml-auto small text-muted"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-eye fa-w-18 "
|
||||
data-icon="eye"
|
||||
data-prefix="far"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 576 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Everyone on edX
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="row align-items-stretch"
|
||||
>
|
||||
<div
|
||||
className="col col-sm-6 d-flex align-items-stretch"
|
||||
>
|
||||
<div
|
||||
className="card mb-4 certificate flex-grow-1"
|
||||
>
|
||||
<div
|
||||
className="certificate-type-illustration"
|
||||
style={
|
||||
Object {
|
||||
"backgroundImage": "url(null)",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="card-body d-flex flex-column"
|
||||
>
|
||||
<div
|
||||
className="card-title"
|
||||
>
|
||||
<p
|
||||
className="small mb-0"
|
||||
>
|
||||
Verified Certificate
|
||||
</p>
|
||||
<h4
|
||||
className="certificate-title"
|
||||
>
|
||||
edX Demonstration Course
|
||||
</h4>
|
||||
</div>
|
||||
<p
|
||||
className="small mb-0"
|
||||
>
|
||||
<span>
|
||||
From
|
||||
</span>
|
||||
</p>
|
||||
<p
|
||||
className="h6 mb-4"
|
||||
>
|
||||
edX
|
||||
</p>
|
||||
<div
|
||||
className="flex-grow-1"
|
||||
/>
|
||||
<p
|
||||
className="small mb-2"
|
||||
>
|
||||
<span>
|
||||
Completed on
|
||||
<span>
|
||||
3/4/2019
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<div>
|
||||
<a
|
||||
className="btn btn-outline-primary"
|
||||
href="http://www.example.com/"
|
||||
onClick={[Function]}
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
View Certificate
|
||||
<span>
|
||||
|
||||
<span
|
||||
aria-hidden={false}
|
||||
aria-label="Opens in a new window"
|
||||
className="fa fa-external-link"
|
||||
title="Opens in a new window"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<ProfilePage /> Renders correctly in various states while saving an edited bio 1`] = `
|
||||
<div
|
||||
className="profile-page"
|
||||
>
|
||||
<div
|
||||
className="profile-page-bg-banner bg-primary d-none d-md-block p-relative"
|
||||
/>
|
||||
<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"
|
||||
>
|
||||
<div
|
||||
className="profile-avatar-wrap position-relative"
|
||||
>
|
||||
<div
|
||||
className="profile-avatar rounded-circle bg-light"
|
||||
>
|
||||
<div
|
||||
className="profile-avatar-menu-container"
|
||||
>
|
||||
<div
|
||||
className="dropdown"
|
||||
>
|
||||
<button
|
||||
aria-expanded={false}
|
||||
aria-haspopup="true"
|
||||
className="btn dropdown-toggle btn-primary"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Change
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
aria-hidden={true}
|
||||
aria-label={
|
||||
<FormattedMessage
|
||||
defaultMessage="Change"
|
||||
description="Change photo button"
|
||||
id="profile.profileavatar.change-button"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
className="dropdown-menu"
|
||||
role="menu"
|
||||
>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
>
|
||||
<span>
|
||||
Upload Photo
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="dropdown-item"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
>
|
||||
<span>
|
||||
Remove
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
alt="profile avatar"
|
||||
className="w-100 h-100 d-block rounded-circle overflow-hidden"
|
||||
src="http://localhost:18000/media/profile-images/d2a9bdc2ba165dcefc73265c54bf9a20_500.jpg?v=1552495012"
|
||||
style={
|
||||
Object {
|
||||
"objectFit": "cover",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<form
|
||||
encType="multipart/form-data"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<input
|
||||
accept=".jpg, .jpeg, .png"
|
||||
className="d-none form-control-file"
|
||||
id="photo-file"
|
||||
name="file"
|
||||
onChange={[Function]}
|
||||
type="file"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="col pl-0"
|
||||
>
|
||||
<div
|
||||
className="d-md-none"
|
||||
>
|
||||
<h1
|
||||
className="h2 mb-0 font-weight-bold"
|
||||
>
|
||||
staff
|
||||
</h1>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span>
|
||||
Member since
|
||||
<span>
|
||||
2017
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<hr
|
||||
className="d-none d-md-block"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="d-none d-md-block float-right"
|
||||
>
|
||||
<a
|
||||
className="btn btn-primary"
|
||||
href="undefined/records"
|
||||
onClick={[Function]}
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
View My Records
|
||||
<span>
|
||||
|
||||
<span
|
||||
aria-hidden={false}
|
||||
aria-label="Opens in a new window"
|
||||
className="fa fa-external-link"
|
||||
title="Opens in a new window"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="row"
|
||||
>
|
||||
<div
|
||||
className="col-md-4 col-lg-4"
|
||||
>
|
||||
<div
|
||||
className="d-none d-md-block mb-4"
|
||||
>
|
||||
<h1
|
||||
className="h2 mb-0 font-weight-bold"
|
||||
>
|
||||
staff
|
||||
</h1>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span>
|
||||
Member since
|
||||
<span>
|
||||
2017
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<hr
|
||||
className="d-none d-md-block"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="d-md-none mb-4"
|
||||
>
|
||||
<a
|
||||
className="btn btn-primary"
|
||||
href="undefined/records"
|
||||
onClick={[Function]}
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
View My Records
|
||||
<span>
|
||||
|
||||
<span
|
||||
aria-hidden={false}
|
||||
aria-label="Opens in a new window"
|
||||
className="fa fa-external-link"
|
||||
title="Opens in a new window"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-5"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="editable-item-header mb-2"
|
||||
>
|
||||
<h2
|
||||
className="edit-section-header"
|
||||
id={null}
|
||||
>
|
||||
Full Name
|
||||
<button
|
||||
className="btn btn-sm btn-link float-right px-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"marginTop": "-.35rem",
|
||||
}
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</h2>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span
|
||||
className="ml-auto small text-muted"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-eye-slash fa-w-20 "
|
||||
data-icon="eye-slash"
|
||||
data-prefix="far"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 640 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M634 471L36 3.51A16 16 0 0 0 13.51 6l-10 12.49A16 16 0 0 0 6 41l598 467.49a16 16 0 0 0 22.49-2.49l10-12.49A16 16 0 0 0 634 471zM296.79 146.47l134.79 105.38C429.36 191.91 380.48 144 320 144a112.26 112.26 0 0 0-23.21 2.47zm46.42 219.07L208.42 260.16C210.65 320.09 259.53 368 320 368a113 113 0 0 0 23.21-2.46zM320 112c98.65 0 189.09 55 237.93 144a285.53 285.53 0 0 1-44 60.2l37.74 29.5a333.7 333.7 0 0 0 52.9-75.11 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64c-36.7 0-71.71 7-104.63 18.81l46.41 36.29c18.94-4.3 38.34-7.1 58.22-7.1zm0 288c-98.65 0-189.08-55-237.93-144a285.47 285.47 0 0 1 44.05-60.19l-37.74-29.5a333.6 333.6 0 0 0-52.89 75.1 32.35 32.35 0 0 0 0 29.19C89.72 376.41 197.08 448 320 448c36.7 0 71.71-7.05 104.63-18.81l-46.41-36.28C359.28 397.2 339.89 400 320 400z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Just me
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
className="h5"
|
||||
>
|
||||
Lemon Seltzer
|
||||
</p>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
>
|
||||
This is the name that appears in your account and on your certificates.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-5"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="editable-item-header mb-2"
|
||||
>
|
||||
<h2
|
||||
className="edit-section-header"
|
||||
id={null}
|
||||
>
|
||||
Location
|
||||
<button
|
||||
className="btn btn-sm btn-link float-right px-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"marginTop": "-.35rem",
|
||||
}
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</h2>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span
|
||||
className="ml-auto small text-muted"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-eye fa-w-18 "
|
||||
data-icon="eye"
|
||||
data-prefix="far"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 576 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Everyone on edX
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
className="h5"
|
||||
>
|
||||
Montenegro
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-5"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="editable-item-header mb-2"
|
||||
>
|
||||
<h2
|
||||
className="edit-section-header"
|
||||
id={null}
|
||||
>
|
||||
Primary Language Spoken
|
||||
<button
|
||||
className="btn btn-sm btn-link float-right px-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"marginTop": "-.35rem",
|
||||
}
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</h2>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span
|
||||
className="ml-auto small text-muted"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-eye fa-w-18 "
|
||||
data-icon="eye"
|
||||
data-prefix="far"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 576 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Everyone on edX
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
className="h5"
|
||||
>
|
||||
Yoruba
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-5"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="editable-item-header mb-2"
|
||||
>
|
||||
<h2
|
||||
className="edit-section-header"
|
||||
id={null}
|
||||
>
|
||||
Education
|
||||
<button
|
||||
className="btn btn-sm btn-link float-right px-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"marginTop": "-.35rem",
|
||||
}
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</h2>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span
|
||||
className="ml-auto small text-muted"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-eye-slash fa-w-20 "
|
||||
data-icon="eye-slash"
|
||||
data-prefix="far"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 640 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M634 471L36 3.51A16 16 0 0 0 13.51 6l-10 12.49A16 16 0 0 0 6 41l598 467.49a16 16 0 0 0 22.49-2.49l10-12.49A16 16 0 0 0 634 471zM296.79 146.47l134.79 105.38C429.36 191.91 380.48 144 320 144a112.26 112.26 0 0 0-23.21 2.47zm46.42 219.07L208.42 260.16C210.65 320.09 259.53 368 320 368a113 113 0 0 0 23.21-2.46zM320 112c98.65 0 189.09 55 237.93 144a285.53 285.53 0 0 1-44 60.2l37.74 29.5a333.7 333.7 0 0 0 52.9-75.11 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64c-36.7 0-71.71 7-104.63 18.81l46.41 36.29c18.94-4.3 38.34-7.1 58.22-7.1zm0 288c-98.65 0-189.08-55-237.93-144a285.47 285.47 0 0 1 44.05-60.19l-37.74-29.5a333.6 333.6 0 0 0-52.89 75.1 32.35 32.35 0 0 0 0 29.19C89.72 376.41 197.08 448 320 448c36.7 0 71.71-7.05 104.63-18.81l-46.41-36.28C359.28 397.2 339.89 400 320 400z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Just me
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
className="h5"
|
||||
>
|
||||
Elementary/primary school
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-5"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="editable-item-header mb-2"
|
||||
>
|
||||
<h2
|
||||
className="edit-section-header"
|
||||
id={null}
|
||||
>
|
||||
Social Links
|
||||
<button
|
||||
className="btn btn-sm btn-link float-right px-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"marginTop": "-.35rem",
|
||||
}
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</h2>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span
|
||||
className="ml-auto small text-muted"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-eye fa-w-18 "
|
||||
data-icon="eye"
|
||||
data-prefix="far"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 576 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Everyone on edX
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<ul
|
||||
className="list-unstyled"
|
||||
>
|
||||
<li
|
||||
className="form-group"
|
||||
>
|
||||
<a
|
||||
className="font-weight-bold"
|
||||
href="https://www.twitter.com/ALOHA"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-twitter fa-w-16 mr-2"
|
||||
data-icon="twitter"
|
||||
data-prefix="fab"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Twitter
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className="form-group"
|
||||
>
|
||||
<a
|
||||
className="font-weight-bold"
|
||||
href="https://www.facebook.com/aloha"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-facebook fa-w-14 mr-2"
|
||||
data-icon="facebook"
|
||||
data-prefix="fab"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 448 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M448 56.7v398.5c0 13.7-11.1 24.7-24.7 24.7H309.1V306.5h58.2l8.7-67.6h-67v-43.2c0-19.6 5.4-32.9 33.5-32.9h35.8v-60.5c-6.2-.8-27.4-2.7-52.2-2.7-51.6 0-87 31.5-87 89.4v49.9h-58.4v67.6h58.4V480H24.7C11.1 480 0 468.9 0 455.3V56.7C0 43.1 11.1 32 24.7 32h398.5c13.7 0 24.8 11.1 24.8 24.7z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Facebook
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className="form-group"
|
||||
>
|
||||
<div>
|
||||
<button
|
||||
className="pl-0 text-left btn btn-link"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
tabIndex={0}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-plus fa-w-14 fa-xs mr-2"
|
||||
data-icon="plus"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 448 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Add
|
||||
LinkedIn
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pt-md-3 col-md-8 col-lg-7 offset-lg-1"
|
||||
>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-5"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
aria-labelledby="bio-label"
|
||||
role="dialog"
|
||||
>
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<label
|
||||
className="edit-section-header"
|
||||
htmlFor="bio"
|
||||
>
|
||||
About Me
|
||||
</label>
|
||||
<textarea
|
||||
aria-describedby=""
|
||||
className="form-control"
|
||||
id="bio"
|
||||
name="bio"
|
||||
onChange={[Function]}
|
||||
value="This is my bio"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="d-flex flex-row-reverse flex-wrap justify-content-end align-items-center"
|
||||
>
|
||||
<div
|
||||
className="form-group d-flex flex-wrap"
|
||||
>
|
||||
<label
|
||||
className="col-form-label"
|
||||
htmlFor="visibilityBio"
|
||||
>
|
||||
Who can see this:
|
||||
</label>
|
||||
<span
|
||||
className="d-flex align-items-center"
|
||||
>
|
||||
<span
|
||||
className="d-inline-block ml-1 mr-2"
|
||||
style={
|
||||
Object {
|
||||
"width": "1.5rem",
|
||||
}
|
||||
}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-eye fa-w-18 "
|
||||
data-icon="eye"
|
||||
data-prefix="far"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 576 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<select
|
||||
className="d-inline-block w-auto form-control"
|
||||
id="visibilityBio"
|
||||
name="visibilityBio"
|
||||
onChange={[Function]}
|
||||
type="select"
|
||||
value="all_users"
|
||||
>
|
||||
<option
|
||||
value="private"
|
||||
>
|
||||
Just me
|
||||
</option>
|
||||
<option
|
||||
value="all_users"
|
||||
>
|
||||
Everyone on edX
|
||||
</option>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="form-group flex-shrink-0 mr-auto"
|
||||
>
|
||||
<button
|
||||
aria-live="assertive"
|
||||
className="btn pgn__stateful-btn pgn__stateful-btn-state-pending btn-primary"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
className="d-flex align-items-center justify-content-center"
|
||||
>
|
||||
<span
|
||||
className="pgn__stateful-btn-icon"
|
||||
>
|
||||
<span
|
||||
aria-hidden={true}
|
||||
className="icon fa fa-spinner fa-spin"
|
||||
id="Icon1"
|
||||
/>
|
||||
</span>
|
||||
Saving
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-link"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative mb-4"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="editable-item-header mb-2"
|
||||
>
|
||||
<h2
|
||||
className="edit-section-header"
|
||||
id={null}
|
||||
>
|
||||
My Certificates
|
||||
<button
|
||||
className="btn btn-sm btn-link float-right px-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"marginTop": "-.35rem",
|
||||
}
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</h2>
|
||||
<p
|
||||
className="mb-0"
|
||||
>
|
||||
<span
|
||||
className="ml-auto small text-muted"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-eye fa-w-18 "
|
||||
data-icon="eye"
|
||||
data-prefix="far"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 576 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M288 144a110.94 110.94 0 0 0-31.24 5 55.4 55.4 0 0 1 7.24 27 56 56 0 0 1-56 56 55.4 55.4 0 0 1-27-7.24A111.71 111.71 0 1 0 288 144zm284.52 97.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400c-98.65 0-189.09-55-237.93-144C98.91 167 189.34 112 288 112s189.09 55 237.93 144C477.1 345 386.66 400 288 400z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
Everyone on edX
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="row align-items-stretch"
|
||||
>
|
||||
<div
|
||||
className="col col-sm-6 d-flex align-items-stretch"
|
||||
>
|
||||
<div
|
||||
className="card mb-4 certificate flex-grow-1"
|
||||
>
|
||||
<div
|
||||
className="certificate-type-illustration"
|
||||
style={
|
||||
Object {
|
||||
"backgroundImage": "url(null)",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="card-body d-flex flex-column"
|
||||
>
|
||||
<div
|
||||
className="card-title"
|
||||
>
|
||||
<p
|
||||
className="small mb-0"
|
||||
>
|
||||
Verified Certificate
|
||||
</p>
|
||||
<h4
|
||||
className="certificate-title"
|
||||
>
|
||||
edX Demonstration Course
|
||||
</h4>
|
||||
</div>
|
||||
<p
|
||||
className="small mb-0"
|
||||
>
|
||||
<span>
|
||||
From
|
||||
</span>
|
||||
</p>
|
||||
<p
|
||||
className="h6 mb-4"
|
||||
>
|
||||
edX
|
||||
</p>
|
||||
<div
|
||||
className="flex-grow-1"
|
||||
/>
|
||||
<p
|
||||
className="small mb-2"
|
||||
>
|
||||
<span>
|
||||
Completed on
|
||||
<span>
|
||||
3/4/2019
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<div>
|
||||
<a
|
||||
className="btn btn-outline-primary"
|
||||
href="http://www.example.com/"
|
||||
onClick={[Function]}
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
View Certificate
|
||||
<span>
|
||||
|
||||
<span
|
||||
aria-hidden={false}
|
||||
aria-label="Opens in a new window"
|
||||
className="fa fa-external-link"
|
||||
title="Opens in a new window"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,7 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
function Banner() {
|
||||
return <div className="profile-page-bg-banner bg-primary d-none d-md-block p-relative" />;
|
||||
}
|
||||
|
||||
export default Banner;
|
||||
@@ -1,21 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import Banner from './Banner';
|
||||
|
||||
function PageLoading() {
|
||||
return (
|
||||
<div>
|
||||
<Banner />
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center flex-column"
|
||||
style={{
|
||||
height: '50vh',
|
||||
}}
|
||||
>
|
||||
<div className="spinner-border text-primary" role="status" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PageLoading;
|
||||
@@ -1,71 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import SiteHeader from '@edx/frontend-component-site-header';
|
||||
import { injectIntl } from '@edx/frontend-i18n'; // eslint-disable-line
|
||||
|
||||
import messages from './SiteHeader.messages';
|
||||
|
||||
import Logo from '../../assets/logo.svg';
|
||||
|
||||
const mapStateToProps = (state, { intl }) => ({
|
||||
logo: Logo,
|
||||
logoDestination: process.env.MARKETING_SITE_BASE_URL,
|
||||
logoAltText: 'edX',
|
||||
mainMenu: [
|
||||
{
|
||||
type: 'item',
|
||||
href: `${process.env.MARKETING_SITE_BASE_URL}/course`,
|
||||
content: intl.formatMessage(messages['siteheader.links.courses']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: `${process.env.MARKETING_SITE_BASE_URL}/course?program=all`,
|
||||
content: intl.formatMessage(messages['siteheader.links.programs']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: `${process.env.MARKETING_SITE_BASE_URL}/schools-partners`,
|
||||
content: intl.formatMessage(messages['siteheader.links.schools']),
|
||||
},
|
||||
],
|
||||
loggedIn: true,
|
||||
username: state.userAccount.username,
|
||||
avatar: state.userAccount.profileImage.hasImage ?
|
||||
state.userAccount.profileImage.imageUrlMedium :
|
||||
null,
|
||||
userMenu: [
|
||||
{
|
||||
type: 'item',
|
||||
href: `${process.env.LMS_BASE_URL}`,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.dashboard']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: `${process.env.BASE_URL}/u/${state.userAccount.username}`,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.profile']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: `${process.env.LMS_BASE_URL}/account/settings`,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.account.settings']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: process.env.LOGOUT_URL,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.logout']),
|
||||
},
|
||||
],
|
||||
loggedOutItems: [
|
||||
{
|
||||
type: 'item',
|
||||
href: `${process.env.LMS_BASE_URL}/login`,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.login']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: `${process.env.LMS_BASE_URL}/register`,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.register']),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export default injectIntl(connect(mapStateToProps)(SiteHeader));
|
||||
Reference in New Issue
Block a user