aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web/components
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-07-06 18:26:41 +0000
committerMohamed Bassem <me@mbassem.com>2025-07-06 18:51:40 +0000
commit0e94ad36b580534877871ece247ed6085b5749ef (patch)
tree4585a326d2a2e36d34c6627c047ccec49b3e1b4d /apps/web/components
parentb60ece578304df21602d39c7022a7a4dbc6437e0 (diff)
downloadkarakeep-0e94ad36b580534877871ece247ed6085b5749ef.tar.zst
feat: Add a new timezone user setting
Diffstat (limited to 'apps/web/components')
-rw-r--r--apps/web/components/settings/UserOptions.tsx80
1 files changed, 77 insertions, 3 deletions
diff --git a/apps/web/components/settings/UserOptions.tsx b/apps/web/components/settings/UserOptions.tsx
index 4f18a2e3..02c27145 100644
--- a/apps/web/components/settings/UserOptions.tsx
+++ b/apps/web/components/settings/UserOptions.tsx
@@ -1,13 +1,13 @@
"use client";
-import { useEffect } from "react";
+import { useEffect, useState } from "react";
import { useClientConfig } from "@/lib/clientConfig";
import { useTranslation } from "@/lib/i18n/client";
import { useInterfaceLang } from "@/lib/userLocalSettings/bookmarksLayout";
import { updateInterfaceLang } from "@/lib/userLocalSettings/userLocalSettings";
import { useUserSettings } from "@/lib/userSettings";
import { zodResolver } from "@hookform/resolvers/zod";
-import { Archive, Bookmark, Globe } from "lucide-react";
+import { Archive, Bookmark, Clock, Globe } from "lucide-react";
import { useForm } from "react-hook-form";
import { z } from "zod";
@@ -71,6 +71,9 @@ export default function UserOptions() {
});
},
});
+ const [timezones, setTimezones] = useState<
+ { label: string; value: string }[] | null
+ >(null);
const bookmarkClickActionTranslation: Record<
ZUserSettings["bookmarkClickAction"],
@@ -92,6 +95,39 @@ export default function UserOptions() {
hide: t("settings.info.user_settings.archive_display_behaviour.hide"),
};
+ // Get all supported timezones and format them nicely
+ useEffect(() => {
+ try {
+ const browserTimezones = Intl.supportedValuesOf("timeZone");
+ setTimezones(
+ browserTimezones
+ .map((tz) => {
+ // Create a more readable label by replacing underscores with spaces
+ // and showing the current time offset
+ const now = new Date();
+ const formatter = new Intl.DateTimeFormat("en", {
+ timeZone: tz,
+ timeZoneName: "short",
+ });
+ const parts = formatter.formatToParts(now);
+ const timeZoneName =
+ parts.find((part) => part.type === "timeZoneName")?.value || "";
+
+ // Format the timezone name for display
+ const displayName = tz.replace(/_/g, " ").replace("/", " / ");
+ const label = timeZoneName
+ ? `${displayName} (${timeZoneName})`
+ : displayName;
+
+ return { value: tz, label };
+ })
+ .sort((a, b) => a.label.localeCompare(b.label)),
+ );
+ } catch {
+ setTimezones(null);
+ }
+ }, []);
+
const form = useForm<z.infer<typeof zUserSettingsSchema>>({
resolver: zodResolver(zUserSettingsSchema),
defaultValues: data,
@@ -119,9 +155,47 @@ export default function UserOptions() {
<LanguageSelect />
</div>
+ <FormField
+ control={form.control}
+ name="timezone"
+ render={({ field }) => (
+ <div className="space-y-2">
+ <Label className="flex items-center gap-2 text-sm font-medium">
+ <Clock className="h-4 w-4" />
+ Timezone
+ </Label>
+ <Select
+ disabled={!!clientConfig.demoMode || timezones === null}
+ value={field.value}
+ onValueChange={(value) => {
+ mutate({
+ timezone: value,
+ });
+ }}
+ >
+ <SelectTrigger className="h-11">
+ <SelectValue>
+ {timezones?.find(
+ (tz: { value: string; label: string }) =>
+ tz.value === field.value,
+ )?.label || field.value}
+ </SelectValue>
+ </SelectTrigger>
+ <SelectContent>
+ {timezones?.map((tz: { value: string; label: string }) => (
+ <SelectItem key={tz.value} value={tz.value}>
+ {tz.label}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
+ )}
+ />
+
<Separator />
- <div className="grid gap-6 md:grid-cols-2">
+ <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<FormField
control={form.control}
name="bookmarkClickAction"