refactor timezone management and API error handling, add option to (#319)
change the database api url
This commit is contained in:
parent
322bb7f124
commit
9d4db95e75
3 changed files with 91 additions and 55 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue