mirror of
https://github.com/logos-messaging/lab.waku.org.git
synced 2026-01-04 06:43:11 +00:00
chore(buddybook): improvements (#106)
This commit is contained in:
parent
7faa598ca7
commit
e5c9a06368
@ -71,13 +71,17 @@ function App() {
|
|||||||
setIsLoadingChains(true);
|
setIsLoadingChains(true);
|
||||||
const messageGenerator = getMessagesFromStore(node as LightNode);
|
const messageGenerator = getMessagesFromStore(node as LightNode);
|
||||||
|
|
||||||
// Process messages as they arrive
|
try {
|
||||||
for await (const message of messageGenerator) {
|
for await (const message of messageGenerator) {
|
||||||
setChainsData(prevChains => {
|
setChainsData(prevChains => {
|
||||||
const blockExists = prevChains.some(block => block.blockUUID === message.blockUUID);
|
const blockExists = prevChains.some(block => block.blockUUID === message.blockUUID);
|
||||||
if (blockExists) return prevChains;
|
if (blockExists) return prevChains;
|
||||||
return [...prevChains, message];
|
return [...prevChains, message];
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error processing message:", error);
|
||||||
|
// Continue processing other messages
|
||||||
}
|
}
|
||||||
|
|
||||||
setWakuStatus(prev => ({ ...prev, store: 'success' }));
|
setWakuStatus(prev => ({ ...prev, store: 'success' }));
|
||||||
@ -130,9 +134,9 @@ function App() {
|
|||||||
<Header wakuStatus={wakuStatus} />
|
<Header wakuStatus={wakuStatus} />
|
||||||
<main className="container mx-auto px-4 py-4 md:py-8 max-w-7xl">
|
<main className="container mx-auto px-4 py-4 md:py-8 max-w-7xl">
|
||||||
<Routes>
|
<Routes>
|
||||||
|
<Route path="" element={<Home />} />
|
||||||
<Route path="create" element={<ChainCreationForm />} />
|
<Route path="create" element={<ChainCreationForm />} />
|
||||||
<Route path="view" element={<ChainList chainsData={chainsData} onChainUpdate={handleChainUpdate} isLoading={isLoadingChains} />} />
|
<Route path="view" element={<ChainList chainsData={chainsData} onChainUpdate={handleChainUpdate} isLoading={isLoadingChains} />} />
|
||||||
<Route path="" element={<Home />} />
|
|
||||||
<Route
|
<Route
|
||||||
path="sign/:chainUUID/:blockUUID"
|
path="sign/:chainUUID/:blockUUID"
|
||||||
element={
|
element={
|
||||||
|
|||||||
@ -145,6 +145,7 @@ const ChainCreationForm: React.FC = () => {
|
|||||||
value={formData.title}
|
value={formData.title}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
maxLength={50}
|
maxLength={50}
|
||||||
|
className="text-base sm:text-sm"
|
||||||
/>
|
/>
|
||||||
{errors.title && <p className="text-sm text-destructive">{errors.title}</p>}
|
{errors.title && <p className="text-sm text-destructive">{errors.title}</p>}
|
||||||
</div>
|
</div>
|
||||||
@ -156,10 +157,11 @@ const ChainCreationForm: React.FC = () => {
|
|||||||
value={formData.description}
|
value={formData.description}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
maxLength={500}
|
maxLength={500}
|
||||||
|
className="min-h-[100px] text-base sm:text-sm"
|
||||||
/>
|
/>
|
||||||
{errors.description && <p className="text-sm text-destructive">{errors.description}</p>}
|
{errors.description && <p className="text-sm text-destructive">{errors.description}</p>}
|
||||||
</div>
|
</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>
|
</form>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<Dialog open={showModal} onOpenChange={handleCloseModal}>
|
<Dialog open={showModal} onOpenChange={handleCloseModal}>
|
||||||
@ -196,15 +198,15 @@ const ChainCreationForm: React.FC = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="flex flex-col items-center space-y-4">
|
<div className="flex flex-col items-center space-y-4">
|
||||||
<QRCode
|
<QRCode
|
||||||
text={`${window.location.origin}/sign/${formData.uuid}/${createdBlockUUID}`}
|
text={`${window.location.origin}${import.meta.env.BASE_URL}sign/${formData.uuid}/${createdBlockUUID}`}
|
||||||
width={200}
|
width={200}
|
||||||
height={200}
|
height={200}
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-center break-all">
|
<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>
|
</p>
|
||||||
<Button
|
<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"
|
variant="outline"
|
||||||
>
|
>
|
||||||
Copy Link
|
Copy Link
|
||||||
|
|||||||
@ -55,28 +55,32 @@ const SignChain: React.FC<SignChainProps> = ({ block, chainsData, onSuccess }) =
|
|||||||
|
|
||||||
const { signMessage } = useSignMessage({
|
const { signMessage } = useSignMessage({
|
||||||
mutation: {
|
mutation: {
|
||||||
|
onMutate() {
|
||||||
|
// Reset any previous errors when starting a new signing attempt
|
||||||
|
setError(null);
|
||||||
|
setIsSigning(true);
|
||||||
|
},
|
||||||
async onSuccess(signature) {
|
async onSuccess(signature) {
|
||||||
if (!address || !node) return;
|
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 {
|
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 wakuMessage = createMessage(newBlock);
|
||||||
const { failures, successes } = await node.lightPush.send(encoder, wakuMessage);
|
const { failures, successes } = await node.lightPush.send(encoder, wakuMessage);
|
||||||
|
|
||||||
@ -89,44 +93,55 @@ const SignChain: React.FC<SignChainProps> = ({ block, chainsData, onSuccess }) =
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating new block:', error);
|
console.error('Error creating new block:', error);
|
||||||
setError('Failed to create new block. Please try again.');
|
setError('Failed to create new block. Please try again.');
|
||||||
} finally {
|
|
||||||
setIsSigning(false);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError(error) {
|
onError(error) {
|
||||||
console.error('Error signing message:', 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);
|
setIsSigning(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSign = () => {
|
const handleSign = async () => {
|
||||||
if (!ensureWalletConnected()) {
|
try {
|
||||||
return;
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={() => setIsOpen(true)} disabled={alreadySigned}>
|
<Button onClick={() => setIsOpen(true)} disabled={alreadySigned}>
|
||||||
{alreadySigned ? 'Already Signed' : 'Sign Chain'}
|
{alreadySigned ? 'Already Signed' : !address ? 'Connect Wallet' : 'Sign Chain'}
|
||||||
</Button>
|
</Button>
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<DialogContent className="sm:max-w-md">
|
<DialogContent className="sm:max-w-md">
|
||||||
@ -157,6 +172,8 @@ const SignChain: React.FC<SignChainProps> = ({ block, chainsData, onSuccess }) =
|
|||||||
</>
|
</>
|
||||||
) : alreadySigned ? (
|
) : alreadySigned ? (
|
||||||
'Already Signed'
|
'Already Signed'
|
||||||
|
) : !address ? (
|
||||||
|
'Connect Wallet'
|
||||||
) : (
|
) : (
|
||||||
'Sign'
|
'Sign'
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -23,7 +23,7 @@ const ChainList: React.FC<ChainListProps> = ({ chainsData, onChainUpdate, isLoad
|
|||||||
const childBlocks = chainsData.filter(b => b.parentBlockUUID === block.blockUUID);
|
const childBlocks = chainsData.filter(b => b.parentBlockUUID === block.blockUUID);
|
||||||
const totalSignatures = block.signatures.length + childBlocks.reduce((acc, child) => acc + child.signatures.length, 0);
|
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 (
|
return (
|
||||||
<li key={`${block.blockUUID}-${depth}`} className="mb-4">
|
<li key={`${block.blockUUID}-${depth}`} className="mb-4">
|
||||||
|
|||||||
@ -56,90 +56,70 @@ const Header: React.FC<HeaderProps> = ({ wakuStatus }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="border-b">
|
<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 mx-auto px-4 py-4">
|
<div className="container">
|
||||||
<div className="flex flex-col md:flex-row justify-between items-center space-y-2 md:space-y-0">
|
<div className="h-14">
|
||||||
<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">
|
<div className="flex h-14 items-center justify-between gap-4">
|
||||||
<h1 className="text-xl md:text-2xl font-bold">BuddyBook</h1>
|
<nav className="flex items-center gap-2 md:gap-4">
|
||||||
<nav className="w-full md:w-auto">
|
<Link
|
||||||
<ul className="flex justify-center md:justify-start space-x-4">
|
to=""
|
||||||
<li>
|
className={`text-sm font-medium ${location.pathname === "" ? "text-foreground" : "text-muted-foreground"}`}
|
||||||
<Link
|
>
|
||||||
to="create"
|
Home
|
||||||
className={`text-sm ${location.pathname.endsWith('/create') ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
|
</Link>
|
||||||
>
|
<Link
|
||||||
Create Chain
|
to="create"
|
||||||
</Link>
|
className={`text-sm font-medium ${location.pathname === "/create" ? "text-foreground" : "text-muted-foreground"}`}
|
||||||
</li>
|
>
|
||||||
<li>
|
Create
|
||||||
<Link
|
</Link>
|
||||||
to="view"
|
<Link
|
||||||
className={`text-sm ${location.pathname.endsWith('/view') ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
|
to="view"
|
||||||
>
|
className={`text-sm font-medium ${location.pathname === "/view" ? "text-foreground" : "text-muted-foreground"}`}
|
||||||
View Chains
|
>
|
||||||
</Link>
|
View
|
||||||
</li>
|
</Link>
|
||||||
<li>
|
|
||||||
<Link
|
|
||||||
to="telemetry"
|
|
||||||
className={`text-sm ${location.pathname.endsWith('/telemetry') ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
|
|
||||||
>
|
|
||||||
Telemetry
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
|
||||||
|
<div className="flex items-center gap-2 md:gap-4">
|
||||||
<div className="flex flex-wrap justify-center md:justify-end items-center gap-2 w-full md:w-auto">
|
<div className="hidden md:flex items-center gap-2">
|
||||||
<div className="flex items-center space-x-2 text-xs md:text-sm">
|
{!isWakuLoading && !wakuError && (
|
||||||
{isWakuLoading ? (
|
<>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-1">
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<span className="text-muted-foreground">Filter:</span>
|
||||||
<span className="text-muted-foreground">Connecting...</span>
|
<div className={`w-2 h-2 md:w-3 md:h-3 rounded-full ${getStatusColor(wakuStatus.filter)}`}></div>
|
||||||
</div>
|
</div>
|
||||||
) : wakuError ? (
|
<div className="flex items-center space-x-1">
|
||||||
<span className="text-destructive">Network Error</span>
|
<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 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>
|
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isConnected ? (
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center space-x-2">
|
{isWakuLoading ? (
|
||||||
<span className="text-xs md:text-sm text-muted-foreground truncate max-w-[120px] md:max-w-none">
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
{ensName || (address ? `${address.slice(0, 6)}...${address.slice(-4)}` : '')}
|
) : wakuError ? (
|
||||||
</span>
|
<span className="text-xs text-red-500">Error</span>
|
||||||
<Button variant="outline" size="sm" onClick={() => disconnect()}>
|
) : (
|
||||||
Logout
|
<div className={`w-2 h-2 rounded-full ${connections > 0 ? "bg-green-500" : "bg-yellow-500"}`} />
|
||||||
</Button>
|
)}
|
||||||
</div>
|
|
||||||
) : (
|
{isConnected ? (
|
||||||
<ConnectKitButton />
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -11,6 +11,7 @@ interface QRCodeProps {
|
|||||||
|
|
||||||
const QRCode: React.FC<QRCodeProps> = ({ text, width = 256, height = 256 }) => {
|
const QRCode: React.FC<QRCodeProps> = ({ text, width = 256, height = 256 }) => {
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
const isMobile = window.innerWidth < 640;
|
||||||
|
|
||||||
const handleCopy = async () => {
|
const handleCopy = async () => {
|
||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
@ -20,18 +21,22 @@ const QRCode: React.FC<QRCodeProps> = ({ text, width = 256, height = 256 }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center space-y-4">
|
<div className="flex flex-col items-center space-y-4">
|
||||||
<QRCodeSVG value={text} size={Math.min(width, height)} />
|
<QRCodeSVG
|
||||||
<div className="flex items-center space-x-2">
|
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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={text}
|
value={text}
|
||||||
readOnly
|
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
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={handleCopy}
|
onClick={handleCopy}
|
||||||
|
className="shrink-0"
|
||||||
>
|
>
|
||||||
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -45,7 +45,11 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
const Comp = asChild ? Slot : "button"
|
const Comp = asChild ? Slot : "button"
|
||||||
return (
|
return (
|
||||||
<Comp
|
<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}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -9,7 +9,7 @@ const Card = React.forwardRef<
|
|||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
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
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -30,24 +30,16 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
|||||||
const DialogContent = React.forwardRef<
|
const DialogContent = React.forwardRef<
|
||||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||||
>(({ className, children, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<DialogPortal>
|
<DialogPrimitive.Content
|
||||||
<DialogOverlay />
|
ref={ref}
|
||||||
<DialogPrimitive.Content
|
className={cn(
|
||||||
ref={ref}
|
"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",
|
||||||
className={cn(
|
"max-h-[85vh] overflow-y-auto",
|
||||||
"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
|
||||||
className
|
)}
|
||||||
)}
|
{...props}
|
||||||
{...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>
|
|
||||||
))
|
))
|
||||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,8 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
className={cn(
|
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
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@ -9,7 +9,8 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|||||||
return (
|
return (
|
||||||
<textarea
|
<textarea
|
||||||
className={cn(
|
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
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@ -4,14 +4,21 @@ export function useWalletPrompt() {
|
|||||||
const { isConnected } = useAccount()
|
const { isConnected } = useAccount()
|
||||||
const { connect, connectors } = useConnect()
|
const { connect, connectors } = useConnect()
|
||||||
|
|
||||||
const ensureWalletConnected = () => {
|
const ensureWalletConnected = async () => {
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
// Find the first available connector (usually injected/metamask)
|
try {
|
||||||
const connector = connectors[0]
|
// Find the first available connector (usually injected/metamask)
|
||||||
if (connector) {
|
const connector = connectors[0]
|
||||||
connect({ connector })
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 0 0% 100%;
|
--background: 0 0% 100%;
|
||||||
@ -27,8 +28,9 @@
|
|||||||
--chart-3: 197 37% 24%;
|
--chart-3: 197 37% 24%;
|
||||||
--chart-4: 43 74% 66%;
|
--chart-4: 43 74% 66%;
|
||||||
--chart-5: 27 87% 67%;
|
--chart-5: 27 87% 67%;
|
||||||
--radius: 0.5rem
|
--radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: 0 0% 3.9%;
|
--background: 0 0% 3.9%;
|
||||||
--foreground: 0 0% 98%;
|
--foreground: 0 0% 98%;
|
||||||
@ -53,36 +55,46 @@
|
|||||||
--chart-2: 160 60% 45%;
|
--chart-2: 160 60% 45%;
|
||||||
--chart-3: 30 80% 55%;
|
--chart-3: 30 80% 55%;
|
||||||
--chart-4: 280 65% 60%;
|
--chart-4: 280 65% 60%;
|
||||||
--chart-5: 340 75% 55%
|
--chart-5: 340 75% 55%;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@layer base {
|
|
||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
overscroll-behavior-y: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button, a {
|
||||||
|
@apply cursor-pointer touch-manipulation;
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
@apply px-4 md:px-6 lg:px-8;
|
@apply px-4 md:px-6 lg:px-8;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@apply text-2xl md:text-4xl font-bold;
|
@apply text-2xl md:text-4xl font-bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@apply text-xl md:text-3xl font-semibold;
|
@apply text-xl md:text-3xl font-semibold;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
@apply text-lg md:text-2xl font-semibold;
|
@apply text-lg md:text-2xl font-semibold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
@apply py-4 md:py-6 lg:py-8;
|
@apply py-4 md:py-6 lg:py-8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
@apply p-4 md:p-6;
|
@apply p-4 md:p-6;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user