mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-19 11:41:04 +02:00
fix: Fixes #139
This commit is contained in:
parent
fe4ea648e4
commit
9ce5a8e5e2
39 changed files with 338 additions and 758 deletions
|
|
@ -14,11 +14,7 @@ import {
|
|||
} from '@/components/ui/dialog';
|
||||
import { downloadFile, getSignedUrl } from '@/lib/files';
|
||||
|
||||
interface MediaPreviewDialogProps {
|
||||
accessToken: string | null;
|
||||
}
|
||||
|
||||
export function MediaPreviewDialog({ accessToken }: MediaPreviewDialogProps) {
|
||||
export function MediaPreviewDialog() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [audioSignedUrl, setAudioSignedUrl] = useState<string | null>(null);
|
||||
const [transcriptContent, setTranscriptContent] = useState<string | null>(null);
|
||||
|
|
@ -29,7 +25,7 @@ export function MediaPreviewDialog({ accessToken }: MediaPreviewDialogProps) {
|
|||
|
||||
const openPreview = useCallback(
|
||||
async (recordingUrl: string | null, transcriptUrl: string | null, runId: number) => {
|
||||
if (!accessToken || (!recordingUrl && !transcriptUrl)) return;
|
||||
if (!recordingUrl && !transcriptUrl) return;
|
||||
setMediaLoading(true);
|
||||
setAudioSignedUrl(null);
|
||||
setTranscriptContent(null);
|
||||
|
|
@ -39,8 +35,8 @@ export function MediaPreviewDialog({ accessToken }: MediaPreviewDialogProps) {
|
|||
setIsOpen(true);
|
||||
|
||||
const [audioResult, transcriptResult] = await Promise.all([
|
||||
recordingUrl ? getSignedUrl(recordingUrl, accessToken) : null,
|
||||
transcriptUrl ? getSignedUrl(transcriptUrl, accessToken, true) : null,
|
||||
recordingUrl ? getSignedUrl(recordingUrl) : null,
|
||||
transcriptUrl ? getSignedUrl(transcriptUrl, true) : null,
|
||||
]);
|
||||
|
||||
if (audioResult) {
|
||||
|
|
@ -59,7 +55,7 @@ export function MediaPreviewDialog({ accessToken }: MediaPreviewDialogProps) {
|
|||
|
||||
setMediaLoading(false);
|
||||
},
|
||||
[accessToken],
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
@ -102,13 +98,13 @@ export function MediaPreviewDialog({ accessToken }: MediaPreviewDialogProps) {
|
|||
<Button variant="secondary">Close</Button>
|
||||
</DialogClose>
|
||||
<div className="flex gap-2">
|
||||
{recordingKey && accessToken && (
|
||||
<Button variant="outline" onClick={() => downloadFile(recordingKey, accessToken)}>
|
||||
{recordingKey && (
|
||||
<Button variant="outline" onClick={() => downloadFile(recordingKey)}>
|
||||
Download Recording
|
||||
</Button>
|
||||
)}
|
||||
{transcriptKey && accessToken && (
|
||||
<Button variant="outline" onClick={() => downloadFile(transcriptKey, accessToken)}>
|
||||
{transcriptKey && (
|
||||
<Button variant="outline" onClick={() => downloadFile(transcriptKey)}>
|
||||
Download Transcript
|
||||
</Button>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { useUserConfig } from "@/context/UserConfigContext";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
// Providers that have MPS voice endpoints
|
||||
|
|
@ -30,7 +29,6 @@ export const VoiceSelector: React.FC<VoiceSelectorProps> = ({
|
|||
onChange,
|
||||
className,
|
||||
}) => {
|
||||
const { accessToken } = useUserConfig();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [isManualInput, setIsManualInput] = useState(false);
|
||||
|
|
@ -60,7 +58,7 @@ export const VoiceSelector: React.FC<VoiceSelectorProps> = ({
|
|||
|
||||
const fetchVoices = useCallback(async () => {
|
||||
const providerKey = getProviderKey(provider);
|
||||
if (!providerKey || !accessToken) {
|
||||
if (!providerKey) {
|
||||
setVoices([]);
|
||||
return;
|
||||
}
|
||||
|
|
@ -71,9 +69,6 @@ export const VoiceSelector: React.FC<VoiceSelectorProps> = ({
|
|||
try {
|
||||
const response = await getVoicesApiV1UserConfigurationsVoicesProviderGet({
|
||||
path: { provider: providerKey },
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.data?.voices) {
|
||||
|
|
@ -86,7 +81,7 @@ export const VoiceSelector: React.FC<VoiceSelectorProps> = ({
|
|||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [provider, getProviderKey, accessToken]);
|
||||
}, [provider, getProviderKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (provider) {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export function CredentialSelector({
|
|||
description = "Select a credential for authentication, or leave empty for no auth.",
|
||||
showLabel = true,
|
||||
}: CredentialSelectorProps) {
|
||||
const { getAccessToken } = useAuth();
|
||||
useAuth();
|
||||
|
||||
const [credentials, setCredentials] = useState<CredentialResponse[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
|
@ -45,10 +45,7 @@ export function CredentialSelector({
|
|||
const fetchCredentials = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const accessToken = await getAccessToken();
|
||||
const response = await listCredentialsApiV1CredentialsGet({
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
const response = await listCredentialsApiV1CredentialsGet({});
|
||||
if (response.error) {
|
||||
console.error("Failed to fetch credentials:", response.error);
|
||||
setCredentials([]);
|
||||
|
|
@ -63,7 +60,7 @@ export function CredentialSelector({
|
|||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [getAccessToken]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchCredentials();
|
||||
|
|
|
|||
|
|
@ -28,44 +28,47 @@ const AppLayout: React.FC<AppLayoutProps> = ({
|
|||
const isWorkflowEditor = /^\/workflow\/\d+/.test(pathname);
|
||||
const isSuperadmin = pathname.startsWith("/superadmin");
|
||||
|
||||
// If no sidebar needed, just return children
|
||||
if (!shouldShowSidebar) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
// Always render SidebarProvider to keep the component tree shape consistent
|
||||
// across route changes (avoids React hooks ordering violations during navigation).
|
||||
return (
|
||||
<SidebarProvider defaultOpen={!isWorkflowEditor && !isSuperadmin}>
|
||||
<div className="flex min-h-screen w-full">
|
||||
<AppSidebar />
|
||||
<SidebarInset className="flex-1">
|
||||
{/* Optional header area for specific pages */}
|
||||
{headerActions && (
|
||||
<header className="sticky top-0 z-50 w-full border-b bg-background">
|
||||
<div className="container mx-auto px-4 py-4">
|
||||
<div className="flex items-center justify-center">
|
||||
{headerActions}
|
||||
{shouldShowSidebar ? (
|
||||
<div className="flex min-h-screen w-full">
|
||||
<AppSidebar />
|
||||
<SidebarInset className="flex-1">
|
||||
{/* Optional header area for specific pages */}
|
||||
{headerActions && (
|
||||
<header className="sticky top-0 z-50 w-full border-b bg-background">
|
||||
<div className="container mx-auto px-4 py-4">
|
||||
<div className="flex items-center justify-center">
|
||||
{headerActions}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)}
|
||||
|
||||
{/* Optional sticky tabs */}
|
||||
{stickyTabs && (
|
||||
<div className="sticky top-0 z-40 bg-[#2a2e39] border-b border-gray-700">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="flex items-center justify-center py-2">
|
||||
{stickyTabs}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* Optional sticky tabs */}
|
||||
{stickyTabs && (
|
||||
<div className="sticky top-0 z-40 bg-[#2a2e39] border-b border-gray-700">
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="flex items-center justify-center py-2">
|
||||
{stickyTabs}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Main content area */}
|
||||
<main className="flex-1">
|
||||
{children}
|
||||
</main>
|
||||
</SidebarInset>
|
||||
</div>
|
||||
{/* Main content area */}
|
||||
<main className="flex-1">
|
||||
{children}
|
||||
</main>
|
||||
</SidebarInset>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-1 w-full">
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</SidebarProvider>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,9 +11,11 @@ import {
|
|||
HelpCircle,
|
||||
Home,
|
||||
Key,
|
||||
LogOut,
|
||||
Megaphone,
|
||||
MessageSquare,
|
||||
Phone,
|
||||
Settings,
|
||||
Star,
|
||||
TrendingUp,
|
||||
Workflow,
|
||||
|
|
@ -26,6 +28,14 @@ import React from "react";
|
|||
|
||||
import ThemeToggle from "@/components/ThemeSwitcher";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
|
|
@ -50,11 +60,6 @@ import { useAppConfig } from "@/context/AppConfigContext";
|
|||
import { useAuth } from "@/lib/auth";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
// Conditionally load Stack components only when using Stack auth
|
||||
const StackUserButton = React.lazy(() =>
|
||||
import("@stackframe/stack").then((mod) => ({ default: mod.UserButton }))
|
||||
);
|
||||
|
||||
// Lazy load SelectedTeamSwitcher - we'll pass selectedTeam from our context
|
||||
const StackTeamSwitcher = React.lazy(() =>
|
||||
import("@stackframe/stack").then((mod) => ({
|
||||
|
|
@ -66,7 +71,7 @@ export function AppSidebar() {
|
|||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const { state } = useSidebar();
|
||||
const { provider, getSelectedTeam } = useAuth();
|
||||
const { provider, getSelectedTeam, logout, user } = useAuth();
|
||||
const { config } = useAppConfig();
|
||||
|
||||
// Get selected team for Stack auth (cast to Team type from Stack)
|
||||
|
|
@ -400,31 +405,53 @@ export function AppSidebar() {
|
|||
</>
|
||||
)}
|
||||
|
||||
{/* User Button for Stack Auth - at the bottom */}
|
||||
{/* User Button - at the bottom */}
|
||||
{provider === "stack" && (
|
||||
<React.Suspense
|
||||
fallback={
|
||||
<div className={cn(
|
||||
"animate-pulse bg-muted rounded",
|
||||
state === "collapsed" ? "h-8 w-8" : "h-[34px] w-[34px]"
|
||||
)} />
|
||||
}
|
||||
>
|
||||
<div className={cn(
|
||||
"flex",
|
||||
state === "collapsed" ? "justify-center" : "justify-start"
|
||||
)}>
|
||||
<StackUserButton
|
||||
extraItems={[
|
||||
{
|
||||
text: "Usage",
|
||||
icon: <CircleDollarSign strokeWidth={2} size={16} />,
|
||||
onClick: () => router.push("/usage"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</React.Suspense>
|
||||
<div className={cn(
|
||||
"flex",
|
||||
state === "collapsed" ? "justify-center" : "justify-start"
|
||||
)}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="rounded-full h-8 w-8 cursor-pointer">
|
||||
<span className="text-xs font-medium">
|
||||
{(user?.displayName || (user as { primaryEmail?: string })?.primaryEmail || "")
|
||||
.split(/[\s@]/)
|
||||
.filter(Boolean)
|
||||
.slice(0, 2)
|
||||
.map((s: string) => s[0]?.toUpperCase())
|
||||
.join("")
|
||||
|| "U"}
|
||||
</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="top" align="start" className="w-56">
|
||||
<DropdownMenuLabel className="font-normal">
|
||||
<div className="flex flex-col space-y-1">
|
||||
{user?.displayName && (
|
||||
<p className="text-sm font-medium">{user.displayName}</p>
|
||||
)}
|
||||
{(user as { primaryEmail?: string })?.primaryEmail && (
|
||||
<p className="text-xs text-muted-foreground">{(user as { primaryEmail?: string }).primaryEmail}</p>
|
||||
)}
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => router.push("/handler/account-settings")} className="cursor-pointer">
|
||||
<Settings className="mr-2 h-4 w-4" />
|
||||
Account settings
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => router.push("/usage")} className="cursor-pointer">
|
||||
<CircleDollarSign className="mr-2 h-4 w-4" />
|
||||
Usage
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => logout()} className="cursor-pointer">
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
Sign out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Theme Toggle - at the very bottom */}
|
||||
|
|
|
|||
|
|
@ -19,20 +19,16 @@ export function ConversationsList({ testSessionId }: ConversationsListProps) {
|
|||
const [conversations, setConversations] = useState<Conversation[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { user, getAccessToken } = useAuth();
|
||||
const { user } = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchConversations = async () => {
|
||||
if (!user) return;
|
||||
try {
|
||||
const accessToken = await getAccessToken();
|
||||
const response = await getTestSessionConversationApiV1LooptalkTestSessionsTestSessionIdConversationGet({
|
||||
path: {
|
||||
test_session_id: testSessionId
|
||||
},
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
// API returns { conversation: Conversation | null }
|
||||
|
|
@ -56,7 +52,7 @@ export function ConversationsList({ testSessionId }: ConversationsListProps) {
|
|||
// Poll for updates every 5 seconds
|
||||
const interval = setInterval(fetchConversations, 5000);
|
||||
return () => clearInterval(interval);
|
||||
}, [testSessionId, user, getAccessToken]);
|
||||
}, [testSessionId, user]);
|
||||
|
||||
if (loading && conversations.length === 0) {
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export function CreateTestSessionButton() {
|
|||
const router = useRouter();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { user, getAccessToken } = useAuth();
|
||||
const { user } = useAuth();
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
description: '',
|
||||
|
|
@ -49,7 +49,6 @@ export function CreateTestSessionButton() {
|
|||
|
||||
try {
|
||||
if (!user) return;
|
||||
const accessToken = await getAccessToken();
|
||||
const response = await createTestSessionApiV1LooptalkTestSessionsPost({
|
||||
body: {
|
||||
name: formData.name,
|
||||
|
|
@ -61,9 +60,6 @@ export function CreateTestSessionButton() {
|
|||
concurrent_pairs: formData.test_type === 'load_test' ? formData.concurrent_pairs : undefined
|
||||
}
|
||||
},
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
toast.success('Test session created successfully');
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ interface CampaignRunsProps {
|
|||
|
||||
export function CampaignRuns({ campaignId, workflowId, searchParams }: CampaignRunsProps) {
|
||||
const router = useRouter();
|
||||
const { getAccessToken } = useAuth();
|
||||
const { isAuthenticated } = useAuth();
|
||||
const [runs, setRuns] = useState<WorkflowRunResponseSchema[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
|
@ -29,7 +29,6 @@ export function CampaignRuns({ campaignId, workflowId, searchParams }: CampaignR
|
|||
const [totalPages, setTotalPages] = useState(1);
|
||||
const [totalCount, setTotalCount] = useState(0);
|
||||
const [isExecutingFilters, setIsExecutingFilters] = useState(false);
|
||||
const [accessToken, setAccessToken] = useState<string | null>(null);
|
||||
|
||||
// Sort state (initialized from URL)
|
||||
const [sortBy, setSortBy] = useState<string | null>(() => {
|
||||
|
|
@ -50,22 +49,13 @@ export function CampaignRuns({ campaignId, workflowId, searchParams }: CampaignR
|
|||
return searchParams ? decodeFiltersFromURL(searchParams, availableAttributes) : [];
|
||||
});
|
||||
|
||||
// Get access token on mount
|
||||
useEffect(() => {
|
||||
const fetchToken = async () => {
|
||||
const token = await getAccessToken();
|
||||
setAccessToken(token);
|
||||
};
|
||||
fetchToken();
|
||||
}, [getAccessToken]);
|
||||
|
||||
const fetchCampaignRuns = useCallback(async (
|
||||
page: number,
|
||||
filters?: ActiveFilter[],
|
||||
sortByParam?: string | null,
|
||||
sortOrderParam?: 'asc' | 'desc'
|
||||
) => {
|
||||
if (!accessToken) return;
|
||||
if (!isAuthenticated) return;
|
||||
try {
|
||||
setLoading(true);
|
||||
// Prepare filter data for API
|
||||
|
|
@ -88,9 +78,6 @@ export function CampaignRuns({ campaignId, workflowId, searchParams }: CampaignR
|
|||
...(sortByParam && { sort_by: sortByParam }),
|
||||
...(sortOrderParam && { sort_order: sortOrderParam }),
|
||||
},
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
}
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
|
|
@ -111,7 +98,7 @@ export function CampaignRuns({ campaignId, workflowId, searchParams }: CampaignR
|
|||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [campaignId, accessToken]);
|
||||
}, [campaignId, isAuthenticated]);
|
||||
|
||||
const updatePageInUrl = useCallback((page: number, filters?: ActiveFilter[], sortByParam?: string | null, sortOrderParam?: 'asc' | 'desc') => {
|
||||
const params = new URLSearchParams();
|
||||
|
|
@ -136,10 +123,10 @@ export function CampaignRuns({ campaignId, workflowId, searchParams }: CampaignR
|
|||
}, [router, campaignId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (accessToken) {
|
||||
if (isAuthenticated) {
|
||||
fetchCampaignRuns(currentPage, appliedFilters, sortBy, sortOrder);
|
||||
}
|
||||
}, [currentPage, appliedFilters, fetchCampaignRuns, accessToken, sortBy, sortOrder]);
|
||||
}, [currentPage, appliedFilters, fetchCampaignRuns, isAuthenticated, sortBy, sortOrder]);
|
||||
|
||||
const handleApplyFilters = useCallback(async () => {
|
||||
setIsExecutingFilters(true);
|
||||
|
|
@ -213,7 +200,6 @@ export function CampaignRuns({ campaignId, workflowId, searchParams }: CampaignR
|
|||
sortOrder={sortOrder}
|
||||
onSort={handleSort}
|
||||
workflowId={workflowId}
|
||||
accessToken={accessToken}
|
||||
onReload={handleReload}
|
||||
title="Campaign Workflow Runs"
|
||||
emptyMessage="No workflow runs found for this campaign"
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ export interface WorkflowRunsTableProps {
|
|||
|
||||
// Navigation & Actions
|
||||
workflowId: number;
|
||||
accessToken: string | null;
|
||||
|
||||
// Reload
|
||||
onReload?: () => void;
|
||||
|
|
@ -78,7 +77,6 @@ export function WorkflowRunsTable({
|
|||
sortOrder = 'desc',
|
||||
onSort,
|
||||
workflowId,
|
||||
accessToken,
|
||||
onReload,
|
||||
title = "Workflow Run History",
|
||||
subtitle,
|
||||
|
|
@ -88,7 +86,7 @@ export function WorkflowRunsTable({
|
|||
const [selectedRowId, setSelectedRowId] = useState<number | null>(null);
|
||||
|
||||
// Media preview dialog
|
||||
const mediaPreview = MediaPreviewDialog({ accessToken });
|
||||
const mediaPreview = MediaPreviewDialog();
|
||||
|
||||
const formatDate = (dateString: string) => new Date(dateString).toLocaleString();
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ import {
|
|||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { useUserConfig } from '@/context/UserConfigContext';
|
||||
|
||||
interface Workflow {
|
||||
id: number;
|
||||
name: string;
|
||||
|
|
@ -32,7 +30,6 @@ interface WorkflowTableProps {
|
|||
|
||||
export function WorkflowTable({ workflows, showArchived }: WorkflowTableProps) {
|
||||
const router = useRouter();
|
||||
const { accessToken } = useUserConfig();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [loadingWorkflowId, setLoadingWorkflowId] = useState<number | null>(null);
|
||||
|
||||
|
|
@ -41,11 +38,6 @@ export function WorkflowTable({ workflows, showArchived }: WorkflowTableProps) {
|
|||
};
|
||||
|
||||
const handleArchiveToggle = async (id: number, currentStatus: string) => {
|
||||
if (!accessToken) {
|
||||
toast.error('Authentication required');
|
||||
return;
|
||||
}
|
||||
|
||||
const newStatus = currentStatus === 'active' ? 'archived' : 'active';
|
||||
const action = currentStatus === 'active' ? 'Archive' : 'Restore';
|
||||
|
||||
|
|
@ -59,9 +51,6 @@ export function WorkflowTable({ workflows, showArchived }: WorkflowTableProps) {
|
|||
body: {
|
||||
status: newStatus,
|
||||
},
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.data) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue