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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security

- Added `docs/security-model.mdx` — re-authored security model in MDX with threat model table (15 threats, mitigations, and statuses) and explicit MVP stub callouts for stub sweep auth, missing encryption at rest, demo-only claim page, and absent CSP enforcement
- Added Web NFC API support for tap-to-share claim links
52 changes: 51 additions & 1 deletion frontend/components/send-form/steps/confirm-step.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useState } from 'react';
import { useState, useEffect } from 'react';
import type { SendFormState } from '../index';

type ConfirmStepProps = {
Expand All @@ -27,6 +27,35 @@ export function ConfirmStep({ state, onBack }: ConfirmStepProps) {
}
}

const [nfcStatus, setNfcStatus] = useState<string>('');
const [isNfcSupported, setIsNfcSupported] = useState(false);

// Safe to check on mount
useEffect(() => {
if (typeof window !== 'undefined' && 'NDEFReader' in window) {
setIsNfcSupported(true);
}
}, []);

async function handleNfcWrite() {
try {
if ('NDEFReader' in window) {
const ndef = new (window as any).NDEFReader();
setNfcStatus('Ready... Please tap your NFC tag to the back of your device.');

const claimUrl = 'https://bridgelet.app/claim/placeholder-token';
await ndef.write(claimUrl);

setNfcStatus('Successfully written to NFC tag!');
setTimeout(() => setNfcStatus(''), 3000);
}
} catch (error) {
console.error('NFC Write Error:', error);
setNfcStatus('Failed to write to NFC tag. Ensure NFC is enabled and try again.');
setTimeout(() => setNfcStatus(''), 4000);
}
}

if (submitted) {
return (
<div
Expand All @@ -39,6 +68,27 @@ export function ConfirmStep({ state, onBack }: ConfirmStepProps) {
A claim link has been sent to <strong>{state.recipientEmail}</strong>. They have 24
hours to claim their funds.
</p>

{isNfcSupported && (
<div className="mt-4 pt-4 border-t border-green-200/60">
<p className="text-sm font-medium text-green-800 mb-2">In-person disbursement:</p>
<button
onClick={handleNfcWrite}
type="button"
className="inline-flex items-center gap-2 rounded-md bg-slate-900 px-4 py-2 text-sm font-medium text-white shadow-sm transition hover:bg-slate-700 focus:ring-2 focus:ring-slate-900 focus:ring-offset-2"
>
<svg className="h-5 w-5 fill-current" viewBox="0 0 24 24">
<path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm0 18c-4.418 0-8-3.582-8-8s3.582-8 8-8 8 3.582 8 8-3.582 8-8 8zm2.293-11.707a1 1 0 0 0-1.414-1.414l-3 3a1 1 0 0 0 0 1.414l3 3a1 1 0 0 0 1.414-1.414L12.414 12l1.879-1.879zM15 12c0-.398-.158-.797-.473-1.102l-1.5-1.5a1 1 0 1 0-1.414 1.414L12.672 12l-1.059 1.059a1 1 0 0 0 1.414 1.414l1.5-1.5C14.842 12.797 15 12.398 15 12z"/>
</svg>
Tap to NFC Tag
</button>
{nfcStatus && (
<p className={`mt-2 text-xs font-medium ${nfcStatus.includes('Failed') ? 'text-red-600' : 'text-slate-700'}`}>
{nfcStatus}
</p>
)}
</div>
)}
</div>
);
}
Expand Down
1 change: 1 addition & 0 deletions frontend/tsconfig.tsbuildinfo

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions pr_body_154.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Closes #154

### What does this PR do?
This PR integrates the Web NFC API into the payment success screen to enable seamlessly writing the claim URL directly to an NFC tag for in-person distributions.

### Description
- **Web NFC Support Check**: Safely detects whether the device supports the Web NFC API (`NDEFReader`) upon component mount. This prevents rendering the button on unsupported devices (e.g., iOS Safari or non-NFC Androids).
- **In-Person Tap-to-Share Flow**: When supported, users are presented with a "Tap to NFC Tag" button. Clicking this initializes a write stream where users can simply tap a compatible NFC card/tag to the back of their device, provisioning it with the generated claim URL.
- **Robust Feedback**: The component gracefully handles success states and common failure scenarios (e.g., NFC disabled, permission denied) with clear, timed UI status updates.
Loading