diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index b0df5fadd4..e7ca4ac48b 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3960,6 +3960,12 @@ "autoFillOnPageLoad": { "message": "Autofill on page load?" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "cardDetails": { "message": "Card details" }, diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.html b/apps/web/src/app/vault/individual-vault/add-edit.component.html index 6313689007..b5a53bd143 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit.component.html +++ b/apps/web/src/app/vault/individual-vault/add-edit.component.html @@ -23,6 +23,9 @@ {{ "personalOwnershipPolicyInEffect" | i18n }} + + {{ "cardExpiredMessage" | i18n }} +
diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.ts b/apps/web/src/app/vault/individual-vault/add-edit.component.ts index 7a96bff039..71ccaab7dd 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit.component.ts @@ -11,6 +11,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { EventType } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -23,6 +24,7 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { Launchable } from "@bitwarden/common/vault/interfaces/launchable"; +import { CardView } from "@bitwarden/common/vault/models/view/card.view"; import { DialogService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -43,6 +45,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On viewingPasswordHistory = false; viewOnly = false; showPasswordCount = false; + cardIsExpired: boolean = false; protected totpInterval: number; protected override componentName = "app-vault-add-edit"; @@ -115,6 +118,12 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On await this.totpTick(interval); }, 1000); } + + const extensionRefreshEnabled = await firstValueFrom( + this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh), + ); + + this.cardIsExpired = extensionRefreshEnabled && this.isCardExpiryInThePast(); } ngOnDestroy() { @@ -226,6 +235,24 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On this.viewingPasswordHistory = !this.viewingPasswordHistory; } + isCardExpiryInThePast() { + if (this.cipher.card) { + const { expMonth, expYear }: CardView = this.cipher.card; + + if (expYear && expMonth) { + // `Date` months are zero-indexed + const parsedMonth = parseInt(expMonth) - 1; + const parsedYear = parseInt(expYear); + + // First day of the next month minus one, to get last day of the card month + const cardExpiry = new Date(parsedYear, parsedMonth + 1, 0); + const now = new Date(); + + return cardExpiry < now; + } + } + } + protected cleanUp() { if (this.totpInterval) { window.clearInterval(this.totpInterval); diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index f7899eae3a..108807b153 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -194,6 +194,12 @@ "dr": { "message": "Dr" }, + "cardExpiredTitle": { + "message": "Expired card" + }, + "cardExpiredMessage": { + "message": "If you've renewed it, update the card's information" + }, "expirationMonth": { "message": "Expiration month" }, diff --git a/libs/vault/src/cipher-view/cipher-view.component.html b/libs/vault/src/cipher-view/cipher-view.component.html index 68c80a7bd5..a675384ff9 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.html +++ b/libs/vault/src/cipher-view/cipher-view.component.html @@ -1,4 +1,8 @@ + + {{ "cardExpiredMessage" | i18n }} + + ; collections$: Observable; private destroyed$: Subject = new Subject(); + cardIsExpired: boolean = false; constructor( private organizationService: OrganizationService, @@ -57,6 +60,8 @@ export class CipherViewComponent implements OnInit, OnDestroy { async ngOnInit() { await this.loadCipherData(); + + this.cardIsExpired = this.isCardExpiryInThePast(); } ngOnDestroy(): void { @@ -97,4 +102,24 @@ export class CipherViewComponent implements OnInit, OnDestroy { .pipe(takeUntil(this.destroyed$)); } } + + isCardExpiryInThePast() { + if (this.cipher.card) { + const { expMonth, expYear }: CardView = this.cipher.card; + + if (expYear && expMonth) { + // `Date` months are zero-indexed + const parsedMonth = parseInt(expMonth) - 1; + const parsedYear = parseInt(expYear); + + // First day of the next month minus one, to get last day of the card month + const cardExpiry = new Date(parsedYear, parsedMonth + 1, 0); + const now = new Date(); + + return cardExpiry < now; + } + } + + return false; + } }