mirror of
https://gitlab.com/soapbox-pub/mkstack.git
synced 2025-08-27 13:09:22 +00:00
Refactor AppProvider, AppConfig and presetRelays
This commit is contained in:
parent
3ad83cd114
commit
048bf4cc69
@ -27,9 +27,16 @@ const defaultConfig: AppConfig = {
|
|||||||
relayUrl: "wss://relay.nostr.band",
|
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() {
|
export function App() {
|
||||||
return (
|
return (
|
||||||
<AppProvider storageKey="nostr:app-config" defaultConfig={defaultConfig}>
|
<AppProvider storageKey="nostr:app-config" defaultConfig={defaultConfig} presetRelays={presetRelays}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<NostrLoginProvider storageKey='nostr:login'>
|
<NostrLoginProvider storageKey='nostr:login'>
|
||||||
<NostrProvider>
|
<NostrProvider>
|
||||||
|
@ -8,13 +8,16 @@ interface AppProviderProps {
|
|||||||
storageKey: string;
|
storageKey: string;
|
||||||
/** Default app configuration */
|
/** Default app configuration */
|
||||||
defaultConfig: AppConfig;
|
defaultConfig: AppConfig;
|
||||||
|
/** Optional list of preset relays to display in the RelaySelector */
|
||||||
|
presetRelays?: { name: string; url: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AppProvider(props: AppProviderProps) {
|
export function AppProvider(props: AppProviderProps) {
|
||||||
const {
|
const {
|
||||||
children,
|
children,
|
||||||
storageKey,
|
storageKey,
|
||||||
defaultConfig
|
defaultConfig,
|
||||||
|
presetRelays,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
// App configuration state with localStorage persistence
|
// App configuration state with localStorage persistence
|
||||||
@ -28,6 +31,7 @@ export function AppProvider(props: AppProviderProps) {
|
|||||||
const appContextValue: AppContextType = {
|
const appContextValue: AppContextType = {
|
||||||
config,
|
config,
|
||||||
updateConfig,
|
updateConfig,
|
||||||
|
presetRelays,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply theme effects to document
|
// Apply theme effects to document
|
||||||
|
@ -2,7 +2,7 @@ import React, { useEffect, useRef } from 'react';
|
|||||||
import { NostrEvent, NPool, NRelay1 } from '@nostrify/nostrify';
|
import { NostrEvent, NPool, NRelay1 } from '@nostrify/nostrify';
|
||||||
import { NostrContext } from '@nostrify/react';
|
import { NostrContext } from '@nostrify/react';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { useAppConfig } from '@/hooks/useAppConfig';
|
import { useAppContext } from '@/hooks/useAppContext';
|
||||||
|
|
||||||
interface NostrProviderProps {
|
interface NostrProviderProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@ -10,7 +10,7 @@ interface NostrProviderProps {
|
|||||||
|
|
||||||
const NostrProvider: React.FC<NostrProviderProps> = (props) => {
|
const NostrProvider: React.FC<NostrProviderProps> = (props) => {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
const { config } = useAppConfig();
|
const { config } = useAppContext();
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
@ -14,21 +14,22 @@ import {
|
|||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { useAppConfig } from "@/hooks/useAppConfig";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
interface RelaySelectorProps {
|
interface RelaySelectorProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
availableRelays?: { name: string; url: string }[];
|
selectedRelay?: string;
|
||||||
|
setSelectedRelay: (relay: string) => void;
|
||||||
|
presetRelays?: { name: string; url: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RelaySelector({ className, availableRelays = [] }: RelaySelectorProps) {
|
export function RelaySelector(props: RelaySelectorProps) {
|
||||||
const { config, updateConfig } = useAppConfig();
|
const { selectedRelay, setSelectedRelay, className, presetRelays = [] } = props;
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [inputValue, setInputValue] = useState("");
|
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
|
// Function to normalize relay URL by adding wss:// if no protocol is present
|
||||||
const normalizeRelayUrl = (url: string): string => {
|
const normalizeRelayUrl = (url: string): string => {
|
||||||
@ -46,12 +47,9 @@ export function RelaySelector({ className, availableRelays = [] }: RelaySelector
|
|||||||
|
|
||||||
// Handle adding a custom relay
|
// Handle adding a custom relay
|
||||||
const handleAddCustomRelay = (url: string) => {
|
const handleAddCustomRelay = (url: string) => {
|
||||||
const normalizedUrl = normalizeRelayUrl(url);
|
setSelectedRelay?.(normalizeRelayUrl(url));
|
||||||
if (normalizedUrl) {
|
setOpen(false);
|
||||||
updateConfig(config => ({ ...config, relayUrl: normalizedUrl }));
|
setInputValue("");
|
||||||
setOpen(false);
|
|
||||||
setInputValue("");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if input value looks like a valid relay URL
|
// Check if input value looks like a valid relay URL
|
||||||
@ -83,8 +81,8 @@ export function RelaySelector({ className, availableRelays = [] }: RelaySelector
|
|||||||
<span className="truncate">
|
<span className="truncate">
|
||||||
{selectedOption
|
{selectedOption
|
||||||
? selectedOption.name
|
? selectedOption.name
|
||||||
: config.relayUrl
|
: selectedRelay
|
||||||
? config.relayUrl.replace(/^wss?:\/\//, '')
|
? selectedRelay.replace(/^wss?:\/\//, '')
|
||||||
: "Select relay..."
|
: "Select relay..."
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
@ -121,7 +119,7 @@ export function RelaySelector({ className, availableRelays = [] }: RelaySelector
|
|||||||
)}
|
)}
|
||||||
</CommandEmpty>
|
</CommandEmpty>
|
||||||
<CommandGroup>
|
<CommandGroup>
|
||||||
{availableRelays
|
{presetRelays
|
||||||
.filter((option) =>
|
.filter((option) =>
|
||||||
!inputValue ||
|
!inputValue ||
|
||||||
option.name.toLowerCase().includes(inputValue.toLowerCase()) ||
|
option.name.toLowerCase().includes(inputValue.toLowerCase()) ||
|
||||||
@ -132,7 +130,7 @@ export function RelaySelector({ className, availableRelays = [] }: RelaySelector
|
|||||||
key={option.url}
|
key={option.url}
|
||||||
value={option.url}
|
value={option.url}
|
||||||
onSelect={(currentValue) => {
|
onSelect={(currentValue) => {
|
||||||
updateConfig(config => ({ ...config, relayUrl: currentValue }));
|
setSelectedRelay(normalizeRelayUrl(currentValue));
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
setInputValue("");
|
setInputValue("");
|
||||||
}}
|
}}
|
||||||
@ -140,7 +138,7 @@ export function RelaySelector({ className, availableRelays = [] }: RelaySelector
|
|||||||
<Check
|
<Check
|
||||||
className={cn(
|
className={cn(
|
||||||
"mr-2 h-4 w-4",
|
"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">
|
<div className="flex flex-col">
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar.tsx';
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar.tsx';
|
||||||
import { RelaySelector } from '@/components/RelaySelector';
|
import { RelaySelector } from '@/components/RelaySelector';
|
||||||
import { useLoggedInAccounts, type Account } from '@/hooks/useLoggedInAccounts';
|
import { useLoggedInAccounts, type Account } from '@/hooks/useLoggedInAccounts';
|
||||||
|
import { useAppContext } from '@/hooks/useAppContext';
|
||||||
import { genUserName } from '@/lib/genUserName';
|
import { genUserName } from '@/lib/genUserName';
|
||||||
|
|
||||||
interface AccountSwitcherProps {
|
interface AccountSwitcherProps {
|
||||||
@ -19,11 +20,14 @@ interface AccountSwitcherProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function AccountSwitcher({ onAddAccountClick }: AccountSwitcherProps) {
|
export function AccountSwitcher({ onAddAccountClick }: AccountSwitcherProps) {
|
||||||
|
const { config, updateConfig, presetRelays } = useAppContext();
|
||||||
const { currentUser, otherUsers, setLogin, removeLogin } = useLoggedInAccounts();
|
const { currentUser, otherUsers, setLogin, removeLogin } = useLoggedInAccounts();
|
||||||
|
|
||||||
if (!currentUser) return null;
|
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 (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@ -41,7 +45,12 @@ export function AccountSwitcher({ onAddAccountClick }: AccountSwitcherProps) {
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className='w-56 p-2 animate-scale-in'>
|
<DropdownMenuContent className='w-56 p-2 animate-scale-in'>
|
||||||
<div className='font-medium text-sm px-2 py-1.5'>Switch Relay</div>
|
<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 />
|
<DropdownMenuSeparator />
|
||||||
<div className='font-medium text-sm px-2 py-1.5'>Switch Account</div>
|
<div className='font-medium text-sm px-2 py-1.5'>Switch Account</div>
|
||||||
{otherUsers.map((user) => (
|
{otherUsers.map((user) => (
|
||||||
|
@ -14,6 +14,8 @@ export interface AppContextType {
|
|||||||
config: AppConfig;
|
config: AppConfig;
|
||||||
/** Update configuration using a callback that receives current config and returns new config */
|
/** Update configuration using a callback that receives current config and returns new config */
|
||||||
updateConfig: (updater: (currentConfig: AppConfig) => AppConfig) => void;
|
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);
|
export const AppContext = createContext<AppContextType | undefined>(undefined);
|
||||||
|
@ -5,10 +5,10 @@ import { AppContext, type AppContextType } from "@/contexts/AppContext";
|
|||||||
* Hook to access and update application configuration
|
* Hook to access and update application configuration
|
||||||
* @returns Application context with config and update methods
|
* @returns Application context with config and update methods
|
||||||
*/
|
*/
|
||||||
export function useAppConfig(): AppContextType {
|
export function useAppContext(): AppContextType {
|
||||||
const context = useContext(AppContext);
|
const context = useContext(AppContext);
|
||||||
if (context === undefined) {
|
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;
|
return context;
|
||||||
}
|
}
|
@ -1,12 +1,12 @@
|
|||||||
import { type Theme } from "@/contexts/AppContext";
|
import { type Theme } from "@/contexts/AppContext";
|
||||||
import { useAppConfig } from "@/hooks/useAppConfig";
|
import { useAppContext } from "@/hooks/useAppContext";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook to get and set the active theme
|
* Hook to get and set the active theme
|
||||||
* @returns Theme context with theme and setTheme
|
* @returns Theme context with theme and setTheme
|
||||||
*/
|
*/
|
||||||
export function useTheme(): { theme: Theme; setTheme: (theme: Theme) => void } {
|
export function useTheme(): { theme: Theme; setTheme: (theme: Theme) => void } {
|
||||||
const { config, updateConfig } = useAppConfig();
|
const { config, updateConfig } = useAppContext();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
theme: config.theme,
|
theme: config.theme,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user