From c807ddaa5cd6714f9497cf77bb36647550b09879 Mon Sep 17 00:00:00 2001 From: OlufunbiIK Date: Thu, 26 Feb 2026 02:15:46 +0100 Subject: [PATCH 1/3] feat:#167 Issue: Add WebSocket Gateway for Real-Time Events --- frontend/src/components/UseCasesSection.tsx | 862 +++++++++++++++++--- 1 file changed, 771 insertions(+), 91 deletions(-) diff --git a/frontend/src/components/UseCasesSection.tsx b/frontend/src/components/UseCasesSection.tsx index 47d99b0..9fe6018 100644 --- a/frontend/src/components/UseCasesSection.tsx +++ b/frontend/src/components/UseCasesSection.tsx @@ -1,143 +1,823 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect, useRef } from "react"; import { Rocket, Play, FileCode, Terminal, Shield } from "lucide-react"; +// ─── VS Code mock content per tab ─────────────────────────────────────────── + +const vscodeMocks: Record< + string, + { + filename: string; + language: string; + lines: { indent: number; tokens: { text: string; color: string }[] }[]; + terminal?: string[]; + sidePanel?: { + label: string; + value: string; + status?: "ok" | "warn" | "err"; + }[]; + } +> = { + Deploy: { + filename: "deploy.ts", + language: "typescript", + lines: [ + { + indent: 0, + tokens: [ + { text: "import", color: "#c792ea" }, + { text: " { StellarSuite } ", color: "#cdd3de" }, + { text: "from", color: "#c792ea" }, + { text: " 'stellar-suite'", color: "#c3e88d" }, + ], + }, + { indent: 0, tokens: [] }, + { + indent: 0, + tokens: [ + { text: "const", color: "#c792ea" }, + { text: " suite ", color: "#cdd3de" }, + { text: "=", color: "#89ddff" }, + { text: " new ", color: "#c792ea" }, + { text: "StellarSuite", color: "#82aaff" }, + { text: "()", color: "#cdd3de" }, + ], + }, + { indent: 0, tokens: [] }, + { + indent: 0, + tokens: [{ text: "// Deploy to testnet", color: "#546e7a" }], + }, + { + indent: 0, + tokens: [ + { text: "await", color: "#c792ea" }, + { text: " suite.", color: "#cdd3de" }, + { text: "deploy", color: "#82aaff" }, + { text: "({", color: "#cdd3de" }, + ], + }, + { + indent: 2, + tokens: [ + { text: "network", color: "#f07178" }, + { text: ": ", color: "#89ddff" }, + { text: "'testnet'", color: "#c3e88d" }, + { text: ",", color: "#cdd3de" }, + ], + }, + { + indent: 2, + tokens: [ + { text: "contract", color: "#f07178" }, + { text: ": ", color: "#89ddff" }, + { text: "'HelloWorld'", color: "#c3e88d" }, + { text: ",", color: "#cdd3de" }, + ], + }, + { + indent: 2, + tokens: [ + { text: "signer", color: "#f07178" }, + { text: ": ", color: "#89ddff" }, + { text: "identity", color: "#cdd3de" }, + ], + }, + { indent: 0, tokens: [{ text: "})", color: "#cdd3de" }] }, + ], + terminal: [ + "$ stellar deploy --network testnet", + "✓ Compiled HelloWorld.wasm (2.3kb)", + "✓ Uploaded to testnet", + "✓ Contract ID: CCHKN...XF72", + " Deployed in 1.42s", + ], + sidePanel: [ + { label: "Network", value: "Testnet", status: "ok" }, + { label: "Contract", value: "HelloWorld", status: "ok" }, + { label: "Status", value: "Deployed ✓", status: "ok" }, + { label: "Gas used", value: "14,820 stroops" }, + ], + }, + Simulate: { + filename: "simulate.ts", + language: "typescript", + lines: [ + { + indent: 0, + tokens: [{ text: "// Simulate before sending", color: "#546e7a" }], + }, + { + indent: 0, + tokens: [ + { text: "const", color: "#c792ea" }, + { text: " result ", color: "#cdd3de" }, + { text: "=", color: "#89ddff" }, + { text: " await ", color: "#c792ea" }, + { text: "suite.", color: "#cdd3de" }, + { text: "simulate", color: "#82aaff" }, + { text: "({", color: "#cdd3de" }, + ], + }, + { + indent: 2, + tokens: [ + { text: "fn", color: "#f07178" }, + { text: ": ", color: "#89ddff" }, + { text: "'transfer'", color: "#c3e88d" }, + { text: ",", color: "#cdd3de" }, + ], + }, + { + indent: 2, + tokens: [ + { text: "args", color: "#f07178" }, + { text: ": ", color: "#89ddff" }, + { text: "[from, to, amount]", color: "#cdd3de" }, + ], + }, + { indent: 0, tokens: [{ text: "})", color: "#cdd3de" }] }, + { indent: 0, tokens: [] }, + { + indent: 0, + tokens: [ + { text: "console", color: "#cdd3de" }, + { text: ".", color: "#89ddff" }, + { text: "log", color: "#82aaff" }, + { text: "(result.", color: "#cdd3de" }, + { text: "returnValue", color: "#f07178" }, + { text: ")", color: "#cdd3de" }, + ], + }, + ], + terminal: [ + "$ stellar simulate transfer", + " Simulating transaction...", + "✓ Return value: true", + "✓ Fee estimate: 203 stroops", + "✓ No state changes detected", + ], + sidePanel: [ + { label: "Function", value: "transfer()", status: "ok" }, + { label: "Fee est.", value: "203 stroops", status: "ok" }, + { label: "Return", value: "true", status: "ok" }, + { label: "Warnings", value: "None", status: "ok" }, + ], + }, + Build: { + filename: "HelloWorld.rs", + language: "rust", + lines: [ + { indent: 0, tokens: [{ text: "#![no_std]", color: "#546e7a" }] }, + { + indent: 0, + tokens: [ + { text: "use", color: "#c792ea" }, + { text: " soroban_sdk", color: "#cdd3de" }, + { text: "::{", color: "#89ddff" }, + { text: "contract, contractimpl, Env", color: "#82aaff" }, + { text: "};", color: "#cdd3de" }, + ], + }, + { indent: 0, tokens: [] }, + { indent: 0, tokens: [{ text: "#[contract]", color: "#c3e88d" }] }, + { + indent: 0, + tokens: [ + { text: "pub", color: "#c792ea" }, + { text: " struct ", color: "#cdd3de" }, + { text: "HelloWorld", color: "#82aaff" }, + { text: ";", color: "#cdd3de" }, + ], + }, + { indent: 0, tokens: [] }, + { indent: 0, tokens: [{ text: "#[contractimpl]", color: "#c3e88d" }] }, + { + indent: 0, + tokens: [ + { text: "impl", color: "#c792ea" }, + { text: " HelloWorld {", color: "#cdd3de" }, + ], + }, + { + indent: 2, + tokens: [ + { text: "pub", color: "#c792ea" }, + { text: " fn ", color: "#cdd3de" }, + { text: "hello", color: "#82aaff" }, + { text: "(_env: ", color: "#cdd3de" }, + { text: "Env", color: "#82aaff" }, + { text: ") -> ", color: "#cdd3de" }, + { text: "String", color: "#82aaff" }, + ], + }, + { + indent: 2, + tokens: [ + { text: " {", color: "#cdd3de" }, + { text: ' "Hello, Stellar!"', color: "#c3e88d" }, + { text: ".into() }", color: "#cdd3de" }, + ], + }, + { indent: 0, tokens: [{ text: "}", color: "#cdd3de" }] }, + ], + terminal: [ + "$ cargo build --target wasm32-unknown-unknown", + " Compiling hello_world v0.1.0", + "✓ Finished release [optimized]", + "✓ hello_world.wasm → target/wasm32/", + " Build time: 3.1s", + ], + sidePanel: [ + { label: "Target", value: "wasm32", status: "ok" }, + { label: "Output", value: "hello_world.wasm", status: "ok" }, + { label: "Size", value: "2.3 KB", status: "ok" }, + { label: "Errors", value: "0", status: "ok" }, + ], + }, + Test: { + filename: "hello_world_test.rs", + language: "rust", + lines: [ + { indent: 0, tokens: [{ text: "#[cfg(test)]", color: "#c3e88d" }] }, + { + indent: 0, + tokens: [ + { text: "mod", color: "#c792ea" }, + { text: " tests {", color: "#cdd3de" }, + ], + }, + { + indent: 2, + tokens: [ + { text: "use", color: "#c792ea" }, + { text: " super::*;", color: "#cdd3de" }, + ], + }, + { indent: 0, tokens: [] }, + { indent: 2, tokens: [{ text: "#[test]", color: "#c3e88d" }] }, + { + indent: 2, + tokens: [ + { text: "fn", color: "#c792ea" }, + { text: " test_hello", color: "#82aaff" }, + { text: "() {", color: "#cdd3de" }, + ], + }, + { + indent: 4, + tokens: [ + { text: "let", color: "#c792ea" }, + { text: " env ", color: "#cdd3de" }, + { text: "=", color: "#89ddff" }, + { text: " Env::", color: "#cdd3de" }, + { text: "default", color: "#82aaff" }, + { text: "();", color: "#cdd3de" }, + ], + }, + { + indent: 4, + tokens: [ + { text: "assert_eq!", color: "#82aaff" }, + { text: "(hello(env), ", color: "#cdd3de" }, + { text: '"Hello, Stellar!"', color: "#c3e88d" }, + { text: ");", color: "#cdd3de" }, + ], + }, + { indent: 2, tokens: [{ text: "}", color: "#cdd3de" }] }, + { indent: 0, tokens: [{ text: "}", color: "#cdd3de" }] }, + ], + terminal: [ + "$ cargo test", + " running 3 tests", + "✓ test_hello ... ok", + "✓ test_transfer ... ok", + "✓ test_balance ... ok", + " test result: ok. 3 passed; 0 failed", + ], + sidePanel: [ + { label: "Tests run", value: "3", status: "ok" }, + { label: "Passed", value: "3 ✓", status: "ok" }, + { label: "Failed", value: "0", status: "ok" }, + { label: "Coverage", value: "94%", status: "ok" }, + ], + }, + Manage: { + filename: "accounts.ts", + language: "typescript", + lines: [ + { + indent: 0, + tokens: [{ text: "// Create & fund a new account", color: "#546e7a" }], + }, + { + indent: 0, + tokens: [ + { text: "const", color: "#c792ea" }, + { text: " account ", color: "#cdd3de" }, + { text: "=", color: "#89ddff" }, + { text: " await ", color: "#c792ea" }, + { text: "suite.", color: "#cdd3de" }, + { text: "createAccount", color: "#82aaff" }, + { text: "({", color: "#cdd3de" }, + ], + }, + { + indent: 2, + tokens: [ + { text: "alias", color: "#f07178" }, + { text: ": ", color: "#89ddff" }, + { text: "'alice'", color: "#c3e88d" }, + { text: ",", color: "#cdd3de" }, + ], + }, + { + indent: 2, + tokens: [ + { text: "fund", color: "#f07178" }, + { text: ": ", color: "#89ddff" }, + { text: "true", color: "#c792ea" }, + ], + }, + { indent: 0, tokens: [{ text: "})", color: "#cdd3de" }] }, + { indent: 0, tokens: [] }, + { + indent: 0, + tokens: [{ text: "// Switch network config", color: "#546e7a" }], + }, + { + indent: 0, + tokens: [ + { text: "suite.", color: "#cdd3de" }, + { text: "setNetwork", color: "#82aaff" }, + { text: "('mainnet')", color: "#c3e88d" }, + ], + }, + ], + terminal: [ + "$ stellar account create alice --fund", + "✓ Keypair generated", + "✓ Funded via Friendbot", + " Balance: 10,000 XLM", + " Public: GDTKN...WQ9X", + ], + sidePanel: [ + { label: "Alias", value: "alice", status: "ok" }, + { label: "Balance", value: "10,000 XLM", status: "ok" }, + { label: "Network", value: "Testnet", status: "ok" }, + { label: "Keys", value: "Stored ✓", status: "ok" }, + ], + }, +}; + +// ─── Animated typing cursor ────────────────────────────────────────────────── +function TypingCursor() { + return ( + + ); +} + +// ─── VS Code Mock Window ───────────────────────────────────────────────────── +function VSCodeWindow({ tabLabel }: { tabLabel: string }) { + const mock = vscodeMocks[tabLabel]; + const [visibleLines, setVisibleLines] = useState(0); + const [visibleTermLines, setVisibleTermLines] = useState(0); + const animatingRef = useRef(false); + + useEffect(() => { + setVisibleLines(0); + setVisibleTermLines(0); + animatingRef.current = true; + + // Animate code lines + let line = 0; + const codeInterval = setInterval(() => { + line++; + setVisibleLines(line); + if (line >= mock.lines.length) { + clearInterval(codeInterval); + // Then animate terminal + if (mock.terminal) { + let tLine = 0; + const termInterval = setInterval(() => { + tLine++; + setVisibleTermLines(tLine); + if (tLine >= mock.terminal!.length) clearInterval(termInterval); + }, 160); + } + } + }, 80); + + return () => { + clearInterval(codeInterval); + animatingRef.current = false; + }; + }, [tabLabel]); + + const statusColor = (s?: "ok" | "warn" | "err") => + s === "ok" + ? "#4ec9b0" + : s === "warn" + ? "#dcdcaa" + : s === "err" + ? "#f44747" + : "#858585"; + + return ( +
+ {/* Title bar */} +
+ + + + + {mock.filename} — Stellar Suite + +
+ + {/* Tab bar */} +
+
+ {mock.filename} +
+
stellar.config.ts
+
+ + {/* Main layout: editor + side panel */} +
+ {/* Line numbers + code */} +
+
+ {/* Line numbers */} +
+ {mock.lines.map((_, i) => ( +
{i + 1}
+ ))} +
+ {/* Code */} +
+ {mock.lines.map((line, i) => ( +
+ {line.tokens.length === 0 ? ( +   + ) : ( + line.tokens.map((t, j) => ( + + {t.text} + + )) + )} + {i === visibleLines - 1 && i < mock.lines.length - 1 && ( + + )} +
+ ))} +
+
+
+ + {/* Side panel */} + {mock.sidePanel && ( +
+
+ Stellar Suite +
+ {mock.sidePanel.map((item) => ( +
+ + {item.label} + + + {item.value} + +
+ ))} +
+ )} +
+ + {/* Terminal panel */} + {mock.terminal && ( +
+
+ + Terminal + + + zsh + +
+
+ {mock.terminal.map((line, i) => ( +
+ {line} + {i === visibleTermLines - 1 && + i < mock.terminal!.length - 1 && } +
+ ))} + {visibleTermLines >= mock.terminal.length && ( +
+ $ +
+ )} +
+
+ )} + + {/* Status bar */} +
+ ⎇ main + Stellar Suite + TypeScript + UTF-8 + Ln {mock.lines.length}, Col 1 +
+
+ ); +} + +// ─── Tab data ──────────────────────────────────────────────────────────────── + const tabs = [ { label: "Deploy", icon: Rocket, - description: "One-click deployment lets you push Soroban smart contracts to testnet or mainnet without leaving VS Code.", + description: + "One-click deployment lets you push Soroban smart contracts to testnet or mainnet without leaving VS Code.", bullets: [ - { title: "One-click deploy:", text: "Select your target network and deploy instantly — no terminal commands needed." }, - { title: "Environment management:", text: "Switch between testnet, futurenet, and mainnet environments effortlessly." }, - { title: "Deploy history:", text: "Track every deployment with built-in logs and contract addresses." }, - { title: "Error handling:", text: "Get clear, actionable error messages right in your editor when deployments fail." }, + { + title: "One-click deploy:", + text: "Select your target network and deploy instantly — no terminal commands needed.", + }, + { + title: "Environment management:", + text: "Switch between testnet, futurenet, and mainnet environments effortlessly.", + }, + { + title: "Deploy history:", + text: "Track every deployment with built-in logs and contract addresses.", + }, + { + title: "Error handling:", + text: "Get clear, actionable error messages right in your editor when deployments fail.", + }, ], }, { label: "Simulate", icon: Play, - description: "Test transactions before committing them to the blockchain — simulate any contract invocation with real-time feedback.", + description: + "Test transactions before committing them to the blockchain — simulate any contract invocation with real-time feedback.", bullets: [ - { title: "Transaction preview:", text: "See exactly what a transaction will do before you send it." }, - { title: "Gas estimation:", text: "Get accurate resource and fee estimates for every transaction." }, - { title: "Debug outputs:", text: "View detailed logs and return values from simulated contract calls." }, - { title: "Iterate faster:", text: "Catch bugs in seconds instead of waiting for on-chain failures." }, + { + title: "Transaction preview:", + text: "See exactly what a transaction will do before you send it.", + }, + { + title: "Gas estimation:", + text: "Get accurate resource and fee estimates for every transaction.", + }, + { + title: "Debug outputs:", + text: "View detailed logs and return values from simulated contract calls.", + }, + { + title: "Iterate faster:", + text: "Catch bugs in seconds instead of waiting for on-chain failures.", + }, ], }, { label: "Build", icon: FileCode, - description: "Scaffold, compile, and manage Soroban projects with built-in tooling that understands your contract structure.", + description: + "Scaffold, compile, and manage Soroban projects with built-in tooling that understands your contract structure.", bullets: [ - { title: "Project scaffolding:", text: "Create new Soroban projects from templates with a single command." }, - { title: "Auto-compile:", text: "Contracts are built automatically on save with real-time error reporting." }, - { title: "WASM management:", text: "Compiled WASM files are organized and ready for deployment." }, - { title: "Multi-contract support:", text: "Manage multiple contracts in a single workspace seamlessly." }, + { + title: "Project scaffolding:", + text: "Create new Soroban projects from templates with a single command.", + }, + { + title: "Auto-compile:", + text: "Contracts are built automatically on save with real-time error reporting.", + }, + { + title: "WASM management:", + text: "Compiled WASM files are organized and ready for deployment.", + }, + { + title: "Multi-contract support:", + text: "Manage multiple contracts in a single workspace seamlessly.", + }, ], }, { label: "Test", icon: Shield, - description: "Run your contract tests with integrated test runners and get results right in the editor.", + description: + "Run your contract tests with integrated test runners and get results right in the editor.", bullets: [ - { title: "Inline test results:", text: "See pass/fail status next to each test function." }, - { title: "Coverage reports:", text: "Understand which parts of your contract are tested." }, - { title: "Watch mode:", text: "Tests re-run automatically as you edit your contracts." }, - { title: "Snapshot testing:", text: "Compare contract state before and after transactions." }, + { + title: "Inline test results:", + text: "See pass/fail status next to each test function.", + }, + { + title: "Coverage reports:", + text: "Understand which parts of your contract are tested.", + }, + { + title: "Watch mode:", + text: "Tests re-run automatically as you edit your contracts.", + }, + { + title: "Snapshot testing:", + text: "Compare contract state before and after transactions.", + }, ], }, { label: "Manage", icon: Terminal, - description: "Manage accounts, keys, identities, and network configurations — all from a visual interface.", + description: + "Manage accounts, keys, identities, and network configurations — all from a visual interface.", bullets: [ - { title: "Account management:", text: "Create, fund, and manage Stellar accounts without the CLI." }, - { title: "Key management:", text: "Securely store and use signing keys within VS Code." }, - { title: "Network config:", text: "Configure custom RPC endpoints and network settings visually." }, - { title: "Contract interactions:", text: "Invoke deployed contracts with a graphical form interface." }, + { + title: "Account management:", + text: "Create, fund, and manage Stellar accounts without the CLI.", + }, + { + title: "Key management:", + text: "Securely store and use signing keys within VS Code.", + }, + { + title: "Network config:", + text: "Configure custom RPC endpoints and network settings visually.", + }, + { + title: "Contract interactions:", + text: "Invoke deployed contracts with a graphical form interface.", + }, ], }, ]; +// ─── Main Section ───────────────────────────────────────────────────────────── + const UseCasesSection = () => { const [active, setActive] = useState(0); const current = tabs[active]; return ( -
-
-
-

- Endless ways to build on Stellar. -

-

- From deploying contracts to simulating transactions, Stellar Suite delivers the tools you need. - Every workflow is faster, easier, and more intuitive. -

-
- - {/* Tabs */} -
- {tabs.map((tab, i) => ( - - ))} -
+ <> + - {/* Content */} -
-
-

- {current.description} +

+
+
+

+ Endless ways to build on Stellar. +

+

+ From deploying contracts to simulating transactions, Stellar Suite + delivers the tools you need. Every workflow is faster, easier, and + more intuitive.

-
    - {current.bullets.map((b) => ( -
  • - - - {b.title} {b.text} - -
  • - ))} -
-
- {/* Visual card */} -
-
- + {/* Tabs */} +
+ {tabs.map((tab, i) => ( + + ))} +
+ + {/* Content */} +
+ {/* Left: description + bullets */} +
+

+ {current.description} +

+
    + {current.bullets.map((b) => ( +
  • + + + + {b.title} + {" "} + {b.text} + +
  • + ))} +
+ +
+ + {/* Right: VS Code mock */} +
+ +

+ Visual workflow in VS Code · Stellar Suite Extension +

-

{current.label}

-

Visual workflow in VS Code

-
-
+
+ ); }; From d2639f39a917e84cd27b0a02589901fbddeca619 Mon Sep 17 00:00:00 2001 From: OlufunbiIK Date: Thu, 26 Feb 2026 02:46:12 +0100 Subject: [PATCH 2/3] feat: blog structure and templates --- frontend/components/layout/Footer.tsx | 2 + frontend/components/layout/Navbar.tsx | 1 + frontend/src/app/blog/[slug]/page.tsx | 215 ++++++++++++++++++++ frontend/src/app/blog/page.tsx | 118 +++++++++++ frontend/src/app/globals.css | 15 +- frontend/src/components/ PostCard.tsx | 90 +++++++++ frontend/src/components/CategoryFilter.tsx | 73 +++++++ frontend/src/components/PostBody.tsx | 189 ++++++++++++++++++ frontend/src/lib/post.ts | 218 +++++++++++++++++++++ 9 files changed, 918 insertions(+), 3 deletions(-) create mode 100644 frontend/src/app/blog/[slug]/page.tsx create mode 100644 frontend/src/app/blog/page.tsx create mode 100644 frontend/src/components/ PostCard.tsx create mode 100644 frontend/src/components/CategoryFilter.tsx create mode 100644 frontend/src/components/PostBody.tsx create mode 100644 frontend/src/lib/post.ts diff --git a/frontend/components/layout/Footer.tsx b/frontend/components/layout/Footer.tsx index 3c4c4c1..91020fc 100644 --- a/frontend/components/layout/Footer.tsx +++ b/frontend/components/layout/Footer.tsx @@ -1,3 +1,4 @@ +// components/layout/Footer.tsx const FOOTER_LINKS = [ { title: "Product", @@ -10,6 +11,7 @@ const FOOTER_LINKS = [ { title: "Resources", links: [ + { label: "Blog", href: "/blog" }, { label: "Documentation", href: "https://github.com/0xVida/stellar-suite#readme", diff --git a/frontend/components/layout/Navbar.tsx b/frontend/components/layout/Navbar.tsx index ae21d51..709c515 100644 --- a/frontend/components/layout/Navbar.tsx +++ b/frontend/components/layout/Navbar.tsx @@ -5,6 +5,7 @@ const NAV_LINKS = [ { label: "Features", href: "/#features" }, { label: "Templates", href: "/#templates" }, { label: "Compare", href: "/#compare" }, + { label: "Blog", href: "/blog" }, { label: "Docs", href: "https://github.com/0xVida/stellar-suite#readme" }, ]; diff --git a/frontend/src/app/blog/[slug]/page.tsx b/frontend/src/app/blog/[slug]/page.tsx new file mode 100644 index 0000000..40155b5 --- /dev/null +++ b/frontend/src/app/blog/[slug]/page.tsx @@ -0,0 +1,215 @@ +// src/app/blog/[slug]/page.tsx +import { notFound } from "next/navigation"; +import Link from "next/link"; +import type { Metadata } from "next"; +import { Navbar } from "../../../../components/layout/Navbar"; +import { Footer } from "../../../../components/layout/Footer"; +import { formatDate, getAllPosts, getPostBySlug } from "@/lib/post"; +import { PostBody } from "@/components/PostBody"; +import { PostCard } from "@/components/ PostCard"; + +// Static generation: pre-render all slugs at build time +export async function generateStaticParams() { + return getAllPosts().map((p) => ({ slug: p.slug })); +} + +// Per-post metadata +export async function generateMetadata({ + params, +}: { + params: { slug: string }; +}): Promise { + const post = getPostBySlug(params.slug); + if (!post) return {}; + return { + title: `${post.title} — Stellar Suite Blog`, + description: post.excerpt, + openGraph: { + title: post.title, + description: post.excerpt, + type: "article", + publishedTime: post.date, + }, + }; +} + +export default function PostPage({ params }: { params: { slug: string } }) { + const post = getPostBySlug(params.slug); + if (!post) notFound(); + + const allPosts = getAllPosts(); + const currentIndex = allPosts.findIndex((p) => p.slug === post.slug); + const related = allPosts + .filter( + (p) => + p.slug !== post.slug && + (p.category === post.category || + p.tags.some((t) => post.tags.includes(t))), + ) + .slice(0, 2); + + return ( +
+ + +
+
+ {/* Breadcrumb */} + + + {/* Category badge */} +
+ + {post.category} + +
+ + {/* Title */} +

+ {post.title} +

+ + {/* Meta row */} +
+
+
+ + {post.author.name[0]} + +
+ + {post.author.name} + +
+ + {formatDate(post.date)} + + + {post.readingTime} + +
+ + {/* Tags */} +
+ {post.tags.map((tag) => ( + + #{tag} + + ))} +
+ + {/* Excerpt (lead) */} +

+ {post.excerpt} +

+ + {/* Post content */} + + + {/* Post nav: prev/next */} +
+ {currentIndex < allPosts.length - 1 ? ( + + + + + + {allPosts[currentIndex + 1].title} + + + ) : ( +
+ )} + {currentIndex > 0 ? ( + + + {allPosts[currentIndex - 1].title} + + + + + + ) : ( +
+ )} +
+ + {/* Back to blog */} +
+ + ← Back to Blog + +
+
+ + {/* Related posts */} + {related.length > 0 && ( +
+

+ Related Posts +

+
+ {related.map((p) => ( + + ))} +
+
+ )} +
+ +
+
+ ); +} diff --git a/frontend/src/app/blog/page.tsx b/frontend/src/app/blog/page.tsx new file mode 100644 index 0000000..22a5c61 --- /dev/null +++ b/frontend/src/app/blog/page.tsx @@ -0,0 +1,118 @@ +// src/app/blog/page.tsx +"use client"; + +import { useState, useMemo } from "react"; +import Link from "next/link"; +import { Navbar } from "../../../components/layout/Navbar"; +import { Footer } from "../../../components/layout/Footer"; +import { getAllCategories, getAllPosts, getAllTags } from "@/lib/post"; +import { CategoryFilter } from "@/components/CategoryFilter"; +import { PostCard } from "@/components/ PostCard"; + +const allPosts = getAllPosts(); +const allCategories = getAllCategories(); +const allTags = getAllTags(); + +export default function BlogIndex() { + const [activeCategory, setActiveCategory] = useState(""); + const [activeTag, setActiveTag] = useState(""); + + const filtered = useMemo(() => { + return allPosts.filter((p) => { + const matchCat = activeCategory === "" || p.category === activeCategory; + const matchTag = activeTag === "" || p.tags.includes(activeTag); + return matchCat && matchTag; + }); + }, [activeCategory, activeTag]); + + const featured = filtered[0]; + const rest = filtered.slice(1); + + return ( +
+ + + {/* Hero */} +
+
+
+ + Home + + / + Blog +
+

+ Blog +

+

+ Release notes, tutorials, and updates from the Stellar Suite team. +

+ + {/* Filters */} +
+ { + setActiveCategory(c); + setActiveTag(""); + }} + onTagChange={(t) => { + setActiveTag(t); + setActiveCategory(""); + }} + /> +
+
+
+ + {/* Posts */} +
+
+ {filtered.length === 0 ? ( +
+

+ No posts match your filter. +

+ +
+ ) : ( +
+ {/* Featured (first) post — full width */} + {featured && ( +
+ +
+ )} + + {/* Remaining posts — 2-col grid */} + {rest.length > 0 && ( +
+ {rest.map((post) => ( + + ))} +
+ )} +
+ )} +
+
+ +
+
+ ); +} diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 5ebfb04..908bc10 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -1,4 +1,4 @@ -@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap'); +@import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap"); @tailwind base; @tailwind components; @@ -120,7 +120,12 @@ @apply bg-background text-foreground font-body antialiased; } - h1, h2, h3, h4, h5, h6 { + h1, + h2, + h3, + h4, + h5, + h6 { @apply font-display; } @@ -134,7 +139,11 @@ @layer utilities { .text-gradient-blue { @apply bg-clip-text text-transparent; - background-image: linear-gradient(135deg, hsl(228 76% 52%), hsl(262 68% 56%)); + background-image: linear-gradient( + 135deg, + hsl(228 76% 52%), + hsl(262 68% 56%) + ); } } diff --git a/frontend/src/components/ PostCard.tsx b/frontend/src/components/ PostCard.tsx new file mode 100644 index 0000000..e035a2b --- /dev/null +++ b/frontend/src/components/ PostCard.tsx @@ -0,0 +1,90 @@ +import { formatDate, Post } from "@/lib/post"; +import Link from "next/link"; + +type Props = { + post: Post; + featured?: boolean; +}; + +export function PostCard({ post, featured = false }: Props) { + return ( + + {/* Color bar accent */} +
+ +
+ {/* Category + reading time */} +
+ + {post.category} + + + {post.readingTime} + +
+ + {/* Title */} +

+ {post.title} +

+ + {/* Excerpt */} +

+ {post.excerpt} +

+ + {/* Footer: date + tags + arrow */} +
+
+ + {formatDate(post.date)} + +
+ {post.tags.slice(0, 3).map((tag) => ( + + #{tag} + + ))} +
+
+ + + + + +
+
+ + ); +} diff --git a/frontend/src/components/CategoryFilter.tsx b/frontend/src/components/CategoryFilter.tsx new file mode 100644 index 0000000..b351984 --- /dev/null +++ b/frontend/src/components/CategoryFilter.tsx @@ -0,0 +1,73 @@ +"use client"; + +type Props = { + categories: string[]; + tags: string[]; + activeCategory: string; + activeTag: string; + onCategoryChange: (c: string) => void; + onTagChange: (t: string) => void; +}; + +export function CategoryFilter({ + categories, + tags, + activeCategory, + activeTag, + onCategoryChange, + onTagChange, +}: Props) { + return ( +
+ {/* Categories */} +
+ + Category + + + {categories.map((cat) => ( + + ))} +
+ + {/* Tags */} +
+ + Tag + + {tags.map((tag) => ( + + ))} +
+
+ ); +} diff --git a/frontend/src/components/PostBody.tsx b/frontend/src/components/PostBody.tsx new file mode 100644 index 0000000..1531922 --- /dev/null +++ b/frontend/src/components/PostBody.tsx @@ -0,0 +1,189 @@ +// src/components/blog/PostBody.tsx +// Renders post.content (markdown string) with consistent typography. +// Uses a lightweight regex-based renderer so no extra dependencies are needed. +// If you later add @next/mdx or remark, replace this component with your MDX renderer. + +type Props = { + content: string; +}; + +/** + * Lightweight markdown → HTML renderer. + * Supports: h2/h3, bold, inline code, fenced code blocks, paragraphs, unordered lists. + */ +function renderMarkdown(md: string): string { + const lines = md.split("\n"); + const html: string[] = []; + let inCode = false; + let codeLang = ""; + let codeLines: string[] = []; + let inList = false; + + const flushList = () => { + if (inList) { + html.push(""); + inList = false; + } + }; + + const inline = (text: string) => + text + // Bold + .replace(/\*\*(.+?)\*\*/g, "$1") + // Inline code + .replace(/`([^`]+)`/g, '$1'); + + for (const line of lines) { + // Fenced code block start + if (!inCode && line.startsWith("```")) { + flushList(); + inCode = true; + codeLang = line.slice(3).trim(); + codeLines = []; + continue; + } + // Fenced code block end + if (inCode && line.startsWith("```")) { + html.push( + `
${codeLines
+          .map((l) =>
+            l
+              .replace(/&/g, "&")
+              .replace(//g, ">"),
+          )
+          .join("\n")}
`, + ); + inCode = false; + codeLines = []; + continue; + } + if (inCode) { + codeLines.push(line); + continue; + } + + // Headings + if (line.startsWith("## ")) { + flushList(); + html.push(`

${inline(line.slice(3))}

`); + continue; + } + if (line.startsWith("### ")) { + flushList(); + html.push(`

${inline(line.slice(4))}

`); + continue; + } + + // List items + if (line.startsWith("- ")) { + if (!inList) { + html.push('
    '); + inList = true; + } + html.push(`
  • ${inline(line.slice(2))}
  • `); + continue; + } + + // Blank line + if (line.trim() === "") { + flushList(); + continue; + } + + // Paragraph + flushList(); + html.push(`

    ${inline(line)}

    `); + } + + flushList(); + return html.join("\n"); +} + +export function PostBody({ content }: Props) { + return ( + <> + +
    + + ); +} diff --git a/frontend/src/lib/post.ts b/frontend/src/lib/post.ts new file mode 100644 index 0000000..775a2fe --- /dev/null +++ b/frontend/src/lib/post.ts @@ -0,0 +1,218 @@ +// ───────────────────────────────────────────────────────────────────────────── +// File-based blog post store. +// To add a new post: add an entry to ALL_POSTS below. +// Fields: slug (URL key), title, excerpt, date (ISO), category, tags, readingTime, content (MDX/markdown string) +// ───────────────────────────────────────────────────────────────────────────── + +export type Post = { + slug: string; + title: string; + excerpt: string; + date: string; // ISO 8601 e.g. "2025-01-15" + category: string; + tags: string[]; + readingTime: string; + author: { name: string; avatar?: string }; + content: string; // Markdown/MDX string +}; + +export const ALL_POSTS: Post[] = [ + { + slug: "introducing-stellar-suite", + title: "Introducing Stellar Suite: Soroban Development, Right in VS Code", + excerpt: + "We built Stellar Suite to eliminate the friction between writing Soroban smart contracts and shipping them — no more terminal juggling, no more context switching.", + date: "2025-01-15", + category: "Announcements", + tags: ["release", "soroban", "vscode"], + readingTime: "4 min read", + author: { name: "Stellar Suite Team" }, + content: ` +## The Problem We Kept Running Into + +Every Soroban developer knows the rhythm: write a function, switch to your terminal, run \`stellar contract deploy\`, parse the output, switch back to your editor. Repeat fifty times a day. + +It's not that the Stellar CLI is bad — it's excellent. But the constant context switch between editor and terminal fragments your focus and slows you down. + +We built Stellar Suite to fix that. + +## What Stellar Suite Does + +Stellar Suite is a VS Code extension that brings the full Soroban development workflow into your editor: + +- **Deploy** contracts to testnet or mainnet with one click, without touching a terminal +- **Simulate** transactions before committing them — see return values, fee estimates, and state changes inline +- **Build** and compile your contracts automatically on save +- **Test** with an integrated test runner that shows pass/fail status next to each function +- **Manage** accounts, keys, and network configs from a clean visual panel + +## How It Works + +The extension connects directly to the Stellar RPC and wraps the Soroban SDK. Everything runs locally — no cloud dependency, no telemetry, no keys leaving your machine. + +\`\`\`typescript +// Before: terminal context switch every time +// $ stellar contract deploy --network testnet --source alice + +// After: one command from the Command Palette +// > Stellar Suite: Deploy Contract +\`\`\` + +## Getting Started + +Install from the VS Code Marketplace, open any Soroban project, and the extension activates automatically. The sidebar panel gives you instant access to every workflow. + +We're shipping fast. Follow the repo for weekly updates. + `.trim(), + }, + { + slug: "simulating-transactions-before-you-commit", + title: "Why You Should Always Simulate Before You Send", + excerpt: + "On-chain failures are expensive and embarrassing. Here's how Stellar Suite's simulation mode lets you catch bugs before they cost you gas — or worse, corrupt state.", + date: "2025-02-03", + category: "Tutorials", + tags: ["simulation", "debugging", "soroban"], + readingTime: "6 min read", + author: { name: "Stellar Suite Team" }, + content: ` +## The Cost of On-Chain Failures + +When a Soroban contract call fails on-chain, you've already paid the transaction fee. Worse, if your contract has partial state mutations before hitting an error, you're debugging a mess. + +The simulation API exists to prevent exactly this. Stellar Suite surfaces it directly in your editor so you never have to remember to call it manually. + +## How Simulation Works + +Under the hood, the Stellar RPC exposes a \`simulateTransaction\` endpoint. It runs your transaction against the current ledger state without committing it, returning: + +- The **return value** your function would produce +- Accurate **resource and fee estimates** +- Any **diagnostic events** or error messages +- A diff of **ledger entries** that would change + +Stellar Suite wraps this into a single "Simulate" action in the command palette and sidebar panel. + +## A Real Example + +Say you have a token transfer function: + +\`\`\`rust +pub fn transfer(env: Env, from: Address, to: Address, amount: i128) { + from.require_auth(); + let balance = get_balance(&env, &from); + if balance < amount { + panic!("insufficient balance"); + } + // ... update balances +} +\`\`\` + +Before Stellar Suite, you'd deploy, invoke, wait for the transaction, and check the result. With simulation: + +1. Open the Stellar Suite sidebar +2. Select your function (\`transfer\`) +3. Fill in the args +4. Click **Simulate** + +In under a second you'll see the return value, the fee (e.g. 203 stroops), and whether any auth is missing — all before touching the network. + +## Iteration Speed + +In practice, this turns a 30-second round-trip into a 1-second feedback loop. For complex contracts with multiple failure modes, that compounds quickly. + +Simulate everything. It costs nothing and saves you from the worst bugs. + `.trim(), + }, + { + slug: "managing-accounts-and-keys-in-vscode", + title: "Account & Key Management Without the CLI", + excerpt: + "Managing Stellar accounts across testnet, futurenet, and mainnet used to mean memorizing CLI flags. Stellar Suite's Manage panel changes that.", + date: "2025-02-20", + category: "Tutorials", + tags: ["accounts", "keys", "manage", "workflow"], + readingTime: "5 min read", + author: { name: "Stellar Suite Team" }, + content: ` +## The Old Way + +\`\`\`bash +stellar keys generate alice +stellar keys address alice +stellar network add testnet --rpc-url https://soroban-testnet.stellar.org ... +stellar contract invoke --network testnet --source alice ... +\`\`\` + +Nothing wrong with any of that — but doing it dozens of times a day across multiple projects adds friction. + +## The Stellar Suite Manage Panel + +The Manage tab in the Stellar Suite sidebar gives you a visual interface for everything account-related: + +### Creating and Funding Accounts + +Click **New Account**, give it an alias (e.g. \`alice\`), and optionally auto-fund it via Friendbot on testnet. The keypair is generated locally and stored in VS Code's secret storage — encrypted, never in plaintext on disk. + +### Switching Networks + +A dropdown at the top of the panel lets you switch between testnet, futurenet, and mainnet in one click. The active network propagates to all other Stellar Suite actions automatically. + +### Custom RPC Endpoints + +If you're running a local Quickstart node or a private RPC, add it under **Network Config**: + +\`\`\` +Name: local-quickstart +RPC URL: http://localhost:8000/soroban/rpc +Network Passphrase: Standalone Network ; February 2017 +\`\`\` + +### Invoking Deployed Contracts + +Once you have a contract deployed and an account set up, the **Invoke** panel lets you call any exported function through a generated form — no ABI file needed, the extension reads your compiled WASM. + +## Security Notes + +Keys are stored using VS Code's \`SecretStorage\` API, which maps to the OS keychain (Keychain on macOS, libsecret on Linux, Credential Manager on Windows). They are never written to disk in plaintext and never leave your machine. + +For mainnet keys, we recommend hardware wallet support — which is on our roadmap. + `.trim(), + }, +]; + +// ─── Helpers ───────────────────────────────────────────────────────────────── + +export function getAllPosts(): Post[] { + return [...ALL_POSTS].sort( + (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(), + ); +} + +export function getPostBySlug(slug: string): Post | undefined { + return ALL_POSTS.find((p) => p.slug === slug); +} + +export function getAllCategories(): string[] { + return Array.from(new Set(ALL_POSTS.map((p) => p.category))); +} + +export function getAllTags(): string[] { + return Array.from(new Set(ALL_POSTS.flatMap((p) => p.tags))); +} + +export function getPostsByCategory(category: string): Post[] { + return getAllPosts().filter((p) => p.category === category); +} + +export function getPostsByTag(tag: string): Post[] { + return getAllPosts().filter((p) => p.tags.includes(tag)); +} + +export function formatDate(iso: string): string { + return new Date(iso).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + }); +} From fdca35ca31e2f02c9ac748e0c5dff1d1bc340eb2 Mon Sep 17 00:00:00 2001 From: OlufunbiIK Date: Thu, 26 Feb 2026 02:57:32 +0100 Subject: [PATCH 3/3] feat: contact us popup form --- frontend/components/ContactDialog.tsx | 365 ++++++++++++++++++++++++++ frontend/components/layout/Footer.tsx | 162 ++++++++---- frontend/components/layout/Navbar.tsx | 12 + frontend/next-env.d.ts | 2 +- 4 files changed, 484 insertions(+), 57 deletions(-) create mode 100644 frontend/components/ContactDialog.tsx diff --git a/frontend/components/ContactDialog.tsx b/frontend/components/ContactDialog.tsx new file mode 100644 index 0000000..19a431e --- /dev/null +++ b/frontend/components/ContactDialog.tsx @@ -0,0 +1,365 @@ +// src/components/ContactDialog.tsx +// ───────────────────────────────────────────────────────────────────────────── +// Wraps the existing in a Radix Dialog (shadcn/ui). +// Usage: +// ← renders its own "Contact us" trigger button +// } /> ← use a custom trigger element +// +// To wire up a real backend later, update the handleSubmit mock in ContactForm.tsx +// (look for "Mock API delay") and replace with your fetch/API call. +// ───────────────────────────────────────────────────────────────────────────── + +"use client"; + +import { useState } from "react"; +import { + MessageSquare, + Loader2, + X, + CheckCircle2, + AlertCircle, +} from "lucide-react"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, +} from "@/components/ui/dialog"; + +// ─── Inline form (self-contained so the modal owns its own state) ───────────── + +type FormData = { + name: string; + email: string; + subject: string; + message: string; +}; +type Status = "idle" | "loading" | "success" | "error"; + +function ContactModalForm({ onSuccess }: { onSuccess?: () => void }) { + const [formData, setFormData] = useState({ + name: "", + email: "", + subject: "", + message: "", + }); + const [errors, setErrors] = useState>({}); + const [status, setStatus] = useState("idle"); + const [feedback, setFeedback] = useState(""); + const [honeypot, setHoneypot] = useState(""); + + const validate = (): boolean => { + const e: Partial = {}; + if (!formData.name.trim()) e.name = "Name is required."; + if (!formData.email.trim()) { + e.email = "Email is required."; + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { + e.email = "Enter a valid email address."; + } + if (!formData.message.trim()) e.message = "Message is required."; + if (formData.message.length > 500) + e.message = "Message cannot exceed 500 characters."; + setErrors(e); + return Object.keys(e).length === 0; + }; + + const handleChange = ( + e: React.ChangeEvent, + ) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + // Clear field error on change + if (errors[name as keyof FormData]) { + setErrors((prev) => ({ ...prev, [name]: undefined })); + } + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (honeypot) { + setStatus("success"); + setFeedback("Message sent!"); + return; + } + if (!validate()) return; + + setStatus("loading"); + setFeedback(""); + + try { + // ── TODO: replace with real API call ────────────────────────────────── + // await fetch("/api/contact", { + // method: "POST", + // headers: { "Content-Type": "application/json" }, + // body: JSON.stringify(formData), + // }); + await new Promise((res) => setTimeout(res, 1500)); + // ───────────────────────────────────────────────────────────────────── + + setStatus("success"); + setFeedback("Thank you! We'll get back to you soon."); + setFormData({ name: "", email: "", subject: "", message: "" }); + onSuccess?.(); + } catch { + setStatus("error"); + setFeedback("Something went wrong. Please try again later."); + } + }; + + const field = + "w-full rounded-lg border bg-background px-4 py-2.5 text-sm text-foreground placeholder:text-muted-foreground transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-1 focus:ring-offset-background disabled:opacity-50"; + const fieldError = "border-destructive focus:ring-destructive"; + const fieldNormal = "border-border"; + + if (status === "success") { + return ( +
    +
    + +
    +
    +

    + Message sent! +

    +

    + {feedback} +

    +
    +
    + ); + } + + return ( +
    + {/* Honeypot */} + + + {/* Name + Email */} +
    +
    + + + {errors.name && ( + + )} +
    + +
    + + + {errors.email && ( + + )} +
    +
    + + {/* Subject (optional) */} +
    + + +
    + + {/* Message */} +
    + +