feat: add timezone indicator on insights for unmatched timezone (#21568)

* feat: adds timezone indicator on insights informing user that user settings timezone is used instead of browser timezone

* added i18 helper for translation

* chore: use entities.decodeHTML instead of custom decoder

* feat(insights): align TimezoneBadge with new timeZone prop flow

* chore

* chore: smaller icon

* do not escape value

---------

Co-authored-by: Kartik Saini <41051387+kart1ka@users.noreply.github.com>
Co-authored-by: Eunjae Lee <hey@eunjae.dev>
This commit is contained in:
Sarthak Kapila
2025-07-01 12:49:21 +00:00
committed by GitHub
co-authored by Kartik Saini Eunjae Lee
parent 4aa05fe523
commit 8a080b91b9
4 changed files with 59 additions and 0 deletions
@@ -19,6 +19,7 @@ import {
MostCancelledBookingsTables,
PopularEventsTable,
RecentFeedbackTable,
TimezoneBadge,
} from "@calcom/features/insights/components";
import "@calcom/features/insights/components/tremor.css";
import { InsightsOrgTeamsProvider } from "@calcom/features/insights/context/InsightsOrgTeamsProvider";
@@ -62,6 +63,7 @@ function InsightsPageContent() {
<div className="grow" />
<Download />
<DateRangeFilter column={createdAtColumn} />
<TimezoneBadge />
</div>
<div className="my-4 space-y-4">
@@ -3267,5 +3267,6 @@
"booking_not_allowed_by_restriction_schedule_error": "Booking outside restriction schedule availability.",
"restriction_schedule_not_found_error": "Restriction schedule not found",
"converted_image_size_limit_exceed": "Image size limit exceeded, please use a smaller image preferably in JPEG format",
"timezone_mismatch_tooltip": "You are viewing the report based on your profile timezone ({{userTimezone}}), while your browser is set to timezone ({{browserTimezone}})",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -0,0 +1,55 @@
import { useMemo } from "react";
import { useDataTable } from "@calcom/features/data-table";
import NoSSR from "@calcom/lib/components/NoSSR";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { CURRENT_TIMEZONE } from "@calcom/lib/timezoneConstants";
import { Badge } from "@calcom/ui/components/badge";
import { Icon } from "@calcom/ui/components/icon";
import { Tooltip } from "@calcom/ui/components/tooltip";
const TimezoneBadgeContent = () => {
const { t } = useLocale();
const { timeZone: userTimezone } = useDataTable();
const timezoneData = useMemo(() => {
// Use Cal's standard CURRENT_TIMEZONE constant
const browserTimezone = CURRENT_TIMEZONE;
if (!browserTimezone || !userTimezone || browserTimezone === userTimezone) return null;
const tooltipContent = t("timezone_mismatch_tooltip", {
browserTimezone,
userTimezone,
interpolation: { escapeValue: false },
});
return {
browser: browserTimezone,
user: userTimezone,
tooltipContent,
badgeContent: userTimezone,
};
}, [userTimezone, t]);
// Don't render anything if no timezone mismatch
if (!timezoneData) {
return null;
}
return (
<Tooltip content={timezoneData.tooltipContent}>
<Badge variant="gray" size="sm" data-testid="timezone-mismatch-badge">
<Icon name="info" />
</Badge>
</Tooltip>
);
};
export const TimezoneBadge = () => {
return (
<NoSSR fallback={null}>
<TimezoneBadgeContent />
</NoSSR>
);
};
@@ -13,3 +13,4 @@ export { RecentFeedbackTable } from "./RecentFeedbackTable";
export { RoutedToPerPeriod } from "./RoutedToPerPeriod";
export { RoutingFormResponsesTable, type RoutingFormTableType } from "./RoutingFormResponsesTable";
export { RoutingKPICards } from "./RoutingKPICards";
export { TimezoneBadge } from "./TimezoneBadge";