From b40e3f4162d96e76a13fd9731f4aade40ba498c7 Mon Sep 17 00:00:00 2001 From: Jake Hash <1508098+jhash@users.noreply.github.com> Date: Fri, 25 Jul 2025 18:10:23 -0400 Subject: [PATCH] Redesign admin panel with modern UI and improved navigation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add consistent navbar across all pages with user avatar and dropdown - Create shared sidebar component with hierarchical links and ARIA support - Style admin panel to match home page with modern Vercel v0-like design - Add blog posts admin pages with full CRUD functionality - Implement filters for users (name, email) and blog posts (title, content) - Create settings page and move Connected Accounts from home page - Use Tailwind grid layout for proper sidebar/main content structure - Add Stimulus controller for dropdown menu functionality - Remove old admin-specific sidebar in favor of shared component 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../admin/sidebar_component.html.erb | 24 ---- app/components/admin/sidebar_component.rb | 28 ----- app/components/navbar_component.html.erb | 64 ++++++++++ app/components/navbar_component.rb | 21 ++++ app/components/sidebar_component.html.erb | 44 +++++++ app/components/sidebar_component.rb | 73 ++++++++++++ .../admin/blog_posts_controller.rb | 62 ++++++++++ app/controllers/admin/users_controller.rb | 5 +- app/controllers/settings_controller.rb | 12 ++ .../controllers/dropdown_controller.js | 26 ++++ app/views/admin/blog_posts/_form.html.erb | 95 +++++++++++++++ app/views/admin/blog_posts/edit.html.erb | 9 ++ app/views/admin/blog_posts/index.html.erb | 94 +++++++++++++++ app/views/admin/blog_posts/new.html.erb | 9 ++ app/views/admin/blog_posts/show.html.erb | 112 ++++++++++++++++++ app/views/admin/users/index.html.erb | 50 ++++++-- app/views/home/index.html.erb | 4 +- app/views/layouts/admin.html.erb | 34 +++--- app/views/layouts/application.html.erb | 66 ++++------- app/views/settings/index.html.erb | 56 +++++++++ config/routes.rb | 4 + db/schema.rb | 36 +++--- 22 files changed, 787 insertions(+), 141 deletions(-) delete mode 100644 app/components/admin/sidebar_component.html.erb delete mode 100644 app/components/admin/sidebar_component.rb create mode 100644 app/components/navbar_component.html.erb create mode 100644 app/components/navbar_component.rb create mode 100644 app/components/sidebar_component.html.erb create mode 100644 app/components/sidebar_component.rb create mode 100644 app/controllers/admin/blog_posts_controller.rb create mode 100644 app/controllers/settings_controller.rb create mode 100644 app/javascript/controllers/dropdown_controller.js create mode 100644 app/views/admin/blog_posts/_form.html.erb create mode 100644 app/views/admin/blog_posts/edit.html.erb create mode 100644 app/views/admin/blog_posts/index.html.erb create mode 100644 app/views/admin/blog_posts/new.html.erb create mode 100644 app/views/admin/blog_posts/show.html.erb create mode 100644 app/views/settings/index.html.erb diff --git a/app/components/admin/sidebar_component.html.erb b/app/components/admin/sidebar_component.html.erb deleted file mode 100644 index 70e7b44..0000000 --- a/app/components/admin/sidebar_component.html.erb +++ /dev/null @@ -1,24 +0,0 @@ - diff --git a/app/components/admin/sidebar_component.rb b/app/components/admin/sidebar_component.rb deleted file mode 100644 index e0cf19d..0000000 --- a/app/components/admin/sidebar_component.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -class Admin::SidebarComponent < ViewComponent::Base - def initialize(current_path:, current_user:) - @current_path = current_path - @current_user = current_user - end - - private - - attr_reader :current_path, :current_user - - def nav_link_classes(path) - base_classes = "flex items-center px-6 py-3 text-sm font-medium transition-colors hover:bg-gray-100" - active_classes = "bg-gray-100 text-gray-900 border-r-2 border-gray-900" - inactive_classes = "text-gray-600" - - is_active = current_path.start_with?(path) - "#{base_classes} #{is_active ? active_classes : inactive_classes}" - end - - def models - [ - { name: "Users", path: admin_users_path }, - { name: "Roles", path: admin_roles_path } - ] - end -end diff --git a/app/components/navbar_component.html.erb b/app/components/navbar_component.html.erb new file mode 100644 index 0000000..2a7a020 --- /dev/null +++ b/app/components/navbar_component.html.erb @@ -0,0 +1,64 @@ + + + \ No newline at end of file diff --git a/app/components/navbar_component.rb b/app/components/navbar_component.rb new file mode 100644 index 0000000..6a63d31 --- /dev/null +++ b/app/components/navbar_component.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class NavbarComponent < ViewComponent::Base + def initialize(current_user:) + @current_user = current_user + end + + private + + attr_reader :current_user + + def user_initials + return "" unless current_user + names = current_user.name.split + if names.size >= 2 + "#{names.first[0]}#{names.last[0]}".upcase + else + names.first[0..1].upcase + end + end +end diff --git a/app/components/sidebar_component.html.erb b/app/components/sidebar_component.html.erb new file mode 100644 index 0000000..c17e919 --- /dev/null +++ b/app/components/sidebar_component.html.erb @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/app/components/sidebar_component.rb b/app/components/sidebar_component.rb new file mode 100644 index 0000000..467815a --- /dev/null +++ b/app/components/sidebar_component.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +class SidebarComponent < ViewComponent::Base + def initialize(current_user:, current_path:) + @current_user = current_user + @current_path = current_path + end + + private + + attr_reader :current_user, :current_path + + def nav_items + items = [] + + if current_user + items << { name: "Home", path: root_path, icon: "home" } + items << { name: "Blog", path: blog_posts_path, icon: "document-text" } + + if current_user.superadmin? + items << { + name: "Admin", + icon: "cog", + children: [ + { name: "Users", path: admin_users_path }, + { name: "Roles", path: admin_roles_path }, + { name: "Blog Posts", path: admin_blog_posts_path } + ] + } + end + end + + items + end + + def is_active?(path) + return false if path.nil? + current_path == path || current_path.start_with?(path) + end + + def any_child_active?(children) + children.any? { |child| is_active?(child[:path]) } + end + + def item_classes(item) + base = "flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors" + if is_active?(item[:path]) + "#{base} bg-gray-900 text-white" + else + "#{base} text-gray-300 hover:bg-gray-700 hover:text-white" + end + end + + def icon_svg(icon_name) + case icon_name + when "home" + ' + + ' + when "document-text" + ' + + ' + when "cog" + ' + + + ' + else + "" + end + end +end diff --git a/app/controllers/admin/blog_posts_controller.rb b/app/controllers/admin/blog_posts_controller.rb new file mode 100644 index 0000000..b1f9d0d --- /dev/null +++ b/app/controllers/admin/blog_posts_controller.rb @@ -0,0 +1,62 @@ +class Admin::BlogPostsController < ApplicationController + layout "admin" + before_action :require_superadmin + before_action :set_blog_post, only: [ :show, :edit, :update, :destroy ] + + def index + @blog_posts = BlogPost.includes(:user, :tags) + @blog_posts = @blog_posts.where("title LIKE ?", "%#{params[:title]}%") if params[:title].present? + @blog_posts = @blog_posts.where("content LIKE ?", "%#{params[:content]}%") if params[:content].present? + @blog_posts = @blog_posts.order(created_at: :desc).page(params[:page]) + end + + def show + end + + def new + @blog_post = BlogPost.new + end + + def edit + end + + def create + @blog_post = BlogPost.new(blog_post_params) + @blog_post.user = current_user + + if @blog_post.save + redirect_to admin_blog_post_path(@blog_post), notice: "Blog post was successfully created." + else + render :new, status: :unprocessable_entity + end + end + + def update + if @blog_post.update(blog_post_params) + redirect_to admin_blog_post_path(@blog_post), notice: "Blog post was successfully updated." + else + render :edit, status: :unprocessable_entity + end + end + + def destroy + @blog_post.destroy + redirect_to admin_blog_posts_path, notice: "Blog post was successfully destroyed." + end + + private + + def set_blog_post + @blog_post = BlogPost.find_by!(slug: params[:id]) + end + + def blog_post_params + params.require(:blog_post).permit(:title, :content, :status, :excerpt, :featured_image_url, + :published_at, :tag_list, :meta_title, :meta_description, + :og_title, :og_description, :twitter_title, :twitter_description) + end + + def require_superadmin + redirect_to root_path, alert: "Not authorized" unless current_user&.superadmin? + end +end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 7469957..5286459 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -3,7 +3,10 @@ class Admin::UsersController < Admin::BaseController before_action :set_user, only: %i[show edit update destroy] def index - @users = User.includes(:roles).order(created_at: :desc) + @users = User.includes(:roles) + @users = @users.where("name LIKE ?", "%#{params[:name]}%") if params[:name].present? + @users = @users.where("email LIKE ?", "%#{params[:email]}%") if params[:email].present? + @users = @users.order(created_at: :desc) end def show diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb new file mode 100644 index 0000000..c0802d4 --- /dev/null +++ b/app/controllers/settings_controller.rb @@ -0,0 +1,12 @@ +class SettingsController < ApplicationController + before_action :require_login + + def index + end + + private + + def require_login + redirect_to root_path, alert: "You must be logged in to access settings" unless logged_in? + end +end diff --git a/app/javascript/controllers/dropdown_controller.js b/app/javascript/controllers/dropdown_controller.js new file mode 100644 index 0000000..8bc1f73 --- /dev/null +++ b/app/javascript/controllers/dropdown_controller.js @@ -0,0 +1,26 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["menu"] + + toggle(event) { + event.stopPropagation() + const isOpen = this.menuTarget.hasAttribute("data-dropdown-open") + + if (isOpen) { + this.hide() + } else { + this.show() + } + } + + show() { + this.menuTarget.setAttribute("data-dropdown-open", "") + this.element.querySelector("button").setAttribute("aria-expanded", "true") + } + + hide() { + this.menuTarget.removeAttribute("data-dropdown-open") + this.element.querySelector("button").setAttribute("aria-expanded", "false") + } +} \ No newline at end of file diff --git a/app/views/admin/blog_posts/_form.html.erb b/app/views/admin/blog_posts/_form.html.erb new file mode 100644 index 0000000..4ef560e --- /dev/null +++ b/app/views/admin/blog_posts/_form.html.erb @@ -0,0 +1,95 @@ +<%= form_with(model: [:admin, blog_post]) do |form| %> + <% if blog_post.errors.any? %> +
+

+ <%= pluralize(blog_post.errors.count, "error") %> prohibited this blog post from being saved: +

+ +
+ <% end %> + +
+
+
+ <%= form.label :title, class: "block text-sm font-medium text-gray-700" %> + <%= form.text_field :title, + class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %> +
+ +
+ <%= form.label :slug, class: "block text-sm font-medium text-gray-700" %> + <%= form.text_field :slug, + class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm", + placeholder: "leave-blank-to-auto-generate" %> +

Leave blank to auto-generate from title

+
+ +
+ <%= form.label :excerpt, class: "block text-sm font-medium text-gray-700" %> + <%= form.text_area :excerpt, rows: 3, + class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %> +
+ +
+ <%= form.label :content, class: "block text-sm font-medium text-gray-700" %> + <%= form.text_area :content, rows: 15, + class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %> +
+ +
+
+ <%= form.label :status, class: "block text-sm font-medium text-gray-700" %> + <%= form.select :status, options_for_select(BlogPost.statuses.map {|key, value| [key.humanize, key]}, blog_post.status), + {}, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %> +
+ +
+ <%= form.label :published_at, class: "block text-sm font-medium text-gray-700" %> + <%= form.datetime_field :published_at, + class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %> +
+
+ +
+ <%= form.label :tag_list, "Tags", class: "block text-sm font-medium text-gray-700" %> + <%= form.text_field :tag_list, value: blog_post.tag_list, + class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm", + placeholder: "ruby, rails, web development" %> +

Separate tags with commas

+
+ +
+ <%= form.label :featured_image_url, class: "block text-sm font-medium text-gray-700" %> + <%= form.text_field :featured_image_url, + class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %> +
+ +
+

SEO Metadata

+ +
+
+ <%= form.label :meta_title, class: "block text-sm font-medium text-gray-700" %> + <%= form.text_field :meta_title, + class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %> +
+ +
+ <%= form.label :meta_description, class: "block text-sm font-medium text-gray-700" %> + <%= form.text_area :meta_description, rows: 2, + class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm" %> +
+
+
+
+ +
+ <%= link_to "Cancel", admin_blog_posts_path, class: "bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" %> + <%= form.submit class: "bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md transition-colors" %> +
+
+<% end %> \ No newline at end of file diff --git a/app/views/admin/blog_posts/edit.html.erb b/app/views/admin/blog_posts/edit.html.erb new file mode 100644 index 0000000..4215549 --- /dev/null +++ b/app/views/admin/blog_posts/edit.html.erb @@ -0,0 +1,9 @@ +
+
+

Edit Blog Post

+
+ +
+ <%= render "form", blog_post: @blog_post %> +
+
\ No newline at end of file diff --git a/app/views/admin/blog_posts/index.html.erb b/app/views/admin/blog_posts/index.html.erb new file mode 100644 index 0000000..d377c39 --- /dev/null +++ b/app/views/admin/blog_posts/index.html.erb @@ -0,0 +1,94 @@ +<% content_for(:sidebar_filters) do %> +

Filters

+ <%= form_with url: admin_blog_posts_path, method: :get, data: { turbo_frame: "_top" } do |f| %> +
+
+ <%= f.label :title, "Title", class: "block text-xs text-gray-400 mb-1" %> + <%= f.text_field :title, value: params[:title], + class: "w-full px-3 py-1 bg-gray-700 border border-gray-600 rounded-md text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500", + placeholder: "Search by title..." %> +
+ +
+ <%= f.label :content, "Content", class: "block text-xs text-gray-400 mb-1" %> + <%= f.text_field :content, value: params[:content], + class: "w-full px-3 py-1 bg-gray-700 border border-gray-600 rounded-md text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500", + placeholder: "Search in content..." %> +
+ +
+ <%= f.submit "Filter", class: "flex-1 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium py-1 px-3 rounded-md transition-colors" %> + <%= link_to "Clear", admin_blog_posts_path, + class: "flex-1 bg-gray-700 hover:bg-gray-600 text-white text-sm font-medium py-1 px-3 rounded-md transition-colors text-center" %> +
+
+ <% end %> +<% end %> + +
+
+
+

Blog Posts

+ <%= link_to "New Blog Post", new_admin_blog_post_path, + class: "bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md transition-colors" %> +
+
+ +
+ + + + + + + + + + + + + <% @blog_posts.each do |blog_post| %> + + + + + + + + + <% end %> + +
TitleAuthorStatusPublished AtViewsActions
+
<%= blog_post.title %>
+
<%= blog_post.slug %>
+
+ <%= blog_post.user.name %> + + + <%= blog_post.status.capitalize %> + + + <%= blog_post.published_at&.strftime("%B %d, %Y") || "-" %> + + <%= number_with_delimiter(blog_post.views_count) %> + +
+ <%= link_to "View", admin_blog_post_path(blog_post), + class: "text-gray-600 hover:text-gray-900" %> + <%= link_to "Edit", edit_admin_blog_post_path(blog_post), + class: "text-indigo-600 hover:text-indigo-900" %> + <%= button_to "Delete", admin_blog_post_path(blog_post), + method: :delete, + data: { turbo_confirm: "Are you sure?" }, + class: "text-red-600 hover:text-red-900" %> +
+
+
+ + <% if @blog_posts.respond_to?(:total_pages) %> +
+ <%= paginate @blog_posts %> +
+ <% end %> +
\ No newline at end of file diff --git a/app/views/admin/blog_posts/new.html.erb b/app/views/admin/blog_posts/new.html.erb new file mode 100644 index 0000000..0c903d4 --- /dev/null +++ b/app/views/admin/blog_posts/new.html.erb @@ -0,0 +1,9 @@ +
+
+

New Blog Post

+
+ +
+ <%= render "form", blog_post: @blog_post %> +
+
\ No newline at end of file diff --git a/app/views/admin/blog_posts/show.html.erb b/app/views/admin/blog_posts/show.html.erb new file mode 100644 index 0000000..1352f9d --- /dev/null +++ b/app/views/admin/blog_posts/show.html.erb @@ -0,0 +1,112 @@ +
+
+
+

<%= @blog_post.title %>

+
+ <%= link_to "Edit", edit_admin_blog_post_path(@blog_post), + class: "bg-gray-600 hover:bg-gray-700 text-white font-medium py-2 px-4 rounded-md transition-colors" %> + <%= button_to "Delete", admin_blog_post_path(@blog_post), + method: :delete, + data: { turbo_confirm: "Are you sure?" }, + class: "bg-red-600 hover:bg-red-700 text-white font-medium py-2 px-4 rounded-md transition-colors" %> +
+
+
+ +
+
+
+

Author

+

<%= @blog_post.user.name %>

+
+ +
+

Status

+

+ + <%= @blog_post.status.capitalize %> + +

+
+ +
+

Published At

+

+ <%= @blog_post.published_at&.strftime("%B %d, %Y at %I:%M %p") || "Not published" %> +

+
+ +
+

Views

+

<%= number_with_delimiter(@blog_post.views_count) %>

+
+ +
+

Reading Time

+

<%= pluralize(@blog_post.reading_time_minutes, "minute") %>

+
+ +
+

Slug

+

<%= @blog_post.slug %>

+
+
+ + <% if @blog_post.excerpt.present? %> +
+

Excerpt

+

<%= @blog_post.excerpt %>

+
+ <% end %> + + <% if @blog_post.tags.any? %> +
+

Tags

+
+ <% @blog_post.tags.each do |tag| %> + + <%= tag.name %> + + <% end %> +
+
+ <% end %> + + <% if @blog_post.featured_image_url.present? %> +
+

Featured Image

+ Featured image +
+ <% end %> + +
+

Content

+
+ <%= simple_format(@blog_post.content) %> +
+
+ + <% if @blog_post.meta_title.present? || @blog_post.meta_description.present? %> +
+

SEO Metadata

+ +
+ <% if @blog_post.meta_title.present? %> +
+

Meta Title

+

<%= @blog_post.meta_title %>

+
+ <% end %> + + <% if @blog_post.meta_description.present? %> +
+

Meta Description

+

<%= @blog_post.meta_description %>

+
+ <% end %> +
+
+ <% end %> +
+
\ No newline at end of file diff --git a/app/views/admin/users/index.html.erb b/app/views/admin/users/index.html.erb index a24c869..9237340 100644 --- a/app/views/admin/users/index.html.erb +++ b/app/views/admin/users/index.html.erb @@ -1,13 +1,43 @@ -
-
-

Users

-

A list of all users in the system

+<% content_for(:sidebar_filters) do %> +

Filters

+ <%= form_with url: admin_users_path, method: :get, data: { turbo_frame: "_top" } do |f| %> +
+
+ <%= f.label :name, "Name", class: "block text-xs text-gray-400 mb-1" %> + <%= f.text_field :name, value: params[:name], + class: "w-full px-3 py-1 bg-gray-700 border border-gray-600 rounded-md text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500", + placeholder: "Search by name..." %> +
+ +
+ <%= f.label :email, "Email", class: "block text-xs text-gray-400 mb-1" %> + <%= f.text_field :email, value: params[:email], + class: "w-full px-3 py-1 bg-gray-700 border border-gray-600 rounded-md text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500", + placeholder: "Search by email..." %> +
+ +
+ <%= f.submit "Filter", class: "flex-1 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium py-1 px-3 rounded-md transition-colors" %> + <%= link_to "Clear", admin_users_path, + class: "flex-1 bg-gray-700 hover:bg-gray-600 text-white text-sm font-medium py-1 px-3 rounded-md transition-colors text-center" %> +
+
+ <% end %> +<% end %> + +
+
+
+
+

Users

+

A list of all users in the system

+
+ <%= link_to "New User", new_admin_user_path, + class: "bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md transition-colors" %> +
-
- <%= link_to "New User", new_admin_user_path, class: "block rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" %> + +
+ <%= render Admin::ModelTableComponent.new(models: @users, model_class: User) %>
-
- -
- <%= render Admin::ModelTableComponent.new(models: @users, model_class: User) %>
\ No newline at end of file diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb index 074640a..9854861 100644 --- a/app/views/home/index.html.erb +++ b/app/views/home/index.html.erb @@ -1,5 +1,5 @@ -
-
+
+

Welcome to Eight

<% if logged_in? %> diff --git a/app/views/layouts/admin.html.erb b/app/views/layouts/admin.html.erb index 2c26b56..77a349d 100644 --- a/app/views/layouts/admin.html.erb +++ b/app/views/layouts/admin.html.erb @@ -19,23 +19,25 @@ -
- <%= render Admin::SidebarComponent.new(current_path: request.path, current_user: current_user) %> + <%= render NavbarComponent.new(current_user: current_user) %> + +
+ <%= render SidebarComponent.new(current_user: current_user, current_path: request.path) %> -
-
- <% if flash[:notice].present? %> -
- <%= flash[:notice] %> -
- <% end %> - - <% if flash[:alert].present? %> -
- <%= flash[:alert] %> -
- <% end %> - +
+ <% if flash[:notice].present? %> +
+ <%= flash[:notice] %> +
+ <% end %> + + <% if flash[:alert].present? %> +
+ <%= flash[:alert] %> +
+ <% end %> + +
<%= yield %>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index cae0941..f4454d9 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -22,49 +22,31 @@ <%= javascript_importmap_tags %> - - + + <%= render NavbarComponent.new(current_user: current_user) %> - <% if notice.present? %> - - <% end %> - - <% if alert.present? %> - - <% end %> +
+ <% if logged_in? %> + <%= render SidebarComponent.new(current_user: current_user, current_path: request.path) %> + <% end %> + +
+ <% if notice.present? %> + + <% end %> + + <% if alert.present? %> + + <% end %> -
- <%= yield %> -
+
+ <%= yield %> +
+
+
diff --git a/app/views/settings/index.html.erb b/app/views/settings/index.html.erb new file mode 100644 index 0000000..ee504ee --- /dev/null +++ b/app/views/settings/index.html.erb @@ -0,0 +1,56 @@ +
+

Settings

+ +
+

Profile Information

+
+
+ Name: +

<%= current_user.name %>

+
+
+ Email: +

<%= current_user.email %>

+
+
+
+ +
+

Connected Accounts

+
+ <% ['google_oauth2', 'github'].each do |provider| %> +
+
+ <% if provider == 'google_oauth2' %> + + + + + + + <% else %> + + + + <% end %> + + <%= provider == 'google_oauth2' ? 'Google' : 'GitHub' %> + +
+ <% if current_user.provider_connected?(provider) %> + + + + + Connected + + <% else %> + <%= link_to "Connect", "/auth/#{provider}", method: :post, + class: "bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md text-sm font-medium transition-colors", + data: { turbo: false } %> + <% end %> +
+ <% end %> +
+
+
\ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index f75ec92..a9040d2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -14,6 +14,9 @@ get "/auth/failure", to: "sessions#failure" delete "/logout", to: "sessions#destroy" + # Settings + get "/settings", to: "settings#index" + # Blog routes resources :blog_posts, path: "blog", only: [ :index, :show ] do collection do @@ -25,6 +28,7 @@ namespace :admin do resources :users resources :roles + resources :blog_posts root "users#index" end diff --git a/db/schema.rb b/db/schema.rb index 9d544f5..7fba5e4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -37,11 +37,11 @@ t.integer "views_count", default: 0 t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.index ["published_at"], name: "index_blog_posts_on_published_at" - t.index ["slug"], name: "index_blog_posts_on_slug", unique: true - t.index ["status", "published_at"], name: "index_blog_posts_on_status_and_published_at" - t.index ["status"], name: "index_blog_posts_on_status" - t.index ["user_id"], name: "index_blog_posts_on_user_id" + t.index [ "published_at" ], name: "index_blog_posts_on_published_at" + t.index [ "slug" ], name: "index_blog_posts_on_slug", unique: true + t.index [ "status", "published_at" ], name: "index_blog_posts_on_status_and_published_at" + t.index [ "status" ], name: "index_blog_posts_on_status" + t.index [ "user_id" ], name: "index_blog_posts_on_user_id" end create_table "identities", force: :cascade do |t| @@ -50,15 +50,15 @@ t.string "uid" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.index ["provider", "uid"], name: "index_identities_on_provider_and_uid", unique: true - t.index ["user_id"], name: "index_identities_on_user_id" + t.index [ "provider", "uid" ], name: "index_identities_on_provider_and_uid", unique: true + t.index [ "user_id" ], name: "index_identities_on_user_id" end create_table "roles", force: :cascade do |t| t.string "name", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.index ["name"], name: "index_roles_on_name", unique: true + t.index [ "name" ], name: "index_roles_on_name", unique: true end create_table "taggings", force: :cascade do |t| @@ -67,10 +67,10 @@ t.integer "taggable_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.index ["tag_id", "taggable_id", "taggable_type"], name: "index_taggings_uniqueness", unique: true - t.index ["tag_id"], name: "index_taggings_on_tag_id" - t.index ["taggable_type", "taggable_id"], name: "index_taggings_on_taggable" - t.index ["taggable_type", "taggable_id"], name: "index_taggings_on_taggable_type_and_taggable_id" + t.index [ "tag_id", "taggable_id", "taggable_type" ], name: "index_taggings_uniqueness", unique: true + t.index [ "tag_id" ], name: "index_taggings_on_tag_id" + t.index [ "taggable_type", "taggable_id" ], name: "index_taggings_on_taggable" + t.index [ "taggable_type", "taggable_id" ], name: "index_taggings_on_taggable_type_and_taggable_id" end create_table "tags", force: :cascade do |t| @@ -78,8 +78,8 @@ t.string "slug", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.index ["name"], name: "index_tags_on_name", unique: true - t.index ["slug"], name: "index_tags_on_slug", unique: true + t.index [ "name" ], name: "index_tags_on_name", unique: true + t.index [ "slug" ], name: "index_tags_on_slug", unique: true end create_table "user_roles", force: :cascade do |t| @@ -87,9 +87,9 @@ t.integer "role_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.index ["role_id"], name: "index_user_roles_on_role_id" - t.index ["user_id", "role_id"], name: "index_user_roles_on_user_id_and_role_id", unique: true - t.index ["user_id"], name: "index_user_roles_on_user_id" + t.index [ "role_id" ], name: "index_user_roles_on_role_id" + t.index [ "user_id", "role_id" ], name: "index_user_roles_on_user_id_and_role_id", unique: true + t.index [ "user_id" ], name: "index_user_roles_on_user_id" end create_table "users", force: :cascade do |t| @@ -97,7 +97,7 @@ t.string "name" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.index ["email"], name: "index_users_on_email", unique: true + t.index [ "email" ], name: "index_users_on_email", unique: true end add_foreign_key "blog_posts", "users"