diff --git a/apps/desktop/src/components/vault/password-import-dialog.tsx b/apps/desktop/src/components/vault/password-import-dialog.tsx index 47f668d5..b35afa6a 100644 --- a/apps/desktop/src/components/vault/password-import-dialog.tsx +++ b/apps/desktop/src/components/vault/password-import-dialog.tsx @@ -21,7 +21,7 @@ import { isGrantCancelled, useVaultGrant, withGrant } from "./grant"; import { isVaultLocked, toastApiError } from "./vault-utils"; import { WorkspaceSelect } from "./workspace-select"; -type Source = "chrome" | "1password" | "other"; +type Source = "chrome" | "1password" | "dashlane" | "other"; type Filter = "all" | "new" | "update" | "unchanged" | "conflict"; const SOURCE_NAMES: Record = { @@ -38,7 +38,7 @@ const SOURCE_NAMES: Record = { }; const GRANT_REASON = "Enter your vault passphrase to compare the export with your saved logins."; -const isExportFile = (f: File) => /\.(csv|1pux|txt)$/i.test(f.name) || f.type === "text/csv"; +const isExportFile = (f: File) => /\.(csv|1pux|txt|zip|json)$/i.test(f.name) || f.type === "text/csv"; /** An export can be older than the vault, so replacing a saved password is always an explicit choice. */ const preselected = (r: PasswordImportRow) => r.action !== "unchanged" && !r.conflictKey && !r.replacesPassword; const plural = (n: number, one: string, many = `${one}s`) => `${n.toLocaleString()} ${n === 1 ? one : many}`; @@ -122,8 +122,12 @@ function ImportFlow({ defaultWorkspaceId, onClose }: { defaultWorkspaceId: strin const readFile = (file: File) => { if (preview.isPending) return; + if (/\.dash$/i.test(file.name)) { + setError("That's Dashlane's encrypted backup. In Dashlane, choose Export to CSV instead and drop the .zip here."); + return; + } if (!isExportFile(file)) { - setError("Choose the .csv or .1pux file your password manager exported."); + setError("Choose the .csv, .zip or .1pux file your password manager exported."); return; } preview.mutate({ file, scope: workspaceId }); @@ -169,7 +173,7 @@ function ImportFlow({ defaultWorkspaceId, onClose }: { defaultWorkspaceId: strin ? "Pick what to bring over. Logins you already saved are updated, not duplicated." : step.kind === "done" ? `Finished importing ${step.file.name}.` - : "Bring your logins over from Chrome, 1Password or another password manager. Nothing is saved until you confirm."} + : "Bring your logins over from Chrome, 1Password, Dashlane or another password manager. Nothing is saved until you confirm."} @@ -248,10 +252,11 @@ function SourcePicker({ value, onChange }: { value: Source; onChange: (s: Source const options: { id: Source; label: string; hint: string; mark: ReactNode }[] = [ { id: "chrome", label: "Chrome", hint: "Edge, Brave & Arc too", mark: }, { id: "1password", label: "1Password", hint: ".1pux or .csv", mark: }, + { id: "dashlane", label: "Dashlane", hint: ".zip or .csv", mark: }, { id: "other", label: "Other", - hint: "Bitwarden, Safari, Firefox…", + hint: "Bitwarden, Safari…", mark: ( @@ -260,7 +265,7 @@ function SourcePicker({ value, onChange }: { value: Source; onChange: (s: Source }, ]; return ( -
+
{options.map((o) => { const active = value === o.id; return ( @@ -347,6 +352,31 @@ function SourceGuide({ source }: { source: Source }) {

Archived items, credit cards and notes are left out. 2FA codes are saved and linked to their login.

)} + {source === "dashlane" && ( + <> +
    + + + Open + + app.dashlane.com + + + and sign in. + + + + Open the Vault menu at the top left, choose Settings → Export data and click Export to CSV. + + + Drop the downloaded .zip below as it is — no need to unpack it. + +
+

+ 2FA codes come along and are linked to their login. Notes, IDs and payment cards in the export are left out; Dashlane can't export passkeys. +

+ + )} {source === "other" && ( <>
@@ -354,7 +384,7 @@ function SourceGuide({ source }: { source: Source }) { ["Bitwarden", "Tools → Export vault → .csv"], ["Apple Passwords", "File → Export All Passwords"], ["Firefox", "about:logins → ⋯ → Export Logins"], - ["LastPass · Dashlane", "Advanced → Export → CSV"], + ["LastPass", "Advanced → Export → CSV"], ["Proton Pass", "Settings → Export → CSV"], ["KeePassXC", "Database → Export → CSV"], ].map(([name, where]) => ( @@ -419,13 +449,13 @@ function ExportDropZone({ busy, onFile }: { busy: boolean; onFile: (file: File)

{busy ? "Reading your export…" : dragging ? "Drop to read the file" : "Drop your export here"}

- .csv or .1pux · or browse files · you'll review everything first + .csv, .zip or .1pux · or browse files · you'll review everything first

{ @@ -802,6 +832,15 @@ function ChromeMark() { ); } +function DashlaneMark() { + return ( + + + + + ); +} + function OnePasswordMark() { return ( diff --git a/apps/desktop/src/pages/vault/logins-page.tsx b/apps/desktop/src/pages/vault/logins-page.tsx index 1d70fbdf..740c952a 100644 --- a/apps/desktop/src/pages/vault/logins-page.tsx +++ b/apps/desktop/src/pages/vault/logins-page.tsx @@ -201,7 +201,7 @@ export default function LoginsPage() {