import { Component, computed, signal } from '@angular/core'; import { AfricaniesIconComponent } from '@africanies/africanies-icons'; import type { AsyncQueryStateModel } from '@africanies/africanies-models'; import { AsyncStateComponent, EmptyStateComponent, ErrorIndicatorComponent, ErrorStateComponent, LoadingStateComponent, } from '@africanies/africanies-ui'; import { DemoSectionComponent } from '../shared/demo-section.component'; import { PageHeaderComponent } from '../shared/page-header.component'; import { FEEDBACK_ASYNC, FEEDBACK_EMPTY, FEEDBACK_ERROR, FEEDBACK_ERROR_INDICATOR, FEEDBACK_LOADING, } from '../snippets'; type DemoKind = | 'loading' | 'error' | 'empty' | 'content' | 'fetching' | 'stale'; interface DemoShipment { id: string; route: string; status: 'In transit' | 'Delivered' | 'Pending'; eta: string; } const SUCCESS_ROWS: DemoShipment[] = [ { id: 'SFN-1001', route: 'Lagos → London', status: 'In transit', eta: 'Aug 14', }, { id: 'SFN-1002', route: 'Accra → Dubai', status: 'Pending', eta: 'Aug 16', }, { id: 'SFN-1003', route: 'Nairobi → Amsterdam', status: 'Delivered', eta: 'Aug 09', }, ]; /** * */ @Component({ selector: 'app-feedback-page', standalone: true, imports: [ LoadingStateComponent, ErrorStateComponent, ErrorIndicatorComponent, EmptyStateComponent, AsyncStateComponent, AfricaniesIconComponent, PageHeaderComponent, DemoSectionComponent, ], template: `

Block

Inline

Socket feed panel — list remains interactive while the indicator prompts reconnect.

Toolbar rates — default Retry label, no background refresh.

Same pattern AsyncState uses for stale errors.

@for (kind of demoKinds; track kind) { }

Shipment list

{{ rowCount() }} active consignments

{{ rowCount() }}
    @for (row of asyncState().data ?? []; track row.id) {
  • {{ row.id }}

    {{ row.route }}

    {{ row.status }}
  • }
@if (lastRetry()) {

Last retry: {{ lastRetry() }}

}
`, }) export class FeedbackPage { protected readonly demo = signal('content'); protected readonly lastRetry = signal(null); protected readonly reconnecting = signal(false); protected readonly refreshingStale = signal(false); protected readonly asyncState = signal>( this.buildState('content'), ); protected readonly rowCount = computed( () => this.asyncState().data?.length ?? 0, ); protected readonly demoKinds: DemoKind[] = [ 'loading', 'error', 'empty', 'content', 'fetching', 'stale', ]; protected readonly kindLabels: Record = { loading: 'First load', error: 'Blocking error', empty: 'Empty', content: 'Success', fetching: 'Background refresh', stale: 'Stale error', }; protected readonly loadingCode = FEEDBACK_LOADING; protected readonly errorCode = FEEDBACK_ERROR; protected readonly errorIndicatorCode = FEEDBACK_ERROR_INDICATOR; protected readonly emptyCode = FEEDBACK_EMPTY; protected readonly asyncCode = FEEDBACK_ASYNC; protected simulateReconnect(): void { this.reconnecting.set(true); this.onRetry('error-indicator-reconnect'); globalThis.setTimeout(() => this.reconnecting.set(false), 1800); } protected simulateStaleRefresh(): void { this.refreshingStale.set(true); this.onRetry('error-indicator-stale'); globalThis.setTimeout(() => this.refreshingStale.set(false), 1800); } protected setDemo(kind: DemoKind): void { this.demo.set(kind); this.asyncState.set(this.buildState(kind)); } protected onRetry(source: string): void { this.lastRetry.set(source); } protected statusClass( status: DemoShipment['status'], ): string { switch (status) { case 'Delivered': return 'bg-export/10 text-export dark:bg-export/15 dark:text-export-light'; case 'Pending': return 'bg-warning-subtle text-warning-dark dark:bg-warning/15 dark:text-warning'; default: return 'bg-import/10 text-import dark:bg-import/15 dark:text-import-light'; } } private buildState(kind: DemoKind): AsyncQueryStateModel { switch (kind) { case 'loading': return { data: undefined, isLoading: true, isFetching: true, isError: false, error: null, }; case 'error': return { data: undefined, isLoading: false, isFetching: false, isError: true, error: 'Network request failed.', }; case 'empty': return { data: [], isLoading: false, isFetching: false, isError: false, error: null, }; case 'fetching': return { data: SUCCESS_ROWS, isLoading: false, isFetching: true, isError: false, error: null, }; case 'stale': return { data: SUCCESS_ROWS.slice(0, 2), isLoading: false, isFetching: false, isError: true, error: 'Refresh failed.', }; default: return { data: SUCCESS_ROWS, isLoading: false, isFetching: false, isError: false, error: null, }; } } }