Skip to content

web: subscription UI#407

Open
Jared-dz wants to merge 12 commits into
mainfrom
subscription-ui
Open

web: subscription UI#407
Jared-dz wants to merge 12 commits into
mainfrom
subscription-ui

Conversation

@Jared-dz

@Jared-dz Jared-dz commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Summary of Changes

Builds the self-serve shreds subscribe flow on top of the basic 3-step form on main. End-state at /dz/shreds/pay:

  • Map-first landing. A maplibre world map with per-metro pins (orange = seats free, grey = full) joined to fetchMetros lat/lng. Clicking a pin opens a sticky right drawer listing the devices in that metro with [Select →] buttons; ?metro=<code> mirrors drawer state in the URL.
  • Linear 4-step wizard (Device → Server IP → Fund → Confirm) entered when ?device=<code> is set. Sticky receipt panel on the right with live total and a Pay button that mirrors the Step 4 Subscribe handler — single transaction path.
  • Epoch presets (1 / 4 / 15 / 90) drive the existing USDC amount field (amount = N × price); custom amounts still work. Live epoch counter and end-of-epoch warning preserved.
  • Latency-based device recommendation. Pastable doublezero latency --json output (with copy command and target-server guidance) ranks devices, badges the top match , and surfaces it on the map pin and in the drawer rows. Available in both Map and List views.
  • Guest vs Login lanes. Sign-in itself works via existing useAuth() / UserPopover; Login-exclusive features (auto-renew, email receipt, card/invoice payment, account-tied subscription history, multi-seat / CSV IPs) render as faded DisabledFeatureCard stubs labeled either "Backend not yet supported" or "Coming with Login flow." Lane banner switches copy based on isAuthenticated; receipt panel header reflects the lane.
  • Dev-only simulate mode via ?simulate=true — amber banner, simulate button, "Simulation passed" state. Simulation error display now surfaces Program log lines instead of generic Anchor codes.
  • Deep link from /dz/devices row click still lands directly in the wizard with Step 1 pre-completed.
  • RPC endpoint read from VITE_SOLANA_RPC_URL (falls back to mainnet-beta).

Net effect: a buyer can land on the page cold, pick a region geographically (or paste latency JSON for precision), see exactly what they'll pay, and submit a USDC subscribe transaction — all without leaving the page. The on-chain instruction set is unchanged.

Diff Breakdown

Category Files Lines (+/-) Net
Core logic 11 +1707 / -426 +1281
Scaffolding 6 +51 / -6 +45

UI-only against the existing on-chain subscribe path; no Go handlers, no API, no on-chain instruction changes.

Key files (click to expand)
  • web/src/components/shreds-subscribe-page.tsx (+615 / -426) — page evolves across the branch from a 3-step form into a thin orchestrator that owns URL state, data fetching, and transaction handlers, then routes to either MapLanding or Wizard based on ?device=.
  • web/src/components/shreds/wizard.tsx (+530, new) — 4-step wizard: step pills, device summary with "Change device", IP input, epoch presets, USDC amount input + balance, on-chain seat-exists notice, epoch progress, disabled stubs (multi-seat, CSV, auto-renew, card/invoice, email receipt), and the wallet-connect / Subscribe / TransactionProgress block.
  • web/src/components/shreds/map-landing.tsx (+503, new) — maplibre map joined to fetchMetros, per-metro pin colour by seat availability, sticky drawer with device rows, Map/List toggle, doublezero latency --json paste expander inside the drawer.
  • web/src/components/shreds/device-picker.tsx (+140, new) — searchable device table with latency column + Recommended / Next-best badges; powers the Map landing's List toggle.
  • web/src/components/shreds/receipt-panel.tsx (+122, new) — sticky lane-aware receipt; Pay button shares the wizard's Subscribe handler so both buttons trigger the same on-chain path.
  • web/src/hooks/use-shred-transaction.ts (+55 / -1) — adds the simulate path used by ?simulate=true, surfaces Program log lines in error state.
  • web/src/components/shreds/transaction-progress.tsx (+57, new) — TransactionProgress + StatusStep (extracted, verbatim).
  • web/src/components/shreds/epoch-progress.tsx (+55, new) — EpochProgress + formatEta + EpochWarning (extracted, verbatim).
  • web/src/hooks/use-epoch-info.ts (+38, new) — polls slot progress + remaining time; powers the live epoch counter.
  • web/src/components/shreds/disabled-feature-card.tsx (+25, new) — reusable disclaimer wrapper (opacity-60, dashed border, AlertCircle, reason copy).
  • web/src/components/shreds/types.ts (+19, new) — shared LatencyEntry / DeviceLatency shapes.

Testing Verification

  • Open /dz/shreds/pay — map renders with per-metro pins, orange where seats are free, grey where full; legend visible; UserPopover shows "Sign in" while logged out; lane banner reads guest copy.
  • Click an active pin → right drawer opens listing devices in that metro with Select → buttons; URL gains ?metro=<code>; back button closes the drawer.
  • Expand "Run a latency check" in the drawer, paste a doublezero latency --json array → top-ranked reachable device floats to the top of the drawer and gets the ⚡ badge on its metro pin.
  • Click List toggle → searchable device table with Recommended / Next best badges (when latency JSON pasted); selecting a row navigates to the wizard with ?device=<code>.
  • Open /dz/shreds/pay?device=<code> directly (the existing /dz/devices deep link) → lands straight in the wizard with Step 1 marked done.
  • In wizard Step 3, click [4] → amount field auto-populates to 4 × price; receipt panel total updates live. Custom-typed amounts also work; below-min and insufficient-balance error states still surface; live epoch counter + end-of-epoch warning render.
  • Every DisabledFeatureCard is non-interactive and shows the correct copy variant ("Backend not yet supported" vs "Coming with Login flow").
  • Connect Phantom / use simulate mode, click either the receipt Pay button or the Step 4 Subscribe button → same handler runs; TransactionProgress advances signing → sending → confirming → confirmed with the Solscan link.
  • ?simulate=true preserves the amber banner, Simulate button, and "Simulation passed" state. A failing simulation surfaces the Program log lines from the on-chain trace.
  • Sign in via Google through the UserPopover → lane banner switches to "Signed in as <email>"; receipt panel header switches from "Guest checkout" badge to the email; Login-exclusive stubs now read "Coming with Login flow." Log out → returns to guest state.
  • Dark mode toggle: map tiles flip (CARTO dark vs light), pins, disclaimers, and step pills all render correctly.

@github-actions

github-actions Bot commented Apr 9, 2026

Copy link
Copy Markdown

🔗 Preview: https://pr-407.data.malbeclabs.com

Jared-dz added 3 commits April 9, 2026 14:01
Run `doublezero latency --json` on the subscribing server, paste the
output into Step 1 to get a ranked device table with Recommended/Next
best badges and latency column. IP input and subscription flow unchanged
for users without the CLI.
- Read VITE_SOLANA_RPC_URL from env (falls back to mainnet-beta)
- Surface Program log lines in simulation errors instead of generic Anchor codes
- Fix latency command display and decimal precision
@ben-dz

ben-dz commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

I went to take a look at this, but it's based off a pretty stale main. Can you rebase it?

Replace the single-screen subscribe form with a map landing (per-metro pins
via maplibre) that drops into a 4-step linear wizard (Device, Server IP,
Fund, Confirm) once a device is picked. URL drives the stage: ?device=
opens the wizard, ?metro= pre-opens the drawer.

Surfaces two checkout lanes — Guest (functional) and Login (placeholder).
Sign-in itself works via existing useAuth/UserPopover; Login-exclusive
features (auto-renew, email receipt, card/invoice payment, subscription
history, multi-seat) render as faded DisabledFeatureCards with either
'Backend not yet supported' or 'Coming with Login flow' disclaimers.

On-chain subscribe path, transaction hooks, simulate mode, and the
existing /dz/devices?device= deep link are preserved verbatim.
@Jared-dz Jared-dz changed the title web: subscription UI web: rework shreds subscribe page as map landing + linear wizard May 12, 2026
@Jared-dz Jared-dz changed the title web: rework shreds subscribe page as map landing + linear wizard web: subscription UI May 12, 2026
@Jared-dz Jared-dz added preview and removed preview labels May 12, 2026
Jared-dz added 2 commits May 12, 2026 13:45
# Conflicts:
#	web/src/App.tsx
#	web/src/components/sidebar.tsx
@Jared-dz Jared-dz added preview and removed preview labels May 12, 2026
Jared-dz added 5 commits May 12, 2026 16:44
Lazy-loads a minimal three.js globe (via react-globe.gl) for the shreds
picker landing, with a WebGL availability check that falls back to the
existing 2D MapLibre map. Keeps three.js out of the main bundle for
list-view users and unsupported browsers.
Search input on the map/list toggle row lets users type a metro code or
name and pick from a ranked dropdown (seats-free desc) instead of
hunting on the globe. Keyboard navigable (arrows / Enter / Esc),
selection opens the existing per-metro devices drawer.
Adds a Subscribe | Withdraw tab control to /dz/shreds/pay (URL-driven
via ?mode=withdraw). The withdraw flow mirrors the subscribe wizard's
step layout: pick a seat from the connected wallet's funded seats, then
confirm with a tenure-loss warning and refund summary before signing.

Reuses buildUnsubscribeInstructions and the seat/escrow on-chain state
hook; no backend or program changes. Section and StepBar are extracted
to wizard-shared so both wizards share the same chrome.

Includes an internal-users-only preview mode (?preview=true) that
injects sample seats and a mocked tx state machine so the workflow can
be demoed without a wallet that holds real seats. Also drops the
unimplemented "Auto-renew" placeholder card from the subscribe wizard.
Adds /account/subscriptions, a logged-in user view of their shred seats
with stat strip, filter toolbar, bulk-select bar, detail drawer
(overview / IPs / activity / receipts / CLI tabs), and deposit/withdraw
flows. Reuses fetchShredClientSeats with funder filter; the drawer's
activity tab uses the seat: filter on escrow events.

Adds a preview mode for internal users (?preview=true gated on
is_internal_user) that swaps in eight fixture seats covering active /
low / pending / expired, fake escrow events per seat, and a shared
useMockedShredTransaction hook so deposit/withdraw/bulk-deposit modals
run the full sign-send-confirm sequence with no wallet calls. The
withdraw wizard's local mocked hook is replaced by the shared one.

The shred-fund-modal also gains preset amount chips (1 / 4 / 15 / 90
epochs). My Subscriptions is reachable from the user popover and from
the Manage tab on /dz/shreds/pay.
The lane banner now shows a clickable Sign in button when signed out,
and is rendered on the Withdraw mode in addition to Subscribe. A third
Manage tab appears beside Subscribe / Withdraw when the user is signed
in and links to /account/subscriptions.

Also fixes a rules-of-hooks crash where useState(showLoginModal) was
called after the pricingLoading / pricingError early returns, causing
the page to error out on first load.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants