Compare commits
2 Commits
aca4a7426e
...
edc896b10e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edc896b10e | ||
|
|
76be5377d9 |
@@ -2,6 +2,7 @@ import { LockKeyhole } from 'lucide-react';
|
|||||||
import { ComponentType, SVGProps } from 'react';
|
import { ComponentType, SVGProps } from 'react';
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useEmbedding } from '@/components/embed-provider';
|
||||||
import { buttonVariants } from '@/components/ui/button';
|
import { buttonVariants } from '@/components/ui/button';
|
||||||
import { Dot } from '@/components/ui/dot';
|
import { Dot } from '@/components/ui/dot';
|
||||||
import {
|
import {
|
||||||
@@ -15,6 +16,7 @@ import {
|
|||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from '@/components/ui/tooltip';
|
} from '@/components/ui/tooltip';
|
||||||
|
import { authenticationSession } from '@/lib/authentication-session';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
export type SidebarItemType = {
|
export type SidebarItemType = {
|
||||||
@@ -34,10 +36,26 @@ export type SidebarItemType = {
|
|||||||
export const ApSidebarItem = (item: SidebarItemType) => {
|
export const ApSidebarItem = (item: SidebarItemType) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { state } = useSidebar();
|
const { state } = useSidebar();
|
||||||
|
const { embedState } = useEmbedding();
|
||||||
const isLinkActive =
|
const isLinkActive =
|
||||||
location.pathname.startsWith(item.to) || item.isActive?.(location.pathname);
|
location.pathname.startsWith(item.to) || item.isActive?.(location.pathname);
|
||||||
const isCollapsed = state === 'collapsed';
|
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 (
|
return (
|
||||||
<SidebarMenuItem
|
<SidebarMenuItem
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
@@ -51,6 +69,7 @@ export const ApSidebarItem = (item: SidebarItemType) => {
|
|||||||
to={item.to}
|
to={item.to}
|
||||||
target={item.newWindow ? '_blank' : ''}
|
target={item.newWindow ? '_blank' : ''}
|
||||||
rel={item.newWindow ? 'noopener noreferrer' : undefined}
|
rel={item.newWindow ? 'noopener noreferrer' : undefined}
|
||||||
|
onClick={handleNewWindowClick}
|
||||||
className={cn(
|
className={cn(
|
||||||
buttonVariants({ variant: 'ghost', size: 'icon' }),
|
buttonVariants({ variant: 'ghost', size: 'icon' }),
|
||||||
isLinkActive && 'bg-sidebar-accent hover:!bg-sidebar-accent',
|
isLinkActive && 'bg-sidebar-accent hover:!bg-sidebar-accent',
|
||||||
@@ -83,6 +102,7 @@ export const ApSidebarItem = (item: SidebarItemType) => {
|
|||||||
to={item.to}
|
to={item.to}
|
||||||
target={item.newWindow ? '_blank' : ''}
|
target={item.newWindow ? '_blank' : ''}
|
||||||
rel={item.newWindow ? 'noopener noreferrer' : undefined}
|
rel={item.newWindow ? 'noopener noreferrer' : undefined}
|
||||||
|
onClick={handleNewWindowClick}
|
||||||
>
|
>
|
||||||
<div className="w-full flex items-center justify-between gap-2">
|
<div className="w-full flex items-center justify-between gap-2">
|
||||||
<div className="flex items-center gap-2 w-full">
|
<div className="flex items-center gap-2 w-full">
|
||||||
|
|||||||
@@ -9,14 +9,22 @@ const AuthenticatePage = () => {
|
|||||||
|
|
||||||
const searchParams = new URLSearchParams(location.search);
|
const searchParams = new URLSearchParams(location.search);
|
||||||
const response = searchParams.get('response');
|
const response = searchParams.get('response');
|
||||||
|
const token = searchParams.get('token');
|
||||||
|
const redirectTo = searchParams.get('redirect') || '/flows';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (response) {
|
if (response) {
|
||||||
|
// Handle full response object (legacy)
|
||||||
const decodedResponse = JSON.parse(response);
|
const decodedResponse = JSON.parse(response);
|
||||||
authenticationSession.saveResponse(decodedResponse, false);
|
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...</>;
|
return <>Please wait...</>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,15 +2,30 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
|
|||||||
|
|
||||||
import { useEmbedding } from '../components/embed-provider';
|
import { useEmbedding } from '../components/embed-provider';
|
||||||
|
|
||||||
|
import { authenticationSession } from './authentication-session';
|
||||||
|
|
||||||
export const useNewWindow = () => {
|
export const useNewWindow = () => {
|
||||||
const { embedState } = useEmbedding();
|
const { embedState } = useEmbedding();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
if (embedState.isEmbedded) {
|
if (embedState.isEmbedded) {
|
||||||
return (route: string, searchParams?: string) =>
|
// In embedded mode, open new tab with authentication token
|
||||||
navigate({
|
return (route: string, searchParams?: string) => {
|
||||||
pathname: route,
|
const token = authenticationSession.getToken();
|
||||||
search: searchParams,
|
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 {
|
} else {
|
||||||
return (route: string, searchParams?: string) =>
|
return (route: string, searchParams?: string) =>
|
||||||
window.open(
|
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';
|
export const FROM_QUERY_PARAM = 'from';
|
||||||
/**State param is for oauth2 flow, it is used to redirect to the page after login*/
|
/**State param is for oauth2 flow, it is used to redirect to the page after login*/
|
||||||
export const STATE_QUERY_PARAM = 'state';
|
export const STATE_QUERY_PARAM = 'state';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { Bot, RefreshCw, AlertTriangle, Loader2, ExternalLink, Sparkles, RotateCcw, ChevronDown } from 'lucide-react';
|
import { Bot, RefreshCw, AlertTriangle, Loader2, Sparkles, RotateCcw, ChevronDown } from 'lucide-react';
|
||||||
import api from '../api/client';
|
import api from '../api/client';
|
||||||
import { usePlanFeatures } from '../hooks/usePlanFeatures';
|
import { usePlanFeatures } from '../hooks/usePlanFeatures';
|
||||||
import { UpgradePrompt } from '../components/UpgradePrompt';
|
import { UpgradePrompt } from '../components/UpgradePrompt';
|
||||||
@@ -387,18 +387,6 @@ export default function Automations() {
|
|||||||
<RefreshCw className="h-5 w-5" />
|
<RefreshCw className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Open in new tab */}
|
|
||||||
{embedData?.embedUrl && (
|
|
||||||
<a
|
|
||||||
href={embedData.embedUrl}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
|
|
||||||
title={t('automations.openInTab', 'Open in new tab')}
|
|
||||||
>
|
|
||||||
<ExternalLink className="h-5 w-5" />
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user