Skip to content

Improve user edit UX, incl add comments#1040

Merged
maebeale merged 33 commits intomainfrom
maebeale/add-user-comments
Feb 16, 2026
Merged

Improve user edit UX, incl add comments#1040
maebeale merged 33 commits intomainfrom
maebeale/add-user-comments

Conversation

@maebeale
Copy link
Collaborator

@maebeale maebeale commented Feb 16, 2026

Summary

User/Person UX

  • Polymorphic comments — Add inline comments to user form matching person form pattern. Add updated_by to comments and show that

  • Redesigned user show page with structured label/value layout, account event history (Ahoy), and comments section

  • Updated user form with improved lock/unlock flow using a boolean field and Stimulus controllers

  • User-Person email sync — Updating a user's email now automatically updates the associated person's email field via after_update callback

  • Updated user index, esp around which icons are showing and adding informative hover text

  • Added created_by/updated_by to users — Migration adding these tracking columns directly to the User model

  • Expanded person and user search — User search now also matches against person.email and person.email_2

  • Scope rename — Renamed User.active scope to User.has_access for clarity, updated all call sites across controllers and policies

  • Person form flow — Improved navigation flow from person to user creation, retaining hidden user_id on person form

  • Duplicate check improvements — Skip flagging duplicates when a person's email matches the user's email and they're already linked; updated check duplicates view styling

  • Devise configuration — Reinstated confirmable on email change; changed lockable settings so only admins can unlock accounts

Other updates

  • Ahoy tracking enhancements — Enhanced Ahoy event properties globally to capture more context; added Ahoy events for all Devise mailers
  • Dirty form tracking — Added dirty_form_controller to warn on unsaved changes
  • Bulk invite rake task — Added bulk_invite rake task for inviting multiple users at once

maebeale and others added 2 commits February 15, 2026 20:19
- Add inline comments to user edit form matching person form pattern
- Track created_by/updated_by on comments via controller
- Show updated_by initials instead of created_by on comment display
- Restrict comment policy to admin-only access
- Move comment edit toggle from inline onclick to Stimulus controller
- Improve dirty form controller with turbo:before-visit and beforeunload guards

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@maebeale maebeale changed the title Add comments to user edit form, improve comment UX Improve user edit UX, incl add comments Feb 16, 2026
…r's email if that user is already connected to that person
def set_form_variables
@organizations = Organization.pluck(:name, :id).sort_by(&:first)
@authors = User.active.or(User.where(id: @community_news.author_id))
@authors = User.has_access.or(User.where(id: @community_news.author_id))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this scope isn't jsut inactive flag anymore bc it takes into account other devise fields

@account_events = user_auth_events
.includes(:user)
.order(time: :desc)
.paginate(page: params[:page], per_page: 10)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add more event data to user show

if email.present? && !email.downcase.end_with?("@example.com")
duplicates = find_duplicate_users(email)
person_id = params[:person_id].presence || params.dig(:user, :person_id).presence || @user.person_id
duplicates = find_duplicate_users(email, exclude_person_id: person_id)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't identify duplicates w people emails for an associated person

person_id = params[:person_id].presence || params.dig(:user, :person_id).presence
@user.person = Person.find(person_id) if person_id
@user.created_by = current_user
@user.updated_by = current_user
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add audit tracking to user. we maybe want to do this more sitewide w a gem instead of direct. or maybe use ahoy records, but they really weren't designed for that.

bypass_sign_in(@user) if @user == current_user
notice = "User was successfully updated."
notice += " A confirmation email has been sent to #{@user.unconfirmed_email}." if @user.unconfirmed_email.present? && @user.saved_change_to_unconfirmed_email?
redirect_to users_path, notice: notice
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initiate normal confirmation flow when a user email is changed, to set it back up to confirm the new email. existing email login will still work.

def user_params
params.require(:user).permit(
:email, :comment, :person_id, :inactive, :primary_address, :time_zone, :super_user,
:email, :comment, :person_id, :inactive, :locked, :primary_address, :time_zone, :super_user,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accept :locked as a param bc form now has it as a boolean checkbox even tho it's not a field on user

// Uses three layers of protection:
// 1. turbo:before-visit – catches Turbo Drive link clicks (custom message)
// 2. beforeunload – catches non-Turbo navigations / tab close (browser message)
// 3. confirmCancel action – explicit cancel button binding (custom message)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add prompt if there are unsaved changes and you try to navigate away.

content_tag(:span, "confirmed", class: "text-green-600 font-medium", title: "Email confirmed")
else
content_tag(:span, "unconfirmed", class: "text-red-600 ml-2 font-medium", title: "Email not confirmed")
content_tag(:span, "unconfirmed", class: "text-red-600 font-medium", title: "Email not confirmed")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove left padding for email confirmation status

{ record_id: @record.id, record_type: "User" },
user: Current.user
)
end
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we weren't making ahoy logs for devise emails sent. want to show these on user show.


included do
after_create -> { track_lifecycle_event("create") }
after_create -> { track_create_event }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

beef up after_create to save more data into ahoy record properties

after_create -> { track_create_event }
after_update -> { track_update_event }
after_destroy -> { track_lifecycle_event("destroy") }
after_destroy -> { track_lifecycle_event("destroy", @_destroy_snapshot || {}) }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

beef up after_destroy to save all data into ahoy record properties

extra[:changes] = format_tracked_changes(changes) if changes.present? && !devise_only_changes?(changes)
extra[:association_changes] = assoc_changes if assoc_changes.present?

track_lifecycle_event("update", extra)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

save more details in ahoy record after_update


# Validations
validates_presence_of :organization_id
validates_presence_of :person_id
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was causing error

devise :database_authenticatable, :recoverable, :confirmable,
:rememberable, :trackable, :validatable

attr_accessor :locked_will_change
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need this so the Lock boolean can be toggled and add/hide lock icon

# Sort by the most recent timestamp (updated_at preferred, fallback to created_at)
recent.sort_by { |item| item.try(:updated_at) || item.created_at }.reverse.first(activity_limit * 8)
end

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove unused method. we're now doing ahoy event records for this

public_send("bookmarked_#{bookmarkable_type.downcase.pluralize}")
.pluck(:id)
end

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove unused methods

end

def gallery_assets # method needed for idea_submission_fyi mailer
def gallery_assets # method needed for idea_submitted_fyi mailer
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix comment to correct mailer name

payload = { name: name, properties: properties.merge(
record_id: id, record_type: "User", updated_by_id: updated_by_id,
resource_type: "User", resource_id: id, resource_title: "#{self.name} (#{email})"
) }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

save resource_title on auth events


def create?
authenticated?
admin?
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments should be admin-only

if resource.is_a?(Asset) && resource.file.attached?
return resource.file.filename.to_s
end

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add resource_title for asset changes so we see filename in ahoy records

<td class="px-4 py-3 text-gray-500">
<%= event.properties["resource_title"].presence || "—" %>
</td>

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

show resource_title for more context in ahoy events table

# Number of authentication tries before locking an account if lock_strategy
# is failed attempts.
config.maximum_attempts = 10
config.unlock_strategy = :none
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't allow people to unlock themselves via email. only admins can unlock an account.

deprecating usage of Inactive in favor of using devise's lockable, which makes users unable to log in

puts "Done. Sent: #{sent}, Failed: #{errors.size}"
errors.each { |e| puts " FAILED: #{e[:email]} — #{e[:error]}" } if errors.any?
end
end
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added rake task for when we need to bulk invite users. we'll want to test this w awbw admins first.

@maebeale maebeale merged commit 4f1678b into main Feb 16, 2026
3 checks passed
@maebeale maebeale deleted the maebeale/add-user-comments branch February 16, 2026 19:43
maebeale added a commit that referenced this pull request Feb 17, 2026
* Add comments to user edit form, improve comment UX

- Add inline comments to user edit form matching person form pattern
- Track created_by/updated_by on comments via controller
- Show updated_by initials instead of created_by on comment display
- Restrict comment policy to admin-only access
- Move comment edit toggle from inline onclick to Stimulus controller
- Improve dirty form controller with turbo:before-visit and beforeunload guards

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* rubocop

* Update claude

* Add created_by_id and updated_by_id directly to user for better tracking

* Change lockable settings for devise so only admins can unlock

* update claude

* Show comments and ahoy events on user show

* Reinstate confirmable if user email is changed

* WIP: rework lock flow (via boolean) and comment display on user

* Update email_confirmation_icon helper to not have left padding

* Update ahoy globally so it captures more in the properties payload

* Update user callbacks and remove unused methods

* Update user show layout (label+value vs br, and, show ahoy events and comments)

* Remove toggle_lock_button from turbo

* Add includes to avoid n+1

* Change user icon display and behavior

* WIP: user ux

* Add ahoy event for all devise mailers

* Rake task for bulk inviting users

* Clean up flow from person to user

* Don't find/flag 'duplicates' if person has email that matches the user's email if that user is already connected to that person

* Update person email if associated user email changes

* Add person email and email_2 to searching

* Set as no access, not active if confirmed_at is nil

* Use has_access instead of active scope bc it's more accurate name now

* Change dropdown search params to be true/false vs another word for access param search of users index

* Update check dupes styling for user dupes display

* Adjust icons and hover text

* Retain hidden user_id on person form

* Fix tests based on ui changes

* Add error handling to ahoy

* Update specs to latest view changes

* Add wait time to help w flaky test

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant