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
26 changes: 26 additions & 0 deletions docs/EXPERIMENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Bridgelet Frontend Experiments

This document records experimental features built for the Bridgelet reference UI.

---

## Experiment 1: WhatsApp Share Button for Claim Links (Issue #150)
- **Goal**: Enable direct messaging sharing of payment claim URLs via WhatsApp (`wa.me`).
- **Target Markets**: Primary communication channel for African and LatAm disbursement flows.
- **Implementation**: `frontend/components/share-prompt.tsx` and `ConfirmStep` render deep links:
```text
https://wa.me/?text=Send%20and%20claim%20crypto%20payments%3A%20<URL>
```

---

## Experiment 2: Multi-Chain Support UI Stubs (Issue #152)
- **Goal**: Provide UI placeholders for future EVM & Soroban multi-chain payment flows while maintaining active Stellar functionality.
- **Implementation**: `ChainSelector` (`frontend/components/chain-selector.tsx`) displays Stellar (active) alongside disabled options for Ethereum, Polygon, and Soroban with `[Coming Soon]` badges.

---

## Experiment 3: Web NFC Tap to Share (Issue #154)
- **Goal**: Allow event organizers and community leaders to write claim URLs directly onto NFC cards/tags via mobile devices.
- **Browser Compatibility**: Utilizes the Web NFC API (`NDEFReader`). Supported natively on Android Chrome.
- **Implementation**: `NfcShareButton` (`frontend/components/nfc-share-button.tsx`) detects browser support, requests NFC write permissions, and writes URL records to nearby tags upon user interaction.
46 changes: 32 additions & 14 deletions frontend/components/send-form/steps/confirm-step.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,32 +149,50 @@ export function ConfirmStep({ state, onBack }: ConfirmStepProps) {
}

if (submitted) {
const claimLink = claimUrl || (typeof window !== 'undefined' ? `${window.location.origin}/claim` : 'https://bridgelet.org/claim');
const whatsappUrl = `https://wa.me/?text=${encodeURIComponent(`Here is your payment claim link via Bridgelet: ${claimLink}`)}`;

return (
<div
role="status"
aria-live="polite"
className="rounded-lg border border-green-200 bg-green-50 px-4 py-4"
className="rounded-lg border border-green-200 bg-green-50 px-4 py-4 space-y-3"
>
<p className="font-medium text-green-800">Payment sent!</p>
<p className="mt-1 text-sm text-green-700">
{state.recipientEmail ? (
<>
A claim link has been sent to <strong>{state.recipientEmail}</strong>.
</>
) : (
<>Your claim link is ready to share with your recipient.</>
)}{' '}
They have {formatExpiryLabel(state.expiresIn || DEFAULT_EXPIRES_IN_SECONDS)} to claim
their funds.
</p>
<div>
<p className="font-medium text-green-800">Payment sent!</p>
<p className="mt-1 text-sm text-green-700">
{state.recipientEmail ? (
<>
A claim link has been sent to <strong>{state.recipientEmail}</strong>.
</>
) : (
<>Your claim link is ready to share with your recipient.</>
)}{' '}
They have {formatExpiryLabel(state.expiresIn || DEFAULT_EXPIRES_IN_SECONDS)} to claim
their funds.
</p>
</div>
{signingModeUsed === 'freighter-client' && (
<p className="mt-2 text-xs text-green-700">
Account creation was authorised with Freighter client-side signing.
</p>
)}

{claimUrl && (
<div className="flex flex-wrap gap-2 pt-2">
<a
href={whatsappUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 rounded-md bg-emerald-600 px-3 py-1.5 text-xs font-medium text-white transition hover:bg-emerald-700"
>
<span>Share via WhatsApp</span>
</a>
</div>
)}

{isSupported && claimUrl && (
<div className="mt-4 border-t border-green-200 pt-4">
<div className="border-t border-green-200 pt-4">
<button
onClick={() => writeUrl(claimUrl)}
disabled={isWriting}
Expand Down
20 changes: 20 additions & 0 deletions frontend/components/share-prompt.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { render, screen } from '@testing-library/react';
import { SharePrompt } from './share-prompt';

describe('SharePrompt', () => {
it('renders WhatsApp share link with wa.me deep link format', () => {
const testUrl = 'https://bridgelet.org/claim?token=123';
render(<SharePrompt appUrl={testUrl} />);

const whatsappLink = screen.getByRole('link', { name: /share on whatsapp/i });
expect(whatsappLink).toBeInTheDocument();
expect(whatsappLink).toHaveAttribute(
'href',
expect.stringContaining('https://wa.me/?text=')
);
expect(whatsappLink).toHaveAttribute(
'href',
expect.stringContaining(encodeURIComponent(testUrl))
);
});
});
72 changes: 46 additions & 26 deletions frontend/components/share-prompt.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';

import { useState } from 'react';
import { NfcShareButton } from './nfc-share-button';

type SharePromptProps = {
appUrl: string;
Expand All @@ -19,16 +20,22 @@ export function SharePrompt({ appUrl }: SharePromptProps) {
}
}

const shareText = encodeURIComponent(`Send and claim crypto payments seamlessly with Bridgelet: ${appUrl}`);
const whatsappUrl = `https://wa.me/?text=${shareText}`;

return (
<div className="mt-8 rounded-xl border border-slate-100 bg-slate-50 p-5">
<p className="text-sm font-medium text-slate-800">
Do you send payments to your team or community?
</p>
<p className="mt-1 text-sm text-slate-600">
Bridgelet lets anyone send funds to recipients who have no crypto wallet — they claim
directly from a link. Share it with employers, payroll managers, or community leaders.
</p>
<div className="mt-4 flex flex-col gap-2 sm:flex-row sm:items-center">
<div className="mt-8 rounded-xl border border-slate-100 bg-slate-50 p-5 space-y-4">
<div>
<p className="text-sm font-medium text-slate-800">
Do you send payments to your team or community?
</p>
<p className="mt-1 text-sm text-slate-600">
Bridgelet lets anyone send funds to recipients who have no crypto wallet — they claim
directly from a link. Share it with employers, payroll managers, or community leaders.
</p>
</div>

<div className="flex flex-col gap-2 sm:flex-row sm:items-center">
<code className="flex-1 rounded-md border border-slate-200 bg-white px-3 py-1.5 text-xs text-slate-700 break-all">
{appUrl}
</code>
Expand All @@ -40,23 +47,36 @@ export function SharePrompt({ appUrl }: SharePromptProps) {
{copied ? 'Copied!' : 'Copy link'}
</button>
</div>
<div className="mt-3 flex gap-4">
<a
href={`https://twitter.com/intent/tweet?text=${encodeURIComponent('Just received a crypto payment via Bridgelet — the recipient needed no wallet. Check it out:')}&url=${encodeURIComponent(appUrl)}`}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-slate-500 underline underline-offset-2 hover:text-slate-800"
>
Share on X
</a>
<a
href={`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(appUrl)}`}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-slate-500 underline underline-offset-2 hover:text-slate-800"
>
Share on LinkedIn
</a>

<div className="pt-1 flex flex-col gap-3">
<NfcShareButton claimUrl={appUrl} />

<div className="flex flex-wrap gap-4">
<a
href={whatsappUrl}
target="_blank"
rel="noopener noreferrer"
className="text-xs font-medium text-emerald-600 underline underline-offset-2 hover:text-emerald-800"
>
Share on WhatsApp
</a>
<a
href={`https://twitter.com/intent/tweet?text=${encodeURIComponent('Just received a crypto payment via Bridgelet — the recipient needed no wallet. Check it out:')}&url=${encodeURIComponent(appUrl)}`}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-slate-500 underline underline-offset-2 hover:text-slate-800"
>
Share on X
</a>
<a
href={`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(appUrl)}`}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-slate-500 underline underline-offset-2 hover:text-slate-800"
>
Share on LinkedIn
</a>
</div>
</div>
</div>
);
Expand Down
Loading