feat: move bs cloud to a more out of the way location and some theme store tweaks

This commit is contained in:
2026-02-25 10:55:36 +10:30
parent 01f5e8f61d
commit e50de00d08
5 changed files with 208 additions and 8 deletions
@@ -46,7 +46,7 @@
</h2> </h2>
<p class="mb-6 text-zinc-600 dark:text-zinc-400"> <p class="mb-6 text-zinc-600 dark:text-zinc-400">
Go to Settings → BetterSEQTA Cloud to sign in, or create an account to get started. Sign in in the Theme Store to save favorites across devices, or create an account to get started.
</p> </p>
<div class="flex flex-wrap gap-2 justify-end"> <div class="flex flex-wrap gap-2 justify-end">
@@ -0,0 +1,203 @@
<script lang="ts">
import { onMount } from "svelte";
import { cloudAuth } from "@/seqta/utils/CloudAuth";
let username = $state("");
let password = $state("");
let loading = $state(false);
let error = $state<string | null>(null);
let cloudState = $state(cloudAuth.state);
let open = $state(false);
let dropdownEl: HTMLElement;
onMount(() => {
const unsubscribe = cloudAuth.subscribe((state) => {
cloudState = state;
});
return unsubscribe;
});
function handleClickOutside(e: MouseEvent) {
if (dropdownEl && !dropdownEl.contains(e.target as Node)) {
open = false;
}
}
$effect(() => {
if (open) {
const timer = setTimeout(() => {
document.addEventListener("click", handleClickOutside);
}, 0);
return () => {
clearTimeout(timer);
document.removeEventListener("click", handleClickOutside);
};
}
});
async function handleLogin() {
if (loading) return;
error = null;
if (!username.trim() || !password) {
error = "Please enter username and password";
return;
}
loading = true;
try {
const result = await cloudAuth.login(username.trim(), password);
if (result.success) {
password = "";
open = false;
} else {
error = result.error ?? "Login failed";
}
} finally {
loading = false;
}
}
async function handleLogout() {
await cloudAuth.logout();
open = false;
}
function getInitials(): string {
const u = cloudState.user;
if (!u) return "?";
if (u.displayName) return u.displayName.slice(0, 2).toUpperCase();
if (u.username) return u.username.slice(0, 2).toUpperCase();
if (u.email) return u.email.slice(0, 2).toUpperCase();
return "?";
}
</script>
<div class="relative flex items-center" bind:this={dropdownEl}>
<button
type="button"
onclick={() => (open = !open)}
class="flex items-center gap-2 px-3 py-2 rounded-lg bg-zinc-100/80 dark:bg-zinc-700/80 hover:bg-zinc-200/80 dark:hover:bg-zinc-600/80 transition-colors duration-200 text-base font-medium text-zinc-900 dark:text-white"
>
{#if cloudState.isLoggedIn}
{#if cloudState.user?.pfpUrl}
<img
src={cloudState.user.pfpUrl}
alt=""
class="w-8 h-8 rounded-full object-cover ring-2 ring-zinc-200 dark:ring-zinc-600"
/>
{:else}
<div class="flex items-center justify-center w-8 h-8 rounded-full bg-zinc-300 dark:bg-zinc-600 text-zinc-700 dark:text-zinc-200 font-semibold text-sm">
{getInitials()}
</div>
{/if}
<span class="hidden max-w-24 truncate sm:inline text-base">
{cloudState.user?.displayName || cloudState.user?.username || cloudState.user?.email || "User"}
</span>
{:else}
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 15a4.5 4.5 0 004.5 4.5H18a3.75 3.75 0 001.332-7.257 3 3 0 00-3.758-3.848 5.25 5.25 0 00-10.233 2.33A4.502 4.502 0 0012.75 15h-10.5z" />
</svg>
<span class="text-base font-medium">Sign in</span>
{/if}
</button>
{#if open}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="absolute right-0 top-full mt-2 w-80 rounded-xl border border-zinc-200 dark:border-zinc-600 bg-white dark:bg-zinc-800 shadow-xl z-[100] overflow-hidden"
onclick={(e) => e.stopPropagation()}
>
<div class="p-4 border-b border-zinc-200 dark:border-zinc-600">
<h3 class="text-xl font-bold text-zinc-900 dark:text-white">BetterSEQTA Cloud</h3>
<p class="text-base text-zinc-500 dark:text-zinc-400">Sync favorites across devices</p>
</div>
<div class="p-4">
{#if cloudState.isLoggedIn}
<div class="flex flex-col gap-3">
<div class="flex items-center gap-3">
{#if cloudState.user?.pfpUrl}
<img
src={cloudState.user.pfpUrl}
alt=""
class="w-12 h-12 rounded-full object-cover ring-2 ring-zinc-200 dark:ring-zinc-600"
/>
{:else}
<div class="flex items-center justify-center w-12 h-12 rounded-full bg-zinc-300 dark:bg-zinc-600 text-zinc-700 dark:text-zinc-200 font-semibold text-base">
{getInitials()}
</div>
{/if}
<div class="min-w-0 flex-1">
<p class="text-base font-medium text-zinc-900 dark:text-white truncate">
{cloudState.user?.displayName || cloudState.user?.username || cloudState.user?.email || "User"}
</p>
{#if cloudState.user?.email && cloudState.user?.email !== (cloudState.user?.displayName || cloudState.user?.username)}
<p class="text-base text-zinc-500 dark:text-zinc-400 truncate">{cloudState.user.email}</p>
{/if}
</div>
</div>
<button
type="button"
onclick={handleLogout}
class="w-full px-4 py-3 text-base font-medium rounded-lg bg-zinc-200 dark:bg-zinc-700 text-zinc-900 dark:text-white hover:bg-zinc-300 dark:hover:bg-zinc-600 transition-colors duration-200"
>
Sign out
</button>
</div>
{:else}
<p class="mb-4 text-base text-zinc-600 dark:text-zinc-400">
Sign in to favorite themes. Your favorites sync across devices when logged in.
</p>
<form
class="flex flex-col gap-3"
autocomplete="off"
onsubmit={(e) => {
e.preventDefault();
handleLogin();
}}
>
<input
type="text"
name="betterseqta-cloud-username"
autocomplete="off"
placeholder="Email or username"
bind:value={username}
disabled={loading}
readonly
onfocus={(e) => e.currentTarget.removeAttribute('readonly')}
class="w-full px-4 py-3 text-base rounded-lg bg-zinc-100 dark:bg-zinc-800 dark:text-white border border-zinc-200 dark:border-zinc-600 focus:outline-none focus:ring-2 focus:ring-accent-ring focus:border-transparent transition-colors duration-200"
/>
<input
type="password"
name="betterseqta-cloud-password"
autocomplete="new-password"
placeholder="Password"
bind:value={password}
disabled={loading}
readonly
onfocus={(e) => e.currentTarget.removeAttribute('readonly')}
class="w-full px-4 py-3 text-base rounded-lg bg-zinc-100 dark:bg-zinc-800 dark:text-white border border-zinc-200 dark:border-zinc-600 focus:outline-none focus:ring-2 focus:ring-accent-ring focus:border-transparent transition-colors duration-200"
/>
{#if error}
<p class="text-base text-red-600 dark:text-red-400">{error}</p>
{/if}
<button
type="submit"
disabled={loading}
class="w-full px-4 py-3 text-base font-medium rounded-lg bg-zinc-800 dark:bg-zinc-200 text-white dark:text-zinc-900 hover:bg-zinc-700 dark:hover:bg-zinc-300 disabled:opacity-50 transition-colors duration-200"
>
{loading ? "Signing in..." : "Sign in"}
</button>
<a
href="https://accounts.betterseqta.org/register"
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center justify-center gap-2 px-4 py-3 text-base font-medium rounded-lg border border-zinc-200 dark:border-zinc-600 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-all duration-200"
>
Create account
</a>
</form>
{/if}
</div>
</div>
{/if}
</div>
@@ -29,7 +29,7 @@
{#if coverThemes.length > 0} {#if coverThemes.length > 0}
<div class="relative w-full overflow-clip rounded-xl transition-opacity" transition:fade> <div class="relative w-full overflow-clip rounded-xl transition-opacity" transition:fade>
<div <div
class="w-full aspect-8/3" class="w-full aspect-[5/1] max-h-[500px]"
use:emblaCarouselSvelte={{ options, plugins }} use:emblaCarouselSvelte={{ options, plugins }}
onemblaInit={onInit} onemblaInit={onInit}
> >
@@ -3,6 +3,7 @@
import logoDark from '@/resources/icons/betterseqta-light-full.png'; import logoDark from '@/resources/icons/betterseqta-light-full.png';
import { closeStore } from '@/seqta/ui/renderStore' import { closeStore } from '@/seqta/ui/renderStore'
import browser from 'webextension-polyfill'; import browser from 'webextension-polyfill';
import CloudHeader from './CloudHeader.svelte';
// Props // Props
let { searchTerm, setSearchTerm, darkMode, activeTab, setActiveTab } = $props<{ let { searchTerm, setSearchTerm, darkMode, activeTab, setActiveTab } = $props<{
@@ -39,6 +40,8 @@
> >
Backgrounds Backgrounds
</button> </button>
<CloudHeader />
</div> </div>
<div class="flex relative gap-2"> <div class="flex relative gap-2">
-6
View File
@@ -3,7 +3,6 @@
import Settings from "./settings/general.svelte"; import Settings from "./settings/general.svelte";
import Shortcuts from "./settings/shortcuts.svelte"; import Shortcuts from "./settings/shortcuts.svelte";
import Theme from "./settings/theme.svelte"; import Theme from "./settings/theme.svelte";
import Cloud from "./settings/cloud.svelte";
import browser from "webextension-polyfill"; import browser from "webextension-polyfill";
import { standalone as StandaloneStore } from "../utils/standalone.svelte"; import { standalone as StandaloneStore } from "../utils/standalone.svelte";
@@ -78,10 +77,6 @@
showColourPicker = false; showColourPicker = false;
}); });
if (window.location.hash === "#cloud") {
settingsActiveTab = 3;
}
if (standalone) { if (standalone) {
StandaloneStore.setStandalone(true); StandaloneStore.setStandalone(true);
} }
@@ -291,7 +286,6 @@
}, },
{ title: "Shortcuts", Content: Shortcuts }, { title: "Shortcuts", Content: Shortcuts },
{ title: "Themes", Content: Theme }, { title: "Themes", Content: Theme },
{ title: "BetterSEQTA Cloud", Content: Cloud },
]} ]}
/> />
</div> </div>