refactor timezone management and API error handling, add option to (#319)

change the database api url
This commit is contained in:
creations 2025-08-03 14:39:48 -04:00 • committed by GitHub
parent 322bb7f124
commit 9d4db95e75
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 91 additions and 55 deletions

View file

@ -10,8 +10,8 @@ import { Margins } from "@utils/margins";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
import { Button, Forms, SearchableSelect, useEffect, useMemo, useState } from "@webpack/common";
import { DATASTORE_KEY, settings, timezones } from ".";
import { getTimezone, setTimezone, setUserDatabaseTimezone } from "./database";
import { DATASTORE_KEY, getSystemTimezone, resolveUserTimezone, settings, timezones } from ".";
import { setTimezone, setUserDatabaseTimezone } from "./database";
export async function setUserTimezone(userId: string, timezone: string | null) {
timezones[userId] = timezone;
@ -21,19 +21,11 @@ export async function setUserTimezone(userId: string, timezone: string | null) {
const cl = classNameFactory("vc-timezone-");
export function SetTimezoneModal({ userId, modalProps, database }: { userId: string, modalProps: ModalProps; database?: boolean; }) {
const [currentValue, setCurrentValue] = useState<string | null>(timezones[userId] ?? null);
const [currentValue, setCurrentValue] = useState<string | null>(null);
useEffect(() => {
const localTimezone = timezones[userId];
const shouldUseDatabase =
settings.store.useDatabase &&
(settings.store.preferDatabaseOverLocal || !localTimezone);
const value = shouldUseDatabase
? getTimezone(userId) ?? localTimezone
: localTimezone;
setCurrentValue(value ?? Intl.DateTimeFormat().resolvedOptions().timeZone);
const resolvedTimezone = resolveUserTimezone(userId);
setCurrentValue(resolvedTimezone ?? getSystemTimezone());
}, [userId, settings.store.useDatabase, settings.store.preferDatabaseOverLocal]);
const options = useMemo(() => {
@ -89,8 +81,10 @@ export function SetTimezoneModal({ userId, modalProps, database }: { userId: str
disabled={currentValue === null}
onClick={async () => {
if (database) {
await setUserDatabaseTimezone(userId, currentValue);
await setTimezone(currentValue!);
const success = await setTimezone(currentValue!);
if (success) {
await setUserDatabaseTimezone(userId, currentValue);
}
} else {
await setUserTimezone(userId, currentValue);
}

View file

@ -7,11 +7,34 @@
import { openModal } from "@utils/index";
import { OAuth2AuthorizeModal, showToast, Toasts } from "@webpack/common";
import { settings } from ".";
const databaseTimezones: Record<string, { value: string | null; }> = {};
const DOMAIN = "https://timezone.creations.works";
const REDIRECT_URI = `${DOMAIN}/auth/discord/callback`;
const CLIENT_ID = "1377021506810417173";
function getDomain(): string {
return settings.store.databaseUrl || "https://timezone.creations.works";
}
function getRedirectUri(): string {
return `${getDomain()}/auth/discord/callback`;
}
function handleApiError(error: any, defaultMessage: string): void {
console.error(defaultMessage, error);
const message = error?.message || defaultMessage;
showToast(message, Toasts.Type.FAILURE);
}
async function safeJsonParse(response: Response): Promise<any> {
try {
return await response.json();
} catch (e) {
console.warn("Failed to parse JSON response:", e);
return { message: "Invalid response format" };
}
}
export async function setUserDatabaseTimezone(userId: string, timezone: string | null) {
databaseTimezones[userId] = { value: timezone };
}
@ -22,7 +45,7 @@ export function getTimezone(userId: string): string | null {
export async function loadDatabaseTimezones(): Promise<boolean> {
try {
const res = await fetch(`${DOMAIN}/list`, {
const res = await fetch(`${getDomain()}/list`, {
headers: { Accept: "application/json" }
});
if (res.ok) {
@ -43,7 +66,7 @@ export async function loadDatabaseTimezones(): Promise<boolean> {
async function checkAuthentication(): Promise<boolean> {
try {
const res = await fetch(`${DOMAIN}/me`, {
const res = await fetch(`${getDomain()}/me`, {
credentials: "include",
headers: { Accept: "application/json" }
});
@ -73,7 +96,7 @@ async function setTimezoneInternal(timezone: string): Promise<boolean> {
formData.append("timezone", timezone);
try {
const res = await fetch(`${DOMAIN}/set`, {
const res = await fetch(`${getDomain()}/set`, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
@ -84,16 +107,15 @@ async function setTimezoneInternal(timezone: string): Promise<boolean> {
});
if (!res.ok) {
const error = await res.json().catch(() => ({ message: "Unknown error" }));
showToast(error.message || "Failed to set timezone", Toasts.Type.FAILURE);
const error = await safeJsonParse(res);
handleApiError(error, "Failed to set timezone");
return false;
}
showToast("Timezone updated successfully!", Toasts.Type.SUCCESS);
return true;
} catch (e) {
console.error("Error setting timezone:", e);
showToast("Failed to set timezone", Toasts.Type.FAILURE);
handleApiError(e, "Failed to set timezone");
return false;
}
}
@ -102,12 +124,19 @@ export async function deleteTimezone(): Promise<boolean> {
const isAuthenticated = await checkAuthentication();
if (!isAuthenticated) {
showToast("You must be logged in to delete your timezone", Toasts.Type.FAILURE);
return false;
return new Promise(resolve => {
authModal(() => {
deleteTimezoneInternal().then(resolve);
});
});
}
return deleteTimezoneInternal();
}
async function deleteTimezoneInternal(): Promise<boolean> {
try {
const res = await fetch(`${DOMAIN}/delete`, {
const res = await fetch(`${getDomain()}/delete`, {
method: "DELETE",
headers: {
Accept: "application/json"
@ -116,16 +145,15 @@ export async function deleteTimezone(): Promise<boolean> {
});
if (!res.ok) {
const error = await res.json().catch(() => ({ message: "Unknown error" }));
showToast(error.message || "Failed to delete timezone", Toasts.Type.FAILURE);
const error = await safeJsonParse(res);
handleApiError(error, "Failed to delete timezone");
return false;
}
showToast("Timezone deleted successfully!", Toasts.Type.SUCCESS);
return true;
} catch (e) {
console.error("Error deleting timezone:", e);
showToast("Failed to delete timezone", Toasts.Type.FAILURE);
handleApiError(e, "Failed to delete timezone");
return false;
}
}
@ -135,7 +163,7 @@ export function authModal(callback?: () => void) {
<OAuth2AuthorizeModal
{...modalProps}
clientId={CLIENT_ID}
redirectUri={REDIRECT_URI}
redirectUri={getRedirectUri()}
responseType="code"
scopes={["identify"]}
permissions={0n}
@ -148,16 +176,15 @@ export function authModal(callback?: () => void) {
credentials: "include",
headers: { Accept: "application/json" }
});
const json = await r.json();
const json = await safeJsonParse(r);
if (!r.ok) {
showToast(json.message ?? "Authorization failed", Toasts.Type.FAILURE);
handleApiError(json, "Authorization failed");
return;
}
showToast("Authorization successful!", Toasts.Type.SUCCESS);
callback?.();
} catch (e) {
console.error("Error during authorization:", e);
showToast("Unexpected error during authorization", Toasts.Type.FAILURE);
handleApiError(e, "Unexpected error during authorization");
}
}}
/>

View file

@ -23,6 +23,22 @@ import { SetTimezoneModal } from "./TimezoneModal";
export let timezones: Record<string, string | null> = {};
export const DATASTORE_KEY = "vencord-timezones";
export function resolveUserTimezone(userId: string): string | null {
const localTimezone = timezones[userId];
const shouldUseDatabase =
settings.store.useDatabase &&
(settings.store.preferDatabaseOverLocal || !localTimezone);
if (shouldUseDatabase) {
return getTimezone(userId) ?? localTimezone;
}
return localTimezone;
}
export function getSystemTimezone(): string {
return Intl.DateTimeFormat().resolvedOptions().timeZone;
}
const classes = findByPropsLazy("timestamp", "compact", "contentOnly");
const locale = findByPropsLazy("getLocale");
@ -63,6 +79,12 @@ export const settings = definePluginSettings({
default: true
},
databaseUrl: {
type: OptionType.STRING,
description: "Database URL for timezone storage",
default: "https://timezone.creations.works"
},
setDatabaseTimezone: {
description: "Set your timezone on the database",
type: OptionType.COMPONENT,
@ -84,19 +106,14 @@ export const settings = definePluginSettings({
onClick={async () => {
try {
await setUserDatabaseTimezone(UserStore.getCurrentUser().id, null);
const success = await deleteTimezone();
if (success) {
showToast("Database timezone reset successfully!", Toasts.Type.SUCCESS);
} else {
showToast("Failed to reset database timezone", Toasts.Type.FAILURE);
}
await deleteTimezone();
} catch (error) {
console.error("Error resetting database timezone:", error);
showToast("Failed to reset database timezone", Toasts.Type.FAILURE);
}
}}
>
Reset Database Timezones
Reset Database Timezone
</Button>
)
},
@ -130,16 +147,7 @@ const TimestampComponent = ErrorBoundary.wrap(({ userId, timestamp, type }: Prop
const [timezone, setTimezone] = useState<string | null>(null);
useEffect(() => {
const localTimezone = timezones[userId];
const shouldUseDatabase =
settings.store.useDatabase &&
(settings.store.preferDatabaseOverLocal || !localTimezone);
if (shouldUseDatabase) {
setTimezone(getTimezone(userId) ?? localTimezone);
} else {
setTimezone(localTimezone);
}
setTimezone(resolveUserTimezone(userId));
}, [userId, settings.store.useDatabase, settings.store.preferDatabaseOverLocal]);
useEffect(() => {
@ -153,8 +161,15 @@ const TimestampComponent = ErrorBoundary.wrap(({ userId, timestamp, type }: Prop
setCurrentTime(Date.now());
}, delay);
return () => clearTimeout(timer);
}, [type, currentTime]);
const interval = setInterval(() => {
setCurrentTime(Date.now());
}, 60000);
return () => {
clearTimeout(timer);
clearInterval(interval);
};
}, [type]);
if (!timezone) return null;