Organize the repo and export profile module properly (#262)

* Moving files to their new homes.

Subsequent commit will wire everything up again.

* Relink files and split stylesheet.

Requires adding resolve-url-loader to fix sass import relativity.

* Remove browserslist warning.

* Remove the need for ProfileMain

* Fix test issues - needed thunk.
This commit is contained in:
David Joy
2019-09-23 13:50:56 -04:00
committed by GitHub
parent 9b126281f1
commit 8f37cc1a8d
60 changed files with 363 additions and 214 deletions

View File

@@ -4,20 +4,27 @@ import { App, AppProvider, APP_ERROR, APP_READY, ErrorPage } from '@edx/frontend
import { NewRelicLoggingService } from '@edx/frontend-logging';
import React from 'react';
import ReactDOM from 'react-dom';
import { Route, Switch } from 'react-router-dom';
import Header, { messages as headerMessages } from '@edx/frontend-component-header';
import Footer from '../footer/Footer';
import appMessages from './i18n';
import './index.scss';
import ProfileMain from './profile/components/ProfileMain';
import { ProfilePage, NotFoundPage } from './profile';
import configureStore from './store';
App.subscribe(APP_READY, () => {
ReactDOM.render(
<AppProvider store={configureStore()}>
<Header />
<ProfileMain />
<main>
<Switch>
<Route path="/u/:username" component={ProfilePage} />
<Route path="/notfound" component={NotFoundPage} />
<Route path="*" component={NotFoundPage} />
</Switch>
</main>
<Footer />
</AppProvider>,
document.getElementById('root'),
@@ -29,4 +36,3 @@ App.subscribe(APP_ERROR, (error) => {
});
App.initialize({ messages: [appMessages, headerMessages], loggingService: NewRelicLoggingService });

View File

@@ -1,145 +1,7 @@
@import '~@edx/paragon/scss/edx/theme.scss';
@import '~@edx/paragon/scss/edx/fonts.scss'; // Roboto
$fa-font-path: "~font-awesome/fonts";
@import "~font-awesome/scss/font-awesome";
@import './profile/index.scss';
@import "~@edx/frontend-component-header/src/index";
@import "~@edx/frontend-component-footer/src/lib/scss/site-footer";
.word-break-all {
word-break: break-all !important;
}
// TODO: Update edx-bootstrap theme to incorporate these edits.
.btn, a.btn {
text-decoration: none;
&:hover {
text-decoration: none;
}
}
.btn-link {
text-decoration: underline;
&:hover {
text-decoration: underline;
}
}
.profile-page-bg-banner {
height: 12rem;
background-image: url('./assets/dot-pattern-light.png');
background-repeat: repeat-x;
background-size: auto 85%;
}
.profile-page {
.edit-section-header {
@extend .h6;
display: block;
font-weight: normal;
letter-spacing: 0;
margin: 0;
}
label.edit-section-header {
margin-bottom: $spacer * .5;
}
.profile-avatar-wrap {
@include media-breakpoint-up(md) {
max-width: 12rem;
margin-right: 0;
margin-top: -8rem;
margin-bottom: 2rem;
}
}
.profile-avatar-menu-container {
background: rgba(0,0,0,.65);
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
@include media-breakpoint-up(md) {
background: linear-gradient(to top, rgba(0,0,0,.65) 4rem, rgba(0,0,0,0) 4rem);
align-items: flex-end;
}
.btn {
text-decoration: none;
@include media-breakpoint-up(md) {
margin-bottom: 1.2rem;
}
}
.dropdown {
@include media-breakpoint-up(md) {
margin-bottom: 1.2rem;
}
.btn {
color: $white;
background: transparent;
border-color: transparent;
margin: 0;
}
}
}
.profile-avatar {
width: 5rem;
height: 5rem;
position: relative;
@include media-breakpoint-up(md) {
width: 12rem;
height: 12rem;
}
.profile-avatar-edit-button {
border: none;
position: absolute;
height: 100%;
left: 0;
width: 100%;
bottom: 0;
display: flex;
justify-content: center;
padding-top: .1rem;
font-weight: 600;
background: rgba(0,0,0,.5);
border-radius:0;
transition: opacity 200ms ease;
@include media-breakpoint-up(md) {
height: 4rem;
}
&:focus, &:hover, &:active, &.active {
opacity: 1;
}
}
}
.certificate {
position: relative;
.certificate-title {
font-family: $font-family-serif;
font-weight: 400;
}
.certificate-type-illustration {
position: absolute;
top: 1rem;
right: 1rem;
bottom: 0;
width: 12rem;
opacity: .06;
background-size: 90%;
background-repeat: no-repeat;
background-position: right top;
}
.card-body {
position: relative;
}
}
}

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { sendTrackingLogEvent } from '@edx/frontend-analytics';
import { App, AppContext } from '@edx/frontend-base';
import { App, AppContext, fetchUserAccount } from '@edx/frontend-base';
import { injectIntl, intlShape } from '@edx/frontend-i18n';
import { StatusAlert, Hyperlink } from '@edx/paragon';
@@ -16,7 +16,7 @@ import {
openForm,
closeForm,
updateDraft,
} from '../actions';
} from './data/actions';
// Components
import ProfileAvatar from './forms/ProfileAvatar';
@@ -33,14 +33,14 @@ import PageLoading from './PageLoading';
import Banner from './Banner';
// Selectors
import { profilePageSelector } from '../selectors';
import { profilePageSelector } from './data/selectors';
// i18n
import messages from './ProfilePage.messages';
App.requireConfig(['CREDENTIALS_BASE_URL', 'LMS_BASE_URL'], 'ProfilePage');
export class ProfilePage extends React.Component {
class ProfilePage extends React.Component {
constructor(props, context) {
super(props, context);
@@ -58,6 +58,7 @@ export class ProfilePage extends React.Component {
}
componentDidMount() {
this.props.fetchUserAccount(this.context.authenticatedUser.username);
this.props.fetchProfile(this.props.match.params.username);
sendTrackingLogEvent('edx.profile.viewed', {
username: this.props.match.params.username,
@@ -331,6 +332,7 @@ ProfilePage.propTypes = {
photoUploadError: PropTypes.objectOf(PropTypes.string),
// Actions
fetchUserAccount: PropTypes.func.isRequired,
fetchProfile: PropTypes.func.isRequired,
saveProfile: PropTypes.func.isRequired,
saveProfilePhoto: PropTypes.func.isRequired,
@@ -370,6 +372,7 @@ ProfilePage.defaultProps = {
export default connect(
profilePageSelector,
{
fetchUserAccount,
fetchProfile,
saveProfilePhoto,
deleteProfilePhoto,

View File

@@ -7,11 +7,12 @@ import React from 'react';
import { Provider } from 'react-redux';
import renderer from 'react-test-renderer';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import messages from '../../i18n';
import ConnectedProfilePage from './ProfilePage';
import messages from '../i18n';
import ProfilePage from './ProfilePage';
const mockStore = configureMockStore();
const mockStore = configureMockStore([thunk]);
const storeMocks = {
loadingApp: require('./__mocks__/loadingApp.mockStore.js'),
viewOwnProfile: require('./__mocks__/viewOwnProfile.mockStore.js'),
@@ -19,6 +20,7 @@ const storeMocks = {
savingEditedBio: require('./__mocks__/savingEditedBio.mockStore.js'),
};
const requiredProfilePageProps = {
fetchUserAccount: () => {},
fetchProfile: () => {},
saveProfile: () => {},
saveProfilePhoto: () => {},
@@ -33,6 +35,7 @@ Object.defineProperty(global.document, 'cookie', {
writable: true,
value: `${App.config.LANGUAGE_PREFERENCE_COOKIE_NAME}=en`,
});
App.apiClient = jest.fn();
configureI18n(App.config, messages);
@@ -49,7 +52,7 @@ describe('<ProfilePage />', () => {
>
<IntlProvider locale="en">
<Provider store={mockStore(storeMocks.loadingApp)}>
<ConnectedProfilePage {...requiredProfilePageProps} />
<ProfilePage {...requiredProfilePageProps} />
</Provider>
</IntlProvider>
</AppContext.Provider>
@@ -69,7 +72,7 @@ describe('<ProfilePage />', () => {
>
<IntlProvider locale="en">
<Provider store={mockStore(storeMocks.viewOwnProfile)}>
<ConnectedProfilePage {...requiredProfilePageProps} />
<ProfilePage {...requiredProfilePageProps} />
</Provider>
</IntlProvider>
</AppContext.Provider>
@@ -89,7 +92,7 @@ describe('<ProfilePage />', () => {
>
<IntlProvider locale="en">
<Provider store={mockStore(storeMocks.viewOtherProfile)}>
<ConnectedProfilePage
<ProfilePage
{...requiredProfilePageProps}
match={{ params: { username: 'verified' } }} // Override default match
/>
@@ -112,7 +115,7 @@ describe('<ProfilePage />', () => {
>
<IntlProvider locale="en">
<Provider store={mockStore(storeMocks.savingEditedBio)}>
<ConnectedProfilePage {...requiredProfilePageProps} />
<ProfilePage {...requiredProfilePageProps} />
</Provider>
</IntlProvider>
</AppContext.Provider>
@@ -134,7 +137,7 @@ describe('<ProfilePage />', () => {
>
<IntlProvider locale="en">
<Provider store={mockStore(storeMocks.loadingApp)}>
<ConnectedProfilePage
<ProfilePage
{...requiredProfilePageProps}
match={{ params: { username: 'test-username' } }}
/>

141
src/profile/_index.scss Normal file
View File

@@ -0,0 +1,141 @@
$fa-font-path: "~font-awesome/fonts";
@import "~font-awesome/scss/font-awesome";
.word-break-all {
word-break: break-all !important;
}
// TODO: Update edx-bootstrap theme to incorporate these edits.
.btn, a.btn {
text-decoration: none;
&:hover {
text-decoration: none;
}
}
.btn-link {
text-decoration: underline;
&:hover {
text-decoration: underline;
}
}
.profile-page-bg-banner {
height: 12rem;
background-image: url('./assets/dot-pattern-light.png');
background-repeat: repeat-x;
background-size: auto 85%;
}
.profile-page {
.edit-section-header {
@extend .h6;
display: block;
font-weight: normal;
letter-spacing: 0;
margin: 0;
}
label.edit-section-header {
margin-bottom: $spacer * .5;
}
.profile-avatar-wrap {
@include media-breakpoint-up(md) {
max-width: 12rem;
margin-right: 0;
margin-top: -8rem;
margin-bottom: 2rem;
}
}
.profile-avatar-menu-container {
background: rgba(0,0,0,.65);
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
@include media-breakpoint-up(md) {
background: linear-gradient(to top, rgba(0,0,0,.65) 4rem, rgba(0,0,0,0) 4rem);
align-items: flex-end;
}
.btn {
text-decoration: none;
@include media-breakpoint-up(md) {
margin-bottom: 1.2rem;
}
}
.dropdown {
@include media-breakpoint-up(md) {
margin-bottom: 1.2rem;
}
.btn {
color: $white;
background: transparent;
border-color: transparent;
margin: 0;
}
}
}
.profile-avatar {
width: 5rem;
height: 5rem;
position: relative;
@include media-breakpoint-up(md) {
width: 12rem;
height: 12rem;
}
.profile-avatar-edit-button {
border: none;
position: absolute;
height: 100%;
left: 0;
width: 100%;
bottom: 0;
display: flex;
justify-content: center;
padding-top: .1rem;
font-weight: 600;
background: rgba(0,0,0,.5);
border-radius:0;
transition: opacity 200ms ease;
@include media-breakpoint-up(md) {
height: 4rem;
}
&:focus, &:hover, &:active, &.active {
opacity: 1;
}
}
}
.certificate {
position: relative;
.certificate-title {
font-family: $font-family-serif;
font-weight: 400;
}
.certificate-type-illustration {
position: absolute;
top: 1rem;
right: 1rem;
bottom: 0;
width: 12rem;
opacity: .06;
background-size: 90%;
background-repeat: no-repeat;
background-position: right top;
}
.card-body {
position: relative;
}
}
}

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -1,37 +0,0 @@
import { AppContext, fetchUserAccount } from '@edx/frontend-base';
import PropTypes from 'prop-types';
import React, { useContext, useEffect } from 'react';
import { connect } from 'react-redux';
import { Route, Switch } from 'react-router-dom';
import NotFoundPage from './NotFoundPage';
import ConnectedProfilePage from './ProfilePage';
function ProfileMain(props) {
const { authenticatedUser } = useContext(AppContext);
useEffect(() => {
props.fetchUserAccount(authenticatedUser.username);
}, [authenticatedUser.username]);
return (
<main>
<Switch>
<Route path="/u/:username" component={ConnectedProfilePage} />
<Route path="/notfound" component={NotFoundPage} />
<Route path="*" component={NotFoundPage} />
</Switch>
</main>
);
}
ProfileMain.propTypes = {
fetchUserAccount: PropTypes.func.isRequired,
};
export default connect(
null,
{
fetchUserAccount,
},
)(ProfileMain);

View File

@@ -13,7 +13,7 @@ import EmptyContent from './elements/EmptyContent';
import SwitchContent from './elements/SwitchContent';
// Selectors
import { editableFormSelector } from '../../selectors';
import { editableFormSelector } from '../data/selectors';
class Bio extends React.Component {
constructor(props) {

View File

@@ -13,11 +13,11 @@ import EditableItemHeader from './elements/EditableItemHeader';
import SwitchContent from './elements/SwitchContent';
// Assets
import professionalCertificateSVG from '../../assets/professional-certificate.svg';
import verifiedCertificateSVG from '../../assets/verified-certificate.svg';
import professionalCertificateSVG from '../assets/professional-certificate.svg';
import verifiedCertificateSVG from '../assets/verified-certificate.svg';
// Selectors
import { certificatesSelector } from '../../selectors';
import { certificatesSelector } from '../data/selectors';
class Certificates extends React.Component {
constructor(props) {

View File

@@ -13,7 +13,7 @@ import EmptyContent from './elements/EmptyContent';
import SwitchContent from './elements/SwitchContent';
// Selectors
import { countrySelector } from '../../selectors';
import { countrySelector } from '../data/selectors';
class Country extends React.Component {
constructor(props) {

View File

@@ -14,10 +14,10 @@ import EmptyContent from './elements/EmptyContent';
import SwitchContent from './elements/SwitchContent';
// Constants
import { EDUCATION_LEVELS } from '../../constants';
import { EDUCATION_LEVELS } from '../data/constants';
// Selectors
import { editableFormSelector } from '../../selectors';
import { editableFormSelector } from '../data/selectors';
class Education extends React.Component {
constructor(props) {

View File

@@ -12,7 +12,7 @@ import EmptyContent from './elements/EmptyContent';
import SwitchContent from './elements/SwitchContent';
// Selectors
import { editableFormSelector } from '../../selectors';
import { editableFormSelector } from '../data/selectors';
class Name extends React.Component {
constructor(props) {

View File

@@ -13,7 +13,7 @@ import EmptyContent from './elements/EmptyContent';
import SwitchContent from './elements/SwitchContent';
// Selectors
import { preferredLanguageSelector } from '../../selectors';
import { preferredLanguageSelector } from '../data/selectors';
class PreferredLanguage extends React.Component {
constructor(props) {

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { Button, Dropdown } from '@edx/paragon';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-i18n';
import { ReactComponent as DefaultAvatar } from '../../assets/avatar.svg';
import { ReactComponent as DefaultAvatar } from '../assets/avatar.svg';
import messages from './ProfileAvatar.messages';

View File

@@ -16,7 +16,7 @@ import EmptyContent from './elements/EmptyContent';
import SwitchContent from './elements/SwitchContent';
// Selectors
import { editableFormSelector } from '../../selectors';
import { editableFormSelector } from '../data/selectors';
const platformDisplayInfo = {
facebook: {

View File

@@ -1,9 +1,5 @@
import profileReducer from './reducers';
import profileSaga from './sagas';
import ConnectedProfilePage from './components/ProfilePage';
export {
ConnectedProfilePage,
profileReducer,
profileSaga,
};
export { default as reducer } from './data/reducers';
export { default as saga } from './data/sagas';
export { default as ProfilePage } from './ProfilePage';
export { default as NotFoundPage } from './NotFoundPage';
export { default as messages } from './ProfilePage.messages';

View File

@@ -1,7 +1,7 @@
import { combineReducers } from 'redux';
import { userAccount } from '@edx/frontend-auth';
import { profileReducer } from './profile';
import { reducer as profileReducer } from './profile';
const createRootReducer = () =>
combineReducers({

View File

@@ -1,6 +1,6 @@
import { all } from 'redux-saga/effects';
import { profileSaga } from './profile';
import { saga as profileSaga } from './profile';
export default function* rootSaga() {
yield all([