Graphical PDS migrator for AT Protocol
1import { useEffect, useState } from "preact/hooks"; 2import { MigrationStep } from "../../components/MigrationStep.tsx"; 3import { 4 parseApiResponse, 5 StepCommonProps, 6 verifyMigrationStep, 7} from "../../lib/migration-types.ts"; 8 9interface FinalizationStepProps extends StepCommonProps { 10 isActive: boolean; 11} 12 13export default function FinalizationStep({ 14 credentials: _credentials, 15 onStepComplete, 16 onStepError, 17 isActive, 18}: FinalizationStepProps) { 19 const [status, setStatus] = useState< 20 "pending" | "in-progress" | "verifying" | "completed" | "error" 21 >("pending"); 22 const [error, setError] = useState<string>(); 23 const [retryCount, setRetryCount] = useState(0); 24 const [showContinueAnyway, setShowContinueAnyway] = useState(false); 25 26 useEffect(() => { 27 if (isActive && status === "pending") { 28 startFinalization(); 29 } 30 }, [isActive]); 31 32 const startFinalization = async () => { 33 setStatus("in-progress"); 34 setError(undefined); 35 36 try { 37 const finalizeRes = await fetch("/api/migrate/finalize", { 38 method: "POST", 39 headers: { "Content-Type": "application/json" }, 40 }); 41 42 const finalizeData = await finalizeRes.text(); 43 if (!finalizeRes.ok) { 44 const parsed = parseApiResponse(finalizeData); 45 throw new Error(parsed.message || "Failed to finalize migration"); 46 } 47 48 const parsed = parseApiResponse(finalizeData); 49 if (!parsed.success) { 50 throw new Error(parsed.message || "Finalization failed"); 51 } 52 53 // Verify the finalization 54 await verifyFinalization(); 55 } catch (error) { 56 const errorMessage = error instanceof Error 57 ? error.message 58 : String(error); 59 setError(errorMessage); 60 setStatus("error"); 61 onStepError(errorMessage); 62 } 63 }; 64 65 const verifyFinalization = async () => { 66 setStatus("verifying"); 67 68 try { 69 const result = await verifyMigrationStep(4); 70 71 if (result.ready) { 72 setStatus("completed"); 73 setRetryCount(0); 74 setShowContinueAnyway(false); 75 onStepComplete(); 76 } else { 77 const statusDetails = { 78 activated: result.activated, 79 validDid: result.validDid, 80 }; 81 const errorMessage = `${ 82 result.reason || "Verification failed" 83 }\nStatus details: ${JSON.stringify(statusDetails, null, 2)}`; 84 85 setRetryCount((prev) => prev + 1); 86 if (retryCount >= 1) { 87 setShowContinueAnyway(true); 88 } 89 90 setError(errorMessage); 91 setStatus("error"); 92 onStepError(errorMessage, true); 93 } 94 } catch (error) { 95 const errorMessage = error instanceof Error 96 ? error.message 97 : String(error); 98 setRetryCount((prev) => prev + 1); 99 if (retryCount >= 1) { 100 setShowContinueAnyway(true); 101 } 102 103 setError(errorMessage); 104 setStatus("error"); 105 onStepError(errorMessage, true); 106 } 107 }; 108 109 const retryVerification = async () => { 110 await verifyFinalization(); 111 }; 112 113 const continueAnyway = () => { 114 setStatus("completed"); 115 setShowContinueAnyway(false); 116 onStepComplete(); 117 }; 118 119 return ( 120 <MigrationStep 121 name="Finalize Migration" 122 status={status} 123 error={error} 124 isVerificationError={status === "error" && 125 error?.includes("Verification failed")} 126 index={3} 127 onRetryVerification={retryVerification} 128 > 129 {status === "error" && showContinueAnyway && ( 130 <div class="flex space-x-2 mt-2"> 131 <button 132 type="button" 133 onClick={continueAnyway} 134 class="px-3 py-1 text-xs bg-white border border-gray-300 text-gray-700 hover:bg-gray-100 rounded transition-colors duration-200 135 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-200 dark:hover:bg-gray-700" 136 > 137 Continue Anyway 138 </button> 139 </div> 140 )} 141 </MigrationStep> 142 ); 143}