diff --git a/.gitignore b/.gitignore index aa7cbcf..ffe7624 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,5 @@ node_modules .env .irb_history + +/.vite/ diff --git a/Gemfile b/Gemfile index 2f921f3..811da4e 100644 --- a/Gemfile +++ b/Gemfile @@ -32,6 +32,7 @@ gem 'sidekiq' # Authentication gem 'devise' +gem 'devise-tailwindcssed', github: 'realstorypro/devise-tailwindcssed-ruby', branch: 'ruby-3.2-support' # Pagination gem 'kaminari', github: 'kaminari/kaminari', branch: 'master' diff --git a/Gemfile.lock b/Gemfile.lock index fd27e34..ab7a347 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -26,6 +26,15 @@ GIT kaminari-core (= 1.2.2) kaminari-core (1.2.2) +GIT + remote: https://github.com/realstorypro/devise-tailwindcssed-ruby.git + revision: 39c29ee8b13a2f969ed45b4c4764248a8f2b4acb + branch: ruby-3.2-support + specs: + devise-tailwindcssed (0.1.3) + rails (>= 5.2.3.4, < 7.1) + railties (> 4.0, < 7.1) + GIT remote: https://github.com/realstorypro/rapid-ui.git revision: fa56a4ca118d969f46839ff5d28188af306f9f45 @@ -444,6 +453,7 @@ DEPENDENCIES config deep_cloneable (~> 3.2.0) devise + devise-tailwindcssed! dotenv-rails factory_bot_rails faker diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 09705d1..8238b2f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,2 +1,13 @@ class ApplicationController < ActionController::Base + layout :layout_by_resource + + private + + def layout_by_resource + if devise_controller? + 'devise_layout' # Name of the layout for Devise controllers + else + 'application' # Default layout + end + end end diff --git a/app/helpers/devise_helper.rb b/app/helpers/devise_helper.rb new file mode 100644 index 0000000..57f3a77 --- /dev/null +++ b/app/helpers/devise_helper.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +# rubocop:disable Metrics/AbcSize, Metrics/MethodLength +# devise helper +module DeviseHelper + def devise_error_messages! + return if resource.errors.empty? + + messages = resource.errors.full_messages.map { |msg| content_tag(:p, "- #{msg}.") } + .join + sentence = I18n.t( + "errors.messages.not_saved", + count: resource.errors.count, + resource: resource.class.model_name.human.downcase + ) + + html = <<-HTML + + HTML + + html.html_safe + end + + def devise_simple_error_messages! + return if resource.errors.empty? + + sentence = "Ooops!" + if resource.errors.count == 1 + message = resource.errors.full_messages[0] + html = <<-HTML + + HTML + else + messages = resource.errors.full_messages.map { |msg| content_tag(:li, "#{msg}.") } + .join + html = <<-HTML + + HTML + end + + html.html_safe + end +end +# rubocop:enable Metrics/AbcSize, Metrics/MethodLength diff --git a/app/views/devise/confirmations/new.html.slim b/app/views/devise/confirmations/new.html.slim new file mode 100644 index 0000000..d170c95 --- /dev/null +++ b/app/views/devise/confirmations/new.html.slim @@ -0,0 +1,12 @@ +=r ux.div "w-full max-w-sm" + = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: "bg-white mb-4 px-8 pt-6 pb-8 rounded shadow-md" }) do |f| + =r ux.h2 "font-hairline mb-6 text-center" + | Resend Confirmation Instructions + = devise_error_messages! + =r ux.div "mb-4" + = f.label :email, class: "block font-bold mb-2 text-gray-700 text-sm" + = f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email), class: "appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none shadow focus:shadow-outline" + =r ux.div "mb-4" + = f.submit "Resend Confirmation Info", class: "button bg-blue-500 hover:bg-blue-700 font-bold text-white focus:outline-none py-2 px-4 rounded focus:shadow-outline w-full" + = render "devise/shared/links" + = render "devise/shared/form_footer" diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb new file mode 100644 index 0000000..dc55f64 --- /dev/null +++ b/app/views/devise/mailer/confirmation_instructions.html.erb @@ -0,0 +1,5 @@ +

Welcome <%= @email %>!

+ +

You can confirm your account email through the link below:

+ +

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

diff --git a/app/views/devise/mailer/email_changed.html.erb b/app/views/devise/mailer/email_changed.html.erb new file mode 100644 index 0000000..32f4ba8 --- /dev/null +++ b/app/views/devise/mailer/email_changed.html.erb @@ -0,0 +1,7 @@ +

Hello <%= @email %>!

+ +<% if @resource.try(:unconfirmed_email?) %> +

We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.

+<% else %> +

We're contacting you to notify you that your email has been changed to <%= @resource.email %>.

+<% end %> diff --git a/app/views/devise/mailer/password_change.html.erb b/app/views/devise/mailer/password_change.html.erb new file mode 100644 index 0000000..b41daf4 --- /dev/null +++ b/app/views/devise/mailer/password_change.html.erb @@ -0,0 +1,3 @@ +

Hello <%= @resource.email %>!

+ +

We're contacting you to notify you that your password has been changed.

diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb new file mode 100644 index 0000000..f667dc1 --- /dev/null +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -0,0 +1,8 @@ +

Hello <%= @resource.email %>!

+ +

Someone has requested a link to change your password. You can do this through the link below.

+ +

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

+ +

If you didn't request this, please ignore this email.

+

Your password won't change until you access the link above and create a new one.

diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb new file mode 100644 index 0000000..41e148b --- /dev/null +++ b/app/views/devise/mailer/unlock_instructions.html.erb @@ -0,0 +1,7 @@ +

Hello <%= @resource.email %>!

+ +

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

+ +

Click the link below to unlock your account:

+ +

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

diff --git a/app/views/devise/passwords/edit.html.slim b/app/views/devise/passwords/edit.html.slim new file mode 100644 index 0000000..20e50d1 --- /dev/null +++ b/app/views/devise/passwords/edit.html.slim @@ -0,0 +1,37 @@ +=r ux.div "w-full max-w-sm" + = form_for(resource, + as: resource_name, + url: user_password_path(resource_name), + html: { method: :put, + class: "bg-white mb-10 px-8 pt-1 pb-8 rounded shadow-md" }) do |f| + + =r ux.h2 "font-hairline mb-6 text-center" + | Change Your Password + = devise_error_messages! + + = f.hidden_field :reset_password_token + + =r ux.div "mb-4" + = f.label :password, "New Password", class: "block font-bold mb-2 text-gray-700 text-sm" + - if @minimum_password_length + =r ux.em 'text-gray-600' + ="(#{@minimum_password_length} characters minimum)" + = f.password_field :password, + autofocus: true, + autocomplete: "new-password", + class: "appearance-none border leading-tight focus:outline-none px-3 py-2 rounded shadow focus:shadow-outline text-gray-700 w-full" + + =r ux.div "mb-4" + = f.label :password_confirmation, + "Confirm New Password", + class: "block font-bold mb-2 text-gray-700 text-sm" + = f.password_field :password_confirmation, + autocomplete: "off", + class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" + + =r ux.div "mb-4" + = f.submit "Change My Password", + class: "button bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline w-full" + + = render "devise/shared/links" + = render "devise/shared/form_footer" diff --git a/app/views/devise/passwords/new.html.slim b/app/views/devise/passwords/new.html.slim new file mode 100644 index 0000000..9ade99a --- /dev/null +++ b/app/views/devise/passwords/new.html.slim @@ -0,0 +1,22 @@ +=r ux.div "w-full max-w-sm" + = form_for(resource, + as: resource_name, + url: password_path(resource_name), + html: { method: :post, + class: "bg-white mb-10 px-8 pt-1 pb-8 rounded shadow-md" }) do |f| + + =r ux.h2 "font-hairline mb-6 text-center" + | Forgot your password? + = devise_error_messages! + + =r ux.div "mb-4" + = f.label :email, class: "block font-bold mb-2 text-gray-700 text-sm" + = f.email_field :email, autofocus: true, autocomplete: "email", + class: "appearance-none border leading-tight focus:outline-none px-3 py-2 rounded shadow focus:shadow-outline text-gray-700 w-full" + + =r ux.div "mb-4" + = f.submit "Send Password Reset Info", + class: "button bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline w-full" + + = render "devise/shared/links" + = render "devise/shared/form_footer" \ No newline at end of file diff --git a/app/views/devise/registrations/edit.html.slim b/app/views/devise/registrations/edit.html.slim new file mode 100644 index 0000000..ff50272 --- /dev/null +++ b/app/views/devise/registrations/edit.html.slim @@ -0,0 +1,41 @@ +=r ux.div "w-full max-w-md" + = form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: "bg-white mb-10 px-8 pt-1 pb-8 rounded shadow-md" }) do |f| + =r ux.h2 "font-hairline mb-6 text-center" + = "Edit #{resource_name.to_s.humanize}" + = devise_error_messages! + + =r ux.div "mb-4" + = f.label :email, class: "block font-bold mb-0 text-gray-700 text-sm" + = f.email_field :email, autofocus: true, autocomplete: "email", class: "appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none shadow focus:shadow-outline" + - if devise_mapping.confirmable? && resource.pending_reconfirmation? + =r ux.div + = "Currently waiting confirmation for: #{resource.unconfirmed_email}" + + =r ux.div "mb-4" + = f.label :password, class: "block font-bold mb-2 text-gray-700 text-sm" + = f.password_field :password, autocomplete: "new-password", class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" + i + | (leave blank if you don't want to change it) + + =r ux.div "mb-4" + = f.label :password_confirmation, class: "block font-bold mb-2 text-gray-700 text-sm" + = f.password_field :password_confirmation, autocomplete: "new-password", class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" + + =r ux.div "mb-4" + = f.label :current_password, class: "block font-bold mb-2 text-gray-700 text-sm" + = f.password_field :current_password, autocomplete: "current-password", class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" + =r ux.div "actions" + = f.submit "Update", class: "button bg-blue-500 hover:bg-blue-700 font-bold text-white focus:outline-none py-2 px-4 rounded focus:shadow-outline w-full" + + =r ux.divider + + =r ux.h4 'text-center !mt-8' + | Unhappy? + =r ux.div 'text-center' + = button_to "Delete my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete, + class: "button bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline w-full" + + br + + =r ux.div 'text-center' + = link_to "Back", root_path \ No newline at end of file diff --git a/app/views/devise/registrations/new.html.slim b/app/views/devise/registrations/new.html.slim new file mode 100644 index 0000000..639276c --- /dev/null +++ b/app/views/devise/registrations/new.html.slim @@ -0,0 +1,30 @@ +=r ux.div "w-full max-w-sm" + = form_for(resource, as: resource_name, url: registration_path(resource_name), html: { class: "bg-white mb-10 px-8 pt-1 pb-8 rounded shadow-md" }) do |f| + =r ux.h2 "font-hairline mb-6 text-center" + | Sign Up + = devise_error_messages! + + =r ux.div "mb-4" + = f.label :email, class: "block font-bold mb-2 text-gray-700 text-sm" + = f.email_field :email, autocomplete: "email", placeholder: "user@example.com", class: "appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none shadow focus:shadow-outline" + + =r ux.div "mb-4" + = f.label :password, class: "block font-bold mb-2 text-gray-700 text-sm" + - if @minimum_password_length + =r ux.small + =r ux.em "text-gray-600" + ="(#{@minimum_password_length} characters minimum)" + = f.password_field :password, autocomplete: "new-password", class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" + + =r ux.div "mb-4" + = f.label :password_confirmation, class: "block font-bold mb-2 text-gray-700 text-sm" + = f.password_field :password_confirmation, + autocomplete: "new-password", + class: "appearance-none border leading-tight focus:outline-none px-3 py-2 rounded shadow focus:shadow-outline text-gray-700 w-full" + + =r ux.div "mb-4" + = f.submit "Sign Up", + class: "button bg-blue-500 hover:bg-blue-700 font-bold text-white focus:outline-none py-2 px-4 rounded focus:shadow-outline w-full" + + = render "devise/shared/links" + = render "devise/shared/form_footer" \ No newline at end of file diff --git a/app/views/devise/sessions/new.html.slim b/app/views/devise/sessions/new.html.slim new file mode 100644 index 0000000..e9aa5cb --- /dev/null +++ b/app/views/devise/sessions/new.html.slim @@ -0,0 +1,25 @@ +=r ux.div "w-full max-w-sm" + = form_for(resource, as: resource_name, url: session_path(resource_name), html: {class: "bg-white mb-10 px-8 pt-1 pb-8 rounded shadow-md"}) do |f| + =r ux.h2 "font-hairline mb-6 text-center" + | Log In + = devise_simple_error_messages! + - if flash.present? + =r ux.div "bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-4" + =r ux.p "font-bold" + | Oops! + - flash.each do |name, msg| + = content_tag :p, msg.humanize, id: "flash_#{name}" if msg.is_a?(String) + =r ux.div "mb-4" + = f.label :email, class: "block text-gray-700 text-sm font-bold mb-2" + = f.email_field :email, autofocus: true, autocomplete: "email", class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight foucs:outline-none focus:shadow-outline" + =r ux.div "mb-4" + = f.label :password, class: "block text-gray-700 text-sm font-bold mb-2" + = f.password_field :password, autocomplete: "current-password", class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" + - if devise_mapping.rememberable? + =r ux.div "mb-4" + = f.check_box :remember_me, class: "mr-2 leading-tight" + = f.label :remember_me, class: "align-baseline inline-block text-gray-700 text-sm" + =r ux.div "mb-4" + = f.submit "Log in", class: "button bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline w-full" + = render "devise/shared/links" + = render "devise/shared/form_footer" \ No newline at end of file diff --git a/app/views/devise/shared/_error_messages.html.erb b/app/views/devise/shared/_error_messages.html.erb new file mode 100644 index 0000000..cabfe30 --- /dev/null +++ b/app/views/devise/shared/_error_messages.html.erb @@ -0,0 +1,15 @@ +<% if resource.errors.any? %> +
+

+ <%= I18n.t("errors.messages.not_saved", + count: resource.errors.count, + resource: resource.class.model_name.human.downcase) + %> +

+ +
+<% end %> diff --git a/app/views/devise/shared/_form_footer.html.slim b/app/views/devise/shared/_form_footer.html.slim new file mode 100644 index 0000000..89ff732 --- /dev/null +++ b/app/views/devise/shared/_form_footer.html.slim @@ -0,0 +1,2 @@ +=r ux.text "text-center text-gray-500 text-xs" + = "© 2023 #{ENV['ORGANIZATION']}. All rights reserved.".html_safe \ No newline at end of file diff --git a/app/views/devise/shared/_links.html.slim b/app/views/devise/shared/_links.html.slim new file mode 100644 index 0000000..d356f39 --- /dev/null +++ b/app/views/devise/shared/_links.html.slim @@ -0,0 +1,20 @@ +- if devise_mapping.registerable? && controller_name != 'registrations' + = link_to "Sign up", new_registration_path(resource_name), + class: "block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" +- if controller_name != 'sessions' + = link_to "Log in", new_session_path(resource_name), + class: "block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" +- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' + = link_to "Forgot Password?", new_user_password_path(resource_name), + class: "block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" +br +- if devise_mapping.confirmable? && controller_name != 'confirmations' + = link_to "Didn't receive confirmation info?", new_user_confirmation_path(resource_name), + class: "block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" +- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' + = link_to "Didn't receive unlock info?", new_user_unlock_path(resource_name), + class: "block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" +- if devise_mapping.omniauthable? + - resource_class.omniauth_providers.each do |provider| + = link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), + class: "block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" \ No newline at end of file diff --git a/app/views/devise/unlocks/new.html.slim b/app/views/devise/unlocks/new.html.slim new file mode 100644 index 0000000..5325ad9 --- /dev/null +++ b/app/views/devise/unlocks/new.html.slim @@ -0,0 +1,12 @@ +=r ux.div "w-full max-w-sm" + = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: "bg-white mb-4 px-8 pt-6 pb-8 rounded shadow-md" }) do |f| + =r ux.h2 "font-hairline mb-6 text-center" + | Resend Unlock Info + = devise_error_messages! + =r ux.div "mb-4" + = f.label :email, class: "block font-bold mb-2 text-gray-700 text-sm" + = f.email_field :email, autofocus: true, autocomplete: "email", class: "appearance-none border leading-tight focus:outline-none px-3 py-2 rounded shadow focus:shadow-outline text-gray-700 w-full" + =r ux.div "mb-4" + = f.submit "Resend unlock instructions", class: "button bg-blue-500 hover:bg-blue-700 font-bold text-white focus:outline-none py-2 px-4 rounded focus:shadow-outline w-full" + = render "devise/shared/links" + = render "devise/shared/form_footer" \ No newline at end of file diff --git a/app/views/layouts/devise_layout.html.slim b/app/views/layouts/devise_layout.html.slim new file mode 100644 index 0000000..16c054b --- /dev/null +++ b/app/views/layouts/devise_layout.html.slim @@ -0,0 +1,24 @@ +doctype html +html lang="en" + head + = display_meta_tags site: "Daily Planet", description: "The News Tool", separator: "—".html_safe + meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1" + + = vite_stylesheet_tag 'application.sass', 'data-turbo-track': 'reload' + = stylesheet_link_tag *'https://fonts.googleapis.com/css2?family=Space Grotesk:wght@400;600;700&display=swap', media: 'none', onload: "if(media!='all')media='all'" + = vite_javascript_tag 'application', 'data-turbo-track': 'reload' + = csrf_meta_tags + = canonical_tag + = action_cable_meta_tag + + -if @no_preview + meta name="turbo-cache-control" content="no-preview" + + -if @no_cache + meta name="turbo-cache-control" content="no-cache" + + + body data-controller="notifications" + =r ux.div "min-h-screen flex items-center justify-center bg-zinc-300" + = yield + = render 'partials/notifications' \ No newline at end of file diff --git a/config/ui.yml b/config/ui.yml index 4d9dd4a..f1b39d4 100644 --- a/config/ui.yml +++ b/config/ui.yml @@ -11,7 +11,7 @@ ui: row: tag: div css_class: 'row' - ui: :off + ui: false column: tag: div @@ -50,7 +50,7 @@ ui: text: tag: p css_class: 'text' - ui: :off + ui: false icon: tag: i @@ -165,4 +165,17 @@ ui: div: tag: div + ui: false + + small: + tag: small + ui: false + + em: + tag: em + ui: false + + + br: + tag: br ui: false \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index 0b68088..deb8a8f 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -6,6 +6,7 @@ module.exports = { './app/helpers/**/*.rb', './app/frontend/**/*.js', './app/views/**/*', + './app/views/devise/**/*', './app/components/**/*', ], theme: {