/* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/click-events-have-key-events */ import { useEffect, useState } from "react"; import { faFolder, faInfoCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate, useParams, useSearch } from "@tanstack/react-router"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal, Input, PageHeader, Spinner, Switch, Tooltip } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; import { useProject } from "@app/context"; import { ProjectPermissionCommitsActions, ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types"; import { usePopUp } from "@app/hooks"; import { useCommitRollback, useGetRollbackPreview } from "@app/hooks/api/folderCommits/queries"; import { SecretVersionDiffView } from "../SecretVersionDiffView"; interface Version { // Common fields id?: string; version: number; createdAt?: string; updatedAt?: string; // Secret-specific fields secretKey?: string; secretValue?: string; secretComment?: string; skipMultilineEncoding?: boolean; secretReminderRepeatDays?: number | null; secretReminderNote?: string | null; secretReminderRecipients?: string[]; tags?: string[]; metadata?: Record; // Folder-specific fields name?: string; envId?: string; folderId?: string; [key: string]: any; } interface RollbackChange { type: "secret" | "folder"; id: string; versionId: string; fromVersion?: number; changeType: "create" | "update" | "delete"; commitId: string; secretKey?: string; secretVersion?: number; folderName?: string; folderVersion?: number; versions?: Version[]; createdAt?: string; } interface FolderChanges { folderId: string; folderName: string; folderPath: string; changes: RollbackChange[]; } export const RollbackPreviewTab = (): JSX.Element => { const [deepRollback, setDeepRollback] = useState(false); const [message, setMessage] = useState(""); const [selectedFolderId, setSelectedFolderId] = useState(null); const { currentProject } = useProject(); const envSlug = useParams({ from: ROUTE_PATHS.SecretManager.RollbackPreviewPage.id, select: (el) => el.environment }); const selectedCommitId = useParams({ from: ROUTE_PATHS.SecretManager.RollbackPreviewPage.id, select: (el) => el.commitId }); const folderId = useParams({ from: ROUTE_PATHS.SecretManager.RollbackPreviewPage.id, select: (el) => el.folderId }); const navigate = useNavigate(); const routerQueryParams = useSearch({ from: ROUTE_PATHS.SecretManager.RollbackPreviewPage.id }); const secretPath = (routerQueryParams.secretPath as string) || "/"; const goBackToHistory = () => { navigate({ to: "/projects/secret-management/$projectId/commits/$environment/$folderId", params: { projectId: currentProject.id, folderId, environment: envSlug }, search: (query) => ({ ...query, secretPath }) }); }; const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ "rollbackConfirm" ] as const); const { mutateAsync: rollback } = useCommitRollback({ projectId: currentProject.id, commitId: selectedCommitId, folderId, deepRollback, environment: envSlug, directory: secretPath, envSlug }); const { data: rollbackChangesNested, isLoading } = useGetRollbackPreview( folderId, selectedCommitId, envSlug, currentProject.id, deepRollback, secretPath ); const handleRollback = async (): Promise => { try { await rollback(message); createNotification({ type: "success", text: "Rollback completed successfully" }); handlePopUpClose("rollbackConfirm"); goBackToHistory(); } catch (error) { createNotification({ type: "error", text: error instanceof Error ? error.message : "Failed to rollback changes" }); } }; const folderChanges: FolderChanges[] = rollbackChangesNested || []; const currentFolderChanges: FolderChanges = folderChanges.find( (folder) => folder.folderId === folderId ) || { folderId: folderId || "", folderName: "Current Folder", folderPath: secretPath, changes: [] }; const nestedFolderChanges: FolderChanges[] = folderChanges.filter( (folder) => folder.folderId !== folderId ); useEffect(() => { // Select the current folder by default if (folderChanges.length > 0) { setSelectedFolderId(currentFolderChanges.folderId); } }, [folderChanges, currentFolderChanges.folderId]); if (!selectedCommitId) { return (

Select a commit to view rollback preview

); } if (isLoading) { return (
); } const renderSidebar = (): JSX.Element => { return (
setSelectedFolderId(currentFolderChanges.folderId)} >
{currentFolderChanges.folderPath || currentFolderChanges.folderName}
{currentFolderChanges.changes.length > 0 && ( {currentFolderChanges.changes.length} )}
{deepRollback && nestedFolderChanges.length > 0 && ( <>
Child folders to be restored
{nestedFolderChanges.map((folder) => (
setSelectedFolderId(folder.folderId)} >
{folder.folderPath || folder.folderName}
{folder.changes.length > 0 && ( {folder.changes.length} )}
))} )}
); }; const getSelectedFolderChanges = (): RollbackChange[] => { if (!selectedFolderId) return []; const folder = folderChanges.find((f) => f.folderId === selectedFolderId); return folder?.changes || []; }; const renderMainContent = (): JSX.Element => { const selectedFolderChanges = getSelectedFolderChanges(); const selectedFolder = folderChanges.find((f) => f.folderId === selectedFolderId); if (!selectedFolder || selectedFolderChanges.length === 0) { return (

No changes in selected folder

); } return (
{selectedFolderChanges.map((change) => (
))}
); }; return (
{renderSidebar()} {renderMainContent()}
Restore All Child Folders
setMessage(e.target.value)} className="w-full border-mineshaft-500 bg-mineshaft-700 py-2 text-sm" maxLength={256} />
handlePopUpToggle("rollbackConfirm", isOpen)} onDeleteApproved={handleRollback} buttonText="Restore" />
{" "}
{" "}
); };