diff --git a/packages/react-ui/src/app/components/dashboard-container.tsx b/packages/react-ui/src/app/components/dashboard-container.tsx index 1cf3574e3ed..94b3faf6c08 100644 --- a/packages/react-ui/src/app/components/dashboard-container.tsx +++ b/packages/react-ui/src/app/components/dashboard-container.tsx @@ -1,10 +1,13 @@ import { t } from 'i18next'; import { AlertCircle, Link2, Logs, Workflow, Wrench } from 'lucide-react'; +import { Navigate } from 'react-router-dom'; import { useEmbedding } from '@/components/embed-provider'; import { issueHooks } from '@/features/issues/hooks/issue-hooks'; -import { flagsHooks } from '@/hooks/flags-hooks'; -import { ApEdition, ApFlagId } from '@activepieces/shared'; +import { platformHooks } from '@/hooks/platform-hooks'; +import { isNil } from '@activepieces/shared'; + +import { authenticationSession } from '../../lib/authentication-session'; import { AllowOnlyLoggedInUserOnlyGuard } from './allow-logged-in-user-only-guard'; import { Sidebar, SidebarLink } from './sidebar'; @@ -14,11 +17,16 @@ type DashboardContainerProps = { }; export function DashboardContainer({ children }: DashboardContainerProps) { - const { data: edition } = flagsHooks.useFlag(ApFlagId.EDITION); - const { data: showIssuesNotification } = - issueHooks.useIssuesNotification(edition); + const { platform } = platformHooks.useCurrentPlatform(); + const { data: showIssuesNotification } = issueHooks.useIssuesNotification( + platform.flowIssuesEnabled, + ); const { embedState } = useEmbedding(); + const currentProjectId = authenticationSession.getProjectId(); + if (isNil(currentProjectId) || currentProjectId === '') { + return ; + } const links: SidebarLink[] = [ { to: '/flows', @@ -51,7 +59,14 @@ export function DashboardContainer({ children }: DashboardContainerProps) { icon: Wrench, showInEmbed: false, }, - ].filter((link) => !embedState.isEmbedded || link.showInEmbed); + ] + .filter((link) => !embedState.isEmbedded || link.showInEmbed) + .map((link) => { + return { + ...link, + to: `/projects/${currentProjectId}${link.to}`, + }; + }); return ( diff --git a/packages/react-ui/src/app/components/project-settings-layout.tsx b/packages/react-ui/src/app/components/project-settings-layout.tsx index 3d32cd54302..eb788997835 100644 --- a/packages/react-ui/src/app/components/project-settings-layout.tsx +++ b/packages/react-ui/src/app/components/project-settings-layout.tsx @@ -7,9 +7,13 @@ import { SunMoon, Users, } from 'lucide-react'; +import { Navigate } from 'react-router-dom'; -import SidebarLayout from '@/app/components/sidebar-layout'; +import SidebarLayout, { SidebarItem } from '@/app/components/sidebar-layout'; import { platformHooks } from '@/hooks/platform-hooks'; +import { isNil } from '@activepieces/shared'; + +import { authenticationSession } from '../../lib/authentication-session'; const iconSize = 20; @@ -49,15 +53,26 @@ const sidebarNavItems = [ interface SettingsLayoutProps { children: React.ReactNode; } + export default function ProjectSettingsLayout({ children, }: SettingsLayoutProps) { const { platform } = platformHooks.useCurrentPlatform(); + const currentProjectId = authenticationSession.getProjectId(); + if (isNil(currentProjectId)) { + return ; + } + + const filterAlerts = (item: SidebarItem) => + platform.alertsEnabled || item.title !== t('Alerts'); + const addProjectIdToHref = (item: SidebarItem) => ({ + ...item, + href: `/projects/${currentProjectId}${item.href}`, + }); - // TODO enable alerts for communityh when it is ready - const filteredNavItems = platform.alertsEnabled - ? sidebarNavItems - : sidebarNavItems.filter((item) => item.title !== t('Alerts')); + const filteredNavItems = sidebarNavItems + .filter(filterAlerts) + .map(addProjectIdToHref); return ( diff --git a/packages/react-ui/src/app/components/sidebar-layout.tsx b/packages/react-ui/src/app/components/sidebar-layout.tsx index c3d00c455bb..10a97796460 100644 --- a/packages/react-ui/src/app/components/sidebar-layout.tsx +++ b/packages/react-ui/src/app/components/sidebar-layout.tsx @@ -10,7 +10,7 @@ interface SidebarLayoutProps { children: React.ReactNode; } -type SidebarItem = { +export type SidebarItem = { title: string; href: string; icon: JSX.Element; diff --git a/packages/react-ui/src/app/router.tsx b/packages/react-ui/src/app/router/index.tsx similarity index 84% rename from packages/react-ui/src/app/router.tsx rename to packages/react-ui/src/app/router/index.tsx index df3cbe1b4d8..58017c39710 100644 --- a/packages/react-ui/src/app/router.tsx +++ b/packages/react-ui/src/app/router/index.tsx @@ -30,32 +30,33 @@ import { ActivepiecesVendorRouteChanged, } from 'ee-embed-sdk'; -import { FlowsPage } from '../app/routes/flows'; +import { AllowOnlyLoggedInUserOnlyGuard } from '../components/allow-logged-in-user-only-guard'; +import { DashboardContainer } from '../components/dashboard-container'; +import { PlatformAdminContainer } from '../components/platform-admin-container'; +import NotFoundPage from '../routes/404-page'; +import { ChangePasswordPage } from '../routes/change-password'; +import AppConnectionsPage from '../routes/connections'; +import { FlowsPage } from '../routes/flows'; +import { FlowBuilderPage } from '../routes/flows/id'; +import { ResetPasswordPage } from '../routes/forget-password'; +import { FormPage } from '../routes/forms'; +import IssuesPage from '../routes/issues'; +import PlansPage from '../routes/plans'; +import AuditLogsPage from '../routes/platform/audit-logs'; +import ProjectsPage from '../routes/platform/projects'; +import TemplatesPage from '../routes/platform/templates'; +import UsersPage from '../routes/platform/users'; +import { FlowRunPage } from '../routes/runs/id'; +import AlertsPage from '../routes/settings/alerts'; +import AppearancePage from '../routes/settings/appearance'; +import GeneralPage from '../routes/settings/general'; +import { GitSyncPage } from '../routes/settings/git-sync'; +import TeamPage from '../routes/settings/team'; +import { SignInPage } from '../routes/sign-in'; +import { SignUpPage } from '../routes/sign-up'; +import { ShareTemplatePage } from '../routes/templates/share-template'; -import { AllowOnlyLoggedInUserOnlyGuard } from './components/allow-logged-in-user-only-guard'; -import { DashboardContainer } from './components/dashboard-container'; -import { PlatformAdminContainer } from './components/platform-admin-container'; -import NotFoundPage from './routes/404-page'; -import { ChangePasswordPage } from './routes/change-password'; -import AppConnectionsPage from './routes/connections'; -import { FlowBuilderPage } from './routes/flows/id'; -import { ResetPasswordPage } from './routes/forget-password'; -import { FormPage } from './routes/forms'; -import IssuesPage from './routes/issues'; -import PlansPage from './routes/plans'; -import AuditLogsPage from './routes/platform/audit-logs'; -import ProjectsPage from './routes/platform/projects'; -import TemplatesPage from './routes/platform/templates'; -import UsersPage from './routes/platform/users'; -import { FlowRunPage } from './routes/runs/id'; -import AlertsPage from './routes/settings/alerts'; -import AppearancePage from './routes/settings/appearance'; -import GeneralPage from './routes/settings/general'; -import { GitSyncPage } from './routes/settings/git-sync'; -import TeamPage from './routes/settings/team'; -import { SignInPage } from './routes/sign-in'; -import { SignUpPage } from './routes/sign-up'; -import { ShareTemplatePage } from './routes/templates/share-template'; +import { ProjectRouterWrapper } from './project-route-wrapper'; const SettingsRerouter = () => { const { hash } = useLocation(); @@ -66,6 +67,7 @@ const SettingsRerouter = () => { ); }; + const routes = [ { path: '/embed', @@ -76,7 +78,7 @@ const routes = [ path: '/switch-to-beta', element: , }, - { + ...ProjectRouterWrapper({ path: '/flows', element: ( @@ -85,8 +87,8 @@ const routes = [ ), - }, - { + }), + ...ProjectRouterWrapper({ path: '/flows/:flowId', element: ( @@ -95,7 +97,7 @@ const routes = [ ), - }, + }), { path: '/forms/:flowId', element: ( @@ -104,7 +106,7 @@ const routes = [ ), }, - { + ...ProjectRouterWrapper({ path: '/runs/:runId', element: ( @@ -113,7 +115,7 @@ const routes = [ ), - }, + }), { path: '/templates/:templateId', element: ( @@ -124,7 +126,7 @@ const routes = [ ), }, - { + ...ProjectRouterWrapper({ path: '/runs', element: ( @@ -133,8 +135,8 @@ const routes = [ ), - }, - { + }), + ...ProjectRouterWrapper({ path: '/issues', element: ( @@ -143,8 +145,8 @@ const routes = [ ), - }, - { + }), + ...ProjectRouterWrapper({ path: '/connections', element: ( @@ -153,8 +155,8 @@ const routes = [ ), - }, - { + }), + ...ProjectRouterWrapper({ path: '/plans', element: ( @@ -163,15 +165,15 @@ const routes = [ ), - }, - { + }), + ...ProjectRouterWrapper({ path: '/settings', element: ( ), - }, + }), { path: '/forget-password', element: ( @@ -212,7 +214,7 @@ const routes = [ ), }, - { + ...ProjectRouterWrapper({ path: '/settings/alerts', element: ( @@ -223,8 +225,8 @@ const routes = [ ), - }, - { + }), + ...ProjectRouterWrapper({ path: '/settings/appearance', element: ( @@ -235,8 +237,8 @@ const routes = [ ), - }, - { + }), + ...ProjectRouterWrapper({ path: '/settings/general', element: ( @@ -247,8 +249,8 @@ const routes = [ ), - }, - { + }), + ...ProjectRouterWrapper({ path: '/settings/pieces', element: ( @@ -259,9 +261,8 @@ const routes = [ ), - }, - - { + }), + ...ProjectRouterWrapper({ path: '/settings/team', element: ( @@ -272,12 +273,13 @@ const routes = [ ), - }, + }), { path: '/team', element: , }, - { + + ...ProjectRouterWrapper({ path: '/settings/git-sync', element: ( @@ -288,7 +290,7 @@ const routes = [ ), - }, + }), { path: '/invitation', diff --git a/packages/react-ui/src/app/router/project-route-wrapper.tsx b/packages/react-ui/src/app/router/project-route-wrapper.tsx new file mode 100644 index 00000000000..6fb7dcd44b5 --- /dev/null +++ b/packages/react-ui/src/app/router/project-route-wrapper.tsx @@ -0,0 +1,103 @@ +import { useQuery, useSuspenseQuery } from '@tanstack/react-query'; +import React from 'react'; +import { useParams, Navigate } from 'react-router-dom'; + +import { useEmbedding } from '@/components/embed-provider'; +import { isNil } from '@activepieces/shared'; + +import { authenticationSession } from '../../lib/authentication-session'; +import { api } from '@/lib/api'; +import { useToast } from '@/components/ui/use-toast'; +import { t } from 'i18next'; + +const TokenCheckerWrapper: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + const { projectId } = useParams<{ projectId: string }>(); + const currentProjectId = authenticationSession.getProjectId(); + const { toast } = useToast(); + const { data: isProjectValid, isError } = useSuspenseQuery({ + queryKey: ['switch-to-project', projectId], + queryFn: async () => { + if (isNil(projectId)) { + return false; + } + try { + await authenticationSession.switchToSession(projectId!); + return true; + } catch (error) { + if (api.isError(error)) { + toast({ + duration: 3000, + title: t('Invalid Access'), + description: t('Either the project does not exist or you do not have access to it.'), + }); + authenticationSession.clearSession(); + } + return false; + } + }, + retry: false, + staleTime: Infinity, + }); + + if (isNil(currentProjectId) || isNil(projectId)) { + return ; + } + + if (isError || !isProjectValid) { + return ; + } + + return <>{children}; +}; + +type RedirectToCurrentProjectRouteProps = { + path: string; + children: React.ReactNode; +}; +const RedirectToCurrentProjectRoute: React.FC< + RedirectToCurrentProjectRouteProps +> = ({ path, children }) => { + const currentProjectId = authenticationSession.getProjectId(); + const params = useParams(); + const { embedState } = useEmbedding(); + if (isNil(currentProjectId)) { + return ; + } + if (embedState.isEmbedded) { + return children; + } + + const pathWithParams = `${path.startsWith('/') ? path : `/${path}`}`.replace( + /:(\w+)/g, + (_, param) => params[param] ?? '', + ); + + return ( + + ); +}; + +interface ProjectRouterWrapperProps { + path: string; + element: React.ReactNode; +} + +export const ProjectRouterWrapper = ({ + element, + path, +}: ProjectRouterWrapperProps) => [ + { + path: `/projects/:projectId${path.startsWith('/') ? path : `/${path}`}`, + element: {element}, + }, + { + path, + element: ( + + {element} + + ), + }, + ]; diff --git a/packages/react-ui/src/app/routes/platform/projects/index.tsx b/packages/react-ui/src/app/routes/platform/projects/index.tsx index 641f783ea4a..7d9c51398b8 100644 --- a/packages/react-ui/src/app/routes/platform/projects/index.tsx +++ b/packages/react-ui/src/app/routes/platform/projects/index.tsx @@ -87,7 +87,7 @@ export default function ProjectsPage() { { - await setCurrentProject(queryClient, project, false); + await setCurrentProject(queryClient, project); navigate('/'); }} columns={[ @@ -177,7 +177,7 @@ export default function ProjectsPage() { onClick={async (e) => { e.stopPropagation(); e.preventDefault(); - await setCurrentProject(queryClient, row, false); + await setCurrentProject(queryClient, row); navigate('/settings/general'); }} > diff --git a/packages/react-ui/src/components/custom/circular-progress.tsx b/packages/react-ui/src/components/custom/circular-progress.tsx index d3897a2ce4d..796cb9bf369 100644 --- a/packages/react-ui/src/components/custom/circular-progress.tsx +++ b/packages/react-ui/src/components/custom/circular-progress.tsx @@ -21,7 +21,7 @@ const ProgressCircularComponent: React.FC<{ data={[ { name: 'plan', - progress: 0, + progress: data.usage, fill: 'hsl(var(--primary))', }, ]} diff --git a/packages/react-ui/src/components/ui/report-bugs-button.tsx b/packages/react-ui/src/components/ui/report-bugs-button.tsx index 91a7f9fceeb..d490e756a74 100644 --- a/packages/react-ui/src/components/ui/report-bugs-button.tsx +++ b/packages/react-ui/src/components/ui/report-bugs-button.tsx @@ -17,7 +17,7 @@ export const ReportBugsButton = ({ return ( showSupport && (