diff --git a/src/background.ts b/src/background.ts index 52941557..0adf7a5d 100644 --- a/src/background.ts +++ b/src/background.ts @@ -78,6 +78,27 @@ browser.runtime.onMessage.addListener( return true; } + case "fetchThemeDetails": { + const { themeId, token } = request; + if (!themeId || typeof themeId !== "string") { + sendResponse({ success: false, error: "Missing themeId" }); + return false; + } + const headers: Record = {}; + if (token) headers["Authorization"] = `Bearer ${token}`; + fetch(`https://betterseqta.org/api/themes/${themeId}`, { + cache: "no-store", + headers, + }) + .then((r) => r.json()) + .then(sendResponse) + .catch((err) => { + console.error("[Background] fetchThemeDetails error:", err); + sendResponse({ success: false, error: err?.message }); + }); + return true; + } + case "fetchFromUrl": { const { url } = request; if (!url || typeof url !== "string") { diff --git a/src/interface/components/SignInToFavoriteModal.svelte b/src/interface/components/SignInToFavoriteModal.svelte new file mode 100644 index 00000000..e3d5b29c --- /dev/null +++ b/src/interface/components/SignInToFavoriteModal.svelte @@ -0,0 +1,77 @@ + + +
{ + if (e.target === e.currentTarget) onClose(); + }} + onkeydown={(e) => { + if (e.key === "Escape") onClose(); + }} + role="button" + tabindex="-1" + transition:fade={{ duration: 150 }} +> + +
e.stopPropagation()} + onkeydown={(e) => e.stopPropagation()} + > +

+ Sign in to favorite themes +

+ +

+ Go to Settings → BetterSEQTA Cloud to sign in, or create an account to get started. +

+ +
+ + + Create account + + +
+
+
diff --git a/src/interface/components/TabbedContainer.svelte b/src/interface/components/TabbedContainer.svelte index 43a62349..d053f890 100644 --- a/src/interface/components/TabbedContainer.svelte +++ b/src/interface/components/TabbedContainer.svelte @@ -3,8 +3,7 @@ import './TabbedContainer.css'; import { onMount } from 'svelte'; - let { tabs } = $props<{ tabs: { title: string, Content: any, props?: any }[] }>(); - let activeTab = $state(0); + let { tabs, activeTab = $bindable(0) } = $props<{ tabs: { title: string, Content: any, props?: any }[]; activeTab?: number }>(); let containerRef: HTMLElement | null = null; let tabWidth = $state(0); diff --git a/src/interface/components/store/ThemeCard.svelte b/src/interface/components/store/ThemeCard.svelte index 5ca16782..7b28a504 100644 --- a/src/interface/components/store/ThemeCard.svelte +++ b/src/interface/components/store/ThemeCard.svelte @@ -2,6 +2,7 @@ import type { Theme } from '@/interface/types/Theme' import { fade } from 'svelte/transition'; import { onMount } from 'svelte'; + import SignInToFavoriteModal from '@/interface/components/SignInToFavoriteModal.svelte'; let { theme, onClick, toggleFavorite, isLoggedIn } = $props<{ theme: Theme; @@ -10,6 +11,7 @@ isLoggedIn: boolean; }>(); let menuOpen = $state(false); + let showSignInModal = $state(false); let menuRef: HTMLDivElement; onMount(() => { @@ -29,7 +31,11 @@ function handleFavoriteClick(e: MouseEvent) { e.stopPropagation(); - if (isLoggedIn) toggleFavorite(theme); + if (isLoggedIn) { + toggleFavorite(theme); + } else { + showSignInModal = true; + } menuOpen = false; } @@ -55,7 +61,7 @@ > - {/if} -

+

{theme.name}

+
+ + + + + {(theme.download_count ?? 0).toLocaleString()} downloads + + + + + + {(theme.favorite_count ?? 0).toLocaleString()} favorites + +
Theme Cover

{theme.description}

- {#if currentThemes.includes(theme.id)} - - {:else} - - {/if} + {theme.is_favorited ? 'Favorited' : 'Favorite'} + + {/if} + {#if currentThemes.includes(theme.id)} + + {:else} + + {/if} +
@@ -148,3 +168,7 @@ + +{#if showSignInModal} + (showSignInModal = false)} /> +{/if} diff --git a/src/interface/components/themes/ThemeSelector.svelte b/src/interface/components/themes/ThemeSelector.svelte index e8606c6f..80be9e8d 100644 --- a/src/interface/components/themes/ThemeSelector.svelte +++ b/src/interface/components/themes/ThemeSelector.svelte @@ -1,11 +1,14 @@ @@ -276,6 +282,7 @@
-
-

BetterSEQTA Cloud

-

- Sign in to favorite themes in the theme store. Your favorites sync across devices when logged in. -

- - {#if cloudState.isLoggedIn} -
-

- Signed in as - - {cloudState.user?.displayName || cloudState.user?.username || cloudState.user?.email || "User"} - -

-
+ +
+ {#if cloudState.isLoggedIn} + +
+
+ {#if cloudState.user?.pfpUrl} + + {:else} +
+ {getInitials()} +
+ {/if} +
+

+ {cloudState.user?.displayName || cloudState.user?.username || cloudState.user?.email || "User"} +

+ {#if cloudState.user?.email && cloudState.user?.email !== (cloudState.user?.displayName || cloudState.user?.username)} +

{cloudState.user.email}

+ {/if} +
+
+
- - {/if} + {:else} + +

+ Sign in to favorite themes in the store. Your favorites sync across devices when logged in. +

+
{ + e.preventDefault(); + handleLogin(); + }} + > +
+ + +
+
+ + +
+ {#if error} +

{error}

+ {/if} + +
+ {/if} +
diff --git a/src/interface/pages/store.svelte b/src/interface/pages/store.svelte index ba7835dc..67de3f78 100644 --- a/src/interface/pages/store.svelte +++ b/src/interface/pages/store.svelte @@ -63,11 +63,18 @@ action: isFavorite ? 'favorite' : 'unfavorite', })) as { success?: boolean }; if (result?.success) { + const delta = isFavorite ? 1 : -1; themes = themes.map((t) => - t.id === theme.id ? { ...t, is_favorited: isFavorite } : t + t.id === theme.id + ? { ...t, is_favorited: isFavorite, favorite_count: Math.max(0, (t.favorite_count ?? 0) + delta) } + : t ); if (displayTheme?.id === theme.id) { - displayTheme = { ...displayTheme, is_favorited: isFavorite }; + displayTheme = { + ...displayTheme, + is_favorited: isFavorite, + favorite_count: Math.max(0, (displayTheme.favorite_count ?? 0) + delta), + }; } } }; diff --git a/src/interface/types/Theme.ts b/src/interface/types/Theme.ts index 1ec639b0..28fb7918 100644 --- a/src/interface/types/Theme.ts +++ b/src/interface/types/Theme.ts @@ -7,4 +7,5 @@ export type Theme = { theme_json_url?: string; is_favorited?: boolean; favorite_count?: number; + download_count?: number; };