to select ↑↓ to navigate
Doppiobox

Doppiobox

Open in ChatGPT
Ask ChatGPT about this page
Open in Claude
Ask Claude about this page

The Dashboard Frontend (React Product UI)

This is the customer-facing control panel for Doppio Box: the page where a logged-in user logs in, creates/starts/stops/resets dev boxes, tops up their wallet, pays for plans, and reads their subscription/transaction history. It is a single-page React app, not a Frappe Desk page and not a Jinja portal. The marketing landing page (/devbox) and the SPA (/dashboard) are separate things served from the same app — see §2.

Audience: a frontend dev who has never seen this codebase. Everything below is verified against the source.


1. The Stack

Layer Choice Where
Framework React 19 + TypeScript dashboard/package.json:24-25
Build tool Vite 7 dashboard/vite.config.ts
Router react-router-dom 7 (BrowserRouter, basename="/dashboard") dashboard/src/main.tsx:9
Backend SDK frappe-react-sdk 1.14 (wraps SWR + Frappe REST) dashboard/package.json:20
UI primitives shadcn/ui built on Base UI (@base-ui/react), not Radix dashboard/package.json:14, components/ui/*
Table @tanstack/react-table 8 components/devbox/data-table.tsx
Styling Tailwind CSS v4 (@tailwindcss/vite) + tw-animate-css src/index.css:1-2
Toasts sonner components/ui/sonner.tsx
OTP input input-otp components/auth/login-form.tsx:3
Icons lucide-react everywhere
Font Geist Mono Variable (everything is monospace) src/index.css:78,124-128
Payments Razorpay Checkout (loaded at runtime) src/lib/razorpay.ts

Note on "Radix": the task brief says Radix, but this project actually uses Base UI (@base-ui/react). shadcn components here use the Base UI render={<...>} prop pattern (e.g. DropdownMenuTrigger render={<Button .../>}) rather than Radix's asChild. yarn.lock contains zero @radix-ui packages.

Design system

components/ui/ holds ~30 shadcn primitives (button, dialog, sheet, table, badge, card, combobox, dropdown-menu, input, input-otp, tabs, sidebar, sonner, skeleton, empty, tooltip, select, textarea, label, field, separator, alert-dialog…). They are not documented individually here — they are stock shadcn wrappers. Two things worth knowing:

  • Theme is dark-by-default and driven entirely by CSS variables in OKLCH (src/index.css:8-75). The primary brand color is a green (--primary: oklch(0.60 0.13 163)). Never hardcode colors — use the --color-* tokens exposed via @theme inline (src/index.css:77-117).
  • Button has the usual variants plus two compact icon sizes used in this app: icon-xs (size-6, used for the row "⋯" actions trigger) and icon-sm (size-7, used for the header logout button). See components/ui/button.tsx:29-30.

How it talks to the backend

Almost every server interaction goes through frappe-react-sdk hooks, which under the hood call Frappe's REST API with the session cookie + CSRF token:

Hook Used for Example
useFrappeAuth() current user, login state, logout() App.tsx:31, dashboard-layout.tsx:31
useFrappeGetCall(method, params, swrKey) GET a whitelisted @frappe.whitelist method billing-page.tsx:31
useFrappePostCall(method) POST a whitelisted method; returns { call, loading } login-form.tsx:18
useFrappeGetDocList(doctype, opts) list documents via /api/resource devbox-list.tsx:38
useFrappeEventListener(event, cb) subscribe to a socket.io realtime event devbox-list.tsx:125

There are also three raw fetch() calls that bypass the SDK, all hitting /api/resource/Dev Box/{slug} or /api/method/run_doc_method directly and manually attaching window.csrf_token:

  • Polling fallback for box status in devbox-list.tsx:175 and create-devbox-dialog.tsx:278.
  • Fetching site/code URLs once a box is active (create-devbox-dialog.tsx:300).
  • Setting the box title right after creation (create-devbox-dialog.tsx:483).

run_doc_method is Frappe's generic "call a whitelisted method on a document" endpoint; the dashboard uses it to invoke Dev Box controller methods (stop_box, start_box, reset_box, change_title, fetch_stats).


2. How Frappe serves the SPA

The dashboard is a www page, bootstrapped by Frappe.

build:  vite build --base=/assets/benchspace/dashboard/   →  ../benchspace/public/dashboard/
        then  copy-html-entry: cp public/dashboard/index.html → benchspace/www/dashboard.html

(dashboard/package.json:8,11, dashboard/vite.config.ts:21)

So the flow is:

  1. vite build emits hashed JS/CSS into benchspace/public/dashboard/assets/ (served by Frappe at /assets/benchspace/dashboard/...).
  2. The generated index.html is copied to benchspace/www/dashboard.html so Frappe serves it at the route /dashboard.
  3. benchspace/www/dashboard.py:get_context() runs server-side and injects a CSRF token + a small boot dict.
  4. The HTML inline-sets window.csrf_token = '{{ frappe.session.csrf_token }}' (dashboard.html:14) — this is what the raw fetch() calls read.
<!-- benchspace/www/dashboard.html -->
<div id="root"></div>
<script>
  window.csrf_token = '{{ frappe.session.csrf_token }}';
  // TODO: load boot data here (context file is created in `www` directory)
</script>

dashboard.py also exposes get_context_for_dev() (developer-mode only) so the Vite dev server (yarn dev, port 8080, proxyOptions.ts proxies /api, /app, /assets, /files, /private to the bench webserver) can fetch the same boot data while running outside Frappe.

Two routes, two HTML files

Route File What it is
/dashboard www/dashboard.html (generated) + dashboard.py The React SPA (this page's subject)
/devbox www/devbox.html (hand-written Jinja) + devbox.py The marketing landing page — hero, features, Jinja-rendered pricing cards, footer. Static; every "Get Started"/"Get Your Doppio Box" link points to /dashboard. Pricing comes from get_plan_info() server-side (devbox.py:15).

The landing page is documented only at the inventory level (see table) — it is plain Tailwind HTML with one scroll-listener script, no React.

Boot & auth sequence

diagram

FrappeProvider is given siteName={window.location.hostname} (App.tsx:50) so the SDK knows which site to call. Authentication is cookie-based: Frappe's session cookie is set on OTP verify, and useFrappeAuth reads it. There is no token storage in JS.


3. App shell & routing

Component / routing map

diagram

App.tsx — routing + auth gate

  • AuthGate (App.tsx:30): while useFrappeAuth().isLoading → centered spinner. If !currentUser || currentUser === "Guest"<LoginForm onSuccess={() => { getUserCookie(); updateCurrentUser() }} />. Otherwise → <AuthenticatedApp/>.
  • AuthenticatedApp (App.tsx:10): nests all routes inside <DashboardLayout/>:
    • index / → "My Boxes" heading + <DevBoxList/>.
    • /billing<BillingPage/>.
    • *<Navigate to="/" replace/> (unknown paths bounce home).
  • <Toaster/> (sonner) is mounted once at the root for all toasts.

DashboardLayout (components/layout/dashboard-layout.tsx)

The persistent chrome around every page:

  • SidebarProvider + <AppSidebar/> + SidebarInset (the main column).
  • Header (h-14, bordered): SidebarTrigger (the collapse/expand button) on the left; on the right, a wallet balance Badge (only if customer.exists) that links to /billing, the current user's email as muted text, and a Logout ghost icon-button (LogOut icon) calling useFrappeAuth().logout().
  • <Outlet/> renders the active route in a scrollable area.
  • A fixed floating Help button (bottom-right circular CircleHelp FAB) that is a mailto: link to developers@buildwithhussain.com (dashboard-layout.tsx:71-77).
  • Subscribes to the wallet_updated socket event and re-fetches get_customer so the header balance stays live (dashboard-layout.tsx:39).

AppSidebar (components/layout/app-sidebar.tsx)

  • Header label "Doppio Box".
  • One group "Workspace" with exactly two nav items (app-sidebar.tsx:16-19):
    • Dev Boxes/ (icon Box)
    • Billing/billing (icon Wallet)
  • Active item is highlighted by comparing useLocation().pathname to the item URL.

Mobile behavior (hooks/use-mobile.ts + sidebar primitive)

  • useIsMobile() returns true below a 768px breakpoint via matchMedia (hooks/use-mobile.ts:3).
  • On mobile the sidebar primitive renders inside a Sheet (off-canvas drawer, width 18rem) toggled by openMobile state instead of the inline collapsible rail (components/ui/sidebar.tsx:182).
  • Keyboard shortcut: Cmd/Ctrl + B toggles the sidebar (components/ui/sidebar.tsx:31,96-100).

4. The Dev Box experience (the "My Boxes" page)

DevBoxList (components/devbox/devbox-list.tsx) is the heart of the app. It loads the user's boxes, decorates them with trial info, renders the table, and owns every dialog and toast.

(screenshot pending: Dev Box list)

Data loading

  • useFrappeGetDocList("Dev Box", …) fetches name, title, status, site_url, code_url, subscription_end_date, is_pool, ordered by modified desc, up to 100 rows (devbox-list.tsx:38).
  • A second list query reads Dev Box Subscription filtered to user = currentUser, is_trial = 1, status not in [Failed] (devbox-list.tsx:57). The set of dev_box names from this query is merged onto each row as is_trial_subscription (devbox-list.tsx:67-75) — that boolean drives the "Free Trial" badge and which subscription-action button shows.
  • Loading → "Loading boxes…"; error → "Failed to load boxes."; empty → an Empty state with a "Create Doppio Box" button (devbox-list.tsx:328-345).

The table columns (components/devbox/columns.tsx)

DataTable (components/devbox/data-table.tsx) is a thin TanStack-table wrapper (core row model only, no sorting/pagination/filtering). The columns:

# Column Renders
1 Name title || name, plus a secondary "Free Trial" badge when is_trial_subscription (columns.tsx:91)
2 Status A Badge whose variant maps from status (active→default/green, creating/starting/resetting→secondary, pending/stopped→outline, error/deleted→destructive) (columns.tsx:29-38)
3 Site If site_url: an "Open" link (new tab) to the live Frappe site; else
4 Code If code_url: an "Open" link to code-server; else
5 Subscription Human countdown via formatSubscriptionEndDate (columns.tsx:40): No Subscription / Expired (red bold) / N days left / Xh Ym left (3:45 PM) in orange when under 24h
6 subscription_action A single context-dependent button (see below)
7 actions The "⋯" dropdown menu (see below)

Column 6 — the subscription-action button (columns.tsx:149-169)

Exactly one of four buttons (or nothing) renders, based on status, whether the subscription is still active (hasActiveSubscription), and whether it is a trial:

Condition Button Icon Calls
active + active-sub + trial Upgrade ArrowUpCircle onUpgrade → opens CreateDevBoxDialog in renewal mode
stopped + no-active-sub + trial Choose a Plan Sparkles onChoosePlan → renewal dialog
active + active-sub + paid Extend RefreshCw onExtend → renewal dialog
stopped + no-active-sub + paid Renew RefreshCw onRenew → renewal dialog

All four handlers in DevBoxList just setRenewDevbox(name), which opens CreateDevBoxDialog with renewForDevBox set (devbox-list.tsx:193-207,361-368).

Column 7 — the "⋯" actions dropdown (columns.tsx:170-256)

Trigger is a ghost icon-xs button (MoreHorizontal). Menu items are gated by status; separators are inserted between groups only when both sides exist. Full inventory:

Menu item Visible when Icon Handler → action
View Stats status active BarChart3 onViewStats → opens StatsDialog
Download Workspace status active or stopped Download onDownloadWorkspace → opens confirm AlertDialog
Start status stopped + active-sub + not trial Play onStartBoxrun_doc_method start_box
Stop status active/creating/starting Square onStopBoxrun_doc_method stop_box
Change Name status ≠ deleted Pencil onChangeName → opens rename dialog
Reset Dev Box (destructive) not a pool box AND status active/stopped/error RotateCcw onResetBox → opens ResetDevBoxDialog

The reset comment in code notes the backend enforces the subscription gate per role (System Managers bypass), so the frontend shows Reset whenever the status is resettable (columns.tsx:184-188).

Start / Stop — toasts + realtime + polling fallback

handleStartBox / handleStopBox (devbox-list.tsx:93-123) follow the same pattern:

  1. toast.loading("Stopping {name}…"), store the toast id keyed by box slug, and add the slug to activeToastSlugs.
  2. await runDocMethod({ dt:"Dev Box", dn:name, method:"stop_box" }), then mutate() to refresh the list.
  3. On error: toast.error(...), drop the toast.

Progress is then reconciled by socket.io events:

  • devbox_progress {slug, step} → updates the loading toast text (devbox-list.tsx:125).
  • devbox_status_update {slug, status} → resolves the toast to success/error and mutate()s (devbox-list.tsx:152).

Because sockets can be missed, a 30s polling fallback runs while any toast is active: it fetches /api/resource/Dev Box/{slug} and, if status is active|stopped|error, resolves the toast (devbox-list.tsx:164-191).

Other DevBoxList dialogs/actions

  • Change Name dialog (devbox-list.tsx:394-414): a Dialog with one Input (pre-filled with current title), Cancel + Save buttons, Enter-to-submit. Save → run_doc_method change_title {title} then mutate() + success toast.
  • Download Workspace is a two-part flow: the menu item opens a confirm AlertDialog (devbox-list.tsx:369-382, "We'll prepare a ZIP and email you a link", Cancel / Continue). Continue → benchspace.api.devbox.download_workspace {devbox}, toast "Download queued — you'll receive an email shortly". When ready, the workspace_download_ready socket event fires a success toast with a "Download" action button that window.opens the file URL (devbox-list.tsx:269-279).

Create Doppio Box button

Top-right of the list (and inside the empty state): Create Doppio Box (Plus icon) → setShowCreateDialog(true) → opens CreateDevBoxDialog with no renewForDevBox (devbox-list.tsx:322-327).


5. CreateDevBoxDialog — the wizard (create and renew)

components/devbox/create-devbox-dialog.tsx is a multi-step stepper used for both first-time creation and renewing/extending/upgrading an existing box (when renewForDevBox is set, isRenewal = true, header switches to "Renew Doppio Box"). Steps are tracked by a Step union (create-devbox-dialog.tsx:50):

template → plan → billing → paymentMethod → review → provisioning

(screenshot pending: Create Dev Box wizard)

A StepIndicator row at the top shows progress (create-devbox-dialog.tsx:522-544). The Template step is hidden if there is only one template; the Billing step is hidden if the user already has a customer record (hasCustomer).

Data the dialog fetches (all whitelisted GETs, only while open)

Hook Method Purpose
get_customer benchspace.api.wallet.get_customer existing customer/billing/balance
get_templates benchspace.api.billing.get_templates environment templates
get_plan_info benchspace.api.billing.get_plan_info plans + USD/INR prices
get_countries benchspace.api.billing.get_countries country combobox options
get_trial_info benchspace.api.billing.get_trial_info trial eligibility + duration (skipped on renewal)

Step-by-step buttons & API calls

Template (:546-583): grid of selectable cards (auto-selects the default template; auto-advances if only one). Button: Next (disabled until a template is picked) → plan.

Plan (:585-645): grid of PlanCards showing USD price (big), INR price, per-month breakdown, and a "Save X%" badge for multi-month plans (PlanCard, :1016). If trialInfo.eligible and not a renewal, a dashed free-trial box appears with an optional name Input and a Start Free Trial button → benchspace.api.billing.start_free_trial {devbox_template} → jumps straight to provisioning (:330-343). Footer: Back (only if template step exists) + NextpaymentMethod (existing customer) or billing (new customer) (:345-348).

Billing (:647-711, new customers only): Combobox Country* , Input Billing Name*, Textarea Billing Address*, Input Tax ID (optional). Back → plan, Next → paymentMethod (disabled until country+name+address filled, billingValid, :327).

Payment Method (:713-780): up to two big choice buttons —

  • Wallet (only if hasCustomer): shows balance; disabled if balance < amount. Click → handlePayWithWalletinitiate_devbox_order {payment_method:"wallet", …} → goes to provisioning and refetchCustomer() (:381-407).
  • Card / UPI: click → initiateRazorpayOrderinitiate_devbox_order {payment_method:"razorpay", …} → sets orderData, goes to review (:355-379).
  • If wallet is short, a "Top up {amount} to use wallet" link opens the TopUpDialog (:764-772).
  • Footer: Back → plan (existing customer) or billing.

Review (Razorpay only, :782-854): optional DevBox Name Input, a summary card (template/plan/duration/billing/amount), an Edit billing details link (existing customers → opens a Sheet, :921), and the big Pay {amount} button → handlePayNow:

  • loads the Razorpay script (lib/razorpay.ts), opens Razorpay Checkout with key_id/order_id from the order.
  • On success handler: calls handle_devbox_renewal (renewal) or handle_devbox_payment (new) with the razorpay ids + subscription_name, then sets the returned slug and goes to provisioning (:427-447).
  • During review the dialog cannot be closed (canClose = step !== "review", :495, showCloseButton/guarded onOpenChange).

Provisioning (:856-910): three states —

  • In progress: spinner + live provisioningStep text fed by the devbox_progress socket event (:245).
  • Active: green check, "Your Doppio Box is ready!", and Open Site / Open Code external links (URLs fetched via raw fetch once status flips active, :298), plus a Done button. Reaching active also calls onCreated() which mutate()s the parent list.
  • Error: red X, "Provisioning failed… payment was successful, contact support", Close button.
  • Same 30s fetch polling fallback for status as the list (:273-295). If the user typed a name, it is pushed after creation via raw run_doc_method change_title (:481-492).

Edit-billing Sheet (:921-966): right-side Sheet with Billing Name / Address / Tax ID (country & currency locked). Savebenchspace.api.wallet.update_customer_billing then refetchCustomer().

Create-a-box sequence

diagram

Wallet path is the same minus Razorpay: initiate_devbox_order(payment_method=wallet) returns the slug directly and jumps to provisioning.


6. ResetDevBoxDialog (components/devbox/reset-devbox-dialog.tsx)

(screenshot pending: Reset Dev Box dialog)

A destructive type-to-confirm dialog. Body warns reset wipes files, packages, Frappe data, SSH keys, git config, editor extensions — but keeps URL, name, and subscription.

  • If the box is downloadable (active/stopped), an "Optionally, download workspace first" link (Download icon) triggers the same download flow (reset-devbox-dialog.tsx:60-69, wired via onDownloadWorkspace in devbox-list.tsx:390).
  • An Input where the user must type the exact box label (title || name). The Reset dev box (destructive) button is disabled until input.trim() === label; Enter also confirms (:40-46,78,87).
  • Confirm → DevBoxList.confirmResetrun_doc_method reset_box with the same loading-toast + socket/poll resolution pattern as start/stop (devbox-list.tsx:223-236).
  • Cancel button closes without action.

7. StatsDialog (components/devbox/stats-dialog.tsx)

(screenshot pending: Stats dialog)

Opened from "View Stats". On open it calls run_doc_method fetch_stats once (no polling — it's a one-shot fetch per open, stats-dialog.tsx:33-50). Shows a spinner, then three StatItems in a grid: CPU (Cpu icon), Memory (MemoryStick), Disk (HardDrive), each a string from {cpu_usage, memory_usage, disk_usage}. On failure: "Failed to fetch stats". No buttons besides the implicit dialog close.


8. Wallet & Billing area

BillingPage (pages/billing-page.tsx) is the /billing route.

(screenshot pending: Billing page)

  • Wallet balance card (billing-page.tsx:56-73): big balance via formatCurrency, currency sub-label, and a Top up button (size="lg") → opens TopUpDialog. Re-fetches on the wallet_updated socket event.
  • Tabs (:75-86): Wallet (default) → WalletTransactionsTable; SubscriptionsSubscriptionsTable.
  • Hosts the shared TopUpDialog and a CreateDevBoxDialog opened in renewal mode when a subscription row's Renew is clicked (:88-105).

WalletTransactionsTable (components/wallet/wallet-transactions-table.tsx)

Calls benchspace.api.wallet.get_wallet_transactions {limit:100}. Columns: Date, Type (badge: Top-up=default, Plan Purchase=secondary, Refund/Adjustment=outline), Description, Amount (green with + when credit, :96-100), Balance (ending_balance). Empty → "No wallet transactions yet. Top up to get started." No row buttons. See page 08 for the money model.

SubscriptionsTable (components/wallet/subscriptions-table.tsx)

useFrappeGetDocList("Dev Box Subscription", …) (100 rows, newest first). A second list query resolves box titles for the linked dev_box names (subscriptions-table.tsx:85). Columns: Plan, Box (title + mono slug, or slug), Status (badge), Start, End, Method (badge Razorpay/Wallet), Amount (right-aligned formatCurrency), and a trailing Renew button shown only when the row has a dev_box and status is Active or Expired (:140,179-188) → onRenew(dev_box) → opens the renewal CreateDevBoxDialog in the parent. Empty → "You don't have any subscriptions yet."

TopUpDialog (components/wallet/top-up-dialog.tsx)

(screenshot pending: Top-up dialog)

Reused from both BillingPage and inside CreateDevBoxDialog. Two modes:

  • First-time (no customer): shows Country* Combobox (locks wallet currency — India→INR, else USD), Billing Name*, Billing Address*, Tax ID (optional). Country options from get_countries.
  • Returning: just the amount section, in the existing wallet currency.

Amount section: preset chip buttons (INR [500,1000,2500,5000] / USD [5,10,25,50]) plus a custom-amount number Input bounded INR 100–50000 / USD 5–1000 (top-up-dialog.tsx:56-59). Footer: Cancel + Pay {amount} (disabled until formValid).

Pay flow (:122-176): benchspace.api.wallet.initiate_topuploadRazorpayScript() → open Razorpay → on success benchspace.api.wallet.confirm_topup {order_id, payment_id, signature} → success toast + onSuccess() (parent re-fetches balance) + close. Dialog can't be dismissed while loading.


9. Auth UI — LoginForm (components/auth/login-form.tsx)

Login - email step (screenshot pending: Login - OTP step)

Two-step passwordless login (email → 6-digit OTP). Right half is a decorative gradient panel (desktop only). Backend details are in page 09.

  • Email step: Input (type=email, autofocus) + Continue button (disabled while sending or empty) → benchspace.api.auth.send_otp {email} → advances to OTP step and starts a 30s resend cooldown (login-form.tsx:27-37).
  • OTP step: InputOTP with 6 digit slots (digits only, REGEXP_ONLY_DIGITS), a Change email text button (back to email step), a Verify button (disabled until 6 digits) → benchspace.api.auth.verify_otp_and_login {email, otp} → on success calls onSuccess() (which the App uses to refresh the session). A Resend code link appears after the cooldown counts down (:50-58,131-145).
  • Errors render as small destructive text under the form.

10. Complete UI inventory

Screen / Component Element Action Backend API
Landing /devbox "Get Started" / "Get Your Doppio Box" / "Dashboard" links navigate to /dashboard — (Jinja page; plans from get_plan_info server-side)
Landing /devbox "Contact Us" / Help links mailto: enquiry
LoginForm (email) Email input type email
LoginForm (email) Continue send OTP benchspace.api.auth.send_otp
LoginForm (OTP) OTP input (6 digits) enter code
LoginForm (OTP) Verify verify & login benchspace.api.auth.verify_otp_and_login
LoginForm (OTP) Change email back to email step
LoginForm (OTP) Resend code resend OTP (after 30s) benchspace.api.auth.send_otp
DashboardLayout header Sidebar trigger collapse/expand sidebar (Cmd/Ctrl+B)
DashboardLayout header Wallet balance badge navigate /billing (data: wallet.get_customer)
DashboardLayout header Logout icon end session useFrappeAuth().logout()
DashboardLayout Help FAB mailto: support
AppSidebar Dev Boxes navigate /
AppSidebar Billing navigate /billing
DevBoxList Create Doppio Box open create wizard (loads templates/plans/customer/trial)
DevBoxList (data load) list boxes / trial subs GET /api/resource/Dev Box, Dev Box Subscription
DevBox row Site/Code Open links open site / code-server (new tab)
DevBox row Upgrade / Choose a Plan / Extend / Renew open renewal wizard (via CreateDevBoxDialog)
DevBox row ⋯ View Stats open StatsDialog run_doc_methodDev Box.fetch_stats
DevBox row ⋯ Download Workspace confirm → queue ZIP email benchspace.api.devbox.download_workspace
DevBox row ⋯ Start start box run_doc_methodDev Box.start_box
DevBox row ⋯ Stop stop box run_doc_methodDev Box.stop_box
DevBox row ⋯ Change Name open rename dialog (then change_title)
DevBox row ⋯ Reset Dev Box open reset dialog (then reset_box)
Rename dialog Name input + Save rename box run_doc_methodDev Box.change_title
Download AlertDialog Continue queue workspace ZIP benchspace.api.devbox.download_workspace
Workspace-ready toast Download action open file url — (socket workspace_download_ready)
ResetDevBoxDialog type-to-confirm input enable reset
ResetDevBoxDialog "download workspace first" queue ZIP benchspace.api.devbox.download_workspace
ResetDevBoxDialog Reset dev box reset box run_doc_methodDev Box.reset_box
StatsDialog (on open) fetch CPU/mem/disk run_doc_methodDev Box.fetch_stats
CreateDevBoxDialog (template) template cards + Next select env get_templates
CreateDevBoxDialog (plan) plan cards + Next select plan get_plan_info
CreateDevBoxDialog (plan) Start Free Trial provision trial box benchspace.api.billing.start_free_trial
CreateDevBoxDialog (billing) country/name/address/tax + Next capture billing get_countries
CreateDevBoxDialog (pay) Wallet charge wallet, provision benchspace.api.billing.initiate_devbox_order (wallet)
CreateDevBoxDialog (pay) Card / UPI create razorpay order benchspace.api.billing.initiate_devbox_order (razorpay)
CreateDevBoxDialog (pay) "Top up to use wallet" open TopUpDialog
CreateDevBoxDialog (review) Pay {amount} Razorpay checkout + verify handle_devbox_payment / handle_devbox_renewal
CreateDevBoxDialog (review) Edit billing details open billing sheet
CreateDevBoxDialog (sheet) Save update billing benchspace.api.wallet.update_customer_billing
CreateDevBoxDialog (provisioning) Open Site / Open Code open box urls GET /api/resource/Dev Box/{slug}
CreateDevBoxDialog (provisioning) Done / Close close dialog
BillingPage Top up open TopUpDialog (data: wallet.get_customer)
BillingPage Wallet / Subscriptions tabs switch view
WalletTransactionsTable (data load) list transactions benchspace.api.wallet.get_wallet_transactions
SubscriptionsTable (data load) list subscriptions GET /api/resource/Dev Box Subscription
SubscriptionsTable Renew open renewal wizard (via CreateDevBoxDialog)
TopUpDialog country/billing (first-time) capture billing get_countries
TopUpDialog preset chips + custom amount set amount
TopUpDialog Pay {amount} Razorpay topup + confirm wallet.initiate_topupwallet.confirm_topup

Realtime (socket.io) events consumed

Event Payload Consumer Effect
wallet_updated DashboardLayout, BillingPage re-fetch get_customer
devbox_progress {slug, step} DevBoxList, CreateDevBoxDialog update loading toast / step text
devbox_status_update {slug, status} DevBoxList, CreateDevBoxDialog resolve toast / advance provisioning
workspace_download_ready {slug, file_url} DevBoxList toast with Download action

Screenshot placeholders

The orchestrator should capture real production screenshots for each placeholder below (saved under docs/doppiobox-wiki/screenshots/):

  • screenshots/devbox-list.png — the "My Boxes" table with at least one box (showing status badge, Site/Code links, subscription countdown, the subscription-action button, and the ⋯ menu, ideally opened).
  • screenshots/create-devbox-wizard.png — the CreateDevBoxDialog, ideally on the Plan step showing plan cards + the free-trial box and the stepper.
  • screenshots/reset-devbox-dialog.png — the ResetDevBoxDialog with the type-to-confirm input and destructive button.
  • screenshots/stats-dialog.png — the StatsDialog showing CPU/Memory/Disk for an active box.
  • screenshots/billing-page.png — the Billing page: wallet balance card + Top up button + the Wallet/Subscriptions tabs.
  • screenshots/top-up-dialog.png — the TopUpDialog showing preset amount chips + custom amount input.
  • screenshots/login-email.png — LoginForm email step (email field + Continue + the right-side gradient panel on desktop).
  • screenshots/login-otp.png — LoginForm OTP step (6-slot OTP input + Verify + resend countdown).
Last updated 1 month ago
Was this helpful?
Thanks!