mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-10 02:54:15 +00:00
improve account
page
This commit is contained in:
parent
1c6e460d25
commit
d14af06f09
@ -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>
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
43
packages/app/src/pages/account/tabs/details.jsx
Normal file
43
packages/app/src/pages/account/tabs/details.jsx
Normal 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>
|
||||
})
|
9
packages/app/src/pages/account/tabs/followers.jsx
Normal file
9
packages/app/src/pages/account/tabs/followers.jsx
Normal file
@ -0,0 +1,9 @@
|
||||
import React from "react"
|
||||
|
||||
import { FollowersList } from "components"
|
||||
|
||||
export default React.memo((props) => {
|
||||
return <FollowersList
|
||||
followers={props.state.followers}
|
||||
/>
|
||||
})
|
11
packages/app/src/pages/account/tabs/posts.jsx
Normal file
11
packages/app/src/pages/account/tabs/posts.jsx
Normal 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>
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user