mirror of
https://github.com/we-promise/sure.git
synced 2026-05-12 07:05:00 +00:00
refactor(design-system): extend DS::Alert and migrate 9 inline alert blocks (#1731)
* feat(design-system): add info semantic color token Mirrors success/warning/destructive: --color-info maps to blue-600 in light mode, blue-500 in dark mode. Unblocks the DS::Alert info variant from carrying a raw 'blue-600' literal in icon_color and lets surface tokens use bg-info/N alpha modifiers like the rest of the system. Refs #1715 * refactor(design-system): adopt semantic tokens and add body slot in DS::Alert Replaces the bg-{blue,green,yellow,red}-50 / text-{...}-700 / border-{...}-200 palette block in DS::Alert with semantic alpha-modifier surfaces (bg-{info,success,warning,destructive}/10 + matching /20 borders). Drops the 'blue-600' literal that icon_color was returning for the info variant; helpers#icon now accepts color: :info backed by the new --color-info token. Adds an optional title: kwarg and an opt-in block-content slot so rich alerts (title + paragraph, lists, embedded actions) can render without callers reaching for a hand-rolled flex layout. The existing message: API stays backward-compatible — nothing in the codebase that already calls DS::Alert.new(message: ..., variant: ...) needs to change. Lookbook gains with_title and with_body_slot examples covering the new shapes. Refs #1715 * refactor(views): migrate api_keys, hostings, lunchflow alerts to DS::Alert Cleans up nine bespoke alert blocks that hand-rolled the same flex + icon + bordered-surface shape DS::Alert already provides: - settings/api_keys/{new,created,created.turbo_stream}.html.erb — three near-identical 'Security Warning' / 'Important Security Note' boxes using the broken bg-warning-50 / text-warning-700 raw-palette pair. - settings/hostings/{_alpha_vantage,_eodhd,_yahoo_finance,_twelve_data,_provider_selection}_settings.html.erb — five amber-50 / amber-200 warning boxes covering rate-limit notes, health-check failure messaging, and the env-configured override banner. The twelve_data plan-restriction block keeps its bullet list and pricing link inside the new DS::Alert body slot. - lunchflow_items/{_api_error,_setup_required}.html.erb — two modal alert headers whose flex+icon scaffolding now collapses onto DS::Alert. The surrounding bg-surface 'Common issues' / 'Setup steps' info cards stay as-is; this PR only touches the alert shape itself. No functional or behavioural changes. Locale keys preserved. amber-* palette uses on the alerts disappear; remaining bg-amber-* hits in the codebase live outside the alert pattern and stay for follow-up sub-PRs of #1715. Refs #1715
This commit is contained in:
committed by
GitHub
parent
712d6baca9
commit
57d71cd55e
@@ -12,6 +12,7 @@
|
||||
--color-success: var(--color-green-600);
|
||||
--color-warning: var(--color-yellow-600);
|
||||
--color-destructive: var(--color-red-600);
|
||||
--color-info: var(--color-blue-600);
|
||||
--color-shadow: --alpha(var(--color-black) / 6%);
|
||||
--color-gray-25: #FAFAFA;
|
||||
--color-gray-50: #F7F7F7;
|
||||
@@ -199,6 +200,7 @@
|
||||
--color-success: var(--color-green-500);
|
||||
--color-warning: var(--color-yellow-400);
|
||||
--color-destructive: var(--color-red-400);
|
||||
--color-info: var(--color-blue-500);
|
||||
--color-shadow: --alpha(var(--color-white) / 8%);
|
||||
--budget-unused-fill: var(--color-gray-500);
|
||||
--budget-unallocated-fill: var(--color-gray-700);
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
<div class="<%= container_classes %>">
|
||||
<%= helpers.icon icon_name, size: "sm", color: icon_color, class: "shrink-0" %>
|
||||
|
||||
<div class="flex-1 text-sm">
|
||||
<%= message %>
|
||||
<div class="flex-1 text-sm text-primary space-y-1">
|
||||
<% if title.present? %>
|
||||
<p class="font-medium text-primary"><%= title %></p>
|
||||
<% end %>
|
||||
|
||||
<% if content.present? %>
|
||||
<%= content %>
|
||||
<% elsif message.present? %>
|
||||
<%= message %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
class DS::Alert < DesignSystemComponent
|
||||
def initialize(message:, variant: :info)
|
||||
VARIANTS = %i[info success warning error destructive].freeze
|
||||
|
||||
def initialize(message: nil, title: nil, variant: :info)
|
||||
@message = message
|
||||
@title = title
|
||||
@variant = variant
|
||||
end
|
||||
|
||||
private
|
||||
attr_reader :message, :variant
|
||||
attr_reader :message, :title, :variant
|
||||
|
||||
def container_classes
|
||||
base_classes = "flex items-start gap-3 p-4 rounded-lg border"
|
||||
|
||||
variant_classes = case variant
|
||||
when :info
|
||||
"bg-blue-50 text-blue-700 border-blue-200 theme-dark:bg-blue-900/20 theme-dark:text-blue-400 theme-dark:border-blue-800"
|
||||
"bg-info/10 border-info/20"
|
||||
when :success
|
||||
"bg-green-50 text-green-700 border-green-200 theme-dark:bg-green-900/20 theme-dark:text-green-400 theme-dark:border-green-800"
|
||||
"bg-success/10 border-success/20"
|
||||
when :warning
|
||||
"bg-yellow-50 text-yellow-700 border-yellow-200 theme-dark:bg-yellow-900/20 theme-dark:text-yellow-400 theme-dark:border-yellow-800"
|
||||
"bg-warning/10 border-warning/20"
|
||||
when :error, :destructive
|
||||
"bg-red-50 text-red-700 border-red-200 theme-dark:bg-red-900/20 theme-dark:text-red-400 theme-dark:border-red-800"
|
||||
"bg-destructive/10 border-destructive/20"
|
||||
end
|
||||
|
||||
"#{base_classes} #{variant_classes}"
|
||||
@@ -46,7 +49,7 @@ class DS::Alert < DesignSystemComponent
|
||||
when :error, :destructive
|
||||
"destructive"
|
||||
else
|
||||
"blue-600"
|
||||
"info"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,7 +17,7 @@ module ApplicationHelper
|
||||
def icon(key, size: "md", color: "default", custom: false, as_button: false, **opts)
|
||||
extra_classes = opts.delete(:class)
|
||||
sizes = { xs: "w-3 h-3", sm: "w-4 h-4", md: "w-5 h-5", lg: "w-6 h-6", xl: "w-7 h-7", "2xl": "w-8 h-8" }
|
||||
colors = { default: "text-secondary", white: "text-inverse", success: "text-success", warning: "text-warning", destructive: "text-destructive", current: "text-current" }
|
||||
colors = { default: "text-secondary", white: "text-inverse", success: "text-success", warning: "text-warning", destructive: "text-destructive", info: "text-info", current: "text-current" }
|
||||
|
||||
icon_classes = class_names(
|
||||
"shrink-0",
|
||||
|
||||
@@ -4,13 +4,11 @@
|
||||
<% dialog.with_header(title: "Lunch Flow Connection Error") %>
|
||||
<% dialog.with_body do %>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<%= icon("alert-circle", class: "text-destructive w-5 h-5 shrink-0 mt-0.5") %>
|
||||
<div class="text-sm">
|
||||
<p class="font-medium text-primary mb-2">Unable to connect to Lunch Flow</p>
|
||||
<p class="text-secondary"><%= error_message %></p>
|
||||
</div>
|
||||
</div>
|
||||
<%= render DS::Alert.new(
|
||||
title: "Unable to connect to Lunch Flow",
|
||||
message: error_message,
|
||||
variant: :error
|
||||
) %>
|
||||
|
||||
<div class="bg-surface rounded-lg p-4 space-y-2 text-sm">
|
||||
<p class="font-medium text-primary">Common Issues:</p>
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
<% dialog.with_header(title: "Lunch Flow Setup Required") %>
|
||||
<% dialog.with_body do %>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<%= icon("alert-circle", class: "text-warning w-5 h-5 shrink-0 mt-0.5") %>
|
||||
<div class="text-sm text-secondary">
|
||||
<p class="font-medium text-primary mb-2">API Key Not Configured</p>
|
||||
<p>Before you can link Lunch Flow accounts, you need to configure your Lunch Flow API key.</p>
|
||||
</div>
|
||||
</div>
|
||||
<%= render DS::Alert.new(
|
||||
title: "API Key Not Configured",
|
||||
message: "Before you can link Lunch Flow accounts, you need to configure your Lunch Flow API key.",
|
||||
variant: :warning
|
||||
) %>
|
||||
|
||||
<div class="bg-surface rounded-lg p-4 space-y-2 text-sm">
|
||||
<p class="font-medium text-primary">Setup Steps:</p>
|
||||
|
||||
@@ -62,18 +62,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-warning-50 border border-warning-200 rounded-xl p-4">
|
||||
<div class="flex items-start gap-2">
|
||||
<%= icon("alert-triangle", class: "w-5 h-5 text-warning-600 mt-0.5") %>
|
||||
<div>
|
||||
<h4 class="font-medium text-warning-800 text-sm">Important Security Note</h4>
|
||||
<p class="text-warning-700 text-sm mt-1">
|
||||
This is the only time your API key will be displayed. Make sure to copy it now and store it securely.
|
||||
If you lose this key, you'll need to generate a new one.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%= render DS::Alert.new(title: "Important Security Note", variant: :warning) do %>
|
||||
<p>
|
||||
This is the only time your API key will be displayed. Make sure to copy it now and store it securely.
|
||||
If you lose this key, you'll need to generate a new one.
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<div class="bg-surface-inset rounded-xl p-4">
|
||||
<h4 class="font-medium text-primary mb-3">How to use your API key</h4>
|
||||
|
||||
@@ -67,18 +67,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-warning-50 border border-warning-200 rounded-xl p-4">
|
||||
<div class="flex items-start gap-2">
|
||||
<%= icon("alert-triangle", class: "w-5 h-5 text-warning-600 mt-0.5") %>
|
||||
<div>
|
||||
<h4 class="font-medium text-warning-800 text-sm">Important Security Note</h4>
|
||||
<p class="text-warning-700 text-sm mt-1">
|
||||
This is the only time your API key will be displayed. Make sure to copy it now and store it securely.
|
||||
If you lose this key, you'll need to generate a new one.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%= render DS::Alert.new(title: "Important Security Note", variant: :warning) do %>
|
||||
<p>
|
||||
This is the only time your API key will be displayed. Make sure to copy it now and store it securely.
|
||||
If you lose this key, you'll need to generate a new one.
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<div class="bg-surface-inset rounded-xl p-4">
|
||||
<h4 class="font-medium text-primary mb-3">How to use your API key</h4>
|
||||
|
||||
@@ -30,18 +30,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-warning-50 border border-warning-200 rounded-xl p-4">
|
||||
<div class="flex items-start gap-2">
|
||||
<%= icon("alert-triangle", class: "w-5 h-5 text-warning-600 mt-0.5") %>
|
||||
<div>
|
||||
<h4 class="font-medium text-warning-800 text-sm">Security Warning</h4>
|
||||
<p class="text-warning-700 text-sm mt-1">
|
||||
Your API key will be displayed only once after creation. Make sure to copy and store it securely.
|
||||
Anyone with access to this key can access your data according to the permissions you select.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%= render DS::Alert.new(title: "Security Warning", variant: :warning) do %>
|
||||
<p>
|
||||
Your API key will be displayed only once after creation. Make sure to copy and store it securely.
|
||||
Anyone with access to this key can access your data according to the permissions you select.
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<div class="flex justify-end gap-3 pt-4 border-t border-primary">
|
||||
<%= render DS::Link.new(
|
||||
|
||||
@@ -34,13 +34,8 @@
|
||||
data: { "auto-submit-form-target": "auto" } %>
|
||||
<% end %>
|
||||
|
||||
<div class="bg-amber-50 border border-amber-200 rounded-lg p-3">
|
||||
<div class="flex items-start gap-2">
|
||||
<%= icon("alert-triangle", size: "sm", class: "text-amber-600 mt-0.5") %>
|
||||
<div class="text-sm text-amber-700">
|
||||
<p><%= t(".rate_limit_warning") %></p>
|
||||
<p class="mt-1"><%= t(".no_health_check_note") %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%= render DS::Alert.new(variant: :warning) do %>
|
||||
<p><%= t(".rate_limit_warning") %></p>
|
||||
<p><%= t(".no_health_check_note") %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -35,12 +35,5 @@
|
||||
data: { "auto-submit-form-target": "auto" } %>
|
||||
<% end %>
|
||||
|
||||
<div class="bg-amber-50 border border-amber-200 rounded-lg p-3">
|
||||
<div class="flex items-start gap-2">
|
||||
<%= icon("alert-triangle", size: "sm", class: "text-amber-600 mt-0.5") %>
|
||||
<p class="text-sm text-amber-700">
|
||||
<%= t(".rate_limit_warning") %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<%= render DS::Alert.new(message: t(".rate_limit_warning"), variant: :warning) %>
|
||||
</div>
|
||||
|
||||
@@ -71,13 +71,6 @@
|
||||
</div>
|
||||
|
||||
<% if ENV["EXCHANGE_RATE_PROVIDER"].present? || ENV["SECURITIES_PROVIDERS"].present? || ENV["SECURITIES_PROVIDER"].present? %>
|
||||
<div class="bg-amber-50 border border-amber-200 rounded-lg p-3">
|
||||
<div class="flex items-start gap-2">
|
||||
<%= icon("alert-triangle", size: "sm", class: "text-amber-600 mt-0.5 shrink-0") %>
|
||||
<p class="text-sm text-amber-700">
|
||||
<%= t(".env_configured_message") %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<%= render DS::Alert.new(message: t(".env_configured_message"), variant: :warning) %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -57,30 +57,26 @@
|
||||
</div>
|
||||
|
||||
<% if @plan_restricted_securities.present? %>
|
||||
<div class="bg-amber-50 border border-amber-200 rounded-lg p-3 mt-4">
|
||||
<div class="flex items-start gap-2">
|
||||
<%= icon("alert-triangle", size: "sm", class: "text-amber-600 mt-0.5") %>
|
||||
<div class="text-sm">
|
||||
<p class="font-medium text-amber-800"><%= t(".plan_upgrade_warning_title") %></p>
|
||||
<p class="text-amber-700 mt-1"><%= t(".plan_upgrade_warning_description") %></p>
|
||||
<ul class="mt-2 space-y-1">
|
||||
<% @plan_restricted_securities.each do |security| %>
|
||||
<li class="text-amber-700">
|
||||
<span class="font-medium"><%= security[:ticker] %></span>
|
||||
<% if security[:name].present? %>
|
||||
<span class="text-amber-600">(<%= security[:name] %>)</span>
|
||||
<% end %>
|
||||
<span class="text-amber-600">— <%= t(".requires_plan", plan: security[:required_plan]) %></span>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<p class="mt-2">
|
||||
<a href="https://twelvedata.com/pricing" target="_blank" rel="noopener noreferrer" class="text-amber-800 underline font-medium">
|
||||
<%= t(".view_pricing") %>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<%= render DS::Alert.new(title: t(".plan_upgrade_warning_title"), variant: :warning) do %>
|
||||
<p><%= t(".plan_upgrade_warning_description") %></p>
|
||||
<ul class="space-y-1">
|
||||
<% @plan_restricted_securities.each do |security| %>
|
||||
<li>
|
||||
<span class="font-medium text-primary"><%= security[:ticker] %></span>
|
||||
<% if security[:name].present? %>
|
||||
<span class="text-secondary">(<%= security[:name] %>)</span>
|
||||
<% end %>
|
||||
<span class="text-secondary">— <%= t(".requires_plan", plan: security[:required_plan]) %></span>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<p>
|
||||
<a href="https://twelvedata.com/pricing" target="_blank" rel="noopener noreferrer" class="text-link underline font-medium">
|
||||
<%= t(".view_pricing") %>
|
||||
</a>
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -20,15 +20,11 @@
|
||||
<%= t(".status_inactive") %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="bg-amber-50 border border-amber-200 rounded-lg p-3">
|
||||
<div class="flex items-start gap-2">
|
||||
<%= icon("alert-triangle", size: "sm", class: "text-amber-600 mt-0.5 shrink-0") %>
|
||||
<div>
|
||||
<h3 class="text-sm font-medium text-amber-700"><%= t(".connection_failed") %></h3>
|
||||
<p class="text-sm text-amber-700 mt-1"><%= t(".troubleshooting") %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%= render DS::Alert.new(
|
||||
title: t(".connection_failed"),
|
||||
message: t(".troubleshooting"),
|
||||
variant: :warning
|
||||
) %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"success": { "$value": "{color.green.600}", "$type": "color", "$extensions": { "sure.dark": "{color.green.500}" } },
|
||||
"warning": { "$value": "{color.yellow.600}", "$type": "color", "$extensions": { "sure.dark": "{color.yellow.400}" } },
|
||||
"destructive": { "$value": "{color.red.600}", "$type": "color", "$extensions": { "sure.dark": "{color.red.400}" } },
|
||||
"info": { "$value": "{color.blue.600}", "$type": "color", "$extensions": { "sure.dark": "{color.blue.500}" } },
|
||||
"shadow": { "$value": "{color.black|6%}", "$type": "color", "$extensions": { "sure.dark": "{color.white|8%}" } },
|
||||
|
||||
"gray": {
|
||||
|
||||
@@ -1,7 +1,34 @@
|
||||
class AlertComponentPreview < Lookbook::Preview
|
||||
# @param message text
|
||||
# @param title text
|
||||
# @param variant select [info, success, warning, error]
|
||||
def default(message: "This is an alert message.", variant: :info)
|
||||
render DS::Alert.new(message: message, variant: variant.to_sym)
|
||||
def default(message: "This is an alert message.", title: nil, variant: :info)
|
||||
render DS::Alert.new(message: message, title: title.presence, variant: variant.to_sym)
|
||||
end
|
||||
|
||||
# @param variant select [info, success, warning, error]
|
||||
def with_title(variant: :warning)
|
||||
render DS::Alert.new(
|
||||
message: "Heads up — this account hasn't synced in 7 days.",
|
||||
title: "Stale connection",
|
||||
variant: variant.to_sym
|
||||
)
|
||||
end
|
||||
|
||||
# @param variant select [info, success, warning, error]
|
||||
def with_body_slot(variant: :error)
|
||||
render DS::Alert.new(title: "We couldn't process this request", variant: variant.to_sym) do
|
||||
tag.div do
|
||||
safe_join([
|
||||
tag.p("Verify the values you submitted and try again. If the issue persists, contact support.", class: "text-secondary"),
|
||||
tag.ul(class: "list-disc list-inside text-secondary") do
|
||||
safe_join([
|
||||
tag.li("Check that all required fields are populated."),
|
||||
tag.li("Confirm the dates fall within an open period.")
|
||||
])
|
||||
end
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user