From b81887697145c869807ed47e183acf2f09f40a87 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Tue, 20 Jan 2026 21:24:52 +0100 Subject: [PATCH] Update browser extension to use generic item icons (#1465) --- .../src/entrypoints/contentScript/Popup.ts | 5 +- .../popup/components/Items/ItemIcon.tsx | 240 +++--------------- apps/browser-extension/src/utils/Constants.ts | 6 - .../utils/dist/core/models/icons/index.d.ts | 64 +++++ .../src/utils/dist/core/models/icons/index.js | 82 ++++++ 5 files changed, 181 insertions(+), 216 deletions(-) create mode 100644 apps/browser-extension/src/utils/dist/core/models/icons/index.d.ts create mode 100644 apps/browser-extension/src/utils/dist/core/models/icons/index.js diff --git a/apps/browser-extension/src/entrypoints/contentScript/Popup.ts b/apps/browser-extension/src/entrypoints/contentScript/Popup.ts index 7925843bb..53a694e39 100644 --- a/apps/browser-extension/src/entrypoints/contentScript/Popup.ts +++ b/apps/browser-extension/src/entrypoints/contentScript/Popup.ts @@ -2,8 +2,9 @@ import { sendMessage } from 'webext-bridge/content-script'; import { fillItem } from '@/entrypoints/contentScript/Form'; -import { DISABLED_SITES_KEY, TEMPORARY_DISABLED_SITES_KEY, GLOBAL_AUTOFILL_POPUP_ENABLED_KEY, VAULT_LOCKED_DISMISS_UNTIL_KEY, AUTOFILL_MATCHING_MODE_KEY, CUSTOM_EMAIL_HISTORY_KEY, CUSTOM_USERNAME_HISTORY_KEY, PLACEHOLDER_ICON_SVG } from '@/utils/Constants'; +import { DISABLED_SITES_KEY, TEMPORARY_DISABLED_SITES_KEY, GLOBAL_AUTOFILL_POPUP_ENABLED_KEY, VAULT_LOCKED_DISMISS_UNTIL_KEY, AUTOFILL_MATCHING_MODE_KEY, CUSTOM_EMAIL_HISTORY_KEY, CUSTOM_USERNAME_HISTORY_KEY } from '@/utils/Constants'; import { CreateIdentityGenerator, IdentityHelperUtils } from '@/utils/dist/core/identity-generator'; +import { ItemTypeIconSvgs } from '@/utils/dist/core/models/icons'; import type { Item, ItemField } from '@/utils/dist/core/models/vault'; import { ItemTypes, FieldKey, createSystemField } from '@/utils/dist/core/models/vault'; import { CreatePasswordGenerator, PasswordGenerator, PasswordSettings } from '@/utils/dist/core/password-generator'; @@ -627,7 +628,7 @@ function createItemList(items: Item[], input: HTMLInputElement, rootContainer: H if (logoSrc) { logoContainer.innerHTML = ``; } else { - logoContainer.innerHTML = PLACEHOLDER_ICON_SVG; + logoContainer.innerHTML = ItemTypeIconSvgs.Placeholder; } itemInfo.appendChild(logoContainer); const itemTextContainer = document.createElement('div'); diff --git a/apps/browser-extension/src/entrypoints/popup/components/Items/ItemIcon.tsx b/apps/browser-extension/src/entrypoints/popup/components/Items/ItemIcon.tsx index fb5482d32..575095ad9 100644 --- a/apps/browser-extension/src/entrypoints/popup/components/Items/ItemIcon.tsx +++ b/apps/browser-extension/src/entrypoints/popup/components/Items/ItemIcon.tsx @@ -1,8 +1,12 @@ import React from 'react'; -import { PLACEHOLDER_ICON_SVG } from '@/utils/Constants'; +import type { ItemTypeIconKey } from '@/utils/dist/core/models/icons'; +import { ItemTypeIconSvgs } from '@/utils/dist/core/models/icons'; import type { Item } from '@/utils/dist/core/models/vault'; -import { FieldKey, ItemTypes } from '@/utils/dist/core/models/vault'; +import { + FieldKey, + ItemTypes, +} from '@/utils/dist/core/models/vault'; import SqliteClient from '@/utils/SqliteClient'; type ItemIconProps = { @@ -11,217 +15,50 @@ type ItemIconProps = { }; /** - * Credit card brand type + * Renders an SVG string as a React component using dangerouslySetInnerHTML. + * The SVG is wrapped in a div to apply className for sizing. */ -type CardBrand = 'visa' | 'mastercard' | 'amex' | 'discover' | 'generic'; +const SvgIcon: React.FC<{ svg: string; className?: string }> = ({ svg, className = 'w-8 h-8' }) => ( +
+); /** - * Detect credit card brand from card number using industry-standard prefixes - * @param cardNumber - The card number to detect brand from - * @returns The detected card brand + * Detect credit card brand from card number using BIN prefixes. */ -const detectCardBrand = (cardNumber: string | undefined): CardBrand => { +const detectCardBrand = (cardNumber: string | undefined): ItemTypeIconKey => { if (!cardNumber) { - return 'generic'; + return 'CreditCard'; } - // Remove spaces and dashes const cleaned = cardNumber.replace(/[\s-]/g, ''); - - // Must be mostly numeric if (!/^\d{4,}/.test(cleaned)) { - return 'generic'; + return 'CreditCard'; } - // Visa: starts with 4 if (/^4/.test(cleaned)) { - return 'visa'; + return 'Visa'; } - - // Mastercard: starts with 51-55 or 2221-2720 if (/^5[1-5]/.test(cleaned) || /^2[2-7]/.test(cleaned)) { - return 'mastercard'; + return 'Mastercard'; } - - // Amex: starts with 34 or 37 if (/^3[47]/.test(cleaned)) { - return 'amex'; + return 'Amex'; } - - // Discover: starts with 6011, 622, 644-649, 65 if (/^6(?:011|22|4[4-9]|5)/.test(cleaned)) { - return 'discover'; + return 'Discover'; } - return 'generic'; + return 'CreditCard'; }; /** - * Generic credit card icon in AliasVault style + * Get the appropriate SVG icon for a credit card brand. */ -const CreditCardIcon: React.FC<{ className?: string }> = ({ className = 'w-8 h-8' }) => ( - - - - - - -); - -/** - * Visa card icon in AliasVault style - */ -const VisaIcon: React.FC<{ className?: string }> = ({ className = 'w-8 h-8' }) => ( - - - - -); - -/** - * Mastercard icon in AliasVault style - */ -const MastercardIcon: React.FC<{ className?: string }> = ({ className = 'w-8 h-8' }) => ( - - - - - - -); - -/** - * Amex card icon in AliasVault style - */ -const AmexIcon: React.FC<{ className?: string }> = ({ className = 'w-8 h-8' }) => ( - - - - AMEX - - -); - -/** - * Discover card icon in AliasVault style - */ -const DiscoverIcon: React.FC<{ className?: string }> = ({ className = 'w-8 h-8' }) => ( - - - - - - - -); - -/** - * Note/document icon in AliasVault style - */ -const NoteIcon: React.FC<{ className?: string }> = ({ className = 'w-8 h-8' }) => ( - - - - - - - -); - -/** - * Placeholder icon for Login/Alias items - traditional key design with outline style - */ -const PlaceholderIcon: React.FC<{ className?: string }> = ({ className = 'w-8 h-8' }) => ( - - {/* Key bow (circular head) - positioned top-left */} - - {/* Key hole in bow */} - - {/* Key shaft - diagonal */} - - {/* Key teeth - perpendicular to shaft */} - - - -); - -/** - * Get the appropriate icon component based on card brand - */ -const getCardIcon = (brand: CardBrand): React.FC<{ className?: string }> => { - switch (brand) { - case 'visa': - return VisaIcon; - case 'mastercard': - return MastercardIcon; - case 'amex': - return AmexIcon; - case 'discover': - return DiscoverIcon; - default: - return CreditCardIcon; - } +const getCardIconSvg = (cardNumber: string | undefined): string => { + return ItemTypeIconSvgs[detectCardBrand(cardNumber)]; }; /** @@ -234,7 +71,7 @@ const getCardIcon = (brand: CardBrand): React.FC<{ className?: string }> => { const ItemIcon: React.FC = ({ item, className = 'w-8 h-8' }) => { // For Note type, always show note icon if (item.ItemType === ItemTypes.Note) { - return ; + return ; } // For CreditCard type, detect card brand and show appropriate icon @@ -244,10 +81,7 @@ const ItemIcon: React.FC = ({ item, className = 'w-8 h-8' }) => { ? (Array.isArray(cardNumberField.Value) ? cardNumberField.Value[0] : cardNumberField.Value) : undefined; - const brand = detectCardBrand(cardNumber); - const CardIcon = getCardIcon(brand); - - return ; + return ; } // For Login/Alias types, use Logo if available, otherwise placeholder @@ -267,7 +101,7 @@ const ItemIcon: React.FC = ({ item, className = 'w-8 h-8' }) => { if (parent) { const placeholder = document.createElement('div'); placeholder.className = className; - placeholder.innerHTML = PLACEHOLDER_ICON_SVG; + placeholder.innerHTML = ItemTypeIconSvgs.Placeholder; parent.insertBefore(placeholder, target); } }} @@ -276,20 +110,10 @@ const ItemIcon: React.FC = ({ item, className = 'w-8 h-8' }) => { } // Default placeholder for Login/Alias without logo - return ; + return ; }; export default ItemIcon; -// Export individual icons for direct use if needed -export { - CreditCardIcon, - VisaIcon, - MastercardIcon, - AmexIcon, - DiscoverIcon, - NoteIcon, - PlaceholderIcon, - detectCardBrand -}; -export type { CardBrand }; +// Export the SvgIcon component and icon utilities for direct use if needed +export { SvgIcon, getCardIconSvg }; diff --git a/apps/browser-extension/src/utils/Constants.ts b/apps/browser-extension/src/utils/Constants.ts index 3ccd3bb91..61f51a305 100644 --- a/apps/browser-extension/src/utils/Constants.ts +++ b/apps/browser-extension/src/utils/Constants.ts @@ -14,9 +14,3 @@ export const PENDING_REDIRECT_URL_KEY = 'session:pendingRedirectUrl'; export const CUSTOM_EMAIL_HISTORY_KEY = 'local:aliasvault_custom_email_history'; export const CUSTOM_USERNAME_HISTORY_KEY = 'local:aliasvault_custom_username_history'; export const SKIP_FORM_RESTORE_KEY = 'local:aliasvault_skip_form_restore'; - -/** - * Placeholder SVG for items without a logo (key icon). - * Used by both ItemIcon.tsx (React) and Popup.ts (content script). - */ -export const PLACEHOLDER_ICON_SVG = ``; diff --git a/apps/browser-extension/src/utils/dist/core/models/icons/index.d.ts b/apps/browser-extension/src/utils/dist/core/models/icons/index.d.ts new file mode 100644 index 000000000..ce141ca8a --- /dev/null +++ b/apps/browser-extension/src/utils/dist/core/models/icons/index.d.ts @@ -0,0 +1,64 @@ +/** + * Centralized icon definitions for item types. + * This is the single source of truth for all item type icons across platforms. + * + * Generated platform-specific files: + * - C# (Blazor): apps/server/Databases/AliasClientDb/Models/ItemTypeIcons.cs + * - Swift (iOS): apps/mobile-app/ios/VaultModels/ItemTypeIcons.swift + * - Kotlin (Android): apps/mobile-app/android/.../vaultstore/models/ItemTypeIcons.kt + * - TypeScript: distributed via build.sh to browser-extension and mobile-app + * + * Color scheme used in icons: + * - Primary orange: #f49541 + * - Dark orange: #d68338 + * - Light yellow: #ffe096 + * - Lighter yellow: #fbcb74 + */ +/** + * SVG icon definitions for each item type and card brand. + * All icons use a 32x32 viewBox for consistency. + */ +declare const ItemTypeIconSvgs: { + /** + * Placeholder key icon for Login/Alias items without a logo. + * Traditional key design with outline style. + */ + readonly Placeholder: "\n \n \n \n \n \n"; + /** + * Note/document icon with folded corner. + * Used for Note item type. + */ + readonly Note: "\n \n \n \n \n \n"; + /** + * Generic credit card icon. + * Used when card brand cannot be detected. + */ + readonly CreditCard: "\n \n \n \n \n"; + /** + * Visa card icon with brand styling. + */ + readonly Visa: "\n \n \n"; + /** + * Mastercard icon with overlapping circles. + */ + readonly Mastercard: "\n \n \n \n \n"; + /** + * American Express card icon. + */ + readonly Amex: "\n \n AMEX\n"; + /** + * Discover card icon with orange circle logo. + */ + readonly Discover: "\n \n \n \n \n \n"; +}; +type ItemTypeIconKey = keyof typeof ItemTypeIconSvgs; +/** + * Get SVG string for an item type icon. + */ +declare function getItemTypeIconSvg(key: ItemTypeIconKey): string; +/** + * Get all available icon keys. + */ +declare function getAllIconKeys(): ItemTypeIconKey[]; + +export { type ItemTypeIconKey, ItemTypeIconSvgs, getAllIconKeys, getItemTypeIconSvg }; diff --git a/apps/browser-extension/src/utils/dist/core/models/icons/index.js b/apps/browser-extension/src/utils/dist/core/models/icons/index.js new file mode 100644 index 000000000..5d1390408 --- /dev/null +++ b/apps/browser-extension/src/utils/dist/core/models/icons/index.js @@ -0,0 +1,82 @@ +// +// This file was automatically generated. Do not edit manually. + + +// src/icons/ItemTypeIcons.ts +var ItemTypeIconSvgs = { + /** + * Placeholder key icon for Login/Alias items without a logo. + * Traditional key design with outline style. + */ + Placeholder: ` + + + + + +`, + /** + * Note/document icon with folded corner. + * Used for Note item type. + */ + Note: ` + + + + + +`, + /** + * Generic credit card icon. + * Used when card brand cannot be detected. + */ + CreditCard: ` + + + + +`, + /** + * Visa card icon with brand styling. + */ + Visa: ` + + +`, + /** + * Mastercard icon with overlapping circles. + */ + Mastercard: ` + + + + +`, + /** + * American Express card icon. + */ + Amex: ` + + AMEX +`, + /** + * Discover card icon with orange circle logo. + */ + Discover: ` + + + + + +` +}; +function getItemTypeIconSvg(key) { + return ItemTypeIconSvgs[key]; +} +function getAllIconKeys() { + return Object.keys(ItemTypeIconSvgs); +} + +export { ItemTypeIconSvgs, getAllIconKeys, getItemTypeIconSvg }; +//# sourceMappingURL=index.js.map +//# sourceMappingURL=index.js.map \ No newline at end of file