diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx index 14e3ca9a..deb8f6a5 100644 --- a/src/AppRouter.tsx +++ b/src/AppRouter.tsx @@ -11,6 +11,11 @@ import { NIP19Page } from "./pages/NIP19Page"; import { NotificationsPage } from "./pages/NotificationsPage"; import { SearchPage } from "./pages/SearchPage"; import { SettingsPage } from "./pages/SettingsPage"; +import { ProfileSettings } from "./pages/ProfileSettings"; +import { AppearanceSettings } from "./pages/AppearanceSettings"; +import { WalletSettingsPage } from "./pages/WalletSettingsPage"; +import { NotificationSettings } from "./pages/NotificationSettings"; +import { AdvancedSettingsPage } from "./pages/AdvancedSettingsPage"; import { HashtagPage } from "./pages/HashtagPage"; import { DomainFeedPage } from "./pages/DomainFeedPage"; import { BookmarksPage } from "./pages/BookmarksPage"; @@ -40,7 +45,12 @@ export function AppRouter() { } /> } /> } /> - } /> + } /> + } /> + } /> + } /> + } /> + } /> } />} /> } />} /> } /> diff --git a/src/components/AdvancedSettings.tsx b/src/components/AdvancedSettings.tsx index 39806538..c9720633 100644 --- a/src/components/AdvancedSettings.tsx +++ b/src/components/AdvancedSettings.tsx @@ -4,7 +4,6 @@ import { Button } from '@/components/ui/button'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; -import { WalletSettings } from '@/components/WalletSettings'; import { RelayListManager } from '@/components/RelayListManager'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useAppContext } from '@/hooks/useAppContext'; @@ -14,7 +13,6 @@ export function AdvancedSettings() { const { user } = useCurrentUser(); const { config, updateConfig } = useAppContext(); const { toast } = useToast(); - const [walletOpen, setWalletOpen] = useState(false); const [relaysOpen, setRelaysOpen] = useState(false); const [statsOpen, setStatsOpen] = useState(false); const [servicesOpen, setServicesOpen] = useState(false); @@ -74,35 +72,6 @@ export function AdvancedSettings() {

- {/* Wallet Section — only when logged in */} - {user && ( -
- - - - - -
-
- -
-
-
-
-
- )} - {/* Network (Relays) Section — only when logged in */} {user && (
diff --git a/src/components/ThemeSelector.tsx b/src/components/ThemeSelector.tsx new file mode 100644 index 00000000..c7cee8bd --- /dev/null +++ b/src/components/ThemeSelector.tsx @@ -0,0 +1,128 @@ +import { Check, Plus } from 'lucide-react'; +import { type Theme } from '@/contexts/AppContext'; +import { useTheme } from '@/hooks/useTheme'; +import { themes, type ThemeTokens } from '@/themes'; +import { cn } from '@/lib/utils'; + +interface ThemeOption { + id: Theme | 'custom'; + label: string; + tokens?: ThemeTokens; +} + +const themeOptions: ThemeOption[] = [ + { id: 'light', label: 'Light', tokens: themes.light }, + { id: 'dark', label: 'Dark', tokens: themes.dark }, + { id: 'black', label: 'Black', tokens: themes.black }, + { id: 'pink', label: 'Pink', tokens: themes.pink }, + { id: 'custom', label: 'Custom' }, +]; + +/** Extracts HSL color string from a theme token value like "258 70% 55%" */ +function hsl(value: string): string { + return `hsl(${value})`; +} + +export function ThemeSelector() { + const { theme, setTheme } = useTheme(); + + return ( +
+
+ {themeOptions.map((option) => { + if (option.id === 'custom') { + return ( + + ); + } + + const tokens = option.tokens!; + const isActive = theme === option.id; + + return ( + + ); + })} +
+
+ ); +} diff --git a/src/pages/AdvancedSettingsPage.tsx b/src/pages/AdvancedSettingsPage.tsx new file mode 100644 index 00000000..41d4705f --- /dev/null +++ b/src/pages/AdvancedSettingsPage.tsx @@ -0,0 +1,34 @@ +import { useSeoMeta } from '@unhead/react'; +import { ArrowLeft } from 'lucide-react'; +import { Link } from 'react-router-dom'; +import { AdvancedSettings } from '@/components/AdvancedSettings'; + +export function AdvancedSettingsPage() { + useSeoMeta({ + title: 'Advanced | Settings | Ditto', + description: 'Advanced settings for relays, upload servers, and system configuration', + }); + + return ( +
+ {/* Header with back link */} +
+
+ + + +
+

Advanced

+

+ Relays, upload servers, and system settings +

+
+
+
+ +
+ +
+
+ ); +} diff --git a/src/pages/AppearanceSettings.tsx b/src/pages/AppearanceSettings.tsx new file mode 100644 index 00000000..12edc1fa --- /dev/null +++ b/src/pages/AppearanceSettings.tsx @@ -0,0 +1,83 @@ +import { useSeoMeta } from '@unhead/react'; +import { ArrowLeft } from 'lucide-react'; +import { Link, useParams } from 'react-router-dom'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { ThemeSelector } from '@/components/ThemeSelector'; +import { ContentSettings } from '@/components/ContentSettings'; +import { cn, STICKY_HEADER_CLASS } from '@/lib/utils'; + +type AppearanceTab = 'theme' | 'content'; + +export function AppearanceSettings() { + const { tab } = useParams<{ tab?: string }>(); + const activeTab: AppearanceTab = tab === 'content' ? 'content' : 'theme'; + + useSeoMeta({ + title: 'Appearance | Settings | Ditto', + description: 'Customize your display preferences and content settings', + }); + + return ( +
+ {/* Header with back link */} +
+
+ + + +
+

Appearance

+

+ Theme, display preferences, and content settings +

+
+
+
+ + {/* Sub-section tabs */} +
+ + +
+ + {/* Content */} +
+ {activeTab === 'theme' ? ( +
+ {/* Theme selector */} + + + Theme +

+ Choose a theme for the interface +

+
+ + + +
+
+ ) : ( + + )} +
+
+ ); +} + +function AppearanceTabLink({ to, label, active }: { to: string; label: string; active: boolean }) { + return ( + + {label} + {active && ( +
+ )} + + ); +} diff --git a/src/pages/NotificationSettings.tsx b/src/pages/NotificationSettings.tsx new file mode 100644 index 00000000..64daa934 --- /dev/null +++ b/src/pages/NotificationSettings.tsx @@ -0,0 +1,124 @@ +import { useState, useEffect } from 'react'; +import { useSeoMeta } from '@unhead/react'; +import { ArrowLeft, Bell, BellOff } from 'lucide-react'; +import { Link, Navigate } from 'react-router-dom'; +import { Card, CardContent } from '@/components/ui/card'; +import { Switch } from '@/components/ui/switch'; +import { Label } from '@/components/ui/label'; +import { Badge } from '@/components/ui/badge'; +import { useCurrentUser } from '@/hooks/useCurrentUser'; + +export function NotificationSettings() { + const { user } = useCurrentUser(); + const [pushEnabled, setPushEnabled] = useState(false); + const [permission, setPermission] = useState('default'); + + useSeoMeta({ + title: 'Notifications | Settings | Ditto', + description: 'Configure your notification preferences', + }); + + // Check current permission state on mount + useEffect(() => { + if ('Notification' in window) { + setPermission(Notification.permission); + setPushEnabled(Notification.permission === 'granted'); + } + }, []); + + const handleTogglePush = async (enabled: boolean) => { + if (!('Notification' in window)) return; + + if (enabled) { + const result = await Notification.requestPermission(); + setPermission(result); + setPushEnabled(result === 'granted'); + } else { + // Browser doesn't allow revoking permissions programmatically, + // but we can track the user's preference + setPushEnabled(false); + } + }; + + if (!user) { + return ; + } + + const isSupported = 'Notification' in window; + const isDenied = permission === 'denied'; + + return ( +
+ {/* Header with back link */} +
+
+ + + +
+

Notifications

+

+ Configure push notification preferences +

+
+
+
+ +
+ {/* Push notifications toggle */} + + +
+
+
+ {pushEnabled ? ( + + ) : ( + + )} +
+
+ +

+ Receive notifications for mentions, replies, and zaps +

+
+
+ +
+ + {/* Status info */} + {!isSupported && ( +
+ + Not Supported + +

+ Your browser does not support push notifications. +

+
+ )} + + {isDenied && ( +
+ + Blocked + +

+ Notifications are blocked. Update your browser settings to allow notifications from this site. +

+
+ )} +
+
+
+
+ ); +} diff --git a/src/pages/ProfileSettings.tsx b/src/pages/ProfileSettings.tsx new file mode 100644 index 00000000..618d2cc4 --- /dev/null +++ b/src/pages/ProfileSettings.tsx @@ -0,0 +1,41 @@ +import { useSeoMeta } from '@unhead/react'; +import { ArrowLeft } from 'lucide-react'; +import { Link, Navigate } from 'react-router-dom'; +import { EditProfileForm } from '@/components/EditProfileForm'; +import { useCurrentUser } from '@/hooks/useCurrentUser'; + +export function ProfileSettings() { + const { user } = useCurrentUser(); + + useSeoMeta({ + title: 'Profile | Settings | Ditto', + description: 'Edit your Ditto profile', + }); + + if (!user) { + return ; + } + + return ( +
+ {/* Header with back link */} +
+
+ + + +
+

Profile

+

+ Edit your display name, bio, and avatar +

+
+
+
+ +
+ +
+
+ ); +} diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index c1106a6a..fe9bd01c 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -1,73 +1,118 @@ import { useSeoMeta } from '@unhead/react'; -import { ArrowLeft, Settings as SettingsIcon } from 'lucide-react'; -import { Link, useParams } from 'react-router-dom'; -import { EditProfileForm } from '@/components/EditProfileForm'; -import { ContentSettings } from '@/components/ContentSettings'; -import { AdvancedSettings } from '@/components/AdvancedSettings'; +import { ArrowLeft, Bell, ChevronRight, Palette, Server, Settings as SettingsIcon, User, Wallet } from 'lucide-react'; +import { Link, useNavigate } from 'react-router-dom'; +import { Card, CardContent } from '@/components/ui/card'; import { useCurrentUser } from '@/hooks/useCurrentUser'; -import { cn, STICKY_HEADER_CLASS } from '@/lib/utils'; + +interface SettingsSection { + id: string; + label: string; + description: string; + icon: React.ComponentType<{ className?: string }>; + path: string; + requiresAuth?: boolean; +} + +const sections: SettingsSection[] = [ + { + id: 'profile', + label: 'Profile', + description: 'Edit your display name, bio, and avatar', + icon: User, + path: '/settings/profile', + requiresAuth: true, + }, + { + id: 'appearance', + label: 'Appearance', + description: 'Theme, display preferences, and content settings', + icon: Palette, + path: '/settings/appearance', + }, + { + id: 'wallet', + label: 'Wallet', + description: 'Manage wallet connections and payments', + icon: Wallet, + path: '/settings/wallet', + requiresAuth: true, + }, + { + id: 'notifications', + label: 'Notifications', + description: 'Configure push notification preferences', + icon: Bell, + path: '/settings/notifications', + requiresAuth: true, + }, + { + id: 'advanced', + label: 'Advanced', + description: 'Relays, upload servers, and system settings', + icon: Server, + path: '/settings/advanced', + }, +]; export function SettingsPage() { - const { section } = useParams<{ section?: string }>(); const { user } = useCurrentUser(); + const navigate = useNavigate(); useSeoMeta({ title: 'Settings | Ditto', description: 'Manage your Ditto settings', }); - const activeSection = section || (user ? 'profile' : 'content'); + const visibleSections = sections.filter( + (section) => !section.requiresAuth || user, + ); return ( -
-
-
- - - -
-
- -

Settings

-
-

Customize your profile, content, and advanced features

+
+ {/* Header */} +
+
+ + + +
+
+ +

Settings

+

+ Customize your experience +

+
- {/* Tab navigation */} -
- {user && } - - -
- -
- {activeSection === 'content' ? ( - - ) : activeSection === 'advanced' ? ( - - ) : user && activeSection === 'profile' ? ( - - ) : null} -
-
- ); -} - -function SettingsTab({ to, label, active }: { to: string; label: string; active: boolean }) { - return ( - - {label} - {active && ( -
- )} - + {/* Settings menu */} +
+ {visibleSections.map((section) => { + const Icon = section.icon; + return ( + navigate(section.path)} + > + +
+ +
+
+

{section.label}

+

+ {section.description} +

+
+ +
+
+ ); + })} +
+
); } diff --git a/src/pages/WalletSettingsPage.tsx b/src/pages/WalletSettingsPage.tsx new file mode 100644 index 00000000..b0d746b1 --- /dev/null +++ b/src/pages/WalletSettingsPage.tsx @@ -0,0 +1,41 @@ +import { useSeoMeta } from '@unhead/react'; +import { ArrowLeft } from 'lucide-react'; +import { Link, Navigate } from 'react-router-dom'; +import { WalletSettings } from '@/components/WalletSettings'; +import { useCurrentUser } from '@/hooks/useCurrentUser'; + +export function WalletSettingsPage() { + const { user } = useCurrentUser(); + + useSeoMeta({ + title: 'Wallet | Settings | Ditto', + description: 'Manage your wallet connections', + }); + + if (!user) { + return ; + } + + return ( +
+ {/* Header with back link */} +
+
+ + + +
+

Wallet

+

+ Manage wallet connections and payments +

+
+
+
+ +
+ +
+
+ ); +}