mirror of
https://github.com/logos-messaging/lab.waku.org.git
synced 2026-01-03 06:13:08 +00:00
chore(buddybook): improvements (#106)
This commit is contained in:
parent
7faa598ca7
commit
e5c9a06368
@ -71,13 +71,17 @@ function App() {
|
||||
setIsLoadingChains(true);
|
||||
const messageGenerator = getMessagesFromStore(node as LightNode);
|
||||
|
||||
// Process messages as they arrive
|
||||
for await (const message of messageGenerator) {
|
||||
setChainsData(prevChains => {
|
||||
const blockExists = prevChains.some(block => block.blockUUID === message.blockUUID);
|
||||
if (blockExists) return prevChains;
|
||||
return [...prevChains, message];
|
||||
});
|
||||
try {
|
||||
for await (const message of messageGenerator) {
|
||||
setChainsData(prevChains => {
|
||||
const blockExists = prevChains.some(block => block.blockUUID === message.blockUUID);
|
||||
if (blockExists) return prevChains;
|
||||
return [...prevChains, message];
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error processing message:", error);
|
||||
// Continue processing other messages
|
||||
}
|
||||
|
||||
setWakuStatus(prev => ({ ...prev, store: 'success' }));
|
||||
@ -130,9 +134,9 @@ function App() {
|
||||
<Header wakuStatus={wakuStatus} />
|
||||
<main className="container mx-auto px-4 py-4 md:py-8 max-w-7xl">
|
||||
<Routes>
|
||||
<Route path="" element={<Home />} />
|
||||
<Route path="create" element={<ChainCreationForm />} />
|
||||
<Route path="view" element={<ChainList chainsData={chainsData} onChainUpdate={handleChainUpdate} isLoading={isLoadingChains} />} />
|
||||
<Route path="" element={<Home />} />
|
||||
<Route
|
||||
path="sign/:chainUUID/:blockUUID"
|
||||
element={
|
||||
|
||||
@ -145,6 +145,7 @@ const ChainCreationForm: React.FC = () => {
|
||||
value={formData.title}
|
||||
onChange={handleInputChange}
|
||||
maxLength={50}
|
||||
className="text-base sm:text-sm"
|
||||
/>
|
||||
{errors.title && <p className="text-sm text-destructive">{errors.title}</p>}
|
||||
</div>
|
||||
@ -156,10 +157,11 @@ const ChainCreationForm: React.FC = () => {
|
||||
value={formData.description}
|
||||
onChange={handleInputChange}
|
||||
maxLength={500}
|
||||
className="min-h-[100px] text-base sm:text-sm"
|
||||
/>
|
||||
{errors.description && <p className="text-sm text-destructive">{errors.description}</p>}
|
||||
</div>
|
||||
<Button type="submit" className="w-full">Create Chain</Button>
|
||||
<Button type="submit" className="w-full py-6 text-base sm:py-2 sm:text-sm">Create Chain</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
<Dialog open={showModal} onOpenChange={handleCloseModal}>
|
||||
@ -196,15 +198,15 @@ const ChainCreationForm: React.FC = () => {
|
||||
<>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<QRCode
|
||||
text={`${window.location.origin}/sign/${formData.uuid}/${createdBlockUUID}`}
|
||||
text={`${window.location.origin}${import.meta.env.BASE_URL}sign/${formData.uuid}/${createdBlockUUID}`}
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
<p className="text-sm text-center break-all">
|
||||
{`${window.location.origin}/sign/${formData.uuid}/${createdBlockUUID}`}
|
||||
{`${window.location.origin}${import.meta.env.BASE_URL}sign/${formData.uuid}/${createdBlockUUID}`}
|
||||
</p>
|
||||
<Button
|
||||
onClick={() => navigator.clipboard.writeText(`${window.location.origin}/sign/${formData.uuid}/${createdBlockUUID}`)}
|
||||
onClick={() => navigator.clipboard.writeText(`${window.location.origin}${import.meta.env.BASE_URL}sign/${formData.uuid}/${createdBlockUUID}`)}
|
||||
variant="outline"
|
||||
>
|
||||
Copy Link
|
||||
|
||||
@ -55,28 +55,32 @@ const SignChain: React.FC<SignChainProps> = ({ block, chainsData, onSuccess }) =
|
||||
|
||||
const { signMessage } = useSignMessage({
|
||||
mutation: {
|
||||
onMutate() {
|
||||
// Reset any previous errors when starting a new signing attempt
|
||||
setError(null);
|
||||
setIsSigning(true);
|
||||
},
|
||||
async onSuccess(signature) {
|
||||
if (!address || !node) return;
|
||||
|
||||
// Double check signature before proceeding
|
||||
if (block.signatures.some(sig => sig.address.toLowerCase() === address.toLowerCase())) {
|
||||
setError('You have already signed this chain.');
|
||||
setIsSigning(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const newBlock: BlockPayload = {
|
||||
chainUUID: block.chainUUID,
|
||||
blockUUID: uuidv4(),
|
||||
title: block.title,
|
||||
description: block.description,
|
||||
signedMessage: signature,
|
||||
timestamp: Date.now(),
|
||||
signatures: [{ address, signature }],
|
||||
parentBlockUUID: block.blockUUID
|
||||
};
|
||||
|
||||
try {
|
||||
// Double check signature before proceeding
|
||||
if (block.signatures.some(sig => sig.address.toLowerCase() === address.toLowerCase())) {
|
||||
setError('You have already signed this chain.');
|
||||
return;
|
||||
}
|
||||
|
||||
const newBlock: BlockPayload = {
|
||||
chainUUID: block.chainUUID,
|
||||
blockUUID: uuidv4(),
|
||||
title: block.title,
|
||||
description: block.description,
|
||||
signedMessage: signature,
|
||||
timestamp: Date.now(),
|
||||
signatures: [{ address, signature }],
|
||||
parentBlockUUID: block.blockUUID
|
||||
};
|
||||
|
||||
const wakuMessage = createMessage(newBlock);
|
||||
const { failures, successes } = await node.lightPush.send(encoder, wakuMessage);
|
||||
|
||||
@ -89,44 +93,55 @@ const SignChain: React.FC<SignChainProps> = ({ block, chainsData, onSuccess }) =
|
||||
} catch (error) {
|
||||
console.error('Error creating new block:', error);
|
||||
setError('Failed to create new block. Please try again.');
|
||||
} finally {
|
||||
setIsSigning(false);
|
||||
}
|
||||
},
|
||||
onError(error) {
|
||||
console.error('Error signing message:', error);
|
||||
setError('Error signing message. Please try again.');
|
||||
setError('Error signing message. Please try again. If using a mobile wallet, please ensure your wallet app is open.');
|
||||
},
|
||||
onSettled() {
|
||||
setIsSigning(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const handleSign = () => {
|
||||
if (!ensureWalletConnected()) {
|
||||
return;
|
||||
const handleSign = async () => {
|
||||
try {
|
||||
if (!address) {
|
||||
// If not connected, try to connect first
|
||||
const connected = await ensureWalletConnected();
|
||||
if (!connected) return;
|
||||
}
|
||||
|
||||
// Check if already signed
|
||||
if (alreadySigned) {
|
||||
setError('You have already signed this chain.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare the message
|
||||
const message = `Sign Block:
|
||||
Chain UUID: ${block.chainUUID}
|
||||
Block UUID: ${block.blockUUID}
|
||||
Title: ${block.title}
|
||||
Description: ${block.description}
|
||||
Timestamp: ${new Date().getTime()}
|
||||
Parent Block UUID: ${block.parentBlockUUID}
|
||||
Signed by: ${ensName || address}`;
|
||||
|
||||
// Trigger signing
|
||||
signMessage({ message });
|
||||
} catch (error) {
|
||||
console.error('Error in sign flow:', error);
|
||||
setError('Failed to initiate signing. Please try again.');
|
||||
setIsSigning(false);
|
||||
}
|
||||
// Add an additional check here before signing
|
||||
if (alreadySigned) {
|
||||
setError('You have already signed this chain.');
|
||||
return;
|
||||
}
|
||||
setIsSigning(true);
|
||||
setError(null);
|
||||
const message = `Sign Block:
|
||||
Chain UUID: ${block.chainUUID}
|
||||
Block UUID: ${block.blockUUID}
|
||||
Title: ${block.title}
|
||||
Description: ${block.description}
|
||||
Timestamp: ${new Date().getTime()}
|
||||
Parent Block UUID: ${block.parentBlockUUID}
|
||||
Signed by: ${ensName || address}`;
|
||||
signMessage({ message });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => setIsOpen(true)} disabled={alreadySigned}>
|
||||
{alreadySigned ? 'Already Signed' : 'Sign Chain'}
|
||||
{alreadySigned ? 'Already Signed' : !address ? 'Connect Wallet' : 'Sign Chain'}
|
||||
</Button>
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
@ -157,6 +172,8 @@ const SignChain: React.FC<SignChainProps> = ({ block, chainsData, onSuccess }) =
|
||||
</>
|
||||
) : alreadySigned ? (
|
||||
'Already Signed'
|
||||
) : !address ? (
|
||||
'Connect Wallet'
|
||||
) : (
|
||||
'Sign'
|
||||
)}
|
||||
|
||||
@ -23,7 +23,7 @@ const ChainList: React.FC<ChainListProps> = ({ chainsData, onChainUpdate, isLoad
|
||||
const childBlocks = chainsData.filter(b => b.parentBlockUUID === block.blockUUID);
|
||||
const totalSignatures = block.signatures.length + childBlocks.reduce((acc, child) => acc + child.signatures.length, 0);
|
||||
|
||||
const shareUrl = `${window.location.origin}/sign/${block.chainUUID ?? block.blockUUID}/${block.blockUUID}`;
|
||||
const shareUrl = `${window.location.origin}${import.meta.env.BASE_URL}sign/${block.chainUUID ?? block.blockUUID}/${block.blockUUID}`;
|
||||
|
||||
return (
|
||||
<li key={`${block.blockUUID}-${depth}`} className="mb-4">
|
||||
|
||||
@ -56,90 +56,70 @@ const Header: React.FC<HeaderProps> = ({ wakuStatus }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="border-b">
|
||||
<div className="container mx-auto px-4 py-4">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center space-y-2 md:space-y-0">
|
||||
<div className="flex flex-col md:flex-row items-center space-y-2 md:space-y-0 md:space-x-4 w-full md:w-auto">
|
||||
<h1 className="text-xl md:text-2xl font-bold">BuddyBook</h1>
|
||||
<nav className="w-full md:w-auto">
|
||||
<ul className="flex justify-center md:justify-start space-x-4">
|
||||
<li>
|
||||
<Link
|
||||
to="create"
|
||||
className={`text-sm ${location.pathname.endsWith('/create') ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
|
||||
>
|
||||
Create Chain
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
to="view"
|
||||
className={`text-sm ${location.pathname.endsWith('/view') ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
|
||||
>
|
||||
View Chains
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
to="telemetry"
|
||||
className={`text-sm ${location.pathname.endsWith('/telemetry') ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
|
||||
>
|
||||
Telemetry
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="container">
|
||||
<div className="h-14">
|
||||
<div className="flex h-14 items-center justify-between gap-4">
|
||||
<nav className="flex items-center gap-2 md:gap-4">
|
||||
<Link
|
||||
to=""
|
||||
className={`text-sm font-medium ${location.pathname === "" ? "text-foreground" : "text-muted-foreground"}`}
|
||||
>
|
||||
Home
|
||||
</Link>
|
||||
<Link
|
||||
to="create"
|
||||
className={`text-sm font-medium ${location.pathname === "/create" ? "text-foreground" : "text-muted-foreground"}`}
|
||||
>
|
||||
Create
|
||||
</Link>
|
||||
<Link
|
||||
to="view"
|
||||
className={`text-sm font-medium ${location.pathname === "/view" ? "text-foreground" : "text-muted-foreground"}`}
|
||||
>
|
||||
View
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap justify-center md:justify-end items-center gap-2 w-full md:w-auto">
|
||||
<div className="flex items-center space-x-2 text-xs md:text-sm">
|
||||
{isWakuLoading ? (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
<span className="text-muted-foreground">Connecting...</span>
|
||||
</div>
|
||||
) : wakuError ? (
|
||||
<span className="text-destructive">Network Error</span>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-center space-x-1">
|
||||
<span className="text-muted-foreground">Filter:</span>
|
||||
<div className={`w-2 h-2 md:w-3 md:h-3 rounded-full ${getStatusColor(wakuStatus.filter)}`}></div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<span className="text-muted-foreground">Store:</span>
|
||||
<div className={`w-2 h-2 md:w-3 md:h-3 rounded-full ${getStatusColor(wakuStatus.store)}`}></div>
|
||||
</div>
|
||||
<span className="text-xs md:text-sm text-muted-foreground hidden md:inline">
|
||||
{connections > 0 ? `${connections} peer${connections === 1 ? '' : 's'}` : 'Connecting...'}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
{isWakuLoading ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : wakuError ? (
|
||||
<span className="text-xs md:text-sm text-red-500">Waku Error</span>
|
||||
) : (
|
||||
<span className="text-xs md:text-sm text-muted-foreground hidden md:inline">
|
||||
Waku Connections: {connections}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2 md:gap-4">
|
||||
<div className="hidden md:flex items-center gap-2">
|
||||
{!isWakuLoading && !wakuError && (
|
||||
<>
|
||||
<div className="flex items-center space-x-1">
|
||||
<span className="text-muted-foreground">Filter:</span>
|
||||
<div className={`w-2 h-2 md:w-3 md:h-3 rounded-full ${getStatusColor(wakuStatus.filter)}`}></div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<span className="text-muted-foreground">Store:</span>
|
||||
<div className={`w-2 h-2 md:w-3 md:h-3 rounded-full ${getStatusColor(wakuStatus.store)}`}></div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isConnected ? (
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-xs md:text-sm text-muted-foreground truncate max-w-[120px] md:max-w-none">
|
||||
{ensName || (address ? `${address.slice(0, 6)}...${address.slice(-4)}` : '')}
|
||||
</span>
|
||||
<Button variant="outline" size="sm" onClick={() => disconnect()}>
|
||||
Logout
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<ConnectKitButton />
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
{isWakuLoading ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : wakuError ? (
|
||||
<span className="text-xs text-red-500">Error</span>
|
||||
) : (
|
||||
<div className={`w-2 h-2 rounded-full ${connections > 0 ? "bg-green-500" : "bg-yellow-500"}`} />
|
||||
)}
|
||||
|
||||
{isConnected ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs md:text-sm text-muted-foreground truncate max-w-[80px] md:max-w-[120px]">
|
||||
{ensName || (address ? `${address.slice(0, 4)}...${address.slice(-4)}` : '')}
|
||||
</span>
|
||||
<Button variant="outline" size="sm" onClick={() => disconnect()}>
|
||||
<span className="md:hidden">×</span>
|
||||
<span className="hidden md:inline">Logout</span>
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<ConnectKitButton />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -11,6 +11,7 @@ interface QRCodeProps {
|
||||
|
||||
const QRCode: React.FC<QRCodeProps> = ({ text, width = 256, height = 256 }) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const isMobile = window.innerWidth < 640;
|
||||
|
||||
const handleCopy = async () => {
|
||||
await navigator.clipboard.writeText(text);
|
||||
@ -20,18 +21,22 @@ const QRCode: React.FC<QRCodeProps> = ({ text, width = 256, height = 256 }) => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<QRCodeSVG value={text} size={Math.min(width, height)} />
|
||||
<div className="flex items-center space-x-2">
|
||||
<QRCodeSVG
|
||||
value={text}
|
||||
size={isMobile ? Math.min(width * 0.8, window.innerWidth - 64) : Math.min(width, height)}
|
||||
/>
|
||||
<div className="flex items-center space-x-2 w-full max-w-[300px]">
|
||||
<input
|
||||
type="text"
|
||||
value={text}
|
||||
readOnly
|
||||
className="flex-1 px-3 py-2 text-sm border rounded-md bg-muted"
|
||||
className="flex-1 px-3 py-2 text-xs sm:text-sm border rounded-md bg-muted truncate"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={handleCopy}
|
||||
className="shrink-0"
|
||||
>
|
||||
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
||||
</Button>
|
||||
|
||||
@ -45,7 +45,11 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
"min-h-[44px] px-4 py-2 md:min-h-[36px] md:px-3 md:py-1.5",
|
||||
buttonVariants({ variant, size, className })
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@ -9,7 +9,7 @@ const Card = React.forwardRef<
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-lg border bg-card text-card-foreground shadow-sm p-4 md:p-6",
|
||||
"rounded-lg border bg-card text-card-foreground shadow-sm p-4 md:p-6 w-full max-w-[95vw] mx-auto",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@ -30,24 +30,16 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<Cross2Icon className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed z-50 grid w-full gap-4 rounded-b-lg border bg-background p-6 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0",
|
||||
"max-h-[85vh] overflow-y-auto",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||
|
||||
|
||||
@ -10,7 +10,8 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base md:text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"touch-manipulation min-h-[44px] md:min-h-[36px]",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@ -9,7 +9,8 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base md:text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"touch-manipulation resize-y",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@ -4,14 +4,21 @@ export function useWalletPrompt() {
|
||||
const { isConnected } = useAccount()
|
||||
const { connect, connectors } = useConnect()
|
||||
|
||||
const ensureWalletConnected = () => {
|
||||
const ensureWalletConnected = async () => {
|
||||
if (!isConnected) {
|
||||
// Find the first available connector (usually injected/metamask)
|
||||
const connector = connectors[0]
|
||||
if (connector) {
|
||||
connect({ connector })
|
||||
try {
|
||||
// Find the first available connector (usually injected/metamask)
|
||||
const connector = connectors[0]
|
||||
if (connector) {
|
||||
await connect({ connector })
|
||||
}
|
||||
// Wait a brief moment for the connection to be established
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Error connecting wallet:', error)
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
@ -27,8 +28,9 @@
|
||||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
--radius: 0.5rem
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 0 0% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
@ -53,36 +55,46 @@
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%
|
||||
--chart-5: 340 75% 55%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
html {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
overscroll-behavior-y: none;
|
||||
}
|
||||
|
||||
button, a {
|
||||
@apply cursor-pointer touch-manipulation;
|
||||
}
|
||||
|
||||
.container {
|
||||
@apply px-4 md:px-6 lg:px-8;
|
||||
}
|
||||
|
||||
|
||||
h1 {
|
||||
@apply text-2xl md:text-4xl font-bold;
|
||||
}
|
||||
|
||||
|
||||
h2 {
|
||||
@apply text-xl md:text-3xl font-semibold;
|
||||
}
|
||||
|
||||
|
||||
h3 {
|
||||
@apply text-lg md:text-2xl font-semibold;
|
||||
}
|
||||
|
||||
.section {
|
||||
@apply py-4 md:py-6 lg:py-8;
|
||||
}
|
||||
|
||||
|
||||
.card {
|
||||
@apply p-4 md:p-6;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user