From 49460a2f627d3b8cb518661af31b48edb79b3d25 Mon Sep 17 00:00:00 2001 From: Danish Arora <35004822+danisharora099@users.noreply.github.com> Date: Fri, 25 Oct 2024 21:12:53 +0530 Subject: [PATCH] feat(buddychain): multi signing and view sharable links (#103) --- examples/buddybook/src/App.tsx | 12 +++-- .../src/components/Chain/SignChain.tsx | 34 ++++++++++-- .../src/components/Chain/SignSharedChain.tsx | 53 +++++++++++++++++++ .../src/components/Chain/View/ChainList.tsx | 39 ++++++++++++-- examples/buddybook/src/components/QRCode.tsx | 45 ++-------------- examples/buddybook/tasks.md | 2 +- 6 files changed, 133 insertions(+), 52 deletions(-) create mode 100644 examples/buddybook/src/components/Chain/SignSharedChain.tsx diff --git a/examples/buddybook/src/App.tsx b/examples/buddybook/src/App.tsx index 89634be..629b6eb 100644 --- a/examples/buddybook/src/App.tsx +++ b/examples/buddybook/src/App.tsx @@ -7,10 +7,11 @@ import { Button } from "@/components/ui/button" import { type LightNode } from "@waku/sdk" import { useWaku } from "@waku/react" import { Loader2 } from "lucide-react" -import { Routes, Route, Navigate, Link } from 'react-router-dom' +import { Routes, Route, Navigate, Link, useParams } from 'react-router-dom' import { BlockPayload, getMessagesFromStore, subscribeToFilter } from './lib/waku' import TelemetryOptIn from './components/TelemetryOptIn'; import TelemetryPage from './components/TelemetryPage'; +import SignSharedChain from './components/Chain/SignSharedChain' type Status = 'success' | 'in-progress' | 'error'; @@ -29,6 +30,7 @@ function App() { store: 'in-progress', }); const [telemetryOptIn, setTelemetryOptIn] = useState(null); + const [isLoadingChains, setIsLoadingChains] = useState(true); useEffect(() => { const storedOptIn = localStorage.getItem('telemetryOptIn'); @@ -65,12 +67,15 @@ function App() { console.log("Starting message listening") try { setWakuStatus(prev => ({ ...prev, store: 'in-progress' })); + setIsLoadingChains(true); const storeMessages = await getMessagesFromStore(node as LightNode) setChainsData(storeMessages) setWakuStatus(prev => ({ ...prev, store: 'success' })); } catch (error) { console.error("Error fetching messages from store:", error); setWakuStatus(prev => ({ ...prev, store: 'error' })); + } finally { + setIsLoadingChains(false); } try { @@ -115,10 +120,11 @@ function App() {
} /> - } /> + } /> } /> - } /> + } /> } /> + } />
diff --git a/examples/buddybook/src/components/Chain/SignChain.tsx b/examples/buddybook/src/components/Chain/SignChain.tsx index d3fb738..773bc8a 100644 --- a/examples/buddybook/src/components/Chain/SignChain.tsx +++ b/examples/buddybook/src/components/Chain/SignChain.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { useAccount, useSignMessage, useEnsName } from 'wagmi'; import type { LightNode } from '@waku/interfaces'; import { useWaku } from '@waku/react'; @@ -18,14 +18,30 @@ const SignChain: React.FC = ({ block, onSuccess }) => { const [isOpen, setIsOpen] = useState(false); const [isSigning, setIsSigning] = useState(false); const [error, setError] = useState(null); + const [alreadySigned, setAlreadySigned] = useState(false); const { address } = useAccount(); const { data: ensName } = useEnsName({ address }); const { node } = useWaku(); + + useEffect(() => { + if (address) { + const hasAlreadySigned = block.signatures.some(sig => sig.address.toLowerCase() === address.toLowerCase()); + setAlreadySigned(hasAlreadySigned); + } + }, [address, block.signatures]); + const { signMessage } = useSignMessage({ mutation: { async onSuccess(signature) { if (!address || !node) return; + // Check if the address has already signed + 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(), @@ -63,6 +79,10 @@ const SignChain: React.FC = ({ block, onSuccess }) => { }); const handleSign = () => { + if (alreadySigned) { + setError('You have already signed this chain.'); + return; + } setIsSigning(true); setError(null); const message = `Sign Block: @@ -78,25 +98,31 @@ const SignChain: React.FC = ({ block, onSuccess }) => { return ( <> - + Sign Chain - Review the block details and sign to add your signature to the chain. + {alreadySigned + ? 'You have already signed this chain.' + : 'Review the block details and sign to add your signature to the chain.'} {error &&

{error}

} - + + + ); + } + + return ( + + + Sign Shared Chain + + +

{block.title}

+

{block.description}

+ +
+
+ ); +}; + +export default SignSharedChain; diff --git a/examples/buddybook/src/components/Chain/View/ChainList.tsx b/examples/buddybook/src/components/Chain/View/ChainList.tsx index a26c5da..39de0c7 100644 --- a/examples/buddybook/src/components/Chain/View/ChainList.tsx +++ b/examples/buddybook/src/components/Chain/View/ChainList.tsx @@ -1,15 +1,20 @@ import React from 'react'; -import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; +import { Card, CardHeader, CardTitle, CardContent} from "@/components/ui/card"; import { type BlockPayload } from '@/lib/waku'; import SignChain from '@/components/Chain/SignChain'; import { useEnsName } from 'wagmi'; +import { Button } from '@/components/ui/button'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; +import QRCode from '@/components/QRCode'; +import { Loader2 } from "lucide-react"; interface ChainListProps { chainsData: BlockPayload[]; onChainUpdate: (newBlock: BlockPayload) => void; + isLoading: boolean; } -const ChainList: React.FC = ({ chainsData, onChainUpdate }) => { +const ChainList: React.FC = ({ chainsData, onChainUpdate, isLoading }) => { const handleChainUpdate = (newBlock: BlockPayload) => { onChainUpdate(newBlock); }; @@ -18,6 +23,8 @@ const ChainList: React.FC = ({ chainsData, onChainUpdate }) => { 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}`; + return (
  • @@ -42,8 +49,28 @@ const ChainList: React.FC = ({ chainsData, onChainUpdate }) => {

    Block UUID: {block.blockUUID}

    -
    +
    + + + + + + + Share this Chain + +
    + +

    {shareUrl}

    + +
    +
    +
    @@ -70,7 +97,11 @@ const ChainList: React.FC = ({ chainsData, onChainUpdate }) => { Existing Chains - {rootBlocks.length === 0 ? ( + {isLoading ? ( +
    + +
    + ) : rootBlocks.length === 0 ? (

    No chains found.

    ) : (
      diff --git a/examples/buddybook/src/components/QRCode.tsx b/examples/buddybook/src/components/QRCode.tsx index 19814c7..a8b24c7 100644 --- a/examples/buddybook/src/components/QRCode.tsx +++ b/examples/buddybook/src/components/QRCode.tsx @@ -1,53 +1,18 @@ import React from 'react'; import { QRCodeSVG } from 'qrcode.react'; -import { BlockPayload } from '@/lib/waku'; -import { Button } from '@/components/ui/button'; -import { useEnsName } from 'wagmi'; interface QRCodeProps { - data: BlockPayload; - size?: number; - onSign?: () => void; + text: string; + width?: number; + height?: number; } -const QRCode: React.FC = ({ data, size = 256, onSign }) => { - const shareableLink = `${window.location.origin}/view/${data.chainUUID}/${data.blockUUID}`; - +const QRCode: React.FC = ({ text, width = 256, height = 256 }) => { return (
      - -
      -

      Title: {data.title}

      -

      Description: {data.description}

      -

      Timestamp: {new Date(data.timestamp).toLocaleString()}

      -

      Signed Message: {`0x${data.signedMessage.slice(2, 6)}....${data.signedMessage.slice(-6)}`}

      -

      Parent Block: {data.parentBlockUUID || 'Root'}

      -

      Signatures:

      -
        - {data.signatures.map((sig, index) => ( - - ))} -
      -
      - - {onSign && } +
      ); }; -const SignatureItem: React.FC<{ address: `0x${string}` }> = ({ address }) => { - const { data: ensName } = useEnsName({ address }); - - return ( -
    • - {ensName || `${address.slice(0, 6)}...${address.slice(-4)}`} -
    • - ); -}; - export default QRCode; diff --git a/examples/buddybook/tasks.md b/examples/buddybook/tasks.md index cfca48d..05c7274 100644 --- a/examples/buddybook/tasks.md +++ b/examples/buddybook/tasks.md @@ -1,4 +1,4 @@ -- [ ] waku connections on header should have green/yellow/red color indicator +- [ x ] waku connections on header should have green/yellow/red color indicator - [ ] clicking on the indicator should show a list of peers - [ ] chains can't be signed twice by an address - [ ] generate waku peer id using the wallet address