feat(frontend): add timezone to new library agent page (#10874)

## Changes 🏗️

<img width="800" height="756" alt="Screenshot 2025-09-09 at 14 03 24"
src="https://github.com/user-attachments/assets/65f3e3a8-1ce0-491c-824a-601a494d3a36"
/>

<img width="600" height="493" alt="Screenshot 2025-09-09 at 14 03 28"
src="https://github.com/user-attachments/assets/457b37a3-6b3b-49b8-912c-c72cf06e8e58"
/>

Following the nice changes @ntindle did regarding timezones, bring them
into the new page:
- display the timezone when scheduling an agent on the new modal
- display the timezone for a schedule on the new schedule details view

## Checklist 📋

### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  - [x] Run the app locally with `agent-new-runs` flag ON
  - [x] Open an agent on the new page
- [x] On the new modal, create a schedule, it display the timezone alert
  - [x] Once created, on the schedule view, it displays the timezone   

### For configuration changes:

None
This commit is contained in:
Ubbe
2025-09-09 14:37:48 +09:00
committed by GitHub
parent dc03ea718c
commit e8cf3edbf4
4 changed files with 80 additions and 9 deletions

View File

@@ -5,6 +5,7 @@ import { Select } from "@/components/atoms/Select/Select";
import { useScheduleView } from "./useScheduleView";
import { useCallback, useState } from "react";
import { validateSchedule } from "./helpers";
import { TimezoneNotice } from "../TimezoneNotice/TimezoneNotice";
interface Props {
scheduleName: string;
@@ -141,8 +142,9 @@ export function ScheduleView({
placeholder="00:00"
error={errors.time}
/>
{/** Agent inputs are rendered in the main modal; none here. */}
<div className="-mt-4">
<TimezoneNotice />
</div>
</div>
);
}

View File

@@ -0,0 +1,41 @@
import { useGetV1GetUserTimezone } from "@/app/api/__generated__/endpoints/auth/auth";
import { getTimezoneDisplayName } from "@/lib/timezone-utils";
import { InfoIcon } from "@phosphor-icons/react";
export function TimezoneNotice() {
const { data: userTimezone, isSuccess } = useGetV1GetUserTimezone({
query: {
select: (res) => (res.status === 200 ? res.data.timezone : undefined),
},
});
if (!isSuccess) {
return null;
}
if (userTimezone === "not-set") {
return (
<div className="mt-1 flex items-center gap-2 rounded-md border border-amber-200 bg-amber-50 p-3">
<InfoIcon className="h-4 w-4 text-amber-600" />
<p className="text-sm text-amber-800">
No timezone set. Schedule will run in UTC.
<a href="/profile/settings" className="ml-1 underline">
Set your timezone
</a>
</p>
</div>
);
}
const tzName = getTimezoneDisplayName(userTimezone || "UTC");
return (
<div className="mt-1 flex items-center gap-2 rounded-md bg-muted/50 p-3">
<InfoIcon className="h-4 w-4 text-muted-foreground" />
<p className="text-sm text-muted-foreground">
Schedule will run in your timezone:{" "}
<span className="font-medium">{tzName}</span>
</p>
</div>
);
}

View File

@@ -25,7 +25,7 @@ import { useScheduleDetailHeader } from "../RunDetailHeader/useScheduleDetailHea
import { DeleteScheduleButton } from "./components/DeleteScheduleButton/DeleteScheduleButton";
import { humanizeCronExpression } from "@/lib/cron-expression-utils";
import { useGetV1GetUserTimezone } from "@/app/api/__generated__/endpoints/auth/auth";
import { formatInTimezone } from "@/lib/timezone-utils";
import { formatInTimezone, getTimezoneDisplayName } from "@/lib/timezone-utils";
import { Skeleton } from "@/components/ui/skeleton";
import { AgentInputsReadOnly } from "../AgentInputsReadOnly/AgentInputsReadOnly";
import { Button } from "@/components/atoms/Button/Button";
@@ -96,10 +96,11 @@ export function ScheduleDetails({
<RunDetailHeader
agent={agent}
run={undefined}
scheduleRecurrence={humanizeCronExpression(
schedule?.cron || "",
userTzRes,
)}
scheduleRecurrence={
schedule
? `${humanizeCronExpression(schedule.cron || "", userTzRes)} · ${getTimezoneDisplayName(userTzRes || "UTC")}`
: undefined
}
/>
</div>
{schedule ? (
@@ -161,6 +162,10 @@ export function ScheduleDetails({
</Text>
<p className="text-sm text-zinc-600">
{humanizeCronExpression(schedule.cron, userTzRes)}
{" • "}
<span className="text-xs text-zinc-600">
{getTimezoneDisplayName(userTzRes || "UTC")}
</span>
</p>
</div>
<div className="flex flex-col gap-1.5">
@@ -179,7 +184,11 @@ export function ScheduleDetails({
minute: "2-digit",
hour12: false,
},
)}
)}{" "}
{" "}
<span className="text-xs text-zinc-600">
{getTimezoneDisplayName(userTzRes || "UTC")}
</span>
</p>
</div>
</div>

View File

@@ -22,7 +22,6 @@ export function formatInTimezone(
day: "numeric",
hour: "2-digit",
minute: "2-digit",
timeZoneName: "short",
...options,
};
@@ -112,3 +111,23 @@ export function getTimezoneDisplayName(timezone: string): string {
return timezone;
}
}
/**
* Get the GMT offset for a given timezone, e.g. "GMT+9" or "UTC"
*/
export function getTimezoneGmtOffset(timezone: string): string {
if (timezone === "not-set" || !timezone) return "";
try {
const date = new Date();
const formatted = new Intl.DateTimeFormat("en-US", {
timeZone: timezone,
timeZoneName: "short",
}).format(date);
// Common outputs look like "1/1/2024, GMT+9" or "1/1/2024, UTC"
const match = formatted.match(/(GMT[+\-]\d{1,2}|UTC)/i);
return match ? match[0].toUpperCase() : "";
} catch {
return "";
}
}