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 }) => {
const page = await electronApp.firstWindow();
await page.waitForSelector("h1");
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({
const ensureAppIsRunning = async (po: PageObject) => {
await po.page.waitForSelector("h1");
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 }) => {
......
......@@ -1177,7 +1177,7 @@ export class PageObject {
async goToAppsTab() {
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() {
......
......@@ -2,13 +2,14 @@ import { Button } from "@/components/ui/button";
import { Upload } from "lucide-react";
import { useState } from "react";
import { ImportAppDialog } from "./ImportAppDialog";
import { cn } from "@/lib/utils";
export function ImportAppButton() {
export function ImportAppButton({ className }: { className?: string }) {
const [isDialogOpen, setIsDialogOpen] = useState(false);
return (
<>
<div className="px-4 pb-1 flex justify-center">
<div className={cn("px-4 pb-1 flex justify-center", className)}>
<Button
variant="default"
size="default"
......
......@@ -6,27 +6,23 @@ import googleLogo from "../../assets/ai-logos/google-logo.svg";
import anthropicLogo from "../../assets/ai-logos/anthropic-logo.svg";
import { IpcClient } from "@/ipc/ipc_client";
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 { cn } from "@/lib/utils";
import { hasDyadProKey } from "@/lib/schemas";
import { useSettings } from "@/hooks/useSettings";
export function ProBanner() {
const { settings } = useSettings();
const { userBudget } = useUserBudgetInfo();
const [selectedBanner] = useState<"ai" | "smart" | "turbo">(() => {
const options = ["ai", "smart", "turbo"] as const;
return options[Math.floor(Math.random() * options.length)];
});
if (settings?.enableDyadPro || userBudget) {
return (
<div className="mt-6 max-w-2xl mx-auto">
<ManageDyadProButton />
</div>
);
if (settings && hasDyadProKey(settings)) {
return null;
}
return (
......@@ -38,25 +34,28 @@ export function ProBanner() {
) : (
<TurboBanner />
)}
<SetupDyadProButton />
</div>
);
}
export function ManageDyadProButton() {
export function ManageDyadProButton({ className }: { className?: string }) {
return (
<Button
variant="outline"
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={() => {
IpcClient.getInstance().openExternalUrl(
"https://academy.dyad.sh/subscription",
);
}}
>
<KeyRound aria-hidden="true" />
Manage Dyad Pro subscription
<Wallet aria-hidden="true" className="w-5 h-5" />
Manage Dyad Pro
<ArrowUpRight aria-hidden="true" className="w-5 h-5" />
</Button>
);
}
......@@ -66,7 +65,7 @@ export function SetupDyadProButton() {
<Button
variant="outline"
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={() => {
IpcClient.getInstance().openExternalUrl(
"https://academy.dyad.sh/settings",
......@@ -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="flex flex-col items-center text-center">
<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 className="text-sm sm:text-base mt-1 text-emerald-700 dark:text-emerald-200/80">
by using Smart Context
......
......@@ -154,7 +154,7 @@ export function SetupBanner() {
if (itemsNeedAction.length === 0) {
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">
Build your dream app
Build a new app
</h1>
);
}
......
......@@ -63,7 +63,7 @@ export const TURBO_EDITS_PROMO_MESSAGE: MessageConfig = {
export const SMART_CONTEXT_PROMO_MESSAGE: MessageConfig = {
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",
content: "Dyad Pro's Smart Context",
......
......@@ -66,9 +66,7 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) {
const providerDisplayName = isDyad
? "Dyad"
: (providerData?.name ?? "Unknown Provider");
const providerWebsiteUrl = isDyad
? "https://academy.dyad.sh/settings"
: providerData?.websiteUrl;
const providerWebsiteUrl = providerData?.websiteUrl;
const hasFreeTier = isDyad ? false : providerData?.hasFreeTier;
const envVarName = isDyad ? undefined : providerData?.envVarName;
......
......@@ -521,7 +521,7 @@ export const CLOUD_PROVIDERS: Record<
},
auto: {
displayName: "Dyad",
websiteUrl: "https://academy.dyad.sh/settings",
websiteUrl: "https://academy.dyad.sh/subscription",
gatewayPrefix: "dyad/",
},
azure: {
......
......@@ -15,6 +15,7 @@ import { usePostHog } from "posthog-js/react";
import { PrivacyBanner } from "@/components/TelemetryBanner";
import { INSPIRATION_PROMPTS } from "@/prompts/inspiration_prompts";
import { useAppVersion } from "@/hooks/useAppVersion";
import {
Dialog,
DialogContent,
......@@ -33,7 +34,12 @@ import { ForceCloseDialog } from "@/components/ForceCloseDialog";
import type { FileAttachment } from "@/ipc/ipc_types";
import { NEON_TEMPLATE_IDS } from "@/shared/templates";
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
export interface HomeSubmitOptions {
......@@ -47,6 +53,7 @@ export default function HomePage() {
const setSelectedAppId = useSetAtom(selectedAppIdAtom);
const { refreshApps } = useLoadApps();
const { settings, updateSettings } = useSettings();
const setIsPreviewOpen = useSetAtom(isPreviewOpenAtom);
const [isLoading, setIsLoading] = useState(false);
const [forceCloseDialogOpen, setForceCloseDialogOpen] = useState(false);
......@@ -202,7 +209,14 @@ export default function HomePage() {
// Main Home Page Content
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
isOpen={forceCloseDialogOpen}
onClose={() => setForceCloseDialogOpen(false)}
......@@ -211,7 +225,9 @@ export default function HomePage() {
<SetupBanner />
<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} />
<div className="flex flex-col gap-4 mt-2">
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论