0 ? Icons.VOLUME : Icons.VOLUME_X} />
diff --git a/src/hooks/auth/useAuth.ts b/src/hooks/auth/useAuth.ts
index f608d148..3ce49163 100644
--- a/src/hooks/auth/useAuth.ts
+++ b/src/hooks/auth/useAuth.ts
@@ -63,6 +63,7 @@ export function useAuth() {
const login = useCallback(
async (loginData: LoginData) => {
+ if (!backendUrl) return;
const keys = await keysFromMnemonic(loginData.mnemonic);
const publicKeyBase64Url = bytesToBase64Url(keys.publicKey);
const { challenge } = await getLoginChallengeToken(
@@ -87,7 +88,7 @@ export function useAuth() {
);
const logout = useCallback(async () => {
- if (!currentAccount) return;
+ if (!currentAccount || !backendUrl) return;
try {
await removeSession(
backendUrl,
@@ -102,6 +103,7 @@ export function useAuth() {
const register = useCallback(
async (registerData: RegistrationData) => {
+ if (!backendUrl) return;
const { challenge } = await getRegisterChallengeToken(
backendUrl,
registerData.recaptchaToken,
@@ -134,6 +136,7 @@ export function useAuth() {
progressItems: Record,
bookmarks: Record,
) => {
+ if (!backendUrl) return;
if (
Object.keys(progressItems).length === 0 &&
Object.keys(bookmarks).length === 0
@@ -159,6 +162,7 @@ export function useAuth() {
const restore = useCallback(
async (account: AccountWithToken) => {
+ if (!backendUrl) return;
let user: { user: UserResponse; session: SessionResponse };
try {
user = await getUser(backendUrl, account.token);
diff --git a/src/hooks/auth/useBackendUrl.ts b/src/hooks/auth/useBackendUrl.ts
index e417ed06..64545227 100644
--- a/src/hooks/auth/useBackendUrl.ts
+++ b/src/hooks/auth/useBackendUrl.ts
@@ -1,7 +1,7 @@
import { conf } from "@/setup/config";
import { useAuthStore } from "@/stores/auth";
-export function useBackendUrl() {
+export function useBackendUrl(): string | undefined {
const backendUrl = useAuthStore((s) => s.backendUrl);
return backendUrl ?? conf().BACKEND_URL;
}
diff --git a/src/hooks/useSettingsState.ts b/src/hooks/useSettingsState.ts
index d0946deb..8e540a19 100644
--- a/src/hooks/useSettingsState.ts
+++ b/src/hooks/useSettingsState.ts
@@ -9,6 +9,7 @@ import {
} from "react";
import { SubtitleStyling } from "@/stores/subtitles";
+import { usePreviewThemeStore } from "@/stores/theme";
export function useDerived(
initial: T,
@@ -56,6 +57,11 @@ export function useSettingsState(
const [backendUrlState, setBackendUrl, resetBackendUrl, backendUrlChanged] =
useDerived(backendUrl);
const [themeState, setTheme, resetTheme, themeChanged] = useDerived(theme);
+ const setPreviewTheme = usePreviewThemeStore((s) => s.setPreviewTheme);
+ const resetPreviewTheme = useCallback(
+ () => setPreviewTheme(theme),
+ [setPreviewTheme, theme],
+ );
const [
appLanguageState,
setAppLanguage,
@@ -81,6 +87,7 @@ export function useSettingsState(
function reset() {
resetTheme();
+ resetPreviewTheme();
resetAppLanguage();
resetSubStyling();
resetProxyUrls();
diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx
index 18b755ca..5fe8843b 100644
--- a/src/pages/Settings.tsx
+++ b/src/pages/Settings.tsx
@@ -1,5 +1,5 @@
import classNames from "classnames";
-import { useCallback, useEffect, useMemo } from "react";
+import { useCallback, useEffect, useMemo, useRef } from "react";
import { useTranslation } from "react-i18next";
import { useAsyncFn } from "react-use";
@@ -33,7 +33,7 @@ import { AccountWithToken, useAuthStore } from "@/stores/auth";
import { useLanguageStore } from "@/stores/language";
import { usePreferencesStore } from "@/stores/preferences";
import { useSubtitleStore } from "@/stores/subtitles";
-import { useThemeStore } from "@/stores/theme";
+import { usePreviewThemeStore, useThemeStore } from "@/stores/theme";
import { SubPageLayout } from "./layouts/SubPageLayout";
import { PreferencesPart } from "./parts/settings/PreferencesPart";
@@ -70,6 +70,7 @@ export function AccountSettings(props: {
const url = useBackendUrl();
const { account } = props;
const [sessionsResult, execSessions] = useAsyncFn(() => {
+ if (!url) return Promise.resolve([]);
return getSessions(url, account);
}, [account, url]);
useEffect(() => {
@@ -103,6 +104,8 @@ export function SettingsPage() {
const { t } = useTranslation();
const activeTheme = useThemeStore((s) => s.theme);
const setTheme = useThemeStore((s) => s.setTheme);
+ const previewTheme = usePreviewThemeStore((s) => s.previewTheme);
+ const setPreviewTheme = usePreviewThemeStore((s) => s.setPreviewTheme);
const appLanguage = useLanguageStore((s) => s.language);
const setAppLanguage = useLanguageStore((s) => s.setLanguage);
@@ -143,8 +146,27 @@ export function SettingsPage() {
enableThumbnails,
);
+ useEffect(() => {
+ setPreviewTheme(activeTheme ?? "default");
+ }, [setPreviewTheme, activeTheme]);
+
+ useEffect(() => {
+ // Clear preview theme on unmount
+ return () => {
+ setPreviewTheme(null);
+ };
+ }, [setPreviewTheme]);
+
+ const setThemeWithPreview = useCallback(
+ (theme: string) => {
+ state.theme.set(theme === "default" ? null : theme);
+ setPreviewTheme(theme);
+ },
+ [state.theme, setPreviewTheme],
+ );
+
const saveChanges = useCallback(async () => {
- if (account) {
+ if (account && backendUrl) {
if (
state.appLanguage.changed ||
state.theme.changed ||
@@ -241,7 +263,11 @@ export function SettingsPage() {
/>
-
+
>
diff --git a/src/pages/onboarding/Onboarding.tsx b/src/pages/onboarding/Onboarding.tsx
index 8ba0ba19..01ff209f 100644
--- a/src/pages/onboarding/Onboarding.tsx
+++ b/src/pages/onboarding/Onboarding.tsx
@@ -13,6 +13,7 @@ import {
} from "@/pages/onboarding/onboardingHooks";
import { Card, CardContent, Link } from "@/pages/onboarding/utils";
import { PageTitle } from "@/pages/parts/util/PageTitle";
+import { getProxyUrls } from "@/utils/proxyUrls";
function VerticalLine(props: { className?: string }) {
return (
@@ -27,6 +28,7 @@ export function OnboardingPage() {
const skipModal = useModal("skip");
const { completeAndRedirect } = useRedirectBack();
const { t } = useTranslation();
+ const noProxies = getProxyUrls().length === 0;
return (
@@ -85,32 +87,34 @@ export function OnboardingPage() {
-
-
-
-
-
-
-
-
-
-
-
+ {noProxies ? null : (
+ <>
+
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
);
diff --git a/src/pages/onboarding/OnboardingExtension.tsx b/src/pages/onboarding/OnboardingExtension.tsx
index a0fa035d..db351dda 100644
--- a/src/pages/onboarding/OnboardingExtension.tsx
+++ b/src/pages/onboarding/OnboardingExtension.tsx
@@ -115,7 +115,7 @@ export function ExtensionStatus(props: {
: null}
- {props.showHelp ? (
+ {props.showHelp && props.status !== "success" ? (
diff --git a/src/pages/onboarding/OnboardingProxy.tsx b/src/pages/onboarding/OnboardingProxy.tsx
index e67c0769..00584eef 100644
--- a/src/pages/onboarding/OnboardingProxy.tsx
+++ b/src/pages/onboarding/OnboardingProxy.tsx
@@ -43,7 +43,7 @@ export function OnboardingProxyPage() {
throw new Error("onboarding.proxy.input.errorNotProxy");
setProxySet([url]);
- if (account) {
+ if (account && backendUrl) {
await updateSettings(backendUrl, account, {
proxyUrls: [url],
});
diff --git a/src/pages/parts/admin/BackendTestPart.tsx b/src/pages/parts/admin/BackendTestPart.tsx
index 449e9643..aadd9397 100644
--- a/src/pages/parts/admin/BackendTestPart.tsx
+++ b/src/pages/parts/admin/BackendTestPart.tsx
@@ -32,13 +32,21 @@ export function BackendTestPart() {
value: null,
});
+ if (!backendUrl) {
+ return setStatus({
+ hasTested: true,
+ success: false,
+ errorText: "Backend URL is not set",
+ value: null,
+ });
+ }
+
try {
const backendData = await getBackendMeta(backendUrl);
return setStatus({
hasTested: true,
success: true,
- errorText:
- "Failed to call backend, double check the URL key and your internet connection",
+ errorText: "",
value: backendData,
});
} catch (err) {
@@ -46,7 +54,7 @@ export function BackendTestPart() {
hasTested: true,
success: false,
errorText:
- "Failed to call backend, double check the URL key and your internet connection",
+ "Failed to call backend, double check the URL, your internet connection, and ensure CORS is properly configured on your backend.",
value: null,
});
}
diff --git a/src/pages/parts/admin/TMDBTestPart.tsx b/src/pages/parts/admin/TMDBTestPart.tsx
index 00198946..a7f8fa50 100644
--- a/src/pages/parts/admin/TMDBTestPart.tsx
+++ b/src/pages/parts/admin/TMDBTestPart.tsx
@@ -29,7 +29,7 @@ export function TMDBTestPart() {
return setStatus({
hasTested: true,
success: false,
- errorText: "TMDB api key is not set",
+ errorText: "TMDB API key is not set",
});
}
const isJWT = tmdbApiKey.split(".").length > 2;
@@ -37,7 +37,7 @@ export function TMDBTestPart() {
return setStatus({
hasTested: true,
success: false,
- errorText: "TMDB api key is not a read only key",
+ errorText: "TMDB API key is not a read only key",
});
}
@@ -48,7 +48,7 @@ export function TMDBTestPart() {
hasTested: true,
success: false,
errorText:
- "Failed to call tmdb, double check api key and your internet connection",
+ "Failed to call TMDB, double check API key and your internet connection",
});
}
@@ -61,7 +61,7 @@ export function TMDBTestPart() {
return (
<>
-
TMDB tests
+
TMDB test
diff --git a/src/pages/parts/admin/WorkerTestPart.tsx b/src/pages/parts/admin/WorkerTestPart.tsx
index e2dd6780..5f8b9853 100644
--- a/src/pages/parts/admin/WorkerTestPart.tsx
+++ b/src/pages/parts/admin/WorkerTestPart.tsx
@@ -52,14 +52,18 @@ export function WorkerTestPart() {
{ id: string; status: "error" | "success"; error?: Error }[]
>([]);
+ const [buttonDisabled, setButtonDisabled] = useState(false);
+
const [testState, runTests] = useAsyncFn(async () => {
+ setButtonDisabled(true);
function updateWorker(id: string, data: (typeof workerState)[number]) {
setWorkerState((s) => {
return [...s.filter((v) => v.id !== id), data];
});
}
setWorkerState([]);
- for (const worker of workerList) {
+
+ const workerPromises = workerList.map(async (worker) => {
try {
if (worker.url.endsWith("/")) {
updateWorker(worker.id, {
@@ -67,7 +71,7 @@ export function WorkerTestPart() {
status: "error",
error: new Error("URL ends with slash"),
});
- continue;
+ return;
}
await singularProxiedFetch(
worker.url,
@@ -85,7 +89,10 @@ export function WorkerTestPart() {
error: err as Error,
});
}
- }
+ });
+
+ await Promise.all(workerPromises);
+ setTimeout(() => setButtonDisabled(false), 5000);
}, [workerList, setWorkerState]);
return (
@@ -112,7 +119,12 @@ export function WorkerTestPart() {
})}
-
+
Test workers
diff --git a/src/pages/parts/auth/LoginFormPart.tsx b/src/pages/parts/auth/LoginFormPart.tsx
index e4bed3ec..ac63fad2 100644
--- a/src/pages/parts/auth/LoginFormPart.tsx
+++ b/src/pages/parts/auth/LoginFormPart.tsx
@@ -52,6 +52,9 @@ export function LoginFormPart(props: LoginFormPartProps) {
throw err;
}
+ if (!account)
+ throw new Error(t("auth.login.validationError") ?? undefined);
+
await importData(account, progressItems, bookmarkItems);
await restore(account);
diff --git a/src/pages/parts/auth/TrustBackendPart.tsx b/src/pages/parts/auth/TrustBackendPart.tsx
index 3fa818ef..16c16c66 100644
--- a/src/pages/parts/auth/TrustBackendPart.tsx
+++ b/src/pages/parts/auth/TrustBackendPart.tsx
@@ -22,8 +22,12 @@ interface TrustBackendPartProps {
export function TrustBackendPart(props: TrustBackendPartProps) {
const navigate = useNavigate();
const backendUrl = useBackendUrl();
- const hostname = useMemo(() => new URL(backendUrl).hostname, [backendUrl]);
+ const hostname = useMemo(
+ () => (backendUrl ? new URL(backendUrl).hostname : undefined),
+ [backendUrl],
+ );
const result = useAsync(() => {
+ if (!backendUrl) return Promise.resolve(null);
return getBackendMeta(backendUrl);
}, [backendUrl]);
const { t } = useTranslation();
@@ -50,38 +54,52 @@ export function TrustBackendPart(props: TrustBackendPartProps) {
return (
}
>
-
-
-
+ {hostname ? (
+
+
+
+ ) : (
+ {t("auth.trust.noHost")}
+ )}
-
- {cardContent}
-
-
- navigate("/")}>
- {t("auth.trust.no")}
-
- result.value && props.onNext?.(result.value)}
- >
- {t("auth.trust.yes")}
-
-
-
-
- .
-
-
+ {hostname ? (
+ <>
+
+ {cardContent}
+
+
+ navigate("/")}>
+ {t("auth.trust.no")}
+
+ result.value && props.onNext?.(result.value)}
+ >
+ {t("auth.trust.yes")}
+
+
+
+
+ .
+
+
+ >
+ ) : (
+
+ navigate("/")}>
+ {t("auth.trust.no")}
+
+
+ )}
);
}
diff --git a/src/pages/parts/auth/VerifyPassphrasePart.tsx b/src/pages/parts/auth/VerifyPassphrasePart.tsx
index 2f040ccb..1d6ea62a 100644
--- a/src/pages/parts/auth/VerifyPassphrasePart.tsx
+++ b/src/pages/parts/auth/VerifyPassphrasePart.tsx
@@ -47,6 +47,8 @@ export function VerifyPassphrase(props: VerifyPassphraseProps) {
const [result, execute] = useAsyncFn(
async (inputMnemonic: string) => {
+ if (!backendUrl)
+ throw new Error(t("auth.verify.noBackendUrl") ?? undefined);
if (!props.mnemonic || !props.userData)
throw new Error(t("auth.verify.invalidData") ?? undefined);
@@ -68,6 +70,9 @@ export function VerifyPassphrase(props: VerifyPassphraseProps) {
recaptchaToken,
});
+ if (!account)
+ throw new Error(t("auth.verify.registrationFailed") ?? undefined);
+
await importData(account, progressItems, bookmarkItems);
await updateSettings(backendUrl, account, {
diff --git a/src/pages/parts/home/HeroPart.tsx b/src/pages/parts/home/HeroPart.tsx
index fab50f4d..9fdea893 100644
--- a/src/pages/parts/home/HeroPart.tsx
+++ b/src/pages/parts/home/HeroPart.tsx
@@ -1,5 +1,4 @@
import { useCallback, useEffect, useRef, useState } from "react";
-import { useTranslation } from "react-i18next";
import Sticky from "react-sticky-el";
import { useWindowSize } from "react-use";
@@ -26,7 +25,6 @@ function getTimeOfDay(date: Date): "night" | "morning" | "day" {
export function HeroPart({ setIsSticky, searchParams }: HeroPartProps) {
const { t: randomT } = useRandomTranslation();
- const { t } = useTranslation();
const [search, setSearch, setSearchUnFocus] = searchParams;
const [, setShowBg] = useState(false);
const bannerSize = useBannerSize();
@@ -54,7 +52,7 @@ export function HeroPart({ setIsSticky, searchParams }: HeroPartProps) {
const time = getTimeOfDay(new Date());
const title = randomT(`home.titles.${time}`);
-
+ const placeholder = randomT(`home.search.placeholder`);
const inputRef = useRef
(null);
useSlashFocus(inputRef);
@@ -77,7 +75,7 @@ export function HeroPart({ setIsSticky, searchParams }: HeroPartProps) {
onChange={setSearch}
value={search}
onUnFocus={setSearchUnFocus}
- placeholder={t("home.search.placeholder") ?? ""}
+ placeholder={placeholder ?? ""}
/>
diff --git a/src/pages/parts/settings/AccountActionsPart.tsx b/src/pages/parts/settings/AccountActionsPart.tsx
index e19aa9e9..f1e50dbf 100644
--- a/src/pages/parts/settings/AccountActionsPart.tsx
+++ b/src/pages/parts/settings/AccountActionsPart.tsx
@@ -18,7 +18,7 @@ export function AccountActionsPart() {
const deleteModal = useModal("account-delete");
const [deleteResult, deleteExec] = useAsyncFn(async () => {
- if (!account) return;
+ if (!account || !url) return;
await deleteUser(url, account);
await logout();
deleteModal.hide();
diff --git a/src/pages/parts/settings/ConnectionsPart.tsx b/src/pages/parts/settings/ConnectionsPart.tsx
index 9b693e9f..990c5de0 100644
--- a/src/pages/parts/settings/ConnectionsPart.tsx
+++ b/src/pages/parts/settings/ConnectionsPart.tsx
@@ -55,7 +55,7 @@ function ProxyEdit({ proxyUrls, setProxyUrls }: ProxyEditProps) {
-
+
Proxy documentation
@@ -125,7 +125,7 @@ function BackendEdit({ backendUrl, setBackendUrl }: BackendEditProps) {
-
+
Backend documentation
diff --git a/src/pages/parts/settings/DeviceListPart.tsx b/src/pages/parts/settings/DeviceListPart.tsx
index afaea589..c7326059 100644
--- a/src/pages/parts/settings/DeviceListPart.tsx
+++ b/src/pages/parts/settings/DeviceListPart.tsx
@@ -24,6 +24,7 @@ export function Device(props: {
const token = useAuthStore((s) => s.account?.token);
const [result, exec] = useAsyncFn(async () => {
if (!token) throw new Error("No token present");
+ if (!url) throw new Error("No backend set");
await removeSession(url, token, props.id);
props.onRemove?.();
}, [url, token, props.id]);
diff --git a/src/pages/parts/settings/SidebarPart.tsx b/src/pages/parts/settings/SidebarPart.tsx
index 47469a5e..13db06fe 100644
--- a/src/pages/parts/settings/SidebarPart.tsx
+++ b/src/pages/parts/settings/SidebarPart.tsx
@@ -14,9 +14,9 @@ import { useAuthStore } from "@/stores/auth";
const rem = 16;
-function SecureBadge(props: { url: string }) {
+function SecureBadge(props: { url: string | undefined }) {
const { t } = useTranslation();
- const secure = props.url.startsWith("https://");
+ const secure = props.url ? props.url.startsWith("https://") : false;
return (
@@ -68,6 +68,7 @@ export function SidebarPart() {
const backendUrl = useBackendUrl();
const backendMeta = useAsync(async () => {
+ if (!backendUrl) return;
return getBackendMeta(backendUrl);
}, [backendUrl]);
@@ -159,7 +160,7 @@ export function SidebarPart() {
- {backendUrl.replace(/https?:\/\//, "")}
+ {backendUrl?.replace(/https?:\/\//, "") ?? "—"}
diff --git a/src/pages/parts/settings/ThemePart.tsx b/src/pages/parts/settings/ThemePart.tsx
index 264c5b40..b5600181 100644
--- a/src/pages/parts/settings/ThemePart.tsx
+++ b/src/pages/parts/settings/ThemePart.tsx
@@ -5,20 +5,29 @@ import { Icon, Icons } from "@/components/Icon";
import { Heading1 } from "@/components/utils/Text";
const availableThemes = [
+ {
+ id: "default",
+ selector: "theme-default",
+ key: "settings.appearance.themes.default",
+ },
{
id: "blue",
+ selector: "theme-blue",
key: "settings.appearance.themes.blue",
},
{
id: "teal",
+ selector: "theme-teal",
key: "settings.appearance.themes.teal",
},
{
id: "red",
+ selector: "theme-red",
key: "settings.appearance.themes.red",
},
{
id: "gray",
+ selector: "theme-gray",
key: "settings.appearance.themes.gray",
},
];
@@ -26,6 +35,7 @@ const availableThemes = [
function ThemePreview(props: {
selector?: string;
active?: boolean;
+ inUse?: boolean;
name: string;
onClick?: () => void;
}) {
@@ -105,7 +115,7 @@ function ThemePreview(props: {
{t("settings.appearance.activeTheme")}
@@ -116,8 +126,9 @@ function ThemePreview(props: {
}
export function ThemePart(props: {
- active: string | null;
- setTheme: (theme: string | null) => void;
+ active: string;
+ inUse: string;
+ setTheme: (theme: string) => void;
}) {
const { t } = useTranslation();
@@ -125,17 +136,11 @@ export function ThemePart(props: {
{t("settings.appearance.title")}
- {/* default theme */}
-
props.setTheme(null)}
- />
{availableThemes.map((v) => (
props.setTheme(v.id)}
diff --git a/src/setup/constants.ts b/src/setup/constants.ts
index 27a9792b..935987e9 100644
--- a/src/setup/constants.ts
+++ b/src/setup/constants.ts
@@ -1,6 +1,6 @@
export const APP_VERSION = import.meta.env.PACKAGE_VERSION;
-export const DISCORD_LINK = "https://discord.movie-web.app";
+export const DISCORD_LINK = "https://movie-web.github.io/links/discord";
export const GITHUB_LINK = "https://github.com/movie-web/movie-web";
export const DONATION_LINK = "https://ko-fi.com/movieweb";
-export const GA_ID = "G-44YVXRL61C";
-export const BACKEND_URL = "https://backend.movie-web.app";
+export const GA_ID = import.meta.env.VITE_GA_ID;
+export const BACKEND_URL = import.meta.env.VITE_BACKEND_URL;
diff --git a/src/setup/ga.ts b/src/setup/ga.ts
index 1fbf488b..9b900c0d 100644
--- a/src/setup/ga.ts
+++ b/src/setup/ga.ts
@@ -2,8 +2,10 @@ import ReactGA from "react-ga4";
import { GA_ID } from "@/setup/constants";
-ReactGA.initialize([
- {
- trackingId: GA_ID,
- },
-]);
+if (GA_ID) {
+ ReactGA.initialize([
+ {
+ trackingId: GA_ID,
+ },
+ ]);
+}
diff --git a/src/stores/bookmarks/BookmarkSyncer.tsx b/src/stores/bookmarks/BookmarkSyncer.tsx
index 1f3b705d..7d4d0bc0 100644
--- a/src/stores/bookmarks/BookmarkSyncer.tsx
+++ b/src/stores/bookmarks/BookmarkSyncer.tsx
@@ -60,6 +60,7 @@ export function BookmarkSyncer() {
useEffect(() => {
const interval = setInterval(() => {
(async () => {
+ if (!url) return;
const state = useBookmarkStore.getState();
const user = useAuthStore.getState();
await syncBookmarks(
diff --git a/src/stores/progress/ProgressSyncer.tsx b/src/stores/progress/ProgressSyncer.tsx
index c9b4eb58..67aae877 100644
--- a/src/stores/progress/ProgressSyncer.tsx
+++ b/src/stores/progress/ProgressSyncer.tsx
@@ -62,6 +62,7 @@ export function ProgressSyncer() {
useEffect(() => {
const interval = setInterval(() => {
(async () => {
+ if (!url) return;
const state = useProgressStore.getState();
const user = useAuthStore.getState();
await syncProgress(
diff --git a/src/stores/subtitles/SettingsSyncer.tsx b/src/stores/subtitles/SettingsSyncer.tsx
index 48b25bbe..ca3c0817 100644
--- a/src/stores/subtitles/SettingsSyncer.tsx
+++ b/src/stores/subtitles/SettingsSyncer.tsx
@@ -16,6 +16,7 @@ export function SettingsSyncer() {
useEffect(() => {
const interval = setInterval(() => {
(async () => {
+ if (!url) return;
const state = useSubtitleStore.getState();
const user = useAuthStore.getState();
if (state.lastSync.lastSelectedLanguage === state.lastSelectedLanguage)
diff --git a/src/stores/theme/index.tsx b/src/stores/theme/index.tsx
index c43ec414..a644c626 100644
--- a/src/stores/theme/index.tsx
+++ b/src/stores/theme/index.tsx
@@ -25,12 +25,31 @@ export const useThemeStore = create(
),
);
+export interface PreviewThemeStore {
+ previewTheme: string | null;
+ setPreviewTheme(v: string | null): void;
+}
+
+export const usePreviewThemeStore = create(
+ immer((set) => ({
+ previewTheme: null,
+ setPreviewTheme(v) {
+ set((s) => {
+ s.previewTheme = v;
+ });
+ },
+ })),
+);
+
export function ThemeProvider(props: {
children?: ReactNode;
applyGlobal?: boolean;
}) {
+ const previewTheme = usePreviewThemeStore((s) => s.previewTheme);
const theme = useThemeStore((s) => s.theme);
- const themeSelector = theme ? `theme-${theme}` : undefined;
+
+ const themeToDisplay = previewTheme ?? theme;
+ const themeSelector = themeToDisplay ? `theme-${themeToDisplay}` : undefined;
return (