diff --git a/apps/browser-extension/src/entrypoints/popup/pages/credentials/ItemAddEdit.tsx b/apps/browser-extension/src/entrypoints/popup/pages/credentials/ItemAddEdit.tsx index 7684da038..ed985c5bd 100644 --- a/apps/browser-extension/src/entrypoints/popup/pages/credentials/ItemAddEdit.tsx +++ b/apps/browser-extension/src/entrypoints/popup/pages/credentials/ItemAddEdit.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; @@ -17,11 +17,11 @@ import { useVaultMutate } from '@/entrypoints/popup/hooks/useVaultMutate'; import { IdentityHelperUtils, CreateIdentityGenerator, convertAgeRangeToBirthdateOptions } from '@/utils/dist/shared/identity-generator'; import type { Item, ItemField, ItemType, FieldType } from '@/utils/dist/shared/models/vault'; -import { getSystemFieldsForItemType, isFieldShownByDefault, getOptionalFieldsForItemType } from '@/utils/dist/shared/models/vault'; +import { getSystemFieldsForItemType, isFieldShownByDefault } from '@/utils/dist/shared/models/vault'; import { CreatePasswordGenerator } from '@/utils/dist/shared/password-generator'; // Valid item types from the shared model -const VALID_ITEM_TYPES: ItemType[] = ['Login', 'CreditCard', 'Identity', 'Note']; +const VALID_ITEM_TYPES: ItemType[] = ['Login', 'Alias', 'CreditCard', 'Note']; // Default item type for new items const DEFAULT_ITEM_TYPE: ItemType = 'Login'; @@ -48,6 +48,15 @@ const ITEM_TYPE_OPTIONS: ItemTypeOption[] = [ ) }, + { + type: 'Alias', + titleKey: 'itemTypes.alias.title', + iconSvg: ( + + + + ) + }, { type: 'CreditCard', titleKey: 'itemTypes.creditCard.title', @@ -57,15 +66,6 @@ const ITEM_TYPE_OPTIONS: ItemTypeOption[] = [ ) }, - { - type: 'Identity', - titleKey: 'itemTypes.identity.title', - iconSvg: ( - - - - ) - }, { type: 'Note', titleKey: 'itemTypes.note.title', @@ -140,6 +140,26 @@ const ItemAddEdit: React.FC = () => { // Add menu dropdown state (unified + button) const [showAddMenu, setShowAddMenu] = useState(false); + // Track if alias was already auto-generated (to avoid re-generating on re-renders) + const aliasGeneratedRef = useRef(false); + + // Ref for the item name input field (for auto-focus) + const nameInputRef = useRef(null); + + // Track last generated values to avoid overwriting manual user entries on regenerate + const [lastGeneratedValues, setLastGeneratedValues] = useState<{ + username: string | null; + password: string | null; + email: string | null; + }>({ + username: null, + password: null, + email: null + }); + + // Track password field visibility (for showing generated passwords) + const [showPassword, setShowPassword] = useState(false); + /** * Get all applicable system fields for the current item type. * These are sorted by DefaultDisplayOrder. @@ -152,21 +172,10 @@ const ItemAddEdit: React.FC = () => { }, [item]); /** - * Get optional fields that are not shown by default for the current item type. - * These can be added via the "+" menu. - */ - const optionalFields = useMemo(() => { - if (!item) { - return []; - } - return getOptionalFieldsForItemType(item.ItemType); - }, [item]); - - /** - * The notes field (login.notes) - handled separately for collapsible UI. + * The notes field (metadata.notes) - handled separately for collapsible UI. */ const notesField = useMemo(() => { - return applicableSystemFields.find(field => field.FieldKey === 'login.notes'); + return applicableSystemFields.find(field => field.FieldKey === 'metadata.notes'); }, [applicableSystemFields]); /** @@ -183,9 +192,16 @@ const ItemAddEdit: React.FC = () => { return isFieldShownByDefault(systemField, item.ItemType); }, [item, applicableSystemFields]); + /** + * Primary fields (like URL) that should be shown in the name block. + */ + const primaryFields = useMemo(() => { + return applicableSystemFields.filter(field => field.Category === 'Primary'); + }, [applicableSystemFields]); + /** * Group system fields by category for organized rendering. - * Excludes metadata fields (notes) which are handled separately. + * Excludes metadata fields (notes) and header fields which are handled separately. */ const groupedSystemFields = useMemo(() => { const groups: Record = {}; @@ -195,6 +211,10 @@ const ItemAddEdit: React.FC = () => { if (field.Category === 'Metadata') { return; } + // Skip primary fields - rendered in name block + if (field.Category === 'Primary') { + return; + } const category = field.Category || 'Other'; if (!groups[category]) { @@ -231,11 +251,16 @@ const ItemAddEdit: React.FC = () => { // Check if notes should be shown by default for this type const typeFields = getSystemFieldsForItemType(effectiveType); - const notesFieldDef = typeFields.find(f => f.FieldKey === 'login.notes'); + const notesFieldDef = typeFields.find(f => f.FieldKey === 'metadata.notes'); if (notesFieldDef && isFieldShownByDefault(notesFieldDef, effectiveType)) { setShowNotes(true); } + // For Alias type, show alias fields by default + if (effectiveType === 'Alias') { + setShowAliasFields(true); + } + // Load folders if (dbContext?.sqliteClient) { const allFolders = dbContext.sqliteClient.getAllFolders(); @@ -291,6 +316,26 @@ const ItemAddEdit: React.FC = () => { } }, [dbContext?.sqliteClient, id, isEditMode, itemTypeParam, itemNameParam, navigate, setIsInitialLoading]); + /** + * Auto-generate alias when Alias type is selected in create mode. + */ + useEffect(() => { + if (!isEditMode && item?.ItemType === 'Alias' && !localLoading && dbContext?.sqliteClient && !aliasGeneratedRef.current) { + aliasGeneratedRef.current = true; + void handleGenerateAlias(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isEditMode, item?.ItemType, localLoading, dbContext?.sqliteClient]); + + /** + * Auto-focus the name input field when in add mode. + */ + useEffect(() => { + if (!isEditMode && !localLoading && nameInputRef.current) { + nameInputRef.current.focus(); + } + }, [isEditMode, localLoading]); + /** * Handle field value change. */ @@ -451,11 +496,19 @@ const ItemAddEdit: React.FC = () => { // Clear field values when changing type (except name) setFieldValues({}); setCustomFields([]); - setShowAliasFields(false); + + // For Alias type, show alias fields by default; otherwise hide + if (newType === 'Alias') { + setShowAliasFields(true); + // Reset the ref so alias will be auto-generated + aliasGeneratedRef.current = false; + } else { + setShowAliasFields(false); + } // Check if notes should be shown by default for the new type const newTypeFields = getSystemFieldsForItemType(newType); - const newNotesField = newTypeFields.find(f => f.FieldKey === 'login.notes'); + const newNotesField = newTypeFields.find(f => f.FieldKey === 'metadata.notes'); const notesShownByDefault = newNotesField ? isFieldShownByDefault(newNotesField, newType) : false; setShowNotes(notesShownByDefault); @@ -488,6 +541,7 @@ const ItemAddEdit: React.FC = () => { /** * Generate random alias and populate alias fields. * This shows the alias fields and fills them with random values. + * Only overwrites username/password/email if they're empty or match the last generated value. */ const handleGenerateAlias = useCallback(async () => { if (!dbContext?.sqliteClient) { @@ -512,25 +566,55 @@ const ItemAddEdit: React.FC = () => { const email = defaultEmailDomain ? `${identity.emailPrefix}@${defaultEmailDomain}` : identity.emailPrefix; // Set field values for alias fields - setFieldValues(prev => ({ - ...prev, - 'alias.email': email, - 'alias.first_name': identity.firstName, - 'alias.last_name': identity.lastName, - 'alias.nickname': identity.nickName, - 'alias.gender': identity.gender, - 'alias.birthdate': IdentityHelperUtils.normalizeBirthDateForDisplay(identity.birthDate.toISOString()), - // Also set username and password if they're empty - 'login.username': prev['login.username'] || identity.nickName, - 'login.password': prev['login.password'] || password - })); + setFieldValues(prev => { + const currentUsername = (prev['login.username'] as string) || ''; + const currentPassword = (prev['login.password'] as string) || ''; + const currentEmail = (prev['alias.email'] as string) || ''; + + const newValues: Record = { + ...prev, + // Always update alias identity fields + 'alias.first_name': identity.firstName, + 'alias.last_name': identity.lastName, + 'alias.nickname': identity.nickName, + 'alias.gender': identity.gender, + 'alias.birthdate': IdentityHelperUtils.normalizeBirthDateForDisplay(identity.birthDate.toISOString()) + }; + + // Only overwrite email if it's empty or matches the last generated value + if (!currentEmail || currentEmail === lastGeneratedValues.email) { + newValues['alias.email'] = email; + } + + // Only overwrite username if it's empty or matches the last generated value + if (!currentUsername || currentUsername === lastGeneratedValues.username) { + newValues['login.username'] = identity.nickName; + } + + // Only overwrite password if it's empty or matches the last generated value + if (!currentPassword || currentPassword === lastGeneratedValues.password) { + newValues['login.password'] = password; + } + + return newValues; + }); + + // Update tracking with new generated values + setLastGeneratedValues({ + username: identity.nickName, + password: password, + email: email + }); + + // Show the generated password (it's random so no need to hide) + setShowPassword(true); // Show alias fields section setShowAliasFields(true); } catch (error) { console.error('Error generating random alias:', error); } - }, [dbContext.sqliteClient, initializeGenerators]); + }, [dbContext.sqliteClient, initializeGenerators, lastGeneratedValues]); /** * Clear all alias field values but keep them visible. @@ -561,7 +645,7 @@ const ItemAddEdit: React.FC = () => { const handleRemoveNotesSection = useCallback(() => { setFieldValues(prev => ({ ...prev, - 'login.notes': '' + 'metadata.notes': '' })); setShowNotes(false); }, []); @@ -603,7 +687,7 @@ const ItemAddEdit: React.FC = () => { // Notes option - show when notes field exists, is optional (not shown by default), not yet shown, and has no value const notesIsOptional = notesField && !shouldShowFieldByDefault(notesField); - if (notesField && notesIsOptional && !showNotes && !fieldValues['login.notes'] && !isEditMode) { + if (notesField && notesIsOptional && !showNotes && !fieldValues['metadata.notes'] && !isEditMode) { options.push({ key: 'notes', label: t('credentials.notes'), @@ -631,19 +715,6 @@ const ItemAddEdit: React.FC = () => { return options; }, [showNotes, notesField, fieldValues, isEditMode, t, handleAddNotesFromMenu, handleAddCustomFieldFromMenu, shouldShowFieldByDefault]); - /** - * Whether to show the dedicated "Add alias" button. - * Show when alias fields exist as optional (not shown by default) and not yet added. - */ - const showAddAliasButton = useMemo(() => { - if (!item || isEditMode) { - return false; - } - // Check if there are any alias fields that are optional (not shown by default) - const hasOptionalAliasFields = optionalFields.some(f => f.Category === 'Alias'); - return hasOptionalAliasFields && !showAliasFields; - }, [item, optionalFields, showAliasFields, isEditMode]); - // Set header buttons useEffect(() => { const headerButtonsJSX = ( @@ -726,6 +797,8 @@ const ItemAddEdit: React.FC = () => { label={label} value={stringValue} onChange={(val) => handleFieldChange(fieldKey, val)} + showPassword={showPassword} + onShowPasswordChange={setShowPassword} /> ); @@ -774,39 +847,66 @@ const ItemAddEdit: React.FC = () => { /> ); } - }, [fieldValues, handleFieldChange]); + }, [fieldValues, handleFieldChange, showPassword]); + + /** + * Handle form submission via Enter key. + */ + const handleFormSubmit = useCallback((e: React.FormEvent) => { + e.preventDefault(); + void handleSave(); + }, [handleSave]); if (localLoading || !item) { return ; } return ( -
+
{/* Item Type Selector (create mode only) */} {!isEditMode && (
- +
+ + {selectedTypeOption?.iconSvg} + + + {t('itemTypes.creating')} {selectedTypeOption ? t(selectedTypeOption.titleKey) : ''} + +
+ + + + + {/* Regenerate alias button - icon only for flexibility */} + {item?.ItemType === 'Alias' && ( + + )} +
{/* Type Dropdown Menu */} {showTypeDropdown && ( @@ -846,44 +946,59 @@ const ItemAddEdit: React.FC = () => {
)} - {/* Item Name - Central prominent field */} -
- -
- setItem({ ...item, Name: e.target.value })} - placeholder={t('credentials.itemName')} - className={`w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:text-white ${folders.length > 0 ? 'pr-28' : ''}`} - required - /> - {/* Folder Button inside input */} - {folders.length > 0 && ( - - )} + {/* Item Name and Header fields block */} +
+
+ +
+ setItem({ ...item, Name: e.target.value })} + placeholder={t('credentials.itemName')} + className={`w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:text-white ${folders.length > 0 ? 'pr-28' : ''}`} + required + /> + {/* Folder Button inside input */} + {folders.length > 0 && ( + + )} +
+ {/* Primary fields (like URL) shown below name */} + {primaryFields.map(field => ( +
+ {renderFieldInput( + field.FieldKey, + field.Label, + field.FieldType, + field.IsHidden, + field.IsMultiValue + )} +
+ ))}
{/* Render fields grouped by category */} @@ -986,13 +1101,13 @@ const ItemAddEdit: React.FC = () => {
)} - {/* Notes Section - Hidden by default in create mode, with remove button */} - {notesField && (showNotes || isEditMode || fieldValues['login.notes']) && ( + {/* Notes Section - Hidden by default in create mode, with remove button if optional */} + {notesField && (showNotes || isEditMode || fieldValues['metadata.notes']) && (

{t('credentials.notes')} - {/* Remove button for notes in create mode */} - {!isEditMode && ( + {/* Remove button for notes in create mode - only if notes is optional (not shown by default) */} + {!isEditMode && !shouldShowFieldByDefault(notesField) && (

)} - {/* Dedicated "Add Alias" button - highlighted as core feature */} - {showAddAliasButton && ( - - )} - {/* Generic + button with dropdown menu for Notes and Custom Fields */}
)} -
+ ); }; diff --git a/apps/browser-extension/src/entrypoints/popup/pages/credentials/ItemTypeSelector.tsx b/apps/browser-extension/src/entrypoints/popup/pages/credentials/ItemTypeSelector.tsx index 7823c3138..9a5499730 100644 --- a/apps/browser-extension/src/entrypoints/popup/pages/credentials/ItemTypeSelector.tsx +++ b/apps/browser-extension/src/entrypoints/popup/pages/credentials/ItemTypeSelector.tsx @@ -29,6 +29,15 @@ const ITEM_TYPE_OPTIONS: ItemTypeOption[] = [ ) }, + { + type: 'Alias', + titleKey: 'itemTypes.alias.title', + iconSvg: ( + + + + ) + }, { type: 'CreditCard', titleKey: 'itemTypes.creditCard.title', @@ -38,15 +47,6 @@ const ITEM_TYPE_OPTIONS: ItemTypeOption[] = [ ) }, - { - type: 'Identity', - titleKey: 'itemTypes.identity.title', - iconSvg: ( - - - - ) - }, { type: 'Note', titleKey: 'itemTypes.note.title', diff --git a/apps/browser-extension/src/i18n/locales/en.json b/apps/browser-extension/src/i18n/locales/en.json index 2598353ce..c9a810572 100644 --- a/apps/browser-extension/src/i18n/locales/en.json +++ b/apps/browser-extension/src/i18n/locales/en.json @@ -295,6 +295,7 @@ "typeLabel": "Item Type", "creating": "Creating", "generateAlias": "Generate random alias", + "regenerateAlias": "Regenerate alias", "addAlias": "Add alias", "addNotes": "Add notes", "addCustomField": "Add custom field", @@ -302,14 +303,14 @@ "title": "Login", "description": "Username, password, and website credentials" }, + "alias": { + "title": "Alias", + "description": "Login with auto-generated email alias and identity" + }, "creditCard": { "title": "Credit Card", "description": "Credit card information and payment details" }, - "identity": { - "title": "Identity", - "description": "Personal information and contact details" - }, "note": { "title": "Secure Note", "description": "Encrypted notes and private information" diff --git a/apps/browser-extension/src/utils/dist/shared/models/vault/index.d.ts b/apps/browser-extension/src/utils/dist/shared/models/vault/index.d.ts index 55c7c461e..edb9c1e40 100644 --- a/apps/browser-extension/src/utils/dist/shared/models/vault/index.d.ts +++ b/apps/browser-extension/src/utils/dist/shared/models/vault/index.d.ts @@ -323,8 +323,12 @@ type ItemTag = { /** * Item types supported by the vault + * - Login: Username/password credentials with optional notes + * - Alias: Login with pre-filled alias identity fields (email, name, etc.) + * - CreditCard: Payment card information + * - Note: Secure notes */ -type ItemType = 'Login' | 'CreditCard' | 'Identity' | 'Note'; +type ItemType = 'Login' | 'Alias' | 'CreditCard' | 'Note'; /** * Item type representing vault entries in the new field-based data model. * Replaces the old Credential type. @@ -428,8 +432,8 @@ type SystemFieldDefinition = { ApplicableToTypes: Partial>; /** Whether to track field value history */ EnableHistory: boolean; - /** Category for grouping in UI */ - Category: 'Login' | 'Alias' | 'Card' | 'Identity' | 'API' | 'Metadata'; + /** Category for grouping in UI. 'Primary' fields are shown in the name block. */ + Category: 'Primary' | 'Login' | 'Alias' | 'Card' | 'Metadata'; /** Default display order within category (lower = first) */ DefaultDisplayOrder: number; }; @@ -437,6 +441,11 @@ type SystemFieldDefinition = { * Registry of all system-defined fields. * These fields are immutable and their metadata is defined in code. * DO NOT modify these definitions without careful consideration of backwards compatibility. + * + * Item Types: + * - Login: Username/password credentials (alias fields optional) + * - Alias: Login with pre-filled alias identity fields shown by default + * - CreditCard: Payment card information */ declare const SystemFieldRegistry: Record; /** diff --git a/apps/browser-extension/src/utils/dist/shared/models/vault/index.js b/apps/browser-extension/src/utils/dist/shared/models/vault/index.js index 350b1bba6..437176cfd 100644 --- a/apps/browser-extension/src/utils/dist/shared/models/vault/index.js +++ b/apps/browser-extension/src/utils/dist/shared/models/vault/index.js @@ -225,7 +225,7 @@ function itemToCredential(item) { // src/vault/SystemFieldRegistry.ts var SystemFieldRegistry = { - // Login Fields + /* =================== LOGIN FIELDS =================== */ "login.username": { FieldKey: "login.username", Label: "Username", @@ -233,7 +233,8 @@ var SystemFieldRegistry = { IsHidden: false, IsMultiValue: false, ApplicableToTypes: { - Login: { ShowByDefault: true } + Login: { ShowByDefault: true }, + Alias: { ShowByDefault: true } }, EnableHistory: true, Category: "Login", @@ -246,7 +247,8 @@ var SystemFieldRegistry = { IsHidden: true, IsMultiValue: false, ApplicableToTypes: { - Login: { ShowByDefault: true } + Login: { ShowByDefault: true }, + Alias: { ShowByDefault: true } }, EnableHistory: true, Category: "Login", @@ -259,30 +261,14 @@ var SystemFieldRegistry = { IsHidden: false, IsMultiValue: true, ApplicableToTypes: { - Login: { ShowByDefault: true } + Login: { ShowByDefault: true }, + Alias: { ShowByDefault: true } }, EnableHistory: false, - Category: "Login", - DefaultDisplayOrder: 30 + Category: "Primary", + DefaultDisplayOrder: 5 }, - // Metadata Fields - "login.notes": { - FieldKey: "login.notes", - Label: "Notes", - FieldType: "TextArea", - IsHidden: false, - IsMultiValue: false, - ApplicableToTypes: { - Login: { ShowByDefault: false }, - CreditCard: { ShowByDefault: false }, - Identity: { ShowByDefault: false }, - Note: { ShowByDefault: true } - }, - EnableHistory: false, - Category: "Metadata", - DefaultDisplayOrder: 100 - }, - // Alias Fields + /* =================== ALIAS FIELDS =================== */ "alias.email": { FieldKey: "alias.email", Label: "Email", @@ -290,7 +276,8 @@ var SystemFieldRegistry = { IsHidden: false, IsMultiValue: false, ApplicableToTypes: { - Login: { ShowByDefault: false } + Login: { ShowByDefault: false }, + Alias: { ShowByDefault: true } }, EnableHistory: true, Category: "Alias", @@ -304,7 +291,7 @@ var SystemFieldRegistry = { IsMultiValue: false, ApplicableToTypes: { Login: { ShowByDefault: false }, - Identity: { ShowByDefault: true } + Alias: { ShowByDefault: true } }, EnableHistory: false, Category: "Alias", @@ -318,7 +305,7 @@ var SystemFieldRegistry = { IsMultiValue: false, ApplicableToTypes: { Login: { ShowByDefault: false }, - Identity: { ShowByDefault: true } + Alias: { ShowByDefault: true } }, EnableHistory: false, Category: "Alias", @@ -331,7 +318,8 @@ var SystemFieldRegistry = { IsHidden: false, IsMultiValue: false, ApplicableToTypes: { - Login: { ShowByDefault: false } + Login: { ShowByDefault: false }, + Alias: { ShowByDefault: true } }, EnableHistory: false, Category: "Alias", @@ -345,7 +333,7 @@ var SystemFieldRegistry = { IsMultiValue: false, ApplicableToTypes: { Login: { ShowByDefault: false }, - Identity: { ShowByDefault: true } + Alias: { ShowByDefault: true } }, EnableHistory: false, Category: "Alias", @@ -359,19 +347,108 @@ var SystemFieldRegistry = { IsMultiValue: false, ApplicableToTypes: { Login: { ShowByDefault: false }, - Identity: { ShowByDefault: true } + Alias: { ShowByDefault: true } }, EnableHistory: false, Category: "Alias", DefaultDisplayOrder: 60 + }, + /* =================== CREDIT CARD FIELDS =================== */ + "card.cardholder_name": { + FieldKey: "card.cardholder_name", + Label: "Cardholder Name", + FieldType: "Text", + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: { + CreditCard: { ShowByDefault: true } + }, + EnableHistory: false, + Category: "Card", + DefaultDisplayOrder: 10 + }, + "card.number": { + FieldKey: "card.number", + Label: "Card Number", + FieldType: "Hidden", + IsHidden: true, + IsMultiValue: false, + ApplicableToTypes: { + CreditCard: { ShowByDefault: true } + }, + EnableHistory: false, + Category: "Card", + DefaultDisplayOrder: 20 + }, + "card.expiry_month": { + FieldKey: "card.expiry_month", + Label: "Expiry Month", + FieldType: "Text", + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: { + CreditCard: { ShowByDefault: true } + }, + EnableHistory: false, + Category: "Card", + DefaultDisplayOrder: 30 + }, + "card.expiry_year": { + FieldKey: "card.expiry_year", + Label: "Expiry Year", + FieldType: "Text", + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: { + CreditCard: { ShowByDefault: true } + }, + EnableHistory: false, + Category: "Card", + DefaultDisplayOrder: 40 + }, + "card.cvv": { + FieldKey: "card.cvv", + Label: "CVV", + FieldType: "Hidden", + IsHidden: true, + IsMultiValue: false, + ApplicableToTypes: { + CreditCard: { ShowByDefault: true } + }, + EnableHistory: false, + Category: "Card", + DefaultDisplayOrder: 50 + }, + "card.pin": { + FieldKey: "card.pin", + Label: "PIN", + FieldType: "Hidden", + IsHidden: true, + IsMultiValue: false, + ApplicableToTypes: { + CreditCard: { ShowByDefault: false } + }, + EnableHistory: false, + Category: "Card", + DefaultDisplayOrder: 60 + }, + /* =================== METADATA FIELDS =================== */ + "metadata.notes": { + FieldKey: "metadata.notes", + Label: "Notes", + FieldType: "TextArea", + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: { + Login: { ShowByDefault: false }, + Alias: { ShowByDefault: false }, + CreditCard: { ShowByDefault: false }, + Note: { ShowByDefault: true } + }, + EnableHistory: false, + Category: "Metadata", + DefaultDisplayOrder: 100 } - /* - * Note: Card, Identity, and API fields can be added here when those item types are implemented - * Example: - * 'card.number': { ... }, - * 'card.cardholder_name': { ... }, - * 'identity.phone_number': { ... }, - */ }; function getSystemField(fieldKey) { return SystemFieldRegistry[fieldKey]; diff --git a/apps/mobile-app/utils/dist/shared/models/vault/index.d.ts b/apps/mobile-app/utils/dist/shared/models/vault/index.d.ts index 55c7c461e..edb9c1e40 100644 --- a/apps/mobile-app/utils/dist/shared/models/vault/index.d.ts +++ b/apps/mobile-app/utils/dist/shared/models/vault/index.d.ts @@ -323,8 +323,12 @@ type ItemTag = { /** * Item types supported by the vault + * - Login: Username/password credentials with optional notes + * - Alias: Login with pre-filled alias identity fields (email, name, etc.) + * - CreditCard: Payment card information + * - Note: Secure notes */ -type ItemType = 'Login' | 'CreditCard' | 'Identity' | 'Note'; +type ItemType = 'Login' | 'Alias' | 'CreditCard' | 'Note'; /** * Item type representing vault entries in the new field-based data model. * Replaces the old Credential type. @@ -428,8 +432,8 @@ type SystemFieldDefinition = { ApplicableToTypes: Partial>; /** Whether to track field value history */ EnableHistory: boolean; - /** Category for grouping in UI */ - Category: 'Login' | 'Alias' | 'Card' | 'Identity' | 'API' | 'Metadata'; + /** Category for grouping in UI. 'Primary' fields are shown in the name block. */ + Category: 'Primary' | 'Login' | 'Alias' | 'Card' | 'Metadata'; /** Default display order within category (lower = first) */ DefaultDisplayOrder: number; }; @@ -437,6 +441,11 @@ type SystemFieldDefinition = { * Registry of all system-defined fields. * These fields are immutable and their metadata is defined in code. * DO NOT modify these definitions without careful consideration of backwards compatibility. + * + * Item Types: + * - Login: Username/password credentials (alias fields optional) + * - Alias: Login with pre-filled alias identity fields shown by default + * - CreditCard: Payment card information */ declare const SystemFieldRegistry: Record; /** diff --git a/apps/mobile-app/utils/dist/shared/models/vault/index.js b/apps/mobile-app/utils/dist/shared/models/vault/index.js index 350b1bba6..437176cfd 100644 --- a/apps/mobile-app/utils/dist/shared/models/vault/index.js +++ b/apps/mobile-app/utils/dist/shared/models/vault/index.js @@ -225,7 +225,7 @@ function itemToCredential(item) { // src/vault/SystemFieldRegistry.ts var SystemFieldRegistry = { - // Login Fields + /* =================== LOGIN FIELDS =================== */ "login.username": { FieldKey: "login.username", Label: "Username", @@ -233,7 +233,8 @@ var SystemFieldRegistry = { IsHidden: false, IsMultiValue: false, ApplicableToTypes: { - Login: { ShowByDefault: true } + Login: { ShowByDefault: true }, + Alias: { ShowByDefault: true } }, EnableHistory: true, Category: "Login", @@ -246,7 +247,8 @@ var SystemFieldRegistry = { IsHidden: true, IsMultiValue: false, ApplicableToTypes: { - Login: { ShowByDefault: true } + Login: { ShowByDefault: true }, + Alias: { ShowByDefault: true } }, EnableHistory: true, Category: "Login", @@ -259,30 +261,14 @@ var SystemFieldRegistry = { IsHidden: false, IsMultiValue: true, ApplicableToTypes: { - Login: { ShowByDefault: true } + Login: { ShowByDefault: true }, + Alias: { ShowByDefault: true } }, EnableHistory: false, - Category: "Login", - DefaultDisplayOrder: 30 + Category: "Primary", + DefaultDisplayOrder: 5 }, - // Metadata Fields - "login.notes": { - FieldKey: "login.notes", - Label: "Notes", - FieldType: "TextArea", - IsHidden: false, - IsMultiValue: false, - ApplicableToTypes: { - Login: { ShowByDefault: false }, - CreditCard: { ShowByDefault: false }, - Identity: { ShowByDefault: false }, - Note: { ShowByDefault: true } - }, - EnableHistory: false, - Category: "Metadata", - DefaultDisplayOrder: 100 - }, - // Alias Fields + /* =================== ALIAS FIELDS =================== */ "alias.email": { FieldKey: "alias.email", Label: "Email", @@ -290,7 +276,8 @@ var SystemFieldRegistry = { IsHidden: false, IsMultiValue: false, ApplicableToTypes: { - Login: { ShowByDefault: false } + Login: { ShowByDefault: false }, + Alias: { ShowByDefault: true } }, EnableHistory: true, Category: "Alias", @@ -304,7 +291,7 @@ var SystemFieldRegistry = { IsMultiValue: false, ApplicableToTypes: { Login: { ShowByDefault: false }, - Identity: { ShowByDefault: true } + Alias: { ShowByDefault: true } }, EnableHistory: false, Category: "Alias", @@ -318,7 +305,7 @@ var SystemFieldRegistry = { IsMultiValue: false, ApplicableToTypes: { Login: { ShowByDefault: false }, - Identity: { ShowByDefault: true } + Alias: { ShowByDefault: true } }, EnableHistory: false, Category: "Alias", @@ -331,7 +318,8 @@ var SystemFieldRegistry = { IsHidden: false, IsMultiValue: false, ApplicableToTypes: { - Login: { ShowByDefault: false } + Login: { ShowByDefault: false }, + Alias: { ShowByDefault: true } }, EnableHistory: false, Category: "Alias", @@ -345,7 +333,7 @@ var SystemFieldRegistry = { IsMultiValue: false, ApplicableToTypes: { Login: { ShowByDefault: false }, - Identity: { ShowByDefault: true } + Alias: { ShowByDefault: true } }, EnableHistory: false, Category: "Alias", @@ -359,19 +347,108 @@ var SystemFieldRegistry = { IsMultiValue: false, ApplicableToTypes: { Login: { ShowByDefault: false }, - Identity: { ShowByDefault: true } + Alias: { ShowByDefault: true } }, EnableHistory: false, Category: "Alias", DefaultDisplayOrder: 60 + }, + /* =================== CREDIT CARD FIELDS =================== */ + "card.cardholder_name": { + FieldKey: "card.cardholder_name", + Label: "Cardholder Name", + FieldType: "Text", + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: { + CreditCard: { ShowByDefault: true } + }, + EnableHistory: false, + Category: "Card", + DefaultDisplayOrder: 10 + }, + "card.number": { + FieldKey: "card.number", + Label: "Card Number", + FieldType: "Hidden", + IsHidden: true, + IsMultiValue: false, + ApplicableToTypes: { + CreditCard: { ShowByDefault: true } + }, + EnableHistory: false, + Category: "Card", + DefaultDisplayOrder: 20 + }, + "card.expiry_month": { + FieldKey: "card.expiry_month", + Label: "Expiry Month", + FieldType: "Text", + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: { + CreditCard: { ShowByDefault: true } + }, + EnableHistory: false, + Category: "Card", + DefaultDisplayOrder: 30 + }, + "card.expiry_year": { + FieldKey: "card.expiry_year", + Label: "Expiry Year", + FieldType: "Text", + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: { + CreditCard: { ShowByDefault: true } + }, + EnableHistory: false, + Category: "Card", + DefaultDisplayOrder: 40 + }, + "card.cvv": { + FieldKey: "card.cvv", + Label: "CVV", + FieldType: "Hidden", + IsHidden: true, + IsMultiValue: false, + ApplicableToTypes: { + CreditCard: { ShowByDefault: true } + }, + EnableHistory: false, + Category: "Card", + DefaultDisplayOrder: 50 + }, + "card.pin": { + FieldKey: "card.pin", + Label: "PIN", + FieldType: "Hidden", + IsHidden: true, + IsMultiValue: false, + ApplicableToTypes: { + CreditCard: { ShowByDefault: false } + }, + EnableHistory: false, + Category: "Card", + DefaultDisplayOrder: 60 + }, + /* =================== METADATA FIELDS =================== */ + "metadata.notes": { + FieldKey: "metadata.notes", + Label: "Notes", + FieldType: "TextArea", + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: { + Login: { ShowByDefault: false }, + Alias: { ShowByDefault: false }, + CreditCard: { ShowByDefault: false }, + Note: { ShowByDefault: true } + }, + EnableHistory: false, + Category: "Metadata", + DefaultDisplayOrder: 100 } - /* - * Note: Card, Identity, and API fields can be added here when those item types are implemented - * Example: - * 'card.number': { ... }, - * 'card.cardholder_name': { ... }, - * 'identity.phone_number': { ... }, - */ }; function getSystemField(fieldKey) { return SystemFieldRegistry[fieldKey]; diff --git a/shared/models/src/vault/Item.ts b/shared/models/src/vault/Item.ts index 5bf8b6ba1..faa638d30 100644 --- a/shared/models/src/vault/Item.ts +++ b/shared/models/src/vault/Item.ts @@ -1,10 +1,14 @@ /** * Item types supported by the vault + * - Login: Username/password credentials with optional notes + * - Alias: Login with pre-filled alias identity fields (email, name, etc.) + * - CreditCard: Payment card information + * - Note: Secure notes */ export type ItemType = | 'Login' + | 'Alias' | 'CreditCard' - | 'Identity' | 'Note'; /** diff --git a/shared/models/src/vault/SystemFieldRegistry.ts b/shared/models/src/vault/SystemFieldRegistry.ts index 902888078..dd2e8e4b4 100644 --- a/shared/models/src/vault/SystemFieldRegistry.ts +++ b/shared/models/src/vault/SystemFieldRegistry.ts @@ -32,8 +32,8 @@ export type SystemFieldDefinition = { ApplicableToTypes: Partial>; /** Whether to track field value history */ EnableHistory: boolean; - /** Category for grouping in UI */ - Category: 'Login' | 'Alias' | 'Card' | 'Identity' | 'API' | 'Metadata'; + /** Category for grouping in UI. 'Primary' fields are shown in the name block. */ + Category: 'Primary' | 'Login' | 'Alias' | 'Card' | 'Metadata'; /** Default display order within category (lower = first) */ DefaultDisplayOrder: number; }; @@ -42,9 +42,14 @@ export type SystemFieldDefinition = { * Registry of all system-defined fields. * These fields are immutable and their metadata is defined in code. * DO NOT modify these definitions without careful consideration of backwards compatibility. + * + * Item Types: + * - Login: Username/password credentials (alias fields optional) + * - Alias: Login with pre-filled alias identity fields shown by default + * - CreditCard: Payment card information */ export const SystemFieldRegistry: Record = { - // Login Fields + /* =================== LOGIN FIELDS =================== */ 'login.username': { FieldKey: 'login.username', Label: 'Username', @@ -52,7 +57,8 @@ export const SystemFieldRegistry: Record = { IsHidden: false, IsMultiValue: false, ApplicableToTypes: { - Login: { ShowByDefault: true } + Login: { ShowByDefault: true }, + Alias: { ShowByDefault: true } }, EnableHistory: true, Category: 'Login', @@ -65,7 +71,8 @@ export const SystemFieldRegistry: Record = { IsHidden: true, IsMultiValue: false, ApplicableToTypes: { - Login: { ShowByDefault: true } + Login: { ShowByDefault: true }, + Alias: { ShowByDefault: true } }, EnableHistory: true, Category: 'Login', @@ -78,32 +85,15 @@ export const SystemFieldRegistry: Record = { IsHidden: false, IsMultiValue: true, ApplicableToTypes: { - Login: { ShowByDefault: true } + Login: { ShowByDefault: true }, + Alias: { ShowByDefault: true } }, EnableHistory: false, - Category: 'Login', - DefaultDisplayOrder: 30 + Category: 'Primary', + DefaultDisplayOrder: 5 }, - // Metadata Fields - 'login.notes': { - FieldKey: 'login.notes', - Label: 'Notes', - FieldType: 'TextArea', - IsHidden: false, - IsMultiValue: false, - ApplicableToTypes: { - Login: { ShowByDefault: false }, - CreditCard: { ShowByDefault: false }, - Identity: { ShowByDefault: false }, - Note: { ShowByDefault: true } - }, - EnableHistory: false, - Category: 'Metadata', - DefaultDisplayOrder: 100 - }, - - // Alias Fields + /* =================== ALIAS FIELDS =================== */ 'alias.email': { FieldKey: 'alias.email', Label: 'Email', @@ -111,7 +101,8 @@ export const SystemFieldRegistry: Record = { IsHidden: false, IsMultiValue: false, ApplicableToTypes: { - Login: { ShowByDefault: false } + Login: { ShowByDefault: false }, + Alias: { ShowByDefault: true } }, EnableHistory: true, Category: 'Alias', @@ -125,7 +116,7 @@ export const SystemFieldRegistry: Record = { IsMultiValue: false, ApplicableToTypes: { Login: { ShowByDefault: false }, - Identity: { ShowByDefault: true } + Alias: { ShowByDefault: true } }, EnableHistory: false, Category: 'Alias', @@ -139,7 +130,7 @@ export const SystemFieldRegistry: Record = { IsMultiValue: false, ApplicableToTypes: { Login: { ShowByDefault: false }, - Identity: { ShowByDefault: true } + Alias: { ShowByDefault: true } }, EnableHistory: false, Category: 'Alias', @@ -152,7 +143,8 @@ export const SystemFieldRegistry: Record = { IsHidden: false, IsMultiValue: false, ApplicableToTypes: { - Login: { ShowByDefault: false } + Login: { ShowByDefault: false }, + Alias: { ShowByDefault: true } }, EnableHistory: false, Category: 'Alias', @@ -166,7 +158,7 @@ export const SystemFieldRegistry: Record = { IsMultiValue: false, ApplicableToTypes: { Login: { ShowByDefault: false }, - Identity: { ShowByDefault: true } + Alias: { ShowByDefault: true } }, EnableHistory: false, Category: 'Alias', @@ -180,20 +172,110 @@ export const SystemFieldRegistry: Record = { IsMultiValue: false, ApplicableToTypes: { Login: { ShowByDefault: false }, - Identity: { ShowByDefault: true } + Alias: { ShowByDefault: true } }, EnableHistory: false, Category: 'Alias', DefaultDisplayOrder: 60 }, - /* - * Note: Card, Identity, and API fields can be added here when those item types are implemented - * Example: - * 'card.number': { ... }, - * 'card.cardholder_name': { ... }, - * 'identity.phone_number': { ... }, - */ + /* =================== CREDIT CARD FIELDS =================== */ + 'card.cardholder_name': { + FieldKey: 'card.cardholder_name', + Label: 'Cardholder Name', + FieldType: 'Text', + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: { + CreditCard: { ShowByDefault: true } + }, + EnableHistory: false, + Category: 'Card', + DefaultDisplayOrder: 10 + }, + 'card.number': { + FieldKey: 'card.number', + Label: 'Card Number', + FieldType: 'Hidden', + IsHidden: true, + IsMultiValue: false, + ApplicableToTypes: { + CreditCard: { ShowByDefault: true } + }, + EnableHistory: false, + Category: 'Card', + DefaultDisplayOrder: 20 + }, + 'card.expiry_month': { + FieldKey: 'card.expiry_month', + Label: 'Expiry Month', + FieldType: 'Text', + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: { + CreditCard: { ShowByDefault: true } + }, + EnableHistory: false, + Category: 'Card', + DefaultDisplayOrder: 30 + }, + 'card.expiry_year': { + FieldKey: 'card.expiry_year', + Label: 'Expiry Year', + FieldType: 'Text', + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: { + CreditCard: { ShowByDefault: true } + }, + EnableHistory: false, + Category: 'Card', + DefaultDisplayOrder: 40 + }, + 'card.cvv': { + FieldKey: 'card.cvv', + Label: 'CVV', + FieldType: 'Hidden', + IsHidden: true, + IsMultiValue: false, + ApplicableToTypes: { + CreditCard: { ShowByDefault: true } + }, + EnableHistory: false, + Category: 'Card', + DefaultDisplayOrder: 50 + }, + 'card.pin': { + FieldKey: 'card.pin', + Label: 'PIN', + FieldType: 'Hidden', + IsHidden: true, + IsMultiValue: false, + ApplicableToTypes: { + CreditCard: { ShowByDefault: false } + }, + EnableHistory: false, + Category: 'Card', + DefaultDisplayOrder: 60 + }, + + /* =================== METADATA FIELDS =================== */ + 'metadata.notes': { + FieldKey: 'metadata.notes', + Label: 'Notes', + FieldType: 'TextArea', + IsHidden: false, + IsMultiValue: false, + ApplicableToTypes: { + Login: { ShowByDefault: false }, + Alias: { ShowByDefault: false }, + CreditCard: { ShowByDefault: false }, + Note: { ShowByDefault: true } + }, + EnableHistory: false, + Category: 'Metadata', + DefaultDisplayOrder: 100 + } }; /**