diff --git a/assets/css/app.css b/assets/css/app.css index ddc3328..e5bca1d 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -30,7 +30,7 @@ --color-base-100: oklch(30.33% 0.016 252.42); --color-base-200: oklch(25.26% 0.014 253.1); --color-base-300: oklch(20.15% 0.012 254.09); - --color-base-content: #f3f7ff; + --color-base-content: #ffffff; --color-primary: #a5b4fc; --color-primary-content: #18162a; --color-secondary: #a5b4fc; @@ -128,6 +128,21 @@ color: var(--color-base-content) !important; } +[data-theme="dark"] .btn-ghost, +[data-theme="dark"] .btn-outline { + background-color: var(--color-base-300); +} + +[data-theme="dark"] .btn-soft.btn-primary { + color: var(--color-primary-content); + background-color: var(--color-primary); +} + +.locale-active { + color: var(--color-neutral-content) !important; + background-color: var(--color-neutral) !important; +} + .aid-map { min-height: 22rem; border-radius: 1.25rem; diff --git a/assets/js/app.js b/assets/js/app.js index 402b6bb..822b752 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -90,6 +90,7 @@ const activateProtectedTokenFragment = () => { } activateProtectedTokenFragment() +window.addEventListener("hashchange", activateProtectedTokenFragment) const liveSocket = new LiveSocket("/live", Socket, { longPollFallbackMs: 2500, diff --git a/e2e/cross-client/android-browser.spec.ts b/e2e/cross-client/android-browser.spec.ts index 992f6f6..e9ab50c 100644 --- a/e2e/cross-client/android-browser.spec.ts +++ b/e2e/cross-client/android-browser.spec.ts @@ -24,7 +24,7 @@ test("browser and Android share chat and live tracking state", async ({ browser await expect(requester.page.getByText(androidMessage, { exact: true })).toBeVisible(); if (phase === "observe_android") { - await expect.poll(() => markerKinds(requester.page)).toContain("Shared live location"); + await expect.poll(() => markerKinds(requester.page)).toContain("Shared location"); await requester.page .getByPlaceholder("Write a safe coordination message…") .fill(browserReply); @@ -32,7 +32,7 @@ test("browser and Android share chat and live tracking state", async ({ browser await expect(requester.page.getByText(browserReply, { exact: true })).toBeVisible(); } else if (phase === "observe_stopped") { await expect(requester.page.getByText(browserReply, { exact: true })).toBeVisible(); - await expect.poll(() => markerKinds(requester.page)).not.toContain("Shared live location"); + await expect.poll(() => markerKinds(requester.page)).not.toContain("Shared location"); } else { throw new Error(`Unsupported CROSS_CLIENT_PHASE: ${phase}`); } diff --git a/e2e/pwa/pwa.spec.ts b/e2e/pwa/pwa.spec.ts index 3559f55..b6ca686 100644 --- a/e2e/pwa/pwa.spec.ts +++ b/e2e/pwa/pwa.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from "@playwright/test"; -const CACHE = "who-need-help-static-v3"; +const CACHE_PREFIX = "who-need-help-static-"; const STALE_CACHE = "who-need-help-static-v0"; const SHELL_PATHS = [ "/offline.html", @@ -45,6 +45,21 @@ test("public HTTPS PWA installs, updates its cache, and falls back safely offlin expect(manifestResponse.status()).toBe(200); expect(manifestResponse.headers()["content-type"]).toContain("application/manifest+json"); + const serviceWorkerResponse = await request.get("/sw.js"); + expect(serviceWorkerResponse.status()).toBe(200); + expect(serviceWorkerResponse.headers()["content-type"]).toContain("javascript"); + const serviceWorker = await serviceWorkerResponse.text(); + const cacheVersionMatch = serviceWorker.match( + /const CACHE = `\$\{CACHE_PREFIX\}(v[0-9]+)`/, + ); + expect(cacheVersionMatch).not.toBeNull(); + + if (!cacheVersionMatch) { + throw new Error("The service worker does not declare a versioned static cache"); + } + + const cacheName = `${CACHE_PREFIX}${cacheVersionMatch[1]}`; + const manifest = (await manifestResponse.json()) as { name: string; short_name: string; @@ -102,12 +117,12 @@ test("public HTTPS PWA installs, updates its cache, and falls back safely offlin await expect .poll(() => page.evaluate(() => caches.keys())) - .toContain(CACHE); + .toContain(cacheName); - const cachedShell = await page.evaluate(async (cacheName) => { - const cache = await caches.open(cacheName); + const cachedShell = await page.evaluate(async (activeCacheName) => { + const cache = await caches.open(activeCacheName); return (await cache.keys()).map((entry) => new URL(entry.url).pathname).sort(); - }, CACHE); + }, cacheName); expect(cachedShell).toEqual(expect.arrayContaining(SHELL_PATHS)); await page.goto("/safety"); @@ -146,7 +161,7 @@ test("public HTTPS PWA installs, updates its cache, and falls back safely offlin await expect .poll(() => updatedPage.evaluate(() => caches.keys())) .not.toContain(STALE_CACHE); - expect(await updatedPage.evaluate(() => caches.keys())).toContain(CACHE); + expect(await updatedPage.evaluate(() => caches.keys())).toContain(cacheName); await context.setOffline(true); const offlineResponse = await updatedPage.goto("/requests"); diff --git a/e2e/tests/active-failover.spec.ts b/e2e/tests/active-failover.spec.ts index 31c4868..d6f4587 100644 --- a/e2e/tests/active-failover.spec.ts +++ b/e2e/tests/active-failover.spec.ts @@ -99,7 +99,7 @@ test("active chat and browser tracking recover after the serving BEAM node resta await expect(helper.page.getByRole("button", { name: "Stop and delete position" })).toBeVisible(); await expect.poll(() => positionCount(helper.page)).toBe(1); await expect.poll(() => positionCount(requester.page)).toBe(1); - await expect.poll(() => markerKinds(requester.page)).toContain("Shared live location"); + await expect.poll(() => markerKinds(requester.page)).toContain("Shared location"); await helper.page .getByPlaceholder("Write a safe coordination message…") @@ -173,7 +173,7 @@ test("active chat and browser tracking recover after the serving BEAM node resta await expect.poll(() => positionCount(helper.page), { timeout: 30_000 }).toBe(1); await expect.poll(() => positionCount(requester.page), { timeout: 30_000 }).toBe(1); await expect.poll(() => markerKinds(requester.page), { timeout: 30_000 }).toContain( - "Shared live location", + "Shared location", ); await helper.page @@ -188,7 +188,7 @@ test("active chat and browser tracking recover after the serving BEAM node resta await expect(helper.page.getByRole("button", { name: "Share location" })).toBeVisible(); await expect.poll(() => positionCount(helper.page)).toBe(0); await expect.poll(() => positionCount(requester.page)).toBe(0); - await expect.poll(() => markerKinds(requester.page)).not.toContain("Shared live location"); + await expect.poll(() => markerKinds(requester.page)).not.toContain("Shared location"); await testInfo.attach("active-failover-evidence", { contentType: "application/json", diff --git a/e2e/tests/helpers.ts b/e2e/tests/helpers.ts index d130bd5..27cf3e0 100644 --- a/e2e/tests/helpers.ts +++ b/e2e/tests/helpers.ts @@ -322,7 +322,13 @@ export async function registerAndConfirm( .check(); await page.getByRole("button", { name: "Create an account" }).click(); await expect(page).toHaveURL(/\/users\/log-in$/); - await expect(page.getByText(`An email was sent to ${email}`)).toBeVisible(); + await expect( + page + .getByRole("alert") + .getByText( + "If this address can be registered or signed in, login instructions will arrive shortly.", + ), + ).toBeVisible(); await gotoWithTransientRetry(page, await waitForMagicLink(request, email)); await expect(page.locator("#magic-link-fragment-form")).toBeVisible(); @@ -357,8 +363,10 @@ export async function loginWithMagicLink( page, await waitForMagicLink(request, email, previousMessageID), ); - await expect(page.getByRole("heading", { name: `Welcome ${email}` })).toBeVisible(); - await page.getByRole("button", { name: "Log me in only this time" }).click(); + await expect(page.locator("#magic-link-fragment-form")).toBeVisible(); + await page + .getByRole("button", { name: "Confirm and log in only this time" }) + .click(); await expect(page.getByText(email, { exact: true })).toBeVisible(); return { context, page, email }; diff --git a/e2e/tests/mutual-aid.spec.ts b/e2e/tests/mutual-aid.spec.ts index ebb8f85..5da994e 100644 --- a/e2e/tests/mutual-aid.spec.ts +++ b/e2e/tests/mutual-aid.spec.ts @@ -83,11 +83,11 @@ test("two users complete medicine tracking, handover, realtime chat, and blind r }); await helper.page.getByRole("button", { name: "Share location" }).click(); await expect(helper.page.getByRole("button", { name: "Stop and delete position" })).toBeVisible(); - await expect.poll(() => markerKinds(requester.page)).toContain("Shared live location"); + await expect.poll(() => markerKinds(requester.page)).toContain("Shared location"); await helper.page.getByRole("button", { name: "Stop and delete position" }).click(); await expect(helper.page.getByRole("button", { name: "Share location" })).toBeVisible(); - await expect.poll(() => markerKinds(requester.page)).not.toContain("Shared live location"); + await expect.poll(() => markerKinds(requester.page)).not.toContain("Shared location"); await helper.page.getByRole("button", { name: "Start helping" }).click(); const handoverCode = ( diff --git a/lib/who_need_help_web/components/layouts.ex b/lib/who_need_help_web/components/layouts.ex index 15d3397..0a2aa47 100644 --- a/lib/who_need_help_web/components/layouts.ex +++ b/lib/who_need_help_web/components/layouts.ex @@ -35,7 +35,7 @@ defmodule WhoNeedHelpWeb.Layouts do def app(assigns) do ~H""" -
+