Show release notes on startup (#44)

This commit is contained in:
Will Chen
2025-04-29 11:02:27 -07:00
committed by GitHub
parent c612017a20
commit 37928a9017
5 changed files with 112 additions and 16 deletions

View File

@@ -0,0 +1,20 @@
import { useState, useEffect } from "react";
import { IpcClient } from "@/ipc/ipc_client";
export function useAppVersion() {
const [appVersion, setAppVersion] = useState<string | null>(null);
useEffect(() => {
const fetchVersion = async () => {
try {
const version = await IpcClient.getInstance().getAppVersion();
setAppVersion(version);
} catch (error) {
setAppVersion(null);
}
};
fetchVersion();
}, []);
return appVersion;
}

View File

@@ -117,6 +117,7 @@ export const UserSettingsSchema = z.object({
enableDyadPro: z.boolean().optional(),
dyadProBudget: DyadProBudgetSchema.optional(),
experiments: ExperimentsSchema.optional(),
lastShownReleaseNotesVersion: z.string().optional(),
// DEPRECATED.
runtimeMode: RuntimeModeSchema.optional(),
});

View File

@@ -14,6 +14,16 @@ import { HomeChatInput } from "@/components/chat/HomeChatInput";
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,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { useTheme } from "@/contexts/ThemeContext";
import { Button } from "@/components/ui/button";
import { ExternalLink } from "lucide-react";
export default function HomePage() {
const [inputValue, setInputValue] = useAtom(homeChatInputValueAtom);
@@ -21,11 +31,40 @@ export default function HomePage() {
const search = useSearch({ from: "/" });
const setSelectedAppId = useSetAtom(selectedAppIdAtom);
const { refreshApps } = useLoadApps();
const { settings, isAnyProviderSetup } = useSettings();
const { settings, updateSettings } = useSettings();
const setIsPreviewOpen = useSetAtom(isPreviewOpenAtom);
const [isLoading, setIsLoading] = useState(false);
const { streamMessage } = useStreamChat({ hasChatId: false });
const posthog = usePostHog();
const appVersion = useAppVersion();
const [releaseNotesOpen, setReleaseNotesOpen] = useState(false);
const [releaseUrl, setReleaseUrl] = useState("");
const { theme } = useTheme();
useEffect(() => {
const updateLastVersionLaunched = async () => {
if (
appVersion &&
appVersion.match(/^\d+\.\d+\.\d+$/) &&
settings &&
settings.lastShownReleaseNotesVersion !== appVersion
) {
await updateSettings({
lastShownReleaseNotesVersion: appVersion,
});
// Check if release notes exist for this version
const url = `https://www.dyad.sh/docs/releases/${appVersion}`;
const exists = await checkPageExists(url);
if (exists) {
setReleaseUrl(url + "?hideHeader=true&theme=" + theme);
setReleaseNotesOpen(true);
}
}
};
updateLastVersionLaunched();
}, [appVersion, settings, updateSettings]);
// Get the appId from search params
const appId = search.appId ? Number(search.appId) : null;
@@ -165,6 +204,51 @@ export default function HomePage() {
</div>
</div>
<PrivacyBanner />
{/* Release Notes Dialog */}
<Dialog open={releaseNotesOpen} onOpenChange={setReleaseNotesOpen}>
<DialogContent className="max-w-4xl bg-(--docs-bg) pr-0 pt-4 pl-4 gap-1">
<DialogHeader>
<DialogTitle>What's new in v{appVersion}?</DialogTitle>
<Button
variant="ghost"
size="sm"
className="absolute right-10 top-2 focus-visible:ring-0 focus-visible:ring-offset-0"
onClick={() =>
window.open(
releaseUrl.replace("?hideHeader=true&theme=" + theme, ""),
"_blank"
)
}
>
<ExternalLink className="w-4 h-4" />
</Button>
</DialogHeader>
<div className="overflow-auto h-[70vh] flex flex-col ">
{releaseUrl && (
<div className="flex-1">
<iframe
src={releaseUrl}
className="w-full h-full border-0 rounded-lg"
title={`Release notes for v${appVersion}`}
/>
</div>
)}
</div>
</DialogContent>
</Dialog>
</div>
);
}
function checkPageExists(url: string) {
return fetch(url, { mode: "no-cors" })
.then(() => {
// Promise resolved - resource likely exists
return true;
})
.catch(() => {
// Promise rejected - resource likely doesn't exist or network error
return false;
});
}

View File

@@ -7,6 +7,7 @@ import { showSuccess, showError } from "@/lib/toast";
import { AutoApproveSwitch } from "@/components/AutoApproveSwitch";
import { TelemetrySwitch } from "@/components/TelemetrySwitch";
import { useSettings } from "@/hooks/useSettings";
import { useAppVersion } from "@/hooks/useAppVersion";
import { Button } from "@/components/ui/button";
import { ArrowLeft } from "lucide-react";
import { useRouter } from "@tanstack/react-router";
@@ -16,23 +17,10 @@ export default function SettingsPage() {
const { theme, setTheme } = useTheme();
const [isResetDialogOpen, setIsResetDialogOpen] = useState(false);
const [isResetting, setIsResetting] = useState(false);
const [appVersion, setAppVersion] = useState<string | null>(null);
const appVersion = useAppVersion();
const { settings } = useSettings();
const router = useRouter();
useEffect(() => {
// Fetch app version
const fetchVersion = async () => {
try {
const version = await IpcClient.getInstance().getAppVersion();
setAppVersion(version);
} catch (error) {
setAppVersion(null);
}
};
fetchVersion();
}, []);
const handleResetEverything = async () => {
setIsResetting(true);
try {

View File

@@ -66,7 +66,7 @@
height: calc(100vh - 64px);
}
:root {
--docs-bg: #f5f5f5;
--default-font-family: 'Geist', sans-serif;
--default-mono-font-family: 'Geist Mono', monospace;
@@ -120,6 +120,9 @@
--tw-prose-pre-bg: var(--background);
}
.dark {
--docs-bg: #121212;
--background-lightest: oklch(0.285 0 0);
--background-lighter: oklch(0.185 0 0);
--background: oklch(0.145 0 0);