mirror of
https://github.com/bitwarden/clients.git
synced 2026-06-04 21:04:29 +08:00
[PM-34108] Add Driver's License to browser (#20638)
* [PM-34108] Add Driver's License copy actions to browser vault list * [PM-34108] Add missing Driver's License i18n keys to browser en messages * prefer else syntax
This commit is contained in:
parent
7f25ac0aba
commit
e3a22ccd34
@ -2196,6 +2196,48 @@
|
||||
"typeDriversLicenseSubtitle": {
|
||||
"message": "Drivers license or ID"
|
||||
},
|
||||
"driversLicenseDetails": {
|
||||
"message": "License details"
|
||||
},
|
||||
"dateOfBirth": {
|
||||
"message": "Date of birth"
|
||||
},
|
||||
"birthMonth": {
|
||||
"message": "Birth month"
|
||||
},
|
||||
"birthDay": {
|
||||
"message": "Birth day"
|
||||
},
|
||||
"birthYear": {
|
||||
"message": "Birth year"
|
||||
},
|
||||
"issuingCountry": {
|
||||
"message": "Issuing country"
|
||||
},
|
||||
"issuingState": {
|
||||
"message": "Issuing state / province"
|
||||
},
|
||||
"issuingAuthority": {
|
||||
"message": "Issuing authority"
|
||||
},
|
||||
"issueDate": {
|
||||
"message": "Issue date"
|
||||
},
|
||||
"issueMonth": {
|
||||
"message": "Issue month"
|
||||
},
|
||||
"issueDay": {
|
||||
"message": "Issue day"
|
||||
},
|
||||
"issueYear": {
|
||||
"message": "Issue year"
|
||||
},
|
||||
"expirationDay": {
|
||||
"message": "Expiration day"
|
||||
},
|
||||
"licenseClass": {
|
||||
"message": "License class"
|
||||
},
|
||||
"newItemHeaderLogin": {
|
||||
"message": "New Login",
|
||||
"description": "Header for new login item type"
|
||||
|
||||
@ -213,3 +213,46 @@
|
||||
</bit-menu>
|
||||
</bit-item-action>
|
||||
}
|
||||
|
||||
@if (CipherViewLikeUtils.getType(_cipher) === CipherType.DriversLicense) {
|
||||
<bit-item-action>
|
||||
@if (singleCopyableDriversLicense) {
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[label]="'copyFieldCipherName' | i18n: singleCopyableDriversLicense.key : _cipher.name"
|
||||
[appCopyField]="singleCopyableDriversLicense.field"
|
||||
[cipher]="_cipher"
|
||||
showToast
|
||||
></button>
|
||||
} @else {
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-clone"
|
||||
size="small"
|
||||
[label]="
|
||||
hasDriversLicenseValues
|
||||
? ('copyInfoTitle' | i18n: _cipher.name)
|
||||
: ('noValuesToCopy' | i18n)
|
||||
"
|
||||
[disabled]="!hasDriversLicenseValues"
|
||||
[bitMenuTriggerFor]="driversLicenseOptions"
|
||||
></button>
|
||||
<bit-menu #driversLicenseOptions>
|
||||
<button type="button" bitMenuItem appCopyField="firstName" [cipher]="_cipher">
|
||||
{{ "copyFirstName" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem appCopyField="middleName" [cipher]="_cipher">
|
||||
{{ "copyMiddleName" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem appCopyField="lastName" [cipher]="_cipher">
|
||||
{{ "copyLastName" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitMenuItem appCopyField="licenseNumber" [cipher]="_cipher">
|
||||
{{ "copyLicenseNumber" | i18n }}
|
||||
</button>
|
||||
</bit-menu>
|
||||
}
|
||||
</bit-item-action>
|
||||
}
|
||||
|
||||
@ -293,6 +293,48 @@ describe("VaultItemCopyActionsComponent", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("singleCopyableDriversLicense", () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(CipherViewLikeUtils, "hasCopyableValue")
|
||||
.mockImplementation(
|
||||
(cipher: CipherViewLike & { __copyable?: Record<string, boolean> }, field) => {
|
||||
return Boolean(cipher.__copyable?.[field]);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("returns the only copyable drivers license field", () => {
|
||||
(component.cipher() as any).__copyable = {
|
||||
firstName: false,
|
||||
middleName: false,
|
||||
lastName: false,
|
||||
licenseNumber: true,
|
||||
};
|
||||
|
||||
const result = component.singleCopyableDriversLicense;
|
||||
|
||||
expect(result).toEqual({
|
||||
key: "translated-licenseNumber",
|
||||
field: "licenseNumber",
|
||||
});
|
||||
expect(i18nService.t).toHaveBeenCalledWith("licenseNumber");
|
||||
});
|
||||
|
||||
it("returns null when multiple drivers license fields are available", () => {
|
||||
(component.cipher() as any).__copyable = {
|
||||
firstName: true,
|
||||
middleName: false,
|
||||
lastName: true,
|
||||
licenseNumber: false,
|
||||
};
|
||||
|
||||
const result = component.singleCopyableDriversLicense;
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("has*Values in non-list view", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(CipherViewLikeUtils, "isCipherListView").mockReturnValue(false);
|
||||
@ -391,6 +433,26 @@ describe("VaultItemCopyActionsComponent", () => {
|
||||
|
||||
expect(component.hasSshKeyValues).toBe(false);
|
||||
});
|
||||
|
||||
it("computes hasDriversLicenseValues from driversLicense fields", () => {
|
||||
(component.cipher() as CipherView).driversLicense = {
|
||||
firstName: "John",
|
||||
middleName: null,
|
||||
lastName: null,
|
||||
licenseNumber: null,
|
||||
} as any;
|
||||
|
||||
expect(component.hasDriversLicenseValues).toBe(true);
|
||||
|
||||
(component.cipher() as CipherView).driversLicense = {
|
||||
firstName: null,
|
||||
middleName: null,
|
||||
lastName: null,
|
||||
licenseNumber: null,
|
||||
} as any;
|
||||
|
||||
expect(component.hasDriversLicenseValues).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("has*Values in list view", () => {
|
||||
@ -463,5 +525,19 @@ describe("VaultItemCopyActionsComponent", () => {
|
||||
|
||||
expect(component.hasSshKeyValues).toBe(false);
|
||||
});
|
||||
|
||||
it("uses copyableFields for drivers license values", () => {
|
||||
(component.cipher() as CipherListView).copyableFields = [
|
||||
"DriversLicenseLicenseNumber",
|
||||
] as CopyableCipherFields[];
|
||||
|
||||
expect(component.hasDriversLicenseValues).toBe(true);
|
||||
|
||||
(component.cipher() as CipherListView).copyableFields = [
|
||||
"LoginUsername",
|
||||
] as CopyableCipherFields[];
|
||||
|
||||
expect(component.hasDriversLicenseValues).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -76,6 +76,16 @@ export class VaultItemCopyActionsComponent {
|
||||
return this.findSingleCopyableItem(this.cipher(), identityItems);
|
||||
}
|
||||
|
||||
get singleCopyableDriversLicense() {
|
||||
const driversLicenseItems: CipherItem[] = [
|
||||
{ key: "firstName", field: "firstName" },
|
||||
{ key: "middleName", field: "middleName" },
|
||||
{ key: "lastName", field: "lastName" },
|
||||
{ key: "licenseNumber", field: "licenseNumber" },
|
||||
];
|
||||
return this.findSingleCopyableItem(this.cipher(), driversLicenseItems);
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a list of CipherItems, if there is only one item with a value,
|
||||
* return it with the translated key. Otherwise return null.
|
||||
@ -110,6 +120,10 @@ export class VaultItemCopyActionsComponent {
|
||||
return this.getNumberOfSshKeyValues(this.cipher()) > 0;
|
||||
}
|
||||
|
||||
get hasDriversLicenseValues() {
|
||||
return this.getNumberOfDriversLicenseValues(this.cipher()) > 0;
|
||||
}
|
||||
|
||||
/** Sets the number of populated login values for the cipher */
|
||||
private getNumberOfLoginValues(cipher: CipherViewLike) {
|
||||
return this.getLoginCopyableItems(cipher)
|
||||
@ -168,4 +182,21 @@ export class VaultItemCopyActionsComponent {
|
||||
Boolean,
|
||||
).length;
|
||||
}
|
||||
|
||||
/** Sets the number of populated drivers license values for the cipher */
|
||||
private getNumberOfDriversLicenseValues(cipher: CipherViewLike) {
|
||||
if (CipherViewLikeUtils.isCipherListView(cipher)) {
|
||||
const copyableDriversLicenseFields: CopyableCipherFields[] = ["DriversLicenseLicenseNumber"];
|
||||
|
||||
return cipher.copyableFields.filter((field) => copyableDriversLicenseFields.includes(field))
|
||||
.length;
|
||||
}
|
||||
|
||||
return [
|
||||
cipher.driversLicense?.firstName,
|
||||
cipher.driversLicense?.middleName,
|
||||
cipher.driversLicense?.lastName,
|
||||
cipher.driversLicense?.licenseNumber,
|
||||
].filter(Boolean).length;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user