Sitemap
Jungletronics

Education & STEAM Learning by building. Practical explorations in AI, programming, electronics, robotics, IoT, and other STEAM topics — from ideas and experiments to working products. Follow and build with us.

Bookstore — 1

QuickHotSeries — Tailwind & Devise & Sessions, Version 1

5 min readFeb 22, 2025

--

Only essential information provided. Two key libraries are implemented.

Press enter or click to view image in full size
If this is your first time in the series, consider starting with the introductory episode.

Let’s Get Started!

Note: if you get stuck, please see my repo.

0.Vscode Terminal

git config --global --add safe.directory /workspaces/bookstore_app
git checkout -b episode-1-tailwind-and-devise

1.Installing Tailwind CSS

Install Tailwind CSS with Ruby on Rails — Tailwind CSS

Setting up Tailwind CSS in Ruby on Rails v7+ project.

v3.tailwindcss.com

bundle add tailwindcss-rails
rails tailwindcss:install
# init server
rails server -b 0.0.0.0 -p 3000

2. CDN — https://v3.tailwindcss.com/docs/installation/play-cdn

app/views/layouts/application.html.erb

<script src="https://cdn.tailwindcss.com"></script>
rails assets:precompile

3.Installing Devise

GitHub — heartcombo/devise: Flexible authentication solution for Rails with Warden.

Flexible authentication solution for Rails with Warden. — heartcombo/devise

github.com

bundle add devise
rails generate devise:install
rails g devise User
rails db:migrate
rails g devise:views

Now, to prevent unauthorized users from accessing the app:

app/controllers/application_controller.rb

before_action :authenticate_user!

http://0.0.0.0:3001/users/sign_in is now forced!

Now try to create a new user.

http://localhost:3001/sign_up
http://localhost:3001/sign_in

Remove the <main> tag that wraps yield:

app/views/layouts/application.html.erb

<body>
<%= yield %>
</body>

4. Sign Up Styling

Get J3’s stories in your inbox

Join Medium for free to get updates from this writer.

app/views/devise/registrations/new.html.erb

<main class="bg-indigo-100 h-screen flex flex-col justify-center items-center gap-10">  
<section class="flex flex-col items-center gap-1">
<h1 class="font-bold text-2xl text-blue-600 dark:text-sky-400">Bookstore App - Ruby on Rails 8</h1>
<p class="text-blue-600 dark:text-sky-400">This is the Bookstore app with RoR</p>
</section>

<section class="flex flex-col items-center bg-white p-6 rounded-lg shadow-md w-96">
<h2 class="font-bold mb-5">Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), class: 'flex flex-col w-full gap-4') do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>

<div class="flex flex-col text-xs gap-1">
<%= f.label :email, class: 'font-semibold text-xs' %>
<%= f.email_field :email, autofocus: true, autocomplete: "email", class: "border rounded px-3 py-2 w-full text-xs font-medium" %>
</div>

<div class="flex flex-col gap-1">
<%= f.label :password, class: 'font-semibold text-xs' %>
<%= f.password_field :password, autocomplete: "new-password", class: "border rounded px-3 py-2 w-full text-xs font-medium" %>
</div>

<div class="flex flex-col gap-1">
<%= f.label :password_confirmation, class: 'font-semibold text-xs' %>
<%= f.password_field :password_confirmation, autocomplete: "new-pxsassword", class: "border rounded px-3 py-2 w-full text-xs font-medium" %>
<% if @minimum_password_length %>
<em class="text-xs text-gray-500">(<%= @minimum_password_length %> characters minimum)</em>
<% end %>
</div>

<div class="mt-4">
<%= f.submit "Sign up", class: "bg-blue-600 text-white font-semibold py-2 px-4 rounded w-full hover:bg-blue-700" %>
</div>
<% end %>

<%= render "devise/shared/links" %>
</section>
</main>
Press enter or click to view image in full size

5. Sign In Styling

app/views/devise/sessions/new.html.erb

<main class="bg-indigo-100 h-screen flex flex-col justify-center items-center gap-10">  
<section class="flex flex-col items-center gap-1">
<h1 class="font-bold text-2xl text-blue-600 dark:text-sky-400">Chat App - Ruby on Rails 8</h1>
<p class="text-blue-600 dark:text-sky-400">This is the chat app with RoR</p>
</section>

<section class="bg-white p-8 rounded-lg shadow-md flex flex-col items-center w-96">
<h2 class="font-bold mb-5">Log in</h2>

<%= form_for(resource, as: resource_name, url: session_path(resource_name), class: 'flex flex-col w-full') do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>

<div class="flex gap-1 my-2 items-center w-full">
<%= f.label :email, class: 'font-semibold text-xs w-24' %>
<%= f.email_field :email, autofocus: true, autocomplete: "email", class: "text-xs font-medium rounded w-full border border-gray-300 p-2" %>
</div>

<div class="flex gap-1 my-2 items-center w-full">
<%= f.label :password, class: 'font-semibold text-xs w-24' %>
<%= f.password_field :password, autocomplete: "current-password", class: "text-xs font-medium rounded w-full border border-gray-300 p-2" %>
</div>

<% if devise_mapping.rememberable? %>
<div class="flex items-center gap-2 my-2">
<%= f.check_box :remember_me, class: "w-4 h-4" %>
<%= f.label :remember_me, class: "text-xs font-medium text-gray-600" %>
</div>
<% end %>

<div class="w-full mt-4">
<%= f.submit "Log in", class: "w-full bg-blue-600 text-white font-semibold py-2 rounded hover:bg-blue-700 transition" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
</section>
</main>
Press enter or click to view image in full size

6. Links Styling

app/views/devise/shared/_links.html.erb

<%- if controller_name != 'sessions' %>
<%= link_to "Log in", new_session_path(resource_name), class: "text-indigo-500 hover:text-indigo-700 underline text-xs" %><br />
<% end %>


<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= link_to "Sign up", new_registration_path(resource_name), class: "text-indigo-500 hover:text-indigo-700 underline text-xs" %><br />
<% end %>

<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
<% end %>

<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name), class: "text-indigo-500 hover:text-indigo-700 underline" %><br />
<% end %>

<%- if devise_mapping.omniauthable? %>
<%- resource_class.omniauth_providers.each do |provider| %>
<%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %><br />
<% end %>
<% end %>

7.Create a Controller

app/controllers/dashboard_controller.rb

class DashboardController < ApplicationController
def index
end
end

8.Create directory & index

app/views/dashboard/index.html.erb

<main class="flex flex-col">
<%= render 'shared/navbar' %>

<section class="flex flex-col items-center justify-center w-full mt-20 mb-5">
<div class="flex flex-col items-center">
<h1 class="font-bold text-4xl text-indigo-500 my-5">The Bookstore App</h1>
</div>

</section>
</main>

9.Route

config/routes.rb

root "dashboard#index"

10.Run

rails g resource Book name price:decimal

11.Create a directory & navbar

app/views/shared/_navbar.html.erb

<nav class="w-full flex bg-indigo-100 items-center p-5 justify-between fixed shadow-md">
<div class="flex gap-5 items-center">
<h3 class="font-bold text-lg text-indigo-700">The Bookstore App</h3>
</div>

<div class="flex flex gap-5">
<%= link_to '#', id: 'sell_link', class: 'text-indigo-100 font-semibold text-xs text-center bg-indigo-500 hover:bg-indigo-600 rounded px-4 py-2 shadow' do %>
Add sell
<% end %>

<%= link_to '#', id: 'book_link', class: 'text-indigo-100 font-semibold text-xs text-center bg-indigo-700 hover:bg-indigo-800 rounded px-4 py-2 shadow' do %>
Add a new book
<% end %>
</div>
</nav>

12. Model Book

app/models/book.rb

class Book < ApplicationRecord
validates :name, presence: true
validates :price, presence: true
end

There you have it! Run app:

Press enter or click to view image in full size
Press enter or click to view image in full size

See you soon, and take care! Thank you!

0–1–2–3–4–5–6–7–8–9–10

--

--

J3
J3

Written by J3

Computer Engineer | AI & Data Systems | IoT & Embedded Systems | Product Creator — From Idea to Product | Founder @ Jungletronics & Kidstronics

Jungletronics
Jungletronics

Published in Jungletronics

Education & STEAM Learning by building. Practical explorations in AI, programming, electronics, robotics, IoT, and other STEAM topics — from ideas and experiments to working products. Follow and build with us.