Improve mobile staff accessibility

This commit is contained in:
SimpleTest 2026-08-03 16:07:11 +03:00
parent 9f359da60a
commit 5be7b6186e
12 changed files with 87 additions and 39 deletions

View File

@ -1,6 +1,11 @@
import AxeBuilder from "@axe-core/playwright";
import { expect, test } from "@playwright/test";
import { newIsolatedContext, projectEmail, registerAndConfirm } from "./helpers";
import {
loginWithMagicLink,
newIsolatedContext,
projectEmail,
registerAndConfirm,
} from "./helpers";
const publicAccessibilityPaths = [
"/",
@ -154,3 +159,34 @@ test("authenticated core workflows remain accessible on a phone viewport", async
await user.context.close();
});
test("staff workspaces remain accessible without horizontal overflow on a phone viewport", async ({
browser,
request,
}) => {
const admin = await loginWithMagicLink(browser, request, "e2e-admin@example.invalid");
await admin.page.setViewportSize({ width: 390, height: 844 });
for (const path of [
"/admin",
"/admin/users",
"/support/operations",
"/moderation",
"/analytics",
"/admin/audit",
]) {
await admin.page.goto(path);
await expectAccessible(admin.page);
const dimensions = await admin.page.evaluate(() => ({
clientWidth: document.documentElement.clientWidth,
scrollWidth: document.documentElement.scrollWidth,
}));
expect(dimensions.scrollWidth, `${path} overflowed at 390px`).toBeLessThanOrEqual(
dimensions.clientWidth,
);
}
await admin.context.close();
});

View File

@ -188,7 +188,7 @@ defmodule WhoNeedHelpWeb.AdminAuditLive do
<div class="flex flex-wrap items-center justify-between gap-3 border-b border-base-300 p-4">
<div>
<h2 class="font-black">{gettext("Recorded events")}</h2>
<p class="text-sm text-base-content/55">
<p class="text-sm text-base-content/70">
{gettext(
"Actor, action, and target remain visible; technical metadata opens on demand."
)}
@ -209,14 +209,14 @@ defmodule WhoNeedHelpWeb.AdminAuditLive do
<time class="text-xs text-base-content/60">{format_datetime(event.inserted_at)}</time>
<div class="min-w-0">
<p class="truncate font-semibold">{actor_label(event)}</p>
<p :if={event.actor_id} class="truncate font-mono text-[0.7rem] text-base-content/45">
<p :if={event.actor_id} class="truncate font-mono text-[0.7rem] text-base-content/70">
{event.actor_id}
</p>
</div>
<code class="break-all text-xs font-bold text-primary">{event.action}</code>
<div class="min-w-0">
<p class="truncate text-sm font-semibold">{event.target_type}</p>
<p class="truncate font-mono text-[0.7rem] text-base-content/45">{event.target_id}</p>
<p class="truncate font-mono text-[0.7rem] text-base-content/70">{event.target_id}</p>
</div>
<span class="flex items-center gap-2 text-sm font-semibold text-base-content/65">
{gettext("Details")}
@ -246,6 +246,7 @@ defmodule WhoNeedHelpWeb.AdminAuditLive do
<button
type="button"
phx-click="previous-page"
aria-label={gettext("Previous")}
disabled={@cursor_history == []}
class="btn btn-sm btn-outline"
>
@ -256,6 +257,7 @@ defmodule WhoNeedHelpWeb.AdminAuditLive do
<button
type="button"
phx-click="next-page"
aria-label={gettext("Next")}
disabled={is_nil(@next_cursor)}
class="btn btn-sm btn-outline"
>

View File

@ -148,7 +148,7 @@ defmodule WhoNeedHelpWeb.AdminUserLive do
</span>
</div>
<p class="mt-1 break-all text-sm text-base-content/60">{@user.email}</p>
<p class="mt-2 font-mono text-xs text-base-content/45">{@user.id}</p>
<p class="mt-2 font-mono text-xs text-base-content/70">{@user.id}</p>
</div>
<div class="flex flex-wrap gap-1 sm:max-w-sm sm:justify-end">
<span :if={Accounts.loaded_staff_roles(@user) == []} class="badge badge-ghost badge-sm">
@ -241,7 +241,7 @@ defmodule WhoNeedHelpWeb.AdminUserLive do
/>
<span class="min-w-0">
<span class="block font-bold">{role_label(role)}</span>
<span class="block text-xs leading-5 text-base-content/55">
<span class="block text-xs leading-5 text-base-content/70">
{role_description(role)}
</span>
</span>

View File

@ -245,7 +245,7 @@ defmodule WhoNeedHelpWeb.AdminUsersLive do
<p class="max-w-80 truncate font-bold" title={account_name(user)}>
{account_name(user)}
</p>
<p class="max-w-80 truncate text-xs text-base-content/55" title={user.email}>
<p class="max-w-80 truncate text-xs text-base-content/70" title={user.email}>
{user.email}
</p>
</td>
@ -293,7 +293,7 @@ defmodule WhoNeedHelpWeb.AdminUsersLive do
<div class="flex items-start justify-between gap-3">
<div class="min-w-0">
<p class="truncate font-bold">{account_name(user)}</p>
<p class="truncate text-sm text-base-content/55">{user.email}</p>
<p class="truncate text-sm text-base-content/70">{user.email}</p>
</div>
<span class={["badge badge-sm shrink-0", status_class(user.moderation_status)]}>
{status_label(user.moderation_status)}
@ -311,7 +311,7 @@ defmodule WhoNeedHelpWeb.AdminUsersLive do
</span>
</div>
<div class="mt-3 flex items-center justify-between gap-3">
<span class="text-xs text-base-content/55">
<span class="text-xs text-base-content/70">
{gettext("Joined %{date}", date: joined_on(user.inserted_at))}
</span>
<.link navigate={~p"/admin/users/#{user.id}"} class="btn btn-outline btn-sm">

View File

@ -248,7 +248,7 @@ defmodule WhoNeedHelpWeb.CategoryProposalLive do
else: gettext("Main category")}
</span>
<p class="mt-2 text-sm text-base-content/65">{proposal.reason}</p>
<p class="mt-3 text-xs text-base-content/45">
<p class="mt-3 text-xs text-base-content/70">
{gettext("by %{name}", name: proposal.proposer.display_name)}
</p>
</div>

View File

@ -671,7 +671,7 @@ defmodule WhoNeedHelpWeb.ModerationLive do
<summary class="grid cursor-pointer list-none gap-3 p-4 marker:hidden md:grid-cols-[minmax(0,1fr)_10rem_9rem_9rem_auto] md:items-center">
<div class="min-w-0">
<p class="truncate font-bold">{report.details}</p>
<p class="mt-1 truncate text-xs text-base-content/55">
<p class="mt-1 truncate text-xs text-base-content/70">
{report.reporter.display_name || report.reporter.email}
</p>
</div>
@ -847,7 +847,7 @@ defmodule WhoNeedHelpWeb.ModerationLive do
>
<summary class="grid cursor-pointer list-none gap-3 p-4 marker:hidden md:grid-cols-[minmax(0,1fr)_14rem_9rem_10rem_auto] md:items-center">
<div class="min-w-0">
<p class="truncate font-bold">{signal.subject.display_name || signal.subject.email}</p><p class="mt-1 truncate text-xs text-base-content/55">
<p class="truncate font-bold">{signal.subject.display_name || signal.subject.email}</p><p class="mt-1 truncate text-xs text-base-content/70">
{signal.subject.email}
</p>
</div>
@ -968,7 +968,7 @@ defmodule WhoNeedHelpWeb.ModerationLive do
>
<summary class="grid cursor-pointer list-none gap-3 p-4 marker:hidden md:grid-cols-[minmax(0,1fr)_9rem_9rem_9rem_auto] md:items-center">
<div class="min-w-0">
<p class="truncate font-bold">{proposal.proposed_name}</p><p class="mt-1 truncate text-xs text-base-content/55">
<p class="truncate font-bold">{proposal.proposed_name}</p><p class="mt-1 truncate text-xs text-base-content/70">
{proposal.reason}
</p>
</div>
@ -1119,6 +1119,7 @@ defmodule WhoNeedHelpWeb.ModerationLive do
<button
type="button"
phx-click="previous-page"
aria-label={gettext("Previous")}
disabled={@previous_disabled}
class="btn btn-sm btn-outline"
>
@ -1127,13 +1128,14 @@ defmodule WhoNeedHelpWeb.ModerationLive do
</button>
<div class="text-center text-sm">
<p class="font-bold">{gettext("Page %{page}", page: @page_number)}</p>
<p :if={@entries != []} class="text-xs text-base-content/55">
<p :if={@entries != []} class="text-xs text-base-content/70">
{gettext("Showing %{first}–%{last}", first: elem(@range, 0), last: elem(@range, 1))}
</p>
</div>
<button
type="button"
phx-click="next-page"
aria-label={gettext("Next")}
disabled={@next_disabled}
class="btn btn-sm btn-outline"
>

View File

@ -272,7 +272,7 @@ defmodule WhoNeedHelpWeb.NotificationLive do
</div>
<div :if={@notifications == []} class="mt-5 rounded-2xl bg-base-200 p-6 text-center">
<.icon name="hero-bell-slash" class="mx-auto size-8 text-base-content/45" />
<.icon name="hero-bell-slash" class="mx-auto size-8 text-base-content/70" />
<p class="mt-3 font-bold">{gettext("No notifications yet")}</p>
<p class="mt-1 text-sm text-base-content/70">
{gettext("Request, message, and nearby-alert updates will appear here.")}
@ -306,7 +306,7 @@ defmodule WhoNeedHelpWeb.NotificationLive do
value={notification.inserted_at}
date_style="short"
time_style="short"
class="mt-2 block text-xs font-normal text-base-content/45"
class="mt-2 block text-xs font-normal text-base-content/70"
/>
</span>
<.icon name="hero-chevron-right" class="mt-2 size-4 shrink-0 opacity-45" />
@ -401,7 +401,7 @@ defmodule WhoNeedHelpWeb.NotificationLive do
value={device.last_seen_at}
date_style="short"
time_style="short"
class="text-xs text-base-content/55"
class="text-xs text-base-content/70"
/>
</div>
<button

View File

@ -231,7 +231,7 @@ defmodule WhoNeedHelpWeb.ProductAnalyticsLive do
<div class="flex flex-wrap items-center justify-between gap-3 border-b border-base-300 p-4">
<div>
<h2 class="font-black">{gettext("Daily counters")}</h2>
<p class="text-sm text-base-content/55">
<p class="text-sm text-base-content/70">
{gettext("One row per date, metric, and non-identifying dimension.")}
</p>
</div>
@ -248,7 +248,7 @@ defmodule WhoNeedHelpWeb.ProductAnalyticsLive do
<article :for={metric <- @metrics} class="grid grid-cols-[1fr_auto] gap-3 p-4">
<div class="min-w-0">
<p class="truncate font-bold capitalize">{metric_label(metric.metric)}</p>
<p class="mt-1 text-sm text-base-content/55">{metric.date} · {metric.dimension}</p>
<p class="mt-1 text-sm text-base-content/70">{metric.date} · {metric.dimension}</p>
</div>
<strong class="text-lg">{compact_number(metric.count)}</strong>
</article>
@ -282,6 +282,7 @@ defmodule WhoNeedHelpWeb.ProductAnalyticsLive do
<button
type="button"
phx-click="previous-page"
aria-label={gettext("Previous")}
disabled={@cursor_history == []}
class="btn btn-sm btn-outline"
>
@ -292,6 +293,7 @@ defmodule WhoNeedHelpWeb.ProductAnalyticsLive do
<button
type="button"
phx-click="next-page"
aria-label={gettext("Next")}
disabled={is_nil(@next_cursor)}
class="btn btn-sm btn-outline"
>
@ -314,7 +316,7 @@ defmodule WhoNeedHelpWeb.ProductAnalyticsLive do
<.icon name={@icon} class="size-5" />
</span>
<div class="min-w-0">
<p class="truncate text-sm text-base-content/55">{@label}</p>
<p class="truncate text-sm text-base-content/70">{@label}</p>
<p class="text-2xl font-black">{compact_number(@value)}</p>
</div>
</article>

View File

@ -74,7 +74,7 @@ defmodule WhoNeedHelpWeb.ReportLive do
value={report.inserted_at}
date_style="short"
time_style="short"
class="mt-4 block text-xs text-base-content/55"
class="mt-4 block text-xs text-base-content/70"
/>
</article>

View File

@ -247,14 +247,14 @@ defmodule WhoNeedHelpWeb.SupportOperationLive do
<div class="flex flex-wrap items-center justify-between gap-2">
<strong>{if message.sender_role == :staff,
do: gettext("Support team"),
else: gettext("Requester")}</strong><time class="text-xs text-base-content/55">{format_datetime(
else: gettext("Requester")}</strong><time class="text-xs text-base-content/70">{format_datetime(
message.inserted_at
)}</time>
</div><p class="mt-1 whitespace-pre-wrap">{message.body}</p>
</article>
<p
:if={@record.conversation_messages == []}
class="rounded-xl bg-base-200 p-4 text-sm text-base-content/55"
class="rounded-xl bg-base-200 p-4 text-sm text-base-content/70"
>
{gettext("No conversation messages yet.")}
</p>
@ -327,7 +327,7 @@ defmodule WhoNeedHelpWeb.SupportOperationLive do
placeholder={gettext("Write the decision, next step, or information you need…")}
/>
</div>
<p class="mt-2 text-xs text-base-content/55">
<p class="mt-2 text-xs text-base-content/70">
{if @record.contact_verified_at,
do: gettext("The verified contact will receive a new reply by email."),
else: gettext("Email is disabled until contact is verified.")}
@ -431,7 +431,7 @@ defmodule WhoNeedHelpWeb.SupportOperationLive do
/>
</div>
<div class="mt-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<p class="text-xs text-base-content/55">
<p class="text-xs text-base-content/70">
{if @record.contact_verified_at,
do: gettext("The verified contact will receive this update by email."),
else: gettext("Email is disabled until contact is verified.")}

View File

@ -564,7 +564,7 @@ defmodule WhoNeedHelpWeb.SupportOperationsLive do
>
{request.subject}
</p><p
class="max-w-96 truncate text-xs text-base-content/55"
class="max-w-96 truncate text-xs text-base-content/70"
title={request.contact_email}
>
{request.contact_email}
@ -609,7 +609,7 @@ defmodule WhoNeedHelpWeb.SupportOperationsLive do
)}</span>
</div>
<div class="mt-3 flex items-center justify-between gap-3">
<span class="text-xs text-base-content/55">{format_datetime(request.inserted_at)}</span><.link
<span class="text-xs text-base-content/70">{format_datetime(request.inserted_at)}</span><.link
navigate={~p"/support/operations/support/#{request.id}"}
class="btn btn-outline btn-sm"
>{gettext("Open")}</.link>
@ -645,7 +645,7 @@ defmodule WhoNeedHelpWeb.SupportOperationsLive do
>
{removal_category(notice.category)}
</p><p
class="max-w-96 truncate text-xs text-base-content/55"
class="max-w-96 truncate text-xs text-base-content/70"
title={notice.contact_email}
>
{notice.contact_email || gettext("No contact email")}
@ -690,7 +690,7 @@ defmodule WhoNeedHelpWeb.SupportOperationsLive do
)}</span>
</div>
<div class="mt-3 flex items-center justify-between gap-3">
<span class="text-xs text-base-content/55">{format_datetime(notice.inserted_at)}</span><.link
<span class="text-xs text-base-content/70">{format_datetime(notice.inserted_at)}</span><.link
navigate={~p"/support/operations/legal/#{notice.id}"}
class="btn btn-outline btn-sm"
>{gettext("Open")}</.link>

View File

@ -7114,10 +7114,13 @@ msgstr ""
msgid "Name, reason, or reference"
msgstr ""
#: lib/who_need_help_web/live/admin_audit_live.ex:262
#: lib/who_need_help_web/live/admin_audit_live.ex:260
#: lib/who_need_help_web/live/admin_audit_live.ex:264
#: lib/who_need_help_web/live/admin_users_live.ex:345
#: lib/who_need_help_web/live/moderation_live.ex:1140
#: lib/who_need_help_web/live/product_analytics_live.ex:298
#: lib/who_need_help_web/live/moderation_live.ex:1138
#: lib/who_need_help_web/live/moderation_live.ex:1142
#: lib/who_need_help_web/live/product_analytics_live.ex:296
#: lib/who_need_help_web/live/product_analytics_live.ex:300
#: lib/who_need_help_web/live/support_operations_live.ex:513
#, elixir-autogen, elixir-format
msgid "Next"
@ -7225,21 +7228,24 @@ msgstr ""
msgid "Overview"
msgstr ""
#: lib/who_need_help_web/live/admin_audit_live.ex:255
#: lib/who_need_help_web/live/admin_audit_live.ex:256
#: lib/who_need_help_web/live/admin_users_live.ex:227
#: lib/who_need_help_web/live/admin_users_live.ex:338
#: lib/who_need_help_web/live/moderation_live.ex:1129
#: lib/who_need_help_web/live/product_analytics_live.ex:291
#: lib/who_need_help_web/live/moderation_live.ex:1130
#: lib/who_need_help_web/live/product_analytics_live.ex:292
#: lib/who_need_help_web/live/support_operations_live.ex:506
#: lib/who_need_help_web/live/support_operations_live.ex:537
#, elixir-autogen, elixir-format
msgid "Page %{page}"
msgstr ""
#: lib/who_need_help_web/live/admin_audit_live.ex:253
#: lib/who_need_help_web/live/admin_audit_live.ex:249
#: lib/who_need_help_web/live/admin_audit_live.ex:254
#: lib/who_need_help_web/live/admin_users_live.ex:336
#: lib/who_need_help_web/live/moderation_live.ex:1126
#: lib/who_need_help_web/live/product_analytics_live.ex:289
#: lib/who_need_help_web/live/moderation_live.ex:1122
#: lib/who_need_help_web/live/moderation_live.ex:1127
#: lib/who_need_help_web/live/product_analytics_live.ex:285
#: lib/who_need_help_web/live/product_analytics_live.ex:290
#: lib/who_need_help_web/live/support_operations_live.ex:504
#, elixir-autogen, elixir-format
msgid "Previous"
@ -7451,7 +7457,7 @@ msgid "Search users"
msgstr ""
#: lib/who_need_help_web/live/admin_users_live.ex:218
#: lib/who_need_help_web/live/moderation_live.ex:1131
#: lib/who_need_help_web/live/moderation_live.ex:1132
#: lib/who_need_help_web/live/support_operations_live.ex:531
#, elixir-autogen, elixir-format
msgid "Showing %{first}–%{last}"