@isa/crm/feature/customer-loyalty-cards
Customer loyalty cards feature module for displaying and managing customer bonus cards (Kundenkarten) with points tracking and card management actions.
Overview
This feature library provides a complete customer loyalty cards management interface for the CRM module. It displays all bonus cards associated with a customer, including:
- Points summary with total Lesepunkte (reading points) from the primary card
- Call-to-action button to navigate to the Prämienshop (rewards shop)
- Interactive carousel for browsing multiple customer cards with barcodes
- Card management actions: add new cards, lock/unlock cards
- Visual indication of blocked cards with overlay states
- Automatic sorting of blocked cards to the end of the carousel
The main component uses the Resource API pattern via CustomerBonusCardsResource for reactive data loading with automatic race condition prevention.
Architecture
This is a feature library (prefix: crm) containing:
- Main container component that orchestrates the feature
- Presentational components for cards, points summary, and carousel
- Integration with
@isa/crm/data-accessfor state management - Reactive data loading using Angular's Resource API
Components
CustomerLoyaltyCardsComponent
Main container component for the customer loyalty cards feature.
Selector: crm-customer-loyalty-cards
Inputs:
customerId: number(required) - Customer ID to load loyalty cards for. When changed, triggers automatic reload of bonus cards.tabId: number(required) - Tab ID for telemetry/logging purposes.
Outputs:
navigateToPraemienshop: void- Emitted when user clicks the "Zum Prämienshop" button. Parent must handle navigation using the autoTriggerContinueFn pattern.cardUpdated: void- Emitted when a card has been added, locked, or unlocked. Parent should reload cards and transactions.
State:
cards: Signal<BonusCardInfo[] | undefined>- All bonus cards for the selected customerisLoading: Signal<boolean>- Loading state for bonus cardserror: Signal<Error | undefined>- Error state from loading bonus cards
Example:
<crm-customer-loyalty-cards
[customerId]="selectedCustomerId()"
[tabId]="currentTabId()"
(navigateToPraemienshop)="handlePraemienshopNavigation()"
(cardUpdated)="reloadCardsAndTransactions()"
/>
CustomerCardPointsSummaryComponent
Displays the points summary with CTA to Prämienshop.
Selector: crm-customer-card-points-summary
Inputs:
cards: BonusCardInfo[](required) - All bonus cards for the customer
Outputs:
navigateToPraemienshop: void- Emitted when user clicks "Zum Prämienshop" button
Features:
- Extracts total points from primary card
- Formats points with German thousands separator (dot)
- Centered layout matching Figma design
CustomerCardsCarouselComponent
Carousel container for displaying multiple customer loyalty cards with horizontal scrolling.
Selector: crm-customer-cards-carousel
Inputs:
cards: BonusCardInfo[](required) - All bonus cards to display in carousel
Outputs:
cardLocked: void- Emitted when a card has been successfully locked or unlocked
Features:
- Horizontal scrolling with navigation arrows (via
@isa/ui/carousel) - Automatic sorting: blocked cards always appear at the end
- Gap spacing between cards
- Smooth scrolling animations
CustomerCardComponent
Individual customer loyalty card display component.
Selector: crm-customer-card
Inputs:
card: BonusCardInfo(required) - Bonus card data to display
Outputs:
cardLocked: void- Emitted when a card has been successfully locked or unlocked
Features:
- Displays card type (Kundenkarte/Mitarbeitendenkarte)
- Shows card number and customer name
- Renders barcode using
@isa/shared/barcode - Blocked state overlay for inactive cards
- Fixed dimensions: 337×213px (based on Figma design)
AddCustomerCardComponent
Button and dialog for adding new customer cards.
Selector: crm-add-customer-card
Outputs:
cardAdded: void- Emitted when a card has been successfully added
Features:
- Opens text input dialog for scanning or entering 8-digit card code
- Validates card code input (required)
- Calls
CustomerCardsFacade.addCard()to add card - Shows success feedback dialog
- Emits event for parent to reload cards
LockCustomerCardComponent
Component for locking/unlocking customer cards.
Selector: crm-lock-customer-card
Inputs:
isActive: boolean(required) - Whether the card is currently active or blockedcardCode: string(required) - Card code to lock/unlock
Outputs:
cardLocked: void- Emitted when a card has been successfully locked or unlocked
Features:
- Shows "Karte sperren" button for active cards
- Shows "Karte entsperren" button for blocked cards
- Handles loading state during API calls
- Shows success/error feedback dialogs
- Emits event for parent to reload cards
Usage
Integration in Parent Component
import { Component, signal } from '@angular/core';
import { CustomerLoyaltyCardsComponent } from '@isa/crm/feature/customer-loyalty-cards';
@Component({
selector: 'app-customer-detail',
imports: [CustomerLoyaltyCardsComponent],
template: `
<crm-customer-loyalty-cards
[customerId]="customerId()"
[tabId]="tabId()"
(navigateToPraemienshop)="navigateToPraemienshop()"
(cardUpdated)="handleCardUpdated()"
/>
`
})
export class CustomerDetailComponent {
customerId = signal(12345);
tabId = signal(1);
navigateToPraemienshop(): void {
// Use autoTriggerContinueFn pattern to navigate with proper customer selection
this.router.navigate(['/praemienshop'], {
queryParams: { customerId: this.customerId() }
});
}
handleCardUpdated(): void {
// Reload cards and related data (e.g., transactions)
this.customerService.reload(this.customerId());
}
}
Required Provider Setup
The component requires CustomerBonusCardsResource to be provided:
import { CustomerBonusCardsResource } from '@isa/crm/data-access';
@Component({
providers: [CustomerBonusCardsResource],
// ...
})
This is typically provided at the feature route or parent component level to enable scoped resource management.
Dependencies
Internal Dependencies
Data Access:
@isa/crm/data-access- State management and API integrationCustomerBonusCardsResource- Resource API for loading bonus cardsCustomerCardsFacade- Facade for add/lock/unlock operationsBonusCardInfo- Type definition for bonus card data
UI Components:
@isa/ui/buttons-ButtonComponent,TextButtonComponent@isa/ui/carousel-CarouselComponentfor card scrolling@isa/ui/dialog- Dialog services for add card and feedback@isa/shared/barcode-BarcodeComponentfor rendering card barcodes
Core:
@isa/core/logging- Logger factory for consistent logging
External Dependencies
@angular/core- Angular framework@angular/forms- Form validation for card code input@angular/cdk/coercion- Number input coercionrxjs- For dialog result handling
State Management
This feature uses the Resource API pattern for state management:
// Reactive loading with automatic race condition prevention
effect(() => {
const customerId = this.customerId();
this.#bonusCardsResource.params({ customerId });
});
// Signals for state
readonly cards = this.#bonusCardsResource.resource.value;
readonly isLoading = this.#bonusCardsResource.resource.isLoading;
readonly error = this.#bonusCardsResource.resource.error;
This approach provides:
- Automatic race condition handling (cancels previous requests)
- Declarative resource management without RxJS subscriptions
- Reactive updates when
customerIdchanges - Built-in loading and error states
Testing
The library uses Vitest for testing with Angular Testing Utilities. Test files follow the pattern:
*.component.spec.ts- Component unit tests- Uses
ComponentFixtureandTestBedfor component testing - Mocks for
CustomerBonusCardsResourceandCustomerCardsFacade
Run tests:
nx test crm-feature-customer-loyalty-cards
Design
Based on Figma designs with:
- Card dimensions: 337×213px
- Black header, white footer
- Barcode sizing: 4.5rem height, 0.125rem width, 0.5rem margin
- Carousel with horizontal scrolling and navigation arrows
- Blocked cards display overlay and sort to end of carousel
Notes
- Blocked cards sorting: Per Figma annotation, blocked cards are always sorted to the end of the carousel ("gesperrte Karte immer nach hinten")
- Primary card points: Only the primary card's points are displayed in the summary
- Navigation pattern: Uses output events instead of direct router navigation to support the autoTriggerContinueFn pattern for proper customer context
- Card updates: All card mutation operations (add, lock, unlock) emit events for parent coordination to reload related data