mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
feat(desktop): Lightning wallet UX prototype behind wallet preview flag
First visual prototype of the Bitcoin-in-Buzz PRD (see #buzz-lightning): - WalletBalanceChip in the top-right of AppTopChrome showing the user's global ₿ balance; opens the wallet panel in a popover. - WalletPanel with three views: overview (provider badge, prominent balance, transaction history, hidden-by-default recovery-phrase reveal), fund (BIP-321 QR encoding a BOLT12 offer with BOLT11 fallback, copy/refresh offer), and send (BOLT12/BOLT11 destination plus ₿ amount). - All data comes from a mock wallet-provider module — no SDK, no keys, no network. Pure UX prototype for the wallet-provider abstraction the PRD defines (Lexe is the v1 integration target). - Gated behind a new `wallet` preview feature, off by default. - Playwright screenshot spec covers the chip and all three panel views. Co-authored-by: Thomas Petersen <thomasp@squareup.com> Signed-off-by: Thomas Petersen <thomasp@squareup.com>
This commit is contained in:
co-authored by
Thomas Petersen
parent
74b63e1846
commit
0b91a093d4
@@ -120,6 +120,7 @@ export default defineConfig({
|
||||
"**/inbox-live-update.spec.ts",
|
||||
"**/mesh-compute.spec.ts",
|
||||
"**/observer-archive-policy.spec.ts",
|
||||
"**/wallet-screenshots.spec.ts",
|
||||
],
|
||||
use: {
|
||||
...devices["Desktop Chrome"],
|
||||
|
||||
@@ -11,6 +11,8 @@ import { useIsFullscreen } from "@/shared/lib/useIsFullscreen";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import { topChromeBackdrop } from "@/shared/layout/chromeLayout";
|
||||
import { FeatureGate } from "@/shared/features";
|
||||
import { WalletBalanceChip } from "@/features/wallet/ui/WalletBalanceChip";
|
||||
import { useOptionalSidebar } from "@/shared/ui/sidebar";
|
||||
|
||||
type AppTopChromeProps = {
|
||||
@@ -131,6 +133,11 @@ export function AppTopChrome({
|
||||
<ChevronRight />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="ml-auto flex items-center">
|
||||
<FeatureGate feature="wallet">
|
||||
<WalletBalanceChip />
|
||||
</FeatureGate>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
// Mock wallet-provider data backing the Lightning wallet UI prototype.
|
||||
//
|
||||
// The PRD (see #buzz-lightning) defines a wallet-provider abstraction that
|
||||
// any conforming Lightning SDK can plug into (Lexe is the v1 target). This
|
||||
// module stands in for that provider so the UX can be designed and reviewed
|
||||
// before any SDK is integrated. Everything here is static, deterministic
|
||||
// fixture data — no keys, no network, no money.
|
||||
|
||||
export type WalletTransactionKind = "zap" | "kudos" | "send" | "fund";
|
||||
|
||||
export interface WalletTransaction {
|
||||
id: string;
|
||||
direction: "receive" | "send";
|
||||
amountSats: number;
|
||||
counterparty: string;
|
||||
memo: string;
|
||||
/** Pre-rendered relative time label; static so screenshots stay stable. */
|
||||
timeLabel: string;
|
||||
kind: WalletTransactionKind;
|
||||
}
|
||||
|
||||
export interface WalletSnapshot {
|
||||
providerName: string;
|
||||
balanceSats: number;
|
||||
/** BOLT12 offer — the core send/receive primitive per the PRD. */
|
||||
bolt12Offer: string;
|
||||
/**
|
||||
* BIP-321 funding URI carrying the amountless BOLT12 offer plus a BOLT11
|
||||
* fallback so external wallets without BOLT12 support can still fund.
|
||||
*/
|
||||
bip321Uri: string;
|
||||
/** 12-word BIP-39 seed phrase (fixture — never a real key). */
|
||||
seedPhrase: string;
|
||||
transactions: WalletTransaction[];
|
||||
}
|
||||
|
||||
/** Format sats with the ₿ symbol, per the PRD ("use ₿ instead of sats"). */
|
||||
export function formatBtc(sats: number): string {
|
||||
return `₿${sats.toLocaleString("en-US")}`;
|
||||
}
|
||||
|
||||
const MOCK_BOLT12_OFFER =
|
||||
"lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcgqgn3qzsyvfkx26qkyypvr5hfx4gdhjy7uq9jjjrrjkflcgwzycqqqqqqqqqqqqqqqqqqqq";
|
||||
|
||||
const MOCK_BOLT11_FALLBACK =
|
||||
"lnbc1pn9qw2app5x7rlq3mmkwvpksct5v4kxz6ryqdq5g9kxy7fqd9h8vmmfvdjsqqqqqqqqqqqq";
|
||||
|
||||
export const MOCK_WALLET: WalletSnapshot = {
|
||||
providerName: "Lexe",
|
||||
balanceSats: 21_450,
|
||||
bolt12Offer: MOCK_BOLT12_OFFER,
|
||||
bip321Uri: `bitcoin:?lno=${MOCK_BOLT12_OFFER}&lightning=${MOCK_BOLT11_FALLBACK}`,
|
||||
seedPhrase:
|
||||
"narrow ensure hollow tribe fiscal vault lyrics aunt shrug wagon rocket kite",
|
||||
transactions: [
|
||||
{
|
||||
id: "tx-01",
|
||||
direction: "receive",
|
||||
amountSats: 100,
|
||||
counterparty: "Eva",
|
||||
memo: "Zap · research synthesis",
|
||||
timeLabel: "12m ago",
|
||||
kind: "zap",
|
||||
},
|
||||
{
|
||||
id: "tx-02",
|
||||
direction: "receive",
|
||||
amountSats: 500,
|
||||
counterparty: "Tyler",
|
||||
memo: "Kudos · shipping the harness fix",
|
||||
timeLabel: "2h ago",
|
||||
kind: "kudos",
|
||||
},
|
||||
{
|
||||
id: "tx-03",
|
||||
direction: "send",
|
||||
amountSats: 250,
|
||||
counterparty: "Dawn",
|
||||
memo: "Shared agent invocation",
|
||||
timeLabel: "5h ago",
|
||||
kind: "send",
|
||||
},
|
||||
{
|
||||
id: "tx-04",
|
||||
direction: "receive",
|
||||
amountSats: 1_000,
|
||||
counterparty: "Mat",
|
||||
memo: "Gifting channel · welcome to #spiral",
|
||||
timeLabel: "1d ago",
|
||||
kind: "fund",
|
||||
},
|
||||
{
|
||||
id: "tx-05",
|
||||
direction: "send",
|
||||
amountSats: 100,
|
||||
counterparty: "Wren",
|
||||
memo: "Zap · rug-test writeup",
|
||||
timeLabel: "2d ago",
|
||||
kind: "zap",
|
||||
},
|
||||
{
|
||||
id: "tx-06",
|
||||
direction: "receive",
|
||||
amountSats: 20_200,
|
||||
counterparty: "External wallet",
|
||||
memo: "Wallet funded via BIP-321",
|
||||
timeLabel: "3d ago",
|
||||
kind: "fund",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Zap } from "lucide-react";
|
||||
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover";
|
||||
import { formatBtc, MOCK_WALLET } from "../lib/mockWallet";
|
||||
import { WalletPanel } from "./WalletPanel";
|
||||
|
||||
// Top-right wallet affordance: the user's global bitcoin balance, always
|
||||
// visible in the app chrome ("running score" per the PRD's top-level balance
|
||||
// discussion). Clicking it opens the wallet panel in a popover.
|
||||
//
|
||||
// Fixed height on purpose: this chip lives in the top chrome beside the
|
||||
// px-pinned nav buttons (see `AppTopChrome`), so it follows the same
|
||||
// deliberate exception to the rem-first rule.
|
||||
export function WalletBalanceChip() {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
aria-label={`Wallet balance ${formatBtc(MOCK_WALLET.balanceSats)}`}
|
||||
className="flex h-[28px] items-center gap-1.5 rounded-full border border-border/60 bg-background/60 px-3 text-xs font-semibold tabular-nums text-sidebar-foreground/90 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
||||
data-testid="wallet-balance-chip"
|
||||
type="button"
|
||||
>
|
||||
<Zap className="size-3.5 text-amber-500" />
|
||||
{formatBtc(MOCK_WALLET.balanceSats)}
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="w-88" sideOffset={8}>
|
||||
<WalletPanel />
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
import * as React from "react";
|
||||
import {
|
||||
ArrowDownLeft,
|
||||
ArrowUpRight,
|
||||
ChevronLeft,
|
||||
Copy,
|
||||
Eye,
|
||||
EyeOff,
|
||||
QrCode,
|
||||
RefreshCw,
|
||||
Send,
|
||||
} from "lucide-react";
|
||||
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import { Badge } from "@/shared/ui/badge";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import { Input } from "@/shared/ui/input";
|
||||
import { Separator } from "@/shared/ui/separator";
|
||||
import { StyledQrCode } from "@/shared/ui/styled-qr-code";
|
||||
import {
|
||||
formatBtc,
|
||||
MOCK_WALLET,
|
||||
type WalletTransaction,
|
||||
} from "../lib/mockWallet";
|
||||
|
||||
// Wallet panel prototype per the Bitcoin-in-Buzz PRD MVP list: show the
|
||||
// provider, show the balance prominently, fund via a BIP-321 QR, send to a
|
||||
// BOLT12/BOLT11/offer string, transaction history, and a hidden-by-default
|
||||
// seed-phrase reveal. Backed entirely by mock provider data — no funds move.
|
||||
|
||||
type WalletView = "overview" | "fund" | "send";
|
||||
|
||||
function TransactionRow({ tx }: { tx: WalletTransaction }) {
|
||||
const isReceive = tx.direction === "receive";
|
||||
return (
|
||||
<div
|
||||
className="flex items-center gap-3 py-2"
|
||||
data-testid={`wallet-tx-${tx.id}`}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex size-7 shrink-0 items-center justify-center rounded-full",
|
||||
isReceive
|
||||
? "bg-emerald-500/15 text-emerald-600 dark:text-emerald-400"
|
||||
: "bg-muted text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{isReceive ? (
|
||||
<ArrowDownLeft className="size-4" />
|
||||
) : (
|
||||
<ArrowUpRight className="size-4" />
|
||||
)}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-medium">{tx.counterparty}</p>
|
||||
<p className="truncate text-xs text-muted-foreground">{tx.memo}</p>
|
||||
</div>
|
||||
<div className="shrink-0 text-right">
|
||||
<p
|
||||
className={cn(
|
||||
"text-sm font-semibold tabular-nums",
|
||||
isReceive
|
||||
? "text-emerald-600 dark:text-emerald-400"
|
||||
: "text-foreground",
|
||||
)}
|
||||
>
|
||||
{isReceive ? "+" : "−"}
|
||||
{formatBtc(tx.amountSats)}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">{tx.timeLabel}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PanelHeader({
|
||||
onBack,
|
||||
title,
|
||||
}: {
|
||||
onBack?: () => void;
|
||||
title: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
{onBack ? (
|
||||
<Button
|
||||
aria-label="Back to wallet overview"
|
||||
data-testid="wallet-panel-back"
|
||||
onClick={onBack}
|
||||
size="icon-xs"
|
||||
variant="ghost"
|
||||
>
|
||||
<ChevronLeft />
|
||||
</Button>
|
||||
) : null}
|
||||
<h2 className="text-sm font-semibold">{title}</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function OverviewView({
|
||||
onFund,
|
||||
onSend,
|
||||
}: {
|
||||
onFund: () => void;
|
||||
onSend: () => void;
|
||||
}) {
|
||||
const [seedRevealed, setSeedRevealed] = React.useState(false);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<PanelHeader title="Wallet" />
|
||||
<Badge data-testid="wallet-provider-badge" variant="outline">
|
||||
{MOCK_WALLET.providerName}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center gap-1 py-2">
|
||||
<p
|
||||
className="text-3xl font-bold tabular-nums tracking-tight"
|
||||
data-testid="wallet-panel-balance"
|
||||
>
|
||||
{formatBtc(MOCK_WALLET.balanceSats)}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Self-custodial · spans all your communities
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Button
|
||||
data-testid="wallet-fund-button"
|
||||
onClick={onFund}
|
||||
variant="outline"
|
||||
>
|
||||
<QrCode className="size-4" />
|
||||
Fund
|
||||
</Button>
|
||||
<Button data-testid="wallet-send-button" onClick={onSend}>
|
||||
<Send className="size-4" />
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div>
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Recent activity
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
className="max-h-64 divide-y divide-border/40 overflow-y-auto"
|
||||
data-testid="wallet-tx-history"
|
||||
>
|
||||
{MOCK_WALLET.transactions.map((tx) => (
|
||||
<TransactionRow key={tx.id} tx={tx} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Recovery phrase
|
||||
</h3>
|
||||
<Button
|
||||
data-testid="wallet-seed-toggle"
|
||||
onClick={() => setSeedRevealed((v) => !v)}
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
>
|
||||
{seedRevealed ? (
|
||||
<EyeOff className="size-3.5" />
|
||||
) : (
|
||||
<Eye className="size-3.5" />
|
||||
)}
|
||||
{seedRevealed ? "Hide" : "Reveal"}
|
||||
</Button>
|
||||
</div>
|
||||
{seedRevealed ? (
|
||||
<p
|
||||
className="rounded-lg border border-border/60 bg-muted/40 p-3 font-mono text-xs leading-relaxed"
|
||||
data-testid="wallet-seed-phrase"
|
||||
>
|
||||
{MOCK_WALLET.seedPhrase}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Hidden. Anyone with these 12 words can spend your bitcoin.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FundView({ onBack }: { onBack: () => void }) {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<PanelHeader onBack={onBack} title="Fund wallet" />
|
||||
<div className="mx-auto rounded-lg border border-border/70 bg-white p-3">
|
||||
<StyledQrCode
|
||||
data-testid="wallet-fund-qr"
|
||||
size={208}
|
||||
title="Fund wallet QR code"
|
||||
value={MOCK_WALLET.bip321Uri}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-center text-xs text-muted-foreground">
|
||||
Scan with any Lightning wallet. Encodes a reusable BOLT12 offer with a
|
||||
BOLT11 fallback (BIP-321).
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Button
|
||||
data-testid="wallet-copy-offer"
|
||||
onClick={() => {
|
||||
void navigator.clipboard.writeText(MOCK_WALLET.bolt12Offer);
|
||||
}}
|
||||
variant="outline"
|
||||
>
|
||||
<Copy className="size-4" />
|
||||
Copy offer
|
||||
</Button>
|
||||
<Button data-testid="wallet-refresh-offer" variant="outline">
|
||||
<RefreshCw className="size-4" />
|
||||
Refresh offer
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SendView({ onBack }: { onBack: () => void }) {
|
||||
const [destination, setDestination] = React.useState("");
|
||||
const [amount, setAmount] = React.useState("");
|
||||
const canSend = destination.trim().length > 0 && amount.trim().length > 0;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<PanelHeader onBack={onBack} title="Send bitcoin" />
|
||||
<div className="flex flex-col gap-2">
|
||||
<label
|
||||
className="text-xs font-medium text-muted-foreground"
|
||||
htmlFor="wallet-send-destination"
|
||||
>
|
||||
To — BOLT12 offer or BOLT11 invoice
|
||||
</label>
|
||||
<Input
|
||||
data-testid="wallet-send-destination"
|
||||
id="wallet-send-destination"
|
||||
onChange={(event) => setDestination(event.target.value)}
|
||||
placeholder="lno1… or lnbc1…"
|
||||
value={destination}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label
|
||||
className="text-xs font-medium text-muted-foreground"
|
||||
htmlFor="wallet-send-amount"
|
||||
>
|
||||
Amount (₿)
|
||||
</label>
|
||||
<Input
|
||||
data-testid="wallet-send-amount"
|
||||
id="wallet-send-amount"
|
||||
inputMode="numeric"
|
||||
onChange={(event) => setAmount(event.target.value)}
|
||||
placeholder="100"
|
||||
value={amount}
|
||||
/>
|
||||
</div>
|
||||
<Button data-testid="wallet-send-confirm" disabled={!canSend}>
|
||||
<Send className="size-4" />
|
||||
Send
|
||||
</Button>
|
||||
<p className="text-center text-xs text-muted-foreground">
|
||||
Prototype — no funds move.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function WalletPanel() {
|
||||
const [view, setView] = React.useState<WalletView>("overview");
|
||||
const backToOverview = React.useCallback(() => setView("overview"), []);
|
||||
|
||||
return (
|
||||
<div data-testid="wallet-panel">
|
||||
{view === "overview" ? (
|
||||
<OverviewView
|
||||
onFund={() => setView("fund")}
|
||||
onSend={() => setView("send")}
|
||||
/>
|
||||
) : view === "fund" ? (
|
||||
<FundView onBack={backToOverview} />
|
||||
) : (
|
||||
<SendView onBack={backToOverview} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { installMockBridge } from "../helpers/bridge";
|
||||
import { waitForAnimations } from "../helpers/animations";
|
||||
|
||||
const SHOTS = "test-results/wallet";
|
||||
|
||||
// Screenshot evidence for the Lightning wallet prototype: the top-chrome
|
||||
// balance chip and each wallet panel view (overview, fund QR, send form).
|
||||
// The `wallet` preview feature is seeded on by the mock bridge.
|
||||
|
||||
test.describe("wallet prototype screenshots", () => {
|
||||
test.use({ viewport: { width: 1280, height: 900 } });
|
||||
|
||||
async function openApp(page: import("@playwright/test").Page) {
|
||||
await installMockBridge(page);
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.getByTestId("channel-general").click();
|
||||
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
||||
}
|
||||
|
||||
test("01 balance chip in top-right chrome", async ({ page }) => {
|
||||
await openApp(page);
|
||||
|
||||
const chip = page.getByTestId("wallet-balance-chip");
|
||||
await expect(chip).toBeVisible();
|
||||
await expect(chip).toContainText("₿21,450");
|
||||
|
||||
await waitForAnimations(page);
|
||||
await page.screenshot({ path: `${SHOTS}/01-balance-chip.png` });
|
||||
});
|
||||
|
||||
test("02 wallet overview panel", async ({ page }) => {
|
||||
await openApp(page);
|
||||
|
||||
await page.getByTestId("wallet-balance-chip").click();
|
||||
const panel = page.getByTestId("wallet-panel");
|
||||
await expect(panel).toBeVisible();
|
||||
await expect(page.getByTestId("wallet-panel-balance")).toHaveText(
|
||||
"₿21,450",
|
||||
);
|
||||
await expect(page.getByTestId("wallet-provider-badge")).toHaveText("Lexe");
|
||||
await expect(page.getByTestId("wallet-tx-tx-01")).toBeVisible();
|
||||
|
||||
await waitForAnimations(page);
|
||||
await page.screenshot({ path: `${SHOTS}/02-wallet-overview.png` });
|
||||
});
|
||||
|
||||
test("03 seed phrase reveal", async ({ page }) => {
|
||||
await openApp(page);
|
||||
|
||||
await page.getByTestId("wallet-balance-chip").click();
|
||||
await expect(page.getByTestId("wallet-seed-phrase")).toHaveCount(0);
|
||||
await page.getByTestId("wallet-seed-toggle").click();
|
||||
await expect(page.getByTestId("wallet-seed-phrase")).toBeVisible();
|
||||
|
||||
await waitForAnimations(page);
|
||||
await page.screenshot({ path: `${SHOTS}/03-seed-reveal.png` });
|
||||
});
|
||||
|
||||
test("04 fund wallet QR", async ({ page }) => {
|
||||
await openApp(page);
|
||||
|
||||
await page.getByTestId("wallet-balance-chip").click();
|
||||
await page.getByTestId("wallet-fund-button").click();
|
||||
await expect(page.getByTestId("wallet-fund-qr")).toBeVisible();
|
||||
|
||||
await waitForAnimations(page);
|
||||
await page.screenshot({ path: `${SHOTS}/04-fund-qr.png` });
|
||||
});
|
||||
|
||||
test("05 send bitcoin form", async ({ page }) => {
|
||||
await openApp(page);
|
||||
|
||||
await page.getByTestId("wallet-balance-chip").click();
|
||||
await page.getByTestId("wallet-send-button").click();
|
||||
|
||||
const confirm = page.getByTestId("wallet-send-confirm");
|
||||
await expect(confirm).toBeDisabled();
|
||||
await page.getByTestId("wallet-send-destination").fill("lno1qgsq…example");
|
||||
await page.getByTestId("wallet-send-amount").fill("500");
|
||||
await expect(confirm).toBeEnabled();
|
||||
|
||||
await waitForAnimations(page);
|
||||
await page.screenshot({ path: `${SHOTS}/05-send-form.png` });
|
||||
});
|
||||
});
|
||||
@@ -40,6 +40,14 @@
|
||||
"platforms": [
|
||||
"desktop"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "wallet",
|
||||
"name": "Bitcoin Wallet",
|
||||
"description": "Self-custodial Lightning wallet prototype: balance in the top chrome, fund, send, and transaction history",
|
||||
"platforms": [
|
||||
"desktop"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user