chore: make app responsive, and fix signing block

This commit is contained in:
Danish Arora 2024-10-28 16:58:57 +05:30
parent 8746cb43b0
commit 899b3a1fb4
No known key found for this signature in database
GPG Key ID: 1C6EF37CDAE1426E
9 changed files with 193 additions and 111 deletions

View File

@ -7,7 +7,7 @@ import { Button } from "@/components/ui/button"
import { type LightNode } from "@waku/sdk" import { type LightNode } from "@waku/sdk"
import { useWaku } from "@waku/react" import { useWaku } from "@waku/react"
import { Loader2 } from "lucide-react" import { Loader2 } from "lucide-react"
import { Routes, Route, Navigate, Link, useParams } from 'react-router-dom' import { Routes, Route, Navigate, Link } from 'react-router-dom'
import { BlockPayload, getMessagesFromStore, subscribeToFilter } from './lib/waku' import { BlockPayload, getMessagesFromStore, subscribeToFilter } from './lib/waku'
import TelemetryOptIn from './components/TelemetryOptIn'; import TelemetryOptIn from './components/TelemetryOptIn';
import TelemetryPage from './components/TelemetryPage'; import TelemetryPage from './components/TelemetryPage';
@ -117,7 +117,7 @@ function App() {
return ( return (
<div className="min-h-screen bg-background text-foreground"> <div className="min-h-screen bg-background text-foreground">
<Header wakuStatus={wakuStatus} /> <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> <Routes>
<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} />} />
@ -132,21 +132,19 @@ function App() {
} }
const Home: React.FC = () => ( const Home: React.FC = () => (
<div className="space-y-6 text-center"> <div className="space-y-4 md:space-y-6 p-4 md:p-6">
<h1 className="text-4xl font-bold">BuddyChain</h1> <h1 className="text-2xl md:text-4xl font-bold">BuddyChain</h1>
<div className="max-w-md mx-auto p-6 bg-card rounded-lg shadow-md"> <div className="w-full max-w-sm mx-auto p-4 md:p-6 bg-card rounded-lg shadow-md">
<Link to="/create"> <Link to="/create">
<Button <Button className="w-full mb-4">
className="w-full mb-4"
>
Create New Chain Create New Chain
</Button> </Button>
</Link> </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. Click the button above to start creating a new chain.
</p> </p>
</div> </div>
<p className="text-sm text-muted-foreground"> <p className="text-xs md:text-sm text-muted-foreground text-center">
Welcome to BuddyChain - Create and share your chains! Welcome to BuddyChain - Create and share your chains!
</p> </p>
</div> </div>

View File

@ -11,10 +11,11 @@ import { v4 as uuidv4 } from 'uuid';
interface SignChainProps { interface SignChainProps {
block: BlockPayload; block: BlockPayload;
chainsData: BlockPayload[]; // Add this prop
onSuccess: (newBlock: BlockPayload) => void; 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 [isOpen, setIsOpen] = useState(false);
const [isSigning, setIsSigning] = useState(false); const [isSigning, setIsSigning] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@ -25,17 +26,37 @@ const SignChain: React.FC<SignChainProps> = ({ block, onSuccess }) => {
useEffect(() => { useEffect(() => {
if (address) { 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); setAlreadySigned(hasAlreadySigned);
} }
}, [address, block.signatures]); }, [address, block, chainsData]);
const { signMessage } = useSignMessage({ const { signMessage } = useSignMessage({
mutation: { mutation: {
async onSuccess(signature) { async onSuccess(signature) {
if (!address || !node) return; 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())) { if (block.signatures.some(sig => sig.address.toLowerCase() === address.toLowerCase())) {
setError('You have already signed this chain.'); setError('You have already signed this chain.');
setIsSigning(false); setIsSigning(false);
@ -79,6 +100,7 @@ const SignChain: React.FC<SignChainProps> = ({ block, onSuccess }) => {
}); });
const handleSign = () => { const handleSign = () => {
// Add an additional check here before signing
if (alreadySigned) { if (alreadySigned) {
setError('You have already signed this chain.'); setError('You have already signed this chain.');
return; return;

View File

@ -44,7 +44,11 @@ const SignSharedChain: React.FC<SignSharedChainProps> = ({ chainsData, onChainUp
<CardContent> <CardContent>
<h2 className="text-xl font-semibold mb-2">{block.title}</h2> <h2 className="text-xl font-semibold mb-2">{block.title}</h2>
<p className="mb-4">{block.description}</p> <p className="mb-4">{block.description}</p>
<SignChain block={block} onSuccess={onChainUpdate} /> <SignChain
block={block}
chainsData={chainsData}
onSuccess={onChainUpdate}
/>
</CardContent> </CardContent>
</Card> </Card>
); );

View File

@ -50,7 +50,11 @@ const ChainList: React.FC<ChainListProps> = ({ chainsData, onChainUpdate, isLoad
Block UUID: {block.blockUUID} Block UUID: {block.blockUUID}
</p> </p>
<div className="mt-2 space-x-2"> <div className="mt-2 space-x-2">
<SignChain block={block} onSuccess={handleChainUpdate} /> <SignChain
block={block}
chainsData={chainsData}
onSuccess={handleChainUpdate}
/>
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="outline">Share</Button> <Button variant="outline">Share</Button>

View File

@ -56,12 +56,13 @@ const Header: React.FC<HeaderProps> = ({ wakuStatus }) => {
}; };
return ( return (
<header className="bg-background border-b border-border"> <header className="border-b">
<div className="container mx-auto px-4 py-4 flex justify-between items-center"> <div className="container mx-auto px-4 py-2 md:py-4">
<div className="flex items-center space-x-4"> <div className="flex flex-col md:flex-row justify-between items-center space-y-2 md:space-y-0">
<h1 className="text-2xl font-bold">BuddyBook</h1> <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">
<nav> <h1 className="text-xl md:text-2xl font-bold">BuddyBook</h1>
<ul className="flex space-x-4"> <nav className="w-full md:w-auto">
<ul className="flex justify-center md:justify-start space-x-4">
<li> <li>
<Link <Link
to="/create" to="/create"
@ -75,7 +76,7 @@ const Header: React.FC<HeaderProps> = ({ wakuStatus }) => {
to="/view" to="/view"
className={`text-sm ${location.pathname === '/view' ? 'text-primary font-semibold' : 'text-muted-foreground'}`} className={`text-sm ${location.pathname === '/view' ? 'text-primary font-semibold' : 'text-muted-foreground'}`}
> >
View Existing Chains View Chains
</Link> </Link>
</li> </li>
<li> <li>
@ -89,38 +90,46 @@ const Header: React.FC<HeaderProps> = ({ wakuStatus }) => {
</ul> </ul>
</nav> </nav>
</div> </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"> <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>
</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>
{isWakuLoading ? ( {isWakuLoading ? (
<Loader2 className="h-4 w-4 animate-spin" /> <Loader2 className="h-4 w-4 animate-spin" />
) : wakuError ? ( ) : wakuError ? (
<span className="text-sm text-red-500">Waku Error</span> <span className="text-xs md:text-sm text-red-500">Waku Error</span>
) : ( ) : (
<span className="text-sm text-muted-foreground"> <span className="text-xs md:text-sm text-muted-foreground hidden md:inline">
Waku Connections: {connections} Waku Connections: {connections}
</span> </span>
)} )}
{isConnected ? ( {isConnected ? (
<> <div className="flex items-center space-x-2">
<span className="text-sm text-muted-foreground"> <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)}` : '')} {ensName || (address ? `${address.slice(0, 6)}...${address.slice(-4)}` : '')}
</span> </span>
<Button variant="outline" size="sm" onClick={() => disconnect()}> <Button variant="outline" size="sm" onClick={() => disconnect()}>
Logout Logout
</Button> </Button>
</> </div>
) : ( ) : (
<ConnectKitButton /> <ConnectKitButton />
)} )}
</div> </div>
</div> </div>
</div>
</div>
</header> </header>
); );
}; };

View File

@ -14,33 +14,50 @@ const PrivacyPolicyOptIn: React.FC<PrivacyPolicyOptInProps> = ({ onOptIn }) => {
const [showFullPolicy, setShowFullPolicy] = useState(false); const [showFullPolicy, setShowFullPolicy] = useState(false);
return ( 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"> <Card className="w-full max-w-md">
<CardHeader> <CardHeader className="space-y-2">
<CardTitle>Privacy Policy & Data Collection</CardTitle> <CardTitle className="text-xl sm:text-2xl">Privacy Policy & Data Collection</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<p className="text-sm text-muted-foreground mb-4"> <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. 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> </p>
<Button variant="link" onClick={() => setShowFullPolicy(true)}> <Button
variant="link"
onClick={() => setShowFullPolicy(true)}
className="px-0 text-sm sm:text-base"
>
View Full Privacy Policy View Full Privacy Policy
</Button> </Button>
</div>
</CardContent> </CardContent>
<CardFooter className="flex justify-between"> <CardFooter className="flex flex-col sm:flex-row gap-3 sm:gap-4">
<Button variant="outline" onClick={() => onOptIn(false)}>Opt Out</Button> <Button
<Button onClick={() => onOptIn(true)}>Opt In</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> </CardFooter>
</Card> </Card>
<Dialog open={showFullPolicy} onOpenChange={setShowFullPolicy}> <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> <DialogHeader>
<DialogTitle>Privacy Policy</DialogTitle> <DialogTitle className="text-xl sm:text-2xl">Privacy Policy</DialogTitle>
</DialogHeader> </DialogHeader>
<ScrollArea className="mt-4 h-[60vh]"> <ScrollArea className="mt-4 h-[50vh] sm:h-[60vh]">
<DialogDescription className="space-y-4"> <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} {privacyPolicy}
</ReactMarkdown> </ReactMarkdown>
</DialogDescription> </DialogDescription>

View File

@ -23,27 +23,32 @@ const PrivacyPolicyPage: React.FC = () => {
}; };
return ( return (
<Card className="w-full max-w-4xl mx-auto"> <Card className="w-full max-w-4xl mx-auto p-4 sm:p-6">
<CardHeader> <CardHeader className="space-y-2">
<CardTitle>Privacy Policy Settings</CardTitle> <CardTitle className="text-2xl sm:text-3xl">Privacy Policy Settings</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="space-y-6"> <div className="space-y-6 sm:space-y-8">
<div> <div className="space-y-4">
<p className="text-sm text-muted-foreground mb-2"> <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. We collect data to improve our services. This data is anonymous and helps us understand how our application is used.
</p> </p>
<p className="font-semibold mb-2"> <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'} Current status: {privacyPolicyOptIn ? 'Opted In' : 'Opted Out'}
</p> </p>
<Button onClick={handleTogglePrivacyPolicy}> <Button
onClick={handleTogglePrivacyPolicy}
className="w-full sm:w-auto"
>
{privacyPolicyOptIn ? 'Opt Out' : 'Opt In'} {privacyPolicyOptIn ? 'Opt Out' : 'Opt In'}
</Button> </Button>
</div> </div>
</div>
<div> <div>
<h3 className="text-lg font-semibold mb-4">Privacy Policy</h3> <h3 className="text-lg sm:text-xl font-semibold mb-4">Privacy Policy</h3>
<ScrollArea className="h-[60vh] border rounded-md p-4"> <ScrollArea className="h-[50vh] sm:h-[60vh] border rounded-md p-2 sm:p-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} {privacyPolicy}
</ReactMarkdown> </ReactMarkdown>
</ScrollArea> </ScrollArea>

View File

@ -9,7 +9,7 @@ const Card = React.forwardRef<
<div <div
ref={ref} ref={ref}
className={cn( 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 className
)} )}
{...props} {...props}
@ -23,7 +23,7 @@ const CardHeader = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div <div
ref={ref} 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} {...props}
/> />
)) ))

View File

@ -56,6 +56,7 @@
--chart-5: 340 75% 55% --chart-5: 340 75% 55%
} }
} }
@layer base { @layer base {
* { * {
@apply border-border; @apply border-border;
@ -63,4 +64,26 @@
body { body {
@apply bg-background text-foreground; @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;
}
} }