// Signal-conditioned views in this file must use , never a
// signal-conditioned early return — see Projects.md §4.1.
import { createMemo, createSignal, Show } from "solid-js";
import { sendMessage } from "../App";
import { PeachWordmark } from "../components/PeachMark";
import {
parseImportSource,
type ImportParseResult,
} from "../../shared/import";
import {
decodePeachScript,
isPeachScript,
normalisePeachScript,
} from "../../crypto/peachScript";
import { parsePayload } from "../../crypto/peachPayload";
import type { VaultEntry } from "../../shared/types";
type ImportSubview = "menu" | "peachscript" | "file";
type ImportMergeMode = "add" | "replace";
interface ImportProps {
mode?: "existing" | "onboarding";
replacesExistingVault?: boolean;
/** Skip the menu and open straight into a specific subview. */
initialView?: ImportSubview;
onBack: () => void;
onImport: () => void | Promise;
}
export function ImportView(props: ImportProps) {
const [view, setView] = createSignal(props.initialView ?? "menu");
const mode = () => props.mode ?? "existing";
const isOnboarding = () => mode() === "onboarding";
// When the caller deep-links into a subview, the back arrow exits the
// ImportView entirely. Otherwise it returns to the import menu.
const subviewBack = () => (props.initialView ? props.onBack() : setView("menu"));
return (
{duplicateWarning()} {duplicateWarning() === 1 ? "entry appears" : "entries appear"} to be duplicates of existing ones. They were still imported — you can review and delete them in your vault.
);
}
function SubviewHeader(props: {
title: string;
onBack: () => void;
mode: "existing" | "onboarding";
}) {
// Onboarding sub-flows reuse the lock-screen header; existing-vault flows
// use the standard overlay back-arrow header.
return (
}
>
);
}
function getImportErrorCopy(error: string): string {
switch (error) {
case "UNSUPPORTED_IMPORT_FORMAT":
return "Unrecognised file format. Use CSV, Bitwarden JSON, Proton Pass JSON/CSV, or a Peach encrypted backup.";
case "INVALID_EXPORT_PASSWORD":
case "INVALID_PASSPHRASE":
return "Wrong backup password. This is the password you chose when you created the backup.";
case "INVALID_PEACH_EXPORT":
return "This Peach backup file looks corrupted or incomplete.";
case "INVALID_PEACHSCRIPT_CHARACTER":
return "This doesn't look like PeachScript. Make sure you copied the entire backup string.";
case "PAYLOAD_TOO_SHORT":
return "PeachScript looks truncated. Copy the full backup string and try again.";
case "BAD_MAGIC":
case "UNSUPPORTED_VERSION":
return "This PeachScript was created by a different version of Peach.";
case "CHECKSUM_FAILED":
return "PeachScript appears corrupted. Re-copy from the source device.";
case "CREATE_VAULT_FAILED":
return "Peach couldn't create a new local vault before importing.";
case "CLEAR_VAULT_FAILED":
return "Peach couldn't clear the existing vault before replacing it.";
case "MASTER_PASSWORD_REQUIRED":
return "Choose a master password for your new vault first.";
case "RESTORE_FAILED":
return "Restore failed. Your existing vault is unchanged.";
case "vault locked":
return "Peach is locked. Open Peach with your master password and try again.";
case "refuse to replace with empty entry set":
return "This PeachScript decoded to zero entries — refusing to wipe the vault.";
case "NO_ENTRIES_IMPORTED":
return "No importable password entries were found in that file.";
default:
return error;
}
}