diff --git a/app/assets/stylesheets/notifications.css b/app/assets/stylesheets/notifications.css index 4b58106a9..0bcba7d3a 100644 --- a/app/assets/stylesheets/notifications.css +++ b/app/assets/stylesheets/notifications.css @@ -2,83 +2,83 @@ height: 4rem; inset: auto auto var(--block-space); position: fixed; -} -.notification { - inset: auto auto 0; - position: absolute; - transform: translate(var(--offsetX), var(--offsetY)); - transform-origin: center center; - transition: transform 0.2s ease-in-out; - z-index: var(--z-index); + .notification { + inset: auto auto 0; + position: absolute; + transform: translate(var(--offsetX), var(--offsetY)); + transform-origin: center center; + transition: transform 0.2s ease-in-out; + z-index: var(--z-index); - .notification__content { - color: var(--color-ink); - inline-size: var(--width); - } + .notification__content { + color: var(--color-ink); + inline-size: var(--width); + } - &:nth-child(1) { - --offsetX: 0; - --offsetY: 0; - --width: 40ch; - --z-index: 0; - } - - &:nth-child(2) { - --offsetX: 0.5ch; - --offsetY: calc(var(--block-space-half) * -1); - --width: 39ch; - --z-index: -1; - } - - &:nth-child(3) { - --offsetX: 1ch; - --offsetY: calc(var(--block-space) * -1); - --width: 38ch; - --z-index: -2; - } - - &:nth-child(4) { - --offsetX: 1.5ch; - --offsetY: calc(var(--block-space) * -1.5); - --width: 37ch; - --z-index: -3; - } - - &:nth-child(5) { - --offsetX: 2ch; - --offsetY: calc(var(--block-space) * -2); - --width: 36ch; - --z-index: -4; - } - - &:nth-child(1n + 6) { - display: none; - } - - .notification-tray:hover & { - &:nth-child(2) { + &:nth-child(1) { --offsetX: 0; - --offsetY: calc((100% + var(--block-space-half)) * -1); + --offsetY: 0; --width: 40ch; + --z-index: 0; + } + + &:nth-child(2) { + --offsetX: 0.5ch; + --offsetY: calc(var(--block-space-half) * -1); + --width: 39ch; + --z-index: -1; } &:nth-child(3) { - --offsetX: 0; - --offsetY: calc((100% + var(--block-space-half)) * -2); - --width: 40ch; + --offsetX: 1ch; + --offsetY: calc(var(--block-space) * -1); + --width: 38ch; + --z-index: -2; } &:nth-child(4) { - --offsetX: 0; - --offsetY: calc((100% + var(--block-space-half)) * -3); - --width: 40ch; + --offsetX: 1.5ch; + --offsetY: calc(var(--block-space) * -1.5); + --width: 37ch; + --z-index: -3; } &:nth-child(5) { - --offsetX: 0; - --offsetY: calc((100% + var(--block-space-half)) * -4); - --width: 40ch; + --offsetX: 2ch; + --offsetY: calc(var(--block-space) * -2); + --width: 36ch; + --z-index: -4; + } + + &:nth-child(1n + 6) { + display: none; + } + + .notification-tray:hover & { + &:nth-child(2) { + --offsetX: 0; + --offsetY: calc((100% + var(--block-space-half)) * -1); + --width: 40ch; + } + + &:nth-child(3) { + --offsetX: 0; + --offsetY: calc((100% + var(--block-space-half)) * -2); + --width: 40ch; + } + + &:nth-child(4) { + --offsetX: 0; + --offsetY: calc((100% + var(--block-space-half)) * -3); + --width: 40ch; + } + + &:nth-child(5) { + --offsetX: 0; + --offsetY: calc((100% + var(--block-space-half)) * -4); + --width: 40ch; + } } } } diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb new file mode 100644 index 000000000..85b656c4c --- /dev/null +++ b/app/controllers/notifications_controller.rb @@ -0,0 +1,6 @@ +class NotificationsController < ApplicationController + def index + @read = Current.user.notifications.read.ordered + @unread = Current.user.notifications.unread.ordered + end +end diff --git a/app/controllers/readings_controller.rb b/app/controllers/readings_controller.rb index 9c5798ed3..1bf106efd 100644 --- a/app/controllers/readings_controller.rb +++ b/app/controllers/readings_controller.rb @@ -8,6 +8,6 @@ class ReadingsController < ApplicationController private def mark_bubble_notifications_read - Current.user.notifications.where(bubble: @bubble).update(read: true) + Current.user.notifications.where(bubble: @bubble).update(read_at: Time.current) end end diff --git a/app/models/notification.rb b/app/models/notification.rb index 531be12bb..da1cc9614 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -4,10 +4,15 @@ class Notification < ApplicationRecord belongs_to :bubble belongs_to :resource, polymorphic: true - scope :unread, -> { where(read: false) } - scope :ordered, -> { order(read: :desc, created_at: :desc) } + scope :unread, -> { where(read_at: nil) } + scope :read, -> { where.not(read_at: nil) } + scope :ordered, -> { order(read_at: :desc, created_at: :desc) } delegate :creator, to: :event broadcasts_to ->(notification) { [ notification.user, :notifications ] }, inserts_by: :prepend + + def read? + read_at.present? + end end diff --git a/app/views/buckets/index.html.erb b/app/views/buckets/index.html.erb index 7c2f59a91..baf1e0ddd 100644 --- a/app/views/buckets/index.html.erb +++ b/app/views/buckets/index.html.erb @@ -7,6 +7,8 @@ Account settings <% end %> + <%= link_to "All notifications", notifications_path %> + <%= link_to new_bucket_path, class: "btn flex-item-justify-end", style: "view-transition-name: new-bucket" do %> <%= image_tag "add.svg", aria: { hidden: true }, size: 24 %> Add a new project diff --git a/app/views/notifications/index.html.erb b/app/views/notifications/index.html.erb new file mode 100644 index 000000000..84e2b4254 --- /dev/null +++ b/app/views/notifications/index.html.erb @@ -0,0 +1,27 @@ +<% @page_title = "Notifications" %> + +<% content_for :header do %> + +<% end %> + +<% if @unread.any? %> +

New for you

+ <%= render @unread %> +<% end %> + +<% if @read.any? %> +

Previously read

+<%= render @read %> +<% end %> diff --git a/config/routes.rb b/config/routes.rb index 92fcc6806..6860a6cf0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -18,8 +18,9 @@ Rails.application.routes.draw do end resources :bubbles + resources :notifications, only: :index namespace :notifications do - resource :tray + resource :tray, only: :show end resources :buckets do diff --git a/db/migrate/20250116163344_change_notification_read_to_read_at.rb b/db/migrate/20250116163344_change_notification_read_to_read_at.rb new file mode 100644 index 000000000..c9bd6bf64 --- /dev/null +++ b/db/migrate/20250116163344_change_notification_read_to_read_at.rb @@ -0,0 +1,12 @@ +class ChangeNotificationReadToReadAt < ActiveRecord::Migration[8.1] + def change + remove_index :notifications, %i[ user_id read created_at ], order: { read: :desc, created_at: :desc } + + change_table :notifications do |t| + t.remove :read + t.datetime :read_at + + t.index %i[ user_id read_at created_at ], order: { read_at: :desc, created_at: :desc } + end + end +end diff --git a/db/schema.rb b/db/schema.rb index a11bb2a66..e1b644f2d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2025_01_15_122810) do +ActiveRecord::Schema[8.1].define(version: 2025_01_16_163344) do create_table "accesses", force: :cascade do |t| t.integer "bucket_id", null: false t.integer "user_id", null: false @@ -183,13 +183,13 @@ ActiveRecord::Schema[8.1].define(version: 2025_01_15_122810) do t.integer "bubble_id", null: false t.string "resource_type", null: false t.integer "resource_id", null: false - t.boolean "read", default: false, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.datetime "read_at" t.index ["bubble_id"], name: "index_notifications_on_bubble_id" t.index ["event_id"], name: "index_notifications_on_event_id" t.index ["resource_type", "resource_id"], name: "index_notifications_on_resource" - t.index ["user_id", "read", "created_at"], name: "index_notifications_on_user_id_and_read_and_created_at", order: { read: :desc, created_at: :desc } + t.index ["user_id", "read_at", "created_at"], name: "index_notifications_on_user_id_and_read_at_and_created_at", order: { read_at: :desc, created_at: :desc } t.index ["user_id"], name: "index_notifications_on_user_id" end diff --git a/test/controllers/notifications_controller_test.rb b/test/controllers/notifications_controller_test.rb new file mode 100644 index 000000000..20b28201d --- /dev/null +++ b/test/controllers/notifications_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class NotificationsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/fixtures/notifications.yml b/test/fixtures/notifications.yml index 89a8256ee..7bff2665c 100644 --- a/test/fixtures/notifications.yml +++ b/test/fixtures/notifications.yml @@ -3,7 +3,6 @@ logo_published_kevin: event: logo_published bubble: logo resource: logo (Bubble) - read: false created_at: <%= 1.week.ago %> layout_commented_kevin: @@ -11,5 +10,4 @@ layout_commented_kevin: event: layout_commented bubble: layout resource: layout_overflowing_david (Comment) - read: false created_at: <%= 1.week.ago %>