mirror of
https://github.com/we-promise/sure.git
synced 2026-05-12 15:15:01 +00:00
fix(settings/providers): drop colour palette + filter polish + drawer warnings
Round of design-feedback fixes. Provider chips - Drop the per-provider raw Tailwind palette (bg-blue-600 etc.) from Provider::Metadata. All cards + drawer logo lock-up now use bg-surface-inset + text-primary, matching the design's §04 "drop colour entirely" recommendation. Solves the long-standing §01 BLOCKER without externalising brand assets. Re-introducing logos later just means an optional logo_svg: field on metadata. - ProviderCard component drops the `logo_bg:` parameter; the chip is now styled in the template. Filter / search - "Available · N" count and the empty-filter state now update client-side as the chip filter and free-text search narrow the grid (new `count` Stimulus target + dedicated update path). - Empty-filter state now offers a Clear filters button that resets both the search input and the active chip in one click. - Search placeholder drops the drifting "Search 9 providers" count for plain "Search providers" — the section heading carries the number. - Chip labels normalised to plural where natural: "Banks · Crypto · Investments" (Crypto stays as the mass noun). Drawer copy / treatment - "IP Whitelisting Required" → "IP whitelisting required" (DS sentence-case). - Binance "do NOT enable withdrawal permissions" lifted out of inline red-text into a proper bg-warning-50 border-warning-200 alert block with an alert-triangle icon. Matches the api_keys / hosting alert pattern. - SnapTrade free-tier inline alert-triangle now uses `size: "sm"` so the icon stops rendering at 20px next to 14px body text. Spacing - Group-heading margin top bumped 5 → 6 (20→24px) so the eyebrow has more breathing room above the search bar.
This commit is contained in:
committed by
Guillem Arias
parent
8c961958b4
commit
6abceb07ff
@@ -2,8 +2,8 @@
|
||||
class: "bg-container shadow-border-xs rounded-xl p-4 flex flex-col gap-2.5 text-primary hover:bg-container-hover transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-alpha-black-100",
|
||||
data: { turbo_frame: "drawer", turbo_prefetch: "false" }.merge(filter_data) do %>
|
||||
<div class="flex items-start gap-2.5">
|
||||
<div class="w-9 h-9 rounded-lg flex items-center justify-center shrink-0 <%= logo_bg %>">
|
||||
<span class="text-xs font-bold text-inverse"><%= logo_text %></span>
|
||||
<div class="w-9 h-9 rounded-lg flex items-center justify-center shrink-0 bg-surface-inset">
|
||||
<span class="text-xs font-bold text-primary"><%= logo_text %></span>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
|
||||
@@ -10,7 +10,7 @@ class Settings::ProviderCard < ApplicationComponent
|
||||
end
|
||||
|
||||
def initialize(provider_key:, name:, tagline: nil, region: nil, kind: nil, tier: nil,
|
||||
maturity: :stable, logo_bg: "bg-gray-500", logo_text: nil)
|
||||
maturity: :stable, logo_text: nil)
|
||||
@provider_key = provider_key
|
||||
@name = name
|
||||
@tagline = tagline
|
||||
@@ -18,11 +18,10 @@ class Settings::ProviderCard < ApplicationComponent
|
||||
@kind = kind
|
||||
@tier = tier
|
||||
@maturity = maturity.to_sym
|
||||
@logo_bg = logo_bg
|
||||
@logo_text = logo_text || name.first(2).upcase
|
||||
end
|
||||
|
||||
attr_reader :name, :tagline, :logo_bg, :logo_text
|
||||
attr_reader :name, :tagline, :logo_text
|
||||
|
||||
def maturity_label
|
||||
self.class.maturity_label(@maturity)
|
||||
|
||||
@@ -2,8 +2,10 @@ import { Controller } from "@hotwired/stimulus";
|
||||
|
||||
// Connects to data-controller="providers-filter"
|
||||
// Filters provider cards by free-text query and a chip-selected kind.
|
||||
// Updates the visible-count target on the section heading and toggles
|
||||
// an empty-state target when no card matches.
|
||||
export default class extends Controller {
|
||||
static targets = ["input", "chip", "card", "empty"];
|
||||
static targets = ["input", "chip", "card", "empty", "count"];
|
||||
static values = { kind: { type: String, default: "all" } };
|
||||
|
||||
connect() {
|
||||
@@ -29,6 +31,10 @@ export default class extends Controller {
|
||||
if (visible) visibleCount++;
|
||||
});
|
||||
|
||||
if (this.hasCountTarget) {
|
||||
this.countTarget.textContent = visibleCount;
|
||||
}
|
||||
|
||||
if (this.hasEmptyTarget) {
|
||||
this.emptyTarget.classList.toggle("hidden", visibleCount > 0);
|
||||
}
|
||||
@@ -40,6 +46,14 @@ export default class extends Controller {
|
||||
this.filter();
|
||||
}
|
||||
|
||||
clear() {
|
||||
if (this.hasInputTarget) this.inputTarget.value = "";
|
||||
this.kindValue = "all";
|
||||
this.syncChipState();
|
||||
this.filter();
|
||||
if (this.hasInputTarget) this.inputTarget.focus();
|
||||
}
|
||||
|
||||
syncChipState() {
|
||||
if (!this.hasChipTarget) return;
|
||||
this.chipTargets.forEach((chip) => {
|
||||
|
||||
@@ -1,97 +1,22 @@
|
||||
class Provider
|
||||
module Metadata
|
||||
REGISTRY = {
|
||||
simplefin: {
|
||||
region: "US",
|
||||
kind: "Bank",
|
||||
maturity: :stable,
|
||||
logo_bg: "bg-blue-600",
|
||||
logo_text: "SF"
|
||||
},
|
||||
lunchflow: {
|
||||
region: "US",
|
||||
kind: "Bank",
|
||||
maturity: :stable,
|
||||
logo_bg: "bg-orange-500",
|
||||
logo_text: "LF"
|
||||
},
|
||||
enable_banking: {
|
||||
region: "EU",
|
||||
kind: "Bank",
|
||||
maturity: :beta,
|
||||
logo_bg: "bg-purple-600",
|
||||
logo_text: "EB"
|
||||
},
|
||||
coinstats: {
|
||||
region: "Global",
|
||||
kind: "Crypto",
|
||||
maturity: :beta,
|
||||
logo_bg: "bg-yellow-500",
|
||||
logo_text: "CS"
|
||||
},
|
||||
mercury: {
|
||||
region: "US",
|
||||
kind: "Bank",
|
||||
maturity: :beta,
|
||||
logo_bg: "bg-cyan-600",
|
||||
logo_text: "ME"
|
||||
},
|
||||
coinbase: {
|
||||
region: "Global",
|
||||
kind: "Crypto",
|
||||
maturity: :beta,
|
||||
logo_bg: "bg-blue-500",
|
||||
logo_text: "CB"
|
||||
},
|
||||
binance: {
|
||||
region: "Global",
|
||||
kind: "Crypto",
|
||||
maturity: :beta,
|
||||
logo_bg: "bg-yellow-400",
|
||||
logo_text: "BI"
|
||||
},
|
||||
snaptrade: {
|
||||
region: "US / CA",
|
||||
kind: "Investment",
|
||||
maturity: :beta,
|
||||
logo_bg: "bg-green-600",
|
||||
logo_text: "ST"
|
||||
},
|
||||
indexa_capital: {
|
||||
region: "ES",
|
||||
kind: "Investment",
|
||||
maturity: :alpha,
|
||||
logo_bg: "bg-red-600",
|
||||
logo_text: "IC"
|
||||
},
|
||||
sophtron: {
|
||||
region: "US",
|
||||
kind: "Bank",
|
||||
maturity: :alpha,
|
||||
logo_bg: "bg-teal-600",
|
||||
logo_text: "SO"
|
||||
},
|
||||
plaid: {
|
||||
region: "US",
|
||||
kind: "Bank",
|
||||
tier: "Paid",
|
||||
maturity: :stable,
|
||||
logo_bg: "bg-indigo-600",
|
||||
logo_text: "PL"
|
||||
},
|
||||
plaid_eu: {
|
||||
name: "Plaid EU",
|
||||
region: "EU",
|
||||
kind: "Bank",
|
||||
tier: "Paid",
|
||||
maturity: :stable,
|
||||
logo_bg: "bg-indigo-600",
|
||||
logo_text: "PL"
|
||||
}
|
||||
simplefin: { region: "US", kind: "Bank", maturity: :stable, logo_text: "SF" },
|
||||
lunchflow: { region: "US", kind: "Bank", maturity: :stable, logo_text: "LF" },
|
||||
enable_banking: { region: "EU", kind: "Bank", maturity: :beta, logo_text: "EB" },
|
||||
coinstats: { region: "Global", kind: "Crypto", maturity: :beta, logo_text: "CS" },
|
||||
mercury: { region: "US", kind: "Bank", maturity: :beta, logo_text: "ME" },
|
||||
coinbase: { region: "Global", kind: "Crypto", maturity: :beta, logo_text: "CB" },
|
||||
binance: { region: "Global", kind: "Crypto", maturity: :beta, logo_text: "BI" },
|
||||
snaptrade: { region: "US / CA", kind: "Investment", maturity: :beta, logo_text: "ST" },
|
||||
indexa_capital: { region: "ES", kind: "Investment", maturity: :alpha, logo_text: "IC" },
|
||||
sophtron: { region: "US", kind: "Bank", maturity: :alpha, logo_text: "SO" },
|
||||
plaid: { region: "US", kind: "Bank", tier: "Paid", maturity: :stable, logo_text: "PL" },
|
||||
plaid_eu: { name: "Plaid EU", region: "EU", kind: "Bank", tier: "Paid", maturity: :stable, logo_text: "PL" }
|
||||
}.freeze
|
||||
|
||||
def self.for(provider_key)
|
||||
REGISTRY[provider_key.to_sym] || { logo_text: provider_key.to_s.first(2).upcase, logo_bg: "bg-gray-500" }
|
||||
REGISTRY[provider_key.to_sym] || { logo_text: provider_key.to_s.first(2).upcase }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,11 @@
|
||||
<li><%= t("settings.providers.binance_panel.step2") %></li>
|
||||
<li><%= t("settings.providers.binance_panel.step3") %></li>
|
||||
</ol>
|
||||
<p class="text-destructive text-xs font-medium"><%= t("settings.providers.binance_panel.no_withdraw_warning") %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-warning-50 border border-warning-200 rounded-xl p-3 flex items-start gap-2">
|
||||
<%= icon "alert-triangle", size: "sm", class: "!w-3.5 !h-3.5 text-warning-600 shrink-0 mt-0.5" %>
|
||||
<p class="text-sm text-warning-700 font-medium"><%= t("settings.providers.binance_panel.no_withdraw_warning") %></p>
|
||||
</div>
|
||||
|
||||
<div class="bg-surface border border-primary p-3 rounded-lg text-sm">
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<% maturity_label = meta ? Settings::ProviderCard.maturity_label(meta[:maturity]) : nil %>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<% if meta && meta[:logo_bg].present? %>
|
||||
<span class="w-7 h-7 rounded-lg flex items-center justify-center shrink-0 <%= meta[:logo_bg] %>">
|
||||
<span class="text-xs font-bold text-inverse"><%= meta[:logo_text] %></span>
|
||||
<% if meta && meta[:logo_text].present? %>
|
||||
<span class="w-7 h-7 rounded-lg flex items-center justify-center shrink-0 bg-surface-inset">
|
||||
<span class="text-xs font-bold text-primary"><%= meta[:logo_text] %></span>
|
||||
</span>
|
||||
<% end %>
|
||||
<h2 class="text-lg font-medium text-primary truncate"><%= title %></h2>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<%# locals: (title:, count: nil, description: nil, anchor: nil) %>
|
||||
<%= tag.div id: anchor.presence, class: "flex items-baseline justify-between gap-3 mt-5 mb-1.5 px-1" do %>
|
||||
<%= tag.div id: anchor.presence, class: "flex items-baseline justify-between gap-3 mt-6 mb-1.5 px-1" do %>
|
||||
<h2 class="text-xs font-medium text-secondary uppercase flex items-baseline gap-2">
|
||||
<%= title %>
|
||||
<% if count %>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
<%# locals: (count:) %>
|
||||
<div class="flex flex-wrap items-center gap-2.5 mt-5 mb-3">
|
||||
<div class="relative flex-1 min-w-[200px]">
|
||||
<input type="search"
|
||||
@@ -6,7 +5,7 @@
|
||||
data-providers-filter-target="input"
|
||||
data-action="input->providers-filter#filter"
|
||||
aria-label="<%= t("settings.providers.search_filters.aria_label") %>"
|
||||
placeholder="<%= t("settings.providers.search_filters.placeholder", count: count) %>"
|
||||
placeholder="<%= t("settings.providers.search_filters.placeholder") %>"
|
||||
class="block w-full border border-secondary rounded-md py-2.5 pl-10 pr-3 bg-container focus:ring-gray-500 sm:text-sm">
|
||||
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||
<%= icon "search", class: "text-secondary" %>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<li><%= t("providers.snaptrade.step_4") %></li>
|
||||
</ol>
|
||||
|
||||
<p class="text-warning text-sm"><%= icon("alert-triangle", class: "inline-block w-4 h-4 mr-1") %><%= t("providers.snaptrade.free_tier_warning") %></p>
|
||||
<p class="text-warning text-sm"><%= icon("alert-triangle", size: "sm", class: "inline-block mr-1") %><%= t("providers.snaptrade.free_tier_warning") %></p>
|
||||
</div>
|
||||
|
||||
<% error_msg = local_assigns[:error_message] || @error_message %>
|
||||
|
||||
@@ -46,16 +46,23 @@
|
||||
|
||||
<% if @available.any? %>
|
||||
<div data-controller="providers-filter">
|
||||
<%= render "settings/providers/search_filters", count: @available.size %>
|
||||
<%= render "settings/providers/search_filters" %>
|
||||
|
||||
<%= render "settings/providers/group_heading",
|
||||
title: t("settings.providers.groups.available"),
|
||||
count: @available.size,
|
||||
anchor: "available" %>
|
||||
<div id="available" class="flex items-baseline justify-between gap-3 mt-6 mb-1.5 px-1">
|
||||
<h2 class="text-xs font-medium text-secondary uppercase flex items-baseline gap-2">
|
||||
<%= t("settings.providers.groups.available") %>
|
||||
<span class="text-subdued font-normal normal-case tabular-nums">
|
||||
· <span data-providers-filter-target="count"><%= @available.size %></span>
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<p data-providers-filter-target="empty" class="hidden text-sm text-secondary px-1 py-2">
|
||||
<%= t("settings.providers.empty_filter") %>
|
||||
</p>
|
||||
<div data-providers-filter-target="empty" class="hidden text-sm text-secondary px-1 py-2 flex items-center gap-2">
|
||||
<span><%= t("settings.providers.empty_filter") %></span>
|
||||
<button type="button" data-action="click->providers-filter#clear" class="text-primary underline">
|
||||
<%= t("settings.providers.clear_filter") %>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<% @available.each do |entry| %>
|
||||
@@ -68,7 +75,6 @@
|
||||
kind: meta[:kind],
|
||||
tier: meta[:tier],
|
||||
maturity: meta[:maturity] || :stable,
|
||||
logo_bg: meta[:logo_bg],
|
||||
logo_text: meta[:logo_text]
|
||||
) %>
|
||||
<% end %>
|
||||
|
||||
@@ -234,15 +234,14 @@ en:
|
||||
plaid_eu: Connect European financial institutions via Plaid (PSD2 / Open Banking).
|
||||
search_filters:
|
||||
aria_label: Search providers
|
||||
placeholder:
|
||||
one: "Search %{count} provider"
|
||||
other: "Search %{count} providers"
|
||||
placeholder: Search providers
|
||||
chips:
|
||||
all: All
|
||||
bank: Banks
|
||||
crypto: Crypto
|
||||
investment: Investment
|
||||
investment: Investments
|
||||
empty_filter: No providers match your filter.
|
||||
clear_filter: Clear filters
|
||||
encryption_error:
|
||||
title: Encryption Configuration Required
|
||||
message: Active Record encryption keys are not configured. Please ensure the encryption credentials (active_record_encryption.primary_key, active_record_encryption.deterministic_key, and active_record_encryption.key_derivation_salt) are properly set up in your Rails credentials or environment variables before using sync providers.
|
||||
@@ -265,7 +264,7 @@ en:
|
||||
step2: "Create a new API key with Enable Reading permission only"
|
||||
step3: "Paste your API Key and Secret below"
|
||||
no_withdraw_warning: "Warning: do NOT enable withdrawal permissions"
|
||||
ip_hint_title: "IP Whitelisting Required"
|
||||
ip_hint_title: "IP whitelisting required"
|
||||
ip_hint_body: "Add the app server's egress IP to the Binance API Key whitelist:"
|
||||
ip_hint_contact_admin: "Contact your administrator to obtain the app server's egress IP address."
|
||||
api_key_label: API Key
|
||||
|
||||
Reference in New Issue
Block a user