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' |
|||
|
|||
interface Props { |
|||
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 |
@ -1,16 +1,16 @@ |
|||
import { denormalize } from 'src/utils/normalization' |
|||
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 [] |
|||
|
|||
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