Skip to content

Hosting Earnings UX Improvement#772

Merged
jhweir merged 10 commits into
devfrom
fix/hosting-earnings-ux
Mar 25, 2026
Merged

Hosting Earnings UX Improvement#772
jhweir merged 10 commits into
devfrom
fix/hosting-earnings-ux

Conversation

@jhweir
Copy link
Copy Markdown
Contributor

@jhweir jhweir commented Mar 23, 2026

Hosting Earnings UX Improvement

Summary

When a host enables "Paid Hosting via wHOT" before completing host index registration, they see a confusing technical error:

Balance: Failed to get wHOT wallet balance: No membrane proof stored — call setUnytMembraneProof first

This PR replaces that with clear, contextual messages that guide the host through each step of the registration → earnings flow.

Problem

The wHOT/Unyt payment system requires a membrane proof issued during host registration. Without it, the Wallet component surfaces raw internal error strings that mean nothing to users and offer no guidance on how to resolve the issue.

Changes

1. Gate Earnings section on registration state and profile completeness (Hosting.tsx)

The Earnings/Wallet area now shows contextual messages based on setup progress:

  • No session → "Complete Host Index Registration in the Settings tab below..."
  • Email verification pending (regStep === "verify") → "Check your inbox or spam folder for the verification email..."
  • Membrane proof pending or profile incomplete (no host name) → "Fill in your host name and details..." or "Complete your hosting profile..."
  • All complete → Renders the full <Wallet /> component

This means the Wallet component is never rendered until the membrane proof is confirmed and the host has filled in at least a name.

2. Simplify Wallet.tsx

  • Removed needsRegistration state, isMembraneProofError() helper, and sawMembraneError tracking
  • Wallet.tsx no longer needs to detect or handle membrane proof errors — it's simply not rendered until ready
  • Wallet always renders its full content (balance, pubkey, withdraw, history)

3. Reorder settings sections and improve onboarding copy (Hosting.tsx)

  • Reorder Settings tab: TLS → SMTP → Host Index Registration (was: Registration → TLS → SMTP)
  • Rename "Hosting Profile" → "Host Index Registration" to clearly indicate it's required for paid hosting
  • Update copy in the paid hosting toggle and registration form to explain the full flow

Files Changed

File Description
ui/src/components/Hosting.tsx Multi-state wallet gate, section reorder, copy updates
ui/src/components/Wallet.tsx Remove registration detection logic, simplify

Summary by CodeRabbit

  • New Features

    • Automatic detection and background polling for the required runtime component, with timeout/failure handling.
    • Wallet visibility gated to show only after hosting setup and runtime component confirmation.
  • UI/UX Improvements

    • Hosting page shows conditional status cards during setup; Wallet appears afterward.
    • Updated hosting setup messaging directing users to Settings; Settings sections reordered.
    • Smaller loading spinners in the wallet UI.
  • Bug Fixes

    • Improved wallet history error handling and removed debug logging.

jhweir added 7 commits March 23, 2026 18:43
…allet

When the wHOT wallet fails due to missing membrane proof (host not
registered), show a clear 'Registration required' message instead of
the internal error string 'No membrane proof stored — call
setUnytMembraneProof first'.

Hide balance, pubkey, and withdraw sections entirely until registration
is complete, rather than showing error strings as field values.
When a host has not yet registered with the hosting index, show a
clear call-to-action prompting them to register instead of rendering
the Wallet component (which would show membrane proof errors).

The CTA links directly to the Hosting Profile registration section.
Show a step-by-step progress indicator when setup is incomplete:
1. Register with hosting index (✅ once logged in)
2. Membrane proof received (⏳ fetching / ✅ done / ❌ error + retry)
3. Unyt DNA installed (⏳ installing / ✅ done)

The indicator auto-hides once all steps are complete. Also track
Unyt DNA installation status with periodic polling after membrane
proof is stored.
Reorder Settings tab sections so infrastructure setup comes first:
1. TLS Configuration
2. Email Configuration (SMTP)
3. Host Index Registration (renamed from 'Hosting Profile')

Rename 'Hosting Profile' to 'Host Index Registration' to make it
clear this step is required for paid hosting earnings.

Update onboarding copy in the paid hosting toggle description and
registration form to explain the full registration → earnings flow.
The step-by-step progress UI (register / membrane proof / DNA install)
was causing layout issues — it pushed the registration form's code
entry field off screen during email verification. Since the membrane
proof and DNA install happen automatically in seconds with no user
action needed, showing their status adds noise without value.

The core UX improvements remain: friendly error messages, gated
earnings section, and clear registration prompt.
- Show contextual messages in Earnings section based on current state:
  - No session: prompt to complete Host Index Registration
  - Email verification pending: prompt to check inbox
  - Membrane proof pending or profile incomplete: prompt to fill in details
  - All complete: render Wallet component
- Remove needsRegistration/isMembraneProofError logic from Wallet.tsx
  (now unnecessary since Hosting.tsx gates rendering)
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 23, 2026

📝 Walkthrough

Walkthrough

Adds Unyt DNA install tracking and polling to Hosting: marks membrane-proof done when Unyt is present, polls runtime for Unyt installation after proof completion, and gates Wallet rendering on membrane-proof/host registration/Unyt state; minor Wallet error-handling and spinner-size tweaks; reordered Settings sections.

Changes

Cohort / File(s) Summary
Hosting — logic, polling & UI gating
ui/src/components/Hosting.tsx
Added unytDnaInstalled and unytInstallFail state; when membrane-proof fetch detects Unyt sets membraneProofStatus="done" and unytDnaInstalled=true; added effect that polls client.runtime.unytVersionInfo() every 5s (stop on install or timeout); gated paid-hosting Wallet rendering on membrane proof, host registration, and Unyt flags; updated explanatory text and reordered Settings sections (TLS/Email before Host Index Registration).
Wallet — UI & error handling
ui/src/components/Wallet.tsx
Introduced inner try/catch to separate fetch vs parse errors for wallet history; removed debug console.log, kept error reporting; reduced Earnings/Balance spinner size from sm to xs; wrapped main UI sections in React fragments.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Hosting as Hosting UI
    participant Runtime as client.runtime
    participant Wallet as Wallet UI

    User->>Hosting: Open Hosting page / enable paid hosting
    Hosting->>Hosting: check membraneProofStatus
    alt membrane proof already satisfied
        Hosting-->>Hosting: set membraneProofStatus = "done"\nset unytDnaInstalled = true
    else after membraneProofStatus becomes "done"
        loop every 5s (up to ~60 attempts)
            Hosting->>Runtime: unytVersionInfo()
            Runtime-->>Hosting: { installed: true|false, ... }
            alt installed == true
                Hosting-->>Hosting: set unytDnaInstalled = true\nstop polling
            end
        end
        alt polling times out or errors
            Hosting-->>Hosting: set unytInstallFail = true\nstop polling
        end
    end
    Hosting->>Wallet: render Wallet only if gating satisfied
    Wallet-->>User: show Wallet UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through hosting, sniffed the runtime ground,
I polled for DNA until the sign was found,
TLS and mail now leap ahead,
Spinners shrink while histories mend,
Wallet waits till rabbit drums the sound.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Hosting Earnings UX Improvement' directly reflects the main objective of the PR: replacing confusing error strings with clear user-facing guidance and gating Wallet rendering until registration prerequisites are met. It accurately summarizes the primary change.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/hosting-earnings-ux

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ui/src/components/Hosting.tsx`:
- Around line 1281-1313: The current conditional collapses membraneProofStatus
=== "error" into the non-ready UI and mounts <Wallet /> as soon as
membraneProofStatus === "done" regardless of unytDnaInstalled, which leaves
users stuck on proof failure and allows Wallet to render before DNA is
installed; update the branching around membraneProofStatus and hostReg.name so
that: 1) membraneProofStatus === "error" renders an explicit error message with
a visible retry action (reuse the existing retry handler/UI from the disabled
block) instead of the generic prompt, and 2) the successful branch only mounts
<Wallet /> when membraneProofStatus === "done" AND unytDnaInstalled is true
(otherwise show a “DNA installing” / progress message); reference
membraneProofStatus, hostReg.name, unytDnaInstalled, and Wallet when making the
changes.

In `@ui/src/components/Wallet.tsx`:
- Around line 153-159: Remove the sensitive console logging in the Wallet
component: eliminate the two console.log calls that print the raw and parsed
wallet history returned by client.runtime.unytWalletHistory (the "Wallet history
raw:" and "Wallet history parsed:" logs) so transaction metadata is not leaked;
keep the JSON.parse and setHistory(parsed) flow but do not log the payload (or
replace with a non-sensitive debug flagged/log level if absolutely needed),
locating these calls near the unytWalletHistory call in Wallet.tsx.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 430f4d3d-5695-45f1-a36a-8889b4133a81

📥 Commits

Reviewing files that changed from the base of the PR and between 19d3dd0 and 05b5fbc.

📒 Files selected for processing (2)
  • ui/src/components/Hosting.tsx
  • ui/src/components/Wallet.tsx

Comment thread ui/src/components/Hosting.tsx Outdated
Comment thread ui/src/components/Wallet.tsx
…tall

- Hosting: add explicit error branch for membraneProofStatus === 'error'
  with visible error message and retry button
- Hosting: gate Wallet mount on unytDnaInstalled, show progress spinner
  while DNA is installing
- Wallet: remove console.log calls that leaked raw wallet history data
lucksus
lucksus previously approved these changes Mar 25, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ui/src/components/Hosting.tsx`:
- Around line 1326-1356: The Earnings card currently renders the raw
membraneProofError when membraneProofStatus === "error"; change this to avoid
exposing backend/internal error strings by replacing the direct render of
membraneProofError with a sanitized user-facing message (e.g., "Failed to
connect to Unyt network" or the output of a new helper like
getFriendlyMembraneError(membraneProofError)). Update the JSX around
membraneProofStatus / membraneProofError in the Hosting component so the UI
shows only the generic message and keep the original error for diagnostics only
(log it to console or send to telemetry inside fetchMembraneProof or a sanitizer
function), and keep the Retry button behavior (onClick -> hostSession &&
fetchMembraneProof(hostSession)) unchanged.
- Around line 1357-1412: The Wallet is being mounted based on the ephemeral
hostReg.name (which becomes truthy on the first keystroke); change the gate so
Wallet only renders when the profile has been persisted (i.e., after
handleUpdateHost completes). Concretely, introduce or use a persisted flag
(e.g., hostRegPersisted, hostReg.id, or a boolean savedHost) that is set when
handleUpdateHost successfully saves the host, and replace checks of hostReg.name
in the ternary (with membraneProofStatus) so the Wallet block only shows when
membraneProofStatus === "done" && persistedFlag is true; keep Wallet key and
existing styling unchanged. Ensure handleUpdateHost sets that persisted flag on
success and any drafts do not flip it.
- Around line 953-969: The polling useEffect (checking
client.runtime.unytVersionInfo inside the anonymous check function) currently
swallows all errors and can poll forever; modify it to detect failure/timeout by
adding a retry/failure counter or timeout state (e.g., unytInstallFail or
unytInstallAttempts) inside the effect, incrementing on catch, and when attempts
exceed a threshold or timeout is reached stop the interval and set a failure
flag (and clearInterval and set cancelled) so the UI can show the retry path;
update the check function and the cleanup return to ensure the interval is
cleared on failure and on unmount, and use the existing names (client,
membraneProofStatus, unytDnaInstalled, check, cancelled) so the change is
localized.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d1eedba9-ee50-4a15-90f8-2bda9d3f2740

📥 Commits

Reviewing files that changed from the base of the PR and between ffe3c24 and 8f8906b.

📒 Files selected for processing (1)
  • ui/src/components/Hosting.tsx

Comment thread ui/src/components/Hosting.tsx
Comment thread ui/src/components/Hosting.tsx
Comment thread ui/src/components/Hosting.tsx Outdated
- Add retry/timeout logic to Unyt DNA polling useEffect (max 60 attempts)
- Sanitize membrane proof error message shown to users
- Gate Wallet render on persisted hostData instead of ephemeral hostReg.name
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
ui/src/components/Hosting.tsx (2)

954-979: ⚠️ Potential issue | 🟠 Major

Timeout/retry flow can stall indefinitely in “Installing Unyt DNA...”

Line 968 increments attempts only inside catch, so repeated { installed: false } responses never hit timeout. Also, the Retry handler on Line 1408 toggles unytInstallFail, but the effect doesn’t depend on it, so polling won’t restart.

Suggested fix
 useEffect(() => {
-  if (!client || membraneProofStatus !== "done" || unytDnaInstalled) return;
+  if (!client || membraneProofStatus !== "done" || unytDnaInstalled || unytInstallFail) return;
   let cancelled = false;
   let attempts = 0;
   const MAX_ATTEMPTS = 60; // ~5 minutes at 5s intervals
   const check = async () => {
+    attempts++;
     try {
       const vi = await client.runtime.unytVersionInfo();
       if (vi) {
         const info = JSON.parse(vi);
         if (info.installed && !cancelled) setUnytDnaInstalled(true);
       }
-    } catch {
-      attempts++;
-      if (attempts >= MAX_ATTEMPTS && !cancelled) {
-        cancelled = true;
-        clearInterval(interval);
-        setUnytInstallFail(true);
-      }
-    }
+    } catch {
+      // continue polling until max attempts
+    }
+    if (attempts >= MAX_ATTEMPTS && !cancelled) {
+      cancelled = true;
+      clearInterval(interval);
+      setUnytInstallFail(true);
+    }
   };
   check();
   const interval = setInterval(check, 5000);
   return () => { cancelled = true; clearInterval(interval); };
-}, [client, membraneProofStatus, unytDnaInstalled]);
+}, [client, membraneProofStatus, unytDnaInstalled, unytInstallFail]);

Also applies to: 1408-1410

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/components/Hosting.tsx` around lines 954 - 979, The polling useEffect
that calls client.runtime.unytVersionInfo (inside the useEffect block around the
check function) never increments attempts when the call succeeds but returns
{installed: false}, so the MAX_ATTEMPTS timeout can stall; update the logic to
increment attempts on each non-installed response (i.e., when vi parses but
info.installed is false) and ensure attempts/cancelled are reset when starting a
new poll; also add the unytInstallFail state (and any retry-triggering state) to
the useEffect dependency array so toggling setUnytInstallFail restarts polling,
and ensure the interval is cleared and recreated correctly with the new
dependencies (references: attempts, MAX_ATTEMPTS, interval, check,
unytDnaInstalled, setUnytInstallFail).

1367-1385: ⚠️ Potential issue | 🟠 Major

Wallet gate should require saved host name, not just hostData object presence

At Line 1367, !hostData is too weak. A saved profile object with empty name still passes and allows <Wallet /> to mount, even though the UI copy says host details must be filled first.

Suggested fix
+const hasSavedHostName = Boolean((hostData?.name ?? "").trim());

-              ) : membraneProofStatus !== "done" || !hostData ? (
+              ) : membraneProofStatus !== "done" || !hasSavedHostName ? (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/src/components/Hosting.tsx` around lines 1367 - 1385, The guard that
allows the <Wallet /> to mount is using a weak check (!hostData); update the
condition to require a non-empty saved host name by checking hostData?.name
(e.g., !hostData?.name || hostData.name.trim() === "") instead of just
!hostData, and use that same stronger check in the JSX branch that shows the
Host Index Registration message so the wallet only activates when
membraneProofStatus === "done" AND hostData.name is present; locate references
to membraneProofStatus and hostData in this component (the ternary controlling
the earnings/Wallet rendering) and replace the condition accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@ui/src/components/Hosting.tsx`:
- Around line 954-979: The polling useEffect that calls
client.runtime.unytVersionInfo (inside the useEffect block around the check
function) never increments attempts when the call succeeds but returns
{installed: false}, so the MAX_ATTEMPTS timeout can stall; update the logic to
increment attempts on each non-installed response (i.e., when vi parses but
info.installed is false) and ensure attempts/cancelled are reset when starting a
new poll; also add the unytInstallFail state (and any retry-triggering state) to
the useEffect dependency array so toggling setUnytInstallFail restarts polling,
and ensure the interval is cleared and recreated correctly with the new
dependencies (references: attempts, MAX_ATTEMPTS, interval, check,
unytDnaInstalled, setUnytInstallFail).
- Around line 1367-1385: The guard that allows the <Wallet /> to mount is using
a weak check (!hostData); update the condition to require a non-empty saved host
name by checking hostData?.name (e.g., !hostData?.name || hostData.name.trim()
=== "") instead of just !hostData, and use that same stronger check in the JSX
branch that shows the Host Index Registration message so the wallet only
activates when membraneProofStatus === "done" AND hostData.name is present;
locate references to membraneProofStatus and hostData in this component (the
ternary controlling the earnings/Wallet rendering) and replace the condition
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d5389159-7abc-487c-9d0b-07be15727ae7

📥 Commits

Reviewing files that changed from the base of the PR and between 8f8906b and b2c8656.

📒 Files selected for processing (1)
  • ui/src/components/Hosting.tsx

@jhweir jhweir merged commit f1bb798 into dev Mar 25, 2026
8 checks passed
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.

2 participants