Unverified 提交 9a606404 authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

Refine home page experience (#2110)

<!-- CURSOR_SUMMARY --> > [!NOTE] > Refines the home experience and Pro visibility/CTA while updating related copy and tests. > > - Changes hero title to `Build a new app` in `SetupBanner` and updates e2e expectations (`e2e-tests/*.spec.ts`) > - Adds fixed Dyad Pro CTA on `home.tsx` top-right: shows `ManageDyadProButton` when `hasDyadProKey(settings)` else `SetupDyadProButton` > - Makes `ImportAppButton` accept an optional `className`; updates layout usage on home > - Simplifies `ProBanner`: now hidden when `hasDyadProKey(settings)`; refreshed icons/labels for `ManageDyadProButton` > - Updates Smart Context savings copy to “up to 3x” in `ProBanner` and `chat/PromoMessage` > - Adjusts provider config: Dyad `websiteUrl` now `/subscription`; removes Dyad-specific override in `ProviderSettingsPage` > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 3abffd759951f473f9ba0949fdc4f72f49ff5ca3. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated description by cubic. --> ## Summary by cubic Refined the home page with a new hero title and a fixed Dyad Pro button, plus cleaner Pro/Smart Context messaging and styling. - **New Features** - Added a fixed top-right Dyad Pro CTA on home: shows Manage when a Dyad Pro key is present, otherwise Setup. - ImportAppButton now accepts className for flexible layout; updated usage on home. - **Refactors** - Simplified ProBanner: hides when userBudget exists; moved the Pro CTA to home; refreshed icons and label. - Updated Smart Context savings copy to “up to 3x” (was 5x), including chat promo. - Dyad provider URL now points to /subscription; removed Dyad-specific override in ProviderSettingsPage. - Updated e2e tests to expect “Build a new app” headline. <sup>Written for commit 5eee69fc80ee021e6a4684213e04206e1fed76c6. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. -->
上级 f718b968
...@@ -5,5 +5,5 @@ test("renders the first page", async ({ electronApp }) => { ...@@ -5,5 +5,5 @@ test("renders the first page", async ({ electronApp }) => {
const page = await electronApp.firstWindow(); const page = await electronApp.firstWindow();
await page.waitForSelector("h1"); await page.waitForSelector("h1");
const text = await page.$eval("h1", (el) => el.textContent); const text = await page.$eval("h1", (el) => el.textContent);
expect(text).toBe("Build your dream app"); expect(text).toBe("Build a new app");
}); });
...@@ -111,7 +111,7 @@ const testWithMultipleBackups = testWithConfig({ ...@@ -111,7 +111,7 @@ const testWithMultipleBackups = testWithConfig({
const ensureAppIsRunning = async (po: PageObject) => { const ensureAppIsRunning = async (po: PageObject) => {
await po.page.waitForSelector("h1"); await po.page.waitForSelector("h1");
const text = await po.page.$eval("h1", (el) => el.textContent); const text = await po.page.$eval("h1", (el) => el.textContent);
expect(text).toBe("Build your dream app"); expect(text).toBe("Build a new app");
}; };
test("backup is not created for first run", async ({ po }) => { test("backup is not created for first run", async ({ po }) => {
......
...@@ -1177,7 +1177,7 @@ export class PageObject { ...@@ -1177,7 +1177,7 @@ export class PageObject {
async goToAppsTab() { async goToAppsTab() {
await this.page.getByRole("link", { name: "Apps" }).click(); await this.page.getByRole("link", { name: "Apps" }).click();
await expect(this.page.getByText("Build your dream app")).toBeVisible(); await expect(this.page.getByText("Build a new app")).toBeVisible();
} }
async goToChatTab() { async goToChatTab() {
......
...@@ -2,13 +2,14 @@ import { Button } from "@/components/ui/button"; ...@@ -2,13 +2,14 @@ import { Button } from "@/components/ui/button";
import { Upload } from "lucide-react"; import { Upload } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { ImportAppDialog } from "./ImportAppDialog"; import { ImportAppDialog } from "./ImportAppDialog";
import { cn } from "@/lib/utils";
export function ImportAppButton() { export function ImportAppButton({ className }: { className?: string }) {
const [isDialogOpen, setIsDialogOpen] = useState(false); const [isDialogOpen, setIsDialogOpen] = useState(false);
return ( return (
<> <>
<div className="px-4 pb-1 flex justify-center"> <div className={cn("px-4 pb-1 flex justify-center", className)}>
<Button <Button
variant="default" variant="default"
size="default" size="default"
......
...@@ -6,27 +6,23 @@ import googleLogo from "../../assets/ai-logos/google-logo.svg"; ...@@ -6,27 +6,23 @@ import googleLogo from "../../assets/ai-logos/google-logo.svg";
import anthropicLogo from "../../assets/ai-logos/anthropic-logo.svg"; import anthropicLogo from "../../assets/ai-logos/anthropic-logo.svg";
import { IpcClient } from "@/ipc/ipc_client"; import { IpcClient } from "@/ipc/ipc_client";
import { useState } from "react"; import { useState } from "react";
import { KeyRound } from "lucide-react"; import { ArrowUpRight, KeyRound, Wallet } from "lucide-react";
import { useSettings } from "@/hooks/useSettings";
import { useUserBudgetInfo } from "@/hooks/useUserBudgetInfo";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { cn } from "@/lib/utils";
import { hasDyadProKey } from "@/lib/schemas";
import { useSettings } from "@/hooks/useSettings";
export function ProBanner() { export function ProBanner() {
const { settings } = useSettings(); const { settings } = useSettings();
const { userBudget } = useUserBudgetInfo();
const [selectedBanner] = useState<"ai" | "smart" | "turbo">(() => { const [selectedBanner] = useState<"ai" | "smart" | "turbo">(() => {
const options = ["ai", "smart", "turbo"] as const; const options = ["ai", "smart", "turbo"] as const;
return options[Math.floor(Math.random() * options.length)]; return options[Math.floor(Math.random() * options.length)];
}); });
if (settings?.enableDyadPro || userBudget) { if (settings && hasDyadProKey(settings)) {
return ( return null;
<div className="mt-6 max-w-2xl mx-auto">
<ManageDyadProButton />
</div>
);
} }
return ( return (
...@@ -38,25 +34,28 @@ export function ProBanner() { ...@@ -38,25 +34,28 @@ export function ProBanner() {
) : ( ) : (
<TurboBanner /> <TurboBanner />
)} )}
<SetupDyadProButton />
</div> </div>
); );
} }
export function ManageDyadProButton() { export function ManageDyadProButton({ className }: { className?: string }) {
return ( return (
<Button <Button
variant="outline" variant="outline"
size="lg" size="lg"
className="w-full mt-4 bg-(--background-lighter) text-primary" className={cn(
"cursor-pointer w-full mt-4 bg-(--background-lighter) text-primary",
className,
)}
onClick={() => { onClick={() => {
IpcClient.getInstance().openExternalUrl( IpcClient.getInstance().openExternalUrl(
"https://academy.dyad.sh/subscription", "https://academy.dyad.sh/subscription",
); );
}} }}
> >
<KeyRound aria-hidden="true" /> <Wallet aria-hidden="true" className="w-5 h-5" />
Manage Dyad Pro subscription Manage Dyad Pro
<ArrowUpRight aria-hidden="true" className="w-5 h-5" />
</Button> </Button>
); );
} }
...@@ -66,7 +65,7 @@ export function SetupDyadProButton() { ...@@ -66,7 +65,7 @@ export function SetupDyadProButton() {
<Button <Button
variant="outline" variant="outline"
size="lg" size="lg"
className="w-full mt-4 bg-(--background-lighter) text-primary" className="cursor-pointer w-full mt-4 bg-(--background-lighter) text-primary"
onClick={() => { onClick={() => {
IpcClient.getInstance().openExternalUrl( IpcClient.getInstance().openExternalUrl(
"https://academy.dyad.sh/settings", "https://academy.dyad.sh/settings",
...@@ -167,7 +166,7 @@ export function SmartContextBanner() { ...@@ -167,7 +166,7 @@ export function SmartContextBanner() {
<div className="mt-0.5 sm:mt-1 flex items-center gap-2 sm:gap-3 justify-center"> <div className="mt-0.5 sm:mt-1 flex items-center gap-2 sm:gap-3 justify-center">
<div className="flex flex-col items-center text-center"> <div className="flex flex-col items-center text-center">
<div className="text-xl font-semibold tracking-tight text-emerald-900 dark:text-emerald-100"> <div className="text-xl font-semibold tracking-tight text-emerald-900 dark:text-emerald-100">
Up to 5x cheaper Up to 3x cheaper
</div> </div>
<div className="text-sm sm:text-base mt-1 text-emerald-700 dark:text-emerald-200/80"> <div className="text-sm sm:text-base mt-1 text-emerald-700 dark:text-emerald-200/80">
by using Smart Context by using Smart Context
......
...@@ -154,7 +154,7 @@ export function SetupBanner() { ...@@ -154,7 +154,7 @@ export function SetupBanner() {
if (itemsNeedAction.length === 0) { if (itemsNeedAction.length === 0) {
return ( return (
<h1 className="text-center text-5xl font-bold mb-8 bg-clip-text text-transparent bg-gradient-to-r from-gray-900 to-gray-600 dark:from-gray-100 dark:to-gray-400 tracking-tight"> <h1 className="text-center text-5xl font-bold mb-8 bg-clip-text text-transparent bg-gradient-to-r from-gray-900 to-gray-600 dark:from-gray-100 dark:to-gray-400 tracking-tight">
Build your dream app Build a new app
</h1> </h1>
); );
} }
......
...@@ -63,7 +63,7 @@ export const TURBO_EDITS_PROMO_MESSAGE: MessageConfig = { ...@@ -63,7 +63,7 @@ export const TURBO_EDITS_PROMO_MESSAGE: MessageConfig = {
export const SMART_CONTEXT_PROMO_MESSAGE: MessageConfig = { export const SMART_CONTEXT_PROMO_MESSAGE: MessageConfig = {
spans: [ spans: [
{ type: "text", content: "Save up to 5x on AI costs with " }, { type: "text", content: "Save up to 3x on AI costs with " },
{ {
type: "link", type: "link",
content: "Dyad Pro's Smart Context", content: "Dyad Pro's Smart Context",
......
...@@ -66,9 +66,7 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) { ...@@ -66,9 +66,7 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) {
const providerDisplayName = isDyad const providerDisplayName = isDyad
? "Dyad" ? "Dyad"
: (providerData?.name ?? "Unknown Provider"); : (providerData?.name ?? "Unknown Provider");
const providerWebsiteUrl = isDyad const providerWebsiteUrl = providerData?.websiteUrl;
? "https://academy.dyad.sh/settings"
: providerData?.websiteUrl;
const hasFreeTier = isDyad ? false : providerData?.hasFreeTier; const hasFreeTier = isDyad ? false : providerData?.hasFreeTier;
const envVarName = isDyad ? undefined : providerData?.envVarName; const envVarName = isDyad ? undefined : providerData?.envVarName;
......
...@@ -521,7 +521,7 @@ export const CLOUD_PROVIDERS: Record< ...@@ -521,7 +521,7 @@ export const CLOUD_PROVIDERS: Record<
}, },
auto: { auto: {
displayName: "Dyad", displayName: "Dyad",
websiteUrl: "https://academy.dyad.sh/settings", websiteUrl: "https://academy.dyad.sh/subscription",
gatewayPrefix: "dyad/", gatewayPrefix: "dyad/",
}, },
azure: { azure: {
......
...@@ -15,6 +15,7 @@ import { usePostHog } from "posthog-js/react"; ...@@ -15,6 +15,7 @@ import { usePostHog } from "posthog-js/react";
import { PrivacyBanner } from "@/components/TelemetryBanner"; import { PrivacyBanner } from "@/components/TelemetryBanner";
import { INSPIRATION_PROMPTS } from "@/prompts/inspiration_prompts"; import { INSPIRATION_PROMPTS } from "@/prompts/inspiration_prompts";
import { useAppVersion } from "@/hooks/useAppVersion"; import { useAppVersion } from "@/hooks/useAppVersion";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
...@@ -33,7 +34,12 @@ import { ForceCloseDialog } from "@/components/ForceCloseDialog"; ...@@ -33,7 +34,12 @@ import { ForceCloseDialog } from "@/components/ForceCloseDialog";
import type { FileAttachment } from "@/ipc/ipc_types"; import type { FileAttachment } from "@/ipc/ipc_types";
import { NEON_TEMPLATE_IDS } from "@/shared/templates"; import { NEON_TEMPLATE_IDS } from "@/shared/templates";
import { neonTemplateHook } from "@/client_logic/template_hook"; import { neonTemplateHook } from "@/client_logic/template_hook";
import { ProBanner } from "@/components/ProBanner"; import {
ProBanner,
ManageDyadProButton,
SetupDyadProButton,
} from "@/components/ProBanner";
import { hasDyadProKey } from "@/lib/schemas";
// Adding an export for attachments // Adding an export for attachments
export interface HomeSubmitOptions { export interface HomeSubmitOptions {
...@@ -47,6 +53,7 @@ export default function HomePage() { ...@@ -47,6 +53,7 @@ export default function HomePage() {
const setSelectedAppId = useSetAtom(selectedAppIdAtom); const setSelectedAppId = useSetAtom(selectedAppIdAtom);
const { refreshApps } = useLoadApps(); const { refreshApps } = useLoadApps();
const { settings, updateSettings } = useSettings(); const { settings, updateSettings } = useSettings();
const setIsPreviewOpen = useSetAtom(isPreviewOpenAtom); const setIsPreviewOpen = useSetAtom(isPreviewOpenAtom);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [forceCloseDialogOpen, setForceCloseDialogOpen] = useState(false); const [forceCloseDialogOpen, setForceCloseDialogOpen] = useState(false);
...@@ -202,7 +209,14 @@ export default function HomePage() { ...@@ -202,7 +209,14 @@ export default function HomePage() {
// Main Home Page Content // Main Home Page Content
return ( return (
<div className="flex flex-col items-center justify-center max-w-3xl w-full m-auto p-8"> <div className="flex flex-col items-center justify-center max-w-3xl w-full m-auto p-8 relative">
<div className="fixed top-16 right-8 z-50">
{settings && hasDyadProKey(settings) ? (
<ManageDyadProButton className="mt-0 w-auto h-9 px-3 text-base shadow-sm bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm hover:bg-white dark:hover:bg-gray-800" />
) : (
<SetupDyadProButton />
)}
</div>
<ForceCloseDialog <ForceCloseDialog
isOpen={forceCloseDialogOpen} isOpen={forceCloseDialogOpen}
onClose={() => setForceCloseDialogOpen(false)} onClose={() => setForceCloseDialogOpen(false)}
...@@ -211,7 +225,9 @@ export default function HomePage() { ...@@ -211,7 +225,9 @@ export default function HomePage() {
<SetupBanner /> <SetupBanner />
<div className="w-full"> <div className="w-full">
<ImportAppButton /> <div className="flex items-center justify-center gap-4 mb-4">
<ImportAppButton className="px-0 pb-0 flex-none" />
</div>
<HomeChatInput onSubmit={handleSubmit} /> <HomeChatInput onSubmit={handleSubmit} />
<div className="flex flex-col gap-4 mt-2"> <div className="flex flex-col gap-4 mt-2">
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论