[CP-30] Added creditCardNumber pipe for viewing saved card numbers properly (#590)
Co-authored-by: Hinton <oscar@oscarhinton.com>
This commit is contained in:
parent
2e2849b4de
commit
7e05089d73
|
@ -23,6 +23,7 @@ import { StopPropDirective } from "./directives/stop-prop.directive";
|
||||||
import { TrueFalseValueDirective } from "./directives/true-false-value.directive";
|
import { TrueFalseValueDirective } from "./directives/true-false-value.directive";
|
||||||
import { ColorPasswordCountPipe } from "./pipes/color-password-count.pipe";
|
import { ColorPasswordCountPipe } from "./pipes/color-password-count.pipe";
|
||||||
import { ColorPasswordPipe } from "./pipes/color-password.pipe";
|
import { ColorPasswordPipe } from "./pipes/color-password.pipe";
|
||||||
|
import { CreditCardNumberPipe } from "./pipes/credit-card-number.pipe";
|
||||||
import { EllipsisPipe } from "./pipes/ellipsis.pipe";
|
import { EllipsisPipe } from "./pipes/ellipsis.pipe";
|
||||||
import { I18nPipe } from "./pipes/i18n.pipe";
|
import { I18nPipe } from "./pipes/i18n.pipe";
|
||||||
import { SearchCiphersPipe } from "./pipes/search-ciphers.pipe";
|
import { SearchCiphersPipe } from "./pipes/search-ciphers.pipe";
|
||||||
|
@ -44,15 +45,19 @@ import { UserNamePipe } from "./pipes/user-name.pipe";
|
||||||
A11yInvalidDirective,
|
A11yInvalidDirective,
|
||||||
A11yTitleDirective,
|
A11yTitleDirective,
|
||||||
ApiActionDirective,
|
ApiActionDirective,
|
||||||
AvatarComponent,
|
|
||||||
AutofocusDirective,
|
AutofocusDirective,
|
||||||
|
AvatarComponent,
|
||||||
BlurClickDirective,
|
BlurClickDirective,
|
||||||
BoxRowDirective,
|
BoxRowDirective,
|
||||||
|
CalloutComponent,
|
||||||
ColorPasswordCountPipe,
|
ColorPasswordCountPipe,
|
||||||
ColorPasswordPipe,
|
ColorPasswordPipe,
|
||||||
|
CreditCardNumberPipe,
|
||||||
EllipsisPipe,
|
EllipsisPipe,
|
||||||
|
ExportScopeCalloutComponent,
|
||||||
FallbackSrcDirective,
|
FallbackSrcDirective,
|
||||||
I18nPipe,
|
I18nPipe,
|
||||||
|
IconComponent,
|
||||||
InputStripSpacesDirective,
|
InputStripSpacesDirective,
|
||||||
InputVerbatimDirective,
|
InputVerbatimDirective,
|
||||||
NotPremiumDirective,
|
NotPremiumDirective,
|
||||||
|
@ -63,24 +68,25 @@ import { UserNamePipe } from "./pipes/user-name.pipe";
|
||||||
StopPropDirective,
|
StopPropDirective,
|
||||||
TrueFalseValueDirective,
|
TrueFalseValueDirective,
|
||||||
UserNamePipe,
|
UserNamePipe,
|
||||||
CalloutComponent,
|
|
||||||
IconComponent,
|
|
||||||
ExportScopeCalloutComponent,
|
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
A11yInvalidDirective,
|
A11yInvalidDirective,
|
||||||
A11yTitleDirective,
|
A11yTitleDirective,
|
||||||
ApiActionDirective,
|
ApiActionDirective,
|
||||||
AvatarComponent,
|
|
||||||
AutofocusDirective,
|
AutofocusDirective,
|
||||||
|
AvatarComponent,
|
||||||
BitwardenToastModule,
|
BitwardenToastModule,
|
||||||
BlurClickDirective,
|
BlurClickDirective,
|
||||||
BoxRowDirective,
|
BoxRowDirective,
|
||||||
|
CalloutComponent,
|
||||||
ColorPasswordCountPipe,
|
ColorPasswordCountPipe,
|
||||||
ColorPasswordPipe,
|
ColorPasswordPipe,
|
||||||
|
CreditCardNumberPipe,
|
||||||
EllipsisPipe,
|
EllipsisPipe,
|
||||||
|
ExportScopeCalloutComponent,
|
||||||
FallbackSrcDirective,
|
FallbackSrcDirective,
|
||||||
I18nPipe,
|
I18nPipe,
|
||||||
|
IconComponent,
|
||||||
InputStripSpacesDirective,
|
InputStripSpacesDirective,
|
||||||
InputVerbatimDirective,
|
InputVerbatimDirective,
|
||||||
NotPremiumDirective,
|
NotPremiumDirective,
|
||||||
|
@ -91,10 +97,7 @@ import { UserNamePipe } from "./pipes/user-name.pipe";
|
||||||
StopPropDirective,
|
StopPropDirective,
|
||||||
TrueFalseValueDirective,
|
TrueFalseValueDirective,
|
||||||
UserNamePipe,
|
UserNamePipe,
|
||||||
CalloutComponent,
|
|
||||||
IconComponent,
|
|
||||||
ExportScopeCalloutComponent,
|
|
||||||
],
|
],
|
||||||
providers: [UserNamePipe, SearchPipe, I18nPipe, DatePipe],
|
providers: [CreditCardNumberPipe, DatePipe, I18nPipe, SearchPipe, UserNamePipe],
|
||||||
})
|
})
|
||||||
export class JslibModule {}
|
export class JslibModule {}
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { Pipe, PipeTransform } from "@angular/core";
|
||||||
|
|
||||||
|
interface CardRuleEntry {
|
||||||
|
cardLength: number;
|
||||||
|
blocks: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// See https://baymard.com/checkout-usability/credit-card-patterns for
|
||||||
|
// all possible credit card spacing patterns. For now, we just handle
|
||||||
|
// the below.
|
||||||
|
const numberFormats: Record<string, CardRuleEntry[]> = {
|
||||||
|
Visa: [{ cardLength: 16, blocks: [4, 4, 4, 4] }],
|
||||||
|
Mastercard: [{ cardLength: 16, blocks: [4, 4, 4, 4] }],
|
||||||
|
Maestro: [
|
||||||
|
{ cardLength: 16, blocks: [4, 4, 4, 4] },
|
||||||
|
{ cardLength: 13, blocks: [4, 4, 5] },
|
||||||
|
{ cardLength: 15, blocks: [4, 6, 5] },
|
||||||
|
{ cardLength: 19, blocks: [4, 4, 4, 4, 3] },
|
||||||
|
],
|
||||||
|
Discover: [{ cardLength: 16, blocks: [4, 4, 4, 4] }],
|
||||||
|
"Diners Club": [{ cardLength: 14, blocks: [4, 6, 4] }],
|
||||||
|
JCB: [{ cardLength: 16, blocks: [4, 4, 4, 4] }],
|
||||||
|
UnionPay: [
|
||||||
|
{ cardLength: 16, blocks: [4, 4, 4, 4] },
|
||||||
|
{ cardLength: 19, blocks: [6, 13] },
|
||||||
|
],
|
||||||
|
Amex: [{ cardLength: 15, blocks: [4, 6, 5] }],
|
||||||
|
Other: [{ cardLength: 16, blocks: [4, 4, 4, 4] }],
|
||||||
|
};
|
||||||
|
|
||||||
|
@Pipe({ name: "creditCardNumber" })
|
||||||
|
export class CreditCardNumberPipe implements PipeTransform {
|
||||||
|
transform(creditCardNumber: string, brand: string): string {
|
||||||
|
let rules = numberFormats[brand];
|
||||||
|
|
||||||
|
if (rules == null) {
|
||||||
|
rules = numberFormats["Other"];
|
||||||
|
}
|
||||||
|
|
||||||
|
const cardLength = creditCardNumber.length;
|
||||||
|
|
||||||
|
let matchingRule = rules.find((r) => r.cardLength == cardLength);
|
||||||
|
if (matchingRule == null) {
|
||||||
|
matchingRule = rules[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const blocks = matchingRule.blocks;
|
||||||
|
|
||||||
|
let chunks: string[] = [];
|
||||||
|
let total = 0;
|
||||||
|
|
||||||
|
blocks.forEach((c) => {
|
||||||
|
chunks.push(creditCardNumber.slice(total, total + c));
|
||||||
|
total += c;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Append the remaining part
|
||||||
|
if (cardLength > total) {
|
||||||
|
chunks.push(creditCardNumber.slice(total));
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks.join(" ");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue