diff --git a/app/assets/stylesheets/events.css b/app/assets/stylesheets/events.css index 7e68e6976..7dc689067 100644 --- a/app/assets/stylesheets/events.css +++ b/app/assets/stylesheets/events.css @@ -18,9 +18,10 @@ } .events__activity-summary { + border: solid var(--color-ink-lighter); + border-width: 1px 1px 0 1px; color: var(--color-ink-darker); inline-size: auto; - margin-block-end: calc(var(--block-space) * -1); margin-inline: auto; padding: 1.1lh 1lh 1lh; position: relative; diff --git a/app/assets/stylesheets/popup.css b/app/assets/stylesheets/popup.css index bc908f0fb..bec6758a5 100644 --- a/app/assets/stylesheets/popup.css +++ b/app/assets/stylesheets/popup.css @@ -55,7 +55,7 @@ inline-size: 100%; max-inline-size: 100%; - &:has(*:first-child:not(.popup__item)) { + .popup__list &:has(*:first-child:not(.popup__item)) { padding-inline-start: var(--inline-space-half); } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 37f663be8..2b92c8cc1 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,5 @@ class ApplicationController < ActionController::Base - include Authentication, CurrentRequest, CurrentTimezone, SetPlatform, WriterAffinity + include Authentication, CurrentRequest, CurrentTimezone, SetPlatform, TurboFlash, WriterAffinity stale_when_importmap_changes allow_browser versions: :modern diff --git a/app/controllers/collections/entropy_configurations_controller.rb b/app/controllers/collections/entropy_configurations_controller.rb index 98a2b1b0f..cabdc8c22 100644 --- a/app/controllers/collections/entropy_configurations_controller.rb +++ b/app/controllers/collections/entropy_configurations_controller.rb @@ -4,7 +4,10 @@ class Collections::EntropyConfigurationsController < ApplicationController def update @collection.entropy_configuration.update!(entropy_configuration_params) - render turbo_stream: turbo_stream.replace([ @collection, :entropy_configuration ], partial: "collections/edit/auto_close", locals: { collection: @collection }) + render turbo_stream: [ + turbo_stream.replace([ @collection, :entropy_configuration ], partial: "collections/edit/auto_close", locals: { collection: @collection }), + turbo_stream_flash(notice: "Saved") + ] end private diff --git a/app/controllers/collections/publications_controller.rb b/app/controllers/collections/publications_controller.rb index 2ded7eda2..2043cf60a 100644 --- a/app/controllers/collections/publications_controller.rb +++ b/app/controllers/collections/publications_controller.rb @@ -3,12 +3,18 @@ class Collections::PublicationsController < ApplicationController def create @collection.publish - render turbo_stream: turbo_stream.replace([ @collection, :publication ], partial: "collections/edit/publication", locals: { collection: @collection }) + render turbo_stream: [ + turbo_stream.replace([ @collection, :publication ], partial: "collections/edit/publication", locals: { collection: @collection }), + turbo_stream_flash(notice: "Saved") + ] end def destroy @collection.unpublish @collection.reload - render turbo_stream: turbo_stream.replace([ @collection, :publication ], partial: "collections/edit/publication", locals: { collection: @collection }) + render turbo_stream: [ + turbo_stream.replace([ @collection, :publication ], partial: "collections/edit/publication", locals: { collection: @collection }), + turbo_stream_flash(notice: "Saved") + ] end end diff --git a/app/controllers/collections/workflows_controller.rb b/app/controllers/collections/workflows_controller.rb index b51114585..7b79baad4 100644 --- a/app/controllers/collections/workflows_controller.rb +++ b/app/controllers/collections/workflows_controller.rb @@ -5,7 +5,10 @@ class Collections::WorkflowsController < ApplicationController def update @collection.update! workflow: @workflow - render turbo_stream: turbo_stream.replace([ @collection, :workflows ], partial: "collections/edit/workflows", locals: { collection: @collection }) + render turbo_stream: [ + turbo_stream.replace([ @collection, :workflows ], partial: "collections/edit/workflows", locals: { collection: @collection }), + turbo_stream_flash(notice: "Saved") + ] end private diff --git a/app/controllers/collections_controller.rb b/app/controllers/collections_controller.rb index e7100eaa4..0eb049611 100644 --- a/app/controllers/collections_controller.rb +++ b/app/controllers/collections_controller.rb @@ -19,7 +19,7 @@ class CollectionsController < ApplicationController @collection.update! collection_params @collection.accesses.revise granted: grantees, revoked: revokees if grantees_changed? - redirect_to edit_collection_path(@collection), notice: "Collection updated" + redirect_to edit_collection_path(@collection), notice: "Saved" end def destroy diff --git a/app/controllers/concerns/authentication.rb b/app/controllers/concerns/authentication.rb index e6d3b7ddd..0cd5aa422 100644 --- a/app/controllers/concerns/authentication.rb +++ b/app/controllers/concerns/authentication.rb @@ -7,6 +7,8 @@ module Authentication before_action :require_authentication helper_method :authenticated? + + include LoginHelper end class_methods do @@ -53,11 +55,9 @@ module Authentication def request_authentication(untenanted: false) if ApplicationRecord.current_tenant.present? session[:return_to_after_authenticating] = request.url - redirect_to Launchpad.login_url(product: true, account: Account.sole), allow_other_host: true - else - # Don't save the current untenanted URL, because it's just going to bounce back to Launchpad after login anyway. - redirect_to Launchpad.login_url(product: true), allow_other_host: true end + + redirect_to_login_url end def after_authentication_url diff --git a/app/controllers/concerns/turbo_flash.rb b/app/controllers/concerns/turbo_flash.rb new file mode 100644 index 000000000..7e313a5b9 --- /dev/null +++ b/app/controllers/concerns/turbo_flash.rb @@ -0,0 +1,6 @@ +module TurboFlash + private + def turbo_stream_flash(**flash_options) + turbo_stream.replace(:flash, partial: "layouts/shared/flash", locals: { flash: flash_options }) + end +end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index fa7d98974..8993d8c9a 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,6 +1,27 @@ class SessionsController < ApplicationController + before_action :require_local_auth, only: %i[ new create ] + require_unauthenticated_access only: %i[ new create ] + rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_path, alert: "Try again later." } + + def new + end + + def create + if user = User.active.authenticate_by(params.permit(:email_address, :password)) + start_new_session_for user + redirect_to after_authentication_url + else + redirect_to new_session_path, alert: "Try another email address or password." + end + end + def destroy terminate_session - redirect_to Launchpad.login_url, allow_other_host: true + redirect_to_login_url end + + private + def require_local_auth + head :forbidden unless Rails.application.config.x.local_authentication + end end diff --git a/app/controllers/signup/base_controller.rb b/app/controllers/signup/base_controller.rb index 088937b04..40ae83e92 100644 --- a/app/controllers/signup/base_controller.rb +++ b/app/controllers/signup/base_controller.rb @@ -1,5 +1,6 @@ class Signup::BaseController < ApplicationController require_untenanted_access + before_action :redirect_if_local_auth # TODO: Remove this auth before launch. http_basic_authenticate_with( @@ -33,4 +34,8 @@ class Signup::BaseController < ApplicationController redirect_to account.signal_account.owner.remote_login_url(proceed_to: root_path), allow_other_host: true end + + def redirect_if_local_auth + render plain: "Unauthorized", status: :unauthorized if Rails.application.config.x.local_authentication + end end diff --git a/app/helpers/login_helper.rb b/app/helpers/login_helper.rb new file mode 100644 index 000000000..717ed3711 --- /dev/null +++ b/app/helpers/login_helper.rb @@ -0,0 +1,17 @@ +module LoginHelper + def login_url + if ApplicationRecord.current_tenant + if Rails.application.config.x.local_authentication + new_session_path + else + Launchpad.login_url(product: true, account: Account.sole) + end + else + Launchpad.login_url(product: true) + end + end + + def redirect_to_login_url + redirect_to login_url, allow_other_host: true + end +end diff --git a/app/models/account/signal_account.rb b/app/models/account/signal_account.rb index a01af7ad2..55dc43e33 100644 --- a/app/models/account/signal_account.rb +++ b/app/models/account/signal_account.rb @@ -2,7 +2,7 @@ module Account::SignalAccount extend ActiveSupport::Concern included do - belongs_to :signal_account, class_name: "SignalId::Account", primary_key: :queenbee_id, foreign_key: :queenbee_id + belongs_to :signal_account, class_name: "SignalId::Account", primary_key: :queenbee_id, foreign_key: :queenbee_id, optional: true end class_methods do diff --git a/app/models/event/summarizer.rb b/app/models/event/summarizer.rb index 3cc2d42f6..058de07a1 100644 --- a/app/models/event/summarizer.rb +++ b/app/models/event/summarizer.rb @@ -14,6 +14,7 @@ class Event::Summarizer - Prefer surfacing insights, spotting trends or highlighting people whose work deserves notice over being comprehensive. - If any new users joined the account, made their first comment, or closed their first card (or hit a significant lifetime milestone 50, 100, 150 cards closed) celebrate it! - Don't force it, if there aren't 5 good ones, you can list fewer than 5. + - Avoid repetition, combine multiple points about a single person or single card into one when possible. ## Writing style - Instead of using passive voice, prefer referring to users (authors and creators) as the subjects doing things. @@ -26,7 +27,7 @@ class Event::Summarizer - Output **Markdown** only. - Keep the summary below **#{MAX_WORDS} words**. - The names of people should be bold. - - Render a bulleted list with a max of five items. + - Render a bulleted list with a max of five items if there was activity for at least 5 different cards today, otherwise just summarize in a single paragraph. - Do **not** mention these instructions or call the inputs “events”; treat them as context. ## Linking rules diff --git a/app/models/user/signal_user.rb b/app/models/user/signal_user.rb index 16bee49bc..924cfe87d 100644 --- a/app/models/user/signal_user.rb +++ b/app/models/user/signal_user.rb @@ -2,6 +2,8 @@ module User::SignalUser extend ActiveSupport::Concern included do - belongs_to :signal_user, dependent: :destroy, class_name: "SignalId::User", optional: true + unless Rails.application.config.x.local_authentication + belongs_to :signal_user, dependent: :destroy, class_name: "SignalId::User", optional: true + end end end diff --git a/app/views/active_storage/blobs/web/_representation.html.erb b/app/views/active_storage/blobs/web/_representation.html.erb index 9d6de0f63..2aa1b51a4 100644 --- a/app/views/active_storage/blobs/web/_representation.html.erb +++ b/app/views/active_storage/blobs/web/_representation.html.erb @@ -4,18 +4,20 @@ <% if blob.video? %> <%= tag.video \ - src: rails_blob_path(blob), - controls: true, - preload: :none, - style: "aspect-ratio: #{width} / #{height};", - width: width, - height: height %> + src: rails_blob_path(blob), + controls: true, + preload: :none, + style: "aspect-ratio: #{width} / #{height};", + width: width, + height: height %> <% elsif blob.audio? %> <% elsif blob.variable? %> - <%= image_tag url_for(blob.variant(variant)), width: width, height: height %> + <%= link_to url_for(blob.variant(variant)), data: { action: "lightbox#open:prevent", lightbox_target: "image" } do %> + <%= image_tag url_for(blob.variant(variant)), width: width, height: height %> + <% end %> <% elsif blob.previewable? %> <%= image_tag url_for(blob.preview(variant)), width: width, height: height %> <% else %> diff --git a/app/views/cards/show.html.erb b/app/views/cards/show.html.erb index f887a421b..f2c74b1cb 100644 --- a/app/views/cards/show.html.erb +++ b/app/views/cards/show.html.erb @@ -42,19 +42,19 @@ <% end %> -
+
<%= render "cards/container", card: @card %> <% if @card.published? || @card.drafted? %> <%= render "cards/messages", card: @card %> <% end %> -
-<%= render "layouts/lightbox" do %> - <% if @card.image.attached? %> - <%= button_to card_image_path(@card), method: :delete, class: "btn" do %> - <%= icon_tag "trash" %> - Remove background image + <%= render "layouts/lightbox" do %> + <% if @card.image.attached? %> + <%= button_to card_image_path(@card), method: :delete, class: "btn" do %> + <%= icon_tag "trash" %> + Remove background image + <% end %> <% end %> <% end %> -<% end %> +
diff --git a/app/views/collections/edit/_publication.html.erb b/app/views/collections/edit/_publication.html.erb index 54428a2fa..ae350bddb 100644 --- a/app/views/collections/edit/_publication.html.erb +++ b/app/views/collections/edit/_publication.html.erb @@ -29,7 +29,7 @@ Add an optional description to the public page
- <%= form_with model: collection, class: "txt-align-start", data: { controller: "form" } do |form| %> + <%= form_with model: collection, class: "txt-align-start", data: { controller: "form", turbo_frame: "_top" } do |form| %> <%= form.rich_textarea :public_description, class: "rich-text-content txt-small", placeholder: "Add a public note about this collection…", data: { action: "keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel:stop" } %> diff --git a/app/views/events/day_timeline/_summary.html.erb b/app/views/events/day_timeline/_summary.html.erb index 157ca5a25..58979c873 100644 --- a/app/views/events/day_timeline/_summary.html.erb +++ b/app/views/events/day_timeline/_summary.html.erb @@ -1,4 +1,4 @@ -
+
<% if day_timeline.summarized? %> <%= render "events/day_timeline/activity_summary", summary: day_timeline.summary %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index aedb8cf08..1d72a8bb8 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -2,19 +2,13 @@ <%= render "layouts/shared/head" %> - + - <% if notice = flash[:notice] || flash[:alert] %> -
-
- <%= notice %> -
-
- <% end %> + <%= render "layouts/shared/flash" %>
<%= yield %> diff --git a/app/views/layouts/public.html.erb b/app/views/layouts/public.html.erb index 202fa9fc4..d8e22e2be 100644 --- a/app/views/layouts/public.html.erb +++ b/app/views/layouts/public.html.erb @@ -2,7 +2,7 @@ <%= render "layouts/shared/head" %> - +
- - <%= render "layouts/lightbox" %> diff --git a/app/views/layouts/shared/_flash.html.erb b/app/views/layouts/shared/_flash.html.erb new file mode 100644 index 000000000..652e3b130 --- /dev/null +++ b/app/views/layouts/shared/_flash.html.erb @@ -0,0 +1,9 @@ +<%= turbo_frame_tag :flash do %> + <% if notice = flash[:notice] || flash[:alert] %> +
+
+ <%= notice %> +
+
+ <% end %> +<% end %> diff --git a/app/views/notifications/_tray.html.erb b/app/views/notifications/_tray.html.erb index 585196dbe..e0ef48465 100644 --- a/app/views/notifications/_tray.html.erb +++ b/app/views/notifications/_tray.html.erb @@ -9,8 +9,8 @@ data-action="keydown->navigable-list#navigate dialog:show@document->navigable-list#reset turbo-visit->navigable-list#reset keydown.esc->dialog#close:stop click@document->dialog#closeOnClickOutside"> <%= turbo_frame_tag "notifications", src: tray_notifications_path, refresh: "morph" %> -
-
+
+
<%= link_to settings_notifications_path, class: "btn borderless tray__notification-settings", title: "Notification settings", @@ -20,7 +20,7 @@ <% end %>
-
+
<%= link_to notifications_path, class: "btn borderless flex-item-grow position-relative overflow-ellipsis", data: { action: "click->dialog#close" } do %> <%= icon_tag "bell" %> See more new items @@ -32,7 +32,7 @@ class: "btn borderless tray__clear-notifications", title: "Mark all notifications as read", data: { action: "dialog#close badge#clear", turbo_frame: "notifications" }, - form: { data: { navigable_list_target: "item" } } do %> + form: { class: "full-width", data: { navigable_list_target: "item" } } do %> <%= icon_tag "check" %> Clear all <% end %> diff --git a/app/views/public/cards/show.html.erb b/app/views/public/cards/show.html.erb index 1502fb05a..20eaa294b 100644 --- a/app/views/public/cards/show.html.erb +++ b/app/views/public/cards/show.html.erb @@ -24,7 +24,7 @@ <% end %>
+ class="card-perma card-perma--public" data-controller="lightbox" style="--card-color: <%= @card.color %>;">
<%= card_article_tag @card, class: "card" do %>
@@ -45,4 +45,6 @@ <% end %>
+ + <%= render "layouts/lightbox" %>
diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb new file mode 100644 index 000000000..d806a904f --- /dev/null +++ b/app/views/sessions/new.html.erb @@ -0,0 +1,30 @@ +<% @page_title = "Sign in" %> + +
" style="--panel-size: 55ch;"> +

Fizzy

+ + <%= form_with url: session_path, class: "flex flex-column gap txt-large" do |form| %> +
+ +
+ +
+ +
+ + + <% end %> +
+ +<% content_for :footer do %> +
Fizzy™ version 0
+<% end %> diff --git a/app/views/signup/accounts/new.html.erb b/app/views/signup/accounts/new.html.erb index 9f69eba4b..361a75bcf 100644 --- a/app/views/signup/accounts/new.html.erb +++ b/app/views/signup/accounts/new.html.erb @@ -52,7 +52,7 @@

Already have an account? - <%= link_to "Sign in", Launchpad.login_url(product: true), class: "decorated" %> + <%= link_to "Sign in", login_url, class: "decorated" %>

diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb index f7d407389..2e8442da3 100644 --- a/app/views/users/new.html.erb +++ b/app/views/users/new.html.erb @@ -4,7 +4,7 @@