mirror of
https://github.com/we-promise/sure.git
synced 2026-09-09 00:24:15 +00:00
* fix(ds): resolve remaining DS Drift Patrol findings from #2157 Migrate leftover hand-rolled UI and i18n defaults to DS primitives and locale entries so missing keys raise in development again. Closes #2157 Co-authored-by: Cursor <cursoragent@cursor.com> * fix(a11y): name PDF import account select via aria-labelledby Wire the existing localized heading to the select so label: false does not leave the control unlabeled for assistive tech. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(akahu): use scoped form.select for account type fields Replace the unusual bracketed method name on a scope-less builder with scope: :account_types and form.select(account.id), and assert the label for= matches the generated select id. Co-authored-by: Cursor <cursoragent@cursor.com> * Add German translation for shared.dot_separator Keeps I18nTest German coverage green after merging main's completed de locale. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ds): resolve remaining DS Drift Patrol findings from #2157 Migrate leftover hand-rolled UI and i18n defaults to DS primitives and locale entries so missing keys raise in development again. Closes #2157 Co-authored-by: Cursor <cursoragent@cursor.com> * fix(a11y): name PDF import account select via aria-labelledby Wire the existing localized heading to the select so label: false does not leave the control unlabeled for assistive tech. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(akahu): use scoped form.select for account type fields Replace the unusual bracketed method name on a scope-less builder with scope: :account_types and form.select(account.id), and assert the label for= matches the generated select id. Co-authored-by: Cursor <cursoragent@cursor.com> * Add German translation for shared.dot_separator Keeps I18nTest German coverage green after merging main's completed de locale. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ds): address tag select review feedback Use the canonical focus-ring-within wrapper and preserve full width for embedded tag search. Add the shared separator key to every shipped locale and document the intentional unknown PDF document-type fallback. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
119 lines
4.8 KiB
ERB
119 lines
4.8 KiB
ERB
<% if @prefill_demo_credentials %>
|
|
<div class="mb-4">
|
|
<%= render DS::Alert.new(
|
|
variant: :info,
|
|
title: t(".demo_banner_title"),
|
|
message: t(".demo_banner_message"),
|
|
live: :polite
|
|
) %>
|
|
</div>
|
|
<% end %>
|
|
|
|
<% passkey_login_enabled = AuthConfig.passkey_login_enabled? %>
|
|
|
|
<% if AuthConfig.local_login_form_visible? %>
|
|
<%= styled_form_with url: sessions_path, class: "space-y-4", data: { turbo: false } do |form| %>
|
|
<%#
|
|
The "webauthn" autocomplete token is what lets browsers offer a saved
|
|
passkey from this field's autofill menu (conditional mediation). It is
|
|
only added when passwordless sign-in is enabled, so the menu never
|
|
advertises a path the server would reject.
|
|
%>
|
|
<%= form.email_field :email,
|
|
label: t(".email"),
|
|
autofocus: false,
|
|
autocomplete: passkey_login_enabled ? "username webauthn" : "email",
|
|
required: "required",
|
|
placeholder: t(".email_placeholder"),
|
|
value: @email %>
|
|
|
|
<%= form.password_field :password,
|
|
label: t(".password"),
|
|
required: "required",
|
|
placeholder: t(".password_placeholder"),
|
|
value: @password %>
|
|
|
|
<%= form.submit t(".submit") %>
|
|
<% end %>
|
|
|
|
<% unless AuthConfig.local_login_enabled? %>
|
|
<p class="mt-2 text-xs text-secondary text-center">
|
|
<%= t(".local_login_admin_only") %>
|
|
</p>
|
|
<% end %>
|
|
|
|
<% if AuthConfig.password_features_enabled? %>
|
|
<div class="mt-6 text-center">
|
|
<%= link_to t(".forgot_password"), new_password_reset_path, class: "font-medium text-sm text-primary hover:underline transition" %>
|
|
</div>
|
|
<% end %>
|
|
|
|
<%#
|
|
Last thing in the local-login branch, so it renders directly above the SSO
|
|
buttons: a passkey is an alternative to the credential form, not part of it.
|
|
|
|
It stays inside this branch on purpose. PasskeySessionsController#create
|
|
gates on AuthConfig.local_login_allowed_for?, so in SSO-only mode (local
|
|
login off, no admin override) every assertion is rejected. Hoisting this
|
|
out would render a button that always fails, and mount a Stimulus
|
|
controller that mints an unredeemable challenge on every page view.
|
|
%>
|
|
<% if passkey_login_enabled %>
|
|
<div class="mt-6 space-y-3"
|
|
data-controller="webauthn-authentication"
|
|
data-webauthn-authentication-conditional-value="true"
|
|
data-webauthn-authentication-options-url-value="<%= passkey_session_options_path %>"
|
|
data-webauthn-authentication-verify-url-value="<%= passkey_session_path %>"
|
|
data-webauthn-authentication-unsupported-message-value="<%= t(".passkey_unsupported") %>"
|
|
data-webauthn-authentication-error-fallback-value="<%= t("passkey_sessions.invalid_credential") %>">
|
|
<%= render DS::Button.new(
|
|
text: t(".passkey_button"),
|
|
variant: :outline,
|
|
size: :md,
|
|
full_width: true,
|
|
icon: "fingerprint",
|
|
type: "button",
|
|
class: "gap-2",
|
|
data: { action: "webauthn-authentication#authenticate" }
|
|
) %>
|
|
<p class="text-sm text-destructive" role="alert" aria-live="assertive" aria-atomic="true" aria-hidden="true" hidden data-webauthn-authentication-target="error"></p>
|
|
</div>
|
|
<% end %>
|
|
<% end %>
|
|
|
|
<% providers = AuthConfig.sso_providers %>
|
|
|
|
<% if providers.any? %>
|
|
<div class="mt-6 space-y-3">
|
|
<% providers.each do |provider| %>
|
|
<% provider_id = provider[:id].to_s %>
|
|
<% provider_name = provider[:name].to_s %>
|
|
<% is_google = provider_id == "google" || provider[:strategy].to_s == "google_oauth2" %>
|
|
<% default_label = is_google ? t(".google_auth_connect") : t(".#{provider_id}", default: provider[:name].to_s.titleize) %>
|
|
|
|
<%#
|
|
SSO buttons use the design-system outline button so they stay consistent
|
|
with each other and read as secondary to the primary email "Log in" CTA.
|
|
Google keeps its official multi-color "G" mark + "Sign in with Google"
|
|
wording (brand-compliant) via the custom-icon asset, so no bespoke CSS.
|
|
%>
|
|
<%= render DS::Button.new(
|
|
href: "/auth/#{provider_name}",
|
|
method: :post,
|
|
variant: :outline,
|
|
size: :md,
|
|
full_width: true,
|
|
icon: is_google ? "google-icon" : provider[:icon].presence,
|
|
icon_custom: is_google,
|
|
text: provider[:label].presence || default_label,
|
|
class: "gap-2",
|
|
form: { data: { turbo: false } }
|
|
) %>
|
|
<% end %>
|
|
</div>
|
|
<% elsif !AuthConfig.local_login_form_visible? %>
|
|
<div class="mt-6">
|
|
<%= render DS::Alert.new(message: t(".no_auth_methods_enabled"), variant: :warning) %>
|
|
</div>
|
|
<% end %>
|