From edacfb1a4f0fb889abebd1a30de4ba21a3f46eb6 Mon Sep 17 00:00:00 2001 From: riggraz Date: Mon, 2 Sep 2019 14:32:57 +0200 Subject: [PATCH] Add new post form --- app/assets/stylesheets/posts.scss | 3 + app/controllers/posts_controller.rb | 20 +++ app/helpers/posts_helper.rb | 2 + app/javascript/components/Board/Board.tsx | 29 +++ app/javascript/components/Board/NewPost.tsx | 170 ++++++++++++++++++ .../components/Board/NewPostForm.tsx | 55 ++++++ app/javascript/components/Board/PostList.tsx | 11 ++ app/javascript/components/Shared/Spinner.tsx | 9 + .../stylesheets/components/Board/Board.scss | 7 + .../stylesheets/components/Board/NewPost.scss | 47 +++++ .../components/Board/NewPostForm.scss | 3 + .../components/Board/PostList.scss | 9 + app/views/boards/show.html.erb | 12 +- config/routes.rb | 2 + spec/controllers/posts_controller_spec.rb | 5 + spec/helpers/posts_helper_spec.rb | 15 ++ 16 files changed, 397 insertions(+), 2 deletions(-) create mode 100644 app/assets/stylesheets/posts.scss create mode 100644 app/controllers/posts_controller.rb create mode 100644 app/helpers/posts_helper.rb create mode 100644 app/javascript/components/Board/Board.tsx create mode 100644 app/javascript/components/Board/NewPost.tsx create mode 100644 app/javascript/components/Board/NewPostForm.tsx create mode 100644 app/javascript/components/Board/PostList.tsx create mode 100644 app/javascript/components/Shared/Spinner.tsx create mode 100644 app/javascript/stylesheets/components/Board/Board.scss create mode 100644 app/javascript/stylesheets/components/Board/NewPost.scss create mode 100644 app/javascript/stylesheets/components/Board/NewPostForm.scss create mode 100644 app/javascript/stylesheets/components/Board/PostList.scss create mode 100644 spec/controllers/posts_controller_spec.rb create mode 100644 spec/helpers/posts_helper_spec.rb diff --git a/app/assets/stylesheets/posts.scss b/app/assets/stylesheets/posts.scss new file mode 100644 index 00000000..1a7e1539 --- /dev/null +++ b/app/assets/stylesheets/posts.scss @@ -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/ diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb new file mode 100644 index 00000000..c092518b --- /dev/null +++ b/app/controllers/posts_controller.rb @@ -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 diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb new file mode 100644 index 00000000..a7b8cec8 --- /dev/null +++ b/app/helpers/posts_helper.rb @@ -0,0 +1,2 @@ +module PostsHelper +end diff --git a/app/javascript/components/Board/Board.tsx b/app/javascript/components/Board/Board.tsx new file mode 100644 index 00000000..a673274d --- /dev/null +++ b/app/javascript/components/Board/Board.tsx @@ -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 { + render() { + const { board, isLoggedIn, authenticityToken } = this.props; + + return ( +
+ + +
+ ); + } +} + +export default Board; \ No newline at end of file diff --git a/app/javascript/components/Board/NewPost.tsx b/app/javascript/components/Board/NewPost.tsx new file mode 100644 index 00000000..e7a4139c --- /dev/null +++ b/app/javascript/components/Board/NewPost.tsx @@ -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 { + 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 ( +
+ {board.name} + {board.description} + {/* {this.props.authenticityToken} */} + { + isLoggedIn ? + + : + + Log in / Sign up + + } + + { + showForm ? + + : + null + } + + { isLoading ? : null } + { error ? {error} : null } + { success ? {success} : null } +
+ ); + } +} + +export default NewPost; \ No newline at end of file diff --git a/app/javascript/components/Board/NewPostForm.tsx b/app/javascript/components/Board/NewPostForm.tsx new file mode 100644 index 00000000..7ca3bd07 --- /dev/null +++ b/app/javascript/components/Board/NewPostForm.tsx @@ -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) => ( +
+
+
+ + handleTitleChange(e.target.value)} + + id="postTitle" + className="form-control" + + autoFocus + /> +
+
+ + +
+ +
+
+); + +export default NewPostForm; \ No newline at end of file diff --git a/app/javascript/components/Board/PostList.tsx b/app/javascript/components/Board/PostList.tsx new file mode 100644 index 00000000..752159b5 --- /dev/null +++ b/app/javascript/components/Board/PostList.tsx @@ -0,0 +1,11 @@ +import * as React from 'react'; + +import '../../stylesheets/components/Board/PostList.scss'; + +const PostList = () => ( +
+ Posts will be show here. +
+); + +export default PostList; \ No newline at end of file diff --git a/app/javascript/components/Shared/Spinner.tsx b/app/javascript/components/Shared/Spinner.tsx new file mode 100644 index 00000000..426b0929 --- /dev/null +++ b/app/javascript/components/Shared/Spinner.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; + +const Spinner = () => ( +
+ Loading... +
+); + +export default Spinner; \ No newline at end of file diff --git a/app/javascript/stylesheets/components/Board/Board.scss b/app/javascript/stylesheets/components/Board/Board.scss new file mode 100644 index 00000000..b6021f91 --- /dev/null +++ b/app/javascript/stylesheets/components/Board/Board.scss @@ -0,0 +1,7 @@ +.boardContainer { + display: flex; + flex: 1 1 auto; + justify-content: space-between; + align-items: flex-start; + flex-wrap: nowrap; +} \ No newline at end of file diff --git a/app/javascript/stylesheets/components/Board/NewPost.scss b/app/javascript/stylesheets/components/Board/NewPost.scss new file mode 100644 index 00000000..e5b39d59 --- /dev/null +++ b/app/javascript/stylesheets/components/Board/NewPost.scss @@ -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; +} \ No newline at end of file diff --git a/app/javascript/stylesheets/components/Board/NewPostForm.scss b/app/javascript/stylesheets/components/Board/NewPostForm.scss new file mode 100644 index 00000000..58c252ac --- /dev/null +++ b/app/javascript/stylesheets/components/Board/NewPostForm.scss @@ -0,0 +1,3 @@ +.submitBtn { + margin-bottom: 8px; +} \ No newline at end of file diff --git a/app/javascript/stylesheets/components/Board/PostList.scss b/app/javascript/stylesheets/components/Board/PostList.scss new file mode 100644 index 00000000..2e470059 --- /dev/null +++ b/app/javascript/stylesheets/components/Board/PostList.scss @@ -0,0 +1,9 @@ +.postListContainer { + flex: 1 1 auto; + + border: 1px solid black; + border-radius: 4px; + + padding: 8px; + margin: 8px; +} \ No newline at end of file diff --git a/app/views/boards/show.html.erb b/app/views/boards/show.html.erb index bc53eb84..aae054db 100644 --- a/app/views/boards/show.html.erb +++ b/app/views/boards/show.html.erb @@ -1,2 +1,10 @@ -

<%= @board.name %>

-

<%= @board.description %>

\ No newline at end of file +<%= + react_component( + 'Board/Board', + { + board: @board, + isLoggedIn: user_signed_in?, + authenticityToken: form_authenticity_token, + } + ) +%> \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 509a47c2..a027eeb7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -13,4 +13,6 @@ Rails.application.routes.draw do devise_for :users resources :boards, only: [:show] + + post '/posts', to: 'posts#create' end diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb new file mode 100644 index 00000000..d2e1d4b4 --- /dev/null +++ b/spec/controllers/posts_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe PostsController, type: :controller do + +end diff --git a/spec/helpers/posts_helper_spec.rb b/spec/helpers/posts_helper_spec.rb new file mode 100644 index 00000000..f3d00cbc --- /dev/null +++ b/spec/helpers/posts_helper_spec.rb @@ -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