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: `
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 }}
ETA {{ row.eta }}
{{ 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,
};
}
}
}