1 import { type FC, useCallback, useRef, useState } from 'react';
2 import { Provider as ReduxProvider, useStore } from 'react-redux';
4 import { Form, FormikProvider } from 'formik';
5 import { c, msgid } from 'ttag';
7 import { Button } from '@proton/atoms';
8 import { useNotifications } from '@proton/components';
9 import { usePassCore } from '@proton/pass/components/Core/PassCoreProvider';
10 import { ImportForm } from '@proton/pass/components/Import/ImportForm';
11 import { ImportProgress } from '@proton/pass/components/Import/ImportProgress';
12 import { ImportVaultsPickerModal } from '@proton/pass/components/Import/ImportVaultsPickerModal';
14 type UseImportFormBeforeSubmit,
15 type UseImportFormBeforeSubmitValue,
17 } from '@proton/pass/hooks/useImportForm';
18 import type { ImportPayload } from '@proton/pass/lib/import/types';
19 import { PROVIDER_INFO_MAP } from '@proton/pass/lib/import/types';
20 import { itemsImportRequest } from '@proton/pass/store/actions/requests';
21 import type { MaybeNull } from '@proton/pass/types';
22 import { pipe, tap } from '@proton/pass/utils/fp/pipe';
23 import { PASS_APP_NAME } from '@proton/shared/lib/constants';
25 import { SettingsPanel } from './SettingsPanel';
26 import { getItemsText } from './helper';
28 export const Import: FC = () => {
29 const store = useStore();
30 const { endpoint } = usePassCore();
31 const { createNotification } = useNotifications();
32 const [importData, setImportData] = useState<MaybeNull<ImportPayload>>(null);
33 const beforeSubmitResolver = useRef<(value: UseImportFormBeforeSubmitValue) => void>();
35 const beforeSubmit = useCallback<UseImportFormBeforeSubmit>(
37 new Promise((resolve) => {
38 setImportData(payload);
39 beforeSubmitResolver.current = pipe(
42 beforeSubmitResolver.current = undefined;
50 const { form, dropzone, busy, result } = useImportForm({
52 onSubmit: (payload) => {
53 const total = payload.vaults.reduce((count, vault) => count + vault.items.length, 0);
55 key: itemsImportRequest(),
56 showCloseButton: false,
59 <ReduxProvider store={store}>
60 <ImportProgress total={total} />
67 const showResultDetails = (result?.ignored.length ?? 0) > 0 || (result?.warnings?.length ?? 0) > 0;
68 const totalImportedItems = result?.total ?? 0;
69 const totalItems = totalImportedItems + (result?.ignored.length ?? 0);
74 <SettingsPanel title={c('Label').t`Latest import`}>
75 <div className="flex flex-column gap-y-1 text-sm">
77 <span className="color-weak">{c('Label').t`Imported from: `}</span>
78 <span className="rounded bg-primary px-1 user-select-none">
79 {PROVIDER_INFO_MAP[result.provider].title}
84 <span className="color-weak">{c('Label').t`Imported on : `}</span>
85 <span>{new Date(result.importedAt * 1000).toLocaleString()}</span>
89 <span className="color-weak">{c('Label').t`Total items: `}</span>
90 <span>{getItemsText(totalItems)}</span>
94 <span className="color-weak">{c('Label').t`Total imported items: `}</span>
95 <span>{getItemsText(totalImportedItems)}</span>
98 {showResultDetails && (
99 <div className="bg-norm rounded-sm p-3 mt-2">
100 {result.ignored.length > 0 && (
101 <span className="mb-2 block">
103 msgid`The following ${result.ignored.length} item could not be imported:`,
104 `The following ${result.ignored.length} items could not be imported:`,
105 result.ignored.length
109 <div className="color-weak overflow-auto" style={{ maxHeight: 150 }}>
110 {result.ignored.map((description, idx) => (
111 <span className="block" key={`ignored-${idx}`}>
115 {result.warnings?.map((warning, idx) => (
116 <span className="block" key={`warning-${idx}`}>
124 {endpoint === 'page' && (
125 <div className="mt-2">
127 .t`To review your imported data, click on the ${PASS_APP_NAME} icon in your browser toolbar.`}
135 title={c('Label').t`Import`}
137 .t`To migrate data from another password manager, go to the password manager, export your data, then upload it to ${PASS_APP_NAME}. Once your data has been imported, delete the exported file.`}
139 <FormikProvider value={form}>
141 <ImportForm form={form} dropzone={dropzone} busy={busy} />
142 {form.values.provider && (
144 className="w-full mt-2"
146 disabled={busy || !form.isValid}
150 {busy ? c('Action').t`Importing` : c('Action').t`Import`}
156 {importData !== null && (
157 <ImportVaultsPickerModal
158 onClose={() => beforeSubmitResolver.current?.({ ok: false })}
160 onSubmit={(payload) =>
161 beforeSubmitResolver?.current?.(
162 payload.vaults.length === 0 ? { ok: false } : { ok: true, payload }