diff --git a/sites/shkor-com/test_account_flow.py b/sites/shkor-com/test_account_flow.py index 521c055..1f781ba 100644 --- a/sites/shkor-com/test_account_flow.py +++ b/sites/shkor-com/test_account_flow.py @@ -48,6 +48,8 @@ try: c,_,b=req("/api/account/agreement",{"signer_name":"Someone Else","accept":True},C); eq(c==400,"name must match") c,_,b=req("/api/account/agreement",{"signer_name":"cab tester","accept":False},C); eq(c==400,"must accept "+b.decode()[:100]) c,_,b=req("/api/account/agreement",{"signer_name":"Cab Tester","accept":True},C); r=json.loads(b); eq(c==200 and r.get("agreement"),"signed "+b.decode()[:200]) + eq(r.get("level")==2,"level 2 right after signing") + eq((r.get("claude") or {}).get("status")=="requested","claude requested after signing: "+str(r.get("claude"))) # product before project c,_,b=req("/api/account/product",{"name":"Pro","price_usd":29,"interval":"month"},C); eq(c==409,"product needs project") # project @@ -62,7 +64,7 @@ try: print("link", p.get("link")) c,_,b=req("/api/account/product-live",{"product_id":p["id"],"note":"Monthly access"},C); r=json.loads(b); eq(c==200 and r["products"][0]["live_request"]=="pending","live requested "+b.decode()[:200]) c,_,b=req("/api/account/product-live",{"product_id":999999},C); eq(c==404,"foreign product 404") - c,_,b=req("/api/account/me",cookie=C); d=json.loads(b); eq(d["cabinet"]["level"]==1 and d["cabinet"]["steps"][3]["done"] is False,"still level1 until live") + c,_,b=req("/api/account/me",cookie=C); d=json.loads(b); eq(d["cabinet"]["level"]==2 and d["cabinet"]["steps"][3]["done"] is False,"level 2 (not 3) until live product") c,_,b=req("/api/account/logout",{},C); eq(c==200,"logout") print(f"{ok} ok, {bad} fail") finally: diff --git a/sites/shkor-com/v2/account/index.html b/sites/shkor-com/v2/account/index.html index bf65af7..6924653 100644 --- a/sites/shkor-com/v2/account/index.html +++ b/sites/shkor-com/v2/account/index.html @@ -109,44 +109,52 @@ function render() { var d = S, c = d.cabinet, first = (d.name || '').split(' ')[0]; document.getElementById('logout').hidden = false; - var h = '
'; + var LV = { 1: 'Community', 2: 'Studio builder', 3: 'Studio partner' }; + var h = ''; if (d.stage !== 'ready' || !c) { var msg = { kyc: 'Finish identity verification to continue.', pay: 'Verified — one step left: payment.', declined: 'Identity verification was not approved.', deploying: 'Your workspace is deploying — usually 15–40 minutes.' }[d.stage] || ''; h += ''; app.innerHTML = h; return; } // workspace - if (d.panel) h += 'Your cloud workspace: OpenCode is ready to go, Claude Code works with your own Claude account. Both are connected to the lab MCP — git, subdomains and payments.
Your cloud workspace: OpenCode is ready to go; Claude Code ' + ((c.claude || {}).status === 'connected' ? 'runs on the studio (Level 2).' : 'works with your own Claude account — or on the studio from Level 2.') + ' Both are connected to the lab MCP — git, subdomains and payments.
Sales through the cooperative’s payments. The studio share is withheld automatically at each payment.
' + 'Withdrawals are coming soon — your balance is already tracked.
Level 2 gives you:
And next, Level 3: a paid role in the studio — by invitation of the board.
'; - // 1 kyc - h += stepHTML(1, 'kyc', true, 'Verify your identity', 'Done with Didit.', '', false); - // 2 agreement - var agr = c.agreement; - h += stepHTML(2, 'agreement', st.agreement, 'Sign the contractor agreement', agr ? 'Signed by ' + esc(agr.signer_name) + ' · ' + esc(agr.signed_at) + ' UTC' : 'You keep your project. The studio gets a share of revenue you set (min ' + c.min_studio_pct + '%).', + var agr = c.agreement, cl = c.claude || { status: 'locked' }; + // ── Level 2 · Studio builder: one step — the contractor agreement + var CL = { locked: ['todo', 'Unlocks when you sign'], requested: ['wip', 'Setting up your account…'], connected: ['ok', 'Connected'] }[cl.status] || ['wip', cl.status]; + var clBody = cl.status === 'connected' ? 'Open your workspace and choose Claude Code — it runs on the studio’s account, nothing to pay or sign in.' + : cl.status === 'requested' ? 'The studio is setting up a Claude Code account for you — usually within a day. This turns green by itself once it works on your server.' + (cl.detail ? '' + (agr ? 'Your perks are on.' : 'Sign the contractor agreement and these switch on right away:') + '
Version ' + esc(agr.version) + '. Read the agreement.
' : 'Read the agreement (' + esc(c.agreement_version) + ') — about 3 minutes. Key points: you own your project; the studio share (min ' + c.min_studio_pct + '%) is withheld from payments through the studio; you’re an independent contractor; 30 days’ notice to end.
' + '', firstOpen === 'agreement'); + + '', !agr); + h += 'Build a business with the studio behind you:
Beyond that: a paid role in the studio — by invitation of the board.
'; // 3 project var p = c.project || { decisions: {} }, decs = ''; DEC.forEach(function (x) { decs += ''; }); - h += stepHTML(3, 'project', st.project, 'Describe your project', c.project ? esc(c.project.name) + ' · studio share ' + esc(c.project.studio_pct) + '%' : 'Name, studio share and the 7 decisions every studio project defines.', + h += stepHTML(1, 'project', st.project, 'Describe your project', c.project ? esc(c.project.name) + ' · studio share ' + esc(c.project.studio_pct) + '%' : 'Name, studio share and the 7 decisions every studio project defines.', '') + 'Create your project first (step 3).
'), firstOpen === 'product'); + : 'Create your project first (step 1).
'), firstOpen === 'product'); // 5 traction var th = c.thresholds || { gross_usd: 300, payments: 10 }, g = Math.min(100, (e.gross || 0) / th.gross_usd * 100), n = Math.min(100, (e.payments || 0) / th.payments * 100); - h += stepHTML(5, 'traction', st.traction, 'Show traction', 'Either ' + money(th.gross_usd) + ' in sales or ' + th.payments + ' paying customers through studio payments.', + h += stepHTML(3, 'traction', st.traction, 'Show traction', 'Either ' + money(th.gross_usd) + ' in sales or ' + th.payments + ' paying customers through studio payments.', 'Sales: ' + money(e.gross) + ' of ' + money(th.gross_usd) + '
' + 'Payments: ' + (e.payments || 0) + ' of ' + th.payments + '
', firstOpen === 'traction'); h += '