// @africanies/africanies-models copy-paste examples. export /** * */ const MODELS_IMPORT = `// Shared API/UI shapes — type-only, no providers. Import from here instead of reinventing. import type { AsyncQueryStateModel, ApiResponseModel, CountryModel, CurrencyModel, PaginationMetaModel, PaginationQueryParamsModel, PaymentMethodModel, ResourceId, ShippingMode, ModeConfigDataModel, } from '@africanies/africanies-models'; `; export /** * */ const MODELS_API_RESPONSE = `// Canonical ApiClient envelope. Fields are T | null — null-check before use. // res.success === false is a failure even on HTTP 200; pagination may be null on detail GETs. import { inject } from '@angular/core'; import { ApiClient } from '@africanies/africanies-core'; import type { ApiErrorDetailModel, ApiResponseModel } from '@africanies/africanies-models'; import { firstValueFrom } from 'rxjs'; interface Shipment { id: number; reference: string; } export class ShipmentApi { private readonly api = inject(ApiClient); async load(id: number): Promise { const res: ApiResponseModel = await firstValueFrom( this.api.get(\`/shipments/\${id}\`), ); if (!res.success || res.data === null) { this.applyFieldErrors(res.errors); throw new Error(res.message ?? 'Could not load shipment.'); } return res.data; } private applyFieldErrors(errors: ApiErrorDetailModel[] | null): void { if (!errors?.length) { return; } for (const err of errors) { // err.field → form control; err.message → validation copy; err.code → i18n key } } } `; export /** * */ const MODELS_PAGINATION = `// ResourceId list GET convention (all list-style endpoints): // null → paginated api.getResourcePage('shipments', { page, size }) // 'all' → full dump api.getResourceAll('shipments') // 42 → one record api.getResourceById('shipments', 42) // Bind res.pagination to africanies-pagination [meta] on paginated calls. import { Component, inject, signal } from '@angular/core'; import { ApiClient } from '@africanies/africanies-core'; import type { ApiResponseModel, PaginationMetaModel, PaginationQueryParamsModel, ResourceId, } from '@africanies/africanies-models'; import { DEFAULT_PAGE_SIZE, PaginationComponent, } from '@africanies/africanies-ui'; import { firstValueFrom } from 'rxjs'; interface Shipment { reference: string; } @Component({ selector: 'app-shipment-list', standalone: true, imports: [PaginationComponent], template: \` @if (meta(); as pager) { } \`, }) export class ShipmentListComponent { private readonly api = inject(ApiClient); protected readonly meta = signal(null); protected readonly rows = signal([]); protected readonly page = signal(1); protected readonly size = signal(DEFAULT_PAGE_SIZE); async loadPage(): Promise { const listId: ResourceId = null; const query: PaginationQueryParamsModel = { page: this.page(), size: this.size(), order: '-createdAt', }; const res: ApiResponseModel = await firstValueFrom( this.api.getResource('shipments', listId, query), // or: this.api.getResourcePage('shipments', query) ); if (!res.success || res.data === null) { throw new Error(res.message ?? 'List fetch failed.'); } this.rows.set(res.data); this.meta.set(res.pagination); } protected onPageChange(next: number): void { this.page.set(next); void this.loadPage(); } protected onSizeChange(next: number): void { this.size.set(next); this.page.set(1); void this.loadPage(); } } `; export /** * */ const MODELS_ASYNC_STATE = `// Map fetch signals into AsyncQueryStateModel for africanies-async-state. // isLoading = first paint; isFetching = background refresh. Keep data undefined until first success. import { Component, computed, inject, signal } from '@angular/core'; import { ApiClient } from '@africanies/africanies-core'; import type { AsyncQueryStateModel } from '@africanies/africanies-models'; import { AsyncStateComponent } from '@africanies/africanies-ui'; import { firstValueFrom } from 'rxjs'; interface Shipment { reference: string; } @Component({ selector: 'app-shipment-async-list', standalone: true, imports: [AsyncStateComponent], template: \` @for (row of listState().data ?? []; track row.reference) {

{{ row.reference }}

}
\`, }) export class ShipmentAsyncListComponent { private readonly api = inject(ApiClient); private readonly rows = signal(undefined); private readonly isLoading = signal(false); private readonly isFetching = signal(false); private readonly isError = signal(false); private readonly error = signal(null); protected readonly listState = computed( (): AsyncQueryStateModel => ({ data: this.rows(), isLoading: this.isLoading(), isFetching: this.isFetching(), isError: this.isError(), error: this.error(), }), ); constructor() { void this.refetch(); } protected async refetch(): Promise { const hadData = this.rows() !== undefined; this.isLoading.set(!hadData); this.isFetching.set(true); this.isError.set(false); this.error.set(null); try { const res = await firstValueFrom( this.api.getResource('shipments', null, { page: 1 }), ); if (!res.success || res.data === null) { throw new Error(res.message ?? 'Fetch failed.'); } this.rows.set(res.data); } catch (err) { this.isError.set(true); this.error.set(err instanceof Error ? err.message : 'Unknown error.'); } finally { this.isLoading.set(false); this.isFetching.set(false); } } // With TanStack injectQuery(): map query.data(), query.isLoading(), etc. directly. } `; export /** * */ const MODELS_SHIPPING_MODE = `// 'stn' | 'sfn' literals for theme + HTTP. Read/set via ShippingModeService — // Persists in sessionStorage (per tab). ModeColorService and the interceptor follow along. // One-off API call in the other mode? Pass shippingMode — tab mode stays unchanged. import { Component, inject } from '@angular/core'; import { ApiClient, ShippingModeService } from '@africanies/africanies-core'; import type { ShippingMode } from '@africanies/africanies-models'; @Component({ selector: 'app-mode-switcher', standalone: true, template: \` \`, }) export class ModeSwitcherComponent { private readonly api = inject(ApiClient); private readonly shippingMode = inject(ShippingModeService); protected activeMode(): ShippingMode { return this.shippingMode.mode(); } protected setMode(mode: ShippingMode): void { this.shippingMode.setMode(mode); } protected claimInStnWhileOnSfn(): void { // Tab stays SFN; this request sends x-shipment-mode: stn this.api.post('/public/shipment_tracking_item/claim', formData, { shippingMode: 'stn', }).subscribe(); } } `; export /** * */ const MODELS_MODE_CONFIG = `// Region currency/units from ModeConfigService — loads with provideAfricaniesSdk. // Call getRegionConfig(countryCode); don't hard-code maps in feature code. import { Component, inject } from '@angular/core'; import { ModeConfigService, ShippingModeService } from '@africanies/africanies-core'; @Component({ selector: 'app-shipment-summary', standalone: true, template: \` @if (region(); as r) {

Value: {{ r.currency_symbol }}{{ amount }}

Weight unit: {{ r.mass_unit }}

} \`, }) export class ShipmentSummaryComponent { private readonly modeConfig = inject(ModeConfigService); private readonly shipping = inject(ShippingModeService); readonly amount = 1250; readonly originCountry = 'cn'; region() { // Uses saved /public/mode/config record + active STN/SFN mode. return this.modeConfig.getRegionConfig(this.originCountry); } } // app.config.ts // provideAfricaniesSdk({ // baseUrl: 'https://test-api-export.africaniestest.com/api', // // loadModeConfig: true by default — GET /public/mode/config on startup // }), `; export /** * */ const MODELS_COUNTRY = `// Countries — GET /public/country/read/{id?} (ResourceId). // readPage() → CountryModel[] · readAll() → CountryModel[] · readById(n) → CountryModel import { Component, inject, signal } from '@angular/core'; import { CountryService } from '@africanies/africanies-core'; import type { CountryModel } from '@africanies/africanies-models'; import { firstValueFrom } from 'rxjs'; @Component({ selector: 'app-country-picker-setup', standalone: true, template: \`

{{ countries().length }} countries loaded

@if (countries()[0]; as first) {

{{ first.name }} ({{ first.iso2 }}) — {{ first.states.length }} states

} \`, }) export class CountryPickerSetupComponent { private readonly countriesApi = inject(CountryService); protected readonly countries = signal([]); async ngOnInit(): Promise { const res = await firstValueFrom(this.countriesApi.readAll()); if (res.success && res.data) { this.countries.set(res.data); } } } `; export /** * */ const MODELS_SERVICE = `// Services — GET /public/service/read/{id?} (ResourceId). import { Component, inject, signal } from '@angular/core'; import { ServiceService } from '@africanies/africanies-core'; import type { ServiceModel } from '@africanies/africanies-models'; import { firstValueFrom } from 'rxjs'; @Component({ selector: 'app-service-setup', standalone: true, template: \`

{{ services().length }} services

@if (services()[0]; as first) {

{{ first.name }}

} \`, }) export class ServiceSetupComponent { private readonly servicesApi = inject(ServiceService); protected readonly services = signal([]); async ngOnInit(): Promise { const res = await firstValueFrom(this.servicesApi.readPage({ page: 1 })); if (res.success && res.data) { this.services.set(res.data); } } } `; export /** * */ const MODELS_DOCUMENT = `// Documents — GET /public/document/read/{id?} (ResourceId). import { Component, inject, signal } from '@angular/core'; import { DocumentService } from '@africanies/africanies-core'; import type { DocumentModel } from '@africanies/africanies-models'; import { firstValueFrom } from 'rxjs'; @Component({ selector: 'app-document-setup', standalone: true, template: \` @if (doc(); as d) {

{{ d.name }}

} \`, }) export class DocumentSetupComponent { private readonly documentsApi = inject(DocumentService); protected readonly doc = signal(null); async load(id: number): Promise { const res = await firstValueFrom(this.documentsApi.readById(id)); if (res.success && res.data) { this.doc.set(res.data); } } } `; export /** * */ const MODELS_PLAN = `// Plans — GET /public/plan/read/{id?} (ResourceId). import { Component, inject, signal } from '@angular/core'; import { PlanService } from '@africanies/africanies-core'; import type { PlanModel } from '@africanies/africanies-models'; import { firstValueFrom } from 'rxjs'; @Component({ selector: 'app-plan-setup', standalone: true, template: \`

{{ plans().length }} plans

@if (plans()[0]; as first) {

{{ first.name }} — {{ first.packages.length }} packages

} \`, }) export class PlanSetupComponent { private readonly plansApi = inject(PlanService); protected readonly plans = signal([]); async ngOnInit(): Promise { const res = await firstValueFrom(this.plansApi.readPage({ page: 1 })); if (res.success && res.data) { this.plans.set(res.data); } } } `; export /** * */ const MODELS_CURRENCY = `// Currencies — GET /currency/read/{id?} (ResourceId). // readPage() → CurrencyModel[] · create / update / remove → envelope // Flags on GET are boolean; create/update accept boolean or "1"/"0". import { Component, inject, signal } from '@angular/core'; import { CurrencyService } from '@africanies/africanies-core'; import type { CurrencyModel } from '@africanies/africanies-models'; import { firstValueFrom } from 'rxjs'; @Component({ selector: 'app-currency-setup', standalone: true, template: \`

{{ currencies().length }} currencies loaded

@if (currencies()[0]; as first) {

{{ first.name }} ({{ first.short_code }}) — {{ first.payment_methods.length }} methods

} \`, }) export class CurrencySetupComponent { private readonly currenciesApi = inject(CurrencyService); protected readonly currencies = signal([]); async ngOnInit(): Promise { const res = await firstValueFrom( this.currenciesApi.readPage({ page: 1, order: 'desc' }), ); if (res.success && res.data) { this.currencies.set(res.data); } } } `; export /** * */ const MODELS_PAYMENT_METHOD = `// Payment methods — GET /payment_method/read/{id?} (ResourceId). // PUT /payment_method/update — active toggle; resend name + model from the row. // No create/delete. After update: toast message + patch updated_at (do not reload). // readPage({ page, order, search, from, to }) → PaymentMethodModel[] // readAll() → PaymentMethodModel[] · readById(n) → PaymentMethodModel import { Component, inject, signal } from '@angular/core'; import { PaymentMethodService } from '@africanies/africanies-core'; import type { PaymentMethodModel, PaymentMethodUpdateRequestModel, } from '@africanies/africanies-models'; import { firstValueFrom } from 'rxjs'; @Component({ selector: 'app-payment-method-setup', standalone: true, template: \`

{{ methods().length }} payment methods loaded

@if (methods()[0]; as first) {

{{ first.name }} — {{ first.currencies.length }} currencies

} \`, }) export class PaymentMethodSetupComponent { private readonly methodsApi = inject(PaymentMethodService); protected readonly methods = signal([]); async ngOnInit(): Promise { const res = await firstValueFrom( this.methodsApi.readPage({ page: 1, order: 'desc' }), ); if (res.success && res.data) { this.methods.set(res.data); } } async setActive(row: PaymentMethodModel, active: boolean): Promise { const body: PaymentMethodUpdateRequestModel = { id: row.id, name: row.name, model: row.model, active, }; const res = await firstValueFrom(this.methodsApi.update(body)); if (res.success) { // toast res.message; patch row locally — do not readPage() } } } `; export /** * */ const MODELS_SHIPMENT_METHOD = `// Carriers — GET /shipment_method/read/{id?} (ResourceId). // readPage() · readAll() · readById(n) → single ShipmentMethodModel import { Component, inject, signal } from '@angular/core'; import { ShipmentMethodService } from '@africanies/africanies-core'; import type { ShipmentMethodModel } from '@africanies/africanies-models'; import { firstValueFrom } from 'rxjs'; @Component({ selector: 'app-carrier-setup', standalone: true, template: \`

{{ methods().length }} methods

@if (methods()[0]; as first) {

{{ first.name }} ({{ first.mode }}) — {{ first.zone_values.total }} zones

} \`, }) export class CarrierSetupComponent { private readonly methodsApi = inject(ShipmentMethodService); protected readonly methods = signal([]); async ngOnInit(): Promise { const res = await firstValueFrom(this.methodsApi.readAll()); if (res.success && res.data) { this.methods.set(res.data); } } } `; export /** * */ const MODELS_WAREHOUSE = `// Warehouses — GET /warehouse/read/{id?} (ResourceId). Nested country reuses CountryModel. import { Component, inject, signal } from '@angular/core'; import { WarehouseService } from '@africanies/africanies-core'; import type { WarehouseModel } from '@africanies/africanies-models'; import { firstValueFrom } from 'rxjs'; @Component({ selector: 'app-warehouse-setup', standalone: true, template: \`

{{ warehouses().length }} warehouses

@if (warehouses()[0]; as first) {

{{ first.name }} — {{ first.city }}, {{ first.country?.iso2 }}

} \`, }) export class WarehouseSetupComponent { private readonly warehousesApi = inject(WarehouseService); protected readonly warehouses = signal([]); async ngOnInit(): Promise { const res = await firstValueFrom(this.warehousesApi.readAll()); if (res.success && res.data) { this.warehouses.set(res.data); } } } `; export /** * */ const MODELS_ZONE = `// Zones — GET /zone/read/records/{id?} (ResourceId). import { Component, inject, signal } from '@angular/core'; import { ZoneService } from '@africanies/africanies-core'; import type { ZoneModel } from '@africanies/africanies-models'; import { firstValueFrom } from 'rxjs'; @Component({ selector: 'app-zone-setup', standalone: true, template: \`

{{ zones().length }} zones

@if (zones()[0]; as first) {

{{ first.name }} ({{ first.type }})

} \`, }) export class ZoneSetupComponent { private readonly zonesApi = inject(ZoneService); protected readonly zones = signal([]); async ngOnInit(): Promise { const res = await firstValueFrom(this.zonesApi.readAll()); if (res.success && res.data) { this.zones.set(res.data); } } } `; export /** * */ const MODELS_USER = `// Current user — bare GET /user (no envelope). UserService.me() → UserModel (snake_case). import { Component, inject, signal } from '@angular/core'; import { UserService } from '@africanies/africanies-core'; import type { UserModel } from '@africanies/africanies-models'; import { firstValueFrom } from 'rxjs'; @Component({ selector: 'app-user-setup', standalone: true, template: \` @if (user(); as u) {

{{ u.first_name }} {{ u.last_name }} · {{ u.email }}

} \`, }) export class UserSetupComponent { private readonly users = inject(UserService); protected readonly user = signal(null); async ngOnInit(): Promise { const res = await firstValueFrom(this.users.me()); if (res.success && res.data) { this.user.set(res.data); } } } `; export /** * */ const MODELS_PRODUCT = `// Products — GET /product/read/{id?} (ResourceId). import { Component, inject, OnInit, signal } from '@angular/core'; import { ProductService } from '@africanies/africanies-core'; import type { ProductModel } from '@africanies/africanies-models'; import { firstValueFrom } from 'rxjs'; @Component({ selector: 'app-product-setup', standalone: true, template: \`

{{ products().length }} products

@if (products()[0]; as first) {

{{ first.name }} — {{ first.hs_code }}

} \`, }) export class ProductSetupComponent implements OnInit { private readonly productsApi = inject(ProductService); protected readonly products = signal([]); async ngOnInit(): Promise { const res = await firstValueFrom(this.productsApi.readAll()); if (res.success && res.data) { this.products.set(res.data); } } } `; export /** * */ const MODELS_FILE = `// File read — POST /file/read with { ref }. // data is a single FileReadModel (not a list / not paginated). import { Component, inject, signal } from '@angular/core'; import { FileService } from '@africanies/africanies-core'; import type { FileReadModel } from '@africanies/africanies-models'; import { firstValueFrom } from 'rxjs'; @Component({ selector: 'app-file-setup', standalone: true, template: \` @if (file(); as f) {

{{ f.mime_type }}

Download } \`, }) export class FileSetupComponent { private readonly filesApi = inject(FileService); protected readonly file = signal(null); async load(ref: string): Promise { const res = await firstValueFrom(this.filesApi.read(ref)); if (res.success && res.data) { this.file.set(res.data); } } } `;