mirror of
https://github.com/waku-org/waku-lab.git
synced 2025-02-03 11:33:53 +00:00
chore(buddychain): improvements (#104)
This commit is contained in:
parent
49460a2f62
commit
ac506bcf5a
11
ci/Jenkinsfile
vendored
11
ci/Jenkinsfile
vendored
@ -44,16 +44,7 @@ pipeline {
|
||||
stage('dogfooding') { steps { script { buildExample() } } }
|
||||
stage('message-monitor') { steps { script { buildExample() } } }
|
||||
stage('flush-notes') { steps { script { buildNextJSExample() } } }
|
||||
stage('buddybook') {
|
||||
steps {
|
||||
script {
|
||||
dir('examples/buddybook') {
|
||||
sh 'npm install'
|
||||
sh 'npm run build:ci'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('buddybook') { steps { script { buildExample() } } }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Buddychain Dogfood</title>
|
||||
<title>BuddyBook Dogfood</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
@ -41,13 +41,8 @@ function App() {
|
||||
|
||||
useEffect(() => {
|
||||
if (isWakuLoading || !node || node.libp2p.getConnections().length === 0 || chainsData.length > 0 || isListening) return;
|
||||
|
||||
setTimeout(() => {
|
||||
setIsListening(true);
|
||||
startMessageListening();
|
||||
}, 3000);
|
||||
|
||||
|
||||
}, [node, isWakuLoading, wakuStatus])
|
||||
|
||||
const handleTelemetryOptIn = (optIn: boolean) => {
|
||||
@ -117,7 +112,7 @@ function App() {
|
||||
return (
|
||||
<div className="min-h-screen bg-background text-foreground">
|
||||
<Header wakuStatus={wakuStatus} />
|
||||
<main className="container mx-auto px-4 py-8">
|
||||
<main className="container mx-auto px-4 py-4 md:py-8 max-w-7xl">
|
||||
<Routes>
|
||||
<Route path="/create" element={<ChainCreationForm />} />
|
||||
<Route path="/view" element={<ChainList chainsData={chainsData} onChainUpdate={handleChainUpdate} isLoading={isLoadingChains} />} />
|
||||
@ -132,22 +127,20 @@ function App() {
|
||||
}
|
||||
|
||||
const Home: React.FC = () => (
|
||||
<div className="space-y-6 text-center">
|
||||
<h1 className="text-4xl font-bold">BuddyChain</h1>
|
||||
<div className="max-w-md mx-auto p-6 bg-card rounded-lg shadow-md">
|
||||
<div className="space-y-4 md:space-y-6 p-4 md:p-6">
|
||||
<h1 className="text-2xl md:text-4xl font-bold">BuddyBook</h1>
|
||||
<div className="w-full max-w-sm mx-auto p-4 md:p-6 bg-card rounded-lg shadow-md">
|
||||
<Link to="/create">
|
||||
<Button
|
||||
className="w-full mb-4"
|
||||
>
|
||||
<Button className="w-full mb-4">
|
||||
Create New Chain
|
||||
</Button>
|
||||
</Link>
|
||||
<p className="text-muted-foreground">
|
||||
<p className="text-sm md:text-base text-muted-foreground">
|
||||
Click the button above to start creating a new chain.
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Welcome to BuddyChain - Create and share your chains!
|
||||
<p className="text-xs md:text-sm text-muted-foreground text-center">
|
||||
Welcome to BuddyBook - Create and share your chains!
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
|
@ -11,10 +11,11 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
interface SignChainProps {
|
||||
block: BlockPayload;
|
||||
chainsData: BlockPayload[]; // Add this prop
|
||||
onSuccess: (newBlock: BlockPayload) => void;
|
||||
}
|
||||
|
||||
const SignChain: React.FC<SignChainProps> = ({ block, onSuccess }) => {
|
||||
const SignChain: React.FC<SignChainProps> = ({ block, chainsData, onSuccess }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isSigning, setIsSigning] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@ -25,17 +26,37 @@ const SignChain: React.FC<SignChainProps> = ({ block, onSuccess }) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (address) {
|
||||
const hasAlreadySigned = block.signatures.some(sig => sig.address.toLowerCase() === address.toLowerCase());
|
||||
// Check if the address has signed this block or any blocks in the chain
|
||||
const checkSignatures = (blockToCheck: BlockPayload): boolean => {
|
||||
// Check current block's signatures
|
||||
if (blockToCheck.signatures.some(
|
||||
sig => sig.address.toLowerCase() === address.toLowerCase()
|
||||
)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check parent blocks
|
||||
const parentBlock = chainsData.find(b => b.blockUUID === blockToCheck.parentBlockUUID);
|
||||
if (parentBlock && checkSignatures(parentBlock)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check child blocks
|
||||
const childBlocks = chainsData.filter(b => b.parentBlockUUID === blockToCheck.blockUUID);
|
||||
return childBlocks.some(childBlock => checkSignatures(childBlock));
|
||||
};
|
||||
|
||||
const hasAlreadySigned = checkSignatures(block);
|
||||
setAlreadySigned(hasAlreadySigned);
|
||||
}
|
||||
}, [address, block.signatures]);
|
||||
}, [address, block, chainsData]);
|
||||
|
||||
const { signMessage } = useSignMessage({
|
||||
mutation: {
|
||||
async onSuccess(signature) {
|
||||
if (!address || !node) return;
|
||||
|
||||
// Check if the address has already signed
|
||||
// Double check signature before proceeding
|
||||
if (block.signatures.some(sig => sig.address.toLowerCase() === address.toLowerCase())) {
|
||||
setError('You have already signed this chain.');
|
||||
setIsSigning(false);
|
||||
@ -79,6 +100,7 @@ const SignChain: React.FC<SignChainProps> = ({ block, onSuccess }) => {
|
||||
});
|
||||
|
||||
const handleSign = () => {
|
||||
// Add an additional check here before signing
|
||||
if (alreadySigned) {
|
||||
setError('You have already signed this chain.');
|
||||
return;
|
||||
@ -102,7 +124,7 @@ const SignChain: React.FC<SignChainProps> = ({ block, onSuccess }) => {
|
||||
{alreadySigned ? 'Already Signed' : 'Sign Chain'}
|
||||
</Button>
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogContent>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Sign Chain</DialogTitle>
|
||||
<DialogDescription>
|
||||
@ -111,7 +133,14 @@ const SignChain: React.FC<SignChainProps> = ({ block, onSuccess }) => {
|
||||
: 'Review the block details and sign to add your signature to the chain.'}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<QRCode data={block} />
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium">Block Details</h4>
|
||||
<p className="text-sm text-muted-foreground">{block.title}</p>
|
||||
<p className="text-sm text-muted-foreground">{block.description}</p>
|
||||
</div>
|
||||
<QRCode text={`${window.location.origin}/sign/${block.chainUUID}/${block.blockUUID}`} />
|
||||
</div>
|
||||
{error && <p className="text-sm text-destructive">{error}</p>}
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" onClick={() => setIsOpen(false)}>Cancel</Button>
|
||||
|
@ -44,7 +44,11 @@ const SignSharedChain: React.FC<SignSharedChainProps> = ({ chainsData, onChainUp
|
||||
<CardContent>
|
||||
<h2 className="text-xl font-semibold mb-2">{block.title}</h2>
|
||||
<p className="mb-4">{block.description}</p>
|
||||
<SignChain block={block} onSuccess={onChainUpdate} />
|
||||
<SignChain
|
||||
block={block}
|
||||
chainsData={chainsData}
|
||||
onSuccess={onChainUpdate}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
@ -4,7 +4,7 @@ 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 { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogDescription } from "@/components/ui/dialog";
|
||||
import QRCode from '@/components/QRCode';
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
@ -50,7 +50,11 @@ const ChainList: React.FC<ChainListProps> = ({ chainsData, onChainUpdate, isLoad
|
||||
Block UUID: {block.blockUUID}
|
||||
</p>
|
||||
<div className="mt-2 space-x-2">
|
||||
<SignChain block={block} onSuccess={handleChainUpdate} />
|
||||
<SignChain
|
||||
block={block}
|
||||
chainsData={chainsData}
|
||||
onSuccess={handleChainUpdate}
|
||||
/>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">Share</Button>
|
||||
@ -58,6 +62,9 @@ const ChainList: React.FC<ChainListProps> = ({ chainsData, onChainUpdate, isLoad
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Share this Chain</DialogTitle>
|
||||
<DialogDescription>
|
||||
Share this chain with others to collect their signatures.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col items-center space-y-4">
|
||||
<QRCode text={shareUrl} width={200} height={200} />
|
||||
|
@ -56,69 +56,78 @@ const Header: React.FC<HeaderProps> = ({ wakuStatus }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="bg-background border-b border-border">
|
||||
<div className="container mx-auto px-4 py-4 flex justify-between items-center">
|
||||
<div className="flex items-center space-x-4">
|
||||
<h1 className="text-2xl font-bold">BuddyBook</h1>
|
||||
<nav>
|
||||
<ul className="flex space-x-4">
|
||||
<li>
|
||||
<Link
|
||||
to="/create"
|
||||
className={`text-sm ${location.pathname === '/create' ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
|
||||
>
|
||||
Create Chain
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
to="/view"
|
||||
className={`text-sm ${location.pathname === '/view' ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
|
||||
>
|
||||
View Existing Chains
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
to="/telemetry"
|
||||
className={`text-sm ${location.pathname === '/telemetry' ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
|
||||
>
|
||||
Telemetry
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex items-center space-x-1">
|
||||
<span className="text-sm text-muted-foreground">Filter:</span>
|
||||
<div className={`w-3 h-3 rounded-full ${getStatusColor(wakuStatus.filter)}`}></div>
|
||||
<header className="border-b">
|
||||
<div className="container mx-auto px-4 py-2 md: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 === '/create' ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
|
||||
>
|
||||
Create Chain
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
to="/view"
|
||||
className={`text-sm ${location.pathname === '/view' ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
|
||||
>
|
||||
View Chains
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
to="/telemetry"
|
||||
className={`text-sm ${location.pathname === '/telemetry' ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
|
||||
>
|
||||
Telemetry
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<span className="text-sm text-muted-foreground">Store:</span>
|
||||
<div className={`w-3 h-3 rounded-full ${getStatusColor(wakuStatus.store)}`}></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">
|
||||
<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>
|
||||
|
||||
<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 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>
|
||||
</div>
|
||||
{isWakuLoading ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : wakuError ? (
|
||||
<span className="text-sm text-red-500">Waku Error</span>
|
||||
) : (
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Waku Connections: {connections}
|
||||
</span>
|
||||
)}
|
||||
{isConnected ? (
|
||||
<>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{ensName || (address ? `${address.slice(0, 6)}...${address.slice(-4)}` : '')}
|
||||
</span>
|
||||
<Button variant="outline" size="sm" onClick={() => disconnect()}>
|
||||
Logout
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<ConnectKitButton />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Check, Copy } from "lucide-react";
|
||||
|
||||
interface QRCodeProps {
|
||||
text: string;
|
||||
@ -8,9 +10,32 @@ interface QRCodeProps {
|
||||
}
|
||||
|
||||
const QRCode: React.FC<QRCodeProps> = ({ text, width = 256, height = 256 }) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = async () => {
|
||||
await navigator.clipboard.writeText(text);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
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">
|
||||
<input
|
||||
type="text"
|
||||
value={text}
|
||||
readOnly
|
||||
className="flex-1 px-3 py-2 text-sm border rounded-md bg-muted"
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={handleCopy}
|
||||
>
|
||||
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -6,41 +6,58 @@ import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { privacyPolicy } from '@/lib/privacyPolicy';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
|
||||
interface TelemetryOptInProps {
|
||||
interface PrivacyPolicyOptInProps {
|
||||
onOptIn: (optIn: boolean) => void;
|
||||
}
|
||||
|
||||
const TelemetryOptIn: React.FC<TelemetryOptInProps> = ({ onOptIn }) => {
|
||||
const PrivacyPolicyOptIn: React.FC<PrivacyPolicyOptInProps> = ({ onOptIn }) => {
|
||||
const [showFullPolicy, setShowFullPolicy] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-background">
|
||||
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader>
|
||||
<CardTitle>Telemetry Data Collection</CardTitle>
|
||||
<CardHeader className="space-y-2">
|
||||
<CardTitle className="text-xl sm:text-2xl">Privacy Policy & Data Collection</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
We collect telemetry data to improve our services. This data is anonymous and helps us understand how our application is used. You can opt-in or opt-out of this data collection.
|
||||
</p>
|
||||
<Button variant="link" onClick={() => setShowFullPolicy(true)}>
|
||||
View Full Privacy Policy
|
||||
</Button>
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm sm:text-base text-muted-foreground">
|
||||
We collect data to improve our services. This data is anonymous and helps us understand how our application is used. You can opt-in or opt-out of this data collection.
|
||||
</p>
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={() => setShowFullPolicy(true)}
|
||||
className="px-0 text-sm sm:text-base"
|
||||
>
|
||||
View Full Privacy Policy
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-between">
|
||||
<Button variant="outline" onClick={() => onOptIn(false)}>Opt Out</Button>
|
||||
<Button onClick={() => onOptIn(true)}>Opt In</Button>
|
||||
<CardFooter className="flex flex-col sm:flex-row gap-3 sm:gap-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => onOptIn(false)}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
Opt Out
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => onOptIn(true)}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
Opt In
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
<Dialog open={showFullPolicy} onOpenChange={setShowFullPolicy}>
|
||||
<DialogContent className="max-w-4xl max-h-[80vh]">
|
||||
<DialogContent className="w-[95vw] max-w-4xl max-h-[90vh] p-4 sm:p-6">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Privacy Policy</DialogTitle>
|
||||
<DialogTitle className="text-xl sm:text-2xl">Privacy Policy</DialogTitle>
|
||||
</DialogHeader>
|
||||
<ScrollArea className="mt-4 h-[60vh]">
|
||||
<ScrollArea className="mt-4 h-[50vh] sm:h-[60vh]">
|
||||
<DialogDescription className="space-y-4">
|
||||
<ReactMarkdown className="prose dark:prose-invert max-w-none">
|
||||
<ReactMarkdown className="prose dark:prose-invert max-w-none text-sm sm:text-base">
|
||||
{privacyPolicy}
|
||||
</ReactMarkdown>
|
||||
</DialogDescription>
|
||||
@ -51,4 +68,4 @@ const TelemetryOptIn: React.FC<TelemetryOptInProps> = ({ onOptIn }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default TelemetryOptIn;
|
||||
export default PrivacyPolicyOptIn;
|
||||
|
@ -6,44 +6,49 @@ import { privacyPolicy } from '@/lib/privacyPolicy';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
|
||||
|
||||
const TelemetryPage: React.FC = () => {
|
||||
const [telemetryOptIn, setTelemetryOptIn] = useState<boolean>(false);
|
||||
const PrivacyPolicyPage: React.FC = () => {
|
||||
const [privacyPolicyOptIn, setPrivacyPolicyOptIn] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const storedOptIn = localStorage.getItem('telemetryOptIn');
|
||||
const storedOptIn = localStorage.getItem('privacyPolicyOptIn');
|
||||
if (storedOptIn !== null) {
|
||||
setTelemetryOptIn(storedOptIn === 'true');
|
||||
setPrivacyPolicyOptIn(storedOptIn === 'true');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleToggleTelemetry = () => {
|
||||
const newOptIn = !telemetryOptIn;
|
||||
setTelemetryOptIn(newOptIn);
|
||||
localStorage.setItem('telemetryOptIn', newOptIn.toString());
|
||||
const handleTogglePrivacyPolicy = () => {
|
||||
const newOptIn = !privacyPolicyOptIn;
|
||||
setPrivacyPolicyOptIn(newOptIn);
|
||||
localStorage.setItem('privacyPolicyOptIn', newOptIn.toString());
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="w-full max-w-4xl mx-auto">
|
||||
<CardHeader>
|
||||
<CardTitle>Telemetry Settings</CardTitle>
|
||||
<Card className="w-full max-w-4xl mx-auto p-4 sm:p-6">
|
||||
<CardHeader className="space-y-2">
|
||||
<CardTitle className="text-2xl sm:text-3xl">Privacy Policy Settings</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground mb-2">
|
||||
We collect telemetry data to improve our services. This data is anonymous and helps us understand how our application is used.
|
||||
<div className="space-y-6 sm:space-y-8">
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm sm:text-base text-muted-foreground">
|
||||
We collect data to improve our services. This data is anonymous and helps us understand how our application is used.
|
||||
</p>
|
||||
<p className="font-semibold mb-2">
|
||||
Current status: {telemetryOptIn ? 'Opted In' : 'Opted Out'}
|
||||
</p>
|
||||
<Button onClick={handleToggleTelemetry}>
|
||||
{telemetryOptIn ? 'Opt Out' : 'Opt In'}
|
||||
</Button>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<p className="font-semibold">
|
||||
Current status: {privacyPolicyOptIn ? 'Opted In' : 'Opted Out'}
|
||||
</p>
|
||||
<Button
|
||||
onClick={handleTogglePrivacyPolicy}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
{privacyPolicyOptIn ? 'Opt Out' : 'Opt In'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-4">Privacy Policy</h3>
|
||||
<ScrollArea className="h-[60vh] border rounded-md p-4">
|
||||
<ReactMarkdown className="prose dark:prose-invert max-w-none">
|
||||
<h3 className="text-lg sm:text-xl font-semibold mb-4">Privacy Policy</h3>
|
||||
<ScrollArea className="h-[50vh] sm:h-[60vh] border rounded-md p-2 sm:p-4">
|
||||
<ReactMarkdown className="prose dark:prose-invert max-w-none text-sm sm:text-base">
|
||||
{privacyPolicy}
|
||||
</ReactMarkdown>
|
||||
</ScrollArea>
|
||||
@ -54,4 +59,4 @@ const TelemetryPage: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default TelemetryPage;
|
||||
export default PrivacyPolicyPage;
|
||||
|
@ -9,7 +9,7 @@ const Card = React.forwardRef<
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-xl border bg-card text-card-foreground shadow",
|
||||
"rounded-lg border bg-card text-card-foreground shadow-sm p-4 md:p-6",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@ -23,7 +23,7 @@ const CardHeader = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||
className={cn("flex flex-col space-y-1.5 p-4 md:p-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
@ -56,11 +56,34 @@
|
||||
--chart-5: 340 75% 55%
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ export type BlockPayload = {
|
||||
parentBlockUUID: string | null;
|
||||
}
|
||||
|
||||
const contentTopic = "/buddychain-dogfood/1/chain/proto";
|
||||
const contentTopic = "/buddybook-dogfood/1/chain/proto";
|
||||
|
||||
export const encoder = createEncoder({
|
||||
contentTopic: contentTopic,
|
||||
|
@ -12,4 +12,4 @@ export const config = createConfig(
|
||||
[mainnet.id]: http(),
|
||||
},
|
||||
}),
|
||||
)
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
- [ 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
|
||||
- [ x ] chains can't be signed twice by an address
|
||||
- [ ] generate waku peer id using the wallet address
|
||||
- [ ] telemetry
|
||||
- [ x ] disclaimer
|
||||
|
Loading…
x
Reference in New Issue
Block a user