Refactor AppProvider, AppConfig and presetRelays

This commit is contained in:
Alex Gleason 2025-06-04 10:20:10 -05:00
parent 3ad83cd114
commit 048bf4cc69
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
8 changed files with 46 additions and 26 deletions

View File

@ -27,9 +27,16 @@ const defaultConfig: AppConfig = {
relayUrl: "wss://relay.nostr.band",
};
const presetRelays = [
{ url: 'wss://ditto.pub/relay', name: 'Ditto' },
{ url: 'wss://relay.nostr.band', name: 'Nostr.Band' },
{ url: 'wss://relay.damus.io', name: 'Damus' },
{ url: 'wss://relay.primal.net', name: 'Primal' },
];
export function App() {
return (
<AppProvider storageKey="nostr:app-config" defaultConfig={defaultConfig}>
<AppProvider storageKey="nostr:app-config" defaultConfig={defaultConfig} presetRelays={presetRelays}>
<QueryClientProvider client={queryClient}>
<NostrLoginProvider storageKey='nostr:login'>
<NostrProvider>

View File

@ -8,13 +8,16 @@ interface AppProviderProps {
storageKey: string;
/** Default app configuration */
defaultConfig: AppConfig;
/** Optional list of preset relays to display in the RelaySelector */
presetRelays?: { name: string; url: string }[];
}
export function AppProvider(props: AppProviderProps) {
const {
children,
storageKey,
defaultConfig
defaultConfig,
presetRelays,
} = props;
// App configuration state with localStorage persistence
@ -28,6 +31,7 @@ export function AppProvider(props: AppProviderProps) {
const appContextValue: AppContextType = {
config,
updateConfig,
presetRelays,
};
// Apply theme effects to document

View File

@ -2,7 +2,7 @@ import React, { useEffect, useRef } from 'react';
import { NostrEvent, NPool, NRelay1 } from '@nostrify/nostrify';
import { NostrContext } from '@nostrify/react';
import { useQueryClient } from '@tanstack/react-query';
import { useAppConfig } from '@/hooks/useAppConfig';
import { useAppContext } from '@/hooks/useAppContext';
interface NostrProviderProps {
children: React.ReactNode;
@ -10,7 +10,7 @@ interface NostrProviderProps {
const NostrProvider: React.FC<NostrProviderProps> = (props) => {
const { children } = props;
const { config } = useAppConfig();
const { config } = useAppContext();
const queryClient = useQueryClient();

View File

@ -14,21 +14,22 @@ import {
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { useAppConfig } from "@/hooks/useAppConfig";
import { useState } from "react";
interface RelaySelectorProps {
className?: string;
availableRelays?: { name: string; url: string }[];
selectedRelay?: string;
setSelectedRelay: (relay: string) => void;
presetRelays?: { name: string; url: string }[];
}
export function RelaySelector({ className, availableRelays = [] }: RelaySelectorProps) {
const { config, updateConfig } = useAppConfig();
export function RelaySelector(props: RelaySelectorProps) {
const { selectedRelay, setSelectedRelay, className, presetRelays = [] } = props;
const [open, setOpen] = useState(false);
const [inputValue, setInputValue] = useState("");
const selectedOption = availableRelays.find((option) => option.url === config.relayUrl);
const selectedOption = presetRelays.find((option) => option.url === selectedRelay);
// Function to normalize relay URL by adding wss:// if no protocol is present
const normalizeRelayUrl = (url: string): string => {
@ -46,12 +47,9 @@ export function RelaySelector({ className, availableRelays = [] }: RelaySelector
// Handle adding a custom relay
const handleAddCustomRelay = (url: string) => {
const normalizedUrl = normalizeRelayUrl(url);
if (normalizedUrl) {
updateConfig(config => ({ ...config, relayUrl: normalizedUrl }));
setOpen(false);
setInputValue("");
}
setSelectedRelay?.(normalizeRelayUrl(url));
setOpen(false);
setInputValue("");
};
// Check if input value looks like a valid relay URL
@ -83,8 +81,8 @@ export function RelaySelector({ className, availableRelays = [] }: RelaySelector
<span className="truncate">
{selectedOption
? selectedOption.name
: config.relayUrl
? config.relayUrl.replace(/^wss?:\/\//, '')
: selectedRelay
? selectedRelay.replace(/^wss?:\/\//, '')
: "Select relay..."
}
</span>
@ -121,7 +119,7 @@ export function RelaySelector({ className, availableRelays = [] }: RelaySelector
)}
</CommandEmpty>
<CommandGroup>
{availableRelays
{presetRelays
.filter((option) =>
!inputValue ||
option.name.toLowerCase().includes(inputValue.toLowerCase()) ||
@ -132,7 +130,7 @@ export function RelaySelector({ className, availableRelays = [] }: RelaySelector
key={option.url}
value={option.url}
onSelect={(currentValue) => {
updateConfig(config => ({ ...config, relayUrl: currentValue }));
setSelectedRelay(normalizeRelayUrl(currentValue));
setOpen(false);
setInputValue("");
}}
@ -140,7 +138,7 @@ export function RelaySelector({ className, availableRelays = [] }: RelaySelector
<Check
className={cn(
"mr-2 h-4 w-4",
config.relayUrl === option.url ? "opacity-100" : "opacity-0"
selectedRelay === option.url ? "opacity-100" : "opacity-0"
)}
/>
<div className="flex flex-col">

View File

@ -12,6 +12,7 @@ import {
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar.tsx';
import { RelaySelector } from '@/components/RelaySelector';
import { useLoggedInAccounts, type Account } from '@/hooks/useLoggedInAccounts';
import { useAppContext } from '@/hooks/useAppContext';
import { genUserName } from '@/lib/genUserName';
interface AccountSwitcherProps {
@ -19,11 +20,14 @@ interface AccountSwitcherProps {
}
export function AccountSwitcher({ onAddAccountClick }: AccountSwitcherProps) {
const { config, updateConfig, presetRelays } = useAppContext();
const { currentUser, otherUsers, setLogin, removeLogin } = useLoggedInAccounts();
if (!currentUser) return null;
const getDisplayName = (account: Account): string => account.metadata.name ?? genUserName(account.pubkey);
const getDisplayName = (account: Account): string => {
return account.metadata.name ?? genUserName(account.pubkey);
}
return (
<DropdownMenu>
@ -41,7 +45,12 @@ export function AccountSwitcher({ onAddAccountClick }: AccountSwitcherProps) {
</DropdownMenuTrigger>
<DropdownMenuContent className='w-56 p-2 animate-scale-in'>
<div className='font-medium text-sm px-2 py-1.5'>Switch Relay</div>
<RelaySelector className="w-full" />
<RelaySelector
className="w-full"
selectedRelay={config.relayUrl}
setSelectedRelay={(relayUrl) => updateConfig((config) => ({ ...config, relayUrl }))}
presetRelays={presetRelays}
/>
<DropdownMenuSeparator />
<div className='font-medium text-sm px-2 py-1.5'>Switch Account</div>
{otherUsers.map((user) => (

View File

@ -14,6 +14,8 @@ export interface AppContextType {
config: AppConfig;
/** Update configuration using a callback that receives current config and returns new config */
updateConfig: (updater: (currentConfig: AppConfig) => AppConfig) => void;
/** Optional list of preset relays to display in the RelaySelector */
presetRelays?: { name: string; url: string }[];
}
export const AppContext = createContext<AppContextType | undefined>(undefined);

View File

@ -5,10 +5,10 @@ import { AppContext, type AppContextType } from "@/contexts/AppContext";
* Hook to access and update application configuration
* @returns Application context with config and update methods
*/
export function useAppConfig(): AppContextType {
export function useAppContext(): AppContextType {
const context = useContext(AppContext);
if (context === undefined) {
throw new Error('useAppConfig must be used within an AppProvider');
throw new Error('useAppContext must be used within an AppProvider');
}
return context;
}

View File

@ -1,12 +1,12 @@
import { type Theme } from "@/contexts/AppContext";
import { useAppConfig } from "@/hooks/useAppConfig";
import { useAppContext } from "@/hooks/useAppContext";
/**
* Hook to get and set the active theme
* @returns Theme context with theme and setTheme
*/
export function useTheme(): { theme: Theme; setTheme: (theme: Theme) => void } {
const { config, updateConfig } = useAppConfig();
const { config, updateConfig } = useAppContext();
return {
theme: config.theme,