├── .DS_Store ├── .browserslistrc ├── .gitignore ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── app ├── .DS_Store ├── assets │ ├── .DS_Store │ ├── config │ │ └── manifest.js │ ├── images │ │ ├── .DS_Store │ │ ├── .keep │ │ ├── home_page.png │ │ └── loader.gif │ └── stylesheets │ │ ├── application.css │ │ └── home.scss ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── controllers │ ├── api │ │ └── v1 │ │ │ └── blogs_controller.rb │ ├── application_controller.rb │ ├── concerns │ │ └── .keep │ └── home_controller.rb ├── helpers │ ├── application_helper.rb │ └── home_helper.rb ├── javascript │ ├── .DS_Store │ ├── channels │ │ ├── consumer.js │ │ └── index.js │ ├── components │ │ ├── App.js │ │ ├── Blog.js │ │ ├── Blogs.js │ │ └── Home.js │ ├── packs │ │ ├── application.js │ │ └── index.jsx │ ├── routes │ │ └── Index.js │ ├── shared │ │ ├── Alert.js │ │ ├── BlogForm.js │ │ ├── Header.js │ │ └── PageLoader.js │ └── stylesheets │ │ ├── App.css │ │ ├── application.scss │ │ └── tailwind.config.js ├── jobs │ └── application_job.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ ├── blog.rb │ └── concerns │ │ └── .keep └── views │ ├── home │ └── index.html.erb │ └── layouts │ ├── application.html.erb │ ├── mailer.html.erb │ └── mailer.text.erb ├── babel.config.js ├── bin ├── bundle ├── rails ├── rake ├── setup ├── spring ├── webpack ├── webpack-dev-server └── yarn ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── content_security_policy.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml ├── puma.rb ├── routes.rb ├── spring.rb ├── storage.yml ├── webpack │ ├── development.js │ ├── environment.js │ ├── production.js │ └── test.js └── webpacker.yml ├── db ├── migrate │ └── 20200811034930_create_blogs.rb ├── schema.rb └── seeds.rb ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── package.json ├── postcss.config.js ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico └── robots.txt ├── storage └── .keep ├── tmp ├── .keep └── pids │ └── .keep ├── vendor └── .keep └── yarn.lock /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MA-Ahmad/react_rails_blog/75a9b99956c80e45fd9d454e9dfaf9b7d508d232/.DS_Store -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | /tmp/* 13 | !/log/.keep 14 | !/tmp/.keep 15 | 16 | # Ignore pidfiles, but keep the directory. 17 | /tmp/pids/* 18 | !/tmp/pids/ 19 | !/tmp/pids/.keep 20 | 21 | # Ignore uploaded files in development. 22 | /storage/* 23 | !/storage/.keep 24 | 25 | /public/assets 26 | .byebug_history 27 | 28 | # Ignore master key for decrypting credentials and more. 29 | /config/master.key 30 | 31 | /public/packs 32 | /public/packs-test 33 | /node_modules 34 | /yarn-error.log 35 | yarn-debug.log* 36 | .yarn-integrity 37 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.7.0 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | ruby '2.7.0' 5 | 6 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 7 | gem 'rails', '~> 6.0.3', '>= 6.0.3.2' 8 | # Use postgresql as the database for Active Record 9 | gem 'pg', '>= 0.18', '< 2.0' 10 | # Use Puma as the app server 11 | gem 'puma', '~> 4.1' 12 | # Use SCSS for stylesheets 13 | gem 'sass-rails', '>= 6' 14 | # Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker 15 | gem 'webpacker', '~> 4.0' 16 | # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks 17 | gem 'turbolinks', '~> 5' 18 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 19 | gem 'jbuilder', '~> 2.7' 20 | # Use Redis adapter to run Action Cable in production 21 | # gem 'redis', '~> 4.0' 22 | # Use Active Model has_secure_password 23 | # gem 'bcrypt', '~> 3.1.7' 24 | 25 | # Use Active Storage variant 26 | # gem 'image_processing', '~> 1.2' 27 | 28 | # Reduces boot times through caching; required in config/boot.rb 29 | gem 'bootsnap', '>= 1.4.2', require: false 30 | 31 | group :development, :test do 32 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 33 | gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] 34 | end 35 | 36 | group :development do 37 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code. 38 | gem 'web-console', '>= 3.3.0' 39 | gem 'listen', '~> 3.2' 40 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 41 | gem 'spring' 42 | gem 'spring-watcher-listen', '~> 2.0.0' 43 | end 44 | 45 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 46 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 47 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (6.0.3.2) 5 | actionpack (= 6.0.3.2) 6 | nio4r (~> 2.0) 7 | websocket-driver (>= 0.6.1) 8 | actionmailbox (6.0.3.2) 9 | actionpack (= 6.0.3.2) 10 | activejob (= 6.0.3.2) 11 | activerecord (= 6.0.3.2) 12 | activestorage (= 6.0.3.2) 13 | activesupport (= 6.0.3.2) 14 | mail (>= 2.7.1) 15 | actionmailer (6.0.3.2) 16 | actionpack (= 6.0.3.2) 17 | actionview (= 6.0.3.2) 18 | activejob (= 6.0.3.2) 19 | mail (~> 2.5, >= 2.5.4) 20 | rails-dom-testing (~> 2.0) 21 | actionpack (6.0.3.2) 22 | actionview (= 6.0.3.2) 23 | activesupport (= 6.0.3.2) 24 | rack (~> 2.0, >= 2.0.8) 25 | rack-test (>= 0.6.3) 26 | rails-dom-testing (~> 2.0) 27 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 28 | actiontext (6.0.3.2) 29 | actionpack (= 6.0.3.2) 30 | activerecord (= 6.0.3.2) 31 | activestorage (= 6.0.3.2) 32 | activesupport (= 6.0.3.2) 33 | nokogiri (>= 1.8.5) 34 | actionview (6.0.3.2) 35 | activesupport (= 6.0.3.2) 36 | builder (~> 3.1) 37 | erubi (~> 1.4) 38 | rails-dom-testing (~> 2.0) 39 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 40 | activejob (6.0.3.2) 41 | activesupport (= 6.0.3.2) 42 | globalid (>= 0.3.6) 43 | activemodel (6.0.3.2) 44 | activesupport (= 6.0.3.2) 45 | activerecord (6.0.3.2) 46 | activemodel (= 6.0.3.2) 47 | activesupport (= 6.0.3.2) 48 | activestorage (6.0.3.2) 49 | actionpack (= 6.0.3.2) 50 | activejob (= 6.0.3.2) 51 | activerecord (= 6.0.3.2) 52 | marcel (~> 0.3.1) 53 | activesupport (6.0.3.2) 54 | concurrent-ruby (~> 1.0, >= 1.0.2) 55 | i18n (>= 0.7, < 2) 56 | minitest (~> 5.1) 57 | tzinfo (~> 1.1) 58 | zeitwerk (~> 2.2, >= 2.2.2) 59 | bindex (0.8.1) 60 | bootsnap (1.4.7) 61 | msgpack (~> 1.0) 62 | builder (3.2.4) 63 | byebug (11.1.3) 64 | concurrent-ruby (1.1.7) 65 | crass (1.0.6) 66 | erubi (1.9.0) 67 | ffi (1.13.1) 68 | globalid (0.4.2) 69 | activesupport (>= 4.2.0) 70 | i18n (1.8.5) 71 | concurrent-ruby (~> 1.0) 72 | jbuilder (2.10.0) 73 | activesupport (>= 5.0.0) 74 | listen (3.2.1) 75 | rb-fsevent (~> 0.10, >= 0.10.3) 76 | rb-inotify (~> 0.9, >= 0.9.10) 77 | loofah (2.6.0) 78 | crass (~> 1.0.2) 79 | nokogiri (>= 1.5.9) 80 | mail (2.7.1) 81 | mini_mime (>= 0.1.1) 82 | marcel (0.3.3) 83 | mimemagic (~> 0.3.2) 84 | method_source (1.0.0) 85 | mimemagic (0.3.5) 86 | mini_mime (1.0.2) 87 | mini_portile2 (2.4.0) 88 | minitest (5.14.1) 89 | msgpack (1.3.3) 90 | nio4r (2.5.2) 91 | nokogiri (1.10.10) 92 | mini_portile2 (~> 2.4.0) 93 | pg (1.2.3) 94 | puma (4.3.5) 95 | nio4r (~> 2.0) 96 | rack (2.2.3) 97 | rack-proxy (0.6.5) 98 | rack 99 | rack-test (1.1.0) 100 | rack (>= 1.0, < 3) 101 | rails (6.0.3.2) 102 | actioncable (= 6.0.3.2) 103 | actionmailbox (= 6.0.3.2) 104 | actionmailer (= 6.0.3.2) 105 | actionpack (= 6.0.3.2) 106 | actiontext (= 6.0.3.2) 107 | actionview (= 6.0.3.2) 108 | activejob (= 6.0.3.2) 109 | activemodel (= 6.0.3.2) 110 | activerecord (= 6.0.3.2) 111 | activestorage (= 6.0.3.2) 112 | activesupport (= 6.0.3.2) 113 | bundler (>= 1.3.0) 114 | railties (= 6.0.3.2) 115 | sprockets-rails (>= 2.0.0) 116 | rails-dom-testing (2.0.3) 117 | activesupport (>= 4.2.0) 118 | nokogiri (>= 1.6) 119 | rails-html-sanitizer (1.3.0) 120 | loofah (~> 2.3) 121 | railties (6.0.3.2) 122 | actionpack (= 6.0.3.2) 123 | activesupport (= 6.0.3.2) 124 | method_source 125 | rake (>= 0.8.7) 126 | thor (>= 0.20.3, < 2.0) 127 | rake (13.0.1) 128 | rb-fsevent (0.10.4) 129 | rb-inotify (0.10.1) 130 | ffi (~> 1.0) 131 | sass-rails (6.0.0) 132 | sassc-rails (~> 2.1, >= 2.1.1) 133 | sassc (2.4.0) 134 | ffi (~> 1.9) 135 | sassc-rails (2.1.2) 136 | railties (>= 4.0.0) 137 | sassc (>= 2.0) 138 | sprockets (> 3.0) 139 | sprockets-rails 140 | tilt 141 | spring (2.1.0) 142 | spring-watcher-listen (2.0.1) 143 | listen (>= 2.7, < 4.0) 144 | spring (>= 1.2, < 3.0) 145 | sprockets (4.0.2) 146 | concurrent-ruby (~> 1.0) 147 | rack (> 1, < 3) 148 | sprockets-rails (3.2.1) 149 | actionpack (>= 4.0) 150 | activesupport (>= 4.0) 151 | sprockets (>= 3.0.0) 152 | thor (1.0.1) 153 | thread_safe (0.3.6) 154 | tilt (2.0.10) 155 | turbolinks (5.2.1) 156 | turbolinks-source (~> 5.2) 157 | turbolinks-source (5.2.0) 158 | tzinfo (1.2.7) 159 | thread_safe (~> 0.1) 160 | web-console (4.0.4) 161 | actionview (>= 6.0.0) 162 | activemodel (>= 6.0.0) 163 | bindex (>= 0.4.0) 164 | railties (>= 6.0.0) 165 | webpacker (4.2.2) 166 | activesupport (>= 4.2) 167 | rack-proxy (>= 0.6.1) 168 | railties (>= 4.2) 169 | websocket-driver (0.7.3) 170 | websocket-extensions (>= 0.1.0) 171 | websocket-extensions (0.1.5) 172 | zeitwerk (2.4.0) 173 | 174 | PLATFORMS 175 | ruby 176 | 177 | DEPENDENCIES 178 | bootsnap (>= 1.4.2) 179 | byebug 180 | jbuilder (~> 2.7) 181 | listen (~> 3.2) 182 | pg (>= 0.18, < 2.0) 183 | puma (~> 4.1) 184 | rails (~> 6.0.3, >= 6.0.3.2) 185 | sass-rails (>= 6) 186 | spring 187 | spring-watcher-listen (~> 2.0.0) 188 | turbolinks (~> 5) 189 | tzinfo-data 190 | web-console (>= 3.3.0) 191 | webpacker (~> 4.0) 192 | 193 | RUBY VERSION 194 | ruby 2.7.0p0 195 | 196 | BUNDLED WITH 197 | 2.1.2 198 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

ReactRails Blog App

2 | 3 |
4 | 5 | [![Linkedin](https://img.shields.io/badge/-LinkedIn-blue?style=flat&logo=Linkedin&logoColor=white)](https://www.linkedin.com/in/muhammad-ahmad20/) 6 | [![Gmail](https://img.shields.io/badge/-Gmail-c14438?style=flat&logo=Gmail&logoColor=white)](mailto:muhammad.ahmad8043@gmail.com) 7 |
8 |

This app shows you how to create a CRUD app with ROR on back-end and React on front-end with Tailwindcss.

9 | 10 | ![Home Page](app/assets/images/home_page.png) 11 | 12 | ## This project will help you: 13 | 14 | - To configure and create a CRUD Rails application with React. 15 | - To configure and use Tailwind in React-Rails(RR) app. 16 | - To show custom alert messages by using Tailwind. 17 | 18 | ## What this App does? 19 | 20 | - By this app, you will be able to: 21 | 22 | - Create a new Blog. 23 | - Edit a Blog. 24 | - Delete a Blog. 25 | - Get a speicific Blog. 26 | - Get list of all Blogs. 27 | 28 | ## Built with 29 | 30 | - Ruby on Rails 31 | - Reactjs (react hooks, react router) 32 | - React-icons 33 | - Tailwind used for styling 34 | 35 | ## Setup 36 | 37 | - Clone the repository 38 | - Use `cd ` 39 | - Run `bundle install` 40 | - Run `npm install` 41 | - Run `rails db:create` 42 | - Run `rails db:migrate` 43 | - Run `rails db:seed` 44 | - Run `rails s` 45 | Now check it on browser `localhost:3000` 46 | 47 | ## Author 48 | 49 | Muhammad Ahmad 50 | 51 | - Github: [@Ahmad](https://github.com/MA-Ahmad) 52 | 53 | - LinkedIn: [Muhammad Ahmad](https://www.linkedin.com/in/muhammad-ahmad20/) 54 | 55 | ## 🤝 Contributing 56 | 57 | Contributions, issues and feature requests are welcome! Start by: 58 | 59 | - Forking the project 60 | - Cloning the project to your local machine 61 | - `cd` into the project directory 62 | - Run `git checkout -b your-branch-name` 63 | - Make your contributions 64 | - Push your branch up to your forked repository 65 | - Open a Pull Request with a detailed description to the development branch of the original project for a review 66 | 67 | ## Show your support 68 | 69 | Give a ⭐️ if you like this project! -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MA-Ahmad/react_rails_blog/75a9b99956c80e45fd9d454e9dfaf9b7d508d232/app/.DS_Store -------------------------------------------------------------------------------- /app/assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MA-Ahmad/react_rails_blog/75a9b99956c80e45fd9d454e9dfaf9b7d508d232/app/assets/.DS_Store -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | -------------------------------------------------------------------------------- /app/assets/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MA-Ahmad/react_rails_blog/75a9b99956c80e45fd9d454e9dfaf9b7d508d232/app/assets/images/.DS_Store -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MA-Ahmad/react_rails_blog/75a9b99956c80e45fd9d454e9dfaf9b7d508d232/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/home_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MA-Ahmad/react_rails_blog/75a9b99956c80e45fd9d454e9dfaf9b7d508d232/app/assets/images/home_page.png -------------------------------------------------------------------------------- /app/assets/images/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MA-Ahmad/react_rails_blog/75a9b99956c80e45fd9d454e9dfaf9b7d508d232/app/assets/images/loader.gif -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's 6 | * vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /app/assets/stylesheets/home.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the home controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: https://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/api/v1/blogs_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::V1::BlogsController < ApplicationController 2 | before_action :find_blog, except: [:index, :new, :create] 3 | 4 | def index 5 | @blogs = Blog.all.order(created_at: :desc) 6 | render json: @blogs 7 | end 8 | 9 | def show 10 | if @blog 11 | render json: @blog 12 | else 13 | render json: @blog.errors 14 | end 15 | end 16 | 17 | def new 18 | @blog = Blog.new 19 | render json: @blog 20 | end 21 | 22 | def create 23 | @blog = Blog.create!(blog_params) 24 | if @blog 25 | render json: @blog 26 | else 27 | render json: @blog.errors 28 | end 29 | end 30 | 31 | def edit 32 | render json: @blog 33 | end 34 | 35 | def update 36 | if @blog.update(blog_params) 37 | render json: @blog 38 | else 39 | render json: @blog.errors 40 | end 41 | end 42 | 43 | def destroy 44 | if @blog.destroy 45 | render json: { message: 'Blog deleted!' } 46 | else 47 | render json: @blog.error 48 | end 49 | end 50 | 51 | private 52 | 53 | def find_blog 54 | @blog = Blog.find(params[:id]) 55 | end 56 | 57 | def blog_params 58 | params.require(:blog).permit(:title, :author, :content, :image) 59 | end 60 | end -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MA-Ahmad/react_rails_blog/75a9b99956c80e45fd9d454e9dfaf9b7d508d232/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | def index 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/home_helper.rb: -------------------------------------------------------------------------------- 1 | module HomeHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/javascript/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MA-Ahmad/react_rails_blog/75a9b99956c80e45fd9d454e9dfaf9b7d508d232/app/javascript/.DS_Store -------------------------------------------------------------------------------- /app/javascript/channels/consumer.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command. 3 | 4 | import { createConsumer } from "@rails/actioncable" 5 | 6 | export default createConsumer() 7 | -------------------------------------------------------------------------------- /app/javascript/channels/index.js: -------------------------------------------------------------------------------- 1 | // Load all the channels within this directory and all subdirectories. 2 | // Channel files must be named *_channel.js. 3 | 4 | const channels = require.context('.', true, /_channel\.js$/) 5 | channels.keys().forEach(channels) 6 | -------------------------------------------------------------------------------- /app/javascript/components/App.js: -------------------------------------------------------------------------------- 1 | // import Routes from "../routes/Index"; 2 | 3 | // export default () => <>{Routes}; 4 | 5 | import React, { useState, useEffect } from "react"; 6 | import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; 7 | import Home from "./Home"; 8 | import Blogs from "./Blogs"; 9 | import Blog from "./Blog"; 10 | import BlogForm from "../shared/BlogForm"; 11 | import Header from "../shared/Header"; 12 | 13 | const App = () => { 14 | const [isDataUpdated, setIsDataUpdated] = useState(false); 15 | const [editAlert, setEditAlert] = useState(false); 16 | const [createAlert, setCreateAlert] = useState(false); 17 | 18 | const handleData = status => { 19 | setIsDataUpdated(status); 20 | }; 21 | 22 | const handleEditAlert = status => { 23 | setEditAlert(status); 24 | }; 25 | 26 | const handleCreateAlert = status => { 27 | setCreateAlert(status); 28 | }; 29 | 30 | return ( 31 | 32 |
33 |
34 | 35 | { 39 | return ( 40 | 47 | ); 48 | }} 49 | /> 50 | { 54 | return ( 55 | 62 | ); 63 | }} 64 | /> 65 | { 69 | return ( 70 | 77 | ); 78 | }} 79 | /> 80 | { 84 | return ( 85 | 92 | ); 93 | }} 94 | /> 95 | 96 | 97 |
98 | 99 | ); 100 | }; 101 | 102 | export default App; 103 | -------------------------------------------------------------------------------- /app/javascript/components/Blog.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import PageLoader from "../shared/PageLoader"; 3 | 4 | const Blog = ({ match }) => { 5 | const [blog, setBlog] = useState({}); 6 | useEffect(() => { 7 | fetch(`/api/v1/blogs/${match.params.id}`) 8 | .then(response => response.json()) 9 | .then(response => { 10 | setBlog(response); 11 | }); 12 | }, []); 13 | 14 | return ( 15 | <> 16 | {blog.id ? ( 17 |
18 |
19 | Blog image 24 |
25 |
26 |
27 |
28 | Title: 29 | {blog.title} 30 |
31 |
32 | Title: 33 | {blog.author} 34 |
35 |
36 | Content: 37 |
{blog.content}
38 |
39 |
40 |
41 |
42 | ) : ( 43 | 44 | )} 45 | 46 | ); 47 | }; 48 | 49 | export default Blog; 50 | -------------------------------------------------------------------------------- /app/javascript/components/Blogs.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import Dotdotdot from "react-dotdotdot"; 4 | import Alert from "../shared/Alert"; 5 | import { Img } from "react-image"; 6 | import PageLoader from "../shared/PageLoader"; 7 | 8 | const Blogs = props => { 9 | const [isDataUpdated, setIsDataUpdated] = useState(props.isDataUpdated); 10 | const [blogs, setBlogs] = useState([]); 11 | const [createAlert, setCreateAlert] = useState(props.createAlert); 12 | const [editAlert, setEditAlert] = useState(props.editAlert); 13 | const [showAlert, setShowAlert] = useState(false); 14 | const [color, setColor] = useState(""); 15 | const [message, setMessage] = useState(""); 16 | 17 | const fetchRequest = () => { 18 | const url = "/api/v1/blogs"; 19 | fetch(url) 20 | .then(response => response.json()) 21 | .then(response => { 22 | setBlogs(response); 23 | }); 24 | setIsDataUpdated(false); 25 | }; 26 | 27 | useEffect(() => { 28 | if (createAlert) { 29 | setShowAlert(true); 30 | setColor("teal"); 31 | setMessage("Blog created successfully"); 32 | } else if (editAlert) { 33 | setShowAlert(true); 34 | setColor("teal"); 35 | setMessage("Blog updated successfully"); 36 | } 37 | fetchRequest(); 38 | }, [isDataUpdated]); 39 | 40 | const deleteBlog = id => { 41 | const token = document.querySelector('meta[name="csrf-token"]').content; 42 | fetch(`/api/v1/blogs/destroy/${id}`, { 43 | method: "DELETE", 44 | headers: { 45 | "X-CSRF-Token": token, 46 | "Content-Type": "application/json" 47 | } 48 | }) 49 | .then(response => { 50 | if (response.ok) { 51 | return response.json(); 52 | } 53 | throw new Error("Network response was not ok."); 54 | }) 55 | .then(() => props.history.push("/")) 56 | .catch(error => console.log(error.message)); 57 | // setBlogId(id); 58 | setIsDataUpdated(true); 59 | setShowAlert(true); 60 | setColor("red"); 61 | setMessage("Blog deleted successfully"); 62 | setEditAlert(false); 63 | setCreateAlert(false); 64 | }; 65 | 66 | return ( 67 | <> 68 | {showAlert ? : null} 69 |
70 | {blogs && 71 | blogs.map(blog => { 72 | return ( 73 |
77 | 78 | Blog image} 83 | /> 84 | 85 |
86 |
87 | {blog.title} 88 |
89 | 90 |

{blog.content}

91 |
92 |
93 |
94 | 98 | {blog.author} 99 | 100 | 101 |
102 | 103 | 104 | Edit 105 | 106 | 107 | deleteBlog(blog.id)} 109 | className="inline-block bg-red-200 rounded-full px-3 py-1 ml-2 text-sm font-semibold text-gray-700 cursor-pointer" 110 | > 111 | Delete 112 | 113 |
114 |
115 |
116 | ); 117 | })} 118 |
119 | 120 | ); 121 | }; 122 | 123 | export default Blogs; 124 | -------------------------------------------------------------------------------- /app/javascript/components/Home.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Blogs from "./Blogs"; 3 | 4 | const Home = props => { 5 | return ; 6 | }; 7 | 8 | export default Home; 9 | -------------------------------------------------------------------------------- /app/javascript/packs/application.js: -------------------------------------------------------------------------------- 1 | // This file is automatically compiled by Webpack, along with any other files 2 | // present in this directory. You're encouraged to place your actual application logic in 3 | // a relevant structure within app/javascript and only use these pack files to reference 4 | // that code so it'll be compiled. 5 | 6 | require("@rails/ujs").start(); 7 | require("turbolinks").start(); 8 | require("@rails/activestorage").start(); 9 | require("channels"); 10 | 11 | // Tailwind 12 | import "stylesheets/application"; 13 | // Uncomment to copy all static images under ../images to the output folder and reference 14 | // them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>) 15 | // or the `imagePath` JavaScript helper below. 16 | // 17 | // const images = require.context('../images', true) 18 | // const imagePath = (name) => images(name, true) 19 | -------------------------------------------------------------------------------- /app/javascript/packs/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "../components/App"; 4 | 5 | document.addEventListener("DOMContentLoaded", () => { 6 | ReactDOM.render( 7 | , 8 | document.body.appendChild(document.createElement("div")) 9 | ); 10 | }); 11 | -------------------------------------------------------------------------------- /app/javascript/routes/Index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; 3 | import Home from "../components/Home"; 4 | import Blogs from "../components/Blogs"; 5 | import Blog from "../components/Blog"; 6 | import BlogForm from "../shared/BlogForm"; 7 | import Header from "../shared/Header"; 8 | 9 | export default ( 10 | 11 |
12 |
13 | 14 | 15 | 16 | { 20 | return ; 21 | }} 22 | /> 23 | { 27 | return ; 28 | }} 29 | /> 30 | 31 | 32 |
33 | 34 | ); 35 | -------------------------------------------------------------------------------- /app/javascript/shared/Alert.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { FiBell } from "react-icons/fi"; 3 | 4 | const Alert = ({ color, message }) => { 5 | const [showAlert, setShowAlert] = useState(true); 6 | return ( 7 | <> 8 | {showAlert ? ( 9 |
16 | 17 | 18 | 19 | 20 | {message} 21 | 22 | 28 |
29 | ) : null} 30 | 31 | ); 32 | }; 33 | 34 | export default Alert; 35 | -------------------------------------------------------------------------------- /app/javascript/shared/BlogForm.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import Alert from "./Alert"; 3 | 4 | const BlogForm = props => { 5 | const { match, history, editMode, handleData } = props; 6 | const [alert, setAlert] = useState(false); 7 | const [color, setColor] = useState(""); 8 | const [message, setMessage] = useState(""); 9 | const [title, setTitle] = useState(""); 10 | const [author, setAuthor] = useState(""); 11 | const [content, setContent] = useState(""); 12 | 13 | useEffect(() => { 14 | if (editMode) { 15 | fetch(`/api/v1/blogs/edit/${match.params.id}`) 16 | .then(response => response.json()) 17 | .then(response => { 18 | setTitle(response.title); 19 | setAuthor(response.author); 20 | setContent(response.content); 21 | }); 22 | } else { 23 | setTitle(""); 24 | setAuthor(""); 25 | setContent(""); 26 | } 27 | }, [editMode]); 28 | 29 | const sendRequest = url => { 30 | const body = { 31 | title, 32 | author, 33 | content: content.replace(/\n/g, "

") 34 | }; 35 | 36 | const token = document.querySelector('meta[name="csrf-token"]').content; 37 | fetch(url, { 38 | method: editMode ? "PUT" : "POST", 39 | headers: { 40 | "X-CSRF-Token": token, 41 | "Content-Type": "application/json" 42 | }, 43 | body: JSON.stringify(body) 44 | }) 45 | .then(response => { 46 | if (response.ok) { 47 | return response.json(); 48 | } 49 | throw new Error("Network response was not ok."); 50 | }) 51 | .then(response => { 52 | if (editMode) { 53 | props.handleEditAlert(true); 54 | props.handleCreateAlert(false); 55 | } else { 56 | props.handleCreateAlert(true); 57 | props.handleEditAlert(false); 58 | } 59 | return history.push("/"); 60 | }) 61 | .catch(error => console.log(error.message)); 62 | }; 63 | 64 | const handleSubmit = event => { 65 | if (title.length == 0 || author.length == 0) { 66 | setAlert(true); 67 | setColor("red"); 68 | setMessage("Please fill the required fields"); 69 | } else { 70 | const url = editMode 71 | ? `/api/v1/blogs/update/${match.params.id}` 72 | : "/api/v1/blogs/create"; 73 | sendRequest(url); 74 | handleData(true); 75 | } 76 | event.preventDefault(); 77 | }; 78 | 79 | return ( 80 | <> 81 | {alert ? : null} 82 |
83 |
84 |
88 |
89 | 95 | setTitle(e.target.value)} 102 | /> 103 |
104 |
105 | 111 | setAuthor(e.target.value)} 118 | /> 119 |
120 |
121 | 127 |