Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions website/src/components/CodeSnippetSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, { useState } from 'react';
import { Code2, Terminal, Copy, Check } from 'lucide-react';

const snippets = {
javascript: `const Web3 = require('web3');
const web3 = new Web3('https://rpc.xinfin.network');

const balance = await web3.eth.getBalance('0x...');
console.log(balance);`,
python: `from web3 import Web3

w3 = Web3(Web3.HTTPProvider('https://rpc.xinfin.network'))
balance = w3.eth.get_balance('0x...')
print(balance)`,
curl: `curl -X POST https://rpc.xinfin/network \\
-H "Content-Type: application/json" \\
-d '{
"jsonrpc":"2.0",
"method":"eth_getBalance",
"params":["0x...","latest"],
"id":1
}'`,
solidity: `// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HelloXDC {
string public message = "Hello XDC";
}`,
};

type Language = keyof typeof snippets;

export function CodeSnippetSwitcher() {
const [lang, setLang] = useState<Language>('javascript');
const [copied, setCopied] = useState(false);

const handleCopy = async () => {
await navigator.clipboard.writeText(snippets[lang]);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};

return (
<div className="my-6 overflow-hidden rounded-xl border border-border bg-card shadow-[0_0_0_1px_rgba(0,0,0,0.08)] dark:shadow-[0_0_0_1px_rgba(255,255,255,0.08)]">
<div className="flex items-center justify-between border-b border-border px-4 py-2">
<div className="flex items-center gap-2">
<Code2 className="h-4 w-4 text-primary" />
<span className="text-sm font-semibold">Code Snippet</span>
</div>
<button
onClick={handleCopy}
className="flex items-center gap-1 rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-primary"
>
{copied ? <Check className="h-3 w-3 text-emerald-500" /> : <Copy className="h-3 w-3" />}
{copied ? 'Copied' : 'Copy'}
</button>
</div>
<div className="flex border-b border-border">
{(Object.keys(snippets) as Language[]).map((l) => (
<button
key={l}
onClick={() => setLang(l)}
className={`px-4 py-2 text-xs font-medium capitalize transition-colors ${
lang === l
? 'border-b-2 border-primary text-primary'
: 'text-muted-foreground hover:text-foreground'
}`}
>
{l}
</button>
))}
</div>
<div className="relative bg-[#111111] p-4">
<pre className="m-0 overflow-x-auto text-xs leading-relaxed text-gray-300">
<code>{snippets[lang]}</code>
</pre>
</div>
</div>
);
}

export default CodeSnippetSwitcher;
54 changes: 54 additions & 0 deletions website/src/components/CodeTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React, { useEffect, useState } from 'react';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@site/src/components/ui/tabs';

interface CodeTabsProps {
defaultValue: string;
children: React.ReactNode;
group?: string;
}

const STORAGE_KEY = 'xdc-docs-code-language';

export function CodeTabs({ defaultValue, children, group = 'default' }: CodeTabsProps) {
const [value, setValue] = useState(defaultValue);
const [mounted, setMounted] = useState(false);

useEffect(() => {
setMounted(true);
try {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
const parsed = JSON.parse(saved);
if (parsed[group]) {
setValue(parsed[group]);
}
}
} catch {
// ignore
}
}, [group]);

const handleChange = (newValue: string) => {
setValue(newValue);
try {
const saved = localStorage.getItem(STORAGE_KEY);
const parsed = saved ? JSON.parse(saved) : {};
parsed[group] = newValue;
localStorage.setItem(STORAGE_KEY, JSON.stringify(parsed));
} catch {
// ignore
}
};

if (!mounted) {
return <div className="opacity-0">{children}</div>;
}

return (
<Tabs value={value} onValueChange={handleChange} className="w-full">
{children}
</Tabs>
);
}

export { TabsList, TabsTrigger, TabsContent };
92 changes: 92 additions & 0 deletions website/src/components/ConsensusVisualizer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import React, { useState } from 'react';
import { Shield, AlertTriangle, Lock, Eye, Users } from 'lucide-react';

const steps = [
{
icon: Users,
title: 'Proposal',
description: 'Masternodes propose the next block based on round-robin scheduling.',
},
{
icon: Eye,
title: 'Verification',
description: 'Validators verify block validity, signatures, and transaction correctness.',
},
{
icon: Lock,
title: 'Commit',
description: 'Two-thirds majority commits the block through BFT voting.',
},
{
icon: Shield,
title: 'Finality',
description: 'Block becomes irreversible with instant finality.',
},
];

export function ConsensusVisualizer() {
const [activeStep, setActiveStep] = useState(0);

return (
<div className="my-6 overflow-hidden rounded-xl border border-border bg-card shadow-[0_0_0_1px_rgba(0,0,0,0.08)] dark:shadow-[0_0_0_1px_rgba(255,255,255,0.08)]">
<div className="border-b border-border px-5 py-3">
<div className="flex items-center gap-2">
<Shield className="h-4 w-4 text-primary" />
<h3 className="text-sm font-semibold">XDPoS Consensus Flow</h3>
</div>
</div>
<div className="p-5">
<div className="relative mb-6 flex items-center justify-between">
{steps.map((_, index) => (
<React.Fragment key={index}>
<button
onClick={() => setActiveStep(index)}
className={`relative z-10 flex h-10 w-10 items-center justify-center rounded-full border-2 transition-all ${
index <= activeStep
? 'border-primary bg-primary text-primary-foreground'
: 'border-border bg-background text-muted-foreground'
}`}
>
{index + 1}
</button>
{index < steps.length - 1 && (
<div
className={`absolute top-1/2 h-0.5 -translate-y-1/2 transition-all ${
index < activeStep ? 'bg-primary' : 'bg-border'
}`}
style={{
left: `${(index / (steps.length - 1)) * 100 + 8}%`,
right: `${100 - ((index + 1) / (steps.length - 1)) * 100 - 8}%`,
}}
/>
)}
</React.Fragment>
))}
</div>

<div className="rounded-lg bg-muted p-5">
<div className="mb-3 flex h-12 w-12 items-center justify-center rounded-xl bg-primary/10 text-primary">
{React.createElement(steps[activeStep].icon, { className: 'h-6 w-6' })}
</div>
<h4 className="text-lg font-semibold">{steps[activeStep].title}</h4>
<p className="mt-2 text-sm text-muted-foreground">{steps[activeStep].description}</p>
</div>

<div className="mt-4 flex gap-2">
{steps.map((_, index) => (
<button
key={index}
onClick={() => setActiveStep(index)}
className={`h-1.5 flex-1 rounded-full transition-all ${
index === activeStep ? 'bg-primary' : 'bg-border'
}`}
aria-label={`Step ${index + 1}`}
/>
))}
</div>
</div>
</div>
);
}

export default ConsensusVisualizer;
59 changes: 59 additions & 0 deletions website/src/components/DocsHero.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React from 'react';
import { cn } from '@site/src/lib/utils';

interface DocsHeroProps {
title: string;
description?: string;
badge?: string;
children?: React.ReactNode;
className?: string;
visual?: 'globe' | 'none';
image?: string;
imageAlt?: string;
}

export function DocsHero({ title, description, badge, children, className, visual = 'none', image, imageAlt }: DocsHeroProps) {
return (
<div
className={cn(
'not-prose relative mb-8 overflow-hidden rounded-2xl border border-border bg-gradient-to-br from-primary/[0.03] via-background to-background p-6 shadow-[0_0_0_1px_rgba(0,0,0,0.08)] dark:from-primary/[0.05] dark:shadow-[0_0_0_1px_rgba(255,255,255,0.08)] sm:mb-10 sm:p-8',
className
)}
>
{visual === 'globe' && !image && (
<div className="pointer-events-none absolute right-0 top-0 h-full w-1/2 opacity-30">
<div className="h-full w-full bg-[radial-gradient(circle_at_center,rgba(30,144,255,0.15)_0%,transparent_70%)]" />
</div>
)}
<div className="relative z-10 flex flex-col gap-6 lg:flex-row lg:items-center">
<div className="flex-1">
{badge && (
<span className="mb-3 inline-flex items-center rounded-full border border-primary/20 bg-primary/5 px-2.5 py-0.5 text-xs font-medium text-primary">
{badge}
</span>
)}
<h1 className="text-2xl font-semibold tracking-tight text-foreground sm:text-3xl lg:text-4xl">{title}</h1>
{description && (
<p className="mt-3 max-w-2xl text-base leading-relaxed text-muted-foreground sm:text-lg">{description}</p>
)}
{children}
</div>
{image && (
<div className="relative flex-shrink-0 lg:w-[320px] xl:w-[380px]">
<div className="relative overflow-hidden rounded-xl border border-border/60 bg-muted/30 p-3 shadow-[0_0_0_1px_rgba(0,0,0,0.06)] dark:bg-muted/20 dark:shadow-[0_0_0_1px_rgba(255,255,255,0.06)]">
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 via-transparent to-primary/5" />
<img
src={image}
alt={imageAlt || title}
className="relative z-10 w-full rounded-lg object-contain"
loading="eager"
/>
</div>
</div>
)}
</div>
</div>
);
}

export default DocsHero;
35 changes: 35 additions & 0 deletions website/src/components/FloatingAIAssistant.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import { Bot, X } from 'lucide-react';
import { useLocation } from '@docusaurus/router';
import { AIChatbot } from '@site/src/components/AIChatbot';

export default function FloatingAIAssistant() {
const [open, setOpen] = useState(false);
const location = useLocation();

useEffect(() => {
if (open) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
return () => {
document.body.style.overflow = '';
};
}, [open]);

return (
<>
<AIChatbot open={open} onClose={() => setOpen(false)} currentPath={location.pathname} />
<button
type="button"
onClick={() => setOpen((o) => !o)}
className="fixed bottom-6 right-6 z-[999] flex h-14 w-14 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg shadow-primary/25 transition-all duration-200 hover:scale-105 hover:shadow-xl hover:shadow-primary/30 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
aria-label={open ? 'Close AI assistant' : 'Open AI assistant'}
title={open ? 'Close AI assistant' : 'Open AI assistant'}
>
{open ? <X className="h-6 w-6" /> : <Bot className="h-6 w-6" />}
</button>
</>
);
}
Loading