From e73f9809b9dd8781cda54c1e088e8cfbcb4f621c Mon Sep 17 00:00:00 2001 From: joshchen-dev Date: Tue, 6 Oct 2026 14:02:52 +0000 Subject: [PATCH] feat(web): show raid times in each viewer's timezone The availability grid showed every date in the team's timezone, so a member abroad had to convert times themselves. Dates and times now use the viewer's timezone, with the team's time beneath when it reads differently ("Wed 7 21:00 Tokyo" when the day differs too). The "next timetable posts" line follows the viewer as well; the schedule editor stays in team time because that is what it configures. Release v0.0.14. --- README.md | 3 +- web/e2e/web.spec.ts | 32 ++++++++++++++++ web/package-lock.json | 4 +- web/package.json | 2 +- web/playwright.config.ts | 2 + web/src/features/landing.tsx | 1 + .../features/overview/availability-grid.tsx | 37 ++++++++++++++++--- web/src/features/overview/overview.tsx | 6 +-- web/src/lib/format.ts | 19 +++++++++- 9 files changed, 92 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index b89b2d1..c424d34 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Raidy is a raid scheduling project for Discord statics: the web console is where everyone plans, and Discord carries the announcements. One Go process runs the Discord bot, web API, scheduler, and production web assets, backed by PostgreSQL. -**Status:** `v0.0.13` is an early development snapshot. +**Status:** `v0.0.14` is an early development snapshot. ```text backend/ Go service, Discord interactions, web API, PostgreSQL migrations @@ -22,6 +22,7 @@ deployment/ local PostgreSQL and Kubernetes deployment manifests - Polls become read-only history when their final occurrence starts. - Discord is announcement-only: each period is posted with its dates in every reader's own timezone, live availability counts and status, and an **Open in Raidy** link. The post is edited in place as votes and decisions change. - `/raidy` replies privately with a link to your team's page. +- Raid times on the web show in each viewer's own timezone, with the team's time beneath when it differs; the schedule editor stays in the team's timezone. - The responsive console supports persisted light and dark themes. ## Run locally diff --git a/web/e2e/web.spec.ts b/web/e2e/web.spec.ts index 9a43e60..b29feed 100644 --- a/web/e2e/web.spec.ts +++ b/web/e2e/web.spec.ts @@ -102,6 +102,38 @@ test("raid times use a 24-hour clock", async ({ page }) => { await expect(header).not.toContainText(/AM|PM/); }); +test.describe("a viewer in Los Angeles", () => { + test.use({ timezoneId: "America/Los_Angeles" }); + + test("sees raid times in their own timezone with the team's time beneath", async ({ page }) => { + await mockSignedInLeader(page); + await page.goto("/app"); + const header = page.getByRole("columnheader").nth(1); + await expect(header).toContainText("Wed 7"); + await expect(header).toContainText("05:00"); + await expect(header).toContainText("21:00 Tokyo"); + }); +}); + +test.describe("a viewer in Auckland", () => { + test.use({ timezoneId: "Pacific/Auckland" }); + + test("sees the team's day when the raid falls on a different local day", async ({ page }) => { + await mockSignedInLeader(page); + await page.goto("/app"); + const header = page.getByRole("columnheader").nth(1); + await expect(header).toContainText("Thu 8"); + await expect(header).toContainText("01:00"); + await expect(header).toContainText("Wed 7 21:00 Tokyo"); + }); +}); + +test("the team's time is not repeated for viewers in the team's timezone", async ({ page }) => { + await mockSignedInLeader(page); + await page.goto("/app"); + await expect(page.getByRole("columnheader").nth(1)).not.toContainText("Tokyo"); +}); + test("a member edits and saves their availability on the web", async ({ page }) => { const recorder = await mockSignedInLeader(page); await page.goto("/app"); diff --git a/web/package-lock.json b/web/package-lock.json index 6e98042..d47fadc 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "raidy-web", - "version": "0.0.13", + "version": "0.0.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "raidy-web", - "version": "0.0.13", + "version": "0.0.14", "dependencies": { "@fontsource-variable/geist": "^5.3.0", "@fontsource-variable/geist-mono": "^5.3.0", diff --git a/web/package.json b/web/package.json index 08f2981..0d00efe 100644 --- a/web/package.json +++ b/web/package.json @@ -1,7 +1,7 @@ { "name": "raidy-web", "private": true, - "version": "0.0.13", + "version": "0.0.14", "type": "module", "scripts": { "dev": "vite", diff --git a/web/playwright.config.ts b/web/playwright.config.ts index 12b03b6..c116d33 100644 --- a/web/playwright.config.ts +++ b/web/playwright.config.ts @@ -9,6 +9,8 @@ export default defineConfig({ use: { baseURL: `http://127.0.0.1:${port}`, trace: "retain-on-failure", + // The fixtures' team is in Tokyo; individual tests move the viewer elsewhere. + timezoneId: "Asia/Tokyo", launchOptions: { executablePath: process.env.PLAYWRIGHT_CHROMIUM_PATH || undefined } }, projects: [ diff --git a/web/src/features/landing.tsx b/web/src/features/landing.tsx index c437798..46ad4d6 100644 --- a/web/src/features/landing.tsx +++ b/web/src/features/landing.tsx @@ -69,6 +69,7 @@ export function Landing({
+ {label} + + ); +} + export function AvailabilityGrid({ poll, members, @@ -86,7 +107,8 @@ export function AvailabilityGrid({ onAction, currentUserID, draft, - onToggle + onToggle, + viewerTimezone = browserTimezone() }: { poll: Poll; members: Member[]; @@ -98,6 +120,8 @@ export function AvailabilityGrid({ /** Dates the current user marks available while editing; null when not editing. */ draft?: Set | null; onToggle?: (occurrenceID: number) => void; + /** Times show in the viewer's timezone, with the team's time beneath when it differs. */ + viewerTimezone?: string; }) { const occurrences = poll.occurrences; const memberIDs = Array.from( @@ -128,10 +152,11 @@ export function AvailabilityGrid({ scope="col" className="min-w-20 px-2 py-3 text-center align-top font-normal sm:min-w-24" > -
{formatDay(occurrence.startsAt, poll.timezone)}
+
{formatDay(occurrence.startsAt, viewerTimezone)}
- {formatTime(occurrence.startsAt, poll.timezone)} + {formatTime(occurrence.startsAt, viewerTimezone)}
+ ))} @@ -152,7 +177,7 @@ export function AvailabilityGrid({