diff --git a/activepieces-fork/packages/react-ui/src/app/components/sidebar/ap-sidebar-item.tsx b/activepieces-fork/packages/react-ui/src/app/components/sidebar/ap-sidebar-item.tsx index 860f5ef9..cb26231a 100644 --- a/activepieces-fork/packages/react-ui/src/app/components/sidebar/ap-sidebar-item.tsx +++ b/activepieces-fork/packages/react-ui/src/app/components/sidebar/ap-sidebar-item.tsx @@ -2,6 +2,7 @@ import { LockKeyhole } from 'lucide-react'; import { ComponentType, SVGProps } from 'react'; import { Link, useLocation } from 'react-router-dom'; +import { useEmbedding } from '@/components/embed-provider'; import { buttonVariants } from '@/components/ui/button'; import { Dot } from '@/components/ui/dot'; import { @@ -15,6 +16,7 @@ import { TooltipProvider, TooltipTrigger, } from '@/components/ui/tooltip'; +import { authenticationSession } from '@/lib/authentication-session'; import { cn } from '@/lib/utils'; export type SidebarItemType = { @@ -34,10 +36,26 @@ export type SidebarItemType = { export const ApSidebarItem = (item: SidebarItemType) => { const location = useLocation(); const { state } = useSidebar(); + const { embedState } = useEmbedding(); const isLinkActive = location.pathname.startsWith(item.to) || item.isActive?.(location.pathname); const isCollapsed = state === 'collapsed'; + // Handle new window clicks with authentication in embedded mode + const handleNewWindowClick = (e: React.MouseEvent) => { + if (item.newWindow && embedState.isEmbedded) { + e.preventDefault(); + const token = authenticationSession.getToken(); + if (token) { + const encodedRedirect = encodeURIComponent(item.to); + const authUrl = `/authenticate?token=${encodeURIComponent(token)}&redirect=${encodedRedirect}`; + window.open(authUrl, '_blank', 'noopener'); + } else { + window.open(item.to, '_blank', 'noopener noreferrer'); + } + } + }; + return ( e.stopPropagation()} @@ -51,6 +69,7 @@ export const ApSidebarItem = (item: SidebarItemType) => { to={item.to} target={item.newWindow ? '_blank' : ''} rel={item.newWindow ? 'noopener noreferrer' : undefined} + onClick={handleNewWindowClick} className={cn( buttonVariants({ variant: 'ghost', size: 'icon' }), isLinkActive && 'bg-sidebar-accent hover:!bg-sidebar-accent', @@ -83,6 +102,7 @@ export const ApSidebarItem = (item: SidebarItemType) => { to={item.to} target={item.newWindow ? '_blank' : ''} rel={item.newWindow ? 'noopener noreferrer' : undefined} + onClick={handleNewWindowClick} >
diff --git a/activepieces-fork/packages/react-ui/src/app/routes/authenticate/index.tsx b/activepieces-fork/packages/react-ui/src/app/routes/authenticate/index.tsx index da6b2887..f830e710 100644 --- a/activepieces-fork/packages/react-ui/src/app/routes/authenticate/index.tsx +++ b/activepieces-fork/packages/react-ui/src/app/routes/authenticate/index.tsx @@ -9,14 +9,22 @@ const AuthenticatePage = () => { const searchParams = new URLSearchParams(location.search); const response = searchParams.get('response'); + const token = searchParams.get('token'); + const redirectTo = searchParams.get('redirect') || '/flows'; useEffect(() => { if (response) { + // Handle full response object (legacy) const decodedResponse = JSON.parse(response); authenticationSession.saveResponse(decodedResponse, false); - navigate('/flows'); + navigate(redirectTo); + } else if (token) { + // Handle standalone JWT token (from embedded mode new tab) + // Save token directly to localStorage for persistence in new tabs + authenticationSession.saveResponse({ token }, false); + navigate(redirectTo); } - }, [response]); + }, [response, token, redirectTo, navigate]); return <>Please wait...; }; diff --git a/activepieces-fork/packages/react-ui/src/lib/navigation-utils.tsx b/activepieces-fork/packages/react-ui/src/lib/navigation-utils.tsx index 6c4c2afd..128a0c59 100644 --- a/activepieces-fork/packages/react-ui/src/lib/navigation-utils.tsx +++ b/activepieces-fork/packages/react-ui/src/lib/navigation-utils.tsx @@ -2,15 +2,30 @@ import { useNavigate, useSearchParams } from 'react-router-dom'; import { useEmbedding } from '../components/embed-provider'; +import { authenticationSession } from './authentication-session'; + export const useNewWindow = () => { const { embedState } = useEmbedding(); const navigate = useNavigate(); if (embedState.isEmbedded) { - return (route: string, searchParams?: string) => - navigate({ - pathname: route, - search: searchParams, - }); + // In embedded mode, open new tab with authentication token + return (route: string, searchParams?: string) => { + const token = authenticationSession.getToken(); + const fullRoute = `${route}${searchParams ? '?' + searchParams : ''}`; + + if (token) { + // Pass token for auto-authentication in new tab + const encodedRedirect = encodeURIComponent(fullRoute); + const authUrl = `/authenticate?token=${encodeURIComponent(token)}&redirect=${encodedRedirect}`; + window.open(authUrl, '_blank', 'noopener'); + } else { + // Fallback to in-iframe navigation if no token + navigate({ + pathname: route, + search: searchParams, + }); + } + }; } else { return (route: string, searchParams?: string) => window.open( @@ -21,6 +36,35 @@ export const useNewWindow = () => { } }; +/** + * Opens a route in a new browser tab with automatic authentication. + * For embedded contexts where sessionStorage isn't shared across tabs, + * this passes the JWT token via URL for auto-login. + */ +export const useOpenInNewTab = () => { + const { embedState } = useEmbedding(); + + return (route: string, searchParams?: string) => { + const token = authenticationSession.getToken(); + + if (embedState.isEmbedded && token) { + // In embedded mode, pass token for auto-authentication in new tab + const encodedRedirect = encodeURIComponent( + `${route}${searchParams ? '?' + searchParams : ''}`, + ); + const authUrl = `/authenticate?token=${encodeURIComponent(token)}&redirect=${encodedRedirect}`; + window.open(authUrl, '_blank', 'noopener'); + } else { + // Non-embedded mode - token is already in localStorage + window.open( + `${route}${searchParams ? '?' + searchParams : ''}`, + '_blank', + 'noopener noreferrer', + ); + } + }; +}; + export const FROM_QUERY_PARAM = 'from'; /**State param is for oauth2 flow, it is used to redirect to the page after login*/ export const STATE_QUERY_PARAM = 'state';