Skip to content
Merged
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
88 changes: 88 additions & 0 deletions samples/frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions samples/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
"test:e2e:headed": "playwright test --headed"
},
"dependencies": {
"@hpke/core": "^1.9.0",
"@noble/curves": "^2.2.0",
"bs58check": "^4.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Expand Down
116 changes: 24 additions & 92 deletions samples/frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,107 +1,39 @@
import { useState } from 'react'
import StepWizard from './components/StepWizard'
import Sidebar, { FlowKey } from './components/Sidebar'
import WebhookStream from './components/WebhookStream'
import CreateCustomer from './steps/CreateCustomer'
import CreateExternalAccount from './steps/CreateExternalAccount'
import CreateQuote from './steps/CreateQuote'
import SandboxFund from './steps/SandboxFund'
import PayoutFlow from './flows/PayoutFlow'
import EmbeddedWalletFlow from './flows/EmbeddedWalletFlow'

export default function App() {
const [activeStep, setActiveStep] = useState(0)
const [customerId, setCustomerId] = useState<string | null>(null)
const [externalAccountId, setExternalAccountId] = useState<string | null>(null)
const [quoteId, setQuoteId] = useState<string | null>(null)
const [selectedCountry, setSelectedCountry] = useState('MX')

const advance = () => setActiveStep((s) => s + 1)

const restartFromExternalAccount = () => {
setExternalAccountId(null)
setQuoteId(null)
setActiveStep(1)
}
const FLOW_META: Record<FlowKey, { title: string; subtitle: string }> = {
payout: {
title: 'Payout to Bank Account',
subtitle: 'Send a real time payment funded with USDC',
},
'embedded-wallet': {
title: 'Global Account',
subtitle: 'Issue a self-custody dollar account and withdraw on behalf of a user',
},
}

const steps = [
{
title: '1. Create Customer',
summary: customerId ? `ID: ${customerId}` : null,
content: (
<CreateCustomer
disabled={activeStep !== 0}
onComplete={(data) => {
setCustomerId(data.id as string)
advance()
}}
/>
),
},
{
title: '2. Create External Account',
summary: externalAccountId ? `ID: ${externalAccountId}` : null,
content: (
<CreateExternalAccount
customerId={customerId}
disabled={activeStep !== 1}
selectedCountry={selectedCountry}
onCountryChange={setSelectedCountry}
onComplete={(data) => {
setExternalAccountId(data.id as string)
advance()
}}
/>
),
},
{
title: '3. Create Quote',
summary: quoteId ? `ID: ${quoteId}` : null,
content: (
<CreateQuote
customerId={customerId}
externalAccountId={externalAccountId}
selectedCountry={selectedCountry}
disabled={activeStep !== 2}
onComplete={(data) => {
setQuoteId((data.quoteId ?? data.id) as string)
advance()
}}
/>
),
},
{
title: '4. Simulate Funding (Sandbox Only)',
summary: activeStep > 3 ? 'Funded' : null,
content: (
<SandboxFund
quoteId={quoteId}
disabled={activeStep !== 3}
onComplete={() => advance()}
/>
),
},
]
export default function App() {
const [activeFlow, setActiveFlow] = useState<FlowKey>('payout')
const meta = FLOW_META[activeFlow]

return (
<div className="min-h-screen bg-gray-950 text-gray-100">
<div className="min-h-screen bg-gray-950 text-gray-100 pb-12">
<header className="border-b border-gray-800 px-6 py-4">
<h1 className="text-xl font-bold">Grid API Sample</h1>
<p className="text-sm text-gray-400">Send a real time payment funded with USDC</p>
<p className="text-sm text-gray-400">{meta.subtitle}</p>
</header>
<div className="flex">
<main className="w-3/5 p-6 border-r border-gray-800 min-h-[calc(100vh-73px)]">
<StepWizard steps={steps} activeStep={activeStep} />
{activeStep >= 1 && (
<button
onClick={restartFromExternalAccount}
className="mt-6 px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded text-sm font-medium text-gray-300"
>
Start New Payment
</button>
)}
<Sidebar activeFlow={activeFlow} onSelect={setActiveFlow} />
<main className="flex-1 p-6 min-h-[calc(100vh-73px)] max-w-5xl">
<h2 className="text-lg font-semibold mb-4">{meta.title}</h2>
{activeFlow === 'payout' && <PayoutFlow key="payout" />}
{activeFlow === 'embedded-wallet' && <EmbeddedWalletFlow key="embedded-wallet" />}
</main>
<aside className="w-2/5 p-6 min-h-[calc(100vh-73px)]">
<WebhookStream />
</aside>
</div>
<WebhookStream />
</div>
)
}
55 changes: 55 additions & 0 deletions samples/frontend/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
export type FlowKey = 'payout' | 'embedded-wallet'

interface FlowEntry {
key: FlowKey
label: string
description: string
}

const FLOWS: FlowEntry[] = [
{
key: 'payout',
label: 'Payout to Bank Account',
description: 'Send a real-time payment funded with USDC',
},
{
key: 'embedded-wallet',
label: 'Global Account',
description: 'Issue a self-custody dollar account and withdraw on behalf of a user',
},
]

interface SidebarProps {
activeFlow: FlowKey
onSelect: (flow: FlowKey) => void
}

export default function Sidebar({ activeFlow, onSelect }: SidebarProps) {
return (
<nav className="w-64 shrink-0 border-r border-gray-800 p-4 min-h-[calc(100vh-73px)]">
<h2 className="text-xs font-semibold uppercase tracking-wider text-gray-500 mb-3 px-2">
Flows
</h2>
<ul className="space-y-1">
{FLOWS.map((flow) => {
const isActive = flow.key === activeFlow
return (
<li key={flow.key}>
<button
onClick={() => onSelect(flow.key)}
className={`w-full text-left rounded px-3 py-2 transition-colors ${
isActive
? 'bg-blue-600/20 border border-blue-600/40 text-blue-100'
: 'border border-transparent hover:bg-gray-800/60 text-gray-300'
}`}
>
<div className="text-sm font-medium">{flow.label}</div>
<div className="text-xs text-gray-500 mt-0.5">{flow.description}</div>
</button>
</li>
)
})}
</ul>
</nav>
)
}
Loading
Loading