Dashboard Notifications & CTAs
Dashboard notifications are the primary mechanism for surfacing offers, required actions and announcements. The server decides what to show; the client decides how — a CTA card on the dashboard, or a modal opened automatically at session start.
Notification types
export enum DashboardNotificationType {
InstantAdvance = 'instant_advance',
InstantTransfer = 'instant_transfer',
OpenInvoices = 'open_invoices',
SellerCard = 'seller_card',
PlaidCollection = 'collection_account',
PlaidAdvance = 'advance_underwriting',
PreAdvanceLine = 'pre_advance_line',
AdvanceLine = 'advance_line',
LongTermContract = 'new_long_term_contract',
LongTermContractRenewal = 'renew_long_term_contract',
WelcomeMessage = 'new_sd_welcome',
SupplierSuspension = 'supplier_suspension',
SellerCardAd = 'seller_card_transfer_ad',
PreInstantAdvance = 'pre_instant_advance',
CancellationSurvey = 'cancellation_survey',
CrossSell = 'product_cross_sell_page',
AirwallexPcardTerms = 'airwallex_pcard_terms',
IdentityDocumentExpired = 'identity_document_expired',
}
The Notification model
src/app/core/auth/_models/notifications.model.ts
export class Notification<D = any> {
notificationSupplierKey: string;
supplierKey: string;
notificationType: DashboardNotificationType;
externalProductId?: any;
startDate: string;
expirationDate?: any;
hidden: boolean;
declined: boolean;
declineLoading: boolean;
acceptLoading: boolean;
visited: boolean;
seen: boolean;
inactivated: boolean;
data: D; (1)
successShow: boolean;
firstSeen: boolean;
actionLoading: boolean;
additional_data?: any;
}
| 1 | Typed per notification. LTC notifications carry
DashboardNotificationLtcDataDto[], the identity notification carries
IdentityDocumentExpiredDataDto, the suspension notification carries
SuspensionNotificationDataDto. |
DashboardNotifications.fromDto() maps the flat API payload onto named fields:
| API key | Model field |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
itemLoginRequired is mapped from the DTO but has no corresponding
DashboardNotificationType member — it is currently read-only state.
Actions on a notification
export enum DashboardNotificationAction {
Visited = 'visited',
Hide = 'hide',
Show = 'show',
Decline = 'decline',
DeclineLoading = 'declineLoading',
Accept = 'accept',
AcceptLoading = 'acceptLoading',
ActionLoading = 'actionLoading',
}
Every state-changing action follows the same effect shape: call
notificationService.callNotificationAction(supplierKey, type, action), then
dispatch updatedDashboardNotifications({ type, action, value }) to reflect it
locally.
visitedNotification$ = createEffect(() =>
this.actions$.pipe(
ofType(DashNotificationActions.visitedDashboardNotifications),
withLatestFrom(this.store.select(selectCurrentUserId)),
switchMap(([{ payload }, supplierKey]) => {
const type = (payload as ActionsDashboardNotificationPayload).type;
return zip(of(type),
this.notificationService.callNotificationAction(supplierKey, type, DashboardNotificationAction.Visited));
}),
map(([type]) => DashNotificationActions.updatedDashboardNotifications({
payload: { type, action: DashboardNotificationAction.Visited, value: true },
})),
),
);
Accept
acceptDashboardNotificationsSuccess opens a success modal whose copy is chosen
by notification type:
map(({ payload: { type, silent } }) => {
if (!silent) {
const phraseKeyByType: Partial<Record<DashboardNotificationType, string>> = {
[DashboardNotificationType.PreAdvanceLine]: 'CTA.PRE_ADVANCE_LINE.SUCCESS',
[DashboardNotificationType.AirwallexPcardTerms]: 'CTA.AIRWALLEX_PCARD_TERMS.SUCCESS',
};
const phraseKey = phraseKeyByType[type] || 'CTA.PRE_INSTANT_ADVANCE.SUCCESS'; (1)
this.translateService.get(phraseKey).subscribe(({ HEADER: title, DESCRIPTION: description }) => {
this.messageModalService.openSuccessModal({ title, description });
});
}
return DashNotificationActions.fetchDashboardNotifications(); (2)
}),
| 1 | Any type not in the map falls back to the Pre Instant Advance copy. Adding a notification that is accepted without adding a mapping produces a plausible-looking but wrong success message. |
| 2 | The whole set is refetched after every accept, so server-side follow-on changes are picked up. |
silent: true suppresses the modal entirely — used when a single dialog resolves
several notifications at once, as in the Seller Card transition.
CTA cards
views/partials/content/cta/ renders notifications as dismissible dashboard
cards. CtaFactory maps a notification type onto a strategy that produces an
IAdCard configuration.
public getCtaConfig(type: DashboardNotificationType, notification: Notification): IAdCard {
switch (type) {
case DashboardNotificationType.PreAdvanceLine: return new CtaPreAdvanceLineStrategy(…).generate();
case DashboardNotificationType.AdvanceLine: return new CtaAdvanceLineStrategy(…).generate();
case DashboardNotificationType.LongTermContract: return new CtaLongTermContractStrategy(…).generate();
case DashboardNotificationType.InstantAdvance: return new CtaInstantAdvanceStrategy(…).generate();
case DashboardNotificationType.PreInstantAdvance: return new CtaPreInstantAdvanceStrategy(…).generate();
case DashboardNotificationType.CrossSell: return new CtaCrossSellStrategy(…).generate();
default: return null;
}
}
Active strategies
|
Pre-qualified Advance Line offer. |
|
Live Advance Line offer. |
|
New LTC offer → |
|
Instant Advance offer. |
|
Pre-qualified Instant Advance offer. |
|
Product cross-sell page. |
Disabled strategies
These exist in the repository but are commented out of the factory:
|
Seller Card offer. |
|
Instant Transfer offer. |
|
Open invoices. |
|
LTC renewal — disabled deliberately per CA-513. |
|
|
Supporting pieces:
|
The abstract strategy base. |
|
Shared LTC behaviour for the new and renewal strategies. |
|
Session-storage keys for per-session dismissal. |
|
A dedicated |
The user’s response is expressed with CtaActionTypesEnum:
export enum CtaActionTypesEnum {
Accept = 'accept',
Decline = 'decline',
SaveForLater = 'save',
}
Auto-opened modals
Three notifications bypass the card system and open a modal directly from
GlobalSubscriptionsService.init(). Each subscribes with take(1), so it fires
at most once per session.
|
|
|
|
|
|
The identity notification suppresses the standalone terms dialog:
filter(notifications =>
this.canSeeAirwallexPcardTerms(notifications?.airwallexPcardTerms) &&
!this.canSeeIdentityDocumentExpired(notifications?.identityDocumentExpired)),
"Can see" means the notification exists and declined is false.
See Seller Card for the full transition flow.
Notifications that drive flows
|
|
|
|
|
The cancellation survey — see Cancellation & Reactivation. |
|
Suspension messaging — see Suspension. |
|
Plaid linking prompts — see Plaid. |
|
|