Skip to content

Commit ac506bc

Browse files
chore(buddychain): improvements (#104)
1 parent 49460a2 commit ac506bc

File tree

15 files changed

+250
-147
lines changed

15 files changed

+250
-147
lines changed

ci/Jenkinsfile

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,7 @@ pipeline {
4444
stage('dogfooding') { steps { script { buildExample() } } }
4545
stage('message-monitor') { steps { script { buildExample() } } }
4646
stage('flush-notes') { steps { script { buildNextJSExample() } } }
47-
stage('buddybook') {
48-
steps {
49-
script {
50-
dir('examples/buddybook') {
51-
sh 'npm install'
52-
sh 'npm run build:ci'
53-
}
54-
}
55-
}
56-
}
47+
stage('buddybook') { steps { script { buildExample() } } }
5748
}
5849
}
5950

examples/buddybook/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="UTF-8" />
55
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<title>Buddychain Dogfood</title>
7+
<title>BuddyBook Dogfood</title>
88
</head>
99
<body>
1010
<div id="root"></div>

examples/buddybook/src/App.tsx

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,8 @@ function App() {
4141

4242
useEffect(() => {
4343
if (isWakuLoading || !node || node.libp2p.getConnections().length === 0 || chainsData.length > 0 || isListening) return;
44-
45-
setTimeout(() => {
4644
setIsListening(true);
4745
startMessageListening();
48-
}, 3000);
49-
50-
5146
}, [node, isWakuLoading, wakuStatus])
5247

5348
const handleTelemetryOptIn = (optIn: boolean) => {
@@ -117,7 +112,7 @@ function App() {
117112
return (
118113
<div className="min-h-screen bg-background text-foreground">
119114
<Header wakuStatus={wakuStatus} />
120-
<main className="container mx-auto px-4 py-8">
115+
<main className="container mx-auto px-4 py-4 md:py-8 max-w-7xl">
121116
<Routes>
122117
<Route path="/create" element={<ChainCreationForm />} />
123118
<Route path="/view" element={<ChainList chainsData={chainsData} onChainUpdate={handleChainUpdate} isLoading={isLoadingChains} />} />
@@ -132,22 +127,20 @@ function App() {
132127
}
133128

134129
const Home: React.FC = () => (
135-
<div className="space-y-6 text-center">
136-
<h1 className="text-4xl font-bold">BuddyChain</h1>
137-
<div className="max-w-md mx-auto p-6 bg-card rounded-lg shadow-md">
130+
<div className="space-y-4 md:space-y-6 p-4 md:p-6">
131+
<h1 className="text-2xl md:text-4xl font-bold">BuddyBook</h1>
132+
<div className="w-full max-w-sm mx-auto p-4 md:p-6 bg-card rounded-lg shadow-md">
138133
<Link to="/create">
139-
<Button
140-
className="w-full mb-4"
141-
>
134+
<Button className="w-full mb-4">
142135
Create New Chain
143136
</Button>
144137
</Link>
145-
<p className="text-muted-foreground">
138+
<p className="text-sm md:text-base text-muted-foreground">
146139
Click the button above to start creating a new chain.
147140
</p>
148141
</div>
149-
<p className="text-sm text-muted-foreground">
150-
Welcome to BuddyChain - Create and share your chains!
142+
<p className="text-xs md:text-sm text-muted-foreground text-center">
143+
Welcome to BuddyBook - Create and share your chains!
151144
</p>
152145
</div>
153146
)

examples/buddybook/src/components/Chain/SignChain.tsx

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ import { v4 as uuidv4 } from 'uuid';
1111

1212
interface SignChainProps {
1313
block: BlockPayload;
14+
chainsData: BlockPayload[]; // Add this prop
1415
onSuccess: (newBlock: BlockPayload) => void;
1516
}
1617

17-
const SignChain: React.FC<SignChainProps> = ({ block, onSuccess }) => {
18+
const SignChain: React.FC<SignChainProps> = ({ block, chainsData, onSuccess }) => {
1819
const [isOpen, setIsOpen] = useState(false);
1920
const [isSigning, setIsSigning] = useState(false);
2021
const [error, setError] = useState<string | null>(null);
@@ -25,17 +26,37 @@ const SignChain: React.FC<SignChainProps> = ({ block, onSuccess }) => {
2526

2627
useEffect(() => {
2728
if (address) {
28-
const hasAlreadySigned = block.signatures.some(sig => sig.address.toLowerCase() === address.toLowerCase());
29+
// Check if the address has signed this block or any blocks in the chain
30+
const checkSignatures = (blockToCheck: BlockPayload): boolean => {
31+
// Check current block's signatures
32+
if (blockToCheck.signatures.some(
33+
sig => sig.address.toLowerCase() === address.toLowerCase()
34+
)) {
35+
return true;
36+
}
37+
38+
// Check parent blocks
39+
const parentBlock = chainsData.find(b => b.blockUUID === blockToCheck.parentBlockUUID);
40+
if (parentBlock && checkSignatures(parentBlock)) {
41+
return true;
42+
}
43+
44+
// Check child blocks
45+
const childBlocks = chainsData.filter(b => b.parentBlockUUID === blockToCheck.blockUUID);
46+
return childBlocks.some(childBlock => checkSignatures(childBlock));
47+
};
48+
49+
const hasAlreadySigned = checkSignatures(block);
2950
setAlreadySigned(hasAlreadySigned);
3051
}
31-
}, [address, block.signatures]);
52+
}, [address, block, chainsData]);
3253

3354
const { signMessage } = useSignMessage({
3455
mutation: {
3556
async onSuccess(signature) {
3657
if (!address || !node) return;
3758

38-
// Check if the address has already signed
59+
// Double check signature before proceeding
3960
if (block.signatures.some(sig => sig.address.toLowerCase() === address.toLowerCase())) {
4061
setError('You have already signed this chain.');
4162
setIsSigning(false);
@@ -79,6 +100,7 @@ const SignChain: React.FC<SignChainProps> = ({ block, onSuccess }) => {
79100
});
80101

81102
const handleSign = () => {
103+
// Add an additional check here before signing
82104
if (alreadySigned) {
83105
setError('You have already signed this chain.');
84106
return;
@@ -102,7 +124,7 @@ const SignChain: React.FC<SignChainProps> = ({ block, onSuccess }) => {
102124
{alreadySigned ? 'Already Signed' : 'Sign Chain'}
103125
</Button>
104126
<Dialog open={isOpen} onOpenChange={setIsOpen}>
105-
<DialogContent>
127+
<DialogContent className="sm:max-w-md">
106128
<DialogHeader>
107129
<DialogTitle>Sign Chain</DialogTitle>
108130
<DialogDescription>
@@ -111,7 +133,14 @@ const SignChain: React.FC<SignChainProps> = ({ block, onSuccess }) => {
111133
: 'Review the block details and sign to add your signature to the chain.'}
112134
</DialogDescription>
113135
</DialogHeader>
114-
<QRCode data={block} />
136+
<div className="flex flex-col space-y-4">
137+
<div className="space-y-2">
138+
<h4 className="font-medium">Block Details</h4>
139+
<p className="text-sm text-muted-foreground">{block.title}</p>
140+
<p className="text-sm text-muted-foreground">{block.description}</p>
141+
</div>
142+
<QRCode text={`${window.location.origin}/sign/${block.chainUUID}/${block.blockUUID}`} />
143+
</div>
115144
{error && <p className="text-sm text-destructive">{error}</p>}
116145
<DialogFooter>
117146
<Button variant="secondary" onClick={() => setIsOpen(false)}>Cancel</Button>

examples/buddybook/src/components/Chain/SignSharedChain.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ const SignSharedChain: React.FC<SignSharedChainProps> = ({ chainsData, onChainUp
4444
<CardContent>
4545
<h2 className="text-xl font-semibold mb-2">{block.title}</h2>
4646
<p className="mb-4">{block.description}</p>
47-
<SignChain block={block} onSuccess={onChainUpdate} />
47+
<SignChain
48+
block={block}
49+
chainsData={chainsData}
50+
onSuccess={onChainUpdate}
51+
/>
4852
</CardContent>
4953
</Card>
5054
);

examples/buddybook/src/components/Chain/View/ChainList.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { type BlockPayload } from '@/lib/waku';
44
import SignChain from '@/components/Chain/SignChain';
55
import { useEnsName } from 'wagmi';
66
import { Button } from '@/components/ui/button';
7-
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
7+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogDescription } from "@/components/ui/dialog";
88
import QRCode from '@/components/QRCode';
99
import { Loader2 } from "lucide-react";
1010

@@ -50,14 +50,21 @@ const ChainList: React.FC<ChainListProps> = ({ chainsData, onChainUpdate, isLoad
5050
Block UUID: {block.blockUUID}
5151
</p>
5252
<div className="mt-2 space-x-2">
53-
<SignChain block={block} onSuccess={handleChainUpdate} />
53+
<SignChain
54+
block={block}
55+
chainsData={chainsData}
56+
onSuccess={handleChainUpdate}
57+
/>
5458
<Dialog>
5559
<DialogTrigger asChild>
5660
<Button variant="outline">Share</Button>
5761
</DialogTrigger>
5862
<DialogContent className="sm:max-w-md">
5963
<DialogHeader>
6064
<DialogTitle>Share this Chain</DialogTitle>
65+
<DialogDescription>
66+
Share this chain with others to collect their signatures.
67+
</DialogDescription>
6168
</DialogHeader>
6269
<div className="flex flex-col items-center space-y-4">
6370
<QRCode text={shareUrl} width={200} height={200} />

examples/buddybook/src/components/Header.tsx

Lines changed: 70 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -56,69 +56,78 @@ const Header: React.FC<HeaderProps> = ({ wakuStatus }) => {
5656
};
5757

5858
return (
59-
<header className="bg-background border-b border-border">
60-
<div className="container mx-auto px-4 py-4 flex justify-between items-center">
61-
<div className="flex items-center space-x-4">
62-
<h1 className="text-2xl font-bold">BuddyBook</h1>
63-
<nav>
64-
<ul className="flex space-x-4">
65-
<li>
66-
<Link
67-
to="/create"
68-
className={`text-sm ${location.pathname === '/create' ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
69-
>
70-
Create Chain
71-
</Link>
72-
</li>
73-
<li>
74-
<Link
75-
to="/view"
76-
className={`text-sm ${location.pathname === '/view' ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
77-
>
78-
View Existing Chains
79-
</Link>
80-
</li>
81-
<li>
82-
<Link
83-
to="/telemetry"
84-
className={`text-sm ${location.pathname === '/telemetry' ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
85-
>
86-
Telemetry
87-
</Link>
88-
</li>
89-
</ul>
90-
</nav>
91-
</div>
92-
<div className="flex items-center space-x-2">
93-
<div className="flex items-center space-x-1">
94-
<span className="text-sm text-muted-foreground">Filter:</span>
95-
<div className={`w-3 h-3 rounded-full ${getStatusColor(wakuStatus.filter)}`}></div>
59+
<header className="border-b">
60+
<div className="container mx-auto px-4 py-2 md:py-4">
61+
<div className="flex flex-col md:flex-row justify-between items-center space-y-2 md:space-y-0">
62+
<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">
63+
<h1 className="text-xl md:text-2xl font-bold">BuddyBook</h1>
64+
<nav className="w-full md:w-auto">
65+
<ul className="flex justify-center md:justify-start space-x-4">
66+
<li>
67+
<Link
68+
to="/create"
69+
className={`text-sm ${location.pathname === '/create' ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
70+
>
71+
Create Chain
72+
</Link>
73+
</li>
74+
<li>
75+
<Link
76+
to="/view"
77+
className={`text-sm ${location.pathname === '/view' ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
78+
>
79+
View Chains
80+
</Link>
81+
</li>
82+
<li>
83+
<Link
84+
to="/telemetry"
85+
className={`text-sm ${location.pathname === '/telemetry' ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
86+
>
87+
Telemetry
88+
</Link>
89+
</li>
90+
</ul>
91+
</nav>
9692
</div>
97-
<div className="flex items-center space-x-1">
98-
<span className="text-sm text-muted-foreground">Store:</span>
99-
<div className={`w-3 h-3 rounded-full ${getStatusColor(wakuStatus.store)}`}></div>
93+
94+
<div className="flex flex-wrap justify-center md:justify-end items-center gap-2 w-full md:w-auto">
95+
<div className="flex items-center space-x-2 text-xs md:text-sm">
96+
<div className="flex items-center space-x-1">
97+
<span className="text-muted-foreground">Filter:</span>
98+
<div className={`w-2 h-2 md:w-3 md:h-3 rounded-full ${getStatusColor(wakuStatus.filter)}`}></div>
99+
</div>
100+
<div className="flex items-center space-x-1">
101+
<span className="text-muted-foreground">Store:</span>
102+
<div className={`w-2 h-2 md:w-3 md:h-3 rounded-full ${getStatusColor(wakuStatus.store)}`}></div>
103+
</div>
104+
</div>
105+
106+
<div className="flex items-center space-x-2">
107+
{isWakuLoading ? (
108+
<Loader2 className="h-4 w-4 animate-spin" />
109+
) : wakuError ? (
110+
<span className="text-xs md:text-sm text-red-500">Waku Error</span>
111+
) : (
112+
<span className="text-xs md:text-sm text-muted-foreground hidden md:inline">
113+
Waku Connections: {connections}
114+
</span>
115+
)}
116+
117+
{isConnected ? (
118+
<div className="flex items-center space-x-2">
119+
<span className="text-xs md:text-sm text-muted-foreground truncate max-w-[120px] md:max-w-none">
120+
{ensName || (address ? `${address.slice(0, 6)}...${address.slice(-4)}` : '')}
121+
</span>
122+
<Button variant="outline" size="sm" onClick={() => disconnect()}>
123+
Logout
124+
</Button>
125+
</div>
126+
) : (
127+
<ConnectKitButton />
128+
)}
129+
</div>
100130
</div>
101-
{isWakuLoading ? (
102-
<Loader2 className="h-4 w-4 animate-spin" />
103-
) : wakuError ? (
104-
<span className="text-sm text-red-500">Waku Error</span>
105-
) : (
106-
<span className="text-sm text-muted-foreground">
107-
Waku Connections: {connections}
108-
</span>
109-
)}
110-
{isConnected ? (
111-
<>
112-
<span className="text-sm text-muted-foreground">
113-
{ensName || (address ? `${address.slice(0, 6)}...${address.slice(-4)}` : '')}
114-
</span>
115-
<Button variant="outline" size="sm" onClick={() => disconnect()}>
116-
Logout
117-
</Button>
118-
</>
119-
) : (
120-
<ConnectKitButton />
121-
)}
122131
</div>
123132
</div>
124133
</header>

examples/buddybook/src/components/QRCode.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import React from 'react';
1+
import React, { useState } from 'react';
22
import { QRCodeSVG } from 'qrcode.react';
3+
import { Button } from "@/components/ui/button";
4+
import { Check, Copy } from "lucide-react";
35

46
interface QRCodeProps {
57
text: string;
@@ -8,9 +10,32 @@ interface QRCodeProps {
810
}
911

1012
const QRCode: React.FC<QRCodeProps> = ({ text, width = 256, height = 256 }) => {
13+
const [copied, setCopied] = useState(false);
14+
15+
const handleCopy = async () => {
16+
await navigator.clipboard.writeText(text);
17+
setCopied(true);
18+
setTimeout(() => setCopied(false), 2000);
19+
};
20+
1121
return (
1222
<div className="flex flex-col items-center space-y-4">
1323
<QRCodeSVG value={text} size={Math.min(width, height)} />
24+
<div className="flex items-center space-x-2">
25+
<input
26+
type="text"
27+
value={text}
28+
readOnly
29+
className="flex-1 px-3 py-2 text-sm border rounded-md bg-muted"
30+
/>
31+
<Button
32+
variant="outline"
33+
size="icon"
34+
onClick={handleCopy}
35+
>
36+
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
37+
</Button>
38+
</div>
1439
</div>
1540
);
1641
};

0 commit comments

Comments
 (0)