Introduce and use "ProfilePage" component
parent
10bd12ccdd
commit
7bba3905b5
|
@ -106,7 +106,7 @@ class Root extends PureComponent<{}, State> {
|
|||
<Route path="manage-sources/new" component={SourcePage} />
|
||||
<Route path="manage-sources/:id/edit" component={SourcePage} />
|
||||
<Route path="delorean" component={FluxPage} />
|
||||
<Route path="user" component={UserPage} />
|
||||
<Route path="user/:tab" component={UserPage} />
|
||||
</Route>
|
||||
</Route>
|
||||
<Route path="*" component={NotFound} />
|
||||
|
|
|
@ -91,7 +91,7 @@ class SideNav extends PureComponent<Props> {
|
|||
{
|
||||
type: NavItemType.Avatar,
|
||||
title: 'My Profile',
|
||||
link: `/user`,
|
||||
link: `/user/settings/${this.sourceParam}`,
|
||||
image: LeroyJenkins.avatar,
|
||||
location: location.pathname,
|
||||
highlightWhen: ['user'],
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
Styles for Profile Page
|
||||
----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
.profile {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
background-color: rgba($g3-castle, 0.5);
|
||||
}
|
||||
|
||||
.profile-nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
flex: 0 1 0;
|
||||
padding-left: $ix-marg-d;
|
||||
}
|
||||
|
||||
.profile-nav--header {
|
||||
@include no-user-select();
|
||||
margin: $ix-marg-d;
|
||||
margin-left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.profile-nav--name {
|
||||
font-size: 19px;
|
||||
line-height: 22px;
|
||||
color: $g15-platinum;
|
||||
margin: 0;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.profile-nav--description {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
line-height: 15px;
|
||||
margin: 0;
|
||||
margin-top: $ix-marg-a + $ix-marg-b;
|
||||
color: $g11-sidewalk;
|
||||
}
|
||||
|
||||
.profile-nav--tabs {
|
||||
@include no-user-select();
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.profile-nav--tab {
|
||||
border-radius: $radius 0 0 $radius;
|
||||
font-size: 17px;
|
||||
height: $nav-size;
|
||||
line-height: $nav-size;
|
||||
padding: 0 17px;
|
||||
color: $g11-sidewalk;
|
||||
transition: background-color 0.25s ease, color 0.25s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($g3-castle, 0.5);
|
||||
color: $g16-pearl;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: $g3-castle;
|
||||
color: $g18-cloud;
|
||||
}
|
||||
}
|
||||
.profile-content {
|
||||
flex: 1 0 0;
|
||||
background-color: $g3-castle;
|
||||
border-radius: $radius;
|
||||
min-height: 500px;
|
||||
padding: $ix-marg-d;
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
// Libraries
|
||||
import React, {Component, ReactElement, ReactNode} from 'react'
|
||||
import {withRouter, InjectedRouter} from 'react-router'
|
||||
|
||||
// Components
|
||||
import ProfilePageSection from 'src/shared/components/profile_page/ProfilePageSection'
|
||||
import ProfilePageTab from 'src/shared/components/profile_page/ProfilePageTab'
|
||||
import Avatar from 'src/shared/components/avatar/Avatar'
|
||||
|
||||
// Decorators
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
name: string
|
||||
avatar: string
|
||||
description?: string
|
||||
children: ReactNode[]
|
||||
activeTabUrl: string
|
||||
router: InjectedRouter
|
||||
parentUrl: string
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class ProfilePage extends Component<Props> {
|
||||
public static Section = ProfilePageSection
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
public render() {
|
||||
this.validateChildTypes()
|
||||
|
||||
return (
|
||||
<div className="profile">
|
||||
<div className="profile-nav">
|
||||
{this.profileNavHeader}
|
||||
{this.profileNavTabs}
|
||||
</div>
|
||||
<div className="profile-content">{this.activeSectionComponent}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get profileNavTabs(): JSX.Element {
|
||||
const {children, activeTabUrl} = this.props
|
||||
|
||||
return (
|
||||
<div className="profile-nav--tabs">
|
||||
{React.Children.map(children, (child: JSX.Element) => (
|
||||
<ProfilePageTab
|
||||
title={child.props.title}
|
||||
key={child.props.id}
|
||||
id={child.props.id}
|
||||
url={child.props.url}
|
||||
active={child.props.url === activeTabUrl}
|
||||
onClick={this.handleTabClick}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get profileNavHeader(): JSX.Element {
|
||||
const {avatar, name, description} = this.props
|
||||
|
||||
return (
|
||||
<div className="profile-nav--header">
|
||||
<Avatar
|
||||
imageURI={avatar}
|
||||
diameterPixels={160}
|
||||
customClass="profile-nav--avatar"
|
||||
/>
|
||||
<h3 className="profile-nav--name">{name}</h3>
|
||||
{description && (
|
||||
<p className="profile-nav--description">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get activeSectionComponent(): JSX.Element[] {
|
||||
const {children, activeTabUrl} = this.props
|
||||
|
||||
// Using ReactElement as type to ensure children have props
|
||||
return React.Children.map(children, (child: ReactElement<any>) => {
|
||||
if (child.props.url === activeTabUrl) {
|
||||
return child.props.children
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public handleTabClick = (url: string) => (): void => {
|
||||
const {router, parentUrl} = this.props
|
||||
router.push(`${parentUrl}/${url}/`)
|
||||
}
|
||||
|
||||
private validateChildTypes = (): void => {
|
||||
const {children} = this.props
|
||||
|
||||
React.Children.forEach(children, (child: JSX.Element) => {
|
||||
if (child.type !== ProfilePageSection) {
|
||||
throw new Error(
|
||||
'<ProfilePage> expected children of type <ProfilePage.Section />'
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(ProfilePage)
|
|
@ -0,0 +1,21 @@
|
|||
// Libraries
|
||||
import React, {Component} from 'react'
|
||||
|
||||
// Decorators
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
id: string
|
||||
title: string
|
||||
url: string
|
||||
children: JSX.Element
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class ProfilePageSection extends Component<Props> {
|
||||
public render() {
|
||||
return <div>{this.props.children}</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default ProfilePageSection
|
|
@ -0,0 +1,22 @@
|
|||
// Libraries
|
||||
import React, {SFC} from 'react'
|
||||
import classnames from 'classnames'
|
||||
|
||||
interface Props {
|
||||
id: string
|
||||
title: string
|
||||
active: boolean
|
||||
url: string
|
||||
onClick: (url: string) => () => void
|
||||
}
|
||||
|
||||
const ProfilePageTab: SFC<Props> = ({title, active, url, onClick}) => (
|
||||
<div
|
||||
className={classnames('profile-nav--tab', {active})}
|
||||
onClick={onClick(url)}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
)
|
||||
|
||||
export default ProfilePageTab
|
|
@ -22,6 +22,7 @@
|
|||
// Components
|
||||
// TODO: Import these styles into their respective components instead of this stylesheet
|
||||
@import 'src/page_layout/PageLayout';
|
||||
@import 'src/shared/components/profile_page/ProfilePage';
|
||||
@import 'src/shared/components/avatar/Avatar';
|
||||
@import 'src/shared/components/fancy_scrollbar/FancyScrollbar';
|
||||
@import 'src/shared/components/notifications/Notifications';
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
// Libraries
|
||||
import React, {Component} from 'react'
|
||||
|
||||
interface Props {
|
||||
token: string
|
||||
}
|
||||
|
||||
class TokenManager extends Component<Props> {
|
||||
public render() {
|
||||
return <div>{this.props.token}</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default TokenManager
|
|
@ -0,0 +1,14 @@
|
|||
// Libraries
|
||||
import React, {Component} from 'react'
|
||||
|
||||
interface Props {
|
||||
blargh: string
|
||||
}
|
||||
|
||||
class UserSettings extends Component<Props> {
|
||||
public render() {
|
||||
return <div>{this.props.blargh}</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default UserSettings
|
|
@ -3,6 +3,9 @@ import React, {PureComponent} from 'react'
|
|||
|
||||
/// Components
|
||||
import {Page} from 'src/page_layout'
|
||||
import ProfilePage from 'src/shared/components/profile_page/ProfilePage'
|
||||
import UserSettings from 'src/user/components/UserSettings'
|
||||
import TokenManager from 'src/user/components/TokenManager'
|
||||
|
||||
// Decorators
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
@ -26,15 +29,20 @@ interface User {
|
|||
|
||||
interface Props {
|
||||
user?: User
|
||||
params: {
|
||||
tab: string
|
||||
}
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
export class FluxPage extends PureComponent<Props> {
|
||||
export class UserPage extends PureComponent<Props> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
user: LeroyJenkins,
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {user, params} = this.props
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<Page.Header fullWidth={false}>
|
||||
|
@ -44,11 +52,33 @@ export class FluxPage extends PureComponent<Props> {
|
|||
<Page.Header.Right />
|
||||
</Page.Header>
|
||||
<Page.Contents fullWidth={false} scrollable={true}>
|
||||
<div>fsfsfsdfs</div>
|
||||
<div className="col-xs-12">
|
||||
<ProfilePage
|
||||
name={user.name}
|
||||
avatar={user.avatar}
|
||||
parentUrl="/user"
|
||||
activeTabUrl={params.tab}
|
||||
>
|
||||
<ProfilePage.Section
|
||||
id="user-profile-tab--settings"
|
||||
url="settings"
|
||||
title="Settings"
|
||||
>
|
||||
<UserSettings blargh="User Settings" />
|
||||
</ProfilePage.Section>
|
||||
<ProfilePage.Section
|
||||
id="user-profile-tab--tokens"
|
||||
url="tokens"
|
||||
title="Tokens"
|
||||
>
|
||||
<TokenManager token="Token Manager" />
|
||||
</ProfilePage.Section>
|
||||
</ProfilePage>
|
||||
</div>
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default FluxPage
|
||||
export default UserPage
|
||||
|
|
Loading…
Reference in New Issue