Dwayne Harris
5 years ago
11 changed files with 284 additions and 63 deletions
-
41src/actions/posts.ts
-
4src/components/app.tsx
-
27src/components/composer.tsx
-
12src/components/pages/home.tsx
-
90src/components/pages/view-post.tsx
-
30src/components/post-list.tsx
-
89src/components/post.tsx
-
14src/selectors/posts.ts
-
20src/styles/app.scss
-
12src/types/entities.ts
-
8src/utils/normalization.ts
@ -0,0 +1,90 @@ |
|||||
|
import React, { FC, useEffect } from 'react' |
||||
|
import { useSelector, useDispatch } from 'react-redux' |
||||
|
import { useParams, useHistory } from 'react-router-dom' |
||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' |
||||
|
import { faArrowsAltV } from '@fortawesome/free-solid-svg-icons' |
||||
|
|
||||
|
import { handleApiError } from 'src/api/errors' |
||||
|
import { fetchPost } from 'src/actions/posts' |
||||
|
import { getAuthenticated, getChecked } from 'src/selectors/authentication' |
||||
|
import { getEntity } from 'src/selectors/entities' |
||||
|
import { getPostParents, getPostChildren } from 'src/selectors/posts' |
||||
|
|
||||
|
import { setTitle } from 'src/utils' |
||||
|
import { AppState, AppThunkDispatch, EntityType, Post } from 'src/types' |
||||
|
|
||||
|
import PageHeader from 'src/components/page-header' |
||||
|
import Loading from 'src/components/pages/loading' |
||||
|
import PostComponent from 'src/components/post' |
||||
|
import PostList from 'src/components/post-list' |
||||
|
import Composer from 'src/components/composer' |
||||
|
|
||||
|
interface Params { |
||||
|
id: string |
||||
|
} |
||||
|
|
||||
|
const ViewPost: FC = () => { |
||||
|
const { id } = useParams<Params>() |
||||
|
const post = useSelector<AppState, Post | undefined>(state => getEntity<Post>(state, EntityType.Post, id)) |
||||
|
const parents = useSelector<AppState, Post[]>(state => getPostParents(state, id)) |
||||
|
const replies = useSelector<AppState, Post[]>(state => getPostChildren(state, id)) |
||||
|
const checked = useSelector<AppState, boolean>(getChecked) |
||||
|
const authenticated = useSelector<AppState, boolean>(getAuthenticated) |
||||
|
const dispatch = useDispatch<AppThunkDispatch>() |
||||
|
const history = useHistory() |
||||
|
|
||||
|
const fetch = async () => { |
||||
|
try { |
||||
|
await dispatch(fetchPost(id)) |
||||
|
} catch (err) { |
||||
|
handleApiError(err, dispatch, history) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (checked) fetch() |
||||
|
}, [checked]) |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (post) setTitle('Post') |
||||
|
}, [post]) |
||||
|
|
||||
|
if (!post) return <Loading /> |
||||
|
|
||||
|
return ( |
||||
|
<div> |
||||
|
<PageHeader title="Post" /> |
||||
|
|
||||
|
<div className="main-content"> |
||||
|
{parents.length > 0 && |
||||
|
<div> |
||||
|
<PostList posts={parents} collapseText="Show Older Posts" /> |
||||
|
<div className="has-text-centered"> |
||||
|
<FontAwesomeIcon icon={faArrowsAltV} /> |
||||
|
</div> |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
<PostComponent post={post} /> |
||||
|
|
||||
|
{authenticated && |
||||
|
<div> |
||||
|
<br /> |
||||
|
<h1 className="title is-size-5 is-primary">Reply</h1> |
||||
|
<Composer parent={post} onPost={fetch} /> |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
{replies.length > 0 && |
||||
|
<div> |
||||
|
<br /> |
||||
|
<h1 className="title is-size-5">Replies</h1> |
||||
|
<PostList posts={replies} /> |
||||
|
</div> |
||||
|
} |
||||
|
</div> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
export default ViewPost |
@ -1,17 +1,31 @@ |
|||||
import React, { FC } from 'react' |
|
||||
|
|
||||
import { Post } from 'src/types' |
|
||||
|
import React, { FC, useState } from 'react' |
||||
|
import classNames from 'classnames' |
||||
|
import { Post, ClassDictionary } from 'src/types' |
||||
|
|
||||
import PostComponent from 'src/components/post' |
import PostComponent from 'src/components/post' |
||||
|
|
||||
interface Props { |
interface Props { |
||||
posts: Post[] |
posts: Post[] |
||||
|
collapseText?: string |
||||
} |
} |
||||
|
|
||||
const PostList: FC<Props> = ({ posts }) => ( |
|
||||
<div className="post-list"> |
|
||||
{posts.map(post => <PostComponent key={post.id} post={post} />)} |
|
||||
</div> |
|
||||
) |
|
||||
|
const PostList: FC<Props> = ({ posts, collapseText }) => { |
||||
|
const [isCollapsed, setIsCollapsed] = useState(!!collapseText) |
||||
|
|
||||
|
const classes: ClassDictionary = { |
||||
|
'post-list': true, |
||||
|
'post-list-collapsed': isCollapsed, |
||||
|
} |
||||
|
|
||||
|
return ( |
||||
|
<div className={classNames(classes)}> |
||||
|
{isCollapsed && |
||||
|
<button className="button is-primary is-fullwidth" onClick={() => setIsCollapsed(false)}>{collapseText}</button> |
||||
|
} |
||||
|
|
||||
|
{!isCollapsed && posts.map(post => <PostComponent key={post.id} post={post} />)} |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
|
||||
export default PostList |
export default PostList |
@ -1,16 +1,16 @@ |
|||||
import { denormalize } from 'src/utils/normalization' |
import { denormalize } from 'src/utils/normalization' |
||||
import { AppState, Post, EntityType, EntityListKey } from 'src/types' |
import { AppState, Post, EntityType, EntityListKey } from 'src/types' |
||||
|
|
||||
export const getTimeline = (state: AppState) => { |
|
||||
const entityList = state.lists[EntityListKey.Timeline] |
|
||||
|
const getPostsFromList = (state: AppState, name: string) => { |
||||
|
const entityList = state.lists[name] |
||||
if (!entityList) return [] |
if (!entityList) return [] |
||||
|
|
||||
return denormalize(entityList.entities, EntityType.Post, state.entities) as Post[] |
return denormalize(entityList.entities, EntityType.Post, state.entities) as Post[] |
||||
} |
} |
||||
|
|
||||
export const getUserPosts = (state: AppState, id: string) => { |
|
||||
const entityList = state.lists[`posts:${id}`] |
|
||||
if (!entityList) return [] |
|
||||
|
export const getTimeline = (state: AppState) => getPostsFromList(state, EntityListKey.Timeline) |
||||
|
export const getUserPosts = (state: AppState, id: string) => getPostsFromList(state, `user:${id}:posts`) |
||||
|
export const getPostParents = (state: AppState, id: string) => getPostsFromList(state, `post:${id}:parents`) |
||||
|
export const getPostChildren = (state: AppState, id: string) => getPostsFromList(state, `post:${id}:children`) |
||||
|
|
||||
return denormalize(entityList.entities, EntityType.Post, state.entities) as Post[] |
|
||||
} |
|
||||
|
export const getPost = (state: AppState, id: string) => denormalize([id], EntityType.Post, state.entities)[0] as Post |
Write
Preview
Loading…
Cancel
Save
Reference in new issue