From 2e851701f9c6e02d7eefcee2ab99f034aee81643 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Wed, 26 Mar 2025 13:56:33 +0100 Subject: [PATCH] Update multistep form flow and reduce boilerplate (#542) --- .../Components/ImportServiceAliasVault.razor | 39 +--- .../Components/ImportServiceBitwarden.razor | 39 +--- .../Components/ImportServiceCard.razor | 213 +++++++++++------- .../Components/ImportServiceKeePass.razor | 39 +--- 4 files changed, 145 insertions(+), 185 deletions(-) diff --git a/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceAliasVault.razor b/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceAliasVault.razor index 83fbaed92..a949f966e 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceAliasVault.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceAliasVault.razor @@ -2,49 +2,22 @@ @inject NavigationManager NavigationManager @inject GlobalNotificationService GlobalNotificationService @using AliasVault.ImportExport.Models +@using AliasVault.ImportExport.Importers -
-

Upload your AliasVault export file:

- -
+ ProcessFileCallback="ProcessFile">
@code { - private ImportServiceCard _importServiceCard = null!; - private IBrowserFile? _selectedFile; - - private void HandleFileUpload(InputFileChangeEventArgs e) + private async Task> ProcessFile(string fileContents) { - Logger.LogInformation($"File selected: {e.File.Name}"); - _selectedFile = e.File; - } - - private async Task ProcessFile() - { - if (_selectedFile == null) - { - throw new ArgumentException("Please select a valid AliasVault export file to import"); - } - - Logger.LogInformation($"Processing AliasVault file: {_selectedFile.Name}"); - - try - { - await using var stream = _selectedFile.OpenReadStream(); - using var reader = new StreamReader(stream); - var fileContents = await reader.ReadToEndAsync(); - } - catch - { - throw new ArgumentException("Error processing AliasVault file. Please check the file format and try again."); - } + // TODO: Implement AliasVault import. + await Task.Delay(50); + return new List(); } private void RefreshVault() diff --git a/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceBitwarden.razor b/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceBitwarden.razor index ee68a7acd..8f38f5e65 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceBitwarden.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceBitwarden.razor @@ -1,3 +1,4 @@ +@using AliasVault.ImportExport.Models @using AliasVault.ImportExport.Importers @inject NavigationManager NavigationManager @inject GlobalNotificationService GlobalNotificationService @@ -8,47 +9,15 @@ Description="Import passwords from your Bitwarden vault" LogoUrl="img/importers/bitwarden.svg" OnImportComplete="RefreshVault" - OnImportConfirmed="ProcessFile" - @ref="_importServiceCard"> -
-

Upload your Bitwarden CSV export file:

- -
+ ProcessFileCallback="ProcessFile"> @code { - private ImportServiceCard _importServiceCard = null!; - private IBrowserFile? _selectedFile; - - private void HandleFileUpload(InputFileChangeEventArgs e) + private async Task> ProcessFile(string fileContents) { - Logger.LogInformation($"File selected: {e.File.Name}"); - _selectedFile = e.File; + return await BitwardenImporter.ImportFromCsvAsync(fileContents); } - private async Task ProcessFile() - { - if (_selectedFile == null || string.IsNullOrEmpty(_selectedFile.Name)) - { - throw new ArgumentException("Please select a valid Bitwarden CSV file to import"); - } - - Logger.LogInformation($"Processing Bitwarden file: {_selectedFile.Name}"); - - try - { - await using var stream = _selectedFile.OpenReadStream(); - using var reader = new StreamReader(stream); - var fileContents = await reader.ReadToEndAsync(); - - var importCredentials = await BitwardenImporter.ImportFromCsvAsync(fileContents); - _importServiceCard.SetImportedCredentials(importCredentials); - } - catch - { - throw new ArgumentException("Error processing Bitwarden CSV file. Please check the file format and try again."); - } - } private void RefreshVault() { diff --git a/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceCard.razor b/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceCard.razor index ec5f02b78..d965074fe 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceCard.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceCard.razor @@ -47,76 +47,80 @@ + @switch (CurrentStep) + { + case ImportStep.FileUpload: + @if (!string.IsNullOrEmpty(ImportError)) + { + + } - @if (IsImporting) - { - - } - else - { - @switch (CurrentStep) - { - case ImportStep.FileUpload: - @if (!string.IsNullOrEmpty(ImportError)) - { - - } + @if (IsImporting) + { + + } + +
- @ChildContent +

Upload your @ServiceName export file:

+
-
- break; +
+ break; - case ImportStep.Preview: -
-

Check if the credentials are correct before importing:

-
- - - - - - - - - - @foreach (var credential in ImportedCredentials.Take(10)) - { - - - - - - } - -
ServiceUsernamePassword
@credential.ServiceName@credential.Username@(new string('*', credential.Password?.Length ?? 0))
- @if (ImportedCredentials.Count > 10) + case ImportStep.Preview: +
+

Check if the following detected credentials look correct before continuing:

+ + + + + + + + + + @foreach (var credential in ImportedCredentials.Take(3)) { -

... and @(ImportedCredentials.Count - 10) more credentials

+ + + + + } - - -
- - -
- break; + +
ServiceUsernamePassword
@credential.ServiceName@credential.Username@(new string('*', credential.Password?.Length ?? 0))
+ @if (ImportedCredentials.Count > 3) + { +

... and @(ImportedCredentials.Count - 3) more credentials

+ } +
+
+ + +
+ break; - case ImportStep.Confirm: + case ImportStep.Confirm: + @if (IsImporting) + { + + } + else {
-

Are you sure you want to import @ImportedCredentials.Count credentials?

+

Are you sure you want to import (@ImportedCredentials.Count) credentials? Note: the import process can take a short while.

- break; - } + } + break; }
@@ -159,6 +163,9 @@ [Parameter] public EventCallback OnImportConfirmed { get; set; } + [Parameter] + public Func>> ProcessFileCallback { get; set; } = default!; + private bool IsModalOpen { get; set; } = false; private bool IsImporting { get; set; } = false; private string? ImportError { get; set; } @@ -177,12 +184,24 @@ private List ImportedCredentials { get; set; } = new(); /// - /// Sets the imported credentials. + /// Sets the imported credentials and continues to the preview step. /// /// The imported credentials. - public void SetImportedCredentials(List importedCredentials) + public async Task SetImportedCredentials(List importedCredentials) { ImportedCredentials = importedCredentials; + + // Continue to step 2. + await HandleNextStep(); + } + + /// + /// Called when a file is selected in the parent file upload step. + /// + public async Task FileSelected() + { + // If the file is selected, we can go to the preview step. + await HandleNextStep(); } /// @@ -208,44 +227,74 @@ StateHasChanged(); } + private async Task HandleFileUpload(InputFileChangeEventArgs e) + { + Logger.LogInformation($"File selected: {e.File.Name}"); + + if (e.File == null || string.IsNullOrEmpty(e.File.Name)) + { + ImportError = $"Please select a valid {ServiceName} export file to import"; + return; + } + + try + { + IsImporting = true; + StateHasChanged(); + + // Add file size validation + if (e.File.Size > 10 * 1024 * 1024) // 10MB limit + { + throw new Exception("File size exceeds 10MB limit"); + } + + // Create a new memory stream to hold the file data + using var stream = e.File.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024); + using var reader = new StreamReader(stream); + var fileContents = await reader.ReadToEndAsync(); + + var processingTask = ProcessFileCallback(fileContents); + var delayTask = Task.Delay(500); + + await Task.WhenAll(processingTask, delayTask); + + ImportedCredentials = await processingTask; + CurrentStep = ImportStep.Preview; + } + catch (Exception ex) + { + Logger.LogError(ex, $"Error processing {ServiceName} export file"); + ImportError = $"Error processing {ServiceName} export file. Please check the file format and try again."; + } + finally + { + IsImporting = false; + StateHasChanged(); + } + } + protected virtual async Task HandleNextStep() { - if (CurrentStep == ImportStep.FileUpload) - { - try - { - ImportError = null; - await OnImportConfirmed.InvokeAsync(); - if (ImportedCredentials.Count > 0) - { - CurrentStep = ImportStep.Preview; - } - } - catch (Exception ex) - { - ImportError = ex.Message; - Logger.LogError(ex, "Error during import confirmation"); - StateHasChanged(); - } - } - else if (CurrentStep == ImportStep.Preview) + if (CurrentStep == ImportStep.Preview) { CurrentStep = ImportStep.Confirm; } - StateHasChanged(); + else if (CurrentStep == ImportStep.Confirm) + { + await HandleModalConfirm(); + } } protected virtual void HandlePreviousStep() { - if (CurrentStep == ImportStep.Confirm) - { - CurrentStep = ImportStep.Preview; - } - else if (CurrentStep == ImportStep.Preview) + if (CurrentStep == ImportStep.Preview) { CurrentStep = ImportStep.FileUpload; } - StateHasChanged(); + else if (CurrentStep == ImportStep.Confirm) + { + CurrentStep = ImportStep.Preview; + } } /// diff --git a/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceKeePass.razor b/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceKeePass.razor index 3f475a5d0..cfe442b01 100644 --- a/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceKeePass.razor +++ b/src/AliasVault.Client/Main/Pages/Settings/ImportExport/Components/ImportServiceKeePass.razor @@ -1,6 +1,7 @@ @inject ILogger Logger @inject NavigationManager NavigationManager @inject GlobalNotificationService GlobalNotificationService +@using AliasVault.ImportExport.Models @using AliasVault.ImportExport.Importers -
-

Upload your KeePass export file:

- -
+ ProcessFileCallback="ProcessFile">
@code { - private ImportServiceCard _importServiceCard = null!; - private IBrowserFile? _selectedFile; - - private void HandleFileUpload(InputFileChangeEventArgs e) + private async Task> ProcessFile(string fileContents) { - Logger.LogInformation($"File selected: {e.File.Name}"); - _selectedFile = e.File; - } - - private async Task ProcessFile() - { - if (_selectedFile == null || string.IsNullOrEmpty(_selectedFile.Name)) - { - throw new ArgumentException("Please select a valid KeePass CSV file to import"); - } - Logger.LogInformation($"Processing KeePass file: {_selectedFile.Name}"); - - try - { - await using var stream = _selectedFile.OpenReadStream(); - using var reader = new StreamReader(stream); - var fileContents = await reader.ReadToEndAsync(); - - var importCredentials = await KeePassImporter.ImportFromCsvAsync(fileContents); - _importServiceCard.SetImportedCredentials(importCredentials); - } - catch - { - throw new ArgumentException("Error processing KeePass CSV file. Please check the file format and try again."); - } + return await KeePassImporter.ImportFromCsvAsync(fileContents); } private void RefreshVault()