mirror of
https://github.com/astuto/astuto.git
synced 2025-12-15 03:07:52 +01:00
Add new post form
This commit is contained in:
3
app/assets/stylesheets/posts.scss
Normal file
3
app/assets/stylesheets/posts.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
// Place all the styles related to the posts controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
20
app/controllers/posts_controller.rb
Normal file
20
app/controllers/posts_controller.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
class PostsController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
|
||||
def create
|
||||
post = Post.new(post_params)
|
||||
post.user_id = current_user.id
|
||||
|
||||
if post.save
|
||||
render json: { status: 'success' }
|
||||
else
|
||||
render json: { status: 'error', message: post.errors.full_messages }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def post_params
|
||||
params.require(:post).permit(:title, :description, :board_id)
|
||||
end
|
||||
end
|
||||
2
app/helpers/posts_helper.rb
Normal file
2
app/helpers/posts_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module PostsHelper
|
||||
end
|
||||
29
app/javascript/components/Board/Board.tsx
Normal file
29
app/javascript/components/Board/Board.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import NewPost from './NewPost';
|
||||
import PostList from './PostList';
|
||||
|
||||
import IBoard from '../../interfaces/IBoard';
|
||||
|
||||
import '../../stylesheets/components/Board/Board.scss';
|
||||
|
||||
interface Props {
|
||||
board: IBoard;
|
||||
isLoggedIn: boolean;
|
||||
authenticityToken: string;
|
||||
}
|
||||
|
||||
class Board extends React.Component<Props> {
|
||||
render() {
|
||||
const { board, isLoggedIn, authenticityToken } = this.props;
|
||||
|
||||
return (
|
||||
<div className="boardContainer">
|
||||
<NewPost board={board} isLoggedIn={isLoggedIn} authenticityToken={authenticityToken} />
|
||||
<PostList />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Board;
|
||||
170
app/javascript/components/Board/NewPost.tsx
Normal file
170
app/javascript/components/Board/NewPost.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import NewPostForm from './NewPostForm';
|
||||
import Spinner from '../Shared/Spinner';
|
||||
|
||||
import IBoard from '../../interfaces/IBoard';
|
||||
|
||||
import '../../stylesheets/components/Board/NewPost.scss';
|
||||
|
||||
interface Props {
|
||||
board: IBoard;
|
||||
isLoggedIn: boolean;
|
||||
authenticityToken: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
showForm: boolean;
|
||||
error: string;
|
||||
success: string;
|
||||
isLoading: boolean;
|
||||
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
class NewPost extends React.Component<Props, State> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
showForm: false,
|
||||
error: '',
|
||||
success: '',
|
||||
isLoading: false,
|
||||
|
||||
title: '',
|
||||
description: '',
|
||||
};
|
||||
|
||||
this.toggleForm = this.toggleForm.bind(this);
|
||||
this.onTitleChange = this.onTitleChange.bind(this);
|
||||
this.onDescriptionChange = this.onDescriptionChange.bind(this);
|
||||
this.submitForm = this.submitForm.bind(this);
|
||||
}
|
||||
|
||||
toggleForm() {
|
||||
this.setState({
|
||||
showForm: !this.state.showForm,
|
||||
error: '',
|
||||
success: '',
|
||||
isLoading: false,
|
||||
});
|
||||
}
|
||||
|
||||
onTitleChange(title) {
|
||||
this.setState({
|
||||
title,
|
||||
error: '',
|
||||
});
|
||||
}
|
||||
|
||||
onDescriptionChange(description) {
|
||||
this.setState({
|
||||
description,
|
||||
});
|
||||
}
|
||||
|
||||
async submitForm(e) {
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({
|
||||
error: '',
|
||||
success: '',
|
||||
isLoading: true,
|
||||
});
|
||||
|
||||
const boardId = this.props.board.id;
|
||||
const { authenticityToken } = this.props;
|
||||
const { title, description } = this.state;
|
||||
|
||||
if (title === '') {
|
||||
this.setState({
|
||||
error: 'You forgot to enter a title!',
|
||||
isLoading: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let res = await fetch('http://localhost:3000/posts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': authenticityToken,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
post: {
|
||||
title,
|
||||
description,
|
||||
board_id: boardId,
|
||||
},
|
||||
}),
|
||||
});
|
||||
this.setState({isLoading: false});
|
||||
|
||||
let data = await res.json();
|
||||
|
||||
if (data.status === 'success') this.setState({success: 'Your post has been published!'});
|
||||
else this.setState({error: data.message});
|
||||
|
||||
} catch (e) {
|
||||
this.setState({
|
||||
error: 'An unknown error occurred, try again.'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { board, isLoggedIn } = this.props;
|
||||
const {
|
||||
showForm,
|
||||
error,
|
||||
success,
|
||||
isLoading,
|
||||
|
||||
title,
|
||||
description
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<div className="newBoardContainer">
|
||||
<span className="boardName">{board.name}</span>
|
||||
<span className="boardDescription">{board.description}</span>
|
||||
{/* <span>{this.props.authenticityToken}</span> */}
|
||||
{
|
||||
isLoggedIn ?
|
||||
<button
|
||||
onClick={this.toggleForm}
|
||||
className="submitBtn btn btn-dark">
|
||||
{ showForm ? 'Cancel' : 'Submit feedback' }
|
||||
</button>
|
||||
:
|
||||
<a href="http://localhost:3000/users/sign_in" className="btn btn-dark">
|
||||
Log in / Sign up
|
||||
</a>
|
||||
}
|
||||
|
||||
{
|
||||
showForm ?
|
||||
<NewPostForm
|
||||
title={title}
|
||||
description={description}
|
||||
handleTitleChange={this.onTitleChange}
|
||||
handleDescriptionChange={this.onDescriptionChange}
|
||||
handleSubmit={this.submitForm}
|
||||
/>
|
||||
:
|
||||
null
|
||||
}
|
||||
|
||||
{ isLoading ? <Spinner /> : null }
|
||||
{ error ? <span className="error">{error}</span> : null }
|
||||
{ success ? <span className="success">{success}</span> : null }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NewPost;
|
||||
55
app/javascript/components/Board/NewPostForm.tsx
Normal file
55
app/javascript/components/Board/NewPostForm.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import '../../stylesheets/components/Board/NewPostForm.scss';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
handleTitleChange(title: string): void;
|
||||
handleDescriptionChange(description: string): void;
|
||||
handleSubmit(e: object): void;
|
||||
}
|
||||
|
||||
const NewPostForm = ({
|
||||
title,
|
||||
description,
|
||||
handleTitleChange,
|
||||
handleDescriptionChange,
|
||||
handleSubmit,
|
||||
}: Props) => (
|
||||
<div className="newPostForm">
|
||||
<form>
|
||||
<div className="form-group">
|
||||
<label htmlFor="postTitle">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={e => handleTitleChange(e.target.value)}
|
||||
|
||||
id="postTitle"
|
||||
className="form-control"
|
||||
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="postDescription">Description (optional)</label>
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={e => handleDescriptionChange(e.target.value)}
|
||||
rows={3}
|
||||
|
||||
className="form-control"
|
||||
id="postDescription"
|
||||
></textarea>
|
||||
</div>
|
||||
<button
|
||||
onClick={e => handleSubmit(e)}
|
||||
className="submitBtn btn btn-dark d-block mx-auto">
|
||||
Submit feedback
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default NewPostForm;
|
||||
11
app/javascript/components/Board/PostList.tsx
Normal file
11
app/javascript/components/Board/PostList.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import '../../stylesheets/components/Board/PostList.scss';
|
||||
|
||||
const PostList = () => (
|
||||
<div className="postListContainer">
|
||||
Posts will be show here.
|
||||
</div>
|
||||
);
|
||||
|
||||
export default PostList;
|
||||
9
app/javascript/components/Shared/Spinner.tsx
Normal file
9
app/javascript/components/Shared/Spinner.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import * as React from 'react';
|
||||
|
||||
const Spinner = () => (
|
||||
<div className="spinner-border" role="status">
|
||||
<span className="sr-only">Loading...</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Spinner;
|
||||
7
app/javascript/stylesheets/components/Board/Board.scss
Normal file
7
app/javascript/stylesheets/components/Board/Board.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
.boardContainer {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
47
app/javascript/stylesheets/components/Board/NewPost.scss
Normal file
47
app/javascript/stylesheets/components/Board/NewPost.scss
Normal file
@@ -0,0 +1,47 @@
|
||||
.newBoardContainer {
|
||||
flex: 0 0 auto;
|
||||
|
||||
width: 250px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
border: 1px solid black;
|
||||
border-radius: 4px;
|
||||
|
||||
padding: 8px;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.boardName {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.boardDescription {
|
||||
color: grey;
|
||||
font-size: 17px;
|
||||
font-weight: 200;
|
||||
|
||||
text-align: center;
|
||||
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.submitBtn {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: green;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.submitBtn {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
.postListContainer {
|
||||
flex: 1 1 auto;
|
||||
|
||||
border: 1px solid black;
|
||||
border-radius: 4px;
|
||||
|
||||
padding: 8px;
|
||||
margin: 8px;
|
||||
}
|
||||
@@ -1,2 +1,10 @@
|
||||
<h1><%= @board.name %></h1>
|
||||
<p><%= @board.description %></p>
|
||||
<%=
|
||||
react_component(
|
||||
'Board/Board',
|
||||
{
|
||||
board: @board,
|
||||
isLoggedIn: user_signed_in?,
|
||||
authenticityToken: form_authenticity_token,
|
||||
}
|
||||
)
|
||||
%>
|
||||
@@ -13,4 +13,6 @@ Rails.application.routes.draw do
|
||||
devise_for :users
|
||||
|
||||
resources :boards, only: [:show]
|
||||
|
||||
post '/posts', to: 'posts#create'
|
||||
end
|
||||
|
||||
5
spec/controllers/posts_controller_spec.rb
Normal file
5
spec/controllers/posts_controller_spec.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe PostsController, type: :controller do
|
||||
|
||||
end
|
||||
15
spec/helpers/posts_helper_spec.rb
Normal file
15
spec/helpers/posts_helper_spec.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
require 'rails_helper'
|
||||
|
||||
# Specs in this file have access to a helper object that includes
|
||||
# the PostsHelper. For example:
|
||||
#
|
||||
# describe PostsHelper do
|
||||
# describe "string concat" do
|
||||
# it "concats two strings with spaces" do
|
||||
# expect(helper.concat_strings("this","that")).to eq("this that")
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
RSpec.describe PostsHelper, type: :helper do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
||||
Reference in New Issue
Block a user