Add open subscription page option to the ClinePass options (#11896)

* Add open subscription page option to the ClinePass options

* Address comments
This commit is contained in:
Tomás Barreiro
2026-06-27 01:07:11 +02:00
committed by GitHub
parent 83339c3c5a
commit 7830472017
2 changed files with 145 additions and 16 deletions
@@ -248,18 +248,44 @@ export function ProviderPickerContent(
);
}
export type ExistingProviderAction = "use_existing" | "reconfigure";
export type ExistingProviderAction =
| "use_existing"
| "reconfigure"
| "open_subscription";
export interface ExistingProviderOption {
value: ExistingProviderAction;
label: string;
onSelect?: () => Promise<void> | void;
}
const CLINE_PASS_SUBSCRIPTION_PATH = "/dashboard/subscription";
const DEFAULT_APP_BASE_URL = "https://app.cline.bot";
export function buildClinePassSubscriptionPageUrl(
appBaseUrl: string | undefined,
): string {
return new URL(
CLINE_PASS_SUBSCRIPTION_PATH,
appBaseUrl || DEFAULT_APP_BASE_URL,
).toString();
}
export function UseExistingOrReconfigureContent(
props: ChoiceContext<ExistingProviderAction> & {
props: ChoiceContext<ExistingProviderOption> & {
providerName: string;
extraOptions?: ExistingProviderOption[];
},
) {
const { resolve, dismiss, dialogId, providerName } = props;
const options: { value: ExistingProviderAction; label: string }[] = [
{ value: "use_existing", label: "Use existing configuration" },
{ value: "reconfigure", label: "Configure again" },
];
const { resolve, dismiss, dialogId, providerName, extraOptions } = props;
const options: ExistingProviderOption[] = useMemo(
() => [
{ value: "use_existing", label: "Use existing configuration" },
{ value: "reconfigure", label: "Configure again" },
...(extraOptions ?? []),
],
[extraOptions],
);
const [selected, setSelected] = useState(0);
useDialogKeyboard((key) => {
@@ -269,7 +295,7 @@ export function UseExistingOrReconfigureContent(
}
if (key.name === "return" || key.name === "enter") {
const opt = options[selected];
if (opt) resolve(opt.value);
if (opt) resolve(opt);
return;
}
if (key.name === "up" || (key.ctrl && key.name === "p")) {
@@ -314,6 +340,59 @@ export function UseExistingOrReconfigureContent(
);
}
export function ClinePassSubscriptionContent(
props: ChoiceContext<boolean> & {
providerName: string;
},
) {
const { resolve, dismiss, dialogId, providerName } = props;
const subscriptionUrl = useMemo(
() =>
buildClinePassSubscriptionPageUrl(getClineEnvironmentConfig().appBaseUrl),
[],
);
const [status, setStatus] = useState("Opening browser...");
useEffect(() => {
void open(subscriptionUrl, { wait: false })
.then(() => {
setStatus("Opened subscription page in your browser.");
})
.catch(() => {
setStatus("Could not open browser automatically. Open the URL below.");
});
}, [subscriptionUrl]);
useDialogKeyboard((key) => {
if (key.name === "escape") {
dismiss();
return;
}
if (key.name === "return" || key.name === "enter") {
resolve(true);
}
}, dialogId);
return (
<box flexDirection="column" paddingX={1} gap={1}>
<text fg="cyan">
<strong>{providerName}</strong>
</text>
<text>{status}</text>
<text fg="gray">Subscription page:</text>
<text fg="cyan" selectable>
<a href={subscriptionUrl}>{subscriptionUrl}</a>
</text>
<text fg="gray">
<em>Enter or Esc to go back</em>
</text>
</box>
);
}
const DEFAULT_FIELD_LABELS: Partial<Record<ProviderConfigFieldKey, string>> = {
apiKey: "API key",
baseUrl: "Base URL",
+58 -8
View File
@@ -18,8 +18,9 @@ import {
import type { Config } from "../../utils/types";
import { withLoadingDialog } from "../components/dialogs/loading-dialog";
import {
ClinePassSubscriptionContent,
CodexCliStatusContent,
type ExistingProviderAction,
type ExistingProviderOption,
OAuthLoginContent,
ProviderConfigInputContent,
ProviderPickerContent,
@@ -78,6 +79,36 @@ function usesModelIdInput(providerId: string): boolean {
return providerId === "openai-compatible";
}
function providerToExistingProviderOptions(input: {
providerId: string;
providerName: string;
dialog: DialogActions;
termHeight: number;
}): ExistingProviderOption[] {
if (input.providerId !== "cline-pass") {
return [];
}
return [
{
value: "open_subscription",
label: "Open ClinePass subscription page",
onSelect: async () => {
await input.dialog.choice<boolean>({
style: { maxHeight: input.termHeight - 2 },
closeOnEscape: false,
content: (ctx: ChoiceContext<boolean>) => (
<ClinePassSubscriptionContent
{...ctx}
providerName={input.providerName}
/>
),
});
},
},
];
}
async function runProviderChange(
dialog: DialogActions,
config: Config,
@@ -102,14 +133,33 @@ async function runProviderChange(
let needsAuth = true;
if (isProviderConfigured(newProviderId, existingSettings)) {
const action = await dialog.choice<ExistingProviderAction>({
style: { maxHeight: termHeight - 2 },
content: (ctx: ChoiceContext<ExistingProviderAction>) => (
<UseExistingOrReconfigureContent {...ctx} providerName={displayName} />
),
let option: ExistingProviderOption | undefined;
const extraOptions = providerToExistingProviderOptions({
providerId: newProviderId,
providerName: displayName,
dialog,
termHeight,
});
if (!action) return false;
needsAuth = action === "reconfigure";
while (true) {
option = await dialog.choice<ExistingProviderOption>({
style: { maxHeight: termHeight - 2 },
content: (ctx: ChoiceContext<ExistingProviderOption>) => (
<UseExistingOrReconfigureContent
{...ctx}
providerName={displayName}
extraOptions={extraOptions}
/>
),
});
if (!option) return false;
if (option.onSelect) {
await option.onSelect();
option = undefined;
continue;
}
break;
}
needsAuth = option.value === "reconfigure";
}
if (needsAuth) {