Add email subscription field to welcome page (#1497)

* Add email subscription field to welcome page

* Fix merge conflict

* Reposition language picker

* Fixes
This commit is contained in:
Saoud Rizwan 2025-01-27 17:17:33 -08:00 committed by GitHub
parent e506f48da8
commit 57a4f2be0e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 126 additions and 25 deletions

View file

@ -630,6 +630,9 @@ export class ClineProvider implements vscode.WebviewViewProvider {
case "getLatestState":
await this.postStateToWebview()
break
case "subscribeEmail":
this.subscribeEmail(message.text)
break
case "accountLoginClicked": {
// Generate nonce for state validation
const nonce = crypto.randomBytes(32).toString("hex")
@ -699,6 +702,36 @@ export class ClineProvider implements vscode.WebviewViewProvider {
)
}
async subscribeEmail(email?: string) {
if (!email) {
return
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(email)) {
vscode.window.showErrorMessage("Please enter a valid email address")
return
}
console.log("Subscribing email:", email)
this.postMessageToWebview({ type: "emailSubscribed" })
// Currently ignoring errors to this endpoint, but after accounts we'll remove this anyways
try {
const response = await axios.post(
"https://app.cline.bot/api/mailing-list",
{
email: email,
},
{
headers: {
"Content-Type": "application/json",
},
},
)
console.log("Email subscribed successfully. Response:", response.data)
} catch (error) {
console.error("Failed to subscribe email:", error)
}
}
async cancelTask() {
if (this.cline) {
const { historyItem } = await this.getTaskWithId(this.cline.taskId)

View file

@ -25,6 +25,7 @@ export interface ExtensionMessage {
| "relinquishControl"
| "vsCodeLmModels"
| "requestVsCodeLmModels"
| "emailSubscribed"
text?: string
action?:
| "chatButtonClicked"

View file

@ -41,6 +41,7 @@ export interface WebviewMessage {
| "getLatestState"
| "accountLoginClicked"
| "accountLogoutClicked"
| "subscribeEmail"
// | "relaunchChromeDebugMode"
text?: string
disabled?: boolean

View file

@ -1,12 +1,14 @@
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { useEffect, useState } from "react"
import { VSCodeButton, VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { useEffect, useState, useCallback } from "react"
import { useExtensionState } from "../../context/ExtensionStateContext"
import { validateApiConfiguration } from "../../utils/validate"
import { vscode } from "../../utils/vscode"
import ApiOptions from "../settings/ApiOptions"
import { useTranslation } from "react-i18next"
import LanguageOptions from "../settings/LanguageOptions"
import { Trans } from "react-i18next"
import { useEvent } from "react-use"
import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage"
import LanguageOptions from "../settings/LanguageOptions"
const WelcomeView = () => {
const { t } = useTranslation("translation", { keyPrefix: "welcomeView" })
@ -14,6 +16,8 @@ const WelcomeView = () => {
const { apiConfiguration } = useExtensionState()
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
const [email, setEmail] = useState("")
const [isSubscribed, setIsSubscribed] = useState(false)
const disableLetsGoButton = apiErrorMessage != null
@ -21,10 +25,27 @@ const WelcomeView = () => {
vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
}
const handleSubscribe = () => {
if (email) {
vscode.postMessage({ type: "subscribeEmail", text: email })
}
}
useEffect(() => {
setApiErrorMessage(validateApiConfiguration(apiConfiguration))
}, [apiConfiguration])
// Add message handler for subscription confirmation
const handleMessage = useCallback((e: MessageEvent) => {
const message: ExtensionMessage = e.data
if (message.type === "emailSubscribed") {
setIsSubscribed(true)
setEmail("")
}
}, [])
useEvent("message", handleMessage)
return (
<div
style={{
@ -33,31 +54,76 @@ const WelcomeView = () => {
left: 0,
right: 0,
bottom: 0,
padding: "0 20px",
}}>
<h2>{t("greeting")}</h2>
<p>
<Trans
i18nKey="welcomeView.description"
components={{
ClaudeLink: (
<VSCodeLink
href="https://www-cdn.anthropic.com/fed9cc193a14b84131812372d8d5857f8f304c52/Model_Card_Claude_3_Addendum.pdf"
style={{ display: "inline" }}
/>
),
}}
/>
</p>
<div
style={{
height: "100%",
padding: "0 20px",
overflow: "auto",
}}>
<h2>{t("greeting")}</h2>
<b>{t("getStarted")}</b>
<div style={{ marginBottom: "10px" }}>
<LanguageOptions />
</div>
<div style={{ marginTop: "10px" }}>
<ApiOptions showModelOptions={false} />
<LanguageOptions />
<VSCodeButton onClick={handleSubmit} disabled={disableLetsGoButton} style={{ marginTop: "3px" }}>
{t("letsGo")}
</VSCodeButton>
<p>
<Trans
i18nKey="welcomeView.description"
components={{
ClaudeLink: (
<VSCodeLink
href="https://www-cdn.anthropic.com/fed9cc193a14b84131812372d8d5857f8f304c52/Model_Card_Claude_3_Addendum.pdf"
style={{ display: "inline" }}
/>
),
}}
/>
</p>
<b>{t("getStarted")}</b>
<div
style={{
marginTop: "15px",
padding: isSubscribed ? "5px 15px 5px 15px" : "12px",
background: "var(--vscode-textBlockQuote-background)",
borderRadius: "6px",
fontSize: "0.9em",
}}>
{isSubscribed ? (
<p style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<span style={{ color: "var(--vscode-testing-iconPassed)", fontSize: "1.5em" }}></span>
Thanks for subscribing! We'll keep you updated on new features.
</p>
) : (
<>
<p style={{ margin: 0, marginBottom: "8px" }}>
While Cline currently requires you bring your own API key, we are working on an official accounts
system with additional capabilities. Subscribe to our mailing list to get updates!
</p>
<div style={{ display: "flex", gap: "10px", alignItems: "center" }}>
<VSCodeTextField
type="email"
value={email}
onInput={(e: any) => setEmail(e.target.value)}
placeholder="Enter your email"
style={{ flex: 1 }}
/>
<VSCodeButton appearance="secondary" onClick={handleSubscribe} disabled={!email}>
Subscribe
</VSCodeButton>
</div>
</>
)}
</div>
<div style={{ marginTop: "15px" }}>
<ApiOptions showModelOptions={false} />
<VSCodeButton onClick={handleSubmit} disabled={disableLetsGoButton} style={{ marginTop: "3px" }}>
{t("letsGo")}
</VSCodeButton>
</div>
</div>
</div>
)