diff --git a/.gitignore b/.gitignore index f22dd34..7357ece 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,6 @@ /yarn-error.log yarn-debug.log* .yarn-integrity + +/app/assets/builds/* +!/app/assets/builds/.keep diff --git a/Gemfile b/Gemfile index 06ae301..2ad540a 100644 --- a/Gemfile +++ b/Gemfile @@ -54,3 +54,5 @@ end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] + +gem "tailwindcss-rails", "~> 2.0" diff --git a/Gemfile.lock b/Gemfile.lock index af5e557..3d9416c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -171,6 +171,8 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) + tailwindcss-rails (2.0.8-x86_64-darwin) + railties (>= 6.0.0) thor (1.2.1) tilt (2.0.10) turbolinks (5.2.1) @@ -215,6 +217,7 @@ DEPENDENCIES sass-rails (>= 6) selenium-webdriver (>= 4.0.0.rc1) spring + tailwindcss-rails (~> 2.0) turbolinks (~> 5) tzinfo-data web-console (>= 4.1.0) diff --git a/Procfile.dev b/Procfile.dev new file mode 100644 index 0000000..023e98a --- /dev/null +++ b/Procfile.dev @@ -0,0 +1,2 @@ +web: bin/rails server -p 3000 +css: bin/rails tailwindcss:watch diff --git a/app/assets/builds/.keep b/app/assets/builds/.keep new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index 5918193..338a0e8 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -1,2 +1,3 @@ //= link_tree ../images //= link_directory ../stylesheets .css +//= link_tree ../builds diff --git a/app/assets/stylesheets/application.tailwind.css b/app/assets/stylesheets/application.tailwind.css new file mode 100644 index 0000000..8666d2f --- /dev/null +++ b/app/assets/stylesheets/application.tailwind.css @@ -0,0 +1,13 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* + +@layer components { + .btn-primary { + @apply py-2 px-4 bg-blue-200; + } +} + +*/ diff --git a/app/assets/stylesheets/homepage.scss b/app/assets/stylesheets/homepage.scss new file mode 100644 index 0000000..dd85132 --- /dev/null +++ b/app/assets/stylesheets/homepage.scss @@ -0,0 +1,74 @@ +// Place all the styles related to the Homepage controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: https://sass-lang.com/ + +.main-container { + padding: 1.25rem; +} + +.title { + font-weight: 700; + font-size: 1.875rem; + line-height: 2.25rem; +} + +.new-task { + margin-top: 1.25rem; + margin-bottom: 1.25rem; +} + +label { + color: rgb(17, 24, 39) +} + +.task-input { + border-width: 1px; + border-color: rgb(209, 213, 219); + padding: 0.25rem; + margin-right: 0.5rem; + border-radius: 0.375rem; +} + +.add-task { + padding-top: 0.25rem; + padding-bottom: 0.25rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + background-color: #3B82F6; + color: #ffffff; + border-radius: 0.375rem; +} + +.task-container { + display: flex; + padding: 0.25rem; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + max-width: 20rem; + border-radius: 0.375rem; +} + +.task-container:hover { + background-color: #F3F4F6; +} + +.task-subcontainer { + display: flex; + flex-direction: row; + align-items: center; +} + +.checkbox { + margin-right: 0.5rem; + border-radius: 0.125rem; +} + +.description { + color: #111827; +} + +.remove-button { + margin-right: 0.5rem; +} \ No newline at end of file diff --git a/app/controllers/api/v1/tasks_controller.rb b/app/controllers/api/v1/tasks_controller.rb new file mode 100644 index 0000000..4ebfda8 --- /dev/null +++ b/app/controllers/api/v1/tasks_controller.rb @@ -0,0 +1,38 @@ +class Api::V1::TasksController < ApplicationController + def index + tasks = Task.all.order(created_at: :desc) + render json: tasks + end + + def create + task = Task.create!(task_params) + if task + render json: task + else + render json: task.errors + end + end + + def show + if task + render json: task + else + render json: task.errors + end + end + + def destroy + task&.destroy + render json: { message: 'Task deleted!' } + end + + private + + def task_params + params.permit(:description, :completed) + end + + def task + @task ||= Task.find(params[:id]) + end +end \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 09705d1..75f1154 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,2 +1,3 @@ class ApplicationController < ActionController::Base + skip_before_action :verify_authenticity_token end diff --git a/app/controllers/homepage_controller.rb b/app/controllers/homepage_controller.rb new file mode 100644 index 0000000..e09cc6e --- /dev/null +++ b/app/controllers/homepage_controller.rb @@ -0,0 +1,4 @@ +class HomepageController < ApplicationController + def index + end +end diff --git a/app/helpers/homepage_helper.rb b/app/helpers/homepage_helper.rb new file mode 100644 index 0000000..c5bbfe5 --- /dev/null +++ b/app/helpers/homepage_helper.rb @@ -0,0 +1,2 @@ +module HomepageHelper +end diff --git a/app/javascript/components/Home.jsx b/app/javascript/components/Home.jsx new file mode 100644 index 0000000..448c0ce --- /dev/null +++ b/app/javascript/components/Home.jsx @@ -0,0 +1,67 @@ +import React, { useState, useEffect } from "react"; + +export default () => { + const [tasks, setTasks] = useState([]); + const [inputValue, setInputValue] = useState(''); + + async function addTask () { + const newTask = { + description: inputValue, + } + const response = await fetch("/api/v1/tasks", { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(newTask), + }); + if (response.ok) { + loadTasks(); + } + } + + async function removeTask (id) { + const response = await fetch(`/api/v1/tasks/${id}`, { + method: 'DELETE', + }); + if (response.ok) { + loadTasks(); + } + } + + async function loadTasks () { + const response = await fetch("/api/v1/tasks") + if (response.ok) { + const jsonResponse = await response.json(); + setTasks(jsonResponse); + } + } + + useEffect(() => { + loadTasks(); + }, []); + + return ( +
+ { task.description } +
+