diff --git a/apps/web/package.json b/apps/web/package.json index fdef9a867..7f9f96095 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -8,7 +8,7 @@ "build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build -- --typecheck", "build:staging": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build --mode staging", "build:production": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build --mode production -- --typecheck", - "build:local": "cross-env NODE_OPTIONS=--max-old-space-size=12288 vite build -- --typecheck --local", + "build:local": "pnpm prebuild && cross-env NODE_OPTIONS=--max-old-space-size=12288 vite build -- --typecheck --local", "build-storybook": "storybook build", "build:visualize": "npx vite-bundle-visualizer", "deploy:dev": "pnpm run build && npx wrangler deploy .output/server/index.mjs --env dev --assets .output/public", diff --git a/apps/web/src/api/createAxiosInstance.ts b/apps/web/src/api/createAxiosInstance.ts index 68a78955b..f33cc22c8 100644 --- a/apps/web/src/api/createAxiosInstance.ts +++ b/apps/web/src/api/createAxiosInstance.ts @@ -69,7 +69,6 @@ export const defaultAxiosRequestHandler = async (config: InternalAxiosRequestCon token = await getSupabaseSessionServerFn().then( ({ data: { session } }) => session.access_token ); - console.log('token', token); } else { // Always check token validity before making requests const tokenResult = await checkTokenValidity(); diff --git a/apps/web/src/api/server-functions/getSupabaseSession.ts b/apps/web/src/api/server-functions/getSupabaseSession.ts index 5316b5816..082633324 100644 --- a/apps/web/src/api/server-functions/getSupabaseSession.ts +++ b/apps/web/src/api/server-functions/getSupabaseSession.ts @@ -11,7 +11,6 @@ export const getSupabaseSessionServerFn = createServerFn({ method: 'GET' }).hand expires_at: session?.expires_at, expires_in: session?.expires_in, }; - console.log('pickedSession', JSON.stringify(pickedSession)); return { data: { diff --git a/apps/web/src/components/features/auth/SignOutHandler.tsx b/apps/web/src/components/features/auth/SignOutHandler.tsx index 3ccd58d38..e76cfaeef 100644 --- a/apps/web/src/components/features/auth/SignOutHandler.tsx +++ b/apps/web/src/components/features/auth/SignOutHandler.tsx @@ -4,8 +4,6 @@ import { useBusterNotifications } from '@/context/BusterNotifications'; import { signOut } from '@/integrations/supabase/signOut'; import { clearAllBrowserStorage } from '@/lib/storage'; -const navigate = useNavigate(); - export const useSignOut = () => { const { openErrorMessage } = useBusterNotifications(); const navigate = useNavigate(); @@ -14,19 +12,17 @@ export const useSignOut = () => { // Then perform server-side sign out await signOut(); - // First clear all client-side storage - clearAllBrowserStorage(); - - navigate({ to: '/auth/login' }); - - // Clear all cookies - for (const cookie of document.cookie.split(';')) { - const cookieName = cookie.replace(/^ +/, '').split('=')[0]; - // biome-ignore lint/suspicious/noDocumentCookie: clearing cookies - document.cookie = `${cookieName}=;expires=${new Date().toUTCString()};path=/`; + try { + // First clear all client-side storage + clearAllBrowserStorage(); + } catch (error) { + console.error('Error clearing browser storage', error); } } catch (error) { + console.error('Error signing out', error); openErrorMessage('Error signing out'); + } finally { + navigate({ to: '/auth/login' }); } }, [navigate, openErrorMessage]); diff --git a/apps/web/src/components/features/global/ComponentErrorCard.tsx b/apps/web/src/components/features/global/ComponentErrorCard.tsx new file mode 100644 index 000000000..3a552be6f --- /dev/null +++ b/apps/web/src/components/features/global/ComponentErrorCard.tsx @@ -0,0 +1,24 @@ +import { ErrorBoundary } from 'react-error-boundary'; +import { rustErrorHandler } from '@/api/errors'; +import { ErrorCard } from './GlobalErrorCard'; + +export const ComponentErrorCard = ({ + children, + header, + message, +}: { + children: React.ReactNode; + header?: string; + message?: string; +}) => { + return ( + { + const errorMessage: string | undefined = rustErrorHandler(e).message || undefined; + return ; + }} + > + {children} + + ); +}; diff --git a/apps/web/src/components/features/global/GlobalErrorCard.tsx b/apps/web/src/components/features/global/GlobalErrorCard.tsx index cdae18618..a23007161 100644 --- a/apps/web/src/components/features/global/GlobalErrorCard.tsx +++ b/apps/web/src/components/features/global/GlobalErrorCard.tsx @@ -3,8 +3,19 @@ import { usePostHog } from 'posthog-js/react'; import { useEffect } from 'react'; import { Button } from '@/components/ui/buttons'; import { Card, CardContent, CardFooter } from '@/components/ui/card/CardBase'; +import { useMount } from '../../../hooks/useMount'; + +export const ErrorCard = ({ + 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; +}) => { + useMount(() => { + console.error('Error in card:', header, message); + }); -export const ErrorCard = () => { return (
{
-

Looks like we hit an unexpected error

+

{header}

-
- {`Our team has been notified via Slack. We'll take a look at the issue ASAP and get back to you.`} -
+
{message}
@@ -42,8 +51,6 @@ export const GlobalErrorCard: ErrorRouteComponent = ({ error }) => { if (isPosthogLoaded) { posthog.captureException(error); } - - console.error('GlobalErrorCard error', error); }, [error]); return ; diff --git a/apps/web/src/components/features/sidebars/SidebarSettings.tsx b/apps/web/src/components/features/sidebars/SidebarSettings.tsx index c4ff9becb..6802f7f2c 100644 --- a/apps/web/src/components/features/sidebars/SidebarSettings.tsx +++ b/apps/web/src/components/features/sidebars/SidebarSettings.tsx @@ -6,6 +6,7 @@ import CircleUser from '@/components/ui/icons/NucleoIconOutlined/circle-user'; import { createSidebarGroup } from '@/components/ui/sidebar/create-sidebar-item'; import LockCircle from '../../ui/icons/NucleoIconOutlined/lock-circle'; import { type ISidebarGroup, Sidebar } from '../../ui/sidebar'; +import { ComponentErrorCard } from '../global/ComponentErrorCard'; import { SidebarUserFooter } from './SidebarUserFooter'; const accountItems: ISidebarGroup = createSidebarGroup({ @@ -107,12 +108,14 @@ export const SidebarSettings = () => { }, [isAdmin]); return ( - , [])} - footer={useMemo(() => , [])} - useCollapsible={isUserRegistered} - /> + + , [])} + footer={useMemo(() => , [])} + useCollapsible={isUserRegistered} + /> + ); }; diff --git a/apps/web/src/components/ui/report/elements/InlineCombobox.tsx b/apps/web/src/components/ui/report/elements/InlineCombobox.tsx index 5370f62a1..504e87980 100644 --- a/apps/web/src/components/ui/report/elements/InlineCombobox.tsx +++ b/apps/web/src/components/ui/report/elements/InlineCombobox.tsx @@ -210,7 +210,6 @@ const InlineComboboxInput = React.forwardRef< })?.width + 8 ); }, [placeholder]); - console.log(placeHolderWidth); /** * To create an auto-resizing input, we render a visually hidden span diff --git a/apps/web/src/context/Posthog/BusterPosthogProvider.tsx b/apps/web/src/context/Posthog/BusterPosthogProvider.tsx index f399b1cb1..211459ce3 100644 --- a/apps/web/src/context/Posthog/BusterPosthogProvider.tsx +++ b/apps/web/src/context/Posthog/BusterPosthogProvider.tsx @@ -7,6 +7,7 @@ import { useGetUserBasicInfo, useGetUserOrganization, } from '@/api/buster_rest/users/useGetUserInfo'; +import { ComponentErrorCard } from '@/components/features/global/ComponentErrorCard'; import { isDev } from '@/config/dev'; import { env } from '@/env'; import packageJson from '../../../package.json'; @@ -16,16 +17,18 @@ const POSTHOG_KEY = env.VITE_PUBLIC_POSTHOG_KEY; const DEBUG_POSTHOG = false; export const BusterPosthogProvider: React.FC = ({ children }) => { - console.log('POSTHOG_KEY', POSTHOG_KEY); - console.log('DEBUG_POSTHOG', DEBUG_POSTHOG); if ((isDev && !DEBUG_POSTHOG) || !POSTHOG_KEY) { return <>{children}; } - console.log('POSTHOG_KEY22', POSTHOG_KEY); - console.log('DEBUG_POSTHOG22', DEBUG_POSTHOG); - - return {children}; + return ( + + {children} + + ); }; BusterPosthogProvider.displayName = 'BusterPosthogProvider'; @@ -49,7 +52,6 @@ const options: Partial = { }; const PosthogWrapper: React.FC = ({ children }) => { - console.log('PosthogWrapper'); const user = useGetUserBasicInfo(); const { data: userTeams } = useGetUserTeams({ userId: user?.id ?? '' }); const userOrganizations = useGetUserOrganization(); @@ -113,8 +115,6 @@ const PosthogWrapper: React.FC = ({ children }) => { return <>{children}; } - console.log('PostHogProvider!!!!', PostHogProvider); - return ( {children} diff --git a/apps/web/src/context/Providers.tsx b/apps/web/src/context/Providers.tsx index 5f586fd02..f7889cbd4 100644 --- a/apps/web/src/context/Providers.tsx +++ b/apps/web/src/context/Providers.tsx @@ -17,8 +17,6 @@ export const AppProviders: React.FC> = ({ user, accessToken, }) => { - console.log('user11', user); - console.log('accessToken11', accessToken); return ( {children} diff --git a/apps/web/src/context/Supabase/SupabaseContextProvider.tsx b/apps/web/src/context/Supabase/SupabaseContextProvider.tsx index 543957981..3535f35fb 100644 --- a/apps/web/src/context/Supabase/SupabaseContextProvider.tsx +++ b/apps/web/src/context/Supabase/SupabaseContextProvider.tsx @@ -66,10 +66,7 @@ export const SupabaseContext = createContext = React.memo(({ user, accessToken, children }) => { - console.log('user22', user); - console.log('accessToken22', accessToken); const value = useSupabaseContextInternal({ user, accessToken }); - console.log('value22', value); return {children}; }); diff --git a/apps/web/src/integrations/supabase/signIn.ts b/apps/web/src/integrations/supabase/signIn.ts index c3b25903a..8d4bc3e69 100644 --- a/apps/web/src/integrations/supabase/signIn.ts +++ b/apps/web/src/integrations/supabase/signIn.ts @@ -13,8 +13,6 @@ const isValidRedirectUrl = (url: string): boolean => { } }; -const fallbackCallbackUrl = `${env.VITE_PUBLIC_URL}/auth/callback`; - // Common OAuth handler to reduce code duplication const handleOAuthSignIn = async ( provider: 'google' | 'github' | 'azure', @@ -55,6 +53,7 @@ export const signInWithEmailAndPassword = createServerFn({ method: 'POST' }) ) .handler(async ({ data }) => { const supabase = getSupabaseServerClient(); + const { error } = await supabase.auth.signInWithPassword({ email: data.email, password: data.password, diff --git a/apps/web/src/layouts/SettingsAppLayout.tsx b/apps/web/src/layouts/SettingsAppLayout.tsx index 5ef4d40ec..51efe3a2c 100644 --- a/apps/web/src/layouts/SettingsAppLayout.tsx +++ b/apps/web/src/layouts/SettingsAppLayout.tsx @@ -1,4 +1,5 @@ import type React from 'react'; +import { ComponentErrorCard } from '@/components/features/global/ComponentErrorCard'; import { SidebarSettings } from '@/components/features/sidebars/SidebarSettings'; import { AppLayout, type LayoutSize } from '@/components/ui/layouts/AppLayout'; @@ -16,13 +17,15 @@ export const SettingsAppLayout: React.FC = ({ defaultLayout, }) => { return ( - } - > - {children} - + + } + > + {children} + + ); }; diff --git a/apps/web/src/routeTree.gen.ts b/apps/web/src/routeTree.gen.ts index a86acfffe..01d958b52 100644 --- a/apps/web/src/routeTree.gen.ts +++ b/apps/web/src/routeTree.gen.ts @@ -20,6 +20,7 @@ import { Route as AppIndexRouteImport } from './routes/app/index' import { Route as AuthResetPasswordRouteImport } from './routes/auth.reset-password' import { Route as AuthLogoutRouteImport } from './routes/auth.logout' import { Route as AuthLoginRouteImport } from './routes/auth.login' +import { Route as AppHealthcheckRouteImport } from './routes/app/healthcheck' import { Route as AppSettingsRouteImport } from './routes/app/_settings' import { Route as AppAppRouteImport } from './routes/app/_app' import { Route as EmbedReportReportIdRouteImport } from './routes/embed.report.$reportId' @@ -29,6 +30,7 @@ import { Route as AppSettingsRestricted_layoutRouteImport } from './routes/app/_ import { Route as AppSettingsPermissionsRouteImport } from './routes/app/_settings/_permissions' import { Route as AppAppHomeRouteImport } from './routes/app/_app/home' import { Route as AppAppAssetRouteImport } from './routes/app/_app/_asset' +import { Route as AppSettingsSettingsIndexRouteImport } from './routes/app/_settings/settings.index' import { Route as AppAppReportsIndexRouteImport } from './routes/app/_app/reports.index' import { Route as AppAppMetricsIndexRouteImport } from './routes/app/_app/metrics.index' import { Route as AppAppLogsIndexRouteImport } from './routes/app/_app/logs.index' @@ -198,6 +200,11 @@ const AuthLoginRoute = AuthLoginRouteImport.update({ path: '/login', getParentRoute: () => AuthRoute, } as any) +const AppHealthcheckRoute = AppHealthcheckRouteImport.update({ + id: '/healthcheck', + path: '/healthcheck', + getParentRoute: () => AppRoute, +} as any) const AppSettingsRoute = AppSettingsRouteImport.update({ id: '/_settings', getParentRoute: () => AppRoute, @@ -240,6 +247,12 @@ const AppAppAssetRoute = AppAppAssetRouteImport.update({ id: '/_asset', getParentRoute: () => AppAppRoute, } as any) +const AppSettingsSettingsIndexRoute = + AppSettingsSettingsIndexRouteImport.update({ + id: '/settings/', + path: '/settings/', + getParentRoute: () => AppSettingsRoute, + } as any) const AppAppReportsIndexRoute = AppAppReportsIndexRouteImport.update({ id: '/reports/', path: '/reports/', @@ -913,6 +926,7 @@ export interface FileRoutesByFullPath { '/app': typeof AppSettingsRestricted_layoutAdmin_onlyRouteWithChildren '/auth': typeof AuthRouteWithChildren '/healthcheck': typeof HealthcheckRoute + '/app/healthcheck': typeof AppHealthcheckRoute '/auth/login': typeof AuthLoginRoute '/auth/logout': typeof AuthLogoutRoute '/auth/reset-password': typeof AuthResetPasswordRoute @@ -929,6 +943,7 @@ export interface FileRoutesByFullPath { '/app/logs': typeof AppAppLogsIndexRoute '/app/metrics': typeof AppAppMetricsIndexRoute '/app/reports': typeof AppAppReportsIndexRoute + '/app/settings': typeof AppSettingsSettingsIndexRoute '/app/chats/$chatId': typeof AppAppAssetChatsChatIdRouteWithChildren '/app/datasets/$datasetId/editor': typeof AppAppDatasetsDatasetIdEditorRoute '/app/datasets/$datasetId/overview': typeof AppAppDatasetsDatasetIdOverviewRoute @@ -1018,6 +1033,7 @@ export interface FileRoutesByTo { '/auth': typeof AuthRouteWithChildren '/healthcheck': typeof HealthcheckRoute '/app': typeof AppSettingsRestricted_layoutAdmin_onlyRouteWithChildren + '/app/healthcheck': typeof AppHealthcheckRoute '/auth/login': typeof AuthLoginRoute '/auth/logout': typeof AuthLogoutRoute '/auth/reset-password': typeof AuthResetPasswordRoute @@ -1033,6 +1049,7 @@ export interface FileRoutesByTo { '/app/logs': typeof AppAppLogsIndexRoute '/app/metrics': typeof AppAppMetricsIndexRoute '/app/reports': typeof AppAppReportsIndexRoute + '/app/settings': typeof AppSettingsSettingsIndexRoute '/app/datasets/$datasetId/editor': typeof AppAppDatasetsDatasetIdEditorRoute '/app/datasets/$datasetId/overview': typeof AppAppDatasetsDatasetIdOverviewRoute '/app/settings/profile': typeof AppSettingsRestricted_layoutSettingsProfileRoute @@ -1108,6 +1125,7 @@ export interface FileRoutesById { '/healthcheck': typeof HealthcheckRoute '/app/_app': typeof AppAppRouteWithChildren '/app/_settings': typeof AppSettingsRouteWithChildren + '/app/healthcheck': typeof AppHealthcheckRoute '/auth/login': typeof AuthLoginRoute '/auth/logout': typeof AuthLogoutRoute '/auth/reset-password': typeof AuthResetPasswordRoute @@ -1128,6 +1146,7 @@ export interface FileRoutesById { '/app/_app/logs/': typeof AppAppLogsIndexRoute '/app/_app/metrics/': typeof AppAppMetricsIndexRoute '/app/_app/reports/': typeof AppAppReportsIndexRoute + '/app/_settings/settings/': typeof AppSettingsSettingsIndexRoute '/app/_app/_asset/chats/$chatId': typeof AppAppAssetChatsChatIdRouteWithChildren '/app/_app/datasets/$datasetId/editor': typeof AppAppDatasetsDatasetIdEditorRoute '/app/_app/datasets/$datasetId/overview': typeof AppAppDatasetsDatasetIdOverviewRoute @@ -1229,6 +1248,7 @@ export interface FileRouteTypes { | '/app' | '/auth' | '/healthcheck' + | '/app/healthcheck' | '/auth/login' | '/auth/logout' | '/auth/reset-password' @@ -1245,6 +1265,7 @@ export interface FileRouteTypes { | '/app/logs' | '/app/metrics' | '/app/reports' + | '/app/settings' | '/app/chats/$chatId' | '/app/datasets/$datasetId/editor' | '/app/datasets/$datasetId/overview' @@ -1334,6 +1355,7 @@ export interface FileRouteTypes { | '/auth' | '/healthcheck' | '/app' + | '/app/healthcheck' | '/auth/login' | '/auth/logout' | '/auth/reset-password' @@ -1349,6 +1371,7 @@ export interface FileRouteTypes { | '/app/logs' | '/app/metrics' | '/app/reports' + | '/app/settings' | '/app/datasets/$datasetId/editor' | '/app/datasets/$datasetId/overview' | '/app/settings/profile' @@ -1423,6 +1446,7 @@ export interface FileRouteTypes { | '/healthcheck' | '/app/_app' | '/app/_settings' + | '/app/healthcheck' | '/auth/login' | '/auth/logout' | '/auth/reset-password' @@ -1443,6 +1467,7 @@ export interface FileRouteTypes { | '/app/_app/logs/' | '/app/_app/metrics/' | '/app/_app/reports/' + | '/app/_settings/settings/' | '/app/_app/_asset/chats/$chatId' | '/app/_app/datasets/$datasetId/editor' | '/app/_app/datasets/$datasetId/overview' @@ -1627,6 +1652,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthLoginRouteImport parentRoute: typeof AuthRoute } + '/app/healthcheck': { + id: '/app/healthcheck' + path: '/healthcheck' + fullPath: '/app/healthcheck' + preLoaderRoute: typeof AppHealthcheckRouteImport + parentRoute: typeof AppRoute + } '/app/_settings': { id: '/app/_settings' path: '' @@ -1690,6 +1722,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AppAppAssetRouteImport parentRoute: typeof AppAppRoute } + '/app/_settings/settings/': { + id: '/app/_settings/settings/' + path: '/settings' + fullPath: '/app/settings' + preLoaderRoute: typeof AppSettingsSettingsIndexRouteImport + parentRoute: typeof AppSettingsRoute + } '/app/_app/reports/': { id: '/app/_app/reports/' path: '/reports' @@ -3120,12 +3159,14 @@ const AppSettingsRestricted_layoutRouteWithChildren = interface AppSettingsRouteChildren { AppSettingsPermissionsRoute: typeof AppSettingsPermissionsRouteWithChildren AppSettingsRestricted_layoutRoute: typeof AppSettingsRestricted_layoutRouteWithChildren + AppSettingsSettingsIndexRoute: typeof AppSettingsSettingsIndexRoute } const AppSettingsRouteChildren: AppSettingsRouteChildren = { AppSettingsPermissionsRoute: AppSettingsPermissionsRouteWithChildren, AppSettingsRestricted_layoutRoute: AppSettingsRestricted_layoutRouteWithChildren, + AppSettingsSettingsIndexRoute: AppSettingsSettingsIndexRoute, } const AppSettingsRouteWithChildren = AppSettingsRoute._addFileChildren( @@ -3135,12 +3176,14 @@ const AppSettingsRouteWithChildren = AppSettingsRoute._addFileChildren( interface AppRouteChildren { AppAppRoute: typeof AppAppRouteWithChildren AppSettingsRoute: typeof AppSettingsRouteWithChildren + AppHealthcheckRoute: typeof AppHealthcheckRoute AppIndexRoute: typeof AppIndexRoute } const AppRouteChildren: AppRouteChildren = { AppAppRoute: AppAppRouteWithChildren, AppSettingsRoute: AppSettingsRouteWithChildren, + AppHealthcheckRoute: AppHealthcheckRoute, AppIndexRoute: AppIndexRoute, } diff --git a/apps/web/src/routes/app.tsx b/apps/web/src/routes/app.tsx index 6570e4859..754184f44 100644 --- a/apps/web/src/routes/app.tsx +++ b/apps/web/src/routes/app.tsx @@ -28,22 +28,14 @@ export const Route = createFileRoute('/app')({ loader: async ({ context }) => { const { queryClient, accessToken } = context; try { - const [initialLayout, user, userInfo, userFavorites, listDatasources, datasets] = - await Promise.all([ - getAppLayout({ id: PRIMARY_APP_LAYOUT_ID }), - getSupabaseUser(), - prefetchGetMyUserInfo(queryClient), - prefetchGetUserFavorites(queryClient), - prefetchListDatasources(queryClient), - prefetchGetDatasets(queryClient), - ]); - - console.log('initialLayout', initialLayout); - console.log('user', user); - console.log('userInfo', userInfo); - console.log('userFavorites', userFavorites); - console.log('listDatasources', listDatasources); - console.log('datasets', datasets); + const [initialLayout, user] = await Promise.all([ + getAppLayout({ id: PRIMARY_APP_LAYOUT_ID }), + getSupabaseUser(), + prefetchGetMyUserInfo(queryClient), + prefetchGetUserFavorites(queryClient), + prefetchListDatasources(queryClient), + prefetchGetDatasets(queryClient), + ]); if (!user) { throw redirect({ to: '/auth/login' }); @@ -63,8 +55,6 @@ export const Route = createFileRoute('/app')({ }, component: () => { const { user, accessToken } = Route.useLoaderData(); - console.log('user', user); - console.log('accessToken', accessToken); return ( diff --git a/apps/web/src/routes/app/_settings.tsx b/apps/web/src/routes/app/_settings.tsx index 27e4adce5..f33c6e916 100644 --- a/apps/web/src/routes/app/_settings.tsx +++ b/apps/web/src/routes/app/_settings.tsx @@ -6,6 +6,7 @@ const routeApi = getRouteApi('/app'); export const Route = createFileRoute('/app/_settings')({ component: () => { const { initialLayout, layoutId, defaultLayout } = routeApi.useLoaderData(); + return ( Hello "/app/_settings/"!
; +} diff --git a/apps/web/src/routes/app/healthcheck.tsx b/apps/web/src/routes/app/healthcheck.tsx new file mode 100644 index 000000000..cd4b3da46 --- /dev/null +++ b/apps/web/src/routes/app/healthcheck.tsx @@ -0,0 +1,375 @@ +import type { HealthCheckResponse } from '@buster/server-shared/healthcheck'; +import { createFileRoute } from '@tanstack/react-router'; +import { useHealthcheck } from '@/api/buster_rest/healthcheck/queryRequests'; +import { useGetUserBasicInfo } from '@/api/buster_rest/users/useGetUserInfo'; +import type { RustApiError } from '@/api/errors'; +import { useGetSupabaseUser } from '@/context/Supabase'; + +export const Route = createFileRoute('/app/healthcheck')({ + component: RouteComponent, +}); + +function RouteComponent() { + const { data, isLoading, error } = useHealthcheck(); + const supabaseUser = useGetSupabaseUser(); + const user = useGetUserBasicInfo(); + + if (isLoading) { + return ; + } + + if (error) { + return ; + } + + if (!data) { + return ; + } + + return ; +} + +function LoadingState() { + return ( +
+
+
+

Checking system health...

+
+
+ ); +} + +function ErrorState({ error }: { error: Error | RustApiError }) { + return ( +
+
+
+
+ + + +
+
+

Health Check Failed

+

Unable to retrieve system status

+
+
+
+

{error.message}

+
+
+
+ ); +} + +function HealthcheckDashboard({ + data, + supabaseUser, + user, +}: { + data: HealthCheckResponse; + supabaseUser: ReturnType; + user: ReturnType; +}) { + const getStatusColor = (status: string) => { + switch (status) { + case 'healthy': + case 'pass': + return 'text-green-600 bg-green-100'; + case 'degraded': + case 'warn': + return 'text-yellow-600 bg-yellow-100'; + case 'unhealthy': + case 'fail': + return 'text-red-600 bg-red-100'; + default: + return 'text-gray-600 bg-gray-100'; + } + }; + + const getStatusIcon = (status: string) => { + switch (status) { + case 'healthy': + case 'pass': + return ( + + + + ); + case 'degraded': + case 'warn': + return ( + + + + ); + case 'unhealthy': + case 'fail': + return ( + + + + ); + default: + return ( + + + + ); + } + }; + + const formatUptime = (seconds: number) => { + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + + if (days > 0) { + return `${days}d ${hours}h ${minutes}m`; + } + if (hours > 0) { + return `${hours}h ${minutes}m`; + } + return `${minutes}m`; + }; + + const formatTimestamp = (timestamp: string) => { + return new Date(timestamp).toLocaleString(); + }; + + return ( +
+
+ {/* Header */} +
+

System Health Dashboard

+

Real-time monitoring of system components

+
+ + {/* User Data Section */} +
+ {/* Supabase User Data */} +
+

+
+ + + +
+ Supabase User +

+
+
+                {JSON.stringify(supabaseUser, null, 2)}
+              
+
+
+ + {/* User Basic Info */} +
+

+
+ + + +
+ User Basic Info +

+
+
+                {JSON.stringify(user, null, 2)}
+              
+
+
+
+ + {/* Overall Status Card */} +
+
+
+
+ {getStatusIcon(data.status)} +
+
+

{data.status}

+

Overall System Status

+
+
+
+

Last checked

+

{formatTimestamp(data.timestamp)}

+
+
+
+ + {/* Metrics Grid */} +
+
+
+
+ + + +
+
+

Uptime

+

{formatUptime(data.uptime)}

+
+
+
+ +
+
+
+ + + +
+
+

Version

+

{data.version}

+
+
+
+ +
+
+
+ + + +
+
+

Environment

+

{data.environment}

+
+
+
+
+ + {/* Component Checks */} +
+

Component Health Checks

+
+ {Object.entries(data.checks).map(([componentName, check]) => ( +
+
+
+
+ {getStatusIcon(check.status)} +
+
+

{componentName}

+ {check.message &&

{check.message}

} +
+
+
+ + {check.status} + + {check.responseTime && ( +

{check.responseTime}ms

+ )} +
+
+
+ ))} +
+
+ + {/* Footer */} +
+

System health is monitored continuously. Data refreshes automatically.

+
+
+
+ ); +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 7e19ce88b..5bf7dd77b 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -10,7 +10,8 @@ const config = defineConfig(({ command, mode }) => { const isProduction = mode === 'production' || mode === 'staging'; const isTypecheck = process.argv.includes('--typecheck') || process.env.TYPECHECK === 'true'; const useChecker = !process.env.VITEST && isBuild; - const isLocalBuild = process.argv.includes('--local'); + const isLocalBuild = process.argv.includes('--local') || mode === 'development'; + const target = isLocalBuild ? ('bun' as const) : ('cloudflare-module' as const); return { server: { port: 3000 }, @@ -20,7 +21,7 @@ const config = defineConfig(({ command, mode }) => { tailwindcss(), tanstackStart({ customViteReactPlugin: true, - target: isLocalBuild ? 'bun' : 'cloudflare-module', + target, }), viteReact(), useChecker