improve account page

This commit is contained in:
srgooglo 2022-09-08 16:52:54 +02:00
parent 51d458c8f6
commit d74f75d00a
5 changed files with 273 additions and 146 deletions

View File

@ -1,40 +1,22 @@
import React from "react"
import * as antd from "antd"
import classnames from "classnames"
import moment from "moment"
import Loadable from "react-loadable"
import { Icons } from "components/Icons"
import { Image, Skeleton, PostsFeed, FollowButton, FollowersList } from "components"
import { Image, Skeleton, FollowButton } from "components"
import { Session, User } from "models"
import DetailsTab from "./tabs/details"
import PostsTab from "./tabs/posts"
import FollowersTab from "./tabs/followers"
import "./index.less"
const TabsComponent = {
"posts": React.memo((props) => {
return <div className="posts">
<PostsFeed
fromUserId={props.state.user._id}
/>
</div>
}),
"followers": React.memo((props) => {
return <FollowersList
followers={props.state.followers}
/>
}),
"details": React.memo((props) => {
return <div id="statistics" className="statistics">
<div>
<span><Icons.Users /> {props.state.followers.length} Followers</span>
</div>
<div>
<span><Icons.FileText /> 0 Posts</span>
</div>
<div>
<span>Joined at {moment(new Date(Number(props.state.user.createdAt))).format("YYYY")}</span>
</div>
</div>
})
"posts": PostsTab,
"followers": FollowersTab,
"details": DetailsTab
}
const TabRender = React.memo((props) => {
@ -56,7 +38,7 @@ const TabRender = React.memo((props) => {
const Tab = TabsComponent[activeKey]
if (!Tab) {
return <h1>Nothing to see here...</h1>
return null
}
return <div className={classnames("fade-opacity-active", { "fade-opacity-leave": transitionActive })}>
@ -64,7 +46,33 @@ const TabRender = React.memo((props) => {
</div>
})
// TODO: profileCard scroll effect (Hide description and wrap with entire body when cover image is not visible)
const UserBadges = React.memo((props) => {
return React.createElement(Loadable({
loader: async () => {
let { badges } = props
if (!badges || Array.isArray(badges) === false || badges.length === 0) {
return null
}
// fetch badges datam from api
const badgesData = await app.api.request("main", "get", "badges", {
_id: badges
}).catch(() => false)
if (!badgesData) {
return null
}
return () => badgesData.map((badge, index) => {
return <antd.Tag color={badge.color ?? "default"} key={index} id={badge.name} className="badge">
<span>{badge.label}</span>
</antd.Tag>
})
},
loading: antd.Skeleton,
}))
})
export default class Account extends React.Component {
state = {
@ -82,6 +90,8 @@ export default class Account extends React.Component {
isNotExistent: false,
}
coverComponent = React.createRef()
api = window.app.api.withEndpoints("main")
componentDidMount = async () => {
@ -135,6 +145,13 @@ export default class Account extends React.Component {
isFollowed,
followers,
})
//
app.eventBus.emit("style.compactMode", true)
}
componentWillUnmount = () => {
app.eventBus.emit("style.compactMode", false)
}
fetchData = async (username) => {
@ -180,6 +197,15 @@ export default class Account extends React.Component {
})
}
handleScroll = (e) => {
// if component scrolled foward set cover height to 0
if (e.target.scrollTop > 0) {
this.coverComponent.current.style.height = "0px"
} else {
this.coverComponent.current.style.height = ""
}
}
render() {
const user = this.state.user
@ -197,59 +223,82 @@ export default class Account extends React.Component {
}
return <div className="accountProfile">
{user.cover && <div className="cover" style={{ backgroundImage: `url("${user.cover}")` }} />}
<div className="profileCard">
<div className="basicData">
<div className="title">
<div className="field">
<div className="avatar">
<Image
alt="ProfileImage"
src={user.avatar}
/>
{user.cover && <div ref={this.coverComponent} className="cover" style={{ backgroundImage: `url("${user.cover}")` }} />}
<div className="profileCardWrapper">
<div className="profileCard">
<div className="basicData">
<div className="title">
<div className="field">
<div className="avatar">
<Image
alt="ProfileImage"
src={user.avatar}
/>
</div>
</div>
<div className="field">
<div>
<h1>{user.fullName ?? user.username}</h1>
{user.verified && <Icons.verifiedBadge />}
</div>
<span>@{user.username}</span>
</div>
</div>
<div className="field">
<div>
<h1>{user.fullName ?? user.username}</h1>
{user.verified && <Icons.verifiedBadge />}
</div>
<span>@{user.username}</span>
</div>
{!this.state.isSelf && <div>
<FollowButton
count={this.state.followers.length}
onClick={this.onClickFollow}
followed={this.state.isFollowed}
/>
</div>}
</div>
<div className="description">
<p>
{user.description}
</p>
</div>
{!this.state.isSelf && <div>
<FollowButton
count={this.state.followers.length}
onClick={this.onClickFollow}
followed={this.state.isFollowed}
/>
</div>}
</div>
<div className="description">
<p>
{user.description}
</p>
</div>
<div className="switchTab">
<antd.Segmented
//block
options={Object.keys(TabsComponent).map((key) => key.toTitleCase())}
value={this.state.tabActiveKey.toTitleCase()}
onChange={this.handlePageTransition}
/>
<div className="badgesTab">
<React.Suspense fallback={<antd.Skeleton />}>
<UserBadges badges={user.badges} />
</React.Suspense>
</div>
</div>
<div className="tabContent">
<TabRender
renderKey={this.state.tabActiveKey}
state={this.state}
/>
<div className="contents" onScroll={this.handleScroll}>
<div className="tabMenuWrapper">
<antd.Menu
className="tabMenu"
mode="vertical"
selectedKeys={[this.state.tabActiveKey]}
onClick={(e) => this.handlePageTransition(e.key)}
>
<antd.Menu.Item key="posts">
Posts
</antd.Menu.Item>
<antd.Menu.Item key="followers">
Followers
</antd.Menu.Item>
<antd.Menu.Item key="details">
Details
</antd.Menu.Item>
</antd.Menu>
</div>
<div className="tabContent">
<TabRender
renderKey={this.state.tabActiveKey}
state={this.state}
/>
</div>
</div>
</div>
}

View File

@ -2,11 +2,17 @@
.accountProfile {
width: 100%;
height: 100%;
height: 100vh;
overflow: hidden;
padding: 10px 20px;
// max-width: 70vw;
// min-width: 900px;
transition: all 0.3s ease-in-out;
.cover {
z-index: 50;
position: relative;
@ -23,13 +29,18 @@
height: 25vh;
transform: translate(0, 10px);
transition: all 0.3s ease-in-out;
}
.profileCardWrapper {
position: sticky;
top: 0;
z-index: 150;
}
.profileCard {
position: sticky;
top: 0;
z-index: 151;
position: relative;
z-index: 150;
display: flex;
flex-direction: column;
@ -42,6 +53,8 @@
padding: 20px 15px;
height: 15vh;
.basicData {
display: inline-flex;
flex-direction: row;
@ -90,53 +103,21 @@
}
.description {
max-height: 5vh;
overflow: hidden;
p {
word-break: break-all;
height: 100%;
overflow: hidden;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
margin: 0;
}
}
.extension {
position: relative;
display: inline-flex;
top: -10px;
width: 100%;
padding: 19px 10px 10px 10px;
border-top: 0;
border-right: 0;
border-style: solid;
border-width: 1px;
border-color: var(--border-color);
border-top-color: transparent;
border-radius: 0 0 @borderRadius @borderRadius;
}
.switchTab {
position: absolute;
bottom: 0;
right: -1px;
border: 1px solid var(--border-color);
border-top: none;
transform: translate(0, 33px);
background-color: var(--background-color-primary);
border-radius: 0 0 8px 8px;
// .ant-segmented {
// background-color: transparent;
// }
// .ant-segmented-item-selected {
// background-color: var(--background-color-accent)!important;
// }
}
h1,
h2,
h3,
@ -149,45 +130,79 @@
}
}
.tabContent {
margin-top: 45px;
.badgesTab {
position: relative;
z-index: 140;
height: 5vh;
display: flex;
flex-direction: row;
padding: 30px 10px 10px 10px;
background-color: var(--background-color-accent);
border-radius: 0 0 @borderRadius @borderRadius;
transform: translate(0, -20px);
border: 1px solid var(--border-color);
.badge {
margin-left: 10px;
height: fit-content;
}
}
.tabs {
transform: translate(0, -20px);
position: relative;
z-index: 55;
.tabMenuWrapper {
position: sticky!important;
top: 20px;
.ant-tabs-nav-wrap {
justify-content: flex-end;
height: fit-content;
.tabMenu {
width: fit-content;
min-width: 10vw;
padding: 10px;
background-color: var(--background-color-accent) !important;
border-radius: @borderRadius;
}
}
.ant-tabs-nav {
margin: 0 20px 20px 0;
.contents {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: 1fr;
grid-column-gap: 10px;
grid-row-gap: 0px;
z-index: 152;
width: 100%;
height: 100vh;
overflow: hidden;
overflow-y: overlay;
margin-top: -1.5vh;
.tabContent {
position: sticky;
top: 0;
border-radius: @borderRadius;
top: -20vh;
padding: 20px 10px 20vh 10px;
}
}
.ant-tabs-nav::before {
border: none !important;
}
.details {
background-color: var(--background-color-accent);
padding: 20px;
border-radius: @borderRadius;
.ant-tabs-tab {
margin-right: 20px !important;
padding: 8px 20px !important;
font-family: "DM Mono", monospace;
border-color: var(--border-color) !important;
background-color: var(--background-color-primary) !important;
border-radius: 4px !important;
svg {
font-size: 1rem;
margin: 0 !important;
}
h1,h2,h3,h4,h5,h6,p,span {
user-select: text;
}
}
}

View File

@ -0,0 +1,43 @@
import React from "react"
import moment from "moment"
import { Icons } from "components/Icons"
export default React.memo((props) => {
return <div id="details" className="details">
{props.state.user.fullName &&
<div>
<h2>{props.state.user.fullName}</h2>
</div>
}
<div>
<h3>
@{props.state.user.username} #{props.state.user._id}
</h3>
</div>
{props.state.user.description &&
<div>
<h4>
{props.state.user.description}
</h4>
</div>
}
{
props.state.user.roles.includes("admin") &&
<div>
<span><Icons.MdAdminPanelSettings /> Administrators Team</span>
</div>
}
<div>
<span><Icons.Users /> {props.state.followers.length} Followers</span>
</div>
{props.state.user?.badges.length > 0 &&
<div>
<span><Icons.Award /> {props.state.user?.badges.length} Badges collected</span>
</div>
}
<div>
<span><Icons.Calendar /> Joined at {moment(new Date(Number(props.state.user.createdAt))).format("YYYY")}</span>
</div>
</div>
})

View File

@ -0,0 +1,9 @@
import React from "react"
import { FollowersList } from "components"
export default React.memo((props) => {
return <FollowersList
followers={props.state.followers}
/>
})

View File

@ -0,0 +1,11 @@
import React from "react"
import { PostsFeed } from "components"
export default React.memo((props) => {
return <div className="posts">
<PostsFeed
fromUserId={props.state.user._id}
/>
</div>
})