From a5fab78bf175775ae9f6a7133e108ca204f1d400 Mon Sep 17 00:00:00 2001 From: sanusishafii989 Date: Tue, 25 Aug 2026 12:39:27 +0100 Subject: [PATCH 1/3] feat(grants): add grant applicant showcase page and featured strip Add /grants/showcase route with filterable card grid of grant-funded projects. Filters for wave and status persist via URL query params (useSearchParams) so browser back/forward navigation works seamlessly. - Create src/data/grant-showcase.json with 5 project entries - Add filterOptedOutContributors utility (case-insensitive opt-out) - Create GrantShowcase page with SEO meta tags and semantic HTML - Create FeaturedGrantShowcase strip component (opt-out aware) - Embed featured strip on Home page and /grants page - Add i18n translations (en + es) for all new strings - Add /grants/showcase to sitemap knownRoutes Closes #132 --- public/feed.xml | 2 +- public/sitemap.xml | 44 ++- scripts/sitemap.ts | 3 + src/App.tsx | 11 + src/components/FeaturedGrantShowcase.tsx | 121 +++++++ src/data/grant-showcase.json | 59 ++++ src/i18n/en.json | 26 ++ src/i18n/es.json | 26 ++ src/pages/GrantShowcase.tsx | 296 ++++++++++++++++ src/pages/Grants.tsx | 421 ++++++++++++----------- src/utils/contributors.ts | 18 + 11 files changed, 810 insertions(+), 217 deletions(-) create mode 100644 src/components/FeaturedGrantShowcase.tsx create mode 100644 src/data/grant-showcase.json create mode 100644 src/pages/GrantShowcase.tsx create mode 100644 src/utils/contributors.ts diff --git a/public/feed.xml b/public/feed.xml index c575d12..1872f23 100644 --- a/public/feed.xml +++ b/public/feed.xml @@ -5,7 +5,7 @@ https://usewraith.xyz/blog Notes on stealth payments, private infrastructure, and the Wraith ecosystem. en-us - Sat, 08 Aug 2026 18:36:39 GMT + Tue, 25 Aug 2026 10:54:35 GMT diff --git a/public/sitemap.xml b/public/sitemap.xml index 839e573..04c0860 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -2,79 +2,97 @@ https://usewraith.xyz - 2026-08-08 + 2026-08-25 daily 1.0 https://usewraith.xyz/faq - 2026-08-08 + 2026-08-25 weekly 0.8 https://usewraith.xyz/privacy - 2026-08-08 + 2026-08-25 weekly 0.8 https://usewraith.xyz/use-cases - 2026-08-08 + 2026-08-25 weekly 0.8 https://usewraith.xyz/roadmap - 2026-08-08 + 2026-08-25 weekly 0.8 https://usewraith.xyz/case-studies - 2026-08-08 + 2026-08-25 weekly 0.8 https://usewraith.xyz/stellar - 2026-08-08 + 2026-08-25 weekly 0.8 https://usewraith.xyz/careers - 2026-08-08 + 2026-08-25 weekly 0.8 https://usewraith.xyz/press - 2026-08-08 + 2026-08-25 + weekly + 0.8 + + + https://usewraith.xyz/grants + 2026-08-25 + weekly + 0.8 + + + https://usewraith.xyz/grants/showcase + 2026-08-25 + weekly + 0.8 + + + https://usewraith.xyz/contributors + 2026-08-25 weekly 0.8 https://usewraith.xyz/case-studies/payroll-processor - 2026-08-08 + 2026-08-25 weekly 0.7 https://usewraith.xyz/blog - 2026-08-08 + 2026-08-25 weekly 0.8 https://usewraith.xyz/compare - 2026-08-08 + 2026-08-25 weekly 0.8 https://usewraith.xyz/newsletter - 2026-08-08 + 2026-08-25 weekly 0.8 diff --git a/scripts/sitemap.ts b/scripts/sitemap.ts index de0b2e3..bea430b 100644 --- a/scripts/sitemap.ts +++ b/scripts/sitemap.ts @@ -18,6 +18,9 @@ const knownRoutes = [ '/stellar', '/careers', '/press', + '/grants', + '/grants/showcase', + '/contributors', ]; function getCaseStudyRoutes(): string[] { diff --git a/src/App.tsx b/src/App.tsx index c8436f2..3a06f6d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,6 +17,7 @@ const StellarMetrics = lazy(() => import('./components/StellarMetrics')); const Compare = lazy(() => import('./components/Compare')); const Showcase = lazy(() => import('./components/Showcase')); const CaseStudiesStrip = lazy(() => import('./components/CaseStudiesStrip')); +const FeaturedGrantShowcase = lazy(() => import('./components/FeaturedGrantShowcase')); const EcosystemPartners = lazy(() => import('./components/EcosystemPartners')); const CtaStrip = lazy(() => import('./components/CtaStrip')); const Footer = lazy(() => import('./components/Footer')); @@ -29,6 +30,7 @@ const UseCases = lazy(() => import('./pages/UseCases')); const Stellar = lazy(() => import('./pages/Stellar')); const Roadmap = lazy(() => import('./pages/Roadmap')); const Grants = lazy(() => import('./pages/Grants')); +const GrantShowcase = lazy(() => import('./pages/GrantShowcase')); const CaseStudies = lazy(() => import('./pages/CaseStudies')); const Careers = lazy(() => import('./pages/Careers')); const About = lazy(() => import('./pages/About')); @@ -57,6 +59,7 @@ function Home() { + @@ -109,6 +112,14 @@ export default function App() { } /> + + + + } + /> = { + live: 'border-tertiary text-tertiary', + 'in progress': 'border-blue text-blue', + shipped: 'border-primary text-primary', +}; + +export default function FeaturedGrantShowcase() { + const { t } = useTranslation(); + + const featured = showcaseEntries.filter((entry) => entry.featured).slice(0, 3); + + if (featured.length === 0) return null; + + return ( +
+
+
+
+ + {t('featuredGrants.eyebrow')} + +

+ {t('featuredGrants.heading')} +

+

+ {t('featuredGrants.description')} +

+
+ + {t('featuredGrants.viewAll')} + + +
+ +
+ {featured.map((entry) => { + const visibleContributors = filterOptedOutContributors(entry.contributors); + const statusClass = statusStyles[entry.status] ?? 'border-outline-variant text-outline'; + + return ( +
+
+
+

+ {entry.name} +

+ + {entry.wave} + +
+ + {entry.status} + +
+ +

+ {entry.description} +

+ + {visibleContributors.length > 0 && ( +
+ {visibleContributors.map((contributor) => ( + + @{contributor} + + ))} +
+ )} + +
+ + {t('featuredGrants.readMore')} + + + → + +
+
+ ); + })} +
+
+
+ ); +} diff --git a/src/data/grant-showcase.json b/src/data/grant-showcase.json new file mode 100644 index 0000000..5a99b8a --- /dev/null +++ b/src/data/grant-showcase.json @@ -0,0 +1,59 @@ +{ + "entries": [ + { + "id": "stealth-wallet-extension", + "name": "Stealth Wallet Extension", + "description": "Browser extension that lets users send and receive stealth payments with one click, no SDK knowledge required.", + "wave": "Wave 1", + "contributors": ["satoshi-dev", "ghost", "crypto-lara"], + "status": "live", + "codeUrl": "https://github.com/wraith-protocol/stealth-wallet-extension", + "demoUrl": "https://wallet.usewraith.xyz", + "featured": true + }, + { + "id": "dao-treasury-shield", + "name": "DAO Treasury Shield", + "description": "Treasury management tool that routes multi-sig payments through stealth addresses, keeping contributor payouts private.", + "wave": "Wave 1", + "contributors": ["defi-builder", "open-source-org"], + "status": "in progress", + "codeUrl": "https://github.com/wraith-protocol/dao-treasury-shield", + "demoUrl": "https://treasury.usewraith.xyz", + "featured": true + }, + { + "id": "cross-chain-stealth-bridge", + "name": "Cross-Chain Stealth Bridge", + "description": "Bridge that moves assets between Horizen and Stellar using stealth addresses so neither side can link the sender to the receiver.", + "wave": "Wave 0", + "contributors": ["bridge-builder", "satoshi-dev"], + "status": "shipped", + "codeUrl": "https://github.com/wraith-protocol/cross-chain-stealth-bridge", + "demoUrl": "https://bridge.usewraith.xyz", + "featured": true + }, + { + "id": "private-payroll-protocol", + "name": "Private Payroll Protocol", + "description": "Payroll system that pays employees via one-time stealth addresses so salary amounts and recipients stay confidential on-chain.", + "wave": "Wave 1", + "contributors": ["payroll-hacker", "crypto-lara"], + "status": "in progress", + "codeUrl": "https://github.com/wraith-protocol/private-payroll-protocol", + "demoUrl": "https://payroll.usewraith.xyz", + "featured": false + }, + { + "id": "stealth-address-indexer", + "name": "Stealth Address Indexer", + "description": "Open-source indexer that scans announcement events across EVM chains and serves them via a fast REST API for stealth-payment clients.", + "wave": "Wave 0", + "contributors": ["indexer-dev", "open-source-org"], + "status": "shipped", + "codeUrl": "https://github.com/wraith-protocol/stealth-address-indexer", + "demoUrl": "https://indexer.usewraith.xyz", + "featured": false + } + ] +} diff --git a/src/i18n/en.json b/src/i18n/en.json index ecaf6d5..03ad983 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -382,5 +382,31 @@ "successShort": "Check your inbox", "errorShort": "Try again" } + }, + "grantShowcase": { + "eyebrow": "GRANT SHOWCASE", + "heading": "Grant-funded projects", + "description": "Explore projects built with Wraith Protocol grant funding. From stealth wallets to cross-chain bridges, see what the community is shipping.", + "totalProjects": "TOTAL PROJECTS", + "project": "project", + "projects": "projects", + "filterByWave": "FILTER BY WAVE", + "allWaves": "All Waves", + "filterByStatus": "FILTER BY STATUS", + "allStatuses": "All Statuses", + "resetFilters": "Reset Filters", + "contributors": "CONTRIBUTORS", + "viewCode": "Code", + "viewDemo": "Demo", + "noResults": "NO PROJECTS FOUND", + "noResultsDescription": "Try selecting different filters.", + "backToGrants": "Back to grants" + }, + "featuredGrants": { + "eyebrow": "GRANT SHOWCASE", + "heading": "Funded projects", + "description": "Projects built with Wraith Protocol grant funding.", + "viewAll": "VIEW ALL", + "readMore": "View Project" } } diff --git a/src/i18n/es.json b/src/i18n/es.json index 4b2fe41..e172535 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -368,5 +368,31 @@ "successShort": "Revisa tu bandeja", "errorShort": "Intenta de nuevo" } + }, + "grantShowcase": { + "eyebrow": "VITRINA DE BECAS", + "heading": "Proyectos financiados por becas", + "description": "Explora proyectos construidos con financiación de becas de Wraith Protocol. Desde billeteras ocultas hasta puentes entre cadenas, ve lo que la comunidad está creando.", + "totalProjects": "PROYECTOS TOTALES", + "project": "proyecto", + "projects": "proyectos", + "filterByWave": "FILTRAR POR OLA", + "allWaves": "Todas las Olas", + "filterByStatus": "FILTRAR POR ESTADO", + "allStatuses": "Todos los Estados", + "resetFilters": "Restablecer Filtros", + "contributors": "COLABORADORES", + "viewCode": "Código", + "viewDemo": "Demo", + "noResults": "NO SE ENCONTRARON PROYECTOS", + "noResultsDescription": "Prueba seleccionar diferentes filtros.", + "backToGrants": "Volver a becas" + }, + "featuredGrants": { + "eyebrow": "VITRINA DE BECAS", + "heading": "Proyectos financiados", + "description": "Proyectos construidos con financiación de becas de Wraith Protocol.", + "viewAll": "VER TODOS", + "readMore": "Ver Proyecto" } } diff --git a/src/pages/GrantShowcase.tsx b/src/pages/GrantShowcase.tsx new file mode 100644 index 0000000..8ece3bb --- /dev/null +++ b/src/pages/GrantShowcase.tsx @@ -0,0 +1,296 @@ +import { useMemo } from 'react'; +import { Link, useSearchParams } from 'react-router-dom'; +import { Helmet } from 'react-helmet-async'; +import { useTranslation } from 'react-i18next'; +import Layout from '../components/Layout'; +import { entries } from '../data/grant-showcase.json'; +import { filterOptedOutContributors } from '../utils/contributors'; + +type GrantShowcaseEntry = { + id: string; + name: string; + description: string; + wave: string; + contributors: string[]; + status: string; + codeUrl: string; + demoUrl: string; + featured: boolean; +}; + +const showcaseEntries = entries as GrantShowcaseEntry[]; + +const labelStyles = 'font-mono text-[10px] font-semibold uppercase tracking-[1.8px] text-outline'; + +const statusStyles: Record = { + live: 'border-tertiary text-tertiary', + 'in progress': 'border-blue text-blue', + shipped: 'border-primary text-primary', +}; + +function StatusBadge({ status }: { status: string }) { + const styles = statusStyles[status] ?? 'border-outline-variant text-outline'; + return ( + + {status} + + ); +} + +function ShowcaseCard({ entry }: { entry: GrantShowcaseEntry }) { + const { t } = useTranslation(); + const visibleContributors = filterOptedOutContributors(entry.contributors); + + return ( + + ); +} + +export default function GrantShowcase() { + const { t } = useTranslation(); + const [searchParams, setSearchParams] = useSearchParams(); + + const activeWave = searchParams.get('wave') ?? 'all'; + const activeStatus = searchParams.get('status') ?? 'all'; + + const uniqueWaves = useMemo( + () => Array.from(new Set(showcaseEntries.map((entry) => entry.wave))).sort(), + [], + ); + const uniqueStatuses = useMemo( + () => Array.from(new Set(showcaseEntries.map((entry) => entry.status))).sort(), + [], + ); + + const filteredEntries = useMemo(() => { + return showcaseEntries.filter((entry) => { + const waveMatch = activeWave === 'all' || entry.wave === activeWave; + const statusMatch = activeStatus === 'all' || entry.status === activeStatus; + return waveMatch && statusMatch; + }); + }, [activeWave, activeStatus]); + + const hasActiveFilters = activeWave !== 'all' || activeStatus !== 'all'; + + const updateFilter = (key: string, value: string) => { + const next = new URLSearchParams(searchParams); + if (value === 'all') { + next.delete(key); + } else { + next.set(key, value); + } + setSearchParams(next, { replace: true }); + }; + + const resetFilters = () => { + setSearchParams({}, { replace: true }); + }; + + return ( + + + Grant Showcase – Wraith Protocol + + + + + + + + +
+ {/* Hero */} +
+ {t('grantShowcase.eyebrow')} +
+

+ {t('grantShowcase.heading')} +

+

+ {t('grantShowcase.description')} +

+
+
+ + {t('grantShowcase.totalProjects')} + + + {showcaseEntries.length}{' '} + {showcaseEntries.length === 1 + ? t('grantShowcase.project') + : t('grantShowcase.projects')} + +
+
+ + {/* Filters */} +
+
+ {t('grantShowcase.filterByWave')} + + {uniqueWaves.map((wave) => ( + + ))} +
+ +
+ {t('grantShowcase.filterByStatus')} + + {uniqueStatuses.map((status) => ( + + ))} +
+ + {hasActiveFilters && ( + + )} +
+ + {/* Card Grid */} + {filteredEntries.length > 0 ? ( +
+ {filteredEntries.map((entry) => ( + + ))} +
+ ) : ( +
+ {t('grantShowcase.noResults')} +

+ {t('grantShowcase.noResultsDescription')} +

+ +
+ )} + + {/* Back to Grants */} +
+ + ← {t('grantShowcase.backToGrants')} + +
+
+
+ ); +} diff --git a/src/pages/Grants.tsx b/src/pages/Grants.tsx index 6131c96..a1493be 100644 --- a/src/pages/Grants.tsx +++ b/src/pages/Grants.tsx @@ -1,4 +1,6 @@ import { useState } from 'react'; +import { Link } from 'react-router-dom'; +import FeaturedGrantShowcase from '../components/FeaturedGrantShowcase'; import waveData from '../data/wave.json'; type Wave = (typeof waveData)['currentWave']; @@ -15,227 +17,240 @@ export default function Grants() { const [openFaqId, setOpenFaqId] = useState(null); return ( -
- {/* Hero */} -
- Grants -
-

- Build private payments. Get funded. -

-

- Wraith runs a grant program supported by{' '} - - Drips - {' '} - recurring revenue and external ecosystem funding. Each wave funds teams building stealth - address infrastructure, SDK integrations, and privacy-preserving payment tooling. -

-
-
- - {/* Current wave */} - {currentWave && ( -
- Current wave -
-
-
-

- {currentWave.name} -

-

- {currentWave.description} -

-
-
- - Budget - - - {currentWave.budget} - - - {currentWave.fundingSource} - -
-
- -
- - {currentWave.status === 'open' ? 'Open for applications' : currentWave.status} - - - {currentWave.rewardRange} per grant - -
- -
-

- {currentWave.howToApply} -

+ <> +
+ {/* Hero */} +
+ Grants +
+

+ Build private payments. Get funded. +

+

+ Wraith runs a grant program supported by{' '} - Apply on Drips - -

-
- - {/* Eligibility */} -
-
-

- Eligibility -

-
    - {currentWave.eligibility.map((item) => ( -
  • - - {item} -
  • - ))} -
-
-
-

- Review criteria -

-
    - {currentWave.reviewCriteria.map((item) => ( -
  • - - {item} -
  • - ))} -
-
+ Drips + {' '} + recurring revenue and external ecosystem funding. Each wave funds teams building + stealth address infrastructure, SDK integrations, and privacy-preserving payment + tooling. +

+ + View funded projects → +
- )} +
- {/* Past waves */} - {pastWaves.length > 0 && ( -
- Past waves -
- {pastWaves.map((wave) => ( -
-
-
-

- {wave.name} -

- - {wave.budget} · {wave.fundingSource} - -
- - Closed + + +
+ {/* Current wave */} + {currentWave && ( +
+ Current wave +
+
+
+

+ {currentWave.name} +

+

+ {currentWave.description} +

+
+
+ + Budget + + + {currentWave.budget} + + + {currentWave.fundingSource}
+
+ +
+ + {currentWave.status === 'open' ? 'Open for applications' : currentWave.status} + + + {currentWave.rewardRange} per grant + +
- {wave.highlights && wave.highlights.length > 0 && ( -
    - {wave.highlights.map((highlight) => ( -
  • - - {highlight} -
  • - ))} -
- )} +
+

+ {currentWave.howToApply} +

+ + Apply on Drips + +
+
- {wave.recipients && wave.recipients.length > 0 && ( -
- - Recipients - -
- {wave.recipients.map((r) => ( -
- {r.name} - {r.project} - - {r.grantAmount} - -
- ))} -
-
- )} + {/* Eligibility */} +
+
+

+ Eligibility +

+
    + {currentWave.eligibility.map((item) => ( +
  • + + {item} +
  • + ))} +
- ))} -
-
- )} +
+

+ Review criteria +

+
    + {currentWave.reviewCriteria.map((item) => ( +
  • + + {item} +
  • + ))} +
+
+
+
+ )} - {/* FAQ */} - {faqEntries.length > 0 && ( -
- FAQ -

- Frequently asked questions -

-
- {faqEntries.map((entry) => { - const isOpen = openFaqId === entry.id; - return ( + {/* Past waves */} + {pastWaves.length > 0 && ( +
+ Past waves +
+ {pastWaves.map((wave) => (
-
-

- -

+
+
+

+ {wave.name} +

+ + {wave.budget} · {wave.fundingSource} + +
+ + Closed +
- {isOpen && ( -
-

- {entry.answer} -

+ + {wave.highlights && wave.highlights.length > 0 && ( +
    + {wave.highlights.map((highlight) => ( +
  • + + {highlight} +
  • + ))} +
+ )} + + {wave.recipients && wave.recipients.length > 0 && ( +
+ + Recipients + +
+ {wave.recipients.map((r) => ( +
+ {r.name} + {r.project} + + {r.grantAmount} + +
+ ))} +
)}
- ); - })} -
{' '} -
- )} -
+ ))} + +
+ )} + + {/* FAQ */} + {faqEntries.length > 0 && ( +
+ FAQ +

+ Frequently asked questions +

+
+ {faqEntries.map((entry) => { + const isOpen = openFaqId === entry.id; + return ( +
+
+

+ +

+
+ {isOpen && ( +
+

+ {entry.answer} +

+
+ )} +
+ ); + })} +
{' '} +
+ )} + + ); } diff --git a/src/utils/contributors.ts b/src/utils/contributors.ts new file mode 100644 index 0000000..06cfdfb --- /dev/null +++ b/src/utils/contributors.ts @@ -0,0 +1,18 @@ +import optOutData from '../data/contributors-optout.json'; + +/** + * Filters out contributors who appear in the opt-out list. + * Comparison is case-insensitive so handle casing differences + * between data sources are handled gracefully. + * + * If every contributor for a project is opted out, an empty + * array is returned — callers should handle this gracefully + * (e.g. render no contributor tags). + */ +export function filterOptedOutContributors( + contributors: string[], + optOutList: string[] = optOutData, +): string[] { + const lowerOptOut = new Set(optOutList.map((name) => name.toLowerCase())); + return contributors.filter((contributor) => !lowerOptOut.has(contributor.toLowerCase())); +} From 9444e042c3fd336cd156478c869023768f498f62 Mon Sep 17 00:00:00 2001 From: sanusishafii989 Date: Wed, 26 Aug 2026 04:04:14 +0100 Subject: [PATCH 2/3] feat(seo): add hreflang tags, canonical URLs, and central Seo helper Add a reusable component in src/utils/seo.tsx that emits canonical URLs, hreflang alternates (en, es, x-default), Open Graph, and Twitter Card metadata on every page. Future pages inherit correct SEO by simply dropping in . - Create src/utils/seo.tsx with Seo component (uses useLocation for self-referencing canonical + locale-aware hreflang) - Add /es/* locale-prefixed routes in App.tsx via LocaleSetter wrapper - Audit all 18 pages: every route now has title, description, canonical, hreflang, og:*, and twitter:* metadata - Replace ad-hoc Helmet blocks on About, Blog, Careers, Ecosystem, GrantShowcase, Stellar with centralized Seo component - Add SEO metadata to 12 pages that had none: Faq, Privacy, Newsletter, UseCases, Roadmap, Grants, CaseStudies, Contributors, Vitals, NotFound, Press, Home - Add /es/* routes to sitemap knownRoutes Closes #125 --- public/feed.xml | 2 +- public/sitemap.xml | 32 +++---- scripts/sitemap.ts | 11 +++ src/App.tsx | 171 +++++++++++++++++++++++++++++++++++- src/pages/About.tsx | 20 ++--- src/pages/Blog.tsx | 29 +++--- src/pages/Careers.tsx | 20 ++--- src/pages/CaseStudies.tsx | 14 +++ src/pages/Contributors.tsx | 5 ++ src/pages/Ecosystem.tsx | 21 ++--- src/pages/Faq.tsx | 5 ++ src/pages/GrantShowcase.tsx | 21 ++--- src/pages/Grants.tsx | 5 ++ src/pages/Newsletter.tsx | 12 +-- src/pages/NotFound.tsx | 5 ++ src/pages/Press.tsx | 5 ++ src/pages/Privacy.tsx | 5 ++ src/pages/Roadmap.tsx | 5 ++ src/pages/Stellar.tsx | 24 ++--- src/pages/UseCases.tsx | 5 ++ src/pages/Vitals.tsx | 5 ++ src/utils/seo.tsx | 93 ++++++++++++++++++++ 22 files changed, 396 insertions(+), 119 deletions(-) create mode 100644 src/utils/seo.tsx diff --git a/public/feed.xml b/public/feed.xml index 1872f23..1f49e57 100644 --- a/public/feed.xml +++ b/public/feed.xml @@ -5,7 +5,7 @@ https://usewraith.xyz/blog Notes on stealth payments, private infrastructure, and the Wraith ecosystem. en-us - Tue, 25 Aug 2026 10:54:35 GMT + Wed, 26 Aug 2026 02:49:58 GMT diff --git a/public/sitemap.xml b/public/sitemap.xml index 04c0860..8c72aa5 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -2,97 +2,97 @@ https://usewraith.xyz - 2026-08-25 + 2026-08-26 daily 1.0 https://usewraith.xyz/faq - 2026-08-25 + 2026-08-26 weekly 0.8 https://usewraith.xyz/privacy - 2026-08-25 + 2026-08-26 weekly 0.8 https://usewraith.xyz/use-cases - 2026-08-25 + 2026-08-26 weekly 0.8 https://usewraith.xyz/roadmap - 2026-08-25 + 2026-08-26 weekly 0.8 https://usewraith.xyz/case-studies - 2026-08-25 + 2026-08-26 weekly 0.8 https://usewraith.xyz/stellar - 2026-08-25 + 2026-08-26 weekly 0.8 https://usewraith.xyz/careers - 2026-08-25 + 2026-08-26 weekly 0.8 https://usewraith.xyz/press - 2026-08-25 + 2026-08-26 weekly 0.8 https://usewraith.xyz/grants - 2026-08-25 + 2026-08-26 weekly 0.8 https://usewraith.xyz/grants/showcase - 2026-08-25 + 2026-08-26 weekly 0.8 https://usewraith.xyz/contributors - 2026-08-25 + 2026-08-26 weekly 0.8 https://usewraith.xyz/case-studies/payroll-processor - 2026-08-25 + 2026-08-26 weekly 0.7 https://usewraith.xyz/blog - 2026-08-25 + 2026-08-26 weekly 0.8 https://usewraith.xyz/compare - 2026-08-25 + 2026-08-26 weekly 0.8 https://usewraith.xyz/newsletter - 2026-08-25 + 2026-08-26 weekly 0.8 diff --git a/scripts/sitemap.ts b/scripts/sitemap.ts index bea430b..4cbf677 100644 --- a/scripts/sitemap.ts +++ b/scripts/sitemap.ts @@ -21,6 +21,17 @@ const knownRoutes = [ '/grants', '/grants/showcase', '/contributors', + '/es', + '/es/faq', + '/es/privacy', + '/es/use-cases', + '/es/roadmap', + '/es/case-studies', + '/es/stellar', + '/es/careers', + '/es/grants', + '/es/grants/showcase', + '/es/contributors', ]; function getCaseStudyRoutes(): string[] { diff --git a/src/App.tsx b/src/App.tsx index 3a06f6d..75f418b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { lazy, Suspense } from 'react'; +import { lazy, Suspense, useEffect, type ReactNode } from 'react'; import { BrowserRouter, Routes, Route } from 'react-router-dom'; import Header from './components/Header'; import Hero from './components/Hero'; @@ -7,6 +7,8 @@ import Layout from './components/Layout'; import TrustStrip from './components/TrustStrip'; import PartnerStrip from './components/PartnerStrip'; import { ThemeProvider } from './context/ThemeContext'; +import { Seo } from './utils/seo'; +import { changeLocale, type Locale } from './i18n'; // Lazy load below-the-fold homepage components const StealthAnimation = lazy(() => import('./components/StealthAnimation')); @@ -39,9 +41,21 @@ const NotFound = lazy(() => import('./pages/NotFound')); const Contributors = lazy(() => import('./pages/Contributors')); const Blog = lazy(() => import('./pages/Blog')); +/** Sets the i18n locale on mount for locale-prefixed routes. */ +function LocaleSetter({ locale, children }: { locale: Locale; children: ReactNode }) { + useEffect(() => { + changeLocale(locale); + }, [locale]); + return <>{children}; +} + function Home() { return (
+ Skip to content @@ -152,6 +166,161 @@ export default function App() { } /> + + {/* Spanish locale-prefixed routes */} + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + + + } + /> + + + + + + } + /> + + + + + + } + /> + + + + + + } + /> + + + + + + } + /> + + + + + + } + /> + + + + + + } + /> + + + + + + } + /> + } /> diff --git a/src/pages/About.tsx b/src/pages/About.tsx index c536d5a..557660f 100644 --- a/src/pages/About.tsx +++ b/src/pages/About.tsx @@ -1,4 +1,4 @@ -import { Helmet } from 'react-helmet-async'; +import { Seo } from '../utils/seo'; import teamData from '../data/team.json'; const socialIcon = (type: 'github' | 'twitter') => { @@ -19,20 +19,10 @@ const socialIcon = (type: 'github' | 'twitter') => { export default function About() { return ( <> - - About – Wraith Protocol - - - - - - + {/* ── Hero ──────────────────────────────────────────────────────────── */}
diff --git a/src/pages/Blog.tsx b/src/pages/Blog.tsx index 3b5a384..0beb43f 100644 --- a/src/pages/Blog.tsx +++ b/src/pages/Blog.tsx @@ -1,4 +1,4 @@ -import { Helmet } from 'react-helmet-async'; +import { Seo } from '../utils/seo'; import { Link, useParams } from 'react-router-dom'; import { getAllPosts, getPostBySlug } from '../utils/blog'; import BlogToc from '../components/BlogToc'; @@ -8,19 +8,17 @@ function BlogList() { return (
- - Blog – Wraith Protocol - + - +

Blog

@@ -82,9 +80,10 @@ function BlogPostDetail({ slug }: { slug: string }) { if (!post) { return (
- - Post Not Found – Wraith Protocol - +

Post Not Found

The requested blog post could not be found.

@@ -98,10 +97,10 @@ function BlogPostDetail({ slug }: { slug: string }) { return (
- - {post.title} – Wraith Protocol - {post.excerpt && } - +
- - Careers – Wraith Protocol - - - - - - +
diff --git a/src/pages/CaseStudies.tsx b/src/pages/CaseStudies.tsx index 177abba..56512ca 100644 --- a/src/pages/CaseStudies.tsx +++ b/src/pages/CaseStudies.tsx @@ -3,6 +3,7 @@ import { Link, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { entries } from '../data/case-studies.json'; import Layout from '../components/Layout'; +import { Seo } from '../utils/seo'; type CaseStudy = { id: string; @@ -67,6 +68,11 @@ function CaseStudyDetail({ study }: { study: CaseStudy }) { return ( +