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 { useWaku } from "@waku/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 TelemetryOptIn from './components/TelemetryOptIn';
import TelemetryPage from './components/TelemetryPage';
@ -117,7 +117,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,21 +132,19 @@ 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">BuddyChain</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">
<p className="text-xs md:text-sm text-muted-foreground text-center">
Welcome to BuddyChain - Create and share your chains!
</p>
</div>

View File

@ -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;

View File

@ -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>
);

View File

@ -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>

View File

@ -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>

View File

@ -14,33 +14,50 @@ 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>Privacy Policy & 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 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>

View File

@ -23,27 +23,32 @@ const PrivacyPolicyPage: React.FC = () => {
};
return (
<Card className="w-full max-w-4xl mx-auto">
<CardHeader>
<CardTitle>Privacy Policy 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">
<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: {privacyPolicyOptIn ? 'Opted In' : 'Opted Out'}
</p>
<Button onClick={handleTogglePrivacyPolicy}>
{privacyPolicyOptIn ? '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>

View File

@ -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}
/>
))

View File

@ -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;
}
}