diff --git a/frontend/app/src/components/molecules/nav-bar/banner.tsx b/frontend/app/src/components/molecules/nav-bar/banner.tsx new file mode 100644 index 000000000..7e6981118 --- /dev/null +++ b/frontend/app/src/components/molecules/nav-bar/banner.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Button } from '@/components/ui/button'; + +export interface BannerProps { + message: JSX.Element; + type?: 'info' | 'warning' | 'success' | 'error'; + actionText?: string; + onAction?: () => void; +} + +export const Banner: React.FC = ({ + message, + type = 'info', + actionText, + onAction, +}) => { + const getBgColor = () => { + switch (type) { + case 'warning': + return 'bg-amber-50 dark:bg-amber-900/20 text-amber-800 dark:text-amber-200'; + case 'success': + return 'bg-green-50 dark:bg-green-900/20 text-green-800 dark:text-green-200'; + case 'error': + return 'bg-red-50 dark:bg-red-900/20 text-red-800 dark:text-red-200'; + default: + return 'bg-blue-50 dark:bg-blue-900/20 text-blue-800 dark:text-blue-200'; + } + }; + + return ( +
+
+
+

{message}

+
+
+ {actionText && onAction && ( + + )} +
+
+
+ ); +}; diff --git a/frontend/app/src/components/molecules/nav-bar/nav-bar.tsx b/frontend/app/src/components/molecules/nav-bar/nav-bar.tsx index 126c055d7..074d7c29a 100644 --- a/frontend/app/src/components/molecules/nav-bar/nav-bar.tsx +++ b/frontend/app/src/components/molecules/nav-bar/nav-bar.tsx @@ -33,13 +33,14 @@ import { BiUserCircle, } from 'react-icons/bi'; import { useTheme } from '@/components/theme-provider'; -import { useMemo } from 'react'; +import { useEffect, useMemo } from 'react'; import useApiMeta from '@/pages/auth/hooks/use-api-meta'; import { VersionInfo } from '@/pages/main/info/components/version-info'; import { useTenant } from '@/lib/atoms'; import { routes } from '@/router'; import { TooltipContent, TooltipProvider } from '@/components/ui/tooltip'; import { Tooltip, TooltipTrigger } from '@/components/ui/tooltip'; +import { Banner, BannerProps } from './banner'; function HelpDropdown() { const meta = useApiMeta(); @@ -107,6 +108,8 @@ function HelpDropdown() { function AccountDropdown({ user }: MainNavProps) { const navigate = useNavigate(); + const { tenant } = useTenant(); + const { handleApiError } = useApiError({}); const { toggleTheme } = useTheme(); @@ -148,6 +151,12 @@ function AccountDropdown({ user }: MainNavProps) { + {tenant?.version == TenantVersion.V1 && + location.pathname.includes('v1') && ( + navigate('/')}> + View Legacy V0 Data + + )} toggleTheme()}> Toggle Theme @@ -215,29 +224,87 @@ function VersionUpgradeButton() { interface MainNavProps { user: User; + setHasBanner?: (state: boolean) => void; } -export default function MainNav({ user }: MainNavProps) { +export default function MainNav({ user, setHasBanner }: MainNavProps) { const { toggleSidebarOpen } = useSidebar(); const { theme } = useTheme(); + const { tenant } = useTenant(); + const { pathname } = useLocation(); + const navigate = useNavigate(); + const [params] = useSearchParams(); + + const versionedRoutes = useMemo( + () => + routes + .at(0) + ?.children?.find((r) => r.path === '/v1/') + ?.children?.find((r) => r.path === '/v1/' && r.children?.length) + ?.children?.map((c) => c.path) + ?.map((p) => p?.replace('/v1', '')) || [], + [], + ); + + const tenantVersion = tenant?.version || TenantVersion.V0; + + const banner: BannerProps | undefined = useMemo(() => { + const shouldShowVersionUpgradeButton = + versionedRoutes.includes(pathname) && // It is a versioned route + !pathname.includes('/v1') && // The user is not already on the v1 version + tenantVersion === TenantVersion.V1; // The tenant is on the v1 version + + if (shouldShowVersionUpgradeButton) { + return { + message: ( + <> + You are viewing legacy V0 data for a tenant that was upgraded to V1 + runtime. + + ), + type: 'warning', + actionText: 'Upgrade', + onAction: () => { + navigate({ + pathname: '/v1' + pathname, + search: params.toString(), + }); + }, + }; + } + + return; + }, [navigate, params, pathname, tenantVersion, versionedRoutes]); + + useEffect(() => { + if (!setHasBanner) { + return; + } + setHasBanner(!!banner); + }, [setHasBanner, banner]); return ( -
-
- -
- - - +
+ {banner && } + + {/* Main Navigation Bar */} +
+
+ +
+ + + +
diff --git a/frontend/app/src/lib/atoms.ts b/frontend/app/src/lib/atoms.ts index e8f2879e3..050f77e02 100644 --- a/frontend/app/src/lib/atoms.ts +++ b/frontend/app/src/lib/atoms.ts @@ -1,7 +1,7 @@ import { atom, useAtom } from 'jotai'; -import { Tenant, queries } from './api'; -import { useSearchParams } from 'react-router-dom'; -import { useCallback, useEffect, useMemo } from 'react'; +import { Tenant, TenantVersion, queries } from './api'; +import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; const getInitialValue = (key: string, defaultValue?: T): T | undefined => { @@ -121,6 +121,40 @@ export function useTenant(): TenantContext { } }, [searchParams, tenant, setTenant]); + // Set the correct path for tenant version + // NOTE: this is hacky and not ideal + + const { pathname } = useLocation(); + const navigate = useNavigate(); + const [params] = useSearchParams(); + + const [lastRedirected, setLastRedirected] = useState(); + + useEffect(() => { + // Only redirect on initial tenant load + if (lastRedirected == tenant?.slug) { + return; + } + + setLastRedirected(tenant?.slug); + + if (tenant?.version == TenantVersion.V0 && pathname.startsWith('/v1')) { + return navigate({ + pathname: pathname.replace('/v1', ''), + search: params.toString(), + }); + } + + if (tenant?.version == TenantVersion.V1 && !pathname.startsWith('/v1')) { + return navigate({ + pathname: '/v1' + pathname, + search: params.toString(), + }); + } + + console.log(tenant?.version); + }, [lastRedirected, navigate, params, pathname, tenant]); + if (!tenant) { return { tenant: undefined, diff --git a/frontend/app/src/pages/authenticated.tsx b/frontend/app/src/pages/authenticated.tsx index beac05078..961394129 100644 --- a/frontend/app/src/pages/authenticated.tsx +++ b/frontend/app/src/pages/authenticated.tsx @@ -12,6 +12,7 @@ import { Loading } from '@/components/ui/loading.tsx'; import { useQuery } from '@tanstack/react-query'; import SupportChat from '@/components/molecules/support-chat'; import AnalyticsProvider from '@/components/molecules/analytics-provider'; +import { useState } from 'react'; const authMiddleware = async (currentUrl: string) => { try { @@ -104,6 +105,8 @@ export default function Authenticated() { memberships: listMembershipsQuery.data?.rows || memberships, }); + const [hasHasBanner, setHasBanner] = useState(false); + if (!user || !memberships) { return ; } @@ -112,8 +115,10 @@ export default function Authenticated() {
- -
+ +
diff --git a/frontend/app/src/pages/onboarding/verify-email/index.tsx b/frontend/app/src/pages/onboarding/verify-email/index.tsx index 6f22b9696..a336a58fe 100644 --- a/frontend/app/src/pages/onboarding/verify-email/index.tsx +++ b/frontend/app/src/pages/onboarding/verify-email/index.tsx @@ -43,7 +43,7 @@ export default function VerifyEmail() { } return ( -
+