diff --git a/frontend/index.html b/frontend/index.html index 5eeb91c..1fd21f6 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,32 @@ - CronStream + + + CronStream - Programmable Payroll for Contractors & Teams + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/og-image.svg b/frontend/public/og-image.svg new file mode 100644 index 0000000..a6165b7 --- /dev/null +++ b/frontend/public/og-image.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + C + + + + CronStream + + + + + Programmable Payroll + + + + + Milestone-Verified Payments + + + + + GitHub • Jira • Bitbucket • Figma + + + + + + + + docs.cronstream.xyz + + diff --git a/frontend/src/hooks/useMetaTags.js b/frontend/src/hooks/useMetaTags.js new file mode 100644 index 0000000..28189b7 --- /dev/null +++ b/frontend/src/hooks/useMetaTags.js @@ -0,0 +1,52 @@ +import { useEffect } from 'react'; + +export function useMetaTags({ + title = 'CronStream - Programmable Payroll for Contractors & Teams', + description = 'Milestone-verified payment streams for teams. Contractors earn as work ships with automatic verification.', + url = 'https://cronstream.xyz', + image = 'https://cronstream.xyz/og-image.png', + type = 'website', +} = {}) { + useEffect(() => { + // Update title + document.title = title; + + // Update or create meta tags + const updateMeta = (name, content, isProperty = false) => { + let element = document.querySelector( + isProperty ? `meta[property="${name}"]` : `meta[name="${name}"]` + ); + if (!element) { + element = document.createElement('meta'); + isProperty ? element.setAttribute('property', name) : element.setAttribute('name', name); + document.head.appendChild(element); + } + element.content = content; + }; + + // Standard meta + updateMeta('description', description); + + // Open Graph + updateMeta('og:type', type, true); + updateMeta('og:url', url, true); + updateMeta('og:title', title, true); + updateMeta('og:description', description, true); + updateMeta('og:image', image, true); + + // Twitter + updateMeta('twitter:url', url); + updateMeta('twitter:title', title); + updateMeta('twitter:description', description); + updateMeta('twitter:image', image); + + // Canonical + let canonical = document.querySelector('link[rel="canonical"]'); + if (!canonical) { + canonical = document.createElement('link'); + canonical.rel = 'canonical'; + document.head.appendChild(canonical); + } + canonical.href = url; + }, [title, description, url, image, type]); +} diff --git a/frontend/src/hooks/useSchemaMarkup.js b/frontend/src/hooks/useSchemaMarkup.js new file mode 100644 index 0000000..d18c1c1 --- /dev/null +++ b/frontend/src/hooks/useSchemaMarkup.js @@ -0,0 +1,15 @@ +import { useEffect } from 'react'; + +export function useSchemaMarkup(schema) { + useEffect(() => { + if (!schema) return; + + let script = document.querySelector('script[type="application/ld+json"]'); + if (!script) { + script = document.createElement('script'); + script.type = 'application/ld+json'; + document.head.appendChild(script); + } + script.textContent = JSON.stringify(schema); + }, [schema]); +} diff --git a/frontend/src/pages/Faucet.jsx b/frontend/src/pages/Faucet.jsx index e1d5b6b..627ffa0 100644 --- a/frontend/src/pages/Faucet.jsx +++ b/frontend/src/pages/Faucet.jsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import { useAccount, useWriteContract, useWaitForTransactionReceipt } from 'wagmi'; import { parseAbi } from 'viem'; import { useNavigate } from 'react-router-dom'; +import { useMetaTags } from '../hooks/useMetaTags'; const CRM_ADDRESS = '0x2Ca6e6FbAA8D0Bc27a64Ca079aFa6bf5cc8C7ad1'; const CRM_ABI = parseAbi(['function faucet() external']); @@ -63,6 +64,12 @@ export default function Faucet() { const navigate = useNavigate(); const [claimed, setClaimed] = useState(false); + useMetaTags({ + title: 'Faucet - CronStream Testnet', + description: 'Get free test tokens on Arbitrum Sepolia and Robinhood Chain for CronStream development and testing.', + url: 'https://cronstream.xyz/faucet', + }); + const { writeContract, data: txHash, isPending, error } = useWriteContract(); const { isLoading: confirming, isSuccess } = useWaitForTransactionReceipt({ hash: txHash }); diff --git a/frontend/src/pages/Landing.jsx b/frontend/src/pages/Landing.jsx index 9b0a766..efa1390 100644 --- a/frontend/src/pages/Landing.jsx +++ b/frontend/src/pages/Landing.jsx @@ -3,6 +3,8 @@ import { useAccount } from 'wagmi'; import { useConnectModal } from '@rainbow-me/rainbowkit'; import { useEffect } from 'react'; import { useProfile } from '../hooks/useProfile'; +import { useMetaTags } from '../hooks/useMetaTags'; +import { useSchemaMarkup } from '../hooks/useSchemaMarkup'; import StreamBackground from '../components/StreamBackground'; import FlowDiagram from '../components/FlowDiagram'; @@ -30,6 +32,36 @@ export default function Landing() { const { openConnectModal } = useConnectModal(); const { profileComplete } = useProfile(address); + // SEO metadata + useMetaTags({ + title: 'CronStream - Programmable Payroll for Contractors & Teams', + description: 'Milestone-verified payment streams for teams. Contractors earn as work ships with automatic verification from GitHub, Jira, Bitbucket, and Figma. No invoices. No delays. No disputes.', + url: 'https://cronstream.xyz', + }); + + // Structured data for Google AI summaries + useSchemaMarkup({ + '@context': 'https://schema.org', + '@type': 'SoftwareApplication', + name: 'CronStream', + description: 'Programmable payroll platform for milestone-verified contractor payments', + applicationCategory: 'FinanceApplication', + offers: { + '@type': 'Offer', + price: '0.5', + priceCurrency: 'USD', + description: '0.5% protocol fee' + }, + featureList: [ + 'Milestone-Gated Streams', + 'Autonomous Agent Verification', + 'ERC-20 Token Support', + 'Per-Second Precision', + 'Full Budget Recovery', + 'EIP-712 Cryptographic Proof' + ], + }); + useEffect(() => { if (isConnected) { navigate(profileComplete ? '/app/dashboard' : '/app/setup', { replace: true }); diff --git a/frontend/src/pages/Privacy.jsx b/frontend/src/pages/Privacy.jsx index b61ac1a..98df59c 100644 --- a/frontend/src/pages/Privacy.jsx +++ b/frontend/src/pages/Privacy.jsx @@ -1,4 +1,5 @@ import { useNavigate } from 'react-router-dom'; +import { useMetaTags } from '../hooks/useMetaTags'; const LAST_UPDATED = 'May 2026'; @@ -35,6 +36,13 @@ const SECTIONS = [ export default function Privacy() { const navigate = useNavigate(); + + useMetaTags({ + title: 'Privacy Policy - CronStream', + description: 'Learn how CronStream protects your data and privacy. We collect only what is necessary to operate the protocol.', + url: 'https://cronstream.xyz/privacy', + }); + return (
{/* Nav */} diff --git a/frontend/src/pages/PublicProfile.jsx b/frontend/src/pages/PublicProfile.jsx index 84dccdb..dc59d53 100644 --- a/frontend/src/pages/PublicProfile.jsx +++ b/frontend/src/pages/PublicProfile.jsx @@ -3,6 +3,7 @@ import { useParams, useNavigate } from 'react-router-dom'; import { useAccount } from 'wagmi'; import { ConnectButton } from '@rainbow-me/rainbowkit'; import { useCreateStream } from '../context/CreateStreamContext'; +import { useMetaTags } from '../hooks/useMetaTags'; import CreateStreamModal from '../components/CreateStreamModal'; import Watermark from '../components/Watermark'; @@ -26,6 +27,17 @@ export default function PublicProfile() { const [status, setStatus] = useState('idle'); const [contractor, setContractor] = useState(null); + // Update metadata when contractor data loads + useMetaTags({ + title: contractor?.name + ? `${contractor.name} - CronStream Profile` + : 'CronStream - Contractor Profile', + description: contractor?.name + ? `View ${contractor.name}'s payment streams and work verification on CronStream` + : 'View contractor profile on CronStream', + url: `https://cronstream.xyz/p/${username}`, + }); + // Only fetch once wallet is connected - never expose contractor info to unauthenticated visitors useEffect(() => { if (!isConnected || !username) return; diff --git a/frontend/src/pages/Terms.jsx b/frontend/src/pages/Terms.jsx index 5d81e13..cd36962 100644 --- a/frontend/src/pages/Terms.jsx +++ b/frontend/src/pages/Terms.jsx @@ -1,4 +1,5 @@ import { useNavigate } from 'react-router-dom'; +import { useMetaTags } from '../hooks/useMetaTags'; const LAST_UPDATED = 'May 2026'; @@ -47,6 +48,13 @@ const SECTIONS = [ export default function Terms() { const navigate = useNavigate(); + + useMetaTags({ + title: 'Terms of Service - CronStream', + description: 'CronStream Terms of Service and legal agreement for using the platform.', + url: 'https://cronstream.xyz/terms', + }); + return (
{/* Nav */}