Skip to content

feat: bolt card nfc payments and scannable pos, activity and detail screens - #97

Merged
pepakriz merged 11 commits into
mainfrom
settings-ux
Sep 25, 2026
Merged

pepakriz merged 11 commits into
mainfrom
settings-ux

Conversation

@pepakriz

@pepakriz pepakriz commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Bolt Card over NFC (Android)

  • The Lightning tab listens for a Bolt Card under the QR. A local NfcPlugin reads the card's lnurlw:// link in reader mode, so Android does not open a browser for the tag.
  • payPaymentWithBoltCard checks that the payment is still pending and has a Lightning invoice, then hands that invoice to the card's LNURL-withdraw service (LUD-03/LUD-17). It writes nothing: the Spark sync settles the payment the same way it does for a scanned QR. A retry cannot charge twice, because a Lightning invoice can only be paid once.
  • The NFC status is shown under the QR: ready, NFC off (with a button to its settings), reading, waiting for the payment, or an error. If the payment does not settle within 75 s, a timeout message asks staff to tap again.
  • The shared LNURL fetch moved to lnurl-client.ts, and its errors dropped the Pay prefix.

Earlier UX commits on this branch

  • Settings: shows current values, the list is regrouped, and search stays visible while lists load.
  • POS floor: one-tap tiles showing bill age, payment state and table totals, a summary with an occupied-tables filter, and an animated home mode switch.
  • Activity, payment detail and bill detail lead with what staff check, and payment rows show method icons.

Test plan

  • bun run check:ts, bun run check:tests (710 passing). check:lint flags only untracked SVGs outside the repo tree.
  • ./gradlew :app:compileDebugJavaWithJavac
  • Tap a real Bolt Card on an Android device: pays, times out, NFC off then turned on in settings
  • Check on the Switchio terminal that the NFC reader does not interfere with card payments

🤖 Generated with Claude Code

https://claude.ai/code/session_01BK6n6yMEXHuB1LsHBmX4Pa

pepakriz and others added 11 commits September 25, 2026 16:27
The whole list body suspended on the "any rows?" query, so the search
input vanished behind the skeleton until it resolved. Search state now
lives above the Suspense boundary and the fallback renders the input, so
only the rows show a loading state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BK6n6yMEXHuB1LsHBmX4Pa
…ment state

A free table now starts a bill wherever its tile is tapped, and a table
with a single bill opens it the same way — the common cases no longer
need aiming at a small button. The "+" stays above the tile for starting
a split check.

Each bill card also shows how long the bill has been open and flags a
payment in flight or a partial settlement, so staff can spot the table
waiting on them. openBillsQuery embeds each bill's payments and claims
so the floor view stays a single query.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BK6n6yMEXHuB1LsHBmX4Pa
On a busy floor most tiles are occupied, and solid-filled tiles stopped
telling them apart; occupied is now a tint with an accent ring, leaving
the solid accent for the payment badges that need attention. Tiles in a
row share a height, so a tall tile no longer leaves a gap beside it.

The table glyph repeated on every tile carried nothing; a free tile shows
a "+" in its place instead of a separate button. A table with several
bills shows its running total instead of the seat count, and bill cards
read "#3" with an item count rather than repeating "Bill #".

The single-bill tile's full-tile link is now aria-hidden and unfocusable,
only widening the bill card's own target, so a tap at the tile's centre
can no longer land on the "+" instead of the bill.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BK6n6yMEXHuB1LsHBmX4Pa
…ables

A line above the grid now sums what is open across the floor, and an
"occupied only" toggle, remembered per device, hides free tiles so a
large floor shrinks to the tables that need attention.

A venue without tables sees a pointer to the tables settings instead of
a lone tile with no explanation. The "no table" tile moves to the end:
where tables exist it is the exception, and without tables it is the only
tile anyway. Bill stats are now derived once per page and shared by the
summary and the tiles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BK6n6yMEXHuB1LsHBmX4Pa
The lone calculator/grid icon gave no hint that it switched views. Both
modes now sit in one segmented control, the active one labelled, and the
pill slides between them in a single shared motion layout so segments
of different widths never make it jump or overstretch. Reduced motion
switches instantly.

Regenerates the home and tables doc screenshots for the new header and
floor view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BK6n6yMEXHuB1LsHBmX4Pa
The payment method tabs now share the home switch's control: the pill
slides between methods and an inactive method shows only its icon. The
motion parts move into src/components/sliding-pill.tsx so both controls
animate identically, and both get wider side padding so an icon-only
segment no longer looks cramped.

Regenerates the home, tables and payment doc screenshots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BK6n6yMEXHuB1LsHBmX4Pa
Every payment row was titled just "Payment", with the amount in small
print, so staff had to open rows one by one to tell them apart. Rows now
lead with the bill label/number (or the payment's serial number), show
the table, tip and settling method icons, and put the amount on the
right in bold, struck through only when the money never arrived.

Day headings read "Today"/"Yesterday" and carry the day's settled total
per currency, left off the last group while more pages are pending so
it never undercounts. Issues render as warning badges with an inset
stripe on the row instead of a dot-joined text line.

Both tabs share the row pieces in activity-row.tsx so they stay
visually identical; the payment method labels move out of
payment-detail.tsx so the list and detail name methods the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BK6n6yMEXHuB1LsHBmX4Pa
The detail page opened on a raw UUID under a duplicated "Payment detail"
title, then a list of rows where half read "—", and each reconciliation
spelled out ten fields including hashes nobody reads at the counter.

The summary card now names the payment by its daily serial number and
shows amount, included tip, payment methods and cancellation up front.
Identifiers and bookkeeping timestamps move into collapsed "Technical
details" sections with copy buttons. The bill card links to the bill
detail and states "No table" instead of a dash. A pending payment gets a
primary "Back to payment" button with its expiry time, and the route
shows a skeleton instead of an empty page while loading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BK6n6yMEXHuB1LsHBmX4Pa
Same treatment the payment detail got: the bill detail opened on its raw
UUID, buried "Back to bill" among the rows as an outline button, and
showed "—" for a bill with no table.

The summary card now carries the status badge in its header with the
creation time, shows when the bill was closed or canceled under the total,
and states "No table" explicitly. "Back to bill" is the primary action on
top for an open bill, the bill and device ids move into the collapsed
technical details shared with the payment detail, and the route shows a
skeleton while loading.

The e2e status checks now match "Closed" exactly, since the text also
appears in the new "Closed {date}" line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BK6n6yMEXHuB1LsHBmX4Pa
The bill detail's payment rows gave only amount, time and status, so
telling a cash payment from a card one meant opening each. The activity
list already shows method icons; the bill detail now does too.

paymentsWithClaimsByBillIdQuery trades its COUNT for an embedded array of
the claimed transactions' kinds, the same move payment-history.tsx made.
The row takes its claim count from the array's length, which also drops
the bigint/string COUNT coercion that existed only for this query.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BK6n6yMEXHuB1LsHBmX4Pa
Customers with a Bolt Card can now tap it under the Lightning QR instead
of scanning. A local Android plugin reads the card's lnurlw:// link in
reader mode (so Android does not open a browser for the tag), and the
payment's invoice is handed to the card's LNURL-withdraw service
(LUD-03/LUD-17). Settlement is still observed by the Spark sync, so the
flow writes nothing and a retry cannot charge twice.

Android only: iOS reads tags only behind a user-opened system sheet,
which cannot wait under a QR. The shared LNURL fetch moved into
lnurl-client.ts so the pay and withdraw clients share it; its errors
lost the Pay prefix accordingly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BK6n6yMEXHuB1LsHBmX4Pa
@vercel

vercel Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
payky Ready Ready Preview Sep 25, 2026 6:32pm UTC

@pepakriz
pepakriz merged commit 314482a into main Sep 25, 2026
5 checks passed
@pepakriz
pepakriz deleted the settings-ux branch September 25, 2026 18:32

This branch was successfully deployed

1 active deployment
Preview — 556c4ce9 Deployed Sep 25, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant