app version updated

This commit is contained in:
Nate Kelley 2025-09-22 16:01:17 -06:00
parent ddde4343d0
commit bbcd0db69a
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
7 changed files with 93 additions and 6 deletions

View File

@ -0,0 +1,8 @@
import { createServerFn } from '@tanstack/react-start';
export const getAppBuildId = createServerFn({ method: 'GET' }).handler(async () => {
return {
buildId: import.meta.env.VITE_BUILD_ID,
buildAt: import.meta.env.VITE_BUILD_AT,
};
});

View File

@ -24,11 +24,11 @@ const Toaster = ({ ...props }: ToasterProps) => {
toastOptions={{
classNames: {
toast:
'group toast group-[.toaster]:bg-background! border !p-3 group-[.toaster]:text-foreground! group-[.toaster]:border-border! group-[.toaster]:shadow!',
'group toast group-[.toaster]:bg-background border p-3 group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow',
description: 'group-[.toast]:text-gray-light',
actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
cancelButton: 'group-[.toast]:bg-border group-[.toast]:text-foreground',
icon: 'mx-0! !flex !justify-center',
icon: 'ml-0 mr-1 !flex !justify-center',
},
}}
{...props}

View File

@ -0,0 +1,67 @@
import { useQuery } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
import { getAppBuildId } from '@/api/server-functions/getAppVersion';
import { Text } from '@/components/ui/typography';
import { useWindowFocus } from '@/hooks/useWindowFocus';
import { useBusterNotifications } from '../BusterNotifications';
const browserBuild = import.meta.env.VITE_BUILD_ID;
export const useAppVersion = () => {
const { openInfoNotification } = useBusterNotifications();
const { data, refetch, isFetched } = useQuery({
queryKey: ['app-version'] as const,
queryFn: getAppBuildId,
refetchInterval: 20000, // 20 seconds
});
const isChanged = data?.buildId !== browserBuild && isFetched && browserBuild;
const reloadWindow = () => {
window.location.reload();
};
useWindowFocus(() => {
refetch().then(() => {
if (isChanged) {
reloadWindow();
}
});
});
useEffect(() => {
if (isChanged) {
openInfoNotification({
duration: Infinity,
title: 'New Version Available',
message: <AppVersionMessage />,
dismissible: false,
className: 'min-w-[450px]',
action: {
label: 'Refresh',
onClick: () => {
reloadWindow();
},
},
});
}
}, [isChanged]);
};
const AppVersionMessage = () => {
// const [countdown, setCountdown] = useState(30);
// useEffect(() => {
// const interval = setInterval(() => {
// setCountdown((prev) => Math.max(prev - 1, 0));
// if (countdown === 0) {
// // window.location.reload();
// }
// }, 1000);
// return () => clearInterval(interval);
// }, []);
return (
<Text>
A new version of the app is available. Please refresh the page to get the latest features.
</Text>
);
};

View File

@ -12,16 +12,16 @@ const ConfirmModal = lazy(() =>
import('@/components/ui/modal/ConfirmModal').then((mod) => ({ default: mod.ConfirmModal }))
);
export interface NotificationProps {
export type NotificationProps = {
type?: NotificationType;
title?: string;
message?: string;
message?: string | React.ReactNode;
duration?: number;
action?: {
label: string;
onClick: () => void | (() => Promise<void>);
};
}
} & ExternalToast;
const openNotification = (props: NotificationProps) => {
const { title, message, type } = props;

View File

@ -1,8 +1,8 @@
import type React from 'react';
import type { PropsWithChildren } from 'react';
import { BusterPosthogProvider } from '@/context/Posthog';
import { useAppVersion } from './AppVersion/useAppVersion';
import { BusterStyleProvider } from './BusterStyles';
import {
SupabaseContextProvider,
type SupabaseContextType,
@ -16,6 +16,8 @@ export const AppProviders: React.FC<PropsWithChildren<SupabaseContextType>> = ({
children,
supabaseSession,
}) => {
useAppVersion();
return (
<SupabaseContextProvider supabaseSession={supabaseSession}>
<BusterPosthogProvider>{children}</BusterPosthogProvider>

View File

@ -28,6 +28,7 @@ export const Route = createFileRoute('/app/_app')({
},
component: () => {
const { initialLayout, defaultLayout } = Route.useLoaderData();
return (
<PrimaryAppLayout
initialLayout={initialLayout}

View File

@ -13,8 +13,17 @@ const config = defineConfig(({ command, mode }) => {
const isLocalBuild = process.argv.includes('--local') || mode === 'development';
const target = isLocalBuild ? ('bun' as const) : ('vercel' as const);
// Generate a unique build ID for cache busting after deployments
const buildId = `build:${process.env.VERCEL_GIT_COMMIT_SHA?.slice(0, 8) || process.env.BUILD_ID || Date.now().toString()}`;
const buildAt = new Date().toISOString();
return {
server: { port: 3000 },
define: {
// Make the build ID available to the app for version tracking
'import.meta.env.VITE_BUILD_ID': JSON.stringify(buildId),
'import.meta.env.VITE_BUILD_AT': JSON.stringify(buildAt),
},
plugins: [
// this is the plugin that enables path aliases
viteTsConfigPaths({ projects: ['./tsconfig.json'] }),