diff --git a/src/components/ThemeSelector.tsx b/src/components/ThemeSelector.tsx
index dfa94967..5dbcbaff 100644
--- a/src/components/ThemeSelector.tsx
+++ b/src/components/ThemeSelector.tsx
@@ -1,5 +1,5 @@
-import { Check, Paintbrush, Globe } from 'lucide-react';
-import { Link } from 'react-router-dom';
+import { Check, Globe, Plus } from 'lucide-react';
+import { Link, useNavigate } from 'react-router-dom';
import { type Theme } from '@/contexts/AppContext';
import { useTheme } from '@/hooks/useTheme';
import { useCurrentUser } from '@/hooks/useCurrentUser';
@@ -78,6 +78,9 @@ export function ThemeSelector() {
const { theme, customTheme, setTheme, applyCustomTheme } = useTheme();
const { user } = useCurrentUser();
const userThemesQuery = useUserThemes(user?.pubkey);
+ const navigate = useNavigate();
+
+ const hasUserThemes = (userThemesQuery.data?.length ?? 0) > 0;
const builtinOptions: { id: Theme; label: string }[] = [
{ id: 'system', label: 'System' },
@@ -98,8 +101,79 @@ export function ThemeSelector() {
};
return (
-
-
+
+
+ {/* ── My Themes section ── */}
+ {user && (
+
+
My Themes
+
+ {/* Create new custom theme */}
+
+
+
+ Create Theme
+
+
+
+ {/* User's published themes */}
+ {userThemesQuery.data?.map((userTheme) => {
+ const isActive = theme === 'custom' && customTheme && JSON.stringify(customTheme) === JSON.stringify(userTheme.tokens);
+
+ return (
+
+ );
+ })}
+
+ {/* Browse public themes */}
+
+
+
+ Browse
+
+
+ Public Themes
+
+
+
+
+ )}
+
+ {/* ── Presets section ── */}
+
+
+ {hasUserThemes ? 'Presets' : 'Themes'}
+
+
{builtinOptions.map((option) => {
if (option.id === 'system') {
const isActive = theme === 'system';
@@ -195,19 +269,6 @@ export function ThemeSelector() {
);
})}
- {/* Active custom theme (if it doesn't match any preset) */}
- {theme === 'custom' && customTheme && !presetOptions.some(p => isPresetActive(p.tokens)) && (
-
- )}
-
{/* Preset buttons */}
{presetOptions.map((preset) => {
const isActive = isPresetActive(preset.tokens);
@@ -234,55 +295,23 @@ export function ThemeSelector() {
);
})}
- {/* User's published themes */}
- {userThemesQuery.data?.map((userTheme) => {
- const isActive = theme === 'custom' && customTheme && JSON.stringify(customTheme) === JSON.stringify(userTheme.tokens);
-
- return (
-
- );
- })}
-
- {/* Browse public themes */}
-
-
-
- Browse
-
-
- Public Themes
-
-
+ {/* Browse public themes — shown in presets section when user has no custom themes */}
+ {!user && (
+
+
+
+ Browse
+
+
+ Public Themes
+
+
+ )}
-
- {/* Customize link */}
-
-
- Customize your own theme
-
+
);
}
diff --git a/src/pages/ThemeBuilderPage.tsx b/src/pages/ThemeBuilderPage.tsx
index dd7833c9..b0eab633 100644
--- a/src/pages/ThemeBuilderPage.tsx
+++ b/src/pages/ThemeBuilderPage.tsx
@@ -95,9 +95,10 @@ export function ThemeBuilderPage() {
const { user } = useCurrentUser();
const { publishTheme, setActiveTheme, deleteTheme, isPending: isPublishing } = usePublishTheme();
- // Check if we're importing from a profile
+ // Check if we're importing from a profile or editing an existing theme
const importPubkey = searchParams.get('import');
const importThemeId = searchParams.get('theme');
+ const editIdentifier = searchParams.get('edit');
// Check if the user currently has a published active profile theme
const ownActiveTheme = useActiveProfileTheme(user?.pubkey);
@@ -135,6 +136,17 @@ export function ThemeBuilderPage() {
if (match) setActiveEditingTheme(match);
}, [_userThemes.data]); // eslint-disable-line react-hooks/exhaustive-deps
+ // Load a specific theme for editing via ?edit= param
+ useEffect(() => {
+ if (!editIdentifier || !_userThemes.data) return;
+ const target = _userThemes.data.find(t => t.identifier === editIdentifier);
+ if (target && activeEditingTheme?.identifier !== editIdentifier) {
+ setTokens(target.tokens);
+ setAutoDerive(false);
+ setActiveEditingTheme(target);
+ }
+ }, [editIdentifier, _userThemes.data]); // eslint-disable-line react-hooks/exhaustive-deps
+
// Clear editing context when starting fresh
const handleNewTheme = useCallback(() => {
setActiveEditingTheme(null);