diff --git a/packages/kilo-vscode/webview-ui/src/App.tsx b/packages/kilo-vscode/webview-ui/src/App.tsx index bdbc1ec859..3a6a568f46 100644 --- a/packages/kilo-vscode/webview-ui/src/App.tsx +++ b/packages/kilo-vscode/webview-ui/src/App.tsx @@ -156,6 +156,7 @@ const AppContent: Component = () => { profileData={server.profileData()} deviceAuth={server.deviceAuth()} onLogin={server.startLogin} + onDone={() => setCurrentView("newTask")} /> diff --git a/packages/kilo-vscode/webview-ui/src/components/ProfileView.tsx b/packages/kilo-vscode/webview-ui/src/components/ProfileView.tsx index 18a1d1325e..6721082092 100644 --- a/packages/kilo-vscode/webview-ui/src/components/ProfileView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/ProfileView.tsx @@ -1,6 +1,5 @@ -import { Component, Show, createSignal, createMemo, createEffect, onMount } from "solid-js" +import { Component, Show, For, createSignal, createMemo, createEffect, onMount } from "solid-js" import { Button } from "@kilocode/kilo-ui/button" -import { Card } from "@kilocode/kilo-ui/card" import { Select } from "@kilocode/kilo-ui/select" import { Tooltip } from "@kilocode/kilo-ui/tooltip" import { useVSCode } from "../context/vscode" @@ -14,6 +13,7 @@ export interface ProfileViewProps { profileData: ProfileData | null | undefined deviceAuth: DeviceAuthState onLogin: () => void + onDone: () => void } const formatBalance = (amount: number): string => { @@ -21,6 +21,13 @@ const formatBalance = (amount: number): string => { } const PERSONAL = "personal" +const APP_BASE_URL = "https://app.kilo.ai" +const CREDIT_PACKAGES = [ + { credits: 20, popular: false }, + { credits: 50, popular: true }, + { credits: 100, popular: false }, + { credits: 200, popular: false }, +] interface OrgOption { value: string @@ -28,6 +35,30 @@ interface OrgOption { description?: string } +const getInitial = (value: string): string => { + const trimmed = value.trim() + if (!trimmed) { + return "?" + } + return trimmed.charAt(0).toUpperCase() +} + +const KiloBrandLogo: Component = () => ( + +) + const ProfileView: Component = (props) => { const vscode = useVSCode() const language = useLanguage() @@ -40,29 +71,29 @@ const ProfileView: Component = (props) => { // Reset pending target whenever profileData changes (success or failure both send a fresh profile) createEffect(() => { - props.profileData // track + props.profileData setTarget(null) }) const switching = createMemo(() => { - const t = target() - if (t === null) return false + const nextTarget = target() + if (nextTarget === null) return false const current = props.profileData?.currentOrgId ?? PERSONAL - return current !== t + return current !== nextTarget }) const orgOptions = createMemo(() => { const orgs = props.profileData?.profile.organizations ?? [] if (orgs.length === 0) return [] return [ - { value: PERSONAL, label: "Personal Account" }, + { value: PERSONAL, label: language.t("profile.account.personal") }, ...orgs.map((org) => ({ value: org.id, label: org.name, description: org.role })), ] }) const currentOrg = createMemo(() => { const id = props.profileData?.currentOrgId ?? PERSONAL - return orgOptions().find((o) => o.value === id) + return orgOptions().find((option) => option.value === id) }) const selectOrg = (option: OrgOption | undefined) => { @@ -89,7 +120,30 @@ const ProfileView: Component = (props) => { } const handleDashboard = () => { - vscode.postMessage({ type: "openExternal", url: "https://app.kilo.ai/profile" }) + vscode.postMessage({ type: "openExternal", url: `${APP_BASE_URL}/profile` }) + } + + const handleUsageDetails = () => { + const orgId = props.profileData?.currentOrgId + if (!orgId) { + return + } + vscode.postMessage({ + type: "openExternal", + url: `${APP_BASE_URL}/organizations/${orgId}/usage-details`, + }) + } + + const handleCreateOrganization = () => { + // TODO(telemetry): capture CREATE_ORGANIZATION_LINK_CLICKED analytics event when telemetry pipeline is implemented. + vscode.postMessage({ type: "openExternal", url: `${APP_BASE_URL}/organizations/new` }) + } + + const handleBuyCredits = (credits: number) => { + vscode.postMessage({ + type: "openExternal", + url: `${APP_BASE_URL}/profile?buyCredits=${credits}`, + }) } const handleCancelLogin = () => { @@ -97,48 +151,101 @@ const ProfileView: Component = (props) => { } return ( -
-

+
- {language.t("profile.title")} -

- -
+

+ {language.t("profile.title")} +

+
+ +
+
+
+ +

+ {language.t("profile.welcome.greeting")} +

- {language.t("profile.notLoggedIn")} + {language.t("profile.welcome.introText1")}

- - +

+ {language.t("profile.welcome.introText2")} +

+

+ {language.t("profile.welcome.introText3")} +

+
+ +
+
} > = (props) => { } > {(data) => ( -
- {/* User header */} - -

- {data().profile.name || data().profile.email} -

-

+

+ + {getInitial(data().profile.name || data().profile.email)} +
+ } > - {data().profile.email} -

-
- - {/* Organization selector */} - 0}> - + {(avatar) => ( + Profile + )} + +

+ {data().profile.name || data().profile.email} +

+

- Account + {data().profile.email}

+
+
+ + 0}> +