From 42bacee4543bc0b35014109761d47811cc107134 Mon Sep 17 00:00:00 2001 From: lordspline Date: Fri, 29 Aug 2025 06:13:17 +0000 Subject: [PATCH] Add minimal billing demo extension and Help menu action to open it. Provides webview with plan tiers and fake upgrade command, plus a Billing status bar entry. - Scout jam: [ab263bac-b642-4e16-902a-6884d699924b](https://scout.new/jam/ab263bac-b642-4e16-902a-6884d699924b) Co-authored-by: Scout --- extensions/billing-demo/media/styles.css | 1 + extensions/billing-demo/package.json | 40 +++++++ extensions/billing-demo/src/extension.ts | 109 ++++++++++++++++++ extensions/billing-demo/tsconfig.json | 12 ++ .../workbench/browser/actions/helpActions.ts | 25 ++++ 5 files changed, 187 insertions(+) create mode 100644 extensions/billing-demo/media/styles.css create mode 100644 extensions/billing-demo/package.json create mode 100644 extensions/billing-demo/src/extension.ts create mode 100644 extensions/billing-demo/tsconfig.json diff --git a/extensions/billing-demo/media/styles.css b/extensions/billing-demo/media/styles.css new file mode 100644 index 0000000000000..a6aabe8797ea9 --- /dev/null +++ b/extensions/billing-demo/media/styles.css @@ -0,0 +1 @@ +*{box-sizing:border-box} body{font-family:var(--vscode-font-family,-apple-system,BlinkMacSystemFont,Segoe WPC,Segoe UI,HelveticaNeue-Light,Ubuntu,Droid Sans,Arial,sans-serif);padding:16px;color:var(--vscode-foreground);background:var(--vscode-editor-background)} h1{font-size:20px;margin:0 0 12px} .plans{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px} .card{border:1px solid var(--vscode-editorGroup-border);background:var(--vscode-sideBar-background);border-radius:8px;box-shadow:0 1px 0 rgba(0,0,0,.05);display:flex;flex-direction:column;overflow:hidden} .card header{padding:12px 14px;background:var(--vscode-editor-inactiveSelectionBackground);font-weight:600} .card .body{padding:12px 14px;flex:1} .price{font-size:22px;font-weight:700;margin:4px 0 8px} ul{padding-left:18px;margin:8px 0} li{margin:4px 0} button{appearance:none;border:1px solid var(--vscode-button-border,transparent);background:var(--vscode-button-background);color:var(--vscode-button-foreground);border-radius:6px;padding:8px 12px;font-weight:600;cursor:pointer} button:hover{filter:brightness(1.05)} .footer{margin-top:12px;color:var(--vscode-descriptionForeground);font-size:12px} .status{margin-bottom:8px;padding:8px 10px;border-radius:6px;border:1px dashed var(--vscode-editorGroup-border);background:var(--vscode-editor-background)} \ No newline at end of file diff --git a/extensions/billing-demo/package.json b/extensions/billing-demo/package.json new file mode 100644 index 0000000000000..0b03e165b8ad5 --- /dev/null +++ b/extensions/billing-demo/package.json @@ -0,0 +1,40 @@ +{ + "name": "billing-demo", + "displayName": "Billing Demo", + "description": "Minimal demo billing UI with plans and fake upgrade button.", + "version": "0.0.1", + "publisher": "vscode", + "license": "MIT", + "engines": { + "vscode": "^1.70.0" + }, + "main": "./out/extension", + "categories": [ + "Other" + ], + "extensionKind": [ + "ui" + ], + "activationEvents": [ + "onCommand:billing.open", + "onCommand:billing.upgrade" + ], + "contributes": { + "commands": [ + { + "command": "billing.open", + "title": "Open Billing", + "category": "Billing" + }, + { + "command": "billing.upgrade", + "title": "Upgrade", + "category": "Billing" + } + ] + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" + } +} diff --git a/extensions/billing-demo/src/extension.ts b/extensions/billing-demo/src/extension.ts new file mode 100644 index 0000000000000..37d598a7d5244 --- /dev/null +++ b/extensions/billing-demo/src/extension.ts @@ -0,0 +1,109 @@ +import * as vscode from 'vscode'; + +function nonce() { + let text = ''; + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} + +function getWebviewContent(webview: vscode.Webview, extensionUri: vscode.Uri) { + const n = nonce(); + const styleUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, 'media', 'styles.css')); + return ` + + + + + + Billing + + + +

Choose your plan

+
Current plan: Free
+
+
+
Free
+
+
$0
+
    +
  • Basic features
  • +
  • Community support
  • +
+
+
+
+
Pro
+
+
$12/mo
+
    +
  • Unlimited projects
  • +
  • Priority support
  • +
+ +
+
+
+
Team
+
+
$29/mo
+
    +
  • Seats and billing
  • +
  • Advanced controls
  • +
+ +
+
+
+ + + +`; +} + +export function activate(context: vscode.ExtensionContext) { + const statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 10000); + statusItem.text = 'Billing'; + statusItem.command = 'billing.open'; + statusItem.show(); + context.subscriptions.push(statusItem); + + const openCmd = vscode.commands.registerCommand('billing.open', () => { + const panel = vscode.window.createWebviewPanel('billing', 'Billing', vscode.ViewColumn.Active, { + enableScripts: true, + retainContextWhenHidden: true, + localResourceRoots: [vscode.Uri.joinPath(context.extensionUri, 'media')] + }); + panel.webview.html = getWebviewContent(panel.webview, context.extensionUri); + panel.webview.onDidReceiveMessage(msg => { + if (msg?.type === 'upgrade') { + vscode.commands.executeCommand('billing.upgrade', msg.plan); + } + }); + }); + + const upgradeCmd = vscode.commands.registerCommand('billing.upgrade', async (plan?: string) => { + const picked = plan ?? 'Pro'; + await vscode.window.showInformationMessage(`Upgraded to ${picked} successfully.`); + }); + + context.subscriptions.push(openCmd, upgradeCmd); +} + +export function deactivate() {} diff --git a/extensions/billing-demo/tsconfig.json b/extensions/billing-demo/tsconfig.json new file mode 100644 index 0000000000000..7d58be198c2d0 --- /dev/null +++ b/extensions/billing-demo/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out", + "types": ["node"], + "rootDir": "./src" + }, + "include": [ + "src/**/*", + "../../src/vscode-dts/vscode.d.ts" + ] +} diff --git a/src/vs/workbench/browser/actions/helpActions.ts b/src/vs/workbench/browser/actions/helpActions.ts index 2487213aa2d3e..5a2daefaabc6d 100644 --- a/src/vs/workbench/browser/actions/helpActions.ts +++ b/src/vs/workbench/browser/actions/helpActions.ts @@ -351,6 +351,30 @@ class AskVSCodeCopilot extends Action2 { } } +class OpenBillingAction extends Action2 { + static readonly ID = 'workbench.action.openBilling'; + constructor() { + super({ + id: OpenBillingAction.ID, + title: { + ...localize2('openBilling', "Billing…"), + mnemonicTitle: localize({ key: 'miBilling', comment: ['&& denotes a mnemonic'] }, "&&Billing…"), + }, + category: Categories.Help, + f1: true, + menu: { + id: MenuId.MenubarHelpMenu, + group: '3_support', + order: 1 + } + }); + } + run(accessor: ServicesAccessor): void { + const commandService = accessor.get(ICommandService); + commandService.executeCommand('billing.open'); + } +} + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { command: { id: AskVSCodeCopilot.ID, @@ -402,3 +426,4 @@ if (OpenPrivacyStatementUrlAction.AVAILABLE) { registerAction2(GetStartedWithAccessibilityFeatures); registerAction2(AskVSCodeCopilot); +registerAction2(OpenBillingAction);