···
status: "pending" | "in-progress" | "verifying" | "completed" | "error";
+
isVerificationError?: boolean;
···
const [migrationState, setMigrationState] = useState<
MigrationStateInfo | null
+
const [retryAttempts, setRetryAttempts] = useState<Record<number, number>>(
+
const [showContinueAnyway, setShowContinueAnyway] = useState<
+
Record<number, boolean>
const [steps, setSteps] = useState<MigrationStep[]>([
{ name: "Create Account", status: "pending" },
···
status: MigrationStep["status"],
+
isVerificationError?: boolean,
`Updating step ${index} to ${status}${
···
prevSteps.map((step, i) =>
+
? { ...step, status, error, isVerificationError }
+
isVerificationError: undefined,
···
updateStepStatus(0, "verifying");
const verified = await verifyStep(0);
+
"Account creation: Verification failed, waiting for user action",
+
// If verification succeeds, continue to data migration
+
await startDataMigration();
error instanceof Error ? error.message : String(error),
···
updateStepStatus(2, "verifying");
const verified = await verifyStep(2);
+
"Identity migration: Verification failed, waiting for user action",
+
// If verification succeeds, continue to finalization
+
await startFinalization();
console.error("Identity migration error:", error);
···
console.log(`Verification: Step ${stepNum + 1} is ready`);
updateStepStatus(stepNum, "completed");
+
// Reset retry state on success
+
setRetryAttempts((prev) => ({ ...prev, [stepNum]: 0 }));
+
setShowContinueAnyway((prev) => ({ ...prev, [stepNum]: false }));
+
// Continue to next step if not the last one
+
setTimeout(() => continueToNextStep(stepNum + 1), 500);
···
data.reason || "Verification failed"
}\nStatus details: ${JSON.stringify(statusDetails, null, 2)}`;
+
// Track retry attempts
+
const currentAttempts = retryAttempts[stepNum] || 0;
+
setRetryAttempts((prev) => ({
+
[stepNum]: currentAttempts + 1,
+
// Show continue anyway option if this is the second failure
+
if (currentAttempts >= 1) {
+
setShowContinueAnyway((prev) => ({ ...prev, [stepNum]: true }));
+
updateStepStatus(stepNum, "error", errorMessage, true);
console.error(`Verification: Error in step ${stepNum + 1}:`, e);
+
const currentAttempts = retryAttempts[stepNum] || 0;
+
setRetryAttempts((prev) => ({ ...prev, [stepNum]: currentAttempts + 1 }));
+
// Show continue anyway option if this is the second failure
+
if (currentAttempts >= 1) {
+
setShowContinueAnyway((prev) => ({ ...prev, [stepNum]: true }));
e instanceof Error ? e.message : String(e),
+
const retryVerification = async (stepNum: number) => {
+
console.log(`Retrying verification for step ${stepNum + 1}`);
+
await verifyStep(stepNum);
+
const continueAnyway = (stepNum: number) => {
+
console.log(`Continuing anyway for step ${stepNum + 1}`);
+
updateStepStatus(stepNum, "completed");
+
setShowContinueAnyway((prev) => ({ ...prev, [stepNum]: false }));
+
// Continue with next step if not the last one
+
continueToNextStep(stepNum + 1);
+
const continueToNextStep = async (stepNum: number) => {
+
// Continue to data migration
+
await startDataMigration();
+
// Continue to identity migration
+
await startIdentityMigration();
+
// Continue to finalization
+
await startFinalization();
+
const startDataMigration = async () => {
+
// Step 2: Migrate Data
+
updateStepStatus(1, "in-progress");
+
console.log("Starting data migration...");
+
// Step 2.1: Migrate Repo
+
console.log("Data migration: Starting repo migration");
+
const repoRes = await fetch("/api/migrate/data/repo", {
+
headers: { "Content-Type": "application/json" },
+
console.log("Repo migration: Response status:", repoRes.status);
+
const repoText = await repoRes.text();
+
console.log("Repo migration: Raw response:", repoText);
+
const json = JSON.parse(repoText);
+
console.error("Repo migration: Error response:", json);
+
throw new Error(json.message || "Failed to migrate repo");
+
console.error("Repo migration: Non-JSON error response:", repoText);
+
throw new Error(repoText || "Failed to migrate repo");
+
// Step 2.2: Migrate Blobs
+
console.log("Data migration: Starting blob migration");
+
const blobsRes = await fetch("/api/migrate/data/blobs", {
+
headers: { "Content-Type": "application/json" },
+
console.log("Blob migration: Response status:", blobsRes.status);
+
const blobsText = await blobsRes.text();
+
console.log("Blob migration: Raw response:", blobsText);
+
const json = JSON.parse(blobsText);
+
console.error("Blob migration: Error response:", json);
+
throw new Error(json.message || "Failed to migrate blobs");
+
"Blob migration: Non-JSON error response:",
+
throw new Error(blobsText || "Failed to migrate blobs");
+
// Step 2.3: Migrate Preferences
+
console.log("Data migration: Starting preferences migration");
+
const prefsRes = await fetch("/api/migrate/data/prefs", {
+
headers: { "Content-Type": "application/json" },
+
console.log("Preferences migration: Response status:", prefsRes.status);
+
const prefsText = await prefsRes.text();
+
console.log("Preferences migration: Raw response:", prefsText);
+
const json = JSON.parse(prefsText);
+
console.error("Preferences migration: Error response:", json);
+
throw new Error(json.message || "Failed to migrate preferences");
+
"Preferences migration: Non-JSON error response:",
+
throw new Error(prefsText || "Failed to migrate preferences");
+
console.log("Data migration: Starting verification");
+
updateStepStatus(1, "verifying");
+
const verified = await verifyStep(1);
+
console.log("Data migration: Verification result:", verified);
+
"Data migration: Verification failed, waiting for user action",
+
// If verification succeeds, continue to next step
+
await startIdentityMigration();
+
console.error("Data migration: Error caught:", error);
+
error instanceof Error ? error.message : String(error),
+
const startIdentityMigration = async () => {
+
// Step 3: Request Identity Migration
+
updateStepStatus(2, "in-progress");
+
console.log("Requesting identity migration...");
+
const requestRes = await fetch("/api/migrate/identity/request", {
+
headers: { "Content-Type": "application/json" },
+
console.log("Identity request response status:", requestRes.status);
+
const requestText = await requestRes.text();
+
console.log("Identity request response:", requestText);
+
const json = JSON.parse(requestText);
+
json.message || "Failed to request identity migration",
+
requestText || "Failed to request identity migration",
+
const jsonData = JSON.parse(requestText);
+
if (!jsonData.success) {
+
jsonData.message || "Identity migration request failed",
+
console.log("Identity migration requested successfully");
+
// Update step name to prompt for token
+
setSteps((prevSteps) =>
+
prevSteps.map((step, i) =>
+
"Enter the token sent to your email to complete identity migration",
+
// Don't continue with migration - wait for token input
+
console.error("Failed to parse identity request response:", e);
+
"Invalid response from server during identity request",
+
error instanceof Error ? error.message : String(error),
+
const startFinalization = async () => {
+
// Step 4: Finalize Migration
+
updateStepStatus(3, "in-progress");
+
const finalizeRes = await fetch("/api/migrate/finalize", {
+
headers: { "Content-Type": "application/json" },
+
const finalizeData = await finalizeRes.text();
+
const json = JSON.parse(finalizeData);
+
throw new Error(json.message || "Failed to finalize migration");
+
throw new Error(finalizeData || "Failed to finalize migration");
+
const jsonData = JSON.parse(finalizeData);
+
if (!jsonData.success) {
+
throw new Error(jsonData.message || "Finalization failed");
+
throw new Error("Invalid response from server during finalization");
+
updateStepStatus(3, "verifying");
+
const verified = await verifyStep(3);
+
"Finalization: Verification failed, waiting for user action",
+
error instanceof Error ? error.message : String(error),
{/* Migration state alert */}
···
{getStepDisplayName(step, index)}
+
<p class="text-sm text-red-600 dark:text-red-400">
+
const err = JSON.parse(step.error);
+
return err.message || step.error;
+
{step.isVerificationError && (
+
<div class="flex space-x-2 mt-2">
+
onClick={() => retryVerification(index)}
+
class="px-3 py-1 text-xs bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors duration-200 dark:bg-blue-500 dark:hover:bg-blue-400"
+
{showContinueAnyway[index] && (
+
onClick={() => continueAnyway(index)}
+
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
+
dark:bg-gray-800 dark:border-gray-600 dark:text-gray-200 dark:hover:bg-gray-700"
{index === 2 && step.status === "in-progress" &&