mirror of
https://github.com/we-promise/sure.git
synced 2026-05-09 13:45:01 +00:00
* chore(design-system): swap raw gray classes for semantic tokens in settings/
Pilot for the broader raw-color sweep. Maps 21 occurrences across 11
files to design-system equivalents:
- text-white bg-gray-900 hover:bg-gray-800 (CTA buttons)
-> text-inverse button-bg-primary hover:button-bg-primary-hover
- bg-gray-25 / bg-gray-50 / bg-gray-100 (subtle surface backgrounds)
-> bg-surface-inset
- bg-gray-800 (tooltip pills) -> bg-inverse
- text-white inside tooltips -> text-inverse
- text-gray-300 (muted tooltip labels) -> text-inverse opacity-70
- text-gray-600 (muted body text) -> text-secondary
- hover:text-gray-700 -> hover:text-primary
- focus:ring-gray-900 -> focus:ring-button-bg-primary
The 7 status-indicator dots (`bg-gray-400`) are intentionally left
as raw classes. Gray-400 against both light and dark container bgs
gives reasonable contrast either way, and there's no semantic token
that fits a "neutral inactive indicator" use case yet. Worth a
follow-up if a `bg-subdued` token would benefit other places.
* fix(design-system): use theme-aware focus ring on provider submit buttons
Two issues caught in code review:
1. focus:ring-button-bg-primary silently emits no CSS (CodeRabbit, Codex).
button-bg-primary is a custom @utility, not a theme color, so Tailwind's
ring-{name} resolution finds no --color-button-bg-primary and falls
back to the default. Replaces with focus:ring-gray-900
theme-dark:focus:ring-white — same color flip as the button bg, but
resolved through theme colors so ring-{name} actually generates CSS.
2. _enable_banking_panel.html.erb dropped focus-ring + transition entirely
in the original sweep (CodeRabbit). Restores parity with the other
provider panels using the corrected ring classes.
Long-term cleanup: tracked under issue #1653 (modifier-aware utilities)
to make button-bg-primary also a theme color so ring-button-bg-primary
becomes valid.
190 lines
8.9 KiB
Plaintext
190 lines
8.9 KiB
Plaintext
<%= content_for :page_title, t(".page_title") %>
|
|
|
|
<%= settings_section title: t(".profile_title"), subtitle: t(".profile_subtitle", product_name: product_name) do %>
|
|
<%= styled_form_with model: @user, url: user_path(@user), class: "space-y-4" do |form| %>
|
|
<%= render "settings/user_avatar_field", form: form, user: @user %>
|
|
|
|
<div>
|
|
<%= form.email_field :email, placeholder: t(".email"), label: t(".email") %>
|
|
|
|
<% if @user.unconfirmed_email.present? %>
|
|
<p class="mt-2 text-sm text-secondary">
|
|
You have requested to change your email to <%= @user.unconfirmed_email %>. Please go to your email and confirm for the change to take effect. If you haven't received the email, please check your spam folder, or <%= link_to "request a new confirmation email", resend_confirmation_email_user_path(@user), class: "hover:underline text-secondary" %>.
|
|
</p>
|
|
<% end %>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
|
<%= form.text_field :first_name, placeholder: t(".first_name"), label: t(".first_name") %>
|
|
<%= form.text_field :last_name, placeholder: t(".last_name"), label: t(".last_name") %>
|
|
</div>
|
|
|
|
<div class="flex justify-end mt-4">
|
|
<%= render DS::Button.new(text: t(".save"), class: "md:w-auto w-full justify-center") %>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<% end %>
|
|
|
|
<% unless Current.user.ui_layout_intro? %>
|
|
<%= settings_section title: family_moniker == "Group" ? t(".group_title", default: "Group") : t(".household_title"), subtitle: t(".household_subtitle", moniker_plural: family_moniker_plural_downcase, moniker: family_moniker_downcase) do %>
|
|
<div class="space-y-4">
|
|
<%= styled_form_with model: Current.user, class: "space-y-4", data: { controller: "auto-submit-form" } do |form| %>
|
|
<%= form.fields_for :family do |family_fields| %>
|
|
<% name_label = family_moniker == "Group" ? t(".group_form_label", default: "Group name") : t(".household_form_label") %>
|
|
<% name_placeholder = family_moniker == "Group" ? t(".group_form_input_placeholder", default: "Enter group name") : t(".household_form_input_placeholder") %>
|
|
<%= family_fields.text_field :name,
|
|
placeholder: name_placeholder,
|
|
label: name_label,
|
|
disabled: !Current.user.admin?,
|
|
"data-auto-submit-form-target": "auto" %>
|
|
<% end %>
|
|
<% end %>
|
|
<div class="bg-container-inset rounded-xl p-1">
|
|
<div class="px-4 py-2">
|
|
<p class="uppercase text-xs text-secondary font-medium"><%= Current.family.name %> · <%= Current.family.users.size %></p>
|
|
</div>
|
|
<% @users.each do |user| %>
|
|
<div class="flex gap-2 mt-2 items-center bg-container p-4 shadow-border-xs rounded-lg">
|
|
<div class="w-9 h-9 shrink-0">
|
|
<%= render "settings/user_avatar", avatar_url: user.profile_image&.variant(:small)&.url, initials: user.initials %>
|
|
</div>
|
|
<p class="text-primary font-medium text-sm"><%= user.display_name %></p>
|
|
<div class="rounded-md bg-surface px-1.5 py-0.5">
|
|
<p class="uppercase text-secondary font-medium text-xs"><%= user.role %></p>
|
|
</div>
|
|
<% if Current.user.admin? && user != Current.user %>
|
|
<div class="ml-auto">
|
|
<%= render DS::Button.new(
|
|
variant: "icon",
|
|
icon: "x",
|
|
href: settings_profile_path(user_id: user),
|
|
method: :delete,
|
|
confirm: CustomConfirm.for_resource_deletion(user.display_name, high_severity: true)
|
|
) %>
|
|
</div>
|
|
<% end %>
|
|
</div>
|
|
<% end %>
|
|
<% if @pending_invitations.any? %>
|
|
<% @pending_invitations.each do |invitation| %>
|
|
<div class="flex gap-2 items-center justify-between bg-container p-4 border border-alpha-black-25 rounded-lg">
|
|
<div class="flex gap-2 items-center">
|
|
<div class="w-9 h-9 shrink-0">
|
|
<div class="text-inverse w-full h-full bg-surface-inset rounded-full flex items-center justify-center text-lg uppercase"><%= invitation.email[0] %></div>
|
|
</div>
|
|
<div class="flex">
|
|
<p class="text-primary font-medium text-sm"><%= invitation.email %></p>
|
|
<div class="rounded-md bg-surface px-1.5 py-0.5">
|
|
<p class="uppercase text-secondary font-medium text-xs"><%= t(".pending") %></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center gap-4">
|
|
<% if self_hosted? %>
|
|
<div class="flex items-center gap-2" data-controller="clipboard">
|
|
<p class="text-secondary text-sm"><%= t(".invitation_link") %></p>
|
|
<span data-clipboard-target="source" class="hidden"><%= accept_invitation_url(invitation.token) %></span>
|
|
<input type="text"
|
|
readonly
|
|
autocomplete="off"
|
|
value="<%= accept_invitation_url(invitation.token) %>"
|
|
class="text-sm bg-surface-inset px-2 py-1 rounded border border-secondary w-72">
|
|
<button data-action="clipboard#copy" class="text-secondary hover:text-primary">
|
|
<span data-clipboard-target="iconDefault">
|
|
<%= icon "copy" %>
|
|
</span>
|
|
<span class="hidden" data-clipboard-target="iconSuccess">
|
|
<%= icon "check" %>
|
|
</span>
|
|
</button>
|
|
</div>
|
|
<% end %>
|
|
|
|
<% if Current.user.admin? %>
|
|
<%= render DS::Button.new(
|
|
variant: "icon",
|
|
icon: "x",
|
|
href: invitation_path(invitation),
|
|
method: :delete,
|
|
confirm: CustomConfirm.for_resource_deletion(invitation.email, high_severity: true)
|
|
) %>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<% end %>
|
|
<% if Current.user.admin? %>
|
|
<%= link_to new_invitation_path,
|
|
class: "bg-container-inset flex items-center justify-center gap-2 text-secondary mt-1 hover:bg-container-inset-hover rounded-lg px-4 py-2 w-full text-center",
|
|
data: { turbo_frame: :modal } do %>
|
|
<%= icon("plus") %>
|
|
<%= t(".invite_member") %>
|
|
<% end %>
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
<% end %>
|
|
|
|
<%= settings_section title: t(".danger_zone_title") do %>
|
|
<div class="space-y-4">
|
|
<% if Current.user.admin? %>
|
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
|
<div class="w-full md:w-2/3">
|
|
<h3 class="font-medium text-primary"><%= t(".reset_account") %></h3>
|
|
<p class="text-secondary text-sm"><%= t(".reset_account_warning") %></p>
|
|
</div>
|
|
|
|
<%= render DS::Button.new(
|
|
text: t(".reset_account"),
|
|
variant: "destructive",
|
|
href: reset_user_path(@user),
|
|
method: :delete,
|
|
confirm: CustomConfirm.new(
|
|
title: t(".confirm_reset.title"),
|
|
body: t(".confirm_reset.body"),
|
|
btn_text: t(".reset_account"),
|
|
destructive: true,
|
|
high_severity: true
|
|
)
|
|
) %>
|
|
</div>
|
|
|
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
|
<div class="w-full md:w-2/3">
|
|
<h3 class="font-medium text-primary"><%= t(".reset_account_with_sample_data") %></h3>
|
|
<p class="text-secondary text-sm"><%= t(".reset_account_with_sample_data_warning") %></p>
|
|
</div>
|
|
|
|
<%= render DS::Button.new(
|
|
text: t(".reset_account_with_sample_data"),
|
|
variant: "destructive",
|
|
href: reset_with_sample_data_user_path(@user),
|
|
method: :delete,
|
|
confirm: CustomConfirm.new(
|
|
title: t(".confirm_reset_with_sample_data.title"),
|
|
body: t(".confirm_reset_with_sample_data.body"),
|
|
btn_text: t(".reset_account_with_sample_data"),
|
|
destructive: true,
|
|
high_severity: true
|
|
)
|
|
) %>
|
|
</div>
|
|
<% end %>
|
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
|
<div class="w-full md:w-2/3">
|
|
<h3 class="font-medium text-primary"><%= t(".delete_account") %></h3>
|
|
<p class="text-secondary text-sm"><%= t(".delete_account_warning") %></p>
|
|
</div>
|
|
|
|
<%= render DS::Button.new(
|
|
text: t(".delete_account"),
|
|
variant: "destructive",
|
|
href: user_path(@user),
|
|
method: :delete,
|
|
confirm: CustomConfirm.for_resource_deletion("your account", high_severity: true)
|
|
) %>
|
|
</div>
|
|
</div>
|
|
<% end %>
|