Compare commits

...

4 Commits

Author SHA1 Message Date
81843f0c08 chore: remove unused jQuery and Bootstrap dependencies
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
- Deleted the `add_jquery.js` file as jQuery is no longer needed.
- Removed references to Bootstrap from the Gemfile and package.json.
- Updated the application layout to reflect the removal of Bootstrap styles.
- Adjusted the paginator HTML to use a class that aligns with the new styling.

These changes streamline the asset pipeline by eliminating unused libraries,
which can improve load times and reduce potential security vulnerabilities.
The application now relies on alternative styling and JavaScript solutions.
2025-01-17 17:08:07 +08:00
0335ef4ed6 refactor: replace list items with buttons in pagination
- Changed pagination elements from <li> to <button> for better accessibility.
- Updated the paginator structure to use a <div> instead of <ul>.
- Ensured that all pagination links are now buttons, improving the user experience.

These changes enhance the semantic structure of the pagination, making it more intuitive and accessible for users, especially those using assistive technologies.
2025-01-17 15:22:13 +08:00
8c598ce3dc chore: remove flowbite dependency
- Remove Flowbite import from application.js
- Update tailwind.config.js to exclude Flowbite
- Delete package-lock.json to reflect the removal of Flowbite

This commit cleans up the project by removing the Flowbite library, which was previously included but is no longer needed. This helps streamline the codebase and may improve performance.
2025-01-17 15:06:51 +08:00
87e0c2eec6 feat: add theme switching and toast notifications
- Implement theme switching functionality with a new ThemeController
- Add ToastController for displaying notifications
- Update various views for improved layout and styling
- Introduce animations for toast notifications

These changes enhance the user experience by allowing users to switch between light and dark themes and receive feedback through toast notifications. The UI has been improved for better accessibility and aesthetics.
2025-01-17 15:02:25 +08:00
39 changed files with 337 additions and 2952 deletions

View File

@ -70,8 +70,6 @@ group :test do
gem "rails-controller-testing"
end
gem "cssbundling-rails", "~> 1.4"
gem "jsbundling-rails", "~> 1.3"
group :production do

View File

@ -102,8 +102,6 @@ GEM
concurrent-ruby (1.3.4)
connection_pool (2.4.1)
crass (1.0.6)
cssbundling-rails (1.4.1)
railties (>= 6.0.0)
date (3.4.1)
debug (1.10.0)
irb (~> 1.10)
@ -441,7 +439,6 @@ DEPENDENCIES
bootsnap
brakeman
capybara
cssbundling-rails (~> 1.4)
debug
faker (~> 3.5)
guard (~> 2.19)

View File

@ -1,3 +0,0 @@
@import 'bootstrap/dist/css/bootstrap';
@import 'bootstrap-icons/font/bootstrap-icons';
//@import './custom';

View File

@ -18,7 +18,7 @@
}
h1 {
@apply text-[3em] tracking-[-2px] mb-[30px] text-center;
@apply text-[3em] tracking-[-2px] mb-8 mt-8 text-center;
}
h2 {

View File

@ -1,201 +0,0 @@
/* color */
$light-gray: #777;
$gray-lighter: #D3D3D3;
/* universal */
body {
padding-top: 60px;
}
section {
overflow: auto;
}
textarea {
resize: vertical;
}
.center {
text-align: center;
h1 {
margin-bottom: 10px;
}
}
/* typography */
h1, h2, h3, h4, h5, h6 {
line-height: 1;
}
h1 {
font-size: 3em;
letter-spacing: -2px;
margin-bottom: 30px;
text-align: center;
}
h2 {
font-size: 1.2em;
letter-spacing: -1px;
margin-bottom: 30px;
text-align: center;
font-weight: normal;
color: $light-gray;
}
p {
font-size: 1.1em;
line-height: 1.7em;
}
/* header */
#logo {
float: left;
margin-right: 10px;
font-size: 1.7em;
color: #fff;
text-transform: uppercase;
letter-spacing: -1px;
padding-top: 9px;
font-weight: bold;
&:hover {
color: $light-gray;
text-decoration: none;
}
}
//img {
// display: none;
//}
/* footer */
footer {
margin-top: 45px;
padding-top: 5px;
border-top: 1px solid #eaeaea;
color: $light-gray;
a {
color: #555;
&hover {
color: #222;
}
}
small {
float: left;
}
ul {
float: right;
list-style: none;
li {
float: left;
margin-left: 15px;
}
}
}
/* mixins, variables, etc. */
$gray-medium-light: #eaeaea;
@mixin box_sizing {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
/* miscellaneous */
.debug_dump {
clear: both;
float: left;
width: 100%;
margin-top: 45px;
@include box-sizing;
}
/* sidebar */
aside {
section.user_info {
margin-top: 20px;
}
section {
padding: 10px 0;
margin-top: 20px;
&:first-child {
border: 0;
padding-top: 0;
}
span{
display: block;
margin-bottom: 3px;
line-height: 1;
}
h1 {
font-size: 1.4em;
text-align: left;
letter-spacing: -1px;
margin-bottom: 3px;
margin-top: 0;
}
}
}
.gravatar {
float: left;
margin-right: 10px;
}
.gravatar_edit {
margin-top: 15px;
}
/* forms */
input, textarea, select, .uneditable-input {
border: 1px solid #bbb;
width: 100%;
margin-bottom: 15px;
@include box_sizing;
}
input {
height: auto !important;
}
/* forms */
#error_explanation {
color: red;
ul {
color: red;
margin: 0 0 30px 0;
}
}
.field_with_errors {
//@extend .has-error;
.form-control {
//color: $state-danger-text;
}
}
.checkbox {
margin-top: -10px;
margin-bottom: 10px;
span {
margin-left: 20px;
font-weight: normal;
}
}
#session_remember_me {
width: auto;
margin-left: 0;
}
/* Users index */
.users {
list-style: none;
margin: 0;
li {
overflow: auto;
padding: 10px 0;
border-bottom: 1px solid $gray-lighter;
}
}

View File

@ -1,3 +0,0 @@
import jquery from 'jquery'
window.jQuery = jquery
window.$ = jquery

View File

@ -1,12 +1,4 @@
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
// import './add_jquery'
// import "jquery/dist/jquery"
// import "bootstrap/dist/js/bootstrap"
import "flowbite/dist/flowbite.turbo"
import "./controllers"

View File

@ -4,3 +4,10 @@
// eagerLoadControllersFrom("controllers", application)
import { application } from "./application"
import ThemeController from "./theme_controller"
import ToastController from "./toast_controller"
import HelloController from "./hello_controller"
application.register("hello", HelloController)
application.register("theme", ThemeController)
application.register("toast", ToastController)

View File

@ -0,0 +1,24 @@
import { Controller } from "@hotwired/stimulus"
// Connects to data-controller="theme"
export default class extends Controller {
static targets = ["toggle"]
connect() {
this.initTheme()
}
initTheme() {
const savedTheme = localStorage.getItem('theme')
if(savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme)
this.toggleTarget.checked = savedTheme === 'dark'
}
}
toggle(event) {
const newTheme = event.target.checked ? 'dark' : 'light'
document.documentElement.setAttribute('data-theme', newTheme)
localStorage.setItem('theme', newTheme)
}
}

View File

@ -0,0 +1,14 @@
import { Controller } from "@hotwired/stimulus"
// Connects to data-controller="toast"
export default class extends Controller {
connect() {
// 3秒后自动隐藏
setTimeout(() => {
this.element.classList.add('animate-fade-out')
setTimeout(() => {
this.element.remove()
}, 500)
}, 3000)
}
}

View File

@ -0,0 +1,22 @@
// 获取切换按钮
const themeToggle = document.querySelector('.theme-controller');
// 监听变化
themeToggle.addEventListener('change', (e) => {
// 切换 HTML data-theme 属性
if(e.target.checked) {
document.documentElement.setAttribute('data-theme', 'dark');
} else {
document.documentElement.setAttribute('data-theme', 'light');
}
// 保存主题设置到 localStorage
localStorage.setItem('theme', e.target.checked ? 'dark' : 'light');
});
// 页面加载时检查之前的主题设置
const savedTheme = localStorage.getItem('theme');
if(savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme);
themeToggle.checked = savedTheme === 'dark';
}

View File

@ -1,3 +1,3 @@
<li>
<button class="join-item btn">
<%= link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote %>
</li>
</button>

View File

@ -1,3 +1,3 @@
<li class='disabled'>
<button class="join-item btn btn-disabled">
<%= content_tag :a, raw(t 'views.pagination.truncate') %>
</li>
</button>

View File

@ -1,3 +1,3 @@
<li>
<button class="join-item btn">
<%= link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, { remote: remote } %>
</li>
</button>

View File

@ -1,3 +1,3 @@
<li>
<button class="join-item btn">
<%= link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote %>
</li>
</button>

View File

@ -1,9 +1,9 @@
<% if page.current? %>
<li class='active'>
<button class='join-item btn btn-active'>
<%= content_tag :a, page, data: { remote: remote }, rel: page.rel %>
</li>
</button>
<% else %>
<li>
<button class="join-item btn">
<%= link_to page, url, remote: remote, rel: page.rel %>
</li>
</button>
<% end %>

View File

@ -1,5 +1,5 @@
<%= paginator.render do -%>
<ul class="pagination">
<div class="join pagination">
<%= first_page_tag unless current_page.first? %>
<%= prev_page_tag unless current_page.first? %>
<% each_page do |page| -%>
@ -11,5 +11,5 @@
<% end -%>
<%= next_page_tag unless current_page.last? %>
<%= last_page_tag unless current_page.last? %>
</ul>
</div>
<% end -%>

View File

@ -1,3 +1,3 @@
<li>
<button class="join-item btn">
<%= link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote %>
</li>
</button>

View File

@ -1,4 +1,4 @@
<footer class="footer footer-center bg-base-200 text-base-content rounded p-10">
<footer class="footer footer-center bg-base-200 text-base-content rounded p-10 mt-8">
<nav class="grid grid-flow-col gap-4">
<%= link_to "About", about_url, class: "link link-hover" %>
<%= link_to "Contact", contact_url, class: "link link-hover" %>

View File

@ -1,6 +1,13 @@
<div class="navbar bg-base-100 glass">
<!-- 固定在顶部容器 -->
<div class="fixed top-0 left-0 right-0 z-50">
<!-- 响应式内边距 -->
<!--<div class="container mx-auto px-3 sm:px-6 lg:px-1 py-4">-->
<!-- 顶部菜单栏 -->
<div class="navbar
backdrop-filter backdrop-blur-lg bg-opacity-30 border-b border-gray-200 border-transparent
shadow-md min-h-0 h-12 px-8 lg:px-12">
<div class="navbar-start">
<%= link_to "sample app", root_url, id: "logo", class: "btn btn-ghost text-xl" %>
<%= link_to "Today AI Weather", root_url, id: "logo", class: "btn btn-ghost text-xl" %>
</div>
<div class="navbar-end">
<div class="hidden lg:flex">
@ -9,18 +16,20 @@
<li><%= link_to "Help", help_url %></li>
<% if logged_in? %>
<li><%= link_to "Users", users_path %></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<li>
<details>
<summary>
Account <b class="caret"></b>
</a>
<ul class="dropdown-menu">
</summary>
<ul class="bg-base-100 rounded-t-none p-2">
<li><%= link_to "Profile", current_user %></li>
<li><%= link_to "Settings", edit_user_path(current_user) %></li>
<li class="divider"></li>
<div class="divider"></div>
<li>
<%= link_to "Log out", logout_path, data: { turbo_method: :delete } %>
</li>
</ul>
</details>
</li>
<% else %>
<li><%= link_to "Log in", login_path %></li>
@ -67,27 +76,11 @@
<% end %>
</ul>
</details>
<label class="swap swap-rotate">
<!-- this hidden checkbox controls the state -->
<input type="checkbox" class="theme-controller" value="light" />
<!-- sun icon -->
<svg
class="swap-off h-10 w-10 fill-current"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<path
d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z" />
</svg>
<!-- moon icon -->
<svg
class="swap-on h-10 w-10 fill-current"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<path
d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z" />
</svg>
</label>
<%= render "layouts/theme_swap" %>
</div>
</div>
<!--</div>-->
</div>
<!-- 添加一个占位 div防止内容被覆盖 -->
<div aria-hidden="true" class="border-none h-12"></div>

View File

@ -0,0 +1,37 @@
<!-- 放在布局文件的 body 标签末尾 -->
<div class="toast toast-end">
<% flash.each do |message_type, message| %>
<% alert_class = case message_type
when "success" then "alert alert-success"
when "danger", "error" then "alert alert-error"
when "warning" then "alert alert-warning"
when "info" then "alert alert-info"
else "alert"
end
%>
<div data-controller="toast" class="<%= alert_class %> animate-slide-in-right">
<div>
<% case message_type %>
<% when "success" %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<% when "error", "danger" %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<% when "warning" %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<% else %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<% end %>
<span><%= message %></span>
</div>
</div>
<% end %>
</div>

View File

@ -0,0 +1,29 @@
<div data-controller="theme">
<label class="swap swap-rotate">
<!-- this hidden checkbox controls the state -->
<input
type="checkbox"
class="theme-controller"
data-theme-target="toggle"
data-action="change->theme#toggle"
/>
<!-- sun icon -->
<svg
class="swap-off h-6 w-6 fill-current"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<path
d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z" />
</svg>
<!-- moon icon -->
<svg
class="swap-on h-6 w-6 fill-current"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<path
d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z" />
</svg>
</label>
</div>

View File

@ -5,17 +5,14 @@
<title><%= full_title(yield(:title)) %></title>
<%= render 'layouts/rails_default' %>
<%= render 'layouts/shim' %>
<%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
<%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="">
<% flash.each do |message_type, message| %>
<%= content_tag(:div, message, class: "alert alert-#{message_type}") %>
<!-- <div class="alert alert-<%#= message_type %>"><%#= message %></div>-->
<% end %>
<%= render 'layouts/message' %>
<%= yield %>
<%= render 'layouts/footer' %>
<%#= debug(params) if Rails.env.development? %>

View File

@ -1,24 +1,45 @@
<% provide(:title, "Log in") %>
<h1>Log in</h1>
<div class ="row">
<div class="col-md-6 col-md-offset-3">
<%= form_with(url: login_path, scope: :session, local: true) do |f| %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<div class ="container mx-auto px-4">
<h1 class="text-3xl font-bold text-center my-8">Log in</h1>
<%= f.label :password %>
<%= link_to "(forgot password)", new_password_reset_path %>
<%= f.password_field :password, class: 'form-control' %>
<div class="max-w-md mx-auto">
<%= form_with(url: login_path, scope: :session, local: true, class: "space-y-4") do |f| %>
<div class="form-control">
<%= f.label :email, class: "label" do %>
<span class="label-text">Email</span>
<% end %>
<%= f.email_field :email, class: "input input-bordered w-full" %>
</div>
<%= f.label :remember_me, class: "checkbox inline" do %>
<%= f.check_box :remember_me %>
<span>Remember me on this computer</span>
<div class="form-control">
<div class="flex justify-between items-center">
<%= f.label :password, class: "label" do %>
<span class="label-text">Password</span>
<% end %>
<%= link_to "(forgot password)", new_password_reset_path,
class: "text-sm text-primary hover:text-primary-focus" %>
</div>
<%= f.password_field :password, class: "input input-bordered w-full" %>
</div>
<div class="form-control">
<label class="label cursor-pointer">
<%= f.check_box :remember_me, class: "checkbox checkbox-primary" %>
<span class="label-text ml-2">Remember me on this computer</span>
</label>
</div>
<div class="form-control mt-6">
<%= f.submit "Log in", class: "btn btn-primary w-full" %>
</div>
<% end %>
<%= f.submit "Log in", class: "btn btn-primary" %>
<% end %>
<p>New user? <%= link_to "Sign up now!", signup_path %></p>
<div class="text-center mt-6">
<p>New user?
<%= link_to "Sign up now!", signup_path,
class: "text-primary hover:text-primary-focus" %>
</p>
</div>
</div>
</div>

View File

@ -2,19 +2,14 @@
<div class="hero bg-base-200 min-h-screen">
<div class="hero-content text-center">
<div class="max-w-md">
<h1 class="text-5xl font-bold">Welcome to the Sample App</h1>
<h1 class="text-5xl font-bold">Welcome to the Today Ai Weather</h1>
<p class="py-6">
This is the home page for the
<a href="https://www.railstutorial.org">Ruby on Rails Tutorial</a>
Sample application.
Today Ai Weather application.
</p>
<%= link_to "Sing up now!", signup_path, class:"btn btn-lg btn-primary" %>
<%= link_to image_tag("rails.svg", alt:"Rails logo", width: "200"),
"https://rubyonrails.org/" %>
</div>
</div>
</div>
<%#= link_to image_tag("kitten.jpg", alt:"Kitten", width:"200") %>

View File

@ -31,6 +31,6 @@
</div>
<div class="form-control mt-6">
<%= f.submit yield(:button_text), class: "btn btn-primary" %>
<%= f.submit yield(:button_text), class: "btn btn-primary mb-6" %>
</div>
<% end %>

View File

@ -1,11 +1,24 @@
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
<div class="card card-compact bg-base-100 shadow-lg hover:shadow-xl transition-shadow">
<div class="card-body flex-row items-center justify-between">
<!-- 用户基本信息 -->
<div class="flex items-center gap-4">
<%= gravatar_for user, size: 50, class: "rounded-full" %>
<%= link_to user.name, user,
class: "link link-hover text-lg font-medium" %>
</div>
<!-- 管理员操作按钮 -->
<% if current_user.admin? && !current_user?(user) %>
| <%= link_to "delete", user,
<%= link_to user,
data: {
turbo_method: :delete,
confirm: "You sure?"
} %>
turbo_confirm: "Are you sure you want to delete this user?"
},
class: "btn btn-ghost btn-sm text-error hover:bg-error hover:text-white" do %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
<% end %>
</li>
<% end %>
</div>
</div>

View File

@ -1,13 +1,34 @@
<% provide(:title, "Edit user") %>
<% provide(:button_text, 'Save changes') %>
<h1>Update your profile</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="container mx-auto px-4 py-8">
<div class="max-w-md mx-auto">
<!-- 标题 -->
<h1 class="text-2xl font-bold text-center mb-8">Update your profile</h1>
<!-- 表单区域 -->
<div class="card bg-base-100 shadow-lg">
<div class="card-body">
<%= render 'form' %>
<div class="gravatar_edit">
<%= gravatar_for @user %>
<a href="https://gravatar.com/emails" target="_blank">change</a>
</div>
</div>
<!-- 头像编辑区 -->
<div class="card bg-base-100 shadow-lg mb-8">
<div class="card-body items-center text-center">
<%= gravatar_for @user, size: 100, class: "rounded-full ring ring-primary ring-offset-2" %>
<%= link_to "https://gravatar.com/emails",
target: "_blank",
rel: "noopener noreferrer",
class: "btn btn-outline btn-sm gap-2 mt-4" do %>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
</svg>
Change Avatar
<% end %>
</div>
</div>
</div>
</div>

View File

@ -1,17 +1,18 @@
<% provide(:title, 'All users') %>
<h1>All users</h1>
<div class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold text-center mb-8">All Users</h1>
<%= paginate @users %>
<ul class="users">
<!-- 用户列表 -->
<div class="grid gap-4 max-w-3xl mx-auto">
<%= render @users %>
<%# auto loop by rails, like after %>
<%# @users.each do |user| %>
<!-- <li>-->
<%#= gravatar_for user, size: 50 %>
<%#= link_to user.name, user %>
<!-- </li>-->
<%# end %>
</ul>
</div>
<!-- 分页 -->
<div class="flex justify-center mb-6 mt-6">
<div class="join">
<%= paginate @users %>
</div>
</div>
</div>

View File

@ -1,12 +1,25 @@
<% provide(:title, @user.name) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<h1>
<%= gravatar_for @user %>
<div class="container mx-auto px-4 py-8">
<aside class="max-w-2xl mx-auto">
<section class="card bg-base-100 shadow-xl">
<div class="card-body">
<!-- 头像和用户名布局 -->
<h1 class="flex flex-col sm:flex-row items-center gap-6">
<!-- 头像添加装饰效果 -->
<div class="relative">
<%= gravatar_for @user, size: 120, class: "rounded-full ring-2 ring-primary ring-offset-2 hover:ring-4 transition-all duration-300" %>
<div class="absolute inset-0 rounded-full bg-primary/10 animate-pulse"></div>
</div>
<!-- 用户名样式 -->
<div class="text-center sm:text-left">
<span class="text-2xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
<%= @user.name %>
</span>
</div>
</h1>
</div>
</section>
</aside>
</div>

View File

@ -5,4 +5,3 @@ pin "application"
# pin "@hotwired/stimulus", to: "stimulus.min.js"
# pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
pin_all_from "app/javascript/controllers", under: "controllers"
# pin "bootstrap", to: "bootstrap.min.js"

View File

@ -5,7 +5,7 @@ Rails.application.config.assets.version = "1.0"
# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
Rails.application.config.assets.paths << Rails.root.join("node_modules/bootstrap/dist/js")
Rails.application.config.assets.paths << Rails.root.join("node_modules/bootstrap-icons/font")
Rails.application.config.assets.paths << Rails.root.join("node_modules/bootstrap/dist/js")
Rails.application.config.assets.precompile << "bootstrap.min.js"
# Rails.application.config.assets.paths << Rails.root.join("node_modules/bootstrap/dist/js")
# Rails.application.config.assets.paths << Rails.root.join("node_modules/bootstrap-icons/font")
# Rails.application.config.assets.paths << Rails.root.join("node_modules/bootstrap/dist/js")
# Rails.application.config.assets.precompile << "bootstrap.min.js"

View File

@ -6,19 +6,41 @@ module.exports = {
'./app/helpers/**/*.rb',
'./app/javascript/**/*.js',
'./app/views/**/*.{erb,haml,html,slim}',
'./node_modules/flowbite/**/*.js'
],
theme: {
extend: {
fontFamily: {
sans: ['Inter var', ...defaultTheme.fontFamily.sans],
},
keyframes: {
'slide-in-right': {
'0%': {
transform: 'translateX(100%)',
opacity: '0'
},
'100%': {
transform: 'translateX(0)',
opacity: '1'
},
},
'fade-out': {
'0%': {
opacity: '1'
},
'100%': {
opacity: '0'
},
}
},
animation: {
'slide-in-right': 'slide-in-right 0.5s ease-out',
'fade-out': 'fade-out 0.5s ease-out'
},
},
},
plugins: [
require('@tailwindcss/typography'),
require('daisyui'),
// require('flowbite/plugin')
// require('@tailwindcss/forms'),
// require('@tailwindcss/typography'),
// require('@tailwindcss/container-queries'),

2479
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,9 +6,6 @@
"@hotwired/turbo-rails": "^8.0.12",
"@popperjs/core": "^2.11.8",
"autoprefixer": "^10.4.20",
"bootstrap": "3",
"bootstrap-icons": "^1.11.3",
"flowbite": "^2.5.2",
"jquery": "^3.7.1",
"nodemon": "^3.1.9",
"postcss": "^8.4.49",
@ -16,7 +13,6 @@
"sass": "^1.83.0"
},
"scripts": {
"build:css:compile": "sass ./app/assets/stylesheets/application.bootstrap.scss:./app/assets/builds/application.css --no-source-map --load-path=node_modules",
"build:css:prefix": "postcss ./app/assets/builds/application.css --use=autoprefixer --output=./app/assets/builds/application.css",
"build:css": "yarn build:css:compile && yarn build:css:prefix",
"watch:css": "nodemon --watch ./app/assets/stylesheets/ --ext scss --exec \"yarn build:css\"",

View File

@ -7,7 +7,7 @@ class SiteLayoutTest < ActionDispatch::IntegrationTest
test "layout links" do
get root_path
assert_template "static_pages/home"
assert_select "a[href=?]", root_url, count: 2
assert_select "a[href=?]", root_url, count: 3
assert_select "a[href=?]", about_url
assert_select "a[href=?]", help_url
assert_select "a[href=?]", contact_url

View File

@ -10,7 +10,7 @@ class UsersIndexTest < ActionDispatch::IntegrationTest
log_in_as(@admin)
get users_path
assert_template "users/index"
assert_select "ul.pagination"
assert_select "div.pagination"
first_page_of_users = User.where(activated: true).page(1)
# first_page_of_users.first.toggle!(:activated)
@ -18,7 +18,7 @@ class UsersIndexTest < ActionDispatch::IntegrationTest
assert user.activated?
assert_select "a[href=?]", user_path(user), text: user.name
unless user == @admin
assert_select "a[href=?]", user_path(user), text: "delete"
assert_select "a[href=?][data-turbo-method='delete']", user_path(user)
end
end

122
yarn.lock
View File

@ -255,7 +255,7 @@
"@parcel/watcher-win32-ia32" "2.5.0"
"@parcel/watcher-win32-x64" "2.5.0"
"@popperjs/core@^2.11.8", "@popperjs/core@^2.9.3":
"@popperjs/core@^2.11.8":
version "2.11.8"
resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz"
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
@ -265,26 +265,6 @@
resolved "https://registry.npmjs.org/@rails/actioncable/-/actioncable-7.2.201.tgz"
integrity sha512-wsTdWoZ5EfG5k3t7ORdyQF0ZmDEgN4aVPCanHAiNEwCROqibSZMXXmCbH7IDJUVri4FOeAVwwbPINI7HVHPKBw==
"@rollup/plugin-node-resolve@^15.2.3":
version "15.3.1"
resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz"
integrity sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==
dependencies:
"@rollup/pluginutils" "^5.0.1"
"@types/resolve" "1.20.2"
deepmerge "^4.2.2"
is-module "^1.0.0"
resolve "^1.22.1"
"@rollup/pluginutils@^5.0.1":
version "5.1.4"
resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz"
integrity sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==
dependencies:
"@types/estree" "^1.0.0"
estree-walker "^2.0.2"
picomatch "^4.0.2"
"@sindresorhus/merge-streams@^2.1.0":
version "2.3.0"
resolved "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz"
@ -300,16 +280,6 @@
lodash.merge "^4.6.2"
postcss-selector-parser "6.0.10"
"@types/estree@^1.0.0":
version "1.0.6"
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz"
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
"@types/resolve@1.20.2":
version "1.20.2"
resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz"
integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz"
@ -352,16 +322,6 @@ binary-extensions@^2.0.0:
resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz"
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
bootstrap-icons@^1.11.3:
version "1.11.3"
resolved "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz"
integrity sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==
bootstrap@3:
version "3.4.1"
resolved "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz"
integrity sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz"
@ -480,11 +440,6 @@ debug@^4:
dependencies:
ms "^2.1.3"
deepmerge@^4.2.2:
version "4.3.1"
resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz"
integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
dependency-graph@^0.11.0:
version "0.11.0"
resolved "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz"
@ -541,11 +496,6 @@ escalade@^3.1.1, escalade@^3.2.0:
resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz"
integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
estree-walker@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz"
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
fast-glob@^3.3.2:
version "3.3.2"
resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz"
@ -576,23 +526,6 @@ fill-range@^7.1.1:
dependencies:
to-regex-range "^5.0.1"
flowbite-datepicker@^1.3.0:
version "1.3.2"
resolved "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.3.2.tgz"
integrity sha512-6Nfm0MCVX3mpaR7YSCjmEO2GO8CDt6CX8ZpQnGdeu03WUCWtEPQ/uy0PUiNtIJjJZWnX0Cm3H55MOhbD1g+E/g==
dependencies:
"@rollup/plugin-node-resolve" "^15.2.3"
flowbite "^2.0.0"
flowbite@^2.0.0, flowbite@^2.5.2:
version "2.5.2"
resolved "https://registry.npmjs.org/flowbite/-/flowbite-2.5.2.tgz"
integrity sha512-kwFD3n8/YW4EG8GlY3Od9IoKND97kitO+/ejISHSqpn3vw2i5K/+ZI8Jm2V+KC4fGdnfi0XZ+TzYqQb4Q1LshA==
dependencies:
"@popperjs/core" "^2.9.3"
flowbite-datepicker "^1.3.0"
mini-svg-data-uri "^1.4.3"
fraction.js@^4.3.7:
version "4.3.7"
resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz"
@ -612,11 +545,6 @@ fsevents@~2.3.2:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"
@ -656,13 +584,6 @@ has-flag@^3.0.0:
resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz"
integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
hasown@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz"
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
dependencies:
function-bind "^1.1.2"
ignore-by-default@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz"
@ -685,13 +606,6 @@ is-binary-path@~2.1.0:
dependencies:
binary-extensions "^2.0.0"
is-core-module@^2.16.0:
version "2.16.1"
resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz"
integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==
dependencies:
hasown "^2.0.2"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
@ -709,11 +623,6 @@ is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
dependencies:
is-extglob "^2.1.1"
is-module@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz"
integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"
@ -766,11 +675,6 @@ micromatch@^4.0.4, micromatch@^4.0.5:
braces "^3.0.3"
picomatch "^2.3.1"
mini-svg-data-uri@^1.4.3:
version "1.4.4"
resolved "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz"
integrity sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==
minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
@ -824,11 +728,6 @@ normalize-range@^0.1.2:
resolved "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz"
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
path-type@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz"
@ -844,11 +743,6 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
picomatch@^4.0.2:
version "4.0.2"
resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz"
integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
pify@^2.3.0:
version "2.3.0"
resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz"
@ -956,15 +850,6 @@ require-directory@^2.1.1:
resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz"
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
resolve@^1.22.1:
version "1.22.10"
resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz"
integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==
dependencies:
is-core-module "^2.16.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz"
@ -1033,11 +918,6 @@ supports-color@^5.5.0:
dependencies:
has-flag "^3.0.0"
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
thenby@^1.3.4:
version "1.3.4"
resolved "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz"