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
26 changes: 10 additions & 16 deletions .github/workflows/frontend-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ jobs:
build-and-test:
name: Lint, type-check, test, and build
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend

steps:
- name: Checkout repository
Expand All @@ -22,28 +25,19 @@ jobs:
with:
node-version: 20
cache: npm
cache-dependency-path: package-lock.json
cache-dependency-path: frontend/package-lock.json

- name: Install dependencies
run: npm ci

- name: Detect package scripts
id: pkg
run: |
node -e "const fs = require('fs'); const pkg = require('./package.json'); const s = pkg.scripts || {}; const out = [`has_lint=${'lint' in s}`, `has_type_check=${'type-check' in s}`, `has_build=${'build' in s}`, `has_test=${'test' in s}`].join('\n'); fs.appendFileSync(process.env.GITHUB_OUTPUT, out + '\n');"

- name: Run lint
if: steps.pkg.outputs.has_lint == 'true'
run: npm run lint
- name: Install Playwright browsers
run: npm run test:e2e:install

- name: Run TypeScript type-check
if: steps.pkg.outputs.has_type_check == 'true'
run: npm run type-check

- name: Run tests
if: steps.pkg.outputs.has_test == 'true'
run: npm test
run: npm run typecheck

- name: Build app
if: steps.pkg.outputs.has_build == 'true'
run: npm run build

- name: Run Playwright E2E tests
run: npm run test:e2e
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ node_modules/

# Testing
coverage/
frontend/test-results/
frontend/playwright-report/
frontend/blob-report/
frontend/.playwright/

# Production
build/
Expand Down
3 changes: 3 additions & 0 deletions frontend/app/claim/[token]/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Loading() {
return <div className="p-8">Loading...</div>
}
40 changes: 17 additions & 23 deletions frontend/app/claim/[token]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,33 @@
import { PageShell } from '@/components/page-shell';
import { SharePrompt } from '@/components/share-prompt';
import { ClaimStatusCard } from '@/components/claim-status-card';
import { publicEnv } from '@/lib/env';
'use client'

import { useSearchParams } from 'next/navigation'
import { PageShell } from '@/components/page-shell'
import { SharePrompt } from '@/components/share-prompt'
import { ClaimStatusCard, type ClaimStatus } from '@/components/claim-status-card'
import { publicEnv } from '@/lib/env'

type ClaimPageProps = {
params: Promise<{ token: string }>;
};
params: { token: string }
}

/**
* Claim page — resolves the token from the URL and renders the correct
* `ClaimStatusCard` state.
*
* In production this would call `GET /claim/:token` to determine the real
* status. Until the API is wired up the page uses a static demo state so all
* three card variants can be exercised by appending `?state=claimed` or
* `?state=expired` to the URL (handled client-side via the `ClaimStatusCard`
* component directly on the demo page).
*/
export default async function ClaimPage({ params }: ClaimPageProps) {
const { token } = await params;
export default function ClaimPage({ params }: ClaimPageProps) {
const searchParams = useSearchParams()
const stateParam = searchParams.get('state') as ClaimStatus | null
const status = stateParam || 'available'

// Demo data — replace with a real API fetch once /claim/:token is live.
const demoExpiresAt = new Date(Date.now() + 23 * 60 * 60 * 1000).toISOString();
const demoExpiresAt = new Date(Date.now() + 23 * 60 * 60 * 1000).toISOString()

return (
<PageShell
title="Claim your payment"
description="A payment has been sent to you via Bridgelet. Review the details below and claim it to your Stellar wallet."
>
<div className="space-y-6">
{/* Available state */}
<section aria-labelledby="available-heading">
<h2 id="available-heading" className="sr-only">Available payment</h2>
<ClaimStatusCard
status="available"
status={status}
amountStroops="50000000"
assetCode="XLM"
expiresAt={demoExpiresAt}
Expand All @@ -44,11 +38,11 @@ export default async function ClaimPage({ params }: ClaimPageProps) {

<p className="text-xs text-slate-400 text-center">
Token:{' '}
<span className="font-mono break-all">{token}</span>
<span className="font-mono break-all">{params.token}</span>
</p>

<SharePrompt appUrl={publicEnv.NEXT_PUBLIC_APP_URL} />
</div>
</PageShell>
);
)
}
24 changes: 24 additions & 0 deletions frontend/e2e/claim-success.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { test, expect } from '@playwright/test'

test.describe('Claim Success Screens', () => {
test('should show claim submission success and match snapshot', async ({ page }) => {
await page.goto('/claim/example-token')

// Click "Claim now" to see the success state
await page.click('button:has-text("Claim now")')

// Wait for success message
await expect(page.getByText('Claim submitted!')).toBeVisible()
await expect(page).toHaveScreenshot('claim-submitted-success.png', { fullPage: true })
})

test('should show already claimed state and match snapshot', async ({ page }) => {
await page.goto('/claim/example-token?state=claimed')
await expect(page).toHaveScreenshot('claim-already-claimed.png', { fullPage: true })
})

test('should show expired state and match snapshot', async ({ page }) => {
await page.goto('/claim/example-token?state=expired')
await expect(page).toHaveScreenshot('claim-expired.png', { fullPage: true })
})
})
8 changes: 8 additions & 0 deletions frontend/e2e/homepage.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { test, expect } from '@playwright/test'

test.describe('Homepage', () => {
test('should render correctly and match snapshot', async ({ page }) => {
await page.goto('/')
await expect(page).toHaveScreenshot('homepage.png', { fullPage: true })
})
})
41 changes: 41 additions & 0 deletions frontend/e2e/send-success.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { test, expect } from '@playwright/test'

test.describe('Send Success Screen', () => {
test('should show success screen and match snapshot', async ({ page }) => {
// Mock the connectFreighter function to return a test public key
await page.addInitScript(() => {
// @ts-ignore: Mocking for test purposes
const original = window.require
// @ts-ignore: Mocking for test purposes
window.require = (mod: string) => {
if (mod === '@/lib/wallet') {
return {
connectFreighter: () => Promise.resolve({ publicKey: 'GABC123456789' })
}
}
return original?.(mod)
}
})

await page.goto('/send')

// Click connect button
await page.click('button:has-text("Connect Freighter Wallet")')

// Wait for wallet connected message
await expect(page.getByText('Wallet connected')).toBeVisible()

// Wait for next step to load, then fill details
await page.waitForSelector('#recipient-email')
await page.fill('#recipient-email', 'test@example.com')
await page.fill('#amount', '5')
await page.click('button:has-text("Review Payment")')

// Confirm and send
await page.click('button:has-text("Confirm & Send")')

// Wait for success screen
await expect(page.getByText('Payment sent!')).toBeVisible()
await expect(page).toHaveScreenshot('send-success.png', { fullPage: true })
})
})
4 changes: 3 additions & 1 deletion frontend/lib/bridgelet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
* See scripts/generate-types.mjs for full instructions.
*/

import { BridgeletClient, type BridgeletClientOptions } from '@/lib/create-bridgelet-client';

// ─── Payment Intent ──────────────────────────────────────────────────────────

/** Request body for POST /send */
Expand Down Expand Up @@ -77,7 +79,7 @@ export interface ApiError {
export {
BridgeletClient,
type BridgeletClientOptions,
} from '@/lib/create-bridgelet-client';
};

let _defaultClient: BridgeletClient | null = null;

Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion frontend/mocks/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ import { claimsHandlers } from './handlers/claims';
* Start this once in development to intercept fetch/XHR calls.
* The worker is only initialised when this module is imported.
*/
export const worker = setupWorker(...accountHandlers, ...horizonHandlers, ...claimsHandlers);
export const worker = setupWorker(...accountHandlers, ...claimsHandlers);
Loading
Loading