Add post status administration (#105)

This commit is contained in:
Riccardo Graziosi
2022-05-01 18:00:38 +02:00
committed by GitHub
parent c5148147e3
commit 5256ea911a
47 changed files with 1580 additions and 32 deletions

View File

@@ -11,6 +11,7 @@ import Button from '../shared/Button';
import IBoard from '../../interfaces/IBoard';
import buildRequestHeaders from '../../helpers/buildRequestHeaders';
import HttpStatus from '../../constants/http_status';
interface Props {
board: IBoard;
@@ -106,7 +107,7 @@ class NewPost extends React.Component<Props, State> {
const json = await res.json();
this.setState({isLoading: false});
if (res.status === 201) {
if (res.status === HttpStatus.Created) {
this.setState({
success: 'Post published! You will be redirected soon...',

View File

@@ -0,0 +1,113 @@
import * as React from 'react';
import { Draggable } from 'react-beautiful-dnd';
import PostStatusLabel from "../../shared/PostStatusLabel";
import DragZone from '../../shared/DragZone';
import Separator from '../../shared/Separator';
import PostStatusForm from './PostStatusForm';
interface Props {
id: number;
name: string;
color: string;
index: number;
settingsAreUpdating: boolean;
handleUpdate(
id: number,
name: string,
color: string,
onSuccess: Function,
): void;
handleDelete(id: number): void;
}
interface State {
editMode: boolean;
}
class PostStatusEditable extends React.Component<Props, State> {
constructor(props) {
super(props);
this.state = {
editMode: false,
};
this.toggleEditMode = this.toggleEditMode.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
}
toggleEditMode() {
this.setState({editMode: !this.state.editMode});
}
handleUpdate(id: number, name: string, color: string) {
this.props.handleUpdate(
id,
name,
color,
() => this.setState({editMode: false}),
);
}
render() {
const {
id,
name,
color,
index,
settingsAreUpdating,
handleDelete
} = this.props;
const {editMode} = this.state;
return (
<Draggable key={id} draggableId={id.toString()} index={index} isDragDisabled={settingsAreUpdating}>
{provided => (
<li className="postStatusEditable" ref={provided.innerRef} {...provided.draggableProps}>
<DragZone dndProvided={provided} isDragDisabled={settingsAreUpdating} />
{ editMode === false ?
<React.Fragment>
<PostStatusLabel name={name} color={color} />
<div className="postStatusEditableActions">
<a onClick={this.toggleEditMode}>Edit</a>
<Separator />
<a
onClick={() => handleDelete(id)}
data-confirm="Are you sure?"
>
Delete
</a>
</div>
</React.Fragment>
:
<React.Fragment>
<PostStatusForm
mode='update'
id={id}
name={name}
color={color}
handleUpdate={this.handleUpdate}
/>
<a
className="postStatusFormCancelButton"
onClick={this.toggleEditMode}>
Cancel
</a>
</React.Fragment>
}
</li>
)}
</Draggable>
);
}
}
export default PostStatusEditable;

View File

@@ -0,0 +1,115 @@
import * as React from 'react';
import Button from '../../shared/Button';
interface Props {
mode: 'create' | 'update';
id?: number;
name?: string;
color?: string;
handleSubmit?(
name: string,
color: string,
onSuccess: Function,
): void;
handleUpdate?(
id: number,
name: string,
color: string,
): void;
}
interface State {
name: string;
color: string;
}
class PostStatusForm extends React.Component<Props, State> {
initialState: State = {
name: '',
color: this.getRandomColor(),
};
constructor(props: Props) {
super(props);
this.state = this.props.mode === 'create' ?
this.initialState
:
{
name: this.props.name,
color: this.props.color,
};
this.onSubmit = this.onSubmit.bind(this);
}
getRandomColor() {
return '#' + (Math.random() * 0xFFFFFF << 0).toString(16).padStart(6, '0');
}
isFormValid() {
return this.state.name && this.state.name.length > 0 &&
this.state.color && this.state.color.length === 7;
}
onNameChange(nameText: string) {
this.setState({
name: nameText,
});
}
onColorChange(colorText: string) {
this.setState({
color: colorText,
});
}
onSubmit() {
if (this.props.mode === 'create') {
this.props.handleSubmit(
this.state.name,
this.state.color,
() => this.setState({...this.initialState, color: this.getRandomColor()}),
);
} else {
this.props.handleUpdate(this.props.id, this.state.name, this.state.color);
}
}
render() {
const {mode} = this.props;
const {name, color} = this.state;
return (
<div className="postStatusForm">
<input
type="text"
placeholder="Name"
value={name}
onChange={e => this.onNameChange(e.target.value)}
className="form-control"
/>
<input
type="color"
value={color}
onChange={e => this.onColorChange(e.target.value)}
className="form-control"
/>
<Button
onClick={this.onSubmit}
className="newPostStatusButton"
disabled={!this.isFormValid()}
>
{mode === 'create' ? 'Create' : 'Save'}
</Button>
</div>
);
}
}
export default PostStatusForm;

View File

@@ -0,0 +1,135 @@
import * as React from 'react';
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import IPostStatus from '../../../interfaces/IPostStatus';
import { PostStatusesState } from "../../../reducers/postStatusesReducer";
import { CenteredMutedText } from '../../shared/CustomTexts';
import SiteSettingsInfoBox from '../../shared/SiteSettingsInfoBox';
import PostStatusForm from './PostStatusForm';
import PostStatusEditable from './PostStatusEditable';
import Spinner from '../../shared/Spinner';
interface Props {
authenticityToken: string;
postStatuses: PostStatusesState;
settingsAreUpdating: boolean;
settingsError: string;
requestPostStatuses(): void;
submitPostStatus(
name: string,
color: string,
onSuccess: Function,
authenticityToken: string,
): void;
updatePostStatus(
id: number,
name: string,
color: string,
onSuccess: Function,
authenticityToken: string,
): void;
updatePostStatusOrder(
id: number,
postStatuses: Array<IPostStatus>,
sourceIndex: number,
destinationIndex: number,
authenticityToken: string,
): void;
deletePostStatus(id: number, authenticityToken: string): void;
}
class PostStatusesSiteSettingsP extends React.Component<Props> {
constructor(props: Props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
this.handleDragEnd = this.handleDragEnd.bind(this);
this.handleDelete = this.handleDelete.bind(this);
}
componentDidMount() {
this.props.requestPostStatuses();
}
handleSubmit(name: string, color: string, onSuccess: Function) {
this.props.submitPostStatus(name, color, onSuccess, this.props.authenticityToken);
}
handleUpdate(id: number, name: string, color: string, onSuccess: Function) {
this.props.updatePostStatus(id, name, color, onSuccess, this.props.authenticityToken);
}
handleDragEnd(result) {
if (result.destination == null || result.source.index === result.destination.index)
return;
this.props.updatePostStatusOrder(
parseInt(result.draggableId),
this.props.postStatuses.items,
result.source.index,
result.destination.index,
this.props.authenticityToken,
);
}
handleDelete(id: number) {
this.props.deletePostStatus(id, this.props.authenticityToken);
}
render() {
const { postStatuses, settingsAreUpdating, settingsError } = this.props;
return (
<React.Fragment>
<div className="content">
<h2>Post statuses</h2>
{
postStatuses.items.length > 0 ?
<DragDropContext onDragEnd={this.handleDragEnd}>
<Droppable droppableId="postStatuses">
{provided => (
<ul ref={provided.innerRef} {...provided.droppableProps} className="postStatusList">
{postStatuses.items.map((postStatus, i) => (
<PostStatusEditable
id={postStatus.id}
name={postStatus.name}
color={postStatus.color}
index={i}
settingsAreUpdating={settingsAreUpdating}
handleUpdate={this.handleUpdate}
handleDelete={this.handleDelete}
key={postStatus.id}
/>
))}
{provided.placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
:
postStatuses.areLoading ?
<Spinner />
:
<CenteredMutedText>There are no post statuses. Create one below!</CenteredMutedText>
}
</div>
<div className="content">
<h2>New</h2>
<PostStatusForm mode='create' handleSubmit={this.handleSubmit} />
</div>
<SiteSettingsInfoBox areUpdating={settingsAreUpdating || postStatuses.areLoading} error={settingsError} />
</React.Fragment>
);
}
}
export default PostStatusesSiteSettingsP;

View File

@@ -0,0 +1,33 @@
import * as React from 'react';
import { Provider } from 'react-redux';
import { Store } from 'redux';
import PostStatusesSiteSettings from '../../../containers/PostStatusesSiteSettings';
import createStoreHelper from '../../../helpers/createStore';
import { State } from '../../../reducers/rootReducer';
interface Props {
authenticityToken: string;
}
class PostStatusesSiteSettingsRoot extends React.Component<Props> {
store: Store<State, any>;
constructor(props: Props) {
super(props);
this.store = createStoreHelper();
}
render() {
return (
<Provider store={this.store}>
<PostStatusesSiteSettings
authenticityToken={this.props.authenticityToken}
/>
</Provider>
);
}
}
export default PostStatusesSiteSettingsRoot;

View File

@@ -5,12 +5,14 @@ interface Props {
onClick(e: React.FormEvent): void;
className?: string;
outline?: boolean;
disabled?: boolean;
}
const Button = ({ children, onClick, className = '', outline = false}: Props) => (
const Button = ({ children, onClick, className = '', outline = false, disabled = false}: Props) => (
<button
onClick={onClick}
className={`${className} btn btn-${outline ? 'outline-' : ''}dark`}
disabled={disabled}
>
{children}
</button>

View File

@@ -0,0 +1,12 @@
import * as React from 'react';
const DragZone = ({dndProvided, isDragDisabled}) => (
<span
className={`drag-zone${isDragDisabled ? ' drag-zone-disabled' : ''}`}
{...dndProvided.dragHandleProps}
>
<span className="drag-icon"></span>
</span>
);
export default DragZone;

View File

@@ -0,0 +1,24 @@
import * as React from 'react';
import Spinner from './Spinner';
interface Props {
areUpdating: boolean;
error: string;
}
const SiteSettingsInfoBox = ({ areUpdating, error }: Props) => (
<div className="content siteSettingsInfo">
{
areUpdating ?
<Spinner />
:
error ?
<span className="error">An error occurred: {error}</span>
:
<span>Everything up to date</span>
}
</div>
);
export default SiteSettingsInfoBox;