Add basic version of post show page

This commit is contained in:
riggraz
2019-09-12 15:51:45 +02:00
parent 5ca113b545
commit f599471af1
18 changed files with 330 additions and 11 deletions

View File

@@ -0,0 +1,42 @@
import { Action } from 'redux';
import { ThunkAction } from 'redux-thunk';
import { State } from '../reducers/rootReducer';
export const CHANGE_POST_STATUS_SUCCESS = 'CHANGE_POST_STATUS_SUCCESS';
interface ChangePostStatusSuccessAction {
type: typeof CHANGE_POST_STATUS_SUCCESS;
newPostStatusId;
}
const changePostStatusSuccess = (newPostStatusId: number): ChangePostStatusSuccessAction => ({
type: CHANGE_POST_STATUS_SUCCESS,
newPostStatusId,
});
export const changePostStatus = (
postId: number,
newPostStatusId: number,
authenticityToken: string,
): ThunkAction<void, State, null, Action<string>> => async (dispatch) => {
try {
const response = await fetch(`/posts/${postId}`, {
method: 'PATCH',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-CSRF-Token': authenticityToken,
},
body: JSON.stringify({
post: {
post_status_id: newPostStatusId,
},
})
});
if (response.status === 204) {
dispatch(changePostStatusSuccess(newPostStatusId));
}
} catch (e) {
console.log(e);
}
}

View File

@@ -0,0 +1,57 @@
import { Action } from 'redux';
import { ThunkAction } from 'redux-thunk';
import IPostJSON from '../interfaces/json/IPost';
import { State } from '../reducers/rootReducer';
export const POST_REQUEST_START = 'POST_REQUEST_START';
interface PostRequestStartAction {
type: typeof POST_REQUEST_START;
}
export const POST_REQUEST_SUCCESS = 'POST_REQUEST_SUCCESS';
interface PostRequestSuccessAction {
type: typeof POST_REQUEST_SUCCESS;
post: IPostJSON;
}
export const POST_REQUEST_FAILURE = 'POST_REQUEST_FAILURE';
interface PostRequestFailureAction {
type: typeof POST_REQUEST_FAILURE;
error: string;
}
export type PostRequestActionTypes =
PostRequestStartAction |
PostRequestSuccessAction |
PostRequestFailureAction;
const postRequestStart = (): PostRequestActionTypes => ({
type: POST_REQUEST_START,
});
export const postRequestSuccess = (post: IPostJSON): PostRequestActionTypes => ({
type: POST_REQUEST_SUCCESS,
post,
});
const postRequestFailure = (error: string): PostRequestActionTypes => ({
type: POST_REQUEST_FAILURE,
error,
});
export const requestPost = (
postId: number,
): ThunkAction<void, State, null, Action<string>> => async (dispatch) => {
dispatch(postRequestStart());
try {
const response = await fetch(`/posts/${postId}.json`);
const json = await response.json();
dispatch(postRequestSuccess(json));
} catch (e) {
dispatch(postRequestFailure(e));
}
}

View File

@@ -42,6 +42,7 @@ const PostList = ({
posts.length > 0 ?
posts.map((post, i) => (
<PostListItem
id={post.id}
title={post.title}
description={post.description}
postStatus={postStatuses.find(postStatus => postStatus.id === post.postStatusId)}

View File

@@ -3,13 +3,14 @@ import * as React from 'react';
import IPostStatus from '../../interfaces/IPostStatus';
interface Props {
id: number;
title: string;
description?: string;
postStatus: IPostStatus;
}
const PostListItem = ({ title, description, postStatus}: Props) => (
<a href="#" className="postLink">
const PostListItem = ({ id, title, description, postStatus}: Props) => (
<a href={`/posts/${id}`} className="postLink">
<div className="postListItem">
<div className="postTitle">{title}</div>
<div className="postDescription">

View File

@@ -0,0 +1,62 @@
import * as React from 'react';
import IPost from '../../interfaces/IPost';
import IPostStatus from '../../interfaces/IPostStatus';
import PostStatusSelect from './PostStatusSelect';
interface Props {
postId: number;
post: IPost;
postStatuses: Array<IPostStatus>;
isLoggedIn: boolean;
isPowerUser: boolean;
authenticityToken: string;
requestPost(postId: number): void;
changePostStatus(
postId: number,
newPostStatusId: number,
authenticityToken: string,
): void;
}
class PostP extends React.Component<Props> {
componentDidMount() {
this.props.requestPost(this.props.postId);
}
render() {
const {
post,
postStatuses,
isPowerUser,
authenticityToken,
changePostStatus,
} = this.props;
return (
<div>
<h1>{post.title}</h1>
{
isPowerUser ?
<PostStatusSelect
postStatuses={postStatuses}
selectedPostStatusId={post.postStatusId}
handleChange={
newPostStatusId => changePostStatus(post.id, newPostStatusId, authenticityToken)
}
/>
:
<span>LLL</span>
}
<p>{post.description}</p>
</div>
);
}
}
export default PostP;

View File

@@ -0,0 +1,37 @@
import * as React from 'react';
import { FormEvent } from 'react';
import IPostStatus from '../../interfaces/IPostStatus';
interface Props {
postStatuses: Array<IPostStatus>;
selectedPostStatusId: number;
handleChange(
newPostStatusId: number,
): void;
}
const PostStatusSelect = ({
postStatuses,
selectedPostStatusId,
handleChange,
}: Props) => (
<select
value={selectedPostStatusId || ''}
onChange={
(e: FormEvent) => (
handleChange(parseInt((e.target as HTMLSelectElement).value))
)}
className="selectPicker"
>
{postStatuses.map((postStatus, i) => (
<option value={postStatus.id} key={i}>
{postStatus.name}
</option>
))}
<option>None</option>
</select>
);
export default PostStatusSelect;

View File

@@ -0,0 +1,23 @@
import * as React from 'react';
import { Provider } from 'react-redux';
import store from '../../stores';
import Post from '../../containers/Post';
import '../../stylesheets/components/Post.scss';
const PostRoot = ({ postId, postStatuses, isLoggedIn, isPowerUser, authenticityToken }) => (
<Provider store={store}>
<Post
postId={postId}
postStatuses={postStatuses}
isLoggedIn={isLoggedIn}
isPowerUser={isPowerUser}
authenticityToken={authenticityToken}
/>
</Provider>
);
export default PostRoot;

View File

@@ -16,6 +16,7 @@ const PostList = ({ posts, boards }: Props) => (
posts.length > 0 ?
posts.map((post, i) => (
<PostListItem
id={post.id}
title={post.title}
boardName={boards.find(board => board.id === post.board_id).name}

View File

@@ -1,12 +1,13 @@
import * as React from 'react';
interface Props {
id: number;
title: string;
boardName: string;
}
const PostListItem = ({title, boardName}: Props) => (
<a href="#" className="postLink">
const PostListItem = ({id, title, boardName}: Props) => (
<a href={`/posts/${id}`} className="postLink">
<div className="postListItem">
<div className="postTitle">{title}</div>
<div className="postBoard">{boardName}</div>

View File

@@ -0,0 +1,27 @@
import { connect } from 'react-redux';
import { requestPost } from '../actions/requestPost';
import { changePostStatus } from '../actions/changePostStatus';
import { State } from '../reducers/rootReducer';
import PostP from '../components/Post/PostP';
const mapStateToProps = (state: State) => ({
post: state.currentPost,
});
const mapDispatchToProps = (dispatch) => ({
requestPost(postId: number) {
dispatch(requestPost(postId));
},
changePostStatus(postId: number, newPostStatusId: number, authenticityToken: string) {
dispatch(changePostStatus(postId, newPostStatusId, authenticityToken));
},
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(PostP);

View File

@@ -1,3 +1,13 @@
import {
POST_REQUEST_START,
POST_REQUEST_SUCCESS,
POST_REQUEST_FAILURE,
} from '../actions/requestPost';
import {
CHANGE_POST_STATUS_SUCCESS,
} from '../actions/changePostStatus';
import IPost from '../interfaces/IPost';
const initialState: IPost = {
@@ -15,7 +25,7 @@ const postReducer = (
action,
): IPost => {
switch (action.type) {
case 'CONVERT':
case POST_REQUEST_SUCCESS:
return {
id: action.post.id,
title: action.post.title,
@@ -26,6 +36,14 @@ const postReducer = (
createdAt: action.post.created_at,
};
case CHANGE_POST_STATUS_SUCCESS:
return {
...state,
postStatusId: action.newPostStatusId,
};
case POST_REQUEST_START:
case POST_REQUEST_FAILURE:
default:
return state;
}

View File

@@ -12,6 +12,8 @@ import {
POSTS_REQUEST_FAILURE,
} from '../actions/requestPosts';
import { postRequestSuccess } from '../actions/requestPost';
import {
ChangeFiltersActionTypes,
SET_SEARCH_FILTER,
@@ -54,9 +56,9 @@ const postsReducer = (
return {
...state,
items: action.page === 1 ?
action.posts.map(post => postReducer(undefined, {type: 'CONVERT', post})) //improve
action.posts.map(post => postReducer(undefined, postRequestSuccess(post)))
:
[...state.items, ...action.posts.map(post => postReducer(undefined, {type: 'CONVERT', post}))],
[...state.items, ...action.posts.map(post => postReducer(undefined, postRequestSuccess(post)))],
page: action.page,
haveMore: action.posts.length === 15,
areLoading: false,

View File

@@ -2,10 +2,12 @@ import { combineReducers } from 'redux';
import postsReducer from './postsReducer';
import postStatusesReducer from './postStatusesReducer';
import postReducer from './postReducer';
const rootReducer = combineReducers({
posts: postsReducer,
postStatuses: postStatusesReducer,
currentPost: postReducer,
});
export type State = ReturnType<typeof rootReducer>