Files
sure/app/views/sessions/new.html.erb
T
William Wei MingandCursor d2eab1c9c7 fix(ds): resolve remaining DS Drift Patrol findings (#2157) (#2977)
* 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>
2026-08-16 07:09:01 +02:00

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 %>