feat: sharable view links

This commit is contained in:
Danish Arora 2024-10-25 21:12:13 +05:30
parent 526e307af1
commit 2753453595
No known key found for this signature in database
GPG Key ID: 1C6EF37CDAE1426E
4 changed files with 102 additions and 47 deletions

View File

@ -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<boolean | null>(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() {
<main className="container mx-auto px-4 py-8">
<Routes>
<Route path="/create" element={<ChainCreationForm />} />
<Route path="/view" element={<ChainList chainsData={chainsData} onChainUpdate={handleChainUpdate} />} />
<Route path="/view" element={<ChainList chainsData={chainsData} onChainUpdate={handleChainUpdate} isLoading={isLoadingChains} />} />
<Route path="/" element={<Home />} />
<Route path="*" element={<Navigate to="/" replace />} />
<Route path="/sign/:chainUUID/:blockUUID" element={<SignSharedChain chainsData={chainsData} onChainUpdate={handleChainUpdate} />} />
<Route path="/telemetry" element={<TelemetryPage />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</main>
</div>

View File

@ -0,0 +1,53 @@
import React, { useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { BlockPayload } from '@/lib/waku';
import SignChain from './SignChain';
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
interface SignSharedChainProps {
chainsData: BlockPayload[];
onChainUpdate: (newBlock: BlockPayload) => void;
}
const SignSharedChain: React.FC<SignSharedChainProps> = ({ chainsData, onChainUpdate }) => {
const { chainUUID, blockUUID } = useParams();
const [block, setBlock] = useState<BlockPayload | null>(null);
const navigate = useNavigate();
useEffect(() => {
const foundBlock = chainsData.find(b => b.chainUUID === chainUUID && b.blockUUID === blockUUID);
if (foundBlock) {
setBlock(foundBlock);
}
}, [chainsData, chainUUID, blockUUID]);
if (!block) {
return (
<Card className="w-full max-w-md mx-auto">
<CardHeader>
<CardTitle>Chain Not Found</CardTitle>
</CardHeader>
<CardContent>
<p className="mb-4">The requested chain or block could not be found.</p>
<Button onClick={() => navigate('/view')}>View All Chains</Button>
</CardContent>
</Card>
);
}
return (
<Card className="w-full max-w-2xl mx-auto">
<CardHeader>
<CardTitle>Sign Shared Chain</CardTitle>
</CardHeader>
<CardContent>
<h2 className="text-xl font-semibold mb-2">{block.title}</h2>
<p className="mb-4">{block.description}</p>
<SignChain block={block} onSuccess={onChainUpdate} />
</CardContent>
</Card>
);
};
export default SignSharedChain;

View File

@ -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<ChainListProps> = ({ chainsData, onChainUpdate }) => {
const ChainList: React.FC<ChainListProps> = ({ chainsData, onChainUpdate, isLoading }) => {
const handleChainUpdate = (newBlock: BlockPayload) => {
onChainUpdate(newBlock);
};
@ -18,6 +23,8 @@ const ChainList: React.FC<ChainListProps> = ({ 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 (
<li key={`${block.blockUUID}-${depth}`} className="mb-4">
<div className="flex items-start">
@ -42,8 +49,28 @@ const ChainList: React.FC<ChainListProps> = ({ chainsData, onChainUpdate }) => {
<p className="text-sm text-muted-foreground">
Block UUID: {block.blockUUID}
</p>
<div className="mt-2">
<div className="mt-2 space-x-2">
<SignChain block={block} onSuccess={handleChainUpdate} />
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Share</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Share this Chain</DialogTitle>
</DialogHeader>
<div className="flex flex-col items-center space-y-4">
<QRCode text={shareUrl} width={200} height={200} />
<p className="text-sm text-center break-all">{shareUrl}</p>
<Button
onClick={() => navigator.clipboard.writeText(shareUrl)}
variant="outline"
>
Copy Link
</Button>
</div>
</DialogContent>
</Dialog>
</div>
</CardContent>
</Card>
@ -70,7 +97,11 @@ const ChainList: React.FC<ChainListProps> = ({ chainsData, onChainUpdate }) => {
<CardTitle>Existing Chains</CardTitle>
</CardHeader>
<CardContent>
{rootBlocks.length === 0 ? (
{isLoading ? (
<div className="flex justify-center items-center h-32">
<Loader2 className="h-8 w-8 animate-spin" />
</div>
) : rootBlocks.length === 0 ? (
<p>No chains found.</p>
) : (
<ul className="space-y-4">

View File

@ -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<QRCodeProps> = ({ data, size = 256, onSign }) => {
const shareableLink = `${window.location.origin}/view/${data.chainUUID}/${data.blockUUID}`;
const QRCode: React.FC<QRCodeProps> = ({ text, width = 256, height = 256 }) => {
return (
<div className="flex flex-col items-center space-y-4">
<QRCodeSVG value={shareableLink} size={size} />
<div className="text-sm text-muted-foreground">
<p><strong>Title:</strong> {data.title}</p>
<p><strong>Description:</strong> {data.description}</p>
<p><strong>Timestamp:</strong> {new Date(data.timestamp).toLocaleString()}</p>
<p><strong>Signed Message:</strong> {`0x${data.signedMessage.slice(2, 6)}....${data.signedMessage.slice(-6)}`}</p>
<p><strong>Parent Block:</strong> {data.parentBlockUUID || 'Root'}</p>
<p><strong>Signatures:</strong></p>
<ul>
{data.signatures.map((sig, index) => (
<SignatureItem key={index} address={sig.address} />
))}
</ul>
</div>
<input
type="text"
value={shareableLink}
readOnly
className="w-full p-2 border rounded"
/>
{onSign && <Button onClick={onSign}>Sign This Block</Button>}
<QRCodeSVG value={text} size={Math.min(width, height)} />
</div>
);
};
const SignatureItem: React.FC<{ address: `0x${string}` }> = ({ address }) => {
const { data: ensName } = useEnsName({ address });
return (
<li>
{ensName || `${address.slice(0, 6)}...${address.slice(-4)}`}
</li>
);
};
export default QRCode;