mirror of
https://github.com/astuto/astuto.git
synced 2025-12-15 19:27:52 +01:00
Add post status administration (#105)
This commit is contained in:
committed by
GitHub
parent
c5148147e3
commit
5256ea911a
@@ -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...',
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
|
||||
12
app/javascript/components/shared/DragZone.tsx
Normal file
12
app/javascript/components/shared/DragZone.tsx
Normal 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;
|
||||
24
app/javascript/components/shared/SiteSettingsInfoBox.tsx
Normal file
24
app/javascript/components/shared/SiteSettingsInfoBox.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user