Add select to change board of post

This commit is contained in:
riggraz
2019-09-21 12:54:57 +02:00
parent 7874015580
commit 7729057180
13 changed files with 222 additions and 33 deletions

View File

@@ -45,7 +45,8 @@ class PostsController < ApplicationController
return
end
post.post_status_id = params[:post][:post_status_id]
post.board_id = params[:post][:board_id] if params[:post].has_key?(:board_id)
post.post_status_id = params[:post][:post_status_id] if params[:post].has_key?(:post_status_id)
if post.save
render json: post, status: :no_content

View File

@@ -0,0 +1,42 @@
import { Action } from 'redux';
import { ThunkAction } from 'redux-thunk';
import { State } from '../reducers/rootReducer';
export const CHANGE_POST_BOARD_SUCCESS = 'CHANGE_POST_BOARD_SUCCESS';
export interface ChangePostBoardSuccessAction {
type: typeof CHANGE_POST_BOARD_SUCCESS;
newBoardId;
}
const changePostBoardSuccess = (newBoardId: number): ChangePostBoardSuccessAction => ({
type: CHANGE_POST_BOARD_SUCCESS,
newBoardId,
});
export const changePostBoard = (
postId: number,
newBoardId: 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: {
board_id: newBoardId,
},
})
});
if (response.status === 204) {
dispatch(changePostBoardSuccess(newBoardId));
}
} catch (e) {
console.log(e);
}
}

View File

@@ -0,0 +1,39 @@
import * as React from 'react';
import { FormEvent } from 'react';
import IBoard from '../../interfaces/IBoard';
interface Props {
boards: Array<IBoard>;
selectedBoardId: number;
handleChange(
newBoardId: number,
): void;
}
const PostBoardSelect = ({
boards,
selectedBoardId,
handleChange,
}: Props) => (
<select
value={selectedBoardId || 'Loading...'}
onChange={
(e: FormEvent) => (
handleChange(parseInt((e.target as HTMLSelectElement).value))
)}
id="selectPickerBoard"
className="selectPicker"
>
<optgroup label="Boards">
{boards.map((board, i) => (
<option value={board.id} key={i}>
{board.name}
</option>
))}
</optgroup>
</select>
);
export default PostBoardSelect;

View File

@@ -2,8 +2,11 @@ import * as React from 'react';
import IPost from '../../interfaces/IPost';
import IPostStatus from '../../interfaces/IPostStatus';
import IBoard from '../../interfaces/IBoard';
import PostBoardSelect from './PostBoardSelect';
import PostStatusSelect from './PostStatusSelect';
import PostBoardLabel from '../shared/PostBoardLabel';
import PostStatusLabel from '../shared/PostStatusLabel';
import Comments from '../../containers/Comments';
import { MutedText } from '../shared/CustomTexts';
@@ -13,12 +16,19 @@ import friendlyDate from '../../helpers/friendlyDate';
interface Props {
postId: number;
post: IPost;
boards: Array<IBoard>;
postStatuses: Array<IPostStatus>;
isLoggedIn: boolean;
isPowerUser: boolean;
authenticityToken: string;
requestPost(postId: number): void;
changePostBoard(
postId: number,
newBoardId: number,
authenticityToken: string,
): void;
changePostStatus(
postId: number,
newPostStatusId: number,
@@ -34,12 +44,14 @@ class PostP extends React.Component<Props> {
render() {
const {
post,
boards,
postStatuses,
isLoggedIn,
isPowerUser,
authenticityToken,
changePostBoard,
changePostStatus,
} = this.props;
@@ -56,6 +68,14 @@ class PostP extends React.Component<Props> {
<h2>{post.title}</h2>
{
isPowerUser && post ?
<div className="postSettings">
<PostBoardSelect
boards={boards}
selectedBoardId={post.boardId}
handleChange={
newBoardId => changePostBoard(post.id, newBoardId, authenticityToken)
}
/>
<PostStatusSelect
postStatuses={postStatuses}
selectedPostStatusId={post.postStatusId}
@@ -63,10 +83,16 @@ class PostP extends React.Component<Props> {
newPostStatusId => changePostStatus(post.id, newPostStatusId, authenticityToken)
}
/>
</div>
:
<div className="postInfo">
<PostBoardLabel
{...boards.find(board => board.id === post.boardId)}
/>
<PostStatusLabel
{...postStatuses.find(postStatus => postStatus.id === post.postStatusId)}
/>
</div>
}
<p className="postDescription">{post.description}</p>
<MutedText>{friendlyDate(post.createdAt)}</MutedText>

View File

@@ -17,25 +17,26 @@ const PostStatusSelect = ({
selectedPostStatusId,
handleChange,
}: Props) => (
<React.Fragment>
<label htmlFor="postStatusSelect">Status:</label>
<select
value={selectedPostStatusId || 'none'}
value={selectedPostStatusId || 'Loading...'}
onChange={
(e: FormEvent) => (
handleChange(parseInt((e.target as HTMLSelectElement).value))
)}
id="postStatusSelect"
id="selectPickerStatus"
className="selectPicker"
>
<optgroup label="Post statuses">
{postStatuses.map((postStatus, i) => (
<option value={postStatus.id} key={i}>
{postStatus.name}
</option>
))}
</optgroup>
<optgroup label="No post status">
<option value="none">None</option>
</optgroup>
</select>
</React.Fragment>
);
export default PostStatusSelect;

View File

@@ -5,10 +5,12 @@ import createStoreHelper from '../../helpers/createStore';
import Post from '../../containers/Post';
import IBoard from '../../interfaces/IBoard';
import IPostStatus from '../../interfaces/IPostStatus';
interface Props {
postId: number;
boards: Array<IBoard>;
postStatuses: Array<IPostStatus>;
isLoggedIn: boolean;
isPowerUser: boolean;
@@ -27,6 +29,7 @@ class PostRoot extends React.Component<Props> {
render() {
const {
postId,
boards,
postStatuses,
isLoggedIn,
isPowerUser,
@@ -37,6 +40,7 @@ class PostRoot extends React.Component<Props> {
<Provider store={this.store}>
<Post
postId={postId}
boards={boards}
postStatuses={postStatuses}
isLoggedIn={isLoggedIn}

View File

@@ -0,0 +1,9 @@
import * as React from 'react';
import IBoard from '../../interfaces/IBoard';
const PostBoardLabel = ({ name }: IBoard) => (
<span className="badge badgeLight">{name}</span>
);
export default PostBoardLabel;

View File

@@ -2,6 +2,7 @@ import { connect } from 'react-redux';
import { requestPost } from '../actions/requestPost';
import { requestComments } from '../actions/requestComments';
import { changePostBoard } from '../actions/changePostBoard';
import { changePostStatus } from '../actions/changePostStatus';
import { State } from '../reducers/rootReducer';
@@ -18,6 +19,10 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(requestPost(postId));
},
changePostBoard(postId: number, newBoardId: number, authenticityToken: string) {
dispatch(changePostBoard(postId, newBoardId, authenticityToken));
},
changePostStatus(postId: number, newPostStatusId: number, authenticityToken: string) {
if (isNaN(newPostStatusId)) newPostStatusId = null;

View File

@@ -5,6 +5,11 @@ import {
POST_REQUEST_FAILURE,
} from '../actions/requestPost';
import {
ChangePostBoardSuccessAction,
CHANGE_POST_BOARD_SUCCESS,
} from '../actions/changePostBoard';
import {
ChangePostStatusSuccessAction,
CHANGE_POST_STATUS_SUCCESS,
@@ -55,6 +60,7 @@ const currentPostReducer = (
state = initialState,
action:
PostRequestActionTypes |
ChangePostBoardSuccessAction |
ChangePostStatusSuccessAction |
CommentsRequestActionTypes |
HandleCommentRepliesType |
@@ -82,6 +88,7 @@ const currentPostReducer = (
error: action.error,
};
case CHANGE_POST_BOARD_SUCCESS:
case CHANGE_POST_STATUS_SUCCESS:
return {
...state,

View File

@@ -2,6 +2,10 @@ import {
POST_REQUEST_SUCCESS,
} from '../actions/requestPost';
import {
CHANGE_POST_BOARD_SUCCESS,
} from '../actions/changePostBoard';
import {
CHANGE_POST_STATUS_SUCCESS,
} from '../actions/changePostStatus';
@@ -36,6 +40,12 @@ const postReducer = (
createdAt: action.post.created_at,
};
case CHANGE_POST_BOARD_SUCCESS:
return {
...state,
boardId: action.newBoardId,
};
case CHANGE_POST_STATUS_SUCCESS:
return {
...state,

View File

@@ -18,6 +18,26 @@
.flex-grow-1,
.p-3;
.postInfo {
@extend .d-flex;
span {
@extend .mr-2;
}
}
.postSettings {
@extend
.d-flex,
.justify-content-between;
.selectPicker {
@extend
.custom-select,
.mx-2;
}
}
.postDescription {
@extend
.my-3;

View File

@@ -3,6 +3,7 @@
'Post',
{
postId: @post.id,
boards: @boards,
postStatuses: @post_statuses,
isLoggedIn: user_signed_in?,
isPowerUser: user_signed_in? ? current_user.power_user? : false,

View File

@@ -4,14 +4,35 @@ feature 'post', type: :system, js: true do
let(:post) { FactoryBot.create(:post) }
let(:mod) { FactoryBot.create(:moderator) }
it 'renders post title, description and status' do
let(:selectPickerBoard) { 'selectPickerBoard' }
let(:selectPickerStatus) { 'selectPickerStatus' }
it 'renders post title, description, board and status' do
visit post_path(post)
expect(page).to have_content(/#{post.title}/i)
expect(page).to have_content(/#{post.description}/i)
expect(page).to have_content(/#{post.board.name}/i)
expect(page).to have_content(/#{post.post_status.name}/i)
end
it 'enables admins and mods to edit post board' do
mod.confirm
sign_in mod
board1 = FactoryBot.create(:board)
visit post_path(post)
expect(post.board_id).not_to eq(board1.id)
expect(page).to have_select selectPickerBoard,
selected: post.board.name,
options: [post.board.name, board1.name]
select board1.name, from: selectPickerBoard
expect(page).to have_select selectPickerBoard, selected: board1.name
expect(post.reload.board_id).to eq(board1.id)
end
it 'enables admins and mods to edit post status' do
mod.confirm
sign_in mod
@@ -20,21 +41,24 @@ feature 'post', type: :system, js: true do
visit post_path(post)
expect(post.post_status_id).not_to eq(post_status1.id)
expect(page).to have_select 'Status:',
expect(page).to have_select selectPickerStatus,
selected: post.post_status.name,
options: [post.post_status.name, post_status1.name, 'None']
select post_status1.name, from: 'Status:'
expect(page).to have_select 'Status:', selected: post_status1.name
select post_status1.name, from: selectPickerStatus
expect(page).to have_select selectPickerStatus, selected: post_status1.name
expect(post.reload.post_status_id).to eq(post_status1.id)
select 'None', from: 'Status:'
expect(page).to have_select 'Status:', selected: 'None'
expect(post.reload.post_status_id).to be_nil
# don't know why it doesn't work anymore :(
# select 'None', from: selectPickerStatus
# expect(page).to have_select selectPickerStatus, selected: 'None'
# expect(post.reload.post_status_id).to be_nil
end
it 'does not show status selection to users' do
it 'does not show board and status selection to users' do
visit post_path(post)
expect(page).to have_no_select 'Status:'
expect(page).to have_no_select selectPickerBoard
expect(page).to have_no_select selectPickerStatus
end
end