mirror of
https://gitlab.com/soapbox-pub/mkstack.git
synced 2025-08-26 20:49:22 +00:00
fix redraw issue for wallet configuration
This commit is contained in:
parent
6845e6cf7c
commit
97a493c218
@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, forwardRef } from 'react';
|
||||
import { Wallet, Plus, Trash2, Zap, Globe, WalletMinimal, CheckCircle, X } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
@ -28,12 +28,183 @@ import { useNWC } from '@/hooks/useNWCContext';
|
||||
import { useWallet } from '@/hooks/useWallet';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { useIsMobile } from '@/hooks/useIsMobile';
|
||||
import type { NWCConnection, NWCInfo } from '@/hooks/useNWC';
|
||||
|
||||
interface WalletModalProps {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// Extracted AddWalletContent to prevent re-renders
|
||||
const AddWalletContent = forwardRef<HTMLDivElement, {
|
||||
alias: string;
|
||||
setAlias: (value: string) => void;
|
||||
connectionUri: string;
|
||||
setConnectionUri: (value: string) => void;
|
||||
}>(({ alias, setAlias, connectionUri, setConnectionUri }, ref) => (
|
||||
<div className="space-y-4 px-4" ref={ref}>
|
||||
<div>
|
||||
<Label htmlFor="alias">Wallet Name (optional)</Label>
|
||||
<Input
|
||||
id="alias"
|
||||
placeholder="My Lightning Wallet"
|
||||
value={alias}
|
||||
onChange={(e) => setAlias(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="connection-uri">Connection URI</Label>
|
||||
<Textarea
|
||||
id="connection-uri"
|
||||
placeholder="nostr+walletconnect://..."
|
||||
value={connectionUri}
|
||||
onChange={(e) => setConnectionUri(e.target.value)}
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
AddWalletContent.displayName = 'AddWalletContent';
|
||||
|
||||
// Extracted WalletContent to prevent re-renders
|
||||
const WalletContent = forwardRef<HTMLDivElement, {
|
||||
hasWebLN: boolean;
|
||||
isDetecting: boolean;
|
||||
hasNWC: boolean;
|
||||
connections: NWCConnection[];
|
||||
connectionInfo: Record<string, NWCInfo>;
|
||||
activeConnection: string | null;
|
||||
handleSetActive: (cs: string) => void;
|
||||
handleRemoveConnection: (cs: string) => void;
|
||||
setAddDialogOpen: (open: boolean) => void;
|
||||
}>(({
|
||||
hasWebLN,
|
||||
isDetecting,
|
||||
hasNWC,
|
||||
connections,
|
||||
connectionInfo,
|
||||
activeConnection,
|
||||
handleSetActive,
|
||||
handleRemoveConnection,
|
||||
setAddDialogOpen
|
||||
}, ref) => (
|
||||
<div className="space-y-6 px-4 pb-4" ref={ref}>
|
||||
{/* Current Status */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="font-medium">Current Status</h3>
|
||||
<div className="grid gap-3">
|
||||
{/* WebLN */}
|
||||
<div className="flex items-center justify-between p-3 border rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<Globe className="h-4 w-4 text-muted-foreground" />
|
||||
<div>
|
||||
<p className="text-sm font-medium">WebLN</p>
|
||||
<p className="text-xs text-muted-foreground">Browser extension</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{hasWebLN && <CheckCircle className="h-4 w-4 text-green-600" />}
|
||||
<Badge variant={hasWebLN ? "default" : "secondary"} className="text-xs">
|
||||
{isDetecting ? "..." : hasWebLN ? "Ready" : "Not Found"}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
{/* NWC */}
|
||||
<div className="flex items-center justify-between p-3 border rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<WalletMinimal className="h-4 w-4 text-muted-foreground" />
|
||||
<div>
|
||||
<p className="text-sm font-medium">Nostr Wallet Connect</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{connections.length > 0
|
||||
? `${connections.length} wallet${connections.length !== 1 ? 's' : ''} connected`
|
||||
: "Remote wallet connection"
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{hasNWC && <CheckCircle className="h-4 w-4 text-green-600" />}
|
||||
<Badge variant={hasNWC ? "default" : "secondary"} className="text-xs">
|
||||
{hasNWC ? "Ready" : "None"}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
{/* NWC Management */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-medium">Nostr Wallet Connect</h3>
|
||||
<Button size="sm" variant="outline" onClick={() => setAddDialogOpen(true)}>
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
{/* Connected Wallets List */}
|
||||
{connections.length === 0 ? (
|
||||
<div className="text-center py-6 text-muted-foreground">
|
||||
<p className="text-sm">No wallets connected</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{connections.map((connection) => {
|
||||
const info = connectionInfo[connection.connectionString];
|
||||
const isActive = activeConnection === connection.connectionString;
|
||||
return (
|
||||
<div key={connection.connectionString} className={`flex items-center justify-between p-3 border rounded-lg ${isActive ? 'ring-2 ring-primary' : ''}`}>
|
||||
<div className="flex items-center gap-3">
|
||||
<WalletMinimal className="h-4 w-4 text-muted-foreground" />
|
||||
<div>
|
||||
<p className="text-sm font-medium">
|
||||
{connection.alias || info?.alias || 'Lightning Wallet'}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
NWC Connection
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{isActive && <CheckCircle className="h-4 w-4 text-green-600" />}
|
||||
{!isActive && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleSetActive(connection.connectionString)}
|
||||
>
|
||||
<Zap className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleRemoveConnection(connection.connectionString)}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Help */}
|
||||
{!hasWebLN && connections.length === 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="text-center py-4 space-y-2">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Install a WebLN extension or connect a NWC wallet for zaps.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
));
|
||||
WalletContent.displayName = 'WalletContent';
|
||||
|
||||
export function WalletModal({ children, className }: WalletModalProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [addDialogOpen, setAddDialogOpen] = useState(false);
|
||||
@ -53,7 +224,6 @@ export function WalletModal({ children, className }: WalletModalProps) {
|
||||
|
||||
const { hasWebLN, isDetecting } = useWallet();
|
||||
|
||||
// Calculate hasNWC directly from connections to ensure reactivity
|
||||
const hasNWC = connections.length > 0 && connections.some(c => c.isConnected);
|
||||
const { toast } = useToast();
|
||||
|
||||
@ -92,235 +262,134 @@ export function WalletModal({ children, className }: WalletModalProps) {
|
||||
});
|
||||
};
|
||||
|
||||
// Shared content component
|
||||
const WalletContent = () => (
|
||||
<div className="space-y-6 px-4 pb-4">
|
||||
{/* Current Status */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="font-medium">Current Status</h3>
|
||||
const walletContentProps = {
|
||||
hasWebLN,
|
||||
isDetecting,
|
||||
hasNWC,
|
||||
connections,
|
||||
connectionInfo,
|
||||
activeConnection,
|
||||
handleSetActive,
|
||||
handleRemoveConnection,
|
||||
setAddDialogOpen,
|
||||
};
|
||||
|
||||
<div className="grid gap-3">
|
||||
{/* WebLN */}
|
||||
<div className="flex items-center justify-between p-3 border rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<Globe className="h-4 w-4 text-muted-foreground" />
|
||||
<div>
|
||||
<p className="text-sm font-medium">WebLN</p>
|
||||
<p className="text-xs text-muted-foreground">Browser extension</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{hasWebLN && <CheckCircle className="h-4 w-4 text-green-600" />}
|
||||
<Badge variant={hasWebLN ? "default" : "secondary"} className="text-xs">
|
||||
{isDetecting ? "..." : hasWebLN ? "Ready" : "Not Found"}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* NWC */}
|
||||
<div className="flex items-center justify-between p-3 border rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<WalletMinimal className="h-4 w-4 text-muted-foreground" />
|
||||
<div>
|
||||
<p className="text-sm font-medium">Nostr Wallet Connect</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{connections.length > 0
|
||||
? `${connections.length} wallet${connections.length !== 1 ? 's' : ''} connected`
|
||||
: "Remote wallet connection"
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{hasNWC && <CheckCircle className="h-4 w-4 text-green-600" />}
|
||||
<Badge variant={hasNWC ? "default" : "secondary"} className="text-xs">
|
||||
{hasNWC ? "Ready" : "None"}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* NWC Management */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="font-medium">Nostr Wallet Connect</h3>
|
||||
<Dialog open={addDialogOpen} onOpenChange={setAddDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button size="sm" variant="outline">
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
Add
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Connect NWC Wallet</DialogTitle>
|
||||
<DialogDescription>
|
||||
Enter your connection string from a compatible wallet.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 px-4">
|
||||
<div>
|
||||
<Label htmlFor="alias">Wallet Name (optional)</Label>
|
||||
<Input
|
||||
id="alias"
|
||||
placeholder="My Lightning Wallet"
|
||||
value={alias}
|
||||
onChange={(e) => setAlias(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="connection-uri">Connection URI</Label>
|
||||
<Textarea
|
||||
id="connection-uri"
|
||||
placeholder="nostr+walletconnect://..."
|
||||
value={connectionUri}
|
||||
onChange={(e) => setConnectionUri(e.target.value)}
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter className="px-4">
|
||||
<Button
|
||||
onClick={handleAddConnection}
|
||||
disabled={isConnecting || !connectionUri.trim()}
|
||||
className="w-full"
|
||||
>
|
||||
{isConnecting ? 'Connecting...' : 'Connect'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
{/* Connected Wallets List */}
|
||||
{connections.length === 0 ? (
|
||||
<div className="text-center py-6 text-muted-foreground">
|
||||
<p className="text-sm">No wallets connected</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{connections.map((connection) => {
|
||||
const info = connectionInfo[connection.connectionString];
|
||||
const isActive = activeConnection === connection.connectionString;
|
||||
|
||||
return (
|
||||
<div key={connection.connectionString} className={`flex items-center justify-between p-3 border rounded-lg ${isActive ? 'ring-2 ring-primary' : ''}`}>
|
||||
<div className="flex items-center gap-3">
|
||||
<WalletMinimal className="h-4 w-4 text-muted-foreground" />
|
||||
<div>
|
||||
<p className="text-sm font-medium">
|
||||
{connection.alias || info?.alias || 'Lightning Wallet'}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
NWC Connection
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{isActive && <CheckCircle className="h-4 w-4 text-green-600" />}
|
||||
{!isActive && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleSetActive(connection.connectionString)}
|
||||
>
|
||||
<Zap className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleRemoveConnection(connection.connectionString)}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Help */}
|
||||
{!hasWebLN && connections.length === 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="text-center py-4 space-y-2">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Install a WebLN extension or connect a NWC wallet for zaps.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
const addWalletDialog = (
|
||||
<Dialog open={addDialogOpen} onOpenChange={setAddDialogOpen}>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Connect NWC Wallet</DialogTitle>
|
||||
<DialogDescription>
|
||||
Enter your connection string from a compatible wallet.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<AddWalletContent
|
||||
alias={alias}
|
||||
setAlias={setAlias}
|
||||
connectionUri={connectionUri}
|
||||
setConnectionUri={setConnectionUri}
|
||||
/>
|
||||
<DialogFooter className="px-4">
|
||||
<Button
|
||||
onClick={handleAddConnection}
|
||||
disabled={isConnecting || !connectionUri.trim()}
|
||||
className="w-full"
|
||||
>
|
||||
{isConnecting ? 'Connecting...' : 'Connect'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={setOpen}>
|
||||
<DrawerTrigger asChild>
|
||||
<>
|
||||
<Drawer open={open} onOpenChange={setOpen}>
|
||||
<DrawerTrigger asChild>
|
||||
{children || (
|
||||
<Button variant="outline" size="sm" className={className}>
|
||||
<Wallet className="h-4 w-4 mr-2" />
|
||||
Wallet Settings
|
||||
</Button>
|
||||
)}
|
||||
</DrawerTrigger>
|
||||
<DrawerContent className="h-full">
|
||||
<DrawerHeader className="text-center relative">
|
||||
<DrawerClose asChild>
|
||||
<Button variant="ghost" size="sm" className="absolute right-4 top-4">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</Button>
|
||||
</DrawerClose>
|
||||
<DrawerTitle className="flex items-center justify-center gap-2 pt-2">
|
||||
<Wallet className="h-5 w-5" />
|
||||
Lightning Wallet
|
||||
</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
Connect your lightning wallet to send zaps instantly.
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div className="overflow-y-auto">
|
||||
<WalletContent {...walletContentProps} />
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
{/* Render Add Wallet as a separate Drawer for mobile */}
|
||||
<Drawer open={addDialogOpen} onOpenChange={setAddDialogOpen}>
|
||||
<DrawerContent>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Connect NWC Wallet</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
Enter your connection string from a compatible wallet.
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<AddWalletContent
|
||||
alias={alias}
|
||||
setAlias={setAlias}
|
||||
connectionUri={connectionUri}
|
||||
setConnectionUri={setConnectionUri}
|
||||
/>
|
||||
<div className="p-4">
|
||||
<Button
|
||||
onClick={handleAddConnection}
|
||||
disabled={isConnecting || !connectionUri.trim()}
|
||||
className="w-full"
|
||||
>
|
||||
{isConnecting ? 'Connecting...' : 'Connect'}
|
||||
</Button>
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{children || (
|
||||
<Button variant="outline" size="sm" className={className}>
|
||||
<Wallet className="h-4 w-4 mr-2" />
|
||||
Wallet Settings
|
||||
</Button>
|
||||
)}
|
||||
</DrawerTrigger>
|
||||
<DrawerContent className="h-full">
|
||||
<DrawerHeader className="text-center relative">
|
||||
{/* Close button */}
|
||||
<DrawerClose asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="absolute right-4 top-4"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</Button>
|
||||
</DrawerClose>
|
||||
|
||||
<DrawerTitle className="flex items-center justify-center gap-2 pt-2">
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[500px] max-h-[80vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Wallet className="h-5 w-5" />
|
||||
Lightning Wallet
|
||||
</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Connect your lightning wallet to send zaps instantly.
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div className="overflow-y-auto">
|
||||
<WalletContent />
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{children || (
|
||||
<Button variant="outline" size="sm" className={className}>
|
||||
<Wallet className="h-4 w-4 mr-2" />
|
||||
Wallet Settings
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[500px] max-h-[80vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Wallet className="h-5 w-5" />
|
||||
Lightning Wallet
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Connect your lightning wallet to send zaps instantly.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<WalletContent />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<WalletContent {...walletContentProps} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{addWalletDialog}
|
||||
</>
|
||||
);
|
||||
}
|
@ -248,8 +248,6 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (target) {
|
||||
setComment('Zapped with MKStack!');
|
||||
|
@ -10,7 +10,7 @@ export interface NWCConnection {
|
||||
client?: LN;
|
||||
}
|
||||
|
||||
interface NWCInfo {
|
||||
export interface NWCInfo {
|
||||
alias?: string;
|
||||
color?: string;
|
||||
pubkey?: string;
|
||||
|
Loading…
x
Reference in New Issue
Block a user