feat: share an invoice by link or QR code, and import it - #209
Draft
Atharva0506 wants to merge 8 commits into
Draft
feat: share an invoice by link or QR code, and import it#209Atharva0506 wants to merge 8 commits into
Atharva0506 wants to merge 8 commits into
Conversation
The dashboard nested four levels of horizontal padding and then applied an inner max-w-7xl on top of a fixed sidebar, which left a wide dead gutter on the right and pushed the invoice items well below the fold. - Add src/utils/layout.js holding the page container, card, header and section-gap classes so the dashboard rhythm is tuned in one place instead of per-page magic classes. - Drop the inner max-width from every dashboard page and trim the padding and section margins across Create Invoice, Create Multiple Invoices, Sent, Received, Request Invoices and Settings. - Replace the mobile MUI Select with a hamburger that opens a slide-out Drawer. The nav list is extracted into DashboardNav and rendered by both the desktop rail and the drawer, so the two cannot drift apart. The hamburger doubles as the current-section label, and navigating closes the drawer instead of leaving the overlay parked over the new page. - Fix the fixed-navbar offset: the navbar is h-24 but the main padding was pt-20, so content sat 16px underneath and only cleared it thanks to an unrelated mt-4 on the dashboard heading.
Removing the inner max-width fixed the dead right gutter but left the page uncapped, which read as stretched. The cause turned out to be alignment rather than width: the invoice meta bar stopped well short of the items table below it, so the right edge was ragged. - Share one gutter (SHELL) between the navbar and every page shell, so the logo, the wallet button and the page content sit on the same left and right edges. Treasure adopts it too. - Give the meta bar, the Client/Payment row, the items table and the total row the same left and right edges, so the layout reads as deliberate at any width. Cards in the row stretch to equal height. - Collapse the desktop rail to a 68px icon-only strip with tooltips; the choice persists in localStorage, read synchronously so the rail does not render expanded and then snap shut. - Replace the sidebar greeting's empty space with the connected address and network, keeping the greeting above it. - Cap the invoice total at a min-width instead of a third of the row, where the label and amount drifted apart on a wide screen. - Settings: let each card own its heading rather than repeating the same title in the section above it.
The sidebar header stacked three lines — greeting, address, network — and the address and network were already shown in the navbar, so two of them were duplication. Drop them and pair "Welcome Back!" with the collapse toggle on a single row, which reclaims about 40px above the nav items.
Addresses CodeRabbit review on StabilityNexus#205. - The drawer was hidden by MUI's lg (1200px) while the hamburger and rail use Tailwind's (1024px), so between the two the drawer stayed mounted with nothing visible to close it. It is now keyed to 1024px like the rest, and closes when the viewport crosses that width so the modal cannot leave the body scroll locked. The hamburger gets aria-controls for the drawer. - toggleRail wrote to localStorage inside the state updater. Updaters are expected to be pure and React may call them more than once per dispatch, so the write now happens before setState. - SHELL dropped its max-width earlier but the comments still described a width cap, and mx-auto had nothing to centre once w-full was the only width. Reworded and removed.
Relay delivery cannot start until the recipient has registered a messaging key on-chain. Until they do, the invoice exists on-chain but its payload has nowhere to go and the recipient sees only the on-chain stub. A share token carries the payload itself, so it works with no key registered, and with the relay down or the message expired. The token is deliberately not encrypted. The only key that could travel with a self-contained link is one embedded in the same link, which anybody holding the link also holds — that is obfuscation, not confidentiality, and calling it encryption would misrepresent it. Integrity is not left to the token either. The payload was committed on-chain as invoiceDataHash when the invoice was created, so the importer recomputes that hash and compares it against the chain. That is strictly stronger than any signature the token could carry about itself, and it needs no wallet: every supported chain ships a public RPC URL.
Adds a Share entry to the drawer's export menu, alongside PDF, CSV and JSON. The dialog reads the payload from IndexedDB rather than taking it from the invoice object the page is rendering. Those are not the same thing: the list pages enrich the stored payload with token logos, decimals and status before rendering, and any of those additions would change the hash and make the token unverifiable on the recipient's side. Length stops mattering because the raw URL is never the interface — Copy, the OS share sheet, a QR code and a file are, and the link itself shows only middle-truncated. A typical invoice lands around 550 characters; the dialog falls back to the file when one grows past what a QR code or a chat client will carry. Outline buttons carry an explicit text colour here: the variant sets a background but no foreground, so on these white cards they would otherwise inherit the dark shell's white and render invisible.
A tapped link or scanned QR lands on /dashboard/import with the token in the hash fragment, which is never sent to a server — so the invoice details stay out of access logs, CDN caches and Referer headers. The page works before the wallet connects: decoding needs no wallet and verification reads the chain over a public RPC, so someone opening a link sees the real invoice immediately rather than a connect prompt in front of a blank page. Connecting is needed only to save it. Nothing is saved on the strength of the link alone. The payload is trusted only once its hash matches the on-chain commitment, and it is then written to the same IndexedDB store the relay writes to — so the existing list pages pick it up with no changes. Importing also recovers a sender's own invoice on a new device, since the payload has only ever lived in local storage.
…drawer Two problems, both reported from testing. Sharing was hidden inside the Export Invoice menu. It is not an export — it is how you get the invoice to someone — and burying it there cost it its discoverability. It now has a share icon in each row's actions, next to resend and view, and a Share button of its own in the drawer footer. Both appear only when the payload is on this device, matching resend: without it there is nothing to put in a link. The dialog was also unreachable. The shadcn dialog and the MUI SwipeableDrawer are separate portal stacks — the drawer sits at z-index 1200 and the dialog at 50 — so the drawer painted over the dialog and clipped its right-hand buttons, Share among them. Rebuilding the dialog on MUI's Dialog puts both under one modal manager, which stacks them correctly, and matches the drawer and confirm dialog already on this page. Verified with hit-testing: every button in the dialog is now the topmost element at its own centre.
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Contributor
|
✅ Build successful! Build Size MetricsWorkflow run · commit db53969 |
Atharva0506
force-pushed
the
feat/invoice-share-link-qr
branch
from
September 4, 2026 17:37
cfdb4b1 to
db53969
Compare
Contributor
🔍 PR Preview
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addressed Issues:
issue number: #206
Relay delivery cannot start until the recipient has registered a messaging key on-chain. Until they do, the invoice exists on-chain but its payload has nowhere to go, and the recipient sees only the on-chain stub — no names, no line items, no dates. This adds a share link, QR code and file that carry the payload themselves, so sharing works with no key registered and with the relay down.
Screenshots/Recordings:
freecompress-qr-link-chainvoice.mp4
Additional Notes:
invoiceDataHashat creation, so the importer recomputes that hash and compares — the same checkstoreRelayInvoicealready makes. Stronger than a signature the token carries about itself, and a tampered link is refused.Refererheader.navigator.share, a QR code and a.cvinvfile are — and the dialog falls back to the file past what a QR or chat client will carry.fflate(~8 KB) andqrcode, which was already in the tree transitively via RainbowKit.26 new tests covering the round trip, hash preservation, tamper and truncation rejection, version rejection and both verification branches. 185 total pass; lint and build clean.
Left as follow-ups to keep this reviewable: offering the link automatically on the "client hasn't registered" branch in
CreateInvoice, forwarding fromReceivedInvoice, a wallet-free public read-only view, relay-backed short links.Builds on #205. Draft until #205 is reviewed and merged, then rebased onto
main.AI Usage Disclosure:
Check one of the checkboxes below:
I have used the following AI models and tools: Claude Code (CLI), model Claude Opus 5
Checklist