Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 0 additions & 24 deletions app/components/admin/sidebar_component.html.erb

This file was deleted.

28 changes: 0 additions & 28 deletions app/components/admin/sidebar_component.rb

This file was deleted.

64 changes: 64 additions & 0 deletions app/components/navbar_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<nav class="bg-gray-900 text-white shadow-lg">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex items-center">
<%= link_to "Eight", root_path, class: "text-xl font-bold hover:text-gray-300 transition-colors" %>
</div>

<div class="flex items-center space-x-4">
<% if current_user %>
<div class="relative" data-controller="dropdown">
<button type="button"
class="flex items-center justify-center w-10 h-10 rounded-full bg-gray-700 hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-900 focus:ring-white transition-colors"
data-action="click->dropdown#toggle click@window->dropdown#hide"
aria-expanded="false"
aria-haspopup="true">
<span class="text-sm font-medium"><%= user_initials %></span>
</button>

<div class="dropdown-menu absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5"
data-dropdown-target="menu"
role="menu"
aria-orientation="vertical"
aria-labelledby="user-menu">
<div class="px-4 py-2 border-b border-gray-200">
<p class="text-sm font-medium text-gray-900"><%= current_user.name %></p>
<p class="text-xs text-gray-500"><%= current_user.email %></p>
</div>

<%= link_to "Settings", settings_path,
class: "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100",
role: "menuitem" %>

<div class="border-t border-gray-200">
<%= button_to "Sign Out", logout_path, method: :delete,
class: "block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100",
data: { turbo: false },
role: "menuitem" %>
</div>
</div>
</div>
<% else %>
<div class="flex space-x-2">
<%= link_to "Sign in with Google", "/auth/google_oauth2", method: :post,
class: "bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded text-sm transition-colors",
data: { turbo: false } %>
<%= link_to "Sign in with GitHub", "/auth/github", method: :post,
class: "bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded text-sm transition-colors",
data: { turbo: false } %>
</div>
<% end %>
</div>
</div>
</div>
</nav>

<style>
.dropdown-menu {
display: none;
}

.dropdown-menu[data-dropdown-open] {
display: block;
}
</style>
21 changes: 21 additions & 0 deletions app/components/navbar_component.rb
Original file line number Diff line number Diff line change
@@ -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
44 changes: 44 additions & 0 deletions app/components/sidebar_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<aside class="bg-gray-800 text-white w-64 min-h-screen flex flex-col" role="navigation" aria-label="Main navigation">
<div class="px-4 py-6">
<nav class="space-y-1" role="tree">
<% nav_items.each_with_index do |item, index| %>
<% if item[:children] %>
<details class="group" <%= "open" if any_child_active?(item[:children]) %>>
<summary class="flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors cursor-pointer hover:bg-gray-700 list-none"
role="treeitem"
aria-expanded="<%= any_child_active?(item[:children]) ? 'true' : 'false' %>"
tabindex="0">
<%= raw icon_svg(item[:icon]) %>
<span class="flex-1"><%= item[:name] %></span>
<svg class="ml-3 h-5 w-5 transform transition-transform group-open:rotate-90" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
</svg>
</summary>
<div class="mt-1 ml-8" role="group">
<% item[:children].each do |child| %>
<%= link_to child[:name], child[:path],
class: item_classes(child),
role: "treeitem",
"aria-selected": is_active?(child[:path]) ? "true" : "false" %>
<% end %>
</div>
</details>
<% else %>
<%= link_to item[:path],
class: item_classes(item),
role: "treeitem",
"aria-selected": is_active?(item[:path]) ? "true" : "false" do %>
<%= raw icon_svg(item[:icon]) %>
<%= item[:name] %>
<% end %>
<% end %>
<% end %>
</nav>
</div>

<% if content_for?(:sidebar_filters) %>
<div class="px-4 py-6 border-t border-gray-700">
<%= yield :sidebar_filters %>
</div>
<% end %>
</aside>
73 changes: 73 additions & 0 deletions app/components/sidebar_component.rb
Original file line number Diff line number Diff line change
@@ -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"
'<svg class="mr-3 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>'
when "document-text"
'<svg class="mr-3 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>'
when "cog"
'<svg class="mr-3 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>'
else
""
end
end
end
62 changes: 62 additions & 0 deletions app/controllers/admin/blog_posts_controller.rb
Original file line number Diff line number Diff line change
@@ -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
5 changes: 4 additions & 1 deletion app/controllers/admin/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions app/controllers/settings_controller.rb
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions app/javascript/controllers/dropdown_controller.js
Original file line number Diff line number Diff line change
@@ -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")
}
}
Loading
Loading