mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-18 21:40:41 -04:00
Update UI (#520)
This commit is contained in:
@@ -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 => (
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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\"",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user