From 281ff6733047aa27627233f940c3be5bea7c2ba2 Mon Sep 17 00:00:00 2001 From: Rodrigo Barraza Date: Tue, 28 May 2024 13:44:00 -0700 Subject: [PATCH] 20138 - Suspended EFT Account Views (#2827) * Suspended EFT Account Views * Test changes * Fixes and composition-api * package * Test fixes * Text update, replacing new date with moment, test fix * Sanitization, cleanup --- auth-web/package-lock.json | 36 ++- auth-web/package.json | 4 +- .../auth/account-freeze/AccountOverview.vue | 75 +++--- .../account-freeze/AccountOverviewNSF.vue | 147 +++++++++++ .../auth/account-freeze/MakePayment.vue | 228 ++++++++++++++++++ .../auth/common/stepper/Stepper.vue | 17 ++ auth-web/src/models/invoice.ts | 10 + auth-web/src/routes/router.ts | 16 ++ auth-web/src/services/payment.services.ts | 4 + auth-web/src/stores/org.ts | 31 ++- auth-web/src/util/common-util.ts | 21 +- .../AccountFreezeUnlockView.vue | 6 +- .../auth/account-freeze/AccountHoldView.vue | 123 ++++++++++ .../AccountSuspendedUnlockView.vue | 174 +++++++++++++ .../account-freeze/AccountSuspendedView.vue | 63 ++++- .../unit/components/AccountOverview.spec.ts | 6 +- .../tests/unit/views/AccountHoldView.spec.ts | 73 ++++++ .../unit/views/AccountSuspendedView.spec.ts | 20 +- 18 files changed, 985 insertions(+), 69 deletions(-) create mode 100644 auth-web/src/components/auth/account-freeze/AccountOverviewNSF.vue create mode 100644 auth-web/src/components/auth/account-freeze/MakePayment.vue create mode 100644 auth-web/src/views/auth/account-freeze/AccountHoldView.vue create mode 100644 auth-web/src/views/auth/account-freeze/AccountSuspendedUnlockView.vue create mode 100644 auth-web/tests/unit/views/AccountHoldView.spec.ts diff --git a/auth-web/package-lock.json b/auth-web/package-lock.json index c14e97341f..b3602519a8 100644 --- a/auth-web/package-lock.json +++ b/auth-web/package-lock.json @@ -1,12 +1,12 @@ { "name": "auth-web", - "version": "2.6.16", + "version": "2.6.17", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "auth-web", - "version": "2.6.16", + "version": "2.6.17", "dependencies": { "@bcrs-shared-components/base-address": "2.0.3", "@bcrs-shared-components/bread-crumb": "1.0.8", @@ -26,6 +26,7 @@ "moment": "^2.29.4", "pinia": "^2.1.6", "pinia-class": "^0.0.3", + "sanitize-html": "^2.13.0", "sbc-common-components": "3.0.12", "vue": "2.6.14", "vue-debounce-decorator": "^1.0.1", @@ -48,6 +49,7 @@ "@pinia/testing": "^0.1.3", "@types/lodash": "^4.14.202", "@types/mime-types": "^2.1.0", + "@types/sanitize-html": "^2.11.0", "@types/vuelidate": "^0.7.13", "@typescript-eslint/eslint-plugin": "^6.4.0", "@typescript-eslint/parser": "^6.4.0", @@ -1857,6 +1859,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.31.tgz", "integrity": "sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==" }, + "node_modules/@types/sanitize-html": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.11.0.tgz", + "integrity": "sha512-7oxPGNQHXLHE48r/r/qjn7q0hlrs3kL7oZnGj0Wf/h9tj/6ibFyRkNbsDxaBBZ4XUZ0Dx5LGCyDJ04ytSofacQ==", + "dev": true, + "dependencies": { + "htmlparser2": "^8.0.0" + } + }, "node_modules/@types/semver": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", @@ -10817,9 +10828,9 @@ "devOptional": true }, "node_modules/sanitize-html": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.12.1.tgz", - "integrity": "sha512-Plh+JAn0UVDpBRP/xEjsk+xDCoOvMBwQUf/K+/cBAVuTbtX8bj2VB7S1sL1dssVpykqp0/KPSesHrqXtokVBpA==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.13.0.tgz", + "integrity": "sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA==", "dependencies": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", @@ -14729,6 +14740,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.31.tgz", "integrity": "sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==" }, + "@types/sanitize-html": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.11.0.tgz", + "integrity": "sha512-7oxPGNQHXLHE48r/r/qjn7q0hlrs3kL7oZnGj0Wf/h9tj/6ibFyRkNbsDxaBBZ4XUZ0Dx5LGCyDJ04ytSofacQ==", + "dev": true, + "requires": { + "htmlparser2": "^8.0.0" + } + }, "@types/semver": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", @@ -21283,9 +21303,9 @@ "devOptional": true }, "sanitize-html": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.12.1.tgz", - "integrity": "sha512-Plh+JAn0UVDpBRP/xEjsk+xDCoOvMBwQUf/K+/cBAVuTbtX8bj2VB7S1sL1dssVpykqp0/KPSesHrqXtokVBpA==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.13.0.tgz", + "integrity": "sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA==", "requires": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", diff --git a/auth-web/package.json b/auth-web/package.json index 66c646ccac..431323dbf5 100644 --- a/auth-web/package.json +++ b/auth-web/package.json @@ -1,6 +1,6 @@ { "name": "auth-web", - "version": "2.6.16", + "version": "2.6.17", "appName": "Auth Web", "sbcName": "SBC Common Components", "private": true, @@ -33,6 +33,7 @@ "moment": "^2.29.4", "pinia": "^2.1.6", "pinia-class": "^0.0.3", + "sanitize-html": "^2.13.0", "sbc-common-components": "3.0.12", "vue": "2.6.14", "vue-debounce-decorator": "^1.0.1", @@ -55,6 +56,7 @@ "@pinia/testing": "^0.1.3", "@types/lodash": "^4.14.202", "@types/mime-types": "^2.1.0", + "@types/sanitize-html": "^2.11.0", "@types/vuelidate": "^0.7.13", "@typescript-eslint/eslint-plugin": "^6.4.0", "@typescript-eslint/parser": "^6.4.0", diff --git a/auth-web/src/components/auth/account-freeze/AccountOverview.vue b/auth-web/src/components/auth/account-freeze/AccountOverview.vue index 8676008a06..3381311714 100644 --- a/auth-web/src/components/auth/account-freeze/AccountOverview.vue +++ b/auth-web/src/components/auth/account-freeze/AccountOverview.vue @@ -1,7 +1,14 @@ +

+ If you need further assistance, reach us at 1-877-526-1526 +

@@ -175,6 +179,10 @@ export default class Stepper extends Vue { @Emit('step-forward') emitStepForward () : void { } + + @Emit('step-back') + emitStepBack () : void { + } } @@ -394,4 +402,13 @@ export default class Stepper extends Vue { .error + hr { display: none; } + .assistance-text { + border-top: 1px solid #e0e0e0; + position: absolute; + bottom: 0; + left: 0; + width: 256px; + text-align: center; + color: #495057; + } diff --git a/auth-web/src/models/invoice.ts b/auth-web/src/models/invoice.ts index 90f9dd53a3..7dc9a3308f 100644 --- a/auth-web/src/models/invoice.ts +++ b/auth-web/src/models/invoice.ts @@ -46,6 +46,11 @@ export interface FailedInvoice { invoices?: InvoiceList[] } +export interface FailedEFTInvoice { + invoices: InvoiceList[] + totalAmountDue: number +} + export interface NonSufficientFundsInvoiceListResponse { invoices: InvoiceList[] total: number @@ -53,3 +58,8 @@ export interface NonSufficientFundsInvoiceListResponse { totalAmountRemaining: number nsfAmount: number } + +export interface EFTInvoiceListResponse { + invoices: InvoiceList[] + totalAmountDue: number +} diff --git a/auth-web/src/routes/router.ts b/auth-web/src/routes/router.ts index 13e6c35f3e..b14d66bc36 100644 --- a/auth-web/src/routes/router.ts +++ b/auth-web/src/routes/router.ts @@ -15,10 +15,12 @@ import AccountCreationSuccessView from '@/views/auth/create-account/AccountCreat import AccountDeactivate from '@/views/auth/AccountDeactivate.vue' import AccountFreezeUnlockView from '@/views/auth/account-freeze/AccountFreezeUnlockView.vue' import AccountFreezeView from '@/views/auth/account-freeze/AccountFreezeView.vue' +import AccountHoldView from '@/views/auth/account-freeze/AccountHoldView.vue' import AccountInstructions from '@/components/auth/create-account/non-bcsc/AccountInstructions.vue' import AccountLoginOptionsChooser from '@/views/auth/AccountLoginOptionsChooser.vue' import AccountLoginOptionsInfo from '@/views/auth/AccountLoginOptionsInfo.vue' import AccountSetupLanding from '@/views/auth/create-account/AccountSetupLanding.vue' +import AccountSuspendedUnlockView from '@/views/auth/account-freeze/AccountSuspendedUnlockView.vue' import AccountSwitching from '@/views/auth/AccountSwitching.vue' import AccountUnlockSuccessView from '@/views/auth/account-freeze/AccountUnlockSuccessView.vue' import AdminDashboardView from '@/views/auth/staff/AdminDashboardView.vue' @@ -421,6 +423,20 @@ export function getRoutes (): RouteConfig[] { props: true, meta: { requiresAuth: true, requiresProfile: true } }, + { + path: '/account-unlock', + name: 'account-unlock', + component: AccountSuspendedUnlockView, + props: true, + meta: { requiresAuth: true, requiresProfile: true } + }, + { + path: '/account-hold', + name: 'account-hold', + component: AccountHoldView, + props: true, + meta: { requiresAuth: true, requiresProfile: true } + }, { path: '/account-freeze', name: 'account-freeze', diff --git a/auth-web/src/services/payment.services.ts b/auth-web/src/services/payment.services.ts index 5284b0229c..288867d849 100644 --- a/auth-web/src/services/payment.services.ts +++ b/auth-web/src/services/payment.services.ts @@ -205,6 +205,10 @@ export default class PaymentService { return axios.get(`${ConfigHelper.getPayAPIURL()}/accounts/${accountId}/nsf`) } + static getEFTInvoices (accountId: string | number): AxiosPromise { + return axios.get(`${ConfigHelper.getPayAPIURL()}/accounts/${accountId}/eft`) + } + static downloadNSFInvoicesPDF (accountId: string | number): AxiosPromise { const url = `${ConfigHelper.getPayAPIURL()}/accounts/${accountId}/nsf/statement` const headers = { 'Accept': 'application/pdf' } diff --git a/auth-web/src/stores/org.ts b/auth-web/src/stores/org.ts index 2af750beab..cb47f91d4a 100644 --- a/auth-web/src/stores/org.ts +++ b/auth-web/src/stores/org.ts @@ -36,7 +36,7 @@ import { } from '@/models/Organization' import { BcolAccountDetails, BcolProfile } from '@/models/bcol' import { CreateRequestBody as CreateInvitationRequestBody, Invitation } from '@/models/Invitation' -import { FailedInvoice, NonSufficientFundsInvoiceListResponse } from '@/models/invoice' +import { EFTInvoiceListResponse, FailedEFTInvoice, FailedInvoice, NonSufficientFundsInvoiceListResponse } from '@/models/invoice' import { Products, ProductsRequestBody } from '@/models/Staff' import { StatementFilterParams, StatementNotificationSettings, StatementSettings, StatementsSummary } from '@/models/statement' import { computed, reactive, toRefs } from '@vue/composition-api' @@ -617,6 +617,12 @@ export const useOrgStore = defineStore('org', () => { } } + async function getAccountAdministrator () { + const response = await OrgService.getOrgMembers(state.currentOrganization.id, 'ACTIVE') + const result = response?.data?.members.find((member: Member) => member.membershipTypeCode === MembershipType.Admin) + return result + } + async function syncActiveOrgMembers () { const response = await OrgService.getOrgMembers(state.currentOrganization.id, 'ACTIVE') const result = response?.data?.members || [] @@ -760,6 +766,16 @@ export const useOrgStore = defineStore('org', () => { } } + async function getEFTInvoices (): Promise { + try { + const response = await PaymentService.getEFTInvoices(state.currentOrganization.id) + return response?.data || {} + } catch (error) { + // eslint-disable-next-line no-console + console.error('get EFT invoices operation failed! - ', error) + } + } + async function downloadNSFInvoicesPDF (): Promise { try { const response = await PaymentService.downloadNSFInvoicesPDF(state.currentOrganization.id) @@ -785,6 +801,17 @@ export const useOrgStore = defineStore('org', () => { } } + async function calculateFailedEFTInvoices (): Promise { + const fetchInvoices = await getEFTInvoices() + const invoices = fetchInvoices?.invoices || [] + const totalAmountDue = fetchInvoices?.totalAmountDue || 0 + + return { + invoices: invoices, + totalAmountDue: totalAmountDue + } + } + async function resetAccountSetupProgress (): Promise { setGrantAccess(false) setCurrentOrganization(undefined) @@ -1092,6 +1119,7 @@ export const useOrgStore = defineStore('org', () => { leaveTeam, updateMember, deleteUser, + getAccountAdministrator, syncActiveOrgMembers, syncAddress, syncPendingOrgMembers, @@ -1108,6 +1136,7 @@ export const useOrgStore = defineStore('org', () => { updateStatementNotifications, getOrgPayments, calculateFailedInvoices, + calculateFailedEFTInvoices, resetAccountSetupProgress, resetAccountWhileSwitchingPremium, createAccountPayment, diff --git a/auth-web/src/util/common-util.ts b/auth-web/src/util/common-util.ts index 570daf32e4..5d99975124 100644 --- a/auth-web/src/util/common-util.ts +++ b/auth-web/src/util/common-util.ts @@ -7,6 +7,25 @@ import moment from 'moment' * A class to put all the common utility methods. */ export default class CommonUtils { + // formats two dates into a range string, takes in most date formats + // eg1. formatDateRange('2021-01-01', new Date()) => 'January 01, 2021 - May 27, 2024' + // eg2. formatDateRange('March 1 2024', moment()) => 'March 01 - May 27, 2024' + static formatDateRange (date1: string | Date | moment.Moment, date2: string | Date): string { + const dateObj1 = moment(date1) + const dateObj2 = moment(date2) + const year = (dateObj1.year() === dateObj2.year()) ? dateObj1.year() : '' + const month = (dateObj1.month() === dateObj2.month()) ? dateObj1.format('MMMM') : '' + if (date1 === date2) { + return dateObj1.format('MMMM DD, YYYY') + } else if (year && !month) { + return `${dateObj1.format('MMMM DD')} - ${dateObj2.format('MMMM DD')}, ${year}` + } else if (year && month) { + return `${month} ${dateObj1.date()} - ${dateObj2.date()}, ${year}` + } else { + return `${dateObj1.format('MMMM DD, YYYY')} - ${dateObj2.format('MMMM DD, YYYY')}` + } + } + // checking url matches the regex static isUrl (value:string): boolean { return value?.startsWith('http') @@ -113,7 +132,7 @@ export default class CommonUtils { } // Formatting date in the desired format for displaying in the template - static formatDisplayDate (date: Date | string, format?: string) { + static formatDisplayDate (date: Date | string | moment.Moment, format?: string) { // not working in CI (getting UTC datetime) return (date) ? moment(date.toLocaleString('en-US', { timeZone: 'America/Vancouver' })) .format(format || 'YYYY-MM-DD') : '' diff --git a/auth-web/src/views/auth/account-freeze/AccountFreezeUnlockView.vue b/auth-web/src/views/auth/account-freeze/AccountFreezeUnlockView.vue index e0771dedb8..2523f6154f 100644 --- a/auth-web/src/views/auth/account-freeze/AccountFreezeUnlockView.vue +++ b/auth-web/src/views/auth/account-freeze/AccountFreezeUnlockView.vue @@ -74,7 +74,7 @@ import { AccountStatus, Pages } from '@/util/constants' import { Component, Vue } from 'vue-property-decorator' import Stepper, { StepConfiguration } from '@/components/auth/common/stepper/Stepper.vue' import { mapActions, mapState } from 'pinia' -import AccountOverview from '@/components/auth/account-freeze/AccountOverview.vue' +import AccountOverviewNSF from '@/components/auth/account-freeze/AccountOverviewNSF.vue' import AccountSuspendedView from './AccountSuspendedView.vue' import ConfigHelper from 'sbc-common-components/src/util/config-helper' import { KCUserProfile } from 'sbc-common-components/src/models/KCUserProfile' @@ -88,7 +88,7 @@ import { useUserStore } from '@/stores/user' @Component({ components: { - AccountOverview, + AccountOverviewNSF, ReviewBankInformation, PaymentReview, Stepper, @@ -129,7 +129,7 @@ export default class AccountFreezeUnlockView extends Vue { { title: 'Account Overview', stepName: 'Account Overview', - component: AccountOverview, + component: AccountOverviewNSF, componentProps: {} }, { diff --git a/auth-web/src/views/auth/account-freeze/AccountHoldView.vue b/auth-web/src/views/auth/account-freeze/AccountHoldView.vue new file mode 100644 index 0000000000..0843fb1b38 --- /dev/null +++ b/auth-web/src/views/auth/account-freeze/AccountHoldView.vue @@ -0,0 +1,123 @@ + + + + diff --git a/auth-web/src/views/auth/account-freeze/AccountSuspendedUnlockView.vue b/auth-web/src/views/auth/account-freeze/AccountSuspendedUnlockView.vue new file mode 100644 index 0000000000..c147430f23 --- /dev/null +++ b/auth-web/src/views/auth/account-freeze/AccountSuspendedUnlockView.vue @@ -0,0 +1,174 @@ + + + + + diff --git a/auth-web/src/views/auth/account-freeze/AccountSuspendedView.vue b/auth-web/src/views/auth/account-freeze/AccountSuspendedView.vue index 6291d2119e..a0cea6a38b 100644 --- a/auth-web/src/views/auth/account-freeze/AccountSuspendedView.vue +++ b/auth-web/src/views/auth/account-freeze/AccountSuspendedView.vue @@ -12,10 +12,10 @@ color="error" class="mb-6" > - mdi-lock-outline + mdi-alert-circle-outline

- Account Suspended + Your Account is Suspended

-

- Your account is suspended. Please contact the account administrator +

+ Your account is suspended from: {{ suspendedDate }} +

+

+ Please contact the account administrator to reactivate your account. +

+

+ Account Administrator Email: {{ accountAdministratorEmail }}

@@ -43,12 +49,51 @@