mirror of https://github.com/buster-so/buster.git
Merge pull request #1051 from buster-so/big-nate-bus-1883-wrap-dynamic-components-in-wrapper
Big nate bus 1883 wrap dynamic components in wrapper
This commit is contained in:
commit
13e6c85ece
|
@ -0,0 +1,9 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getAppBuildId } from '@/api/server-functions/getAppVersion';
|
||||
|
||||
export const useGetAppBuildId = () => {
|
||||
return useQuery({
|
||||
queryKey: ['app-version'] as const,
|
||||
queryFn: getAppBuildId,
|
||||
});
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
import { queryOptions } from '@tanstack/react-query';
|
||||
import { getAppBuildId } from '@/api/server-functions/getAppVersion';
|
||||
|
||||
export const versionGetAppVersion = queryOptions({
|
||||
queryKey: ['app-version'] as const,
|
||||
queryFn: getAppBuildId,
|
||||
refetchInterval: 20000, // 20 seconds
|
||||
});
|
|
@ -2,7 +2,7 @@ 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,
|
||||
buildId: import.meta.env.VITE_BUILD_ID as string,
|
||||
buildAt: import.meta.env.VITE_BUILD_AT as string,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -4,14 +4,19 @@ import { useEffect } from 'react';
|
|||
import { Button } from '@/components/ui/buttons';
|
||||
import { Card, CardContent, CardFooter } from '@/components/ui/card/CardBase';
|
||||
import { Title } from '@/components/ui/typography';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useMount } from '../../../hooks/useMount';
|
||||
|
||||
export const ErrorCard = ({
|
||||
footer,
|
||||
className,
|
||||
header = 'Looks like we hit an unexpected error',
|
||||
message = "Our team has been notified via Slack. We'll take a look at the issue ASAP and get back to you.",
|
||||
}: {
|
||||
header?: string;
|
||||
message?: string;
|
||||
footer?: React.ReactNode;
|
||||
className?: string;
|
||||
}) => {
|
||||
useMount(() => {
|
||||
console.error('Error in card:', header, message);
|
||||
|
@ -19,7 +24,10 @@ export const ErrorCard = ({
|
|||
|
||||
return (
|
||||
<div
|
||||
className=" flex h-full w-full flex-col items-center absolute inset-0 justify-center bg-linear-to-br bg-background p-8 backdrop-blur-xs backdrop-filter"
|
||||
className={cn(
|
||||
'flex h-full w-full flex-col items-center absolute inset-0 justify-center bg-linear-to-br bg-background p-8 backdrop-blur-xs backdrop-filter',
|
||||
className
|
||||
)}
|
||||
role="alert"
|
||||
>
|
||||
<Card className="-mt-10 max-w-100">
|
||||
|
@ -31,11 +39,13 @@ export const ErrorCard = ({
|
|||
</CardContent>
|
||||
|
||||
<CardFooter className="w-full pt-0">
|
||||
<Link to="/" className="w-full">
|
||||
<Button variant="black" block size="tall">
|
||||
Take me home
|
||||
</Button>
|
||||
</Link>
|
||||
{footer || (
|
||||
<Link to="/" className="w-full">
|
||||
<Button variant="black" block size="tall">
|
||||
Take me home
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
import { useNavigate } from '@tanstack/react-router';
|
||||
import type React from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { Button } from '@/components/ui/buttons';
|
||||
import { Paragraph, Text, Title } from '@/components/ui/typography';
|
||||
import { useIsVersionChanged } from '@/context/AppVersion/useAppVersion';
|
||||
import { useMount } from '@/hooks/useMount';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Route as HomeRoute } from '@/routes/app/_app/home';
|
||||
|
||||
export const LazyErrorBoundary: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
const isChanged = useIsVersionChanged();
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<ErrorBoundary
|
||||
fallbackRender={() => {
|
||||
if (isChanged) {
|
||||
return (
|
||||
<ComponentErrorCard
|
||||
highlightType="info"
|
||||
message="The app has been updated. Please reload the page."
|
||||
title="New version available"
|
||||
buttonText="Reload"
|
||||
onButtonClick={() => {
|
||||
navigate({ reloadDocument: true });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ComponentErrorCard
|
||||
onButtonClick={() => {
|
||||
navigate({ to: HomeRoute.to, reloadDocument: true });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
const TestThrow = () => {
|
||||
const [showThrow, setShowThrow] = useState(false);
|
||||
|
||||
useMount(() => {
|
||||
setTimeout(() => {
|
||||
setShowThrow(true);
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
if (showThrow) {
|
||||
throw new Error('Test error');
|
||||
}
|
||||
|
||||
return 'loading';
|
||||
};
|
||||
|
||||
const ComponentErrorCard: React.FC<{
|
||||
title?: string;
|
||||
message?: string;
|
||||
containerClassName?: string;
|
||||
cardClassName?: string;
|
||||
buttonText?: string;
|
||||
onButtonClick?: () => void;
|
||||
highlightType?: 'danger' | 'info' | 'none';
|
||||
}> = ({
|
||||
containerClassName,
|
||||
cardClassName,
|
||||
highlightType = 'danger',
|
||||
onButtonClick,
|
||||
buttonText = 'Take me home',
|
||||
title = 'We hit an unexpected error',
|
||||
message = "Our team has been notified via Slack. We'll take a look at the issue ASAP and get back to you.",
|
||||
}) => {
|
||||
const style: React.CSSProperties = useMemo(() => {
|
||||
let vars: React.CSSProperties = {};
|
||||
|
||||
if (highlightType === 'danger') {
|
||||
vars = {
|
||||
'--color-highlight-background': 'var(--color-red-100)',
|
||||
'--color-highlight-border': 'red',
|
||||
} as React.CSSProperties;
|
||||
}
|
||||
|
||||
if (highlightType === 'info') {
|
||||
vars = {
|
||||
'--color-highlight-background': 'var(--color-purple-50)',
|
||||
'--color-highlight-border': 'blue',
|
||||
} as React.CSSProperties;
|
||||
}
|
||||
|
||||
return {
|
||||
...vars,
|
||||
'--color-highlight-to-background': 'transparent',
|
||||
'--color-highlight-to-border': 'transparent',
|
||||
'--duration': '600ms',
|
||||
} as React.CSSProperties;
|
||||
}, [highlightType]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'animate-highlight-fade',
|
||||
'flex h-full w-full flex-col items-center justify-center p-5 bg-background text-center gap-4',
|
||||
containerClassName
|
||||
)}
|
||||
style={style}
|
||||
role="alert"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'bg-background flex flex-col gap-4 p-5 rounded duration-300 shadow hover:shadow-lg transition-all border',
|
||||
highlightType === 'danger' && 'shadow-red-100 ',
|
||||
cardClassName
|
||||
)}
|
||||
>
|
||||
<Title as={'h4'}>{title}</Title>
|
||||
<Paragraph>{message}</Paragraph>
|
||||
<Button variant="black" block size="tall" onClick={onButtonClick}>
|
||||
{buttonText}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,5 +1,6 @@
|
|||
import { ClientOnly } from '@tanstack/react-router';
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { LazyErrorBoundary } from '@/components/features/global/LazyErrorBoundary';
|
||||
import { PreparingYourRequestLoader } from './LoadingComponents/ChartLoadingComponents';
|
||||
|
||||
const BusterChartLazy = lazy(() =>
|
||||
|
@ -7,9 +8,11 @@ const BusterChartLazy = lazy(() =>
|
|||
);
|
||||
|
||||
export const BusterChartDynamic = (props: Parameters<typeof BusterChartLazy>[0]) => (
|
||||
<Suspense fallback={<PreparingYourRequestLoader text="Loading chart..." />}>
|
||||
<ClientOnly>
|
||||
<BusterChartLazy {...props} />
|
||||
</ClientOnly>
|
||||
</Suspense>
|
||||
<LazyErrorBoundary>
|
||||
<Suspense fallback={<PreparingYourRequestLoader text="Loading chart..." />}>
|
||||
<ClientOnly>
|
||||
<BusterChartLazy {...props} />
|
||||
</ClientOnly>
|
||||
</Suspense>
|
||||
</LazyErrorBoundary>
|
||||
);
|
||||
|
|
|
@ -5,6 +5,7 @@ import type { EditorProps, OnMount } from '@monaco-editor/react';
|
|||
import { ClientOnly } from '@tanstack/react-router';
|
||||
import type React from 'react';
|
||||
import { forwardRef, lazy, Suspense, useCallback, useMemo } from 'react';
|
||||
import { LazyErrorBoundary } from '@/components/features/global/LazyErrorBoundary';
|
||||
import { useMount } from '@/hooks/useMount';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { isServer } from '@/lib/window';
|
||||
|
@ -178,23 +179,25 @@ export const AppCodeEditor = forwardRef<AppCodeEditorHandle, AppCodeEditorProps>
|
|||
)}
|
||||
style={style}
|
||||
>
|
||||
<ClientOnly fallback={<LoadingCodeEditor />}>
|
||||
<Suspense fallback={<LoadingCodeEditor />}>
|
||||
<Editor
|
||||
key={useDarkMode ? 'dark' : 'light'}
|
||||
height={height}
|
||||
language={language}
|
||||
className={className}
|
||||
defaultValue={defaultValue}
|
||||
value={value}
|
||||
theme={useDarkMode ? 'night-owl' : 'github-light'}
|
||||
onMount={onMountCodeEditor}
|
||||
onChange={onChangeCodeEditor}
|
||||
options={memoizedMonacoEditorOptions}
|
||||
loading={null}
|
||||
/>
|
||||
</Suspense>
|
||||
</ClientOnly>
|
||||
<LazyErrorBoundary>
|
||||
<ClientOnly fallback={<LoadingCodeEditor />}>
|
||||
<Suspense fallback={<LoadingCodeEditor />}>
|
||||
<Editor
|
||||
key={useDarkMode ? 'dark' : 'light'}
|
||||
height={height}
|
||||
language={language}
|
||||
className={className}
|
||||
defaultValue={defaultValue}
|
||||
value={value}
|
||||
theme={useDarkMode ? 'night-owl' : 'github-light'}
|
||||
onMount={onMountCodeEditor}
|
||||
onChange={onChangeCodeEditor}
|
||||
options={memoizedMonacoEditorOptions}
|
||||
loading={null}
|
||||
/>
|
||||
</Suspense>
|
||||
</ClientOnly>
|
||||
</LazyErrorBoundary>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { ClientOnly } from '@tanstack/react-router';
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { LazyErrorBoundary } from '@/components/features/global/LazyErrorBoundary';
|
||||
import type { AppCodeEditorProps } from './AppCodeEditor';
|
||||
import { LoadingCodeEditor } from './LoadingCodeEditor';
|
||||
|
||||
|
@ -13,10 +14,12 @@ const AppCodeEditor = lazy(() =>
|
|||
|
||||
export const AppCodeEditorDynamic = (props: AppCodeEditorProps) => {
|
||||
return (
|
||||
<ClientOnly fallback={<LoadingCodeEditor />}>
|
||||
<Suspense fallback={<LoadingCodeEditor />}>
|
||||
<AppCodeEditor {...props} />
|
||||
</Suspense>
|
||||
</ClientOnly>
|
||||
<LazyErrorBoundary>
|
||||
<ClientOnly fallback={<LoadingCodeEditor />}>
|
||||
<Suspense fallback={<LoadingCodeEditor />}>
|
||||
<AppCodeEditor {...props} />
|
||||
</Suspense>
|
||||
</ClientOnly>
|
||||
</LazyErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { lazy, Suspense } from 'react';
|
||||
import { LazyErrorBoundary } from '@/components/features/global/LazyErrorBoundary';
|
||||
import { type UsePageReadyOptions, usePageReady } from '@/hooks/usePageReady';
|
||||
import type { ReportEditorProps } from './ReportEditor';
|
||||
import { ReportEditorSkeleton } from './ReportEditorSkeleton';
|
||||
|
@ -31,9 +32,11 @@ export const DynamicReportEditor = ({ loadingOptions, ...props }: DynamicReportE
|
|||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={<ReportEditorSkeleton />}>
|
||||
<DynamicReportEditorBase {...props} />
|
||||
</Suspense>
|
||||
<LazyErrorBoundary>
|
||||
<Suspense fallback={<ReportEditorSkeleton />}>
|
||||
<DynamicReportEditorBase {...props} />
|
||||
</Suspense>
|
||||
</LazyErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getAppBuildId } from '@/api/server-functions/getAppVersion';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { versionGetAppVersion } from '@/api/query_keys/version';
|
||||
import { Text } from '@/components/ui/typography';
|
||||
import { useWindowFocus } from '@/hooks/useWindowFocus';
|
||||
import { useBusterNotifications } from '../BusterNotifications';
|
||||
|
||||
const browserBuild = import.meta.env.VITE_BUILD_ID;
|
||||
const browserBuild = import.meta.env.VITE_BUILD_ID as string;
|
||||
|
||||
const checkNewVersion = (buildId: string | undefined): boolean => {
|
||||
if (!buildId || !browserBuild) return false;
|
||||
return buildId !== browserBuild;
|
||||
};
|
||||
|
||||
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 { data, refetch, isFetched } = useQuery(versionGetAppVersion);
|
||||
const isChanged = checkNewVersion(data?.buildId);
|
||||
|
||||
const reloadWindow = () => {
|
||||
window.location.reload();
|
||||
|
@ -23,7 +24,7 @@ export const useAppVersion = () => {
|
|||
useWindowFocus(() => {
|
||||
refetch().then(() => {
|
||||
if (isChanged) {
|
||||
// reloadWindow();
|
||||
reloadWindow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -48,16 +49,16 @@ export const useAppVersion = () => {
|
|||
};
|
||||
|
||||
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);
|
||||
// }, []);
|
||||
// const [countdown, setCountdown] = useState(180);
|
||||
// useEffect(() => {
|
||||
// const interval = setInterval(() => {
|
||||
// setCountdown((prev) => Math.max(prev - 1, 0));
|
||||
// if (countdown === 0) {
|
||||
// window.location.reload();
|
||||
// }
|
||||
// }, 1000);
|
||||
// return () => clearInterval(interval);
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<Text>
|
||||
|
@ -65,3 +66,11 @@ const AppVersionMessage = () => {
|
|||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
export const useIsVersionChanged = () => {
|
||||
const { data = false } = useQuery({
|
||||
...versionGetAppVersion,
|
||||
select: useCallback((data: { buildId: string }) => checkNewVersion(data.buildId), []),
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { ClientOnly, Outlet, useLocation, useNavigate, useSearch } from '@tanstack/react-router';
|
||||
import { lazy, Suspense, useRef, useTransition } from 'react';
|
||||
import { z } from 'zod';
|
||||
import { LazyErrorBoundary } from '@/components/features/global/LazyErrorBoundary';
|
||||
import { AppSplitter, type LayoutSize } from '@/components/ui/layouts/AppSplitter';
|
||||
import { useGetMetricParams } from '@/context/Metrics/useGetMetricParams';
|
||||
import { MetricViewChartController } from '@/controllers/MetricController/MetricViewChartController';
|
||||
|
@ -89,8 +90,10 @@ const MetricEditController = lazy(() =>
|
|||
|
||||
const RightChildren = ({ metricId, renderChart }: { metricId: string; renderChart: boolean }) => {
|
||||
return renderChart ? (
|
||||
<Suspense fallback={<CircleSpinnerLoaderContainer />}>
|
||||
<MetricEditController metricId={metricId} />
|
||||
</Suspense>
|
||||
<LazyErrorBoundary>
|
||||
<Suspense fallback={<CircleSpinnerLoaderContainer />}>
|
||||
<MetricEditController metricId={metricId} />
|
||||
</Suspense>
|
||||
</LazyErrorBoundary>
|
||||
) : null;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue