[ABANDONED] React/Redux front end for the Flexor social network.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

193 lines
7.3 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
4 years ago
5 years ago
5 years ago
4 years ago
5 years ago
5 years ago
4 years ago
5 years ago
5 years ago
4 years ago
5 years ago
5 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. // view-user.tsx
  2. // Copyright (C) 2020 Dwayne Harris
  3. // This program is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU General Public License as published by
  5. // the Free Software Foundation, either version 3 of the License, or
  6. // (at your option) any later version.
  7. // This program is distributed in the hope that it will be useful,
  8. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. // GNU General Public License for more details.
  11. // You should have received a copy of the GNU General Public License
  12. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. import React, { FC, useEffect, useState } from 'react'
  14. import { useSelector, useDispatch } from 'react-redux'
  15. import { useParams, useHistory } from 'react-router-dom'
  16. import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
  17. import { faUserPlus, faUserMinus, faUserClock, faBan } from '@fortawesome/free-solid-svg-icons'
  18. import moment from 'moment'
  19. import { handleApiError } from '../../api/errors'
  20. import { fetchUser, subscribe, unsubscribe } from '../../actions/users'
  21. import { fetchUserPosts } from '../../actions/posts'
  22. import { setTheme } from '../../actions/theme'
  23. import { getEntity } from '../../selectors/entities'
  24. import { getAuthenticatedUser, getChecked } from '../../selectors/authentication'
  25. import { getUserPosts } from '../../selectors/posts'
  26. import { getThemeName } from '../../selectors/theme'
  27. import { useDeepCompareEffect, useTheme, useSetting } from '../../hooks'
  28. import { setTitle } from '../../utils'
  29. import { AppState, EntityType, User, Post, AppThunkDispatch, LevelItem } from '../../types'
  30. import Title from '../../components/title'
  31. import Subtitle from '../../components/subtitle'
  32. import Level from '../../components/level'
  33. import PostList from '../../components/post-list'
  34. import Section from '../../components/section'
  35. import HorizontalRule from '../../components/horizontal-rule'
  36. import Loading from '../../components/pages/loading'
  37. interface Params {
  38. id: string
  39. }
  40. const ViewUser: FC = () => {
  41. const { id } = useParams<Params>()
  42. const theme = useTheme()
  43. const themeName = useSelector(getThemeName)
  44. const [selectedThemeName] = useState(themeName)
  45. const checked = useSelector(getChecked)
  46. const self = useSelector(getAuthenticatedUser)
  47. const user = useSelector<AppState, User | undefined>(state => getEntity<User>(state, EntityType.User, id))
  48. const posts = useSelector<AppState, Post[]>(state => getUserPosts(state, id))
  49. const dispatch = useDispatch<AppThunkDispatch>()
  50. const history = useHistory()
  51. const allowThemeChange = useSetting<boolean>('allowThemeChange', true)
  52. useEffect(() => {
  53. const init = async () => {
  54. try {
  55. await dispatch(fetchUser(id))
  56. await dispatch(fetchUserPosts(id))
  57. } catch (err) {
  58. handleApiError(err, dispatch, history)
  59. }
  60. }
  61. if (checked) init()
  62. }, [checked])
  63. useDeepCompareEffect(() => {
  64. if (user) {
  65. setTitle(user.name)
  66. if (allowThemeChange && user.theme) dispatch(setTheme(user.theme))
  67. }
  68. return () => {
  69. if (allowThemeChange && selectedThemeName) dispatch(setTheme(selectedThemeName))
  70. }
  71. }, [user])
  72. if (!user) return <Loading />
  73. const isSelf = self && self.id === user.id
  74. const isGroup = self && self.group && user.group && self.group.id === user.group.id
  75. const subscription = self && user.subscriptions ? user.subscriptions.find(subscription => subscription.from === self.id && subscription.to === user.id) : undefined
  76. const subscribed = subscription && !subscription.pending
  77. const subscriptionPending = subscription && subscription.pending
  78. const items: LevelItem[] = []
  79. items.push({
  80. label: 'Posts',
  81. content: user.posts,
  82. })
  83. items.push({
  84. label: 'Awards',
  85. content: user.awards,
  86. })
  87. items.push({
  88. label: 'Points',
  89. content: user.points,
  90. })
  91. items.push({
  92. label: 'Joined',
  93. content: moment(user.created).format('MMMM Do, YYYY'),
  94. })
  95. return (
  96. <div>
  97. <Section>
  98. {user.coverImageUrl &&
  99. <div className="cover-image">
  100. <img src={user.coverImageUrl} />
  101. </div>
  102. }
  103. <div className="header">
  104. {user.imageUrl &&
  105. <div className="image">
  106. <img src={user.imageUrl} style={{ width: 128 }} />
  107. </div>
  108. }
  109. <div>
  110. <Title>{user.name}</Title>
  111. <p style={{ color: theme.text }}>{user.about}</p>
  112. </div>
  113. </div>
  114. <Level items={items} />
  115. <HorizontalRule />
  116. <div className="buttons">
  117. {subscribed &&
  118. <button style={{ backgroundColor: theme.red, color: 'white' }} onClick={() => dispatch(unsubscribe(user.id))}>
  119. <span className="button-icon">
  120. <FontAwesomeIcon icon={faUserMinus} />
  121. </span>
  122. <span>Unsusbcribe</span>
  123. </button>
  124. }
  125. {subscriptionPending &&
  126. <button style={{ backgroundColor: theme.blue, color: 'white' }}>
  127. <span className="button-icon">
  128. <FontAwesomeIcon icon={faUserClock} />
  129. </span>
  130. <span>Pending</span>
  131. </button>
  132. }
  133. {self && !isSelf && !subscribed && !subscriptionPending &&
  134. <button style={{ backgroundColor: theme.green, color: 'white' }} onClick={() => dispatch(subscribe(user.id))}>
  135. <span className="button-icon">
  136. <FontAwesomeIcon icon={faUserPlus} />
  137. </span>
  138. <span>Subscribe</span>
  139. </button>
  140. }
  141. {!isSelf &&
  142. <button style={{ backgroundColor: theme.red, color: 'white' }}>
  143. <span className="button-icon">
  144. <FontAwesomeIcon icon={faBan} />
  145. </span>
  146. <span>Block</span>
  147. </button>
  148. }
  149. {user.group && !isGroup &&
  150. <button style={{ backgroundColor: theme.red, color: 'white' }}>
  151. <span className="button-icon">
  152. <FontAwesomeIcon icon={faBan} />
  153. </span>
  154. <span>Block Community: {user.group.name}</span>
  155. </button>
  156. }
  157. </div>
  158. </Section>
  159. <div style={{ padding: '0px 1rem' }}>
  160. <Subtitle>Posts</Subtitle>
  161. </div>
  162. <PostList posts={posts} />
  163. </div>
  164. )
  165. }
  166. export default ViewUser