Update UI (#520)

This commit is contained in:
Leendert de Borst
2025-10-17 10:22:40 +02:00
parent 8d3034676b
commit ac78bb1afc
7 changed files with 69 additions and 18 deletions

View File

@@ -375,6 +375,15 @@ const CredentialsList: React.FC = () => {
{t('credentials.welcomeDescription')}
</p>
</div>
) : filteredCredentials.length === 0 ? (
<div className="text-gray-500 dark:text-gray-400 space-y-2 mb-10">
<p>
{filterType === 'passkeys'
? t('credentials.noPasskeysFound')
: t('credentials.noMatchingCredentials')
}
</p>
</div>
) : (
<ul className="space-y-2">
{filteredCredentials.map(cred => (

View File

@@ -199,6 +199,8 @@
"searchPlaceholder": "Search credentials...",
"welcomeTitle": "Welcome to AliasVault!",
"welcomeDescription": "To use the AliasVault browser extension: navigate to a website and use the AliasVault autofill popup to create a new credential.",
"noPasskeysFound": "No passkeys have been created yet. Passkeys are created by visiting a website that offers passkeys as an authentication method.",
"noMatchingCredentials": "No matching credentials found",
"createdAt": "Created",
"updatedAt": "Last updated",
"autofill": "Autofill",

View File

@@ -315,7 +315,6 @@ export default function CredentialsScreen() : React.ReactNode {
color: colors.textMuted,
fontSize: 20,
lineHeight: 28,
marginRight: 'auto',
},
filterMenu: {
backgroundColor: colors.accentBackground,
@@ -512,7 +511,7 @@ export default function CredentialsScreen() : React.ReactNode {
</ThemedText>
<MaterialIcons
name={showFilterMenu ? "keyboard-arrow-up" : "keyboard-arrow-down"}
size={24}
size={28}
color={colors.text}
/>
</TouchableOpacity>
@@ -643,7 +642,12 @@ export default function CredentialsScreen() : React.ReactNode {
ListEmptyComponent={
!isLoadingCredentials ? (
<Text style={styles.emptyText}>
{searchQuery ? t('credentials.noMatchingCredentials') : t('credentials.noCredentialsFound')}
{searchQuery
? t('credentials.noMatchingCredentials')
: filterType === 'passkeys'
? t('credentials.noPasskeysFound')
: t('credentials.noCredentialsFound')
}
</Text>
) : null
}

View File

@@ -126,6 +126,7 @@
"searchPlaceholder": "Search vault...",
"noMatchingCredentials": "No matching credentials found",
"noCredentialsFound": "No credentials found. Create one to get started. Tip: you can also login to the AliasVault web app to import credentials from other password managers.",
"noPasskeysFound": "No passkeys have been created yet. Passkeys are created by visiting a website that offers passkeys as an authentication method.",
"recentEmails": "Recent emails",
"loadingEmails": "Loading emails...",
"noEmailsYet": "No emails received yet.",
@@ -197,7 +198,7 @@
"iosAutofill": "Autofill & Passkeys",
"iosAutofillSettings": {
"headerText": "You can configure AliasVault to provide native password and passkey autofill functionality in iOS. Follow the instructions below to enable it.",
"passkeyNotice": "Passkeys are created and managed via iOS and require the autofill integration to be enabled in order for passkeys to work with AliasVault.",
"passkeyNotice": "Passkeys are created through iOS. To store them in AliasVault, ensure Autofill below is enabled.",
"howToEnable": "How to enable Autofill & Passkeys:",
"step1": "1. Open iOS Settings via the button below",
"step2": "2. Go to \"General\"",

View File

@@ -77,7 +77,14 @@ extension VaultStore {
)
}
// Step 2: Write decrypted data to temp file
// Step 2: Clean up any existing connection to prevent leftover attachments
if self.dbConnection != nil {
// Try to detach any existing "source" database from previous failed attempts
try? self.dbConnection?.execute("DETACH DATABASE source")
self.dbConnection = nil
}
// Step 3: Write decrypted data to temp file
let tempDbPath = FileManager.default.temporaryDirectory.appendingPathComponent("temp_db.sqlite")
do {
try decryptedDbData.write(to: tempDbPath)
@@ -92,7 +99,7 @@ extension VaultStore {
)
}
// Step 3: Create in-memory database connection
// Step 4: Create in-memory database connection
do {
self.dbConnection = try Connection(":memory:")
} catch {
@@ -189,33 +196,36 @@ extension VaultStore {
for attempt in 1...5 {
do {
// First check if source database is still attached
let attachedDbs = try self.dbConnection?.prepare("PRAGMA database_list")
// First check if source database is still attached by collecting database names
// We collect names first to ensure the statement is finalized before detaching
var isSourceAttached = false
if let attachedDbs = attachedDbs {
for db in attachedDbs {
if let dbName = db[1] as? String, dbName == "source" {
isSourceAttached = true
break
}
if let attachedDbs = try self.dbConnection?.prepare("PRAGMA database_list") {
let dbNames = attachedDbs.map { row -> String? in
row[1] as? String
}
isSourceAttached = dbNames.contains("source")
}
if !isSourceAttached {
// Source is already detached (shouldn't happen, but handle gracefully)
detachSuccess = true
break
}
// Close any open statements or cursors
try self.dbConnection?.execute("PRAGMA optimize")
// Try to detach
try self.dbConnection?.execute("DETACH DATABASE source")
detachSuccess = true
break
} catch {
} catch let error as NSError {
// Check if error is "no such database" - means it's already detached
if error.localizedDescription.lowercased().contains("no such database") {
detachSuccess = true
break
}
lastDetachError = error
if attempt < 5 {
// Small delay before retry
Thread.sleep(forTimeInterval: Double(attempt) * 0.01)
}
}

View File

@@ -125,6 +125,21 @@ else
</div>
</div>
}
else if (!FilteredAndSortedCredentials.Any())
{
<div class="credential-card col-span-full p-4 space-y-2 bg-amber-50 border border-primary-500 rounded-lg shadow-sm dark:border-primary-700 dark:bg-gray-800">
<div class="px-4 py-6 text-gray-700 dark:text-gray-200 rounded text-center">
@if (FilterType == CredentialFilterType.Passkeys)
{
<p>@Localizer["NoPasskeysFound"]</p>
}
else
{
<p>@Localizer["NoCredentialsFound"]</p>
}
</div>
</div>
}
@foreach (var credential in FilteredAndSortedCredentials)
{
<CredentialCard Obj="@credential"/>

View File

@@ -160,4 +160,14 @@
<value>Passwords</value>
<comment>Filter option to show only username/passwords</comment>
</data>
<!-- Filtered Empty States -->
<data name="NoPasskeysFound" xml:space="preserve">
<value>No passkeys have been created yet. Passkeys are added through the browser extension or mobile apps when visiting a website that supports passkeys and offers passkey authentication.</value>
<comment>Empty state message when no passkeys are found</comment>
</data>
<data name="NoCredentialsFound" xml:space="preserve">
<value>No credentials match the selected filter.</value>
<comment>Empty state message when no credentials match the filter</comment>
</data>
</root>