@africanies/angular-web-sdk 0.1.12 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nx/cache/13293303228070520113/dist/libs/africanies-core/README.md +85 -0
- package/.nx/cache/13293303228070520113/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs +4699 -0
- package/.nx/cache/13293303228070520113/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +1 -0
- package/.nx/cache/13293303228070520113/dist/libs/africanies-core/package.json +31 -0
- package/.nx/cache/13293303228070520113/dist/libs/africanies-core/types/africanies-africanies-core.d.ts +2748 -0
- package/.nx/cache/13387631073062967069/dist/libs/africanies-theme/README.md +46 -0
- package/.nx/cache/13387631073062967069/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs +249 -0
- package/.nx/cache/13387631073062967069/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +1 -0
- package/.nx/cache/13387631073062967069/dist/libs/africanies-theme/package.json +35 -0
- package/.nx/cache/13387631073062967069/dist/libs/africanies-theme/tailwind-preset.cjs +330 -0
- package/.nx/cache/13387631073062967069/dist/libs/africanies-theme/types/africanies-africanies-theme.d.ts +178 -0
- package/.nx/cache/16704640000606796352/dist/libs/africanies-storage/README.md +31 -0
- package/.nx/cache/16704640000606796352/dist/libs/africanies-storage/fesm2022/africanies-africanies-storage.mjs +249 -0
- package/.nx/cache/16704640000606796352/dist/libs/africanies-storage/fesm2022/africanies-africanies-storage.mjs.map +1 -0
- package/.nx/cache/16704640000606796352/dist/libs/africanies-storage/package.json +26 -0
- package/.nx/cache/16704640000606796352/dist/libs/africanies-storage/types/africanies-africanies-storage.d.ts +210 -0
- package/.nx/cache/2427575433215940812/dist/libs/africanies-ui/README.md +68 -0
- package/.nx/cache/2427575433215940812/dist/libs/africanies-ui/assets/brand/africanies-logo-mini.png +0 -0
- package/.nx/cache/2427575433215940812/dist/libs/africanies-ui/assets/brand/africanies-logo.png +0 -0
- package/.nx/cache/2427575433215940812/dist/libs/africanies-ui/assets/brand/africanies-logo.svg +5 -0
- package/.nx/cache/2427575433215940812/dist/libs/africanies-ui/assets/carriers/dhl.svg +7 -0
- package/.nx/cache/2427575433215940812/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +21251 -0
- package/.nx/cache/2427575433215940812/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -0
- package/.nx/cache/2427575433215940812/dist/libs/africanies-ui/package.json +35 -0
- package/.nx/cache/2427575433215940812/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +5803 -0
- package/.nx/cache/7730350229628359718/dist/libs/africanies-icons/README.md +69 -0
- package/.nx/cache/7730350229628359718/dist/libs/africanies-icons/assets/icons.sprite.svg +1851 -0
- package/.nx/cache/7730350229628359718/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs +839 -0
- package/.nx/cache/7730350229628359718/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs.map +1 -0
- package/.nx/cache/7730350229628359718/dist/libs/africanies-icons/package.json +30 -0
- package/.nx/cache/7730350229628359718/dist/libs/africanies-icons/types/africanies-africanies-icons.d.ts +108 -0
- package/.nx/cache/run.json +35 -35
- package/.nx/cache/terminalOutputs/10603154579345867998 +397 -0
- package/.nx/cache/terminalOutputs/12354163057083828162 +80 -0
- package/.nx/cache/terminalOutputs/13293303228070520113 +21 -0
- package/.nx/cache/terminalOutputs/13387631073062967069 +21 -0
- package/.nx/cache/terminalOutputs/14876351543379349919 +176 -0
- package/.nx/cache/terminalOutputs/15336883221450327589 +21 -0
- package/.nx/cache/terminalOutputs/16208231681037145050 +86 -84
- package/.nx/cache/terminalOutputs/16704640000606796352 +21 -0
- package/.nx/cache/terminalOutputs/1811006905937373707 +74 -0
- package/.nx/cache/terminalOutputs/2005781115771702035 +8 -0
- package/.nx/cache/terminalOutputs/2360743690449633551 +21 -0
- package/.nx/cache/terminalOutputs/2427575433215940812 +21 -0
- package/.nx/cache/terminalOutputs/2981230413857865352 +8 -0
- package/.nx/cache/terminalOutputs/3243550283886864639 +8 -0
- package/.nx/cache/terminalOutputs/3380188914363501109 +231 -0
- package/.nx/cache/terminalOutputs/3699068646900552690 +8 -0
- package/.nx/cache/terminalOutputs/5175669339257931410 +8 -0
- package/.nx/cache/terminalOutputs/6040230831125637867 +8 -0
- package/.nx/cache/terminalOutputs/7730350229628359718 +21 -0
- package/.nx/cache/terminalOutputs/8098730470212963224 +87 -0
- package/.nx/cache/terminalOutputs/8928690901537686687 +21 -0
- package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db +0 -0
- package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db-shm +0 -0
- package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db-wal +0 -0
- package/.nx/workspace-data/d/daemon.log +2859 -0
- package/.nx/workspace-data/d/server-process.json +2 -2
- package/.nx/workspace-data/file-map.json +2155 -2183
- package/.nx/workspace-data/nx_files.nxt +0 -0
- package/.nx/workspace-data/project-graph.json +2 -2
- package/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs +60 -0
- package/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +1 -1
- package/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs +6 -0
- package/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs.map +1 -1
- package/dist/libs/africanies-storage/fesm2022/africanies-africanies-storage.mjs +6 -0
- package/dist/libs/africanies-storage/fesm2022/africanies-africanies-storage.mjs.map +1 -1
- package/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs +6 -0
- package/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +1 -1
- package/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +297 -51
- package/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -1
- package/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +12 -12
- package/libs/africanies-core/src/lib/auth/auth-token.service.ts +3 -0
- package/libs/africanies-core/src/lib/auth/auth.service.ts +3 -0
- package/libs/africanies-core/src/lib/country/country.service.ts +3 -0
- package/libs/africanies-core/src/lib/currency/currency.service.ts +3 -0
- package/libs/africanies-core/src/lib/document/document.service.ts +3 -0
- package/libs/africanies-core/src/lib/file/file.service.ts +3 -0
- package/libs/africanies-core/src/lib/filters/filter-options.resolver.ts +3 -0
- package/libs/africanies-core/src/lib/http/api-client.ts +3 -0
- package/libs/africanies-core/src/lib/mode/mode-config.service.ts +3 -0
- package/libs/africanies-core/src/lib/notification/notification.service.ts +3 -0
- package/libs/africanies-core/src/lib/overlay/route-overlay.service.ts +3 -0
- package/libs/africanies-core/src/lib/payment-method/payment-method.service.ts +3 -0
- package/libs/africanies-core/src/lib/plan/plan.service.ts +3 -0
- package/libs/africanies-core/src/lib/product/product.service.ts +3 -0
- package/libs/africanies-core/src/lib/service/service.service.ts +3 -0
- package/libs/africanies-core/src/lib/shipment-method/shipment-method.service.ts +3 -0
- package/libs/africanies-core/src/lib/shipping/shipping-mode.service.ts +3 -0
- package/libs/africanies-core/src/lib/user/user.service.ts +3 -0
- package/libs/africanies-core/src/lib/warehouse/warehouse.service.ts +3 -0
- package/libs/africanies-core/src/lib/zone/zone.service.ts +3 -0
- package/libs/africanies-icons/src/lib/africanies-icon.component.ts +3 -0
- package/libs/africanies-icons/src/lib/icon-registry.service.ts +3 -0
- package/libs/africanies-storage/src/lib/local-storage.service.ts +3 -0
- package/libs/africanies-storage/src/lib/session-storage.service.ts +3 -0
- package/libs/africanies-theme/src/lib/mode-color.service.ts +3 -0
- package/libs/africanies-theme/src/lib/theme.service.ts +3 -0
- package/libs/africanies-ui/src/index.ts +11 -11
- package/libs/africanies-ui/src/lib/accordion/accordion.component.ts +3 -0
- package/libs/africanies-ui/src/lib/action-menu/action-menu-trigger.directive.ts +3 -0
- package/libs/africanies-ui/src/lib/action-menu/action-menu.component.ts +3 -0
- package/libs/africanies-ui/src/lib/alert/alert.component.ts +3 -0
- package/libs/africanies-ui/src/lib/avatar/avatar-menu.component.ts +3 -0
- package/libs/africanies-ui/src/lib/avatar/avatar.component.ts +3 -0
- package/libs/africanies-ui/src/lib/brand/brand-logo.component.ts +3 -0
- package/libs/africanies-ui/src/lib/button/button.component.ts +3 -0
- package/libs/africanies-ui/src/lib/carrier/carrier-logo.component.ts +3 -0
- package/libs/africanies-ui/src/lib/chip/chip.component.ts +3 -0
- package/libs/africanies-ui/src/lib/content-stack/content-stack.component.ts +3 -0
- package/libs/africanies-ui/src/lib/copy-button/copy-button.component.ts +3 -0
- package/libs/africanies-ui/src/lib/feedback/async-state.component.ts +3 -0
- package/libs/africanies-ui/src/lib/feedback/empty-state.component.ts +3 -0
- package/libs/africanies-ui/src/lib/feedback/error-indicator.component.ts +3 -0
- package/libs/africanies-ui/src/lib/feedback/error-state.component.ts +3 -0
- package/libs/africanies-ui/src/lib/feedback/loading-state.component.ts +3 -0
- package/libs/africanies-ui/src/lib/filters/filter-drawer.panel.ts +3 -0
- package/libs/africanies-ui/src/lib/filters/filter-drawer.service.ts +3 -0
- package/libs/africanies-ui/src/lib/filters/filter-query.service.ts +3 -0
- package/libs/africanies-ui/src/lib/forms/address-input/address-input.component.ts +3 -0
- package/libs/africanies-ui/src/lib/forms/address-input/google-places.service.ts +3 -0
- package/libs/africanies-ui/src/lib/forms/checkbox/checkbox.component.ts +3 -0
- package/libs/africanies-ui/src/lib/forms/date-picker/date-picker.component.ts +3 -0
- package/libs/africanies-ui/src/lib/forms/file-upload/camera-capture.dialog.ts +3 -0
- package/libs/africanies-ui/src/lib/forms/file-upload/file-preview.dialog.ts +3 -0
- package/libs/africanies-ui/src/lib/forms/file-upload/file-upload.component.ts +3 -0
- package/libs/africanies-ui/src/lib/forms/index.ts +3 -3
- package/libs/africanies-ui/src/lib/forms/number-input/number-input.component.ts +3 -0
- package/libs/africanies-ui/src/lib/forms/otp-input/otp-input.component.ts +3 -0
- package/libs/africanies-ui/src/lib/forms/phone-input/phone-input.component.ts +9 -6
- package/libs/africanies-ui/src/lib/forms/radio/radio.component.ts +3 -0
- package/libs/africanies-ui/src/lib/forms/search-combobox/search-combobox.component.ts +3 -0
- package/libs/africanies-ui/src/lib/forms/select/select.component.ts +3 -0
- package/libs/africanies-ui/src/lib/forms/text-input/text-input.component.ts +3 -0
- package/libs/africanies-ui/src/lib/forms/textarea/textarea.component.ts +3 -0
- package/libs/africanies-ui/src/lib/forms/toggle/toggle.component.ts +3 -0
- package/libs/africanies-ui/src/lib/image/image.component.ts +3 -0
- package/libs/africanies-ui/src/lib/info-popover/info-popover-content.directive.ts +3 -0
- package/libs/africanies-ui/src/lib/info-popover/info-popover-trigger.directive.ts +3 -0
- package/libs/africanies-ui/src/lib/info-popover/info-popover.component.ts +3 -0
- package/libs/africanies-ui/src/lib/layout/app-shell-content-header.component.ts +3 -0
- package/libs/africanies-ui/src/lib/layout/app-shell-header-slot.directive.ts +3 -0
- package/libs/africanies-ui/src/lib/layout/app-shell-header.component.ts +9 -0
- package/libs/africanies-ui/src/lib/layout/app-shell.component.ts +3 -0
- package/libs/africanies-ui/src/lib/layout/header-weather.spec.ts +62 -94
- package/libs/africanies-ui/src/lib/layout/header-weather.ts +54 -61
- package/libs/africanies-ui/src/lib/layout/page-header.component.ts +3 -0
- package/libs/africanies-ui/src/lib/modules/africanies-actions.module.ts +3 -0
- package/libs/africanies-ui/src/lib/modules/africanies-feedback.module.ts +3 -0
- package/libs/africanies-ui/src/lib/modules/africanies-forms.module.ts +3 -0
- package/libs/africanies-ui/src/lib/modules/africanies-info-popover.module.ts +3 -0
- package/libs/africanies-ui/src/lib/modules/africanies-navigation.module.ts +3 -0
- package/libs/africanies-ui/src/lib/modules/africanies-stepper.module.ts +3 -0
- package/libs/africanies-ui/src/lib/modules/africanies-table.module.ts +3 -0
- package/libs/africanies-ui/src/lib/modules/africanies-tooltip.module.ts +3 -0
- package/libs/africanies-ui/src/lib/navigation/breadcrumb/breadcrumb.component.ts +3 -0
- package/libs/africanies-ui/src/lib/navigation/segment/segment.component.ts +3 -0
- package/libs/africanies-ui/src/lib/navigation/shipping-mode-switch/shipping-mode-switch.component.ts +3 -0
- package/libs/africanies-ui/src/lib/navigation/side-nav/africanies-side-nav.component.ts +3 -0
- package/libs/africanies-ui/src/lib/navigation/tabs/tab-def.directive.ts +3 -0
- package/libs/africanies-ui/src/lib/navigation/tabs/tabs.component.ts +3 -0
- package/libs/africanies-ui/src/lib/notifications/notification-drawer.panel.ts +3 -0
- package/libs/africanies-ui/src/lib/notifications/notification-drawer.service.ts +3 -0
- package/libs/africanies-ui/src/lib/overlay/confirm-dialog.component.ts +3 -0
- package/libs/africanies-ui/src/lib/overlay/confirm.service.ts +3 -0
- package/libs/africanies-ui/src/lib/overlay/drawer.service.ts +3 -0
- package/libs/africanies-ui/src/lib/overlay/modal.service.ts +3 -0
- package/libs/africanies-ui/src/lib/overlay/overlay-frame.component.ts +9 -0
- package/libs/africanies-ui/src/lib/pagination/pagination.component.ts +3 -0
- package/libs/africanies-ui/src/lib/stepper/step-def.directive.ts +3 -0
- package/libs/africanies-ui/src/lib/stepper/stepper.component.ts +3 -0
- package/libs/africanies-ui/src/lib/table/cell-def.directive.ts +3 -0
- package/libs/africanies-ui/src/lib/table/header-cell-def.directive.ts +3 -0
- package/libs/africanies-ui/src/lib/table/row-detail-def.directive.ts +3 -0
- package/libs/africanies-ui/src/lib/table/table.component.ts +3 -0
- package/libs/africanies-ui/src/lib/toast/toast-host.component.ts +4 -1
- package/libs/africanies-ui/src/lib/toast/toast-item.component.ts +3 -0
- package/libs/africanies-ui/src/lib/toast/toast.service.ts +3 -0
- package/libs/africanies-ui/src/lib/tooltip/tooltip-trigger.directive.ts +3 -0
- package/libs/africanies-ui/src/lib/tooltip/tooltip.component.ts +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,4699 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, Injector, signal, Injectable, makeEnvironmentProviders, provideAppInitializer } from '@angular/core';
|
|
3
|
+
import { SessionStorageService, AFRICANIES_SHIPPING_MODE_KEY, STORAGE_TOKEN, AFRICANIES_MODE_CONFIG_KEY, provideLocalStorage, AFRICANIES_ACCESS_TOKEN_KEY } from '@africanies/africanies-storage';
|
|
4
|
+
import { of, timeout, throwError, map as map$1, tap as tap$1, catchError, finalize, firstValueFrom, forkJoin } from 'rxjs';
|
|
5
|
+
import { HttpErrorResponse, HttpContextToken, HttpContext, HttpClient, HttpHeaders, HttpParams, provideHttpClient, withInterceptors } from '@angular/common/http';
|
|
6
|
+
import { DEFAULT_PAGE_SIZE, NOTIFICATION_PAGE_SIZE } from '@africanies/africanies-models';
|
|
7
|
+
import { take, tap, map } from 'rxjs/operators';
|
|
8
|
+
import { ActivatedRoute, Router } from '@angular/router';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* DI token for the active {@link AfricaniesSdkConfig}.
|
|
12
|
+
*
|
|
13
|
+
* Apps must call {@link provideAfricaniesSdk} at bootstrap; injecting without that
|
|
14
|
+
* provider throws so misconfiguration fails fast rather than silently using
|
|
15
|
+
* an empty base URL.
|
|
16
|
+
*/
|
|
17
|
+
const AFRICANIES_SDK_CONFIG = new InjectionToken('AFRICANIES_SDK_CONFIG');
|
|
18
|
+
|
|
19
|
+
const DEFAULT_MODE = 'sfn';
|
|
20
|
+
/**
|
|
21
|
+
* Signal-based holder for the active {@link ShippingMode}.
|
|
22
|
+
*
|
|
23
|
+
* Persists through {@link SessionStorageService} under
|
|
24
|
+
* {@link AFRICANIES_SHIPPING_MODE_KEY} so each browser tab can hold its own STN/SFN
|
|
25
|
+
* context (refresh within the tab keeps the choice; other tabs are unaffected).
|
|
26
|
+
* Defaults to `'sfn'` when nothing is stored or the stored value is not a known
|
|
27
|
+
* mode — preferring a safe outbound default over failing open on corrupt storage.
|
|
28
|
+
*
|
|
29
|
+
* Changing mode clears {@link ApiClient}'s GET cache so `readAll` / by-id
|
|
30
|
+
* dumps cannot cross STN↔SFN (cache keys omit the mode header). `ApiClient`
|
|
31
|
+
* is resolved lazily via {@link Injector} to avoid a DI cycle.
|
|
32
|
+
*
|
|
33
|
+
* List screens should drop in-memory rows and show a blocking loader until
|
|
34
|
+
* the new mode's page arrives — STN rows must not linger on SFN. Use
|
|
35
|
+
* `listFetchKind` with `reason: 'mode'`.
|
|
36
|
+
*
|
|
37
|
+
* Features that must warn before a switch (e.g. mid Create Shipment) can
|
|
38
|
+
* {@link registerModeChangeGuard}. The shell switch uses
|
|
39
|
+
* {@link requestModeChange}; programmatic {@link setMode} bypasses the guard.
|
|
40
|
+
*
|
|
41
|
+
* Provided in root; no explicit provider registration is required.
|
|
42
|
+
* Pair with {@link shipmentModeInterceptor} so HTTP calls advertise the mode.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* const shipping = inject(ShippingModeService);
|
|
47
|
+
* shipping.setMode('stn');
|
|
48
|
+
* console.log(shipping.mode()); // 'stn'
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
/**
|
|
52
|
+
* Signal-based holder for the active {@link ShippingMode}.
|
|
53
|
+
*/
|
|
54
|
+
class ShippingModeService {
|
|
55
|
+
storage = inject(SessionStorageService);
|
|
56
|
+
injector = inject(Injector);
|
|
57
|
+
_mode = signal(this.readInitialMode(), /* @ts-ignore */
|
|
58
|
+
...(ngDevMode ? [{ debugName: "_mode" }] : /* istanbul ignore next */ []));
|
|
59
|
+
modeChangeGuard = null;
|
|
60
|
+
/**
|
|
61
|
+
* Read-only view of the current shipping mode (`Signal` = Angular's
|
|
62
|
+
* readonly signal surface; mutate only via {@link setMode} /
|
|
63
|
+
* {@link requestModeChange}).
|
|
64
|
+
*/
|
|
65
|
+
mode = this._mode.asReadonly();
|
|
66
|
+
/**
|
|
67
|
+
* Register a single guard for shell mode switches. Pass `null` to clear
|
|
68
|
+
* (e.g. on feature destroy). Only one guard is active at a time.
|
|
69
|
+
* @param guard
|
|
70
|
+
*/
|
|
71
|
+
registerModeChangeGuard(guard) {
|
|
72
|
+
this.modeChangeGuard = guard;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Shell switch entry point: runs any registered guard, then applies the
|
|
76
|
+
* mode when allowed.
|
|
77
|
+
*
|
|
78
|
+
* @param mode - `'stn'` or `'sfn'`.
|
|
79
|
+
* @returns Emits once with `true` when the mode changed, else `false`.
|
|
80
|
+
*/
|
|
81
|
+
requestModeChange(mode) {
|
|
82
|
+
if (mode === this._mode()) {
|
|
83
|
+
return of(false);
|
|
84
|
+
}
|
|
85
|
+
const current = this._mode();
|
|
86
|
+
const guard = this.modeChangeGuard;
|
|
87
|
+
const allowed$ = guard ? guard(mode, current) : of(true);
|
|
88
|
+
return allowed$.pipe(take(1), tap((ok) => {
|
|
89
|
+
if (ok) {
|
|
90
|
+
this.applyMode(mode);
|
|
91
|
+
}
|
|
92
|
+
}), map((ok) => !!ok));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Updates the active mode and persists it for the current tab.
|
|
96
|
+
* No-ops when `mode` already matches the current value.
|
|
97
|
+
* Bypasses {@link registerModeChangeGuard} — prefer
|
|
98
|
+
* {@link requestModeChange} for user-driven switches.
|
|
99
|
+
*
|
|
100
|
+
* @param mode - `'stn'` or `'sfn'`.
|
|
101
|
+
*/
|
|
102
|
+
setMode(mode) {
|
|
103
|
+
if (mode === this._mode()) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.applyMode(mode);
|
|
107
|
+
}
|
|
108
|
+
applyMode(mode) {
|
|
109
|
+
this._mode.set(mode);
|
|
110
|
+
this.storage.set(AFRICANIES_SHIPPING_MODE_KEY, mode);
|
|
111
|
+
this.injector.get(ApiClient).clearCache();
|
|
112
|
+
}
|
|
113
|
+
readInitialMode() {
|
|
114
|
+
const stored = this.storage.get(AFRICANIES_SHIPPING_MODE_KEY);
|
|
115
|
+
return stored === 'stn' || stored === 'sfn' ? stored : DEFAULT_MODE;
|
|
116
|
+
}
|
|
117
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ShippingModeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
118
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ShippingModeService, providedIn: 'root' });
|
|
119
|
+
}
|
|
120
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ShippingModeService, decorators: [{
|
|
121
|
+
type: Injectable,
|
|
122
|
+
args: [{ providedIn: 'root' }]
|
|
123
|
+
}] });
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Null-safe coercions for AFRICANIES wire JSON.
|
|
127
|
+
*
|
|
128
|
+
* Mappers should run every request/response field through these helpers so
|
|
129
|
+
* missing keys, `null`, and wrong JSON types never throw at `.map` / `.trim`.
|
|
130
|
+
*/
|
|
131
|
+
/**
|
|
132
|
+
* Narrow unknown JSON into a plain object record.
|
|
133
|
+
* @param value - Candidate value.
|
|
134
|
+
* @returns Record when a non-array object; otherwise `null`.
|
|
135
|
+
*/
|
|
136
|
+
function asRecord(value) {
|
|
137
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Coerce a value to an array. Non-arrays become `[]`.
|
|
144
|
+
* @param value - Candidate list.
|
|
145
|
+
*/
|
|
146
|
+
function asArray(value) {
|
|
147
|
+
return Array.isArray(value) ? value : [];
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Map every element of a wire list. Non-arrays yield `[]`.
|
|
151
|
+
* @param value - Candidate list.
|
|
152
|
+
* @param mapOne - Per-item mapper.
|
|
153
|
+
*/
|
|
154
|
+
function mapArray(value, mapOne) {
|
|
155
|
+
return asArray(value).map(mapOne);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* If a list payload is wrapped in a Laravel paginator / `{ data | items }`,
|
|
159
|
+
* return the inner array. Otherwise return `value` unchanged.
|
|
160
|
+
* @param value - Envelope `data` or a nested list field.
|
|
161
|
+
*/
|
|
162
|
+
function unwrapListPayload(value) {
|
|
163
|
+
if (Array.isArray(value) || value == null) {
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
const record = asRecord(value);
|
|
167
|
+
if (record === null) {
|
|
168
|
+
return value;
|
|
169
|
+
}
|
|
170
|
+
const nested = record['data'] ?? record['items'] ?? record['results'];
|
|
171
|
+
if (Array.isArray(nested)) {
|
|
172
|
+
return nested;
|
|
173
|
+
}
|
|
174
|
+
if (nested == null &&
|
|
175
|
+
('current_page' in record ||
|
|
176
|
+
'currentPage' in record ||
|
|
177
|
+
'last_page' in record ||
|
|
178
|
+
'lastPage' in record ||
|
|
179
|
+
'per_page' in record ||
|
|
180
|
+
'perPage' in record)) {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Map a list-or-single payload (Laravel sometimes sends one object).
|
|
187
|
+
*
|
|
188
|
+
* - `null` / `undefined` → `[]`
|
|
189
|
+
* - paginator / `{ data | items }` → mapped inner rows
|
|
190
|
+
* - array → mapped items
|
|
191
|
+
* - anything else → one mapped item
|
|
192
|
+
*
|
|
193
|
+
* @param value - Envelope `data` (or a nested list field).
|
|
194
|
+
* @param mapOne - Per-item mapper.
|
|
195
|
+
*/
|
|
196
|
+
function mapList(value, mapOne) {
|
|
197
|
+
const list = unwrapListPayload(value);
|
|
198
|
+
if (list == null) {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
if (Array.isArray(list)) {
|
|
202
|
+
return list.map(mapOne);
|
|
203
|
+
}
|
|
204
|
+
return [mapOne(list)];
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Finite number, or `fallback` when missing/invalid.
|
|
208
|
+
* @param value - Raw numeric field.
|
|
209
|
+
* @param fallback - Default when not a finite number.
|
|
210
|
+
*/
|
|
211
|
+
function asNumber(value, fallback = 0) {
|
|
212
|
+
const n = Number(value);
|
|
213
|
+
return Number.isFinite(n) ? n : fallback;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Finite number, or `null` when missing/invalid.
|
|
217
|
+
* @param value - Raw numeric field.
|
|
218
|
+
*/
|
|
219
|
+
function asNullableNumber(value) {
|
|
220
|
+
if (value == null || value === '') {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
const n = Number(value);
|
|
224
|
+
return Number.isFinite(n) ? n : null;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* String, or `fallback` when the value cannot be stringified usefully.
|
|
228
|
+
* @param value - Raw string field.
|
|
229
|
+
* @param fallback - Default when nullish / non-scalar.
|
|
230
|
+
*/
|
|
231
|
+
function asString(value, fallback = '') {
|
|
232
|
+
if (value == null) {
|
|
233
|
+
return fallback;
|
|
234
|
+
}
|
|
235
|
+
if (typeof value === 'string') {
|
|
236
|
+
return value;
|
|
237
|
+
}
|
|
238
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
239
|
+
return String(value);
|
|
240
|
+
}
|
|
241
|
+
return fallback;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* String or `null` (never `undefined`).
|
|
245
|
+
* @param value - Raw string field.
|
|
246
|
+
*/
|
|
247
|
+
function asNullableString(value) {
|
|
248
|
+
if (value == null) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
if (typeof value === 'string') {
|
|
252
|
+
return value;
|
|
253
|
+
}
|
|
254
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
255
|
+
return String(value);
|
|
256
|
+
}
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Boolean from wire flags (`true` / `false` / `1` / `0` / `"true"` / `"1"`).
|
|
261
|
+
* Missing values are `false`.
|
|
262
|
+
* @param value - Raw flag.
|
|
263
|
+
*/
|
|
264
|
+
function asBoolean(value) {
|
|
265
|
+
if (typeof value === 'boolean') {
|
|
266
|
+
return value;
|
|
267
|
+
}
|
|
268
|
+
if (typeof value === 'number') {
|
|
269
|
+
return value === 1;
|
|
270
|
+
}
|
|
271
|
+
const trimmed = String(value ?? '')
|
|
272
|
+
.trim()
|
|
273
|
+
.toLowerCase();
|
|
274
|
+
return trimmed === '1' || trimmed === 'true';
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Boolean or `null` when the field is absent / unparseable.
|
|
278
|
+
* @param value - Raw flag.
|
|
279
|
+
*/
|
|
280
|
+
function asNullableBoolean(value) {
|
|
281
|
+
if (value == null || value === '') {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
if (typeof value === 'boolean') {
|
|
285
|
+
return value;
|
|
286
|
+
}
|
|
287
|
+
if (typeof value === 'number') {
|
|
288
|
+
return value === 1;
|
|
289
|
+
}
|
|
290
|
+
if (typeof value === 'string') {
|
|
291
|
+
const trimmed = value.trim().toLowerCase();
|
|
292
|
+
if (trimmed === '1' || trimmed === 'true') {
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
if (trimmed === '0' || trimmed === 'false') {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Wire `"1"` / `"0"` from a boolean, number, or string flag.
|
|
303
|
+
* @param value - Host or wire flag.
|
|
304
|
+
*/
|
|
305
|
+
function toFlag01(value) {
|
|
306
|
+
return asBoolean(value) ? '1' : '0';
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* User-model `0` / `1` flag, or `null` when absent.
|
|
310
|
+
* @param value - Raw flag.
|
|
311
|
+
*/
|
|
312
|
+
function asNullableFlag01(value) {
|
|
313
|
+
if (value == null || value === '') {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
if (value === 1 || value === '1' || value === true) {
|
|
317
|
+
return 1;
|
|
318
|
+
}
|
|
319
|
+
if (value === 0 || value === '0' || value === false) {
|
|
320
|
+
return 0;
|
|
321
|
+
}
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Laravel-style validation bag: `{ field: ["msg", ...] }`.
|
|
327
|
+
* @param value - Candidate JSON object.
|
|
328
|
+
*/
|
|
329
|
+
function isLaravelValidationBag(value) {
|
|
330
|
+
const record = asRecord(value);
|
|
331
|
+
if (record === null) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
const keys = Object.keys(record);
|
|
335
|
+
if (keys.length === 0) {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
return keys.every((key) => {
|
|
339
|
+
const entry = record[key];
|
|
340
|
+
return (Array.isArray(entry) &&
|
|
341
|
+
entry.every((item) => typeof item === 'string'));
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Flatten a validation bag into {@link ApiErrorDetailModel} rows.
|
|
346
|
+
*
|
|
347
|
+
* Uses messages in order; the first message per field is what
|
|
348
|
+
* {@link fieldErrorsMap} exposes for form binding.
|
|
349
|
+
* @param bag - Field → messages map.
|
|
350
|
+
*/
|
|
351
|
+
function mapLaravelValidationBag(bag) {
|
|
352
|
+
const details = [];
|
|
353
|
+
for (const [field, rawMessages] of Object.entries(bag)) {
|
|
354
|
+
const messages = asArray(rawMessages)
|
|
355
|
+
.map((m) => asString(m).trim())
|
|
356
|
+
.filter((m) => m.length > 0);
|
|
357
|
+
for (const message of messages) {
|
|
358
|
+
details.push({ field, message, code: null });
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return details;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Join field errors for toast / banner copy (one line per message, de-duped).
|
|
365
|
+
* Prefer this over top-level `message` when a bag is present — the envelope
|
|
366
|
+
* `message` is often only the first field.
|
|
367
|
+
* @param errors - Normalized field errors.
|
|
368
|
+
*/
|
|
369
|
+
function joinApiErrorMessages(errors) {
|
|
370
|
+
if (!errors?.length) {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
const lines = [];
|
|
374
|
+
const seen = new Set();
|
|
375
|
+
for (const detail of errors) {
|
|
376
|
+
const message = detail.message.trim();
|
|
377
|
+
if (!message || seen.has(message)) {
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
seen.add(message);
|
|
381
|
+
lines.push(message);
|
|
382
|
+
}
|
|
383
|
+
return lines.length > 0 ? lines.join('\n') : null;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* First message per field for form control binding.
|
|
387
|
+
* @param errors - Normalized field errors.
|
|
388
|
+
*/
|
|
389
|
+
function fieldErrorsMap(errors) {
|
|
390
|
+
const map = {};
|
|
391
|
+
if (!errors?.length) {
|
|
392
|
+
return map;
|
|
393
|
+
}
|
|
394
|
+
for (const detail of errors) {
|
|
395
|
+
const field = detail.field?.trim();
|
|
396
|
+
const message = detail.message.trim();
|
|
397
|
+
if (!field || !message || field in map) {
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
map[field] = message;
|
|
401
|
+
}
|
|
402
|
+
return map;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Whether the payload looks like an AFRICANIES API envelope.
|
|
407
|
+
* @param value
|
|
408
|
+
*/
|
|
409
|
+
function isWrappedEnvelope(value) {
|
|
410
|
+
const record = asRecord(value);
|
|
411
|
+
return record !== null && 'success' in record && 'data' in record;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Laravel LengthAwarePaginator nested inside the envelope `data` field.
|
|
415
|
+
* @param value
|
|
416
|
+
*/
|
|
417
|
+
function isLaravelPaginator(value) {
|
|
418
|
+
const record = asRecord(value);
|
|
419
|
+
if (record === null) {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
return ((Array.isArray(record['data']) || record['data'] == null) &&
|
|
423
|
+
('current_page' in record ||
|
|
424
|
+
'currentPage' in record ||
|
|
425
|
+
'last_page' in record ||
|
|
426
|
+
'lastPage' in record ||
|
|
427
|
+
'per_page' in record ||
|
|
428
|
+
'perPage' in record));
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Map pagination from snake_case (preferred), legacy camelCase, or Laravel keys.
|
|
432
|
+
* @param value
|
|
433
|
+
*/
|
|
434
|
+
function normalizePagination(value) {
|
|
435
|
+
const record = asRecord(value);
|
|
436
|
+
if (record === null) {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
const current_page = record['current_page'] ?? record['currentPage'];
|
|
440
|
+
const per_page = record['per_page'] ?? record['perPage'];
|
|
441
|
+
const total_items = record['total_items'] ?? record['totalItems'] ?? record['total'];
|
|
442
|
+
const total_pages = record['total_pages'] ??
|
|
443
|
+
record['totalPages'] ??
|
|
444
|
+
record['last_page'] ??
|
|
445
|
+
record['lastPage'];
|
|
446
|
+
let has_next_page = record['has_next_page'] ?? record['hasNextPage'];
|
|
447
|
+
let has_previous_page = record['has_previous_page'] ?? record['hasPreviousPage'];
|
|
448
|
+
if (has_next_page == null) {
|
|
449
|
+
const next_page_url = record['next_page_url'] ?? record['nextPageUrl'];
|
|
450
|
+
has_next_page = next_page_url != null && next_page_url !== '';
|
|
451
|
+
}
|
|
452
|
+
if (has_previous_page == null) {
|
|
453
|
+
const prev_page_url = record['prev_page_url'] ?? record['prevPageUrl'];
|
|
454
|
+
has_previous_page = prev_page_url != null && prev_page_url !== '';
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
current_page: asNumber(current_page),
|
|
458
|
+
per_page: asNumber(per_page),
|
|
459
|
+
total_items: asNumber(total_items),
|
|
460
|
+
total_pages: asNumber(total_pages),
|
|
461
|
+
has_next_page: Boolean(has_next_page),
|
|
462
|
+
has_previous_page: Boolean(has_previous_page),
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Flatten a Laravel paginator in `data` into list payload + {@link PaginationMetaModel}.
|
|
467
|
+
* @param raw
|
|
468
|
+
*/
|
|
469
|
+
function unwrapLaravelPaginator(raw) {
|
|
470
|
+
if (!isLaravelPaginator(raw)) {
|
|
471
|
+
return {
|
|
472
|
+
data: raw ?? null,
|
|
473
|
+
pagination: null,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
const record = asRecord(raw);
|
|
477
|
+
if (record === null) {
|
|
478
|
+
return { data: null, pagination: null };
|
|
479
|
+
}
|
|
480
|
+
return {
|
|
481
|
+
data: Array.isArray(record['data']) ? record['data'] : [],
|
|
482
|
+
pagination: normalizePagination(record),
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Coalesce a single error entry so missing field/code become `null`.
|
|
487
|
+
* @param value
|
|
488
|
+
*/
|
|
489
|
+
function normalizeErrorDetail(value) {
|
|
490
|
+
const record = asRecord(value) ?? {};
|
|
491
|
+
return {
|
|
492
|
+
field: asString(record['field']) || null,
|
|
493
|
+
message: asString(record['message']),
|
|
494
|
+
code: asString(record['code']) || null,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Resolve field errors from an envelope `errors` value or a failure `data` bag.
|
|
499
|
+
* @param errorsRaw - Wire `errors` (array or Laravel object map).
|
|
500
|
+
* @param data - Envelope `data` (may hold a validation bag on failure).
|
|
501
|
+
* @param success - Envelope success flag.
|
|
502
|
+
*/
|
|
503
|
+
function resolveErrors(errorsRaw, data, success) {
|
|
504
|
+
if (Array.isArray(errorsRaw)) {
|
|
505
|
+
const details = asArray(errorsRaw).map(normalizeErrorDetail);
|
|
506
|
+
return details.length > 0 ? details : null;
|
|
507
|
+
}
|
|
508
|
+
if (isLaravelValidationBag(errorsRaw)) {
|
|
509
|
+
const details = mapLaravelValidationBag(asRecord(errorsRaw) ?? {});
|
|
510
|
+
return details.length > 0 ? details : null;
|
|
511
|
+
}
|
|
512
|
+
// Soft/hard validation failures often put the bag in `data` (not `errors`).
|
|
513
|
+
if (!success && isLaravelValidationBag(data)) {
|
|
514
|
+
const details = mapLaravelValidationBag(asRecord(data) ?? {});
|
|
515
|
+
return details.length > 0 ? details : null;
|
|
516
|
+
}
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Normalize any HTTP JSON body into a fully null-safe {@link ApiResponseModel}.
|
|
521
|
+
*
|
|
522
|
+
* Envelope fields use snake_case where the wire does (`status_code`,
|
|
523
|
+
* pagination keys). Paginated list reads often embed a Laravel paginator in
|
|
524
|
+
* `data` (`data.data` + totals) — that shape is flattened here so consumers
|
|
525
|
+
* always see `data: T[]` and `pagination`.
|
|
526
|
+
*
|
|
527
|
+
* Validation failures that return a Laravel field bag in `errors` or `data`
|
|
528
|
+
* are lifted into `errors: ApiErrorDetailModel[]`. On failure, a bag in
|
|
529
|
+
* `data` is cleared (`data: null`) so it is not typed as success payload `T`.
|
|
530
|
+
* Top-level `message` is enriched to the joined field messages when a bag
|
|
531
|
+
* exists (API `message` alone is often only the first field).
|
|
532
|
+
* @param raw
|
|
533
|
+
*/
|
|
534
|
+
function normalize(raw) {
|
|
535
|
+
if (isWrappedEnvelope(raw)) {
|
|
536
|
+
const record = asRecord(raw);
|
|
537
|
+
if (record !== null) {
|
|
538
|
+
const successRaw = record['success'];
|
|
539
|
+
const success = typeof successRaw === 'boolean' ? successRaw : Boolean(successRaw);
|
|
540
|
+
const status_code = record['status_code'] ?? record['statusCode'] ?? null;
|
|
541
|
+
let pagination = normalizePagination(record['pagination']);
|
|
542
|
+
let data = record['data'] ?? null;
|
|
543
|
+
if (pagination === null && isLaravelPaginator(data)) {
|
|
544
|
+
const unwrapped = unwrapLaravelPaginator(data);
|
|
545
|
+
data = unwrapped.data ?? null;
|
|
546
|
+
pagination = unwrapped.pagination;
|
|
547
|
+
}
|
|
548
|
+
const errors = resolveErrors(record['errors'], data, success);
|
|
549
|
+
const apiMessage = asNullableString(record['message']);
|
|
550
|
+
// Do not leave a validation bag typed as success payload `T`.
|
|
551
|
+
if (!success && isLaravelValidationBag(data)) {
|
|
552
|
+
data = null;
|
|
553
|
+
}
|
|
554
|
+
return {
|
|
555
|
+
success,
|
|
556
|
+
message: joinApiErrorMessages(errors) ?? apiMessage,
|
|
557
|
+
data,
|
|
558
|
+
errors,
|
|
559
|
+
pagination,
|
|
560
|
+
status_code: status_code === null || status_code === undefined
|
|
561
|
+
? null
|
|
562
|
+
: asNumber(status_code),
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
success: true,
|
|
568
|
+
message: null,
|
|
569
|
+
data: raw ?? null,
|
|
570
|
+
errors: null,
|
|
571
|
+
pagination: null,
|
|
572
|
+
status_code: null,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Resolve user-facing error copy from an API envelope, raw body, or HttpClient error.
|
|
578
|
+
*
|
|
579
|
+
* Prefers joined field messages when present; otherwise envelope / HTTP `message`.
|
|
580
|
+
*
|
|
581
|
+
* Prefer relying on {@link ApiClient}: HTTP failures are already rethrown as
|
|
582
|
+
* `Error` with this text on `.message`. Hosts mainly need this helper when
|
|
583
|
+
* calling Angular `HttpClient` directly (e.g. toast interceptor).
|
|
584
|
+
*
|
|
585
|
+
* @param input - Normalized response, raw JSON body, or {@link HttpErrorResponse}.
|
|
586
|
+
*/
|
|
587
|
+
function formatApiErrorMessage(input) {
|
|
588
|
+
if (typeof input === 'string' && input.trim()) {
|
|
589
|
+
return input.trim();
|
|
590
|
+
}
|
|
591
|
+
if (input instanceof HttpErrorResponse) {
|
|
592
|
+
const fromBody = formatApiErrorMessage(input.error);
|
|
593
|
+
if (fromBody !== 'Something went wrong.') {
|
|
594
|
+
return fromBody;
|
|
595
|
+
}
|
|
596
|
+
if (input.status === 0) {
|
|
597
|
+
return 'Network error. Check your connection.';
|
|
598
|
+
}
|
|
599
|
+
if (input.status === 401 || input.status === 403) {
|
|
600
|
+
return 'Authentication failed. Check your access token.';
|
|
601
|
+
}
|
|
602
|
+
if (input.status >= 500) {
|
|
603
|
+
return 'Server error. Try again in a moment.';
|
|
604
|
+
}
|
|
605
|
+
if (input.message?.trim()) {
|
|
606
|
+
return input.message.trim();
|
|
607
|
+
}
|
|
608
|
+
return `Request failed (${input.status})`;
|
|
609
|
+
}
|
|
610
|
+
if (input && typeof input === 'object' && 'success' in input && 'data' in input) {
|
|
611
|
+
const normalized = normalize(input);
|
|
612
|
+
return (joinApiErrorMessages(normalized.errors) ??
|
|
613
|
+
normalized.message?.trim() ??
|
|
614
|
+
'Something went wrong.');
|
|
615
|
+
}
|
|
616
|
+
if (input && typeof input === 'object') {
|
|
617
|
+
const record = asRecord(input);
|
|
618
|
+
if (record &&
|
|
619
|
+
typeof record['message'] === 'string' &&
|
|
620
|
+
record['message'].trim()) {
|
|
621
|
+
return record['message'].trim();
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (input instanceof Error && input.message.trim()) {
|
|
625
|
+
return input.message.trim();
|
|
626
|
+
}
|
|
627
|
+
return 'Something went wrong.';
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Tiny in-memory TTL cache for {@link ApiClient} GET responses.
|
|
632
|
+
*
|
|
633
|
+
* Intentionally process-local and non-persistent: it exists to skip identical
|
|
634
|
+
* GETs within a short window (e.g. rapid remounts), not to replace TanStack
|
|
635
|
+
* Query or server cache headers.
|
|
636
|
+
*
|
|
637
|
+
* ## When NOT to use caching (`cacheTtlMs`)
|
|
638
|
+
*
|
|
639
|
+
* - Frequently changing data (dashboards, live rates, inventory) — stale reads
|
|
640
|
+
* are worse than an extra round-trip.
|
|
641
|
+
* - User-specific or permission-sensitive payloads — another user/session in
|
|
642
|
+
* the same SPA tab could theoretically share the in-memory map.
|
|
643
|
+
* - After mutations that invalidate list/detail views — this cache has no
|
|
644
|
+
* tag-based invalidation; prefer TanStack Query for that.
|
|
645
|
+
* - Large payloads — every entry stays in heap until TTL expiry.
|
|
646
|
+
*
|
|
647
|
+
* Prefer omitting `cacheTtlMs` unless you have a measured hot-spot and a
|
|
648
|
+
* known staleness budget.
|
|
649
|
+
*/
|
|
650
|
+
/**
|
|
651
|
+
* Key/value store with per-entry TTL eviction on read.
|
|
652
|
+
*/
|
|
653
|
+
class HttpResponseCache {
|
|
654
|
+
store = new Map();
|
|
655
|
+
/**
|
|
656
|
+
* Returns a cached value when present and not expired.
|
|
657
|
+
*
|
|
658
|
+
* @param key - Stable request identity (method + URL + relevant params).
|
|
659
|
+
* @returns The stored value, or `null` on miss / expiry.
|
|
660
|
+
*/
|
|
661
|
+
get(key) {
|
|
662
|
+
const entry = this.store.get(key);
|
|
663
|
+
if (!entry) {
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
if (Date.now() >= entry.expiresAt) {
|
|
667
|
+
this.store.delete(key);
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
return entry.value;
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Stores a value until `ttlMs` elapses.
|
|
674
|
+
*
|
|
675
|
+
* @param key - Stable request identity.
|
|
676
|
+
* @param value - Value to reuse for subsequent hits.
|
|
677
|
+
* @param ttlMs - Time-to-live in milliseconds from now.
|
|
678
|
+
*/
|
|
679
|
+
set(key, value, ttlMs) {
|
|
680
|
+
this.store.set(key, {
|
|
681
|
+
value,
|
|
682
|
+
expiresAt: Date.now() + ttlMs,
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Drops every entry. Useful after logout or global invalidation hooks.
|
|
687
|
+
*/
|
|
688
|
+
clear() {
|
|
689
|
+
this.store.clear();
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Per-request shipping-mode override for {@link shipmentModeInterceptor}.
|
|
695
|
+
*
|
|
696
|
+
* When set, `x-shipment-mode` uses this value instead of
|
|
697
|
+
* {@link ShippingModeService.mode}. The tab / session mode is unchanged.
|
|
698
|
+
* Sentinel `null` means “use the active tab mode”.
|
|
699
|
+
*/
|
|
700
|
+
const SHIPPING_MODE_OVERRIDE = new HttpContextToken(() => null);
|
|
701
|
+
/**
|
|
702
|
+
* Narrow unknown JSON / option values to a {@link ShippingMode}.
|
|
703
|
+
*
|
|
704
|
+
* @param value - Candidate value.
|
|
705
|
+
* @returns `'stn'` or `'sfn'` when valid; otherwise `undefined`.
|
|
706
|
+
*/
|
|
707
|
+
function asShippingMode$1(value) {
|
|
708
|
+
return value === 'stn' || value === 'sfn' ? value : undefined;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Tag a request so {@link shipmentModeInterceptor} sends a different
|
|
712
|
+
* `x-shipment-mode` without calling {@link ShippingModeService.setMode}.
|
|
713
|
+
*
|
|
714
|
+
* Prefer {@link ApiRequestOptions.shippingMode} on {@link ApiClient} calls.
|
|
715
|
+
* Use this helper with raw `HttpClient`.
|
|
716
|
+
*
|
|
717
|
+
* @param mode - Mode for this request only.
|
|
718
|
+
* @param context - Existing context to merge (e.g. from {@link withToast}).
|
|
719
|
+
* @returns HttpContext with the override set.
|
|
720
|
+
*
|
|
721
|
+
* @example
|
|
722
|
+
* ```ts
|
|
723
|
+
* this.http.post(url, body, { context: withShippingMode('stn') });
|
|
724
|
+
* ```
|
|
725
|
+
*/
|
|
726
|
+
function withShippingMode(mode, context) {
|
|
727
|
+
return (context ?? new HttpContext()).set(SHIPPING_MODE_OVERRIDE, mode);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/** DI token for the HTTP → toast bridge. */
|
|
731
|
+
const AFRICANIES_HTTP_TOAST = new InjectionToken('AFRICANIES_HTTP_TOAST');
|
|
732
|
+
/**
|
|
733
|
+
* Present only when the request was tagged with {@link withToast}.
|
|
734
|
+
* Sentinel `null` means “not tagged”.
|
|
735
|
+
*/
|
|
736
|
+
const TOAST_HTTP_OPTIONS = new HttpContextToken(() => null);
|
|
737
|
+
/**
|
|
738
|
+
* Opt a request into HTTP toasts.
|
|
739
|
+
*
|
|
740
|
+
* Defaults: `success: true`, `error: true`. Pass flags to silence either side.
|
|
741
|
+
*
|
|
742
|
+
* @param options - Partial overrides for the defaults.
|
|
743
|
+
* @returns HttpContext ready to pass as `context` on HttpClient calls.
|
|
744
|
+
*
|
|
745
|
+
* @example
|
|
746
|
+
* ```ts
|
|
747
|
+
* // Success + error toasts
|
|
748
|
+
* this.http.post(url, body, { context: withToast() });
|
|
749
|
+
*
|
|
750
|
+
* // Errors only
|
|
751
|
+
* this.http.post(url, body, { context: withToast({ success: false }) });
|
|
752
|
+
*
|
|
753
|
+
* // Custom copy
|
|
754
|
+
* this.http.post(url, body, {
|
|
755
|
+
* context: withToast({
|
|
756
|
+
* successMessage: 'Shipment saved',
|
|
757
|
+
* errorMessage: 'Could not save shipment',
|
|
758
|
+
* }),
|
|
759
|
+
* });
|
|
760
|
+
* ```
|
|
761
|
+
*/
|
|
762
|
+
function withToast(options) {
|
|
763
|
+
return new HttpContext().set(TOAST_HTTP_OPTIONS, {
|
|
764
|
+
success: options?.success ?? true,
|
|
765
|
+
error: options?.error ?? true,
|
|
766
|
+
successMessage: options?.successMessage,
|
|
767
|
+
errorMessage: options?.errorMessage,
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Resolve {@link HttpContext} toast flags for an {@link ApiClient} request.
|
|
773
|
+
*
|
|
774
|
+
* Precedence:
|
|
775
|
+
* 1. Per-request `toast: false` — never tag.
|
|
776
|
+
* 2. GET — silent by default (list/detail failures stay in-page). Opt in with
|
|
777
|
+
* an explicit per-request toast object.
|
|
778
|
+
* 3. Otherwise merge config {@link AfricaniesSdkHttpToasts} with per-request overrides
|
|
779
|
+
* (POST / PUT / PATCH / DELETE toast errors when config is `'errors'`).
|
|
780
|
+
*
|
|
781
|
+
* @param configMode - From {@link AfricaniesSdkConfig.httpToasts} (defaults to `'off'`).
|
|
782
|
+
* @param perRequest - From {@link ApiRequestOptions.toast}.
|
|
783
|
+
* @param method - HTTP verb so GET stays quiet under `'errors'` / `'all'`.
|
|
784
|
+
*/
|
|
785
|
+
function resolveHttpToastContext(configMode, perRequest, method) {
|
|
786
|
+
if (perRequest === false) {
|
|
787
|
+
return undefined;
|
|
788
|
+
}
|
|
789
|
+
const isGet = (method ?? '').toUpperCase() === 'GET';
|
|
790
|
+
if (isGet && perRequest == null) {
|
|
791
|
+
return undefined;
|
|
792
|
+
}
|
|
793
|
+
const fromConfig = isGet ? null : configToastDefaults(configMode);
|
|
794
|
+
const merged = perRequest
|
|
795
|
+
? { ...(fromConfig ?? {}), ...perRequest }
|
|
796
|
+
: fromConfig;
|
|
797
|
+
if (!merged) {
|
|
798
|
+
return undefined;
|
|
799
|
+
}
|
|
800
|
+
const success = merged.success ?? true;
|
|
801
|
+
const error = merged.error ?? true;
|
|
802
|
+
if (!success && !error) {
|
|
803
|
+
return undefined;
|
|
804
|
+
}
|
|
805
|
+
return withToast({
|
|
806
|
+
success,
|
|
807
|
+
error,
|
|
808
|
+
successMessage: merged.successMessage,
|
|
809
|
+
errorMessage: merged.errorMessage,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
function configToastDefaults(mode) {
|
|
813
|
+
if (mode == null || mode === 'off') {
|
|
814
|
+
return null;
|
|
815
|
+
}
|
|
816
|
+
if (mode === 'errors') {
|
|
817
|
+
return { success: false, error: true };
|
|
818
|
+
}
|
|
819
|
+
if (mode === 'all') {
|
|
820
|
+
return { success: true, error: true };
|
|
821
|
+
}
|
|
822
|
+
return mode;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Build {@link HttpContext} for an {@link ApiClient} request.
|
|
827
|
+
*
|
|
828
|
+
* Merges SDK config toasts, per-request toast flags, and optional
|
|
829
|
+
* {@link ApiRequestOptions.shippingMode} override.
|
|
830
|
+
*
|
|
831
|
+
* @param configMode - From {@link AfricaniesSdkConfig.httpToasts}.
|
|
832
|
+
* @param options - Per-request {@link ApiClient} options.
|
|
833
|
+
* @param method
|
|
834
|
+
*/
|
|
835
|
+
function resolveApiRequestContext(configMode, options, method) {
|
|
836
|
+
let context = resolveHttpToastContext(configMode, options.toast, method);
|
|
837
|
+
const shippingMode = asShippingMode$1(options.shippingMode);
|
|
838
|
+
if (shippingMode) {
|
|
839
|
+
context = withShippingMode(shippingMode, context);
|
|
840
|
+
}
|
|
841
|
+
return context;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Effective shipping mode for one {@link ApiClient} request.
|
|
845
|
+
*
|
|
846
|
+
* @param options - Per-request options (may include {@link ApiRequestOptions.shippingMode}).
|
|
847
|
+
* @param activeMode - Current tab mode from {@link ShippingModeService}.
|
|
848
|
+
*/
|
|
849
|
+
function resolveRequestShippingMode(options, activeMode) {
|
|
850
|
+
return asShippingMode$1(options?.shippingMode) ?? activeMode;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Build a path for the AFRICANIES list/detail `ResourceId` convention.
|
|
855
|
+
*
|
|
856
|
+
* | `id` | Path |
|
|
857
|
+
* |------|------|
|
|
858
|
+
* | `null` | `{basePath}` — paginated |
|
|
859
|
+
* | `'all'` | `{basePath}/all` — full list |
|
|
860
|
+
* | `number` | `{basePath}/{id}` — single record |
|
|
861
|
+
*
|
|
862
|
+
* @param basePath - Endpoint base (e.g. `/product/read`).
|
|
863
|
+
* @param id - {@link ResourceId}; defaults to `null` (paginated).
|
|
864
|
+
* @returns Absolute-or-relative path segment for {@link ApiClient.get}.
|
|
865
|
+
*
|
|
866
|
+
* @example
|
|
867
|
+
* ```ts
|
|
868
|
+
* buildResourcePath('/product/read') // '/product/read'
|
|
869
|
+
* buildResourcePath('/product/read', 'all') // '/product/read/all'
|
|
870
|
+
* buildResourcePath('/product/read', 42) // '/product/read/42'
|
|
871
|
+
* ```
|
|
872
|
+
*/
|
|
873
|
+
function buildResourcePath(basePath, id = null) {
|
|
874
|
+
const trimmed = basePath.replace(/\/+$/, '');
|
|
875
|
+
return id === null ? trimmed : `${trimmed}/${id}`;
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Build query params for a {@link ResourceId} request.
|
|
879
|
+
*
|
|
880
|
+
* Pagination fields (`page`, `size`, `order`) are included only when
|
|
881
|
+
* `id === null`. Other keys are always passed through.
|
|
882
|
+
*
|
|
883
|
+
* Paginated lists (`id === null`) always send `size`, defaulting to
|
|
884
|
+
* {@link DEFAULT_PAGE_SIZE} (`15`) when omitted.
|
|
885
|
+
*
|
|
886
|
+
* @param id - Active resource id mode.
|
|
887
|
+
* @param params - Optional pagination + filter bag.
|
|
888
|
+
* @returns Params object for {@link ApiClient}, or `undefined` when empty.
|
|
889
|
+
*/
|
|
890
|
+
function buildResourceQueryParams(id, params) {
|
|
891
|
+
const { page, size, order, ...rest } = params ?? {};
|
|
892
|
+
const out = {};
|
|
893
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
894
|
+
if (value === null || value === undefined || value === '') {
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
out[key] = value;
|
|
898
|
+
}
|
|
899
|
+
if (id === null) {
|
|
900
|
+
if (page !== undefined) {
|
|
901
|
+
out['page'] = page;
|
|
902
|
+
}
|
|
903
|
+
out['size'] = size ?? DEFAULT_PAGE_SIZE;
|
|
904
|
+
if (order !== undefined) {
|
|
905
|
+
out['order'] = order;
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* In-memory GET cache TTL for reference reads.
|
|
912
|
+
*
|
|
913
|
+
* Paginated lists (`id === null`) are never cached — page contents change.
|
|
914
|
+
* `'all'` and by-id may use a short TTL for stable catalogs.
|
|
915
|
+
*
|
|
916
|
+
* @param id - Active resource id mode.
|
|
917
|
+
* @param ttlMs - Desired TTL for stable reads.
|
|
918
|
+
* @returns TTL to pass to {@link ApiClient}, or `undefined` to skip cache.
|
|
919
|
+
*/
|
|
920
|
+
function resourceCacheTtlMs(id, ttlMs) {
|
|
921
|
+
return id === null ? undefined : ttlMs;
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Map wire `data` according to {@link ResourceId} shape.
|
|
925
|
+
*
|
|
926
|
+
* - `null` / `'all'` → list mapper → `T[]`
|
|
927
|
+
* - `number` → one mapper → `T` (first element if the wire sent an array)
|
|
928
|
+
*
|
|
929
|
+
* @typeParam T - Domain model type.
|
|
930
|
+
* @param id - Active resource id mode.
|
|
931
|
+
* @param raw - Envelope `data` payload.
|
|
932
|
+
* @param mapOne - Mapper for a single record.
|
|
933
|
+
* @param mapMany - Mapper for a list (or single object coerced to list).
|
|
934
|
+
* @returns Mapped payload, or `null` when by-id data is missing.
|
|
935
|
+
* List reads (`null` / `'all'`) return `[]` when `raw` is nullish.
|
|
936
|
+
*/
|
|
937
|
+
function mapResourcePayload(id, raw, mapOne, mapMany) {
|
|
938
|
+
if (raw == null) {
|
|
939
|
+
return typeof id === 'number' ? null : [];
|
|
940
|
+
}
|
|
941
|
+
if (typeof id === 'number') {
|
|
942
|
+
const item = Array.isArray(raw) ? raw[0] : raw;
|
|
943
|
+
return item == null ? null : mapOne(item);
|
|
944
|
+
}
|
|
945
|
+
return mapMany(raw);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Endpoint-agnostic HTTP façade over Angular {@link HttpClient}.
|
|
950
|
+
*
|
|
951
|
+
* Domain services own path strings — this client never hard-codes product
|
|
952
|
+
* routes (except helpers like {@link getResource} that only know conventions).
|
|
953
|
+
*
|
|
954
|
+
* Features:
|
|
955
|
+
* - `responseMode` overloads (`wrapped` | `raw`)
|
|
956
|
+
* - {@link normalize} on every **2xx** body
|
|
957
|
+
* - HTTP failures rethrown as `Error` whose `.message` is already
|
|
958
|
+
* user-facing ({@link formatApiErrorMessage}) — consumers do not need to
|
|
959
|
+
* parse `HttpErrorResponse` bodies
|
|
960
|
+
* - **No automatic retry** — fail fast so UIs can show Select / table /
|
|
961
|
+
* error-state Retry instead of a stuck loading spinner
|
|
962
|
+
* - Optional per-GET TTL cache via `cacheTtlMs`
|
|
963
|
+
*
|
|
964
|
+
* @example
|
|
965
|
+
* ```ts
|
|
966
|
+
* const api = inject(ApiClient);
|
|
967
|
+
* api.get<User>('/users/1').subscribe({
|
|
968
|
+
* next: (res) => {
|
|
969
|
+
* if (res.success) console.log(res.data);
|
|
970
|
+
* else console.error(res.message); // already joined when a validation bag exists
|
|
971
|
+
* },
|
|
972
|
+
* error: (err: Error) => console.error(err.message),
|
|
973
|
+
* });
|
|
974
|
+
* ```
|
|
975
|
+
*/
|
|
976
|
+
/**
|
|
977
|
+
* Endpoint-agnostic HTTP façade over Angular {@link HttpClient}.
|
|
978
|
+
*/
|
|
979
|
+
class ApiClient {
|
|
980
|
+
http = inject(HttpClient);
|
|
981
|
+
config = inject(AFRICANIES_SDK_CONFIG);
|
|
982
|
+
shippingMode = inject(ShippingModeService);
|
|
983
|
+
cache = new HttpResponseCache();
|
|
984
|
+
get(path, options = {}) {
|
|
985
|
+
return this.request('GET', path, undefined, options);
|
|
986
|
+
}
|
|
987
|
+
post(path, body, options = {}) {
|
|
988
|
+
return this.request('POST', path, body, options);
|
|
989
|
+
}
|
|
990
|
+
patch(path, body, options = {}) {
|
|
991
|
+
return this.request('PATCH', path, body, options);
|
|
992
|
+
}
|
|
993
|
+
put(path, body, options = {}) {
|
|
994
|
+
return this.request('PUT', path, body, options);
|
|
995
|
+
}
|
|
996
|
+
delete(path, body, options = {}) {
|
|
997
|
+
return this.request('DELETE', path, body, options);
|
|
998
|
+
}
|
|
999
|
+
getResource(basePath, id, query, options = {}) {
|
|
1000
|
+
const path = buildResourcePath(basePath, id);
|
|
1001
|
+
const params = {
|
|
1002
|
+
mode: resolveRequestShippingMode(options, this.shippingMode.mode()),
|
|
1003
|
+
...(options.params ?? {}),
|
|
1004
|
+
};
|
|
1005
|
+
// Pagination applies only to the paginated-list shape (id === null).
|
|
1006
|
+
// Size always ships — SDK default is 15 unless the caller overrides.
|
|
1007
|
+
if (id === null) {
|
|
1008
|
+
if (query?.page !== undefined) {
|
|
1009
|
+
params['page'] = query.page;
|
|
1010
|
+
}
|
|
1011
|
+
params['size'] = query?.size ?? DEFAULT_PAGE_SIZE;
|
|
1012
|
+
if (query?.order !== undefined) {
|
|
1013
|
+
params['order'] = query.order;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
const { responseMode: _ignored, ...requestOptions } = options;
|
|
1017
|
+
return this.get(path, { ...requestOptions, params });
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Paginated list — {@link ResourceId} `null`.
|
|
1021
|
+
* IDE-friendly alias for `getResource(basePath, null, query)`.
|
|
1022
|
+
*
|
|
1023
|
+
* Page size defaults to {@link DEFAULT_PAGE_SIZE} (`15`) unless
|
|
1024
|
+
* `query.size` is set. Bind `res.pagination` to `africanies-pagination`.
|
|
1025
|
+
*
|
|
1026
|
+
* @typeParam T - Element type of the list.
|
|
1027
|
+
* @param basePath - Resource base path (no trailing id segment).
|
|
1028
|
+
* @param query - Optional page/size/order.
|
|
1029
|
+
*/
|
|
1030
|
+
getResourcePage(basePath, query) {
|
|
1031
|
+
return this.getResource(basePath, null, query);
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Full unpaginated dump — {@link ResourceId} `'all'`.
|
|
1035
|
+
* IDE-friendly alias for `getResource(basePath, 'all')`.
|
|
1036
|
+
*
|
|
1037
|
+
* @typeParam T - Element type of the list.
|
|
1038
|
+
* @param basePath - Resource base path.
|
|
1039
|
+
*/
|
|
1040
|
+
getResourceAll(basePath) {
|
|
1041
|
+
return this.getResource(basePath, 'all');
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Single record — {@link ResourceId} number.
|
|
1045
|
+
* IDE-friendly alias for `getResource(basePath, id)`.
|
|
1046
|
+
*
|
|
1047
|
+
* @typeParam T - Record type.
|
|
1048
|
+
* @param basePath - Resource base path.
|
|
1049
|
+
* @param id - Numeric primary key.
|
|
1050
|
+
*/
|
|
1051
|
+
getResourceById(basePath, id) {
|
|
1052
|
+
return this.getResource(basePath, id);
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Clears the in-memory GET cache (e.g. on logout).
|
|
1056
|
+
*/
|
|
1057
|
+
clearCache() {
|
|
1058
|
+
this.cache.clear();
|
|
1059
|
+
}
|
|
1060
|
+
buildHttpContext(options, method) {
|
|
1061
|
+
return resolveApiRequestContext(this.config.httpToasts, options, method);
|
|
1062
|
+
}
|
|
1063
|
+
request(method, path, body, options) {
|
|
1064
|
+
const responseMode = options.responseMode ?? 'wrapped';
|
|
1065
|
+
const url = this.resolveUrl(path);
|
|
1066
|
+
const hasBody = method === 'POST' ||
|
|
1067
|
+
method === 'PUT' ||
|
|
1068
|
+
method === 'PATCH' ||
|
|
1069
|
+
(method === 'DELETE' && body !== undefined && body !== null);
|
|
1070
|
+
const isFormData = typeof FormData !== 'undefined' && body instanceof FormData;
|
|
1071
|
+
// FormData must not send application/json — the browser sets multipart + boundary.
|
|
1072
|
+
let headers = this.buildHeaders(options.headers, hasBody && !isFormData);
|
|
1073
|
+
if (isFormData && headers.has('Content-Type')) {
|
|
1074
|
+
headers = headers.delete('Content-Type');
|
|
1075
|
+
}
|
|
1076
|
+
const params = this.buildParams(options.params);
|
|
1077
|
+
const context = this.buildHttpContext(options, method);
|
|
1078
|
+
const cacheKey = method === 'GET' && options.cacheTtlMs != null
|
|
1079
|
+
? `${method} ${url}?${params.toString()}`
|
|
1080
|
+
: null;
|
|
1081
|
+
if (cacheKey) {
|
|
1082
|
+
const hit = this.cache.get(cacheKey);
|
|
1083
|
+
if (hit !== null) {
|
|
1084
|
+
return of(this.unwrap(hit, responseMode));
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
let req$;
|
|
1088
|
+
const httpOpts = context ? { headers, params, context } : { headers, params };
|
|
1089
|
+
switch (method) {
|
|
1090
|
+
case 'GET':
|
|
1091
|
+
req$ = this.http.get(url, httpOpts);
|
|
1092
|
+
break;
|
|
1093
|
+
case 'POST':
|
|
1094
|
+
req$ = this.http.post(url, body, httpOpts);
|
|
1095
|
+
break;
|
|
1096
|
+
case 'PUT':
|
|
1097
|
+
req$ = this.http.put(url, body, httpOpts);
|
|
1098
|
+
break;
|
|
1099
|
+
case 'PATCH':
|
|
1100
|
+
req$ = this.http.patch(url, body, httpOpts);
|
|
1101
|
+
break;
|
|
1102
|
+
case 'DELETE':
|
|
1103
|
+
req$ = this.http.delete(url, hasBody ? { ...httpOpts, body } : httpOpts);
|
|
1104
|
+
break;
|
|
1105
|
+
}
|
|
1106
|
+
// No automatic GET retry — admin shells own Retry via UI
|
|
1107
|
+
// (`africanies-select` showRetry, table error→refreshClick, error-state).
|
|
1108
|
+
if (this.config.timeout != null) {
|
|
1109
|
+
req$ = req$.pipe(timeout({
|
|
1110
|
+
each: this.config.timeout,
|
|
1111
|
+
with: () => throwError(() => new Error(`AFRICANIES API request timed out after ${this.config.timeout}ms: ${method} ${url}`)),
|
|
1112
|
+
}));
|
|
1113
|
+
}
|
|
1114
|
+
return req$.pipe(map$1((raw) => normalize(raw)), tap$1((envelope) => {
|
|
1115
|
+
if (cacheKey && options.cacheTtlMs != null) {
|
|
1116
|
+
this.cache.set(cacheKey, envelope, options.cacheTtlMs);
|
|
1117
|
+
}
|
|
1118
|
+
}), map$1((envelope) => this.unwrap(envelope, responseMode)), catchError((err) => throwError(() => new Error(formatApiErrorMessage(err)))));
|
|
1119
|
+
}
|
|
1120
|
+
unwrap(envelope, responseMode) {
|
|
1121
|
+
if (responseMode === 'raw') {
|
|
1122
|
+
// Callers opting into raw want the payload; null stays null-compatible via cast.
|
|
1123
|
+
return envelope.data;
|
|
1124
|
+
}
|
|
1125
|
+
return envelope;
|
|
1126
|
+
}
|
|
1127
|
+
resolveUrl(path) {
|
|
1128
|
+
if (/^https?:\/\//i.test(path)) {
|
|
1129
|
+
return path;
|
|
1130
|
+
}
|
|
1131
|
+
const base = this.config.baseUrl.replace(/\/+$/, '');
|
|
1132
|
+
const suffix = path.startsWith('/') ? path : `/${path}`;
|
|
1133
|
+
return `${base}${suffix}`;
|
|
1134
|
+
}
|
|
1135
|
+
buildHeaders(extra, withJsonContentType = false) {
|
|
1136
|
+
let headers = new HttpHeaders({
|
|
1137
|
+
Accept: 'application/json',
|
|
1138
|
+
...(this.config.defaultHeaders ?? {}),
|
|
1139
|
+
...(extra ?? {}),
|
|
1140
|
+
});
|
|
1141
|
+
// Only set JSON content-type when sending a body; avoid spurious GET headers.
|
|
1142
|
+
if (withJsonContentType && !headers.has('Content-Type')) {
|
|
1143
|
+
headers = headers.set('Content-Type', 'application/json');
|
|
1144
|
+
}
|
|
1145
|
+
return headers;
|
|
1146
|
+
}
|
|
1147
|
+
buildParams(params) {
|
|
1148
|
+
let httpParams = new HttpParams();
|
|
1149
|
+
if (!params) {
|
|
1150
|
+
return httpParams;
|
|
1151
|
+
}
|
|
1152
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1153
|
+
if (value === null || value === undefined || value === '') {
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
httpParams = httpParams.set(key, String(value));
|
|
1157
|
+
}
|
|
1158
|
+
return httpParams;
|
|
1159
|
+
}
|
|
1160
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ApiClient, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1161
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ApiClient, providedIn: 'root' });
|
|
1162
|
+
}
|
|
1163
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ApiClient, decorators: [{
|
|
1164
|
+
type: Injectable,
|
|
1165
|
+
args: [{ providedIn: 'root' }]
|
|
1166
|
+
}] });
|
|
1167
|
+
|
|
1168
|
+
/** Public mode-config endpoint path (relative to {@link AfricaniesSdkConfig.baseUrl}). */
|
|
1169
|
+
const MODE_CONFIG_PATH = '/public/mode/config';
|
|
1170
|
+
/** Fallback region when the wire omits a required branch/key. */
|
|
1171
|
+
const DEFAULT_REGION = {
|
|
1172
|
+
dimension_unit: 'cm',
|
|
1173
|
+
mass_unit: 'KG',
|
|
1174
|
+
currency: 'NGN',
|
|
1175
|
+
currency_symbol: '',
|
|
1176
|
+
};
|
|
1177
|
+
function asDimensionUnit(value) {
|
|
1178
|
+
return value === 'inches' ? 'inches' : 'cm';
|
|
1179
|
+
}
|
|
1180
|
+
function asMassUnit(value) {
|
|
1181
|
+
return value === 'LBS' ? 'LBS' : 'KG';
|
|
1182
|
+
}
|
|
1183
|
+
function asCurrency(value) {
|
|
1184
|
+
return value === 'USD' ? 'USD' : 'NGN';
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Map a region object into {@link ModeRegionConfigModel} (snake_case).
|
|
1188
|
+
* @param raw
|
|
1189
|
+
*/
|
|
1190
|
+
function mapRegionConfig(raw) {
|
|
1191
|
+
const record = asRecord(raw) ?? {};
|
|
1192
|
+
return {
|
|
1193
|
+
dimension_unit: asDimensionUnit(record['dimension_unit'] ?? record['dimensionUnit']),
|
|
1194
|
+
mass_unit: asMassUnit(record['mass_unit'] ?? record['massUnit']),
|
|
1195
|
+
currency: asCurrency(record['currency']),
|
|
1196
|
+
currency_symbol: asString(record['currency_symbol'] ?? record['currencySymbol']),
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Map every region entry under a mode branch (`default`, `ng`, `us`, …).
|
|
1201
|
+
* @param modeRaw
|
|
1202
|
+
*/
|
|
1203
|
+
function mapModeRegions(modeRaw) {
|
|
1204
|
+
const mode = asRecord(modeRaw) ?? {};
|
|
1205
|
+
const out = {};
|
|
1206
|
+
for (const [key, value] of Object.entries(mode)) {
|
|
1207
|
+
out[key] = mapRegionConfig(value);
|
|
1208
|
+
}
|
|
1209
|
+
return out;
|
|
1210
|
+
}
|
|
1211
|
+
function regionOrDefault(regions, key) {
|
|
1212
|
+
return regions[key] ?? regions['default'] ?? { ...DEFAULT_REGION };
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Deep-map mode config preserving snake_case region fields.
|
|
1216
|
+
* @param raw
|
|
1217
|
+
*/
|
|
1218
|
+
function mapModeConfigData(raw) {
|
|
1219
|
+
const record = asRecord(raw) ?? {};
|
|
1220
|
+
const sfnRegions = mapModeRegions(record['sfn']);
|
|
1221
|
+
const stnRegions = mapModeRegions(record['stn']);
|
|
1222
|
+
const sfn = {
|
|
1223
|
+
default: regionOrDefault(sfnRegions, 'default'),
|
|
1224
|
+
ng: regionOrDefault(sfnRegions, 'ng'),
|
|
1225
|
+
};
|
|
1226
|
+
const stn = {
|
|
1227
|
+
default: regionOrDefault(stnRegions, 'default'),
|
|
1228
|
+
us: regionOrDefault(stnRegions, 'us'),
|
|
1229
|
+
cn: regionOrDefault(stnRegions, 'cn'),
|
|
1230
|
+
gb: regionOrDefault(stnRegions, 'gb'),
|
|
1231
|
+
};
|
|
1232
|
+
return { sfn, stn };
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Minimal runtime guard for hydrated storage payloads.
|
|
1236
|
+
* @param value
|
|
1237
|
+
*/
|
|
1238
|
+
function isModeConfigData(value) {
|
|
1239
|
+
if (value === null || typeof value !== 'object') {
|
|
1240
|
+
return false;
|
|
1241
|
+
}
|
|
1242
|
+
const record = value;
|
|
1243
|
+
const sfn = record['sfn'];
|
|
1244
|
+
const stn = record['stn'];
|
|
1245
|
+
if (sfn === null || typeof sfn !== 'object' || Array.isArray(sfn)) {
|
|
1246
|
+
return false;
|
|
1247
|
+
}
|
|
1248
|
+
if (stn === null || typeof stn !== 'object' || Array.isArray(stn)) {
|
|
1249
|
+
return false;
|
|
1250
|
+
}
|
|
1251
|
+
const sfnRecord = sfn;
|
|
1252
|
+
const stnRecord = stn;
|
|
1253
|
+
return (sfnRecord['default'] != null &&
|
|
1254
|
+
typeof sfnRecord['default'] === 'object' &&
|
|
1255
|
+
stnRecord['default'] != null &&
|
|
1256
|
+
typeof stnRecord['default'] === 'object');
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Resolve currency and measurement units for a country within a shipping mode.
|
|
1260
|
+
* @param config
|
|
1261
|
+
* @param mode
|
|
1262
|
+
* @param countryCode
|
|
1263
|
+
*/
|
|
1264
|
+
function resolveModeRegionConfig(config, mode, countryCode) {
|
|
1265
|
+
const regions = mode === 'sfn' ? config?.sfn : config?.stn;
|
|
1266
|
+
const keyed = (regions ?? {});
|
|
1267
|
+
if (countryCode != null && countryCode !== '') {
|
|
1268
|
+
const key = asString(countryCode).toLowerCase();
|
|
1269
|
+
if (key !== 'default') {
|
|
1270
|
+
const region = keyed[key];
|
|
1271
|
+
if (region != null) {
|
|
1272
|
+
return region;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return keyed['default'] ?? { ...DEFAULT_REGION };
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Source of truth for region currency and measurement units (STN / SFN).
|
|
1281
|
+
*
|
|
1282
|
+
* On startup (via {@link provideModeConfig}), loads
|
|
1283
|
+
* `GET /public/mode/config`, normalizes snake_case wire fields once, and
|
|
1284
|
+
* persists the mapped record to storage. {@link getRegionConfig} reads that
|
|
1285
|
+
* saved record and resolves by country code + active shipping mode.
|
|
1286
|
+
*
|
|
1287
|
+
* @example
|
|
1288
|
+
* ```ts
|
|
1289
|
+
* // app.config.ts
|
|
1290
|
+
* providers: [
|
|
1291
|
+
* provideAfricaniesSdk({ baseUrl: 'https://test-api-export.africaniestest.com/api' }),
|
|
1292
|
+
* provideHttpClient(withInterceptors([shipmentModeInterceptor])),
|
|
1293
|
+
* provideModeConfig(),
|
|
1294
|
+
* ];
|
|
1295
|
+
*
|
|
1296
|
+
* // feature — format for shipment origin country
|
|
1297
|
+
* const modeConfig = inject(ModeConfigService);
|
|
1298
|
+
* const region = modeConfig.getRegionConfig('us');
|
|
1299
|
+
* if (region) {
|
|
1300
|
+
* console.log(region.currency_symbol, region.mass_unit);
|
|
1301
|
+
* }
|
|
1302
|
+
* ```
|
|
1303
|
+
*/
|
|
1304
|
+
/**
|
|
1305
|
+
* Source of truth for region currency and measurement units (STN / SFN).
|
|
1306
|
+
*/
|
|
1307
|
+
class ModeConfigService {
|
|
1308
|
+
api = inject(ApiClient);
|
|
1309
|
+
storage = inject(STORAGE_TOKEN);
|
|
1310
|
+
shippingMode = inject(ShippingModeService);
|
|
1311
|
+
_config = signal(null, /* @ts-ignore */
|
|
1312
|
+
...(ngDevMode ? [{ debugName: "_config" }] : /* istanbul ignore next */ []));
|
|
1313
|
+
_loading = signal(false, /* @ts-ignore */
|
|
1314
|
+
...(ngDevMode ? [{ debugName: "_loading" }] : /* istanbul ignore next */ []));
|
|
1315
|
+
/** Latest mode-config record (storage hydrate or last successful fetch). */
|
|
1316
|
+
config = this._config.asReadonly();
|
|
1317
|
+
/** `true` while {@link loadConfig} is in flight. */
|
|
1318
|
+
loading = this._loading.asReadonly();
|
|
1319
|
+
constructor() {
|
|
1320
|
+
this.hydrateFromStorage();
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Fetches mode config from the server, updates {@link config}, and persists.
|
|
1324
|
+
*
|
|
1325
|
+
* @returns Normalized API envelope; errors propagate to subscribers.
|
|
1326
|
+
*/
|
|
1327
|
+
loadConfig() {
|
|
1328
|
+
this._loading.set(true);
|
|
1329
|
+
return this.api.get(MODE_CONFIG_PATH).pipe(tap$1((res) => {
|
|
1330
|
+
if (res.success && res.data !== null) {
|
|
1331
|
+
this.saveRecord(mapModeConfigData(res.data));
|
|
1332
|
+
}
|
|
1333
|
+
}), finalize(() => {
|
|
1334
|
+
this._loading.set(false);
|
|
1335
|
+
}));
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Region units and currency for a country code under the active (or given) mode.
|
|
1339
|
+
*
|
|
1340
|
+
* @param countryCode - e.g. `'ng'`, `'us'`, `'cn'` — unknown keys use `default`.
|
|
1341
|
+
* @param appType - Defaults to {@link ShippingModeService.mode}.
|
|
1342
|
+
* @returns Resolved region, or `null` before the first load/hydrate.
|
|
1343
|
+
*/
|
|
1344
|
+
getRegionConfig(countryCode, appType) {
|
|
1345
|
+
const config = this._config();
|
|
1346
|
+
if (config === null) {
|
|
1347
|
+
return null;
|
|
1348
|
+
}
|
|
1349
|
+
const mode = appType ?? this.shippingMode.mode();
|
|
1350
|
+
return resolveModeRegionConfig(config, mode, countryCode);
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Replace the in-memory record and persist — used after {@link loadConfig}.
|
|
1354
|
+
* @param config
|
|
1355
|
+
*/
|
|
1356
|
+
saveRecord(config) {
|
|
1357
|
+
this._config.set(config);
|
|
1358
|
+
this.storage.set(AFRICANIES_MODE_CONFIG_KEY, config);
|
|
1359
|
+
}
|
|
1360
|
+
/** Restore the last saved server record so region lookups work offline. */
|
|
1361
|
+
hydrateFromStorage() {
|
|
1362
|
+
const stored = this.storage.get(AFRICANIES_MODE_CONFIG_KEY);
|
|
1363
|
+
if (isModeConfigData(stored)) {
|
|
1364
|
+
this._config.set(stored);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ModeConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1368
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ModeConfigService, providedIn: 'root' });
|
|
1369
|
+
}
|
|
1370
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ModeConfigService, decorators: [{
|
|
1371
|
+
type: Injectable,
|
|
1372
|
+
args: [{ providedIn: 'root' }]
|
|
1373
|
+
}], ctorParameters: () => [] });
|
|
1374
|
+
|
|
1375
|
+
/**
|
|
1376
|
+
* Fetches `/public/mode/config` on app startup and hydrates {@link ModeConfigService}.
|
|
1377
|
+
*
|
|
1378
|
+
* Failures do not block bootstrap — cached storage (if any) remains usable.
|
|
1379
|
+
* Pair with {@link provideAfricaniesSdk} and a real {@link AfricaniesSdkConfig.baseUrl}.
|
|
1380
|
+
*
|
|
1381
|
+
* @returns Environment providers that run the mode-config initializer.
|
|
1382
|
+
*/
|
|
1383
|
+
function provideModeConfig() {
|
|
1384
|
+
return makeEnvironmentProviders([
|
|
1385
|
+
provideAppInitializer(() => {
|
|
1386
|
+
const modeConfig = inject(ModeConfigService);
|
|
1387
|
+
return firstValueFrom(modeConfig.loadConfig().pipe(catchError(() => of(null))));
|
|
1388
|
+
}),
|
|
1389
|
+
]);
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
/**
|
|
1393
|
+
* Registers SDK configuration and ensures browser storage is available.
|
|
1394
|
+
*
|
|
1395
|
+
* Also calls {@link provideLocalStorage} so theme / mode-config / auth-token
|
|
1396
|
+
* persistence works out of the box. {@link ShippingModeService} always uses
|
|
1397
|
+
* {@link SessionStorageService} so each tab can hold its own STN/SFN mode.
|
|
1398
|
+
* When {@link AfricaniesSdkConfig.loadModeConfig} is not `false`, {@link provideModeConfig}
|
|
1399
|
+
* runs on startup.
|
|
1400
|
+
*
|
|
1401
|
+
* @param config - API origin and optional timeout / default headers.
|
|
1402
|
+
* @returns Environment providers for `app.config.ts`.
|
|
1403
|
+
*
|
|
1404
|
+
* @example
|
|
1405
|
+
* ```ts
|
|
1406
|
+
* // app.config.ts
|
|
1407
|
+
* import { ApplicationConfig } from '@angular/core';
|
|
1408
|
+
* import {
|
|
1409
|
+
* provideAfricaniesSdk,
|
|
1410
|
+
* provideAfricaniesHttpClient,
|
|
1411
|
+
* } from '@africanies/africanies-core';
|
|
1412
|
+
* import { provideSessionStorage } from '@africanies/africanies-storage';
|
|
1413
|
+
*
|
|
1414
|
+
* export const appConfig: ApplicationConfig = {
|
|
1415
|
+
* providers: [
|
|
1416
|
+
* provideAfricaniesSdk({
|
|
1417
|
+
* baseUrl: import.meta.env['NG_APP_API_URL'],
|
|
1418
|
+
* timeout: 30_000,
|
|
1419
|
+
* }),
|
|
1420
|
+
* // Optional: override the localStorage default registered above
|
|
1421
|
+
* // provideSessionStorage(),
|
|
1422
|
+
* provideAfricaniesHttpClient(),
|
|
1423
|
+
* // Or with app interceptors:
|
|
1424
|
+
* // provideAfricaniesHttpClient({ interceptors: [loggingInterceptor] }),
|
|
1425
|
+
* ],
|
|
1426
|
+
* };
|
|
1427
|
+
* ```
|
|
1428
|
+
*/
|
|
1429
|
+
function provideAfricaniesSdk(config) {
|
|
1430
|
+
return makeEnvironmentProviders([
|
|
1431
|
+
{ provide: AFRICANIES_SDK_CONFIG, useValue: config },
|
|
1432
|
+
provideLocalStorage(),
|
|
1433
|
+
...(config.loadModeConfig !== false ? [provideModeConfig()] : []),
|
|
1434
|
+
]);
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
/** Forgot-password path (relative to {@link AfricaniesSdkConfig.baseUrl}). */
|
|
1438
|
+
const AUTH_FORGOT_PASSWORD_PATH = '/auth/forgot/password';
|
|
1439
|
+
|
|
1440
|
+
/**
|
|
1441
|
+
* Unauthenticated auth endpoints (`POST /auth/…`).
|
|
1442
|
+
*
|
|
1443
|
+
* **Forgot password is email-only.** `forgot()` POSTs `{ email }`; the
|
|
1444
|
+
* backend emails a reset link. This product UI never collects a new password
|
|
1445
|
+
* for that link (no token in the route, `/onboarding/reset-password` is
|
|
1446
|
+
* **not** that flow).
|
|
1447
|
+
*
|
|
1448
|
+
* `/onboarding/reset-password` is first login with a default password: after
|
|
1449
|
+
* login, if `user.default_password` is set, the host sends the user there to
|
|
1450
|
+
* change current → new via {@link UserService.changePassword}.
|
|
1451
|
+
*
|
|
1452
|
+
* Token persistence stays on {@link AuthTokenService}. These calls do not
|
|
1453
|
+
* require a bearer token — the interceptor omits `Authorization` when none
|
|
1454
|
+
* is stored. Admin user/partner screens reuse {@link forgot} with that
|
|
1455
|
+
* user’s email.
|
|
1456
|
+
*
|
|
1457
|
+
* @example
|
|
1458
|
+
* ```ts
|
|
1459
|
+
* const authApi = inject(AuthService);
|
|
1460
|
+
*
|
|
1461
|
+
* authApi.forgot('user@example.com').subscribe((res) => {
|
|
1462
|
+
* if (res.success) {
|
|
1463
|
+
* // Show res.message, then “Reset completed? Login here”.
|
|
1464
|
+
* }
|
|
1465
|
+
* });
|
|
1466
|
+
* ```
|
|
1467
|
+
*/
|
|
1468
|
+
/**
|
|
1469
|
+
* Unauthenticated auth endpoints (`POST /auth/…`).
|
|
1470
|
+
*/
|
|
1471
|
+
class AuthService {
|
|
1472
|
+
api = inject(ApiClient);
|
|
1473
|
+
/**
|
|
1474
|
+
* Request a password-reset email (`POST /auth/forgot/password`).
|
|
1475
|
+
*
|
|
1476
|
+
* Enable submit only when the address looks valid. Wire `data` is an empty
|
|
1477
|
+
* array on success — use {@link ApiResponseModel.message} for the banner.
|
|
1478
|
+
*
|
|
1479
|
+
* @param email - Registered account email.
|
|
1480
|
+
* @returns Normalized envelope (`data` is typically `[]`).
|
|
1481
|
+
*/
|
|
1482
|
+
forgot(email) {
|
|
1483
|
+
return this.api.post(AUTH_FORGOT_PASSWORD_PATH, { email: asString(email).trim() });
|
|
1484
|
+
}
|
|
1485
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1486
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AuthService, providedIn: 'root' });
|
|
1487
|
+
}
|
|
1488
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AuthService, decorators: [{
|
|
1489
|
+
type: Injectable,
|
|
1490
|
+
args: [{ providedIn: 'root' }]
|
|
1491
|
+
}] });
|
|
1492
|
+
|
|
1493
|
+
/**
|
|
1494
|
+
* Holds the bearer access token used by {@link authInterceptor}.
|
|
1495
|
+
*
|
|
1496
|
+
* After login/register in the host app, call {@link set}. The SDK persists the
|
|
1497
|
+
* token via {@link STORAGE_TOKEN} and attaches `Authorization: Bearer …` on
|
|
1498
|
+
* outbound HTTP. On logout, call {@link UserService.logoutFromAllSessions}
|
|
1499
|
+
* while the token is still set, then {@link clear} (also clears the GET cache).
|
|
1500
|
+
*
|
|
1501
|
+
* @example
|
|
1502
|
+
* ```ts
|
|
1503
|
+
* const auth = inject(AuthTokenService);
|
|
1504
|
+
* const users = inject(UserService);
|
|
1505
|
+
*
|
|
1506
|
+
* // after login
|
|
1507
|
+
* auth.set(res.access_token);
|
|
1508
|
+
*
|
|
1509
|
+
* // later — UserService.me() is authenticated automatically
|
|
1510
|
+
* // on logout — POST while the token is still set, then drop it
|
|
1511
|
+
* users.logoutFromAllSessions().subscribe({
|
|
1512
|
+
* next: () => auth.clear(),
|
|
1513
|
+
* error: () => auth.clear(),
|
|
1514
|
+
* });
|
|
1515
|
+
* ```
|
|
1516
|
+
*/
|
|
1517
|
+
/**
|
|
1518
|
+
* Holds the bearer access token used by {@link authInterceptor}.
|
|
1519
|
+
*/
|
|
1520
|
+
class AuthTokenService {
|
|
1521
|
+
storage = inject(STORAGE_TOKEN);
|
|
1522
|
+
api = inject(ApiClient);
|
|
1523
|
+
_token = signal(this.readInitialToken(), /* @ts-ignore */
|
|
1524
|
+
...(ngDevMode ? [{ debugName: "_token" }] : /* istanbul ignore next */ []));
|
|
1525
|
+
/** Read-only view of the current access token. */
|
|
1526
|
+
token = this._token.asReadonly();
|
|
1527
|
+
/**
|
|
1528
|
+
* Current access token, or `null` when logged out / unset.
|
|
1529
|
+
*/
|
|
1530
|
+
get() {
|
|
1531
|
+
return this._token();
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* Persist and activate an access token from login/register.
|
|
1535
|
+
*
|
|
1536
|
+
* @param accessToken - Bearer token string (whitespace-only is treated as clear).
|
|
1537
|
+
*/
|
|
1538
|
+
set(accessToken) {
|
|
1539
|
+
const trimmed = asString(accessToken).trim();
|
|
1540
|
+
if (trimmed === '') {
|
|
1541
|
+
this.clear();
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
this._token.set(trimmed);
|
|
1545
|
+
this.storage.set(AFRICANIES_ACCESS_TOKEN_KEY, trimmed);
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* Remove the token from memory and storage, and clear the HTTP GET cache.
|
|
1549
|
+
*/
|
|
1550
|
+
clear() {
|
|
1551
|
+
this._token.set(null);
|
|
1552
|
+
this.storage.remove(AFRICANIES_ACCESS_TOKEN_KEY);
|
|
1553
|
+
this.api.clearCache();
|
|
1554
|
+
}
|
|
1555
|
+
readInitialToken() {
|
|
1556
|
+
const stored = this.storage.get(AFRICANIES_ACCESS_TOKEN_KEY);
|
|
1557
|
+
if (typeof stored !== 'string') {
|
|
1558
|
+
return null;
|
|
1559
|
+
}
|
|
1560
|
+
const trimmed = stored.trim();
|
|
1561
|
+
return trimmed === '' ? null : trimmed;
|
|
1562
|
+
}
|
|
1563
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AuthTokenService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1564
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AuthTokenService, providedIn: 'root' });
|
|
1565
|
+
}
|
|
1566
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AuthTokenService, decorators: [{
|
|
1567
|
+
type: Injectable,
|
|
1568
|
+
args: [{ providedIn: 'root' }]
|
|
1569
|
+
}] });
|
|
1570
|
+
|
|
1571
|
+
/**
|
|
1572
|
+
* Enable forgot-password submit when the address looks like an email.
|
|
1573
|
+
*
|
|
1574
|
+
* Intentionally loose (`local@host.tld`) — the API owns uniqueness and
|
|
1575
|
+
* deliverability. Empty / whitespace-only values fail.
|
|
1576
|
+
*
|
|
1577
|
+
* @param value - Raw field value.
|
|
1578
|
+
* @returns Whether submit should be enabled.
|
|
1579
|
+
*/
|
|
1580
|
+
function isValidEmail(value) {
|
|
1581
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(asString(value).trim());
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
/**
|
|
1585
|
+
* Attaches the current shipping mode as an `x-shipment-mode` header.
|
|
1586
|
+
*
|
|
1587
|
+
* Uses {@link SHIPPING_MODE_OVERRIDE} when the request was tagged (via
|
|
1588
|
+
* {@link ApiRequestOptions.shippingMode} or {@link withShippingMode});
|
|
1589
|
+
* otherwise {@link ShippingModeService.mode}. The tab mode is never mutated.
|
|
1590
|
+
*
|
|
1591
|
+
* Some backends also expect `mode` as a query param (see {@link ApiClient.getResource});
|
|
1592
|
+
* this interceptor covers the header half so domain calls stay consistent
|
|
1593
|
+
* without each service remembering the header name.
|
|
1594
|
+
*
|
|
1595
|
+
* Prefer {@link provideAfricaniesHttpClient}, which registers this interceptor by
|
|
1596
|
+
* default. {@link ShippingModeService} is `providedIn: 'root'` and needs no
|
|
1597
|
+
* extra provider.
|
|
1598
|
+
*
|
|
1599
|
+
* @param req - Outgoing request.
|
|
1600
|
+
* @param next - Next handler in the interceptor chain.
|
|
1601
|
+
* @returns The downstream observable for the cloned request.
|
|
1602
|
+
* @example
|
|
1603
|
+
* ```ts
|
|
1604
|
+
* provideAfricaniesSdk({ baseUrl: 'https://api.example.com' }),
|
|
1605
|
+
* provideAfricaniesHttpClient(),
|
|
1606
|
+
*
|
|
1607
|
+
* api.post('/claim', body, { shippingMode: 'stn' });
|
|
1608
|
+
* ```
|
|
1609
|
+
*/
|
|
1610
|
+
const shipmentModeInterceptor = (req, next) => {
|
|
1611
|
+
const override = req.context.get(SHIPPING_MODE_OVERRIDE);
|
|
1612
|
+
const mode = override ?? inject(ShippingModeService).mode();
|
|
1613
|
+
return next(req.clone({
|
|
1614
|
+
setHeaders: { 'x-shipment-mode': mode },
|
|
1615
|
+
}));
|
|
1616
|
+
};
|
|
1617
|
+
|
|
1618
|
+
/**
|
|
1619
|
+
* Functional interceptor that sets `Authorization: Bearer <token>` when
|
|
1620
|
+
* {@link AuthTokenService} has an access token (from {@link AuthTokenService.set}).
|
|
1621
|
+
*
|
|
1622
|
+
* Compose via {@link provideAfricaniesHttpClient} (preferred) or pass explicitly to
|
|
1623
|
+
* `provideHttpClient(withInterceptors([...]))`.
|
|
1624
|
+
* @param req
|
|
1625
|
+
* @param next
|
|
1626
|
+
*/
|
|
1627
|
+
const authInterceptor = (req, next) => {
|
|
1628
|
+
const token = inject(AuthTokenService).get();
|
|
1629
|
+
if (token === null || token === '') {
|
|
1630
|
+
return next(req);
|
|
1631
|
+
}
|
|
1632
|
+
return next(req.clone({
|
|
1633
|
+
setHeaders: { Authorization: `Bearer ${token}` },
|
|
1634
|
+
}));
|
|
1635
|
+
};
|
|
1636
|
+
|
|
1637
|
+
/**
|
|
1638
|
+
* Shows success / error toasts for requests tagged with {@link withToast}.
|
|
1639
|
+
*
|
|
1640
|
+
* Requires {@link AFRICANIES_HTTP_TOAST} (from `provideAfricaniesToasts` in `@africanies/africanies-ui`).
|
|
1641
|
+
* Untagged requests never toast. Missing handler → silent no-op.
|
|
1642
|
+
* @param req - Outgoing HTTP request.
|
|
1643
|
+
* @param next - Next interceptor handler.
|
|
1644
|
+
* @returns Observable for the HTTP response stream.
|
|
1645
|
+
*/
|
|
1646
|
+
const httpToastInterceptor = (req, next) => {
|
|
1647
|
+
const options = req.context.get(TOAST_HTTP_OPTIONS);
|
|
1648
|
+
if (!options) {
|
|
1649
|
+
return next(req);
|
|
1650
|
+
}
|
|
1651
|
+
const toast = inject(AFRICANIES_HTTP_TOAST, { optional: true });
|
|
1652
|
+
if (!toast) {
|
|
1653
|
+
return next(req);
|
|
1654
|
+
}
|
|
1655
|
+
return next(req).pipe(tap$1(() => {
|
|
1656
|
+
if (options.success) {
|
|
1657
|
+
toast.success(options.successMessage ?? 'Done');
|
|
1658
|
+
}
|
|
1659
|
+
}), catchError((err) => {
|
|
1660
|
+
if (options.error) {
|
|
1661
|
+
toast.error(options.errorMessage ?? defaultHttpErrorMessage(err));
|
|
1662
|
+
}
|
|
1663
|
+
return throwError(() => err);
|
|
1664
|
+
}));
|
|
1665
|
+
};
|
|
1666
|
+
/**
|
|
1667
|
+
* @param err - Failure from HttpClient.
|
|
1668
|
+
* @returns User-facing copy (joined field errors when the body is a validation bag).
|
|
1669
|
+
*/
|
|
1670
|
+
function defaultHttpErrorMessage(err) {
|
|
1671
|
+
return formatApiErrorMessage(err);
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
/**
|
|
1675
|
+
* Deep-map arbitrary JSON into {@link ApiJsonValue} with no `undefined`.
|
|
1676
|
+
*
|
|
1677
|
+
* - `undefined` / unsupported types → `null`
|
|
1678
|
+
* - Arrays → every element mapped (never sparse/`undefined` slots)
|
|
1679
|
+
* - Objects → every own key mapped to {@link ApiJsonValue}
|
|
1680
|
+
*
|
|
1681
|
+
* @param raw - Wire value (may be missing or malformed).
|
|
1682
|
+
* @returns Null-safe JSON tree, or `null` when `raw` is nullish/unusable.
|
|
1683
|
+
*/
|
|
1684
|
+
function mapApiJsonValue(raw) {
|
|
1685
|
+
if (raw === null || raw === undefined) {
|
|
1686
|
+
return null;
|
|
1687
|
+
}
|
|
1688
|
+
if (typeof raw === 'string' || typeof raw === 'boolean') {
|
|
1689
|
+
return raw;
|
|
1690
|
+
}
|
|
1691
|
+
if (typeof raw === 'number') {
|
|
1692
|
+
return Number.isFinite(raw) ? raw : null;
|
|
1693
|
+
}
|
|
1694
|
+
if (Array.isArray(raw)) {
|
|
1695
|
+
return raw.map((entry) => mapApiJsonValue(entry) ?? null);
|
|
1696
|
+
}
|
|
1697
|
+
const record = asRecord(raw);
|
|
1698
|
+
if (record === null) {
|
|
1699
|
+
return null;
|
|
1700
|
+
}
|
|
1701
|
+
const out = {};
|
|
1702
|
+
for (const [key, value] of Object.entries(record)) {
|
|
1703
|
+
out[key] = mapApiJsonValue(value) ?? null;
|
|
1704
|
+
}
|
|
1705
|
+
return out;
|
|
1706
|
+
}
|
|
1707
|
+
/**
|
|
1708
|
+
* Map a wire list into a null-safe {@link ApiJsonValue} array.
|
|
1709
|
+
* Non-arrays become `[]`.
|
|
1710
|
+
* @param raw - Candidate list.
|
|
1711
|
+
* @returns Mapped array (never null/undefined).
|
|
1712
|
+
*/
|
|
1713
|
+
function mapApiJsonList(raw) {
|
|
1714
|
+
return asArray(raw).map((entry) => mapApiJsonValue(entry) ?? null);
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
/**
|
|
1718
|
+
* Registers `HttpClient` with AFRICANIES default interceptors baked in.
|
|
1719
|
+
*
|
|
1720
|
+
* Defaults (in order):
|
|
1721
|
+
* 1. {@link shipmentModeInterceptor} — `x-shipment-mode`
|
|
1722
|
+
* 2. {@link authInterceptor} — `Authorization` when {@link AuthTokenService} has a token
|
|
1723
|
+
* 3. {@link httpToastInterceptor} — toasts for requests tagged with {@link withToast}
|
|
1724
|
+
* (no-op until `provideAfricaniesToasts()` registers {@link AFRICANIES_HTTP_TOAST})
|
|
1725
|
+
* 4. Any {@link AfricaniesHttpClientOptions.interceptors} from the host
|
|
1726
|
+
*
|
|
1727
|
+
* @param options - Optional extra interceptors.
|
|
1728
|
+
* @param features - Extra `provideHttpClient` features (`withFetch`, etc.).
|
|
1729
|
+
* @returns Environment providers for `app.config.ts`.
|
|
1730
|
+
*
|
|
1731
|
+
* @example
|
|
1732
|
+
* ```ts
|
|
1733
|
+
* provideAfricaniesSdk({ baseUrl: 'https://api.example.com' }),
|
|
1734
|
+
* provideAfricaniesHttpClient(),
|
|
1735
|
+
* provideAfricaniesToasts(), // from @africanies/africanies-ui — enables HTTP toasts
|
|
1736
|
+
* ```
|
|
1737
|
+
*/
|
|
1738
|
+
function provideAfricaniesHttpClient(options, ...features) {
|
|
1739
|
+
const extras = options?.interceptors ?? [];
|
|
1740
|
+
return provideHttpClient(withInterceptors([
|
|
1741
|
+
shipmentModeInterceptor,
|
|
1742
|
+
authInterceptor,
|
|
1743
|
+
httpToastInterceptor,
|
|
1744
|
+
...extras,
|
|
1745
|
+
]), ...features);
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
/**
|
|
1749
|
+
* Pick blocking vs keep-rows fetch for a list table.
|
|
1750
|
+
*
|
|
1751
|
+
* Body loading only when there is no initial data or the shipping mode
|
|
1752
|
+
* switched. Page/size keeps rows. Tab focus and Refresh keep rows.
|
|
1753
|
+
*
|
|
1754
|
+
* @param options
|
|
1755
|
+
* @param options.hasData - Rows from the *current* mode are already on screen.
|
|
1756
|
+
* @param options.reason - What triggered this fetch.
|
|
1757
|
+
* @returns Kind to map onto `loading` / `pageLoading` / `refreshing`.
|
|
1758
|
+
*
|
|
1759
|
+
* @example
|
|
1760
|
+
* ```ts
|
|
1761
|
+
* const kind = listFetchKind({
|
|
1762
|
+
* hasData: this.rows().length > 0,
|
|
1763
|
+
* reason: 'mode',
|
|
1764
|
+
* });
|
|
1765
|
+
* this.isLoading.set(kind === 'loading');
|
|
1766
|
+
* if (kind === 'loading') {
|
|
1767
|
+
* this.rows.set([]);
|
|
1768
|
+
* }
|
|
1769
|
+
* ```
|
|
1770
|
+
*/
|
|
1771
|
+
function listFetchKind(options) {
|
|
1772
|
+
if (!options.hasData || options.reason === 'mode') {
|
|
1773
|
+
return 'loading';
|
|
1774
|
+
}
|
|
1775
|
+
if (options.reason === 'page') {
|
|
1776
|
+
return 'pagination';
|
|
1777
|
+
}
|
|
1778
|
+
return 'refreshing';
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
/**
|
|
1782
|
+
* Shared TanStack Query defaults for AFRICANIES consuming apps.
|
|
1783
|
+
*
|
|
1784
|
+
* `@tanstack/angular-query-experimental` is **not** a dependency of this SDK —
|
|
1785
|
+
* query cache lifetime is app state. These helpers return a plain
|
|
1786
|
+
* `defaultOptions` object you pass into your own `QueryClient`.
|
|
1787
|
+
*
|
|
1788
|
+
* Pin an **exact** `@tanstack/angular-query-experimental` version in the app
|
|
1789
|
+
* (no caret/tilde): TanStack marks the Angular adapter experimental and
|
|
1790
|
+
* breaking changes land without major bumps.
|
|
1791
|
+
*
|
|
1792
|
+
* Query retries default to **0** to match {@link ApiClient} (fail fast;
|
|
1793
|
+
* screens use manual Retry). Apps can override per `QueryClient` if needed.
|
|
1794
|
+
*/
|
|
1795
|
+
/**
|
|
1796
|
+
* Factory for TanStack `QueryClient` `defaultOptions`.
|
|
1797
|
+
*
|
|
1798
|
+
* @returns Plain defaults object — no Angular providers, no TanStack imports.
|
|
1799
|
+
*
|
|
1800
|
+
* @example
|
|
1801
|
+
* ```ts
|
|
1802
|
+
* // app.config.ts — pin exact experimental version in package.json
|
|
1803
|
+
* import {
|
|
1804
|
+
* provideAngularQuery,
|
|
1805
|
+
* QueryClient,
|
|
1806
|
+
* } from '@tanstack/angular-query-experimental';
|
|
1807
|
+
* import { createAfricaniesQueryClientDefaults } from '@africanies/africanies-core';
|
|
1808
|
+
*
|
|
1809
|
+
* const queryClient = new QueryClient({
|
|
1810
|
+
* defaultOptions: createAfricaniesQueryClientDefaults(),
|
|
1811
|
+
* });
|
|
1812
|
+
*
|
|
1813
|
+
* export const appConfig = {
|
|
1814
|
+
* providers: [provideAngularQuery(queryClient)],
|
|
1815
|
+
* };
|
|
1816
|
+
* ```
|
|
1817
|
+
*
|
|
1818
|
+
* @example
|
|
1819
|
+
* ```ts
|
|
1820
|
+
* // Map injectQuery() signals → AsyncQueryStateModel for <africanies-async-state>
|
|
1821
|
+
* import type { AsyncQueryStateModel } from '@africanies/africanies-models';
|
|
1822
|
+
* import { injectQuery } from '@tanstack/angular-query-experimental';
|
|
1823
|
+
*
|
|
1824
|
+
* const query = injectQuery(() => ({
|
|
1825
|
+
* queryKey: ['shipments'],
|
|
1826
|
+
* queryFn: () => firstValueFrom(api.getResource<Shipment>('shipments', null)),
|
|
1827
|
+
* }));
|
|
1828
|
+
*
|
|
1829
|
+
* const state: AsyncQueryStateModel<Shipment[] | null> = {
|
|
1830
|
+
* data: query.data()?.data ?? undefined,
|
|
1831
|
+
* isLoading: query.isLoading(),
|
|
1832
|
+
* isFetching: query.isFetching(),
|
|
1833
|
+
* isError: query.isError(),
|
|
1834
|
+
* error: query.error()?.message ?? null,
|
|
1835
|
+
* };
|
|
1836
|
+
* ```
|
|
1837
|
+
*/
|
|
1838
|
+
function createAfricaniesQueryClientDefaults() {
|
|
1839
|
+
return {
|
|
1840
|
+
queries: {
|
|
1841
|
+
staleTime: 60_000,
|
|
1842
|
+
gcTime: 5 * 60_000,
|
|
1843
|
+
retry: 0,
|
|
1844
|
+
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30_000),
|
|
1845
|
+
},
|
|
1846
|
+
mutations: {
|
|
1847
|
+
retry: 0,
|
|
1848
|
+
},
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
/**
|
|
1852
|
+
* Alias of {@link createAfricaniesQueryClientDefaults} for apps that prefer a
|
|
1853
|
+
* `provide*` naming style beside {@link provideAfricaniesSdk}.
|
|
1854
|
+
*
|
|
1855
|
+
* Returns a plain object (not `EnvironmentProviders`) so it can be passed
|
|
1856
|
+
* straight into `new QueryClient({ defaultOptions: ... })`.
|
|
1857
|
+
*
|
|
1858
|
+
* @returns Same object as {@link createAfricaniesQueryClientDefaults}.
|
|
1859
|
+
*
|
|
1860
|
+
* @example
|
|
1861
|
+
* ```ts
|
|
1862
|
+
* const queryClient = new QueryClient({
|
|
1863
|
+
* defaultOptions: provideAfricaniesQueryDefaults(),
|
|
1864
|
+
* });
|
|
1865
|
+
* ```
|
|
1866
|
+
*/
|
|
1867
|
+
function provideAfricaniesQueryDefaults() {
|
|
1868
|
+
return createAfricaniesQueryClientDefaults();
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
/** Public country-read base path (relative to {@link AfricaniesSdkConfig.baseUrl}). */
|
|
1872
|
+
const COUNTRY_READ_PATH = '/public/country/read';
|
|
1873
|
+
/**
|
|
1874
|
+
* Map a wire state object into {@link CountryStateModel} (snake_case preserved).
|
|
1875
|
+
* @param raw - State object from the wire.
|
|
1876
|
+
* @returns Normalized {@link CountryStateModel}.
|
|
1877
|
+
*/
|
|
1878
|
+
function mapCountryState(raw) {
|
|
1879
|
+
const record = asRecord(raw) ?? {};
|
|
1880
|
+
return {
|
|
1881
|
+
name: asString(record['name']),
|
|
1882
|
+
state_code: asString(record['state_code'] ?? record['stateCode']),
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
/**
|
|
1886
|
+
* Map a wire country object into {@link CountryModel} (snake_case preserved).
|
|
1887
|
+
* @param raw - Country object from the wire.
|
|
1888
|
+
* @returns Normalized {@link CountryModel}.
|
|
1889
|
+
*/
|
|
1890
|
+
function mapCountry(raw) {
|
|
1891
|
+
const record = asRecord(raw) ?? {};
|
|
1892
|
+
return {
|
|
1893
|
+
id: asNumber(record['id']),
|
|
1894
|
+
name: asString(record['name']),
|
|
1895
|
+
iso3: asString(record['iso3']),
|
|
1896
|
+
iso2: asString(record['iso2']),
|
|
1897
|
+
states: mapArray(record['states'], mapCountryState),
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
/**
|
|
1901
|
+
* Map a list (or single object) payload into {@link CountryModel}[].
|
|
1902
|
+
*
|
|
1903
|
+
* @param raw - `data` payload from `/public/country/read/{id|all}`.
|
|
1904
|
+
* @returns Mapped country list (empty when `raw` is null/undefined).
|
|
1905
|
+
*/
|
|
1906
|
+
function mapCountryList(raw) {
|
|
1907
|
+
return mapList(raw, mapCountry);
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
/** In-memory GET cache TTL for country reference dumps / by-id (5 minutes). */
|
|
1911
|
+
const COUNTRY_CACHE_TTL_MS = 5 * 60_000;
|
|
1912
|
+
/**
|
|
1913
|
+
* Public country utility reads (`GET /public/country/read/{id?}`).
|
|
1914
|
+
*
|
|
1915
|
+
* Uses the AFRICANIES {@link ResourceId} convention:
|
|
1916
|
+
* - `null` (default) → paginated page
|
|
1917
|
+
* - `'all'` → full list
|
|
1918
|
+
* - `number` → single {@link CountryModel}
|
|
1919
|
+
*
|
|
1920
|
+
* @example
|
|
1921
|
+
* ```ts
|
|
1922
|
+
* const countries = inject(CountryService);
|
|
1923
|
+
*
|
|
1924
|
+
* countries.read(null, { page: 1 }).subscribe((res) => {
|
|
1925
|
+
* console.log(res.data, res.pagination);
|
|
1926
|
+
* });
|
|
1927
|
+
*
|
|
1928
|
+
* countries.readAll().subscribe((res) => console.log(res.data?.length));
|
|
1929
|
+
* countries.readById(1).subscribe((res) => console.log(res.data?.name));
|
|
1930
|
+
* ```
|
|
1931
|
+
*/
|
|
1932
|
+
/**
|
|
1933
|
+
* Public country utility reads (`GET /public/country/read/{id?}`).
|
|
1934
|
+
*/
|
|
1935
|
+
class CountryService {
|
|
1936
|
+
api = inject(ApiClient);
|
|
1937
|
+
read(id = null, params) {
|
|
1938
|
+
return this.api
|
|
1939
|
+
.get(buildResourcePath(COUNTRY_READ_PATH, id), {
|
|
1940
|
+
params: buildResourceQueryParams(id, params),
|
|
1941
|
+
cacheTtlMs: resourceCacheTtlMs(id, COUNTRY_CACHE_TTL_MS),
|
|
1942
|
+
})
|
|
1943
|
+
.pipe(map$1((res) => ({
|
|
1944
|
+
...res,
|
|
1945
|
+
data: mapResourcePayload(id, res.data, mapCountry, mapCountryList),
|
|
1946
|
+
})));
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Paginated page — alias for {@link read}(`null`, params).
|
|
1950
|
+
* @param params
|
|
1951
|
+
*/
|
|
1952
|
+
readPage(params) {
|
|
1953
|
+
return this.read(null, params);
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Full list — alias for {@link read}(`'all'`).
|
|
1957
|
+
* @param params
|
|
1958
|
+
*/
|
|
1959
|
+
readAll(params) {
|
|
1960
|
+
return this.read('all', params);
|
|
1961
|
+
}
|
|
1962
|
+
/**
|
|
1963
|
+
* Single record — alias for {@link read}(id).
|
|
1964
|
+
* @param id
|
|
1965
|
+
* @param params
|
|
1966
|
+
*/
|
|
1967
|
+
readById(id, params) {
|
|
1968
|
+
return this.read(id, params);
|
|
1969
|
+
}
|
|
1970
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CountryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1971
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CountryService, providedIn: 'root' });
|
|
1972
|
+
}
|
|
1973
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CountryService, decorators: [{
|
|
1974
|
+
type: Injectable,
|
|
1975
|
+
args: [{ providedIn: 'root' }]
|
|
1976
|
+
}] });
|
|
1977
|
+
|
|
1978
|
+
/** Default CDN for ISO country flags ([flagcdn.com](https://flagcdn.com)). */
|
|
1979
|
+
const COUNTRY_FLAG_CDN_BASE = 'https://flagcdn.com';
|
|
1980
|
+
/**
|
|
1981
|
+
* Build a flagcdn.com URL from an ISO 3166-1 alpha-2 code.
|
|
1982
|
+
*
|
|
1983
|
+
* Examples:
|
|
1984
|
+
* - `countryFlagUrl('NG')` → `https://flagcdn.com/w40/ng.png`
|
|
1985
|
+
* - `countryFlagUrl('us', { width: 80, format: 'webp' })` → `https://flagcdn.com/w80/us.webp`
|
|
1986
|
+
*
|
|
1987
|
+
* @param iso2 - Two-letter country code (case-insensitive).
|
|
1988
|
+
* @param options - Width, height, or format overrides.
|
|
1989
|
+
* @returns CDN URL, or `''` when `iso2` is not two letters.
|
|
1990
|
+
*/
|
|
1991
|
+
function countryFlagUrl(iso2, options = {}) {
|
|
1992
|
+
const code = iso2.trim().toLowerCase();
|
|
1993
|
+
if (!/^[a-z]{2}$/.test(code)) {
|
|
1994
|
+
return '';
|
|
1995
|
+
}
|
|
1996
|
+
const format = options.format ?? 'png';
|
|
1997
|
+
if (options.height != null) {
|
|
1998
|
+
return `${COUNTRY_FLAG_CDN_BASE}/h${options.height}/${code}.${format}`;
|
|
1999
|
+
}
|
|
2000
|
+
const width = options.width ?? 40;
|
|
2001
|
+
return `${COUNTRY_FLAG_CDN_BASE}/w${width}/${code}.${format}`;
|
|
2002
|
+
}
|
|
2003
|
+
/**
|
|
2004
|
+
* Map {@link CountryModel} rows into select options with flag CDN URLs.
|
|
2005
|
+
*
|
|
2006
|
+
* @param countries - Countries from {@link CountryService.readAll} / `readPage`.
|
|
2007
|
+
* @param options - Passed through to {@link countryFlagUrl}.
|
|
2008
|
+
*/
|
|
2009
|
+
function mapCountrySelectOptions(countries, options) {
|
|
2010
|
+
if (!countries?.length) {
|
|
2011
|
+
return [];
|
|
2012
|
+
}
|
|
2013
|
+
return countries.map((country) => ({
|
|
2014
|
+
label: country.name,
|
|
2015
|
+
value: country.id,
|
|
2016
|
+
iso2: country.iso2,
|
|
2017
|
+
prefixImageUrl: countryFlagUrl(country.iso2, options),
|
|
2018
|
+
}));
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
/**
|
|
2022
|
+
* Public country utility exports.
|
|
2023
|
+
*/
|
|
2024
|
+
|
|
2025
|
+
/** File read path (relative to {@link AfricaniesSdkConfig.baseUrl}). */
|
|
2026
|
+
const FILE_READ_PATH = '/file/read';
|
|
2027
|
+
/** Query flag for multi-file waybill reads (`POST /file/read?multiple=yes`). */
|
|
2028
|
+
const FILE_READ_MULTIPLE_PARAM = 'yes';
|
|
2029
|
+
/**
|
|
2030
|
+
* Map wire `data` into {@link FileReadModel} (snake_case preserved).
|
|
2031
|
+
*
|
|
2032
|
+
* `POST /file/read` returns a single object in `data` (not a list).
|
|
2033
|
+
* If the wire unexpectedly sends a one-element array, the first entry is used.
|
|
2034
|
+
*
|
|
2035
|
+
* @param raw - Envelope `data` from `POST /file/read`.
|
|
2036
|
+
* @returns Normalized {@link FileReadModel}.
|
|
2037
|
+
*/
|
|
2038
|
+
function mapFileRead(raw) {
|
|
2039
|
+
const entry = Array.isArray(raw) ? raw[0] : raw;
|
|
2040
|
+
const record = asRecord(entry) ?? {};
|
|
2041
|
+
return {
|
|
2042
|
+
mime_type: asString(record['mime_type'] ?? record['mimeType']),
|
|
2043
|
+
base_64: asString(record['base_64'] ?? record['base64']),
|
|
2044
|
+
url: asString(record['url']),
|
|
2045
|
+
};
|
|
2046
|
+
}
|
|
2047
|
+
/**
|
|
2048
|
+
* Map a list (or single object) payload into {@link FileReadModel}[].
|
|
2049
|
+
*
|
|
2050
|
+
* Used for `POST /file/read?multiple=yes` (e-commerce waybills).
|
|
2051
|
+
*
|
|
2052
|
+
* @param raw - Envelope `data` from multi-file reads.
|
|
2053
|
+
* @returns Mapped file list (empty when `raw` is null/undefined).
|
|
2054
|
+
*/
|
|
2055
|
+
function mapFileReadList(raw) {
|
|
2056
|
+
if (Array.isArray(raw)) {
|
|
2057
|
+
return raw.map((entry) => mapFileRead(entry));
|
|
2058
|
+
}
|
|
2059
|
+
if (raw == null) {
|
|
2060
|
+
return [];
|
|
2061
|
+
}
|
|
2062
|
+
return [mapFileRead(raw)];
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
/** Public document read base path (relative to {@link AfricaniesSdkConfig.baseUrl}). */
|
|
2066
|
+
const DOCUMENT_READ_PATH = '/public/document/read';
|
|
2067
|
+
/**
|
|
2068
|
+
* Map nested or legacy flat preview fields into {@link FileReadModel}.
|
|
2069
|
+
* @param record - Document wire object.
|
|
2070
|
+
*/
|
|
2071
|
+
function mapDocumentFileRef(record) {
|
|
2072
|
+
const nested = asRecord(record['file_ref'] ?? record['fileRef']);
|
|
2073
|
+
if (nested !== null) {
|
|
2074
|
+
return mapFileRead(nested);
|
|
2075
|
+
}
|
|
2076
|
+
const mime_type = asNullableString(record['mime_type'] ?? record['mimeType']);
|
|
2077
|
+
const base_64 = asNullableString(record['base_64'] ?? record['base64']);
|
|
2078
|
+
const url = asNullableString(record['url']);
|
|
2079
|
+
if (mime_type === null && base_64 === null && url === null) {
|
|
2080
|
+
return null;
|
|
2081
|
+
}
|
|
2082
|
+
return mapFileRead({
|
|
2083
|
+
mime_type,
|
|
2084
|
+
base_64,
|
|
2085
|
+
url,
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
2088
|
+
/**
|
|
2089
|
+
* Map a wire document into {@link DocumentModel} (snake_case preserved).
|
|
2090
|
+
* @param raw - Single document object from the API.
|
|
2091
|
+
* @returns Normalized {@link DocumentModel}.
|
|
2092
|
+
*/
|
|
2093
|
+
function mapDocument(raw) {
|
|
2094
|
+
const record = asRecord(raw) ?? {};
|
|
2095
|
+
return {
|
|
2096
|
+
id: asNumber(record['id']),
|
|
2097
|
+
name: asString(record['name']),
|
|
2098
|
+
description: asNullableString(record['description']),
|
|
2099
|
+
type: asNullableString(record['type']),
|
|
2100
|
+
active: asBoolean(record['active']),
|
|
2101
|
+
deleted_at: asNullableString(record['deleted_at'] ?? record['deletedAt']),
|
|
2102
|
+
created_at: asNullableString(record['created_at'] ?? record['createdAt']),
|
|
2103
|
+
updated_at: asNullableString(record['updated_at'] ?? record['updatedAt']),
|
|
2104
|
+
file_ref: mapDocumentFileRef(record),
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Map a list (or single object) payload into {@link DocumentModel}[].
|
|
2109
|
+
* @param raw - `data` payload from `/public/document/read/{id|all}`.
|
|
2110
|
+
* @returns Mapped document list.
|
|
2111
|
+
*/
|
|
2112
|
+
function mapDocumentList(raw) {
|
|
2113
|
+
return mapList(raw, mapDocument);
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
/** In-memory GET cache TTL for document reference dumps / by-id (5 minutes). */
|
|
2117
|
+
const DOCUMENT_CACHE_TTL_MS = 5 * 60_000;
|
|
2118
|
+
/**
|
|
2119
|
+
* Public document catalog reads (`GET /public/document/read/{id?}`).
|
|
2120
|
+
*
|
|
2121
|
+
* Uses the AFRICANIES {@link ResourceId} convention:
|
|
2122
|
+
* - `null` (default) → paginated page
|
|
2123
|
+
* - `'all'` → full list
|
|
2124
|
+
* - `number` → single {@link DocumentModel} (may include preview `url` / `base_64`)
|
|
2125
|
+
*
|
|
2126
|
+
* @example
|
|
2127
|
+
* ```ts
|
|
2128
|
+
* const documents = inject(DocumentService);
|
|
2129
|
+
*
|
|
2130
|
+
* documents.readPage({ page: 1 }).subscribe((res) => {
|
|
2131
|
+
* console.log(res.data, res.pagination);
|
|
2132
|
+
* });
|
|
2133
|
+
* documents.readById(12).subscribe((res) => {
|
|
2134
|
+
* console.log(res.data?.url ?? res.data?.base_64);
|
|
2135
|
+
* });
|
|
2136
|
+
* ```
|
|
2137
|
+
*/
|
|
2138
|
+
/**
|
|
2139
|
+
* Public document catalog reads (`GET /public/document/read/{id?}`).
|
|
2140
|
+
*/
|
|
2141
|
+
class DocumentService {
|
|
2142
|
+
api = inject(ApiClient);
|
|
2143
|
+
read(id = null, params) {
|
|
2144
|
+
return this.api
|
|
2145
|
+
.get(buildResourcePath(DOCUMENT_READ_PATH, id), {
|
|
2146
|
+
params: buildResourceQueryParams(id, params),
|
|
2147
|
+
cacheTtlMs: resourceCacheTtlMs(id, DOCUMENT_CACHE_TTL_MS),
|
|
2148
|
+
})
|
|
2149
|
+
.pipe(map$1((res) => ({
|
|
2150
|
+
...res,
|
|
2151
|
+
data: mapResourcePayload(id, res.data, mapDocument, mapDocumentList),
|
|
2152
|
+
})));
|
|
2153
|
+
}
|
|
2154
|
+
/**
|
|
2155
|
+
* Paginated page — alias for {@link read}(`null`, params).
|
|
2156
|
+
* @param params
|
|
2157
|
+
*/
|
|
2158
|
+
readPage(params) {
|
|
2159
|
+
return this.read(null, params);
|
|
2160
|
+
}
|
|
2161
|
+
/**
|
|
2162
|
+
* Full list — alias for {@link read}(`'all'`).
|
|
2163
|
+
* @param params
|
|
2164
|
+
*/
|
|
2165
|
+
readAll(params) {
|
|
2166
|
+
return this.read('all', params);
|
|
2167
|
+
}
|
|
2168
|
+
/**
|
|
2169
|
+
* Single record — alias for {@link read}(id).
|
|
2170
|
+
* @param id
|
|
2171
|
+
* @param params
|
|
2172
|
+
*/
|
|
2173
|
+
readById(id, params) {
|
|
2174
|
+
return this.read(id, params);
|
|
2175
|
+
}
|
|
2176
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: DocumentService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2177
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: DocumentService, providedIn: 'root' });
|
|
2178
|
+
}
|
|
2179
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: DocumentService, decorators: [{
|
|
2180
|
+
type: Injectable,
|
|
2181
|
+
args: [{ providedIn: 'root' }]
|
|
2182
|
+
}] });
|
|
2183
|
+
|
|
2184
|
+
/**
|
|
2185
|
+
* Public document catalog exports.
|
|
2186
|
+
*/
|
|
2187
|
+
|
|
2188
|
+
/** Public plan read base path (relative to {@link AfricaniesSdkConfig.baseUrl}). */
|
|
2189
|
+
const PLAN_READ_PATH = '/public/plan/read';
|
|
2190
|
+
/**
|
|
2191
|
+
* Map a wire plan package into {@link PlanPackageModel}.
|
|
2192
|
+
* @param raw - Package object nested under a plan.
|
|
2193
|
+
* @returns Normalized {@link PlanPackageModel}.
|
|
2194
|
+
*/
|
|
2195
|
+
function mapPlanPackage(raw) {
|
|
2196
|
+
const record = asRecord(raw) ?? {};
|
|
2197
|
+
return {
|
|
2198
|
+
id: asNumber(record['id']),
|
|
2199
|
+
plan_id: asNullableNumber(record['plan_id'] ?? record['planId']),
|
|
2200
|
+
company_service_id: asNullableNumber(record['company_service_id'] ?? record['companyServiceId']),
|
|
2201
|
+
name: asString(record['name']),
|
|
2202
|
+
metrics: asNullableString(record['metrics']),
|
|
2203
|
+
volume: asNullableNumber(record['volume']),
|
|
2204
|
+
discount: asNullableString(record['discount']),
|
|
2205
|
+
model: asNullableString(record['model']),
|
|
2206
|
+
monthly: asNullableString(record['monthly']),
|
|
2207
|
+
quarterly: asNullableString(record['quarterly']),
|
|
2208
|
+
biannually: asNullableString(record['biannually']),
|
|
2209
|
+
annually: asNullableString(record['annually']),
|
|
2210
|
+
active: asBoolean(record['active']),
|
|
2211
|
+
deleted_at: asNullableString(record['deleted_at'] ?? record['deletedAt']),
|
|
2212
|
+
created_at: asNullableString(record['created_at'] ?? record['createdAt']),
|
|
2213
|
+
updated_at: asNullableString(record['updated_at'] ?? record['updatedAt']),
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
/**
|
|
2217
|
+
* Map a wire plan into {@link PlanModel} (snake_case preserved).
|
|
2218
|
+
* @param raw - Single plan object from the API.
|
|
2219
|
+
* @returns Normalized {@link PlanModel}.
|
|
2220
|
+
*/
|
|
2221
|
+
function mapPlan(raw) {
|
|
2222
|
+
const record = asRecord(raw) ?? {};
|
|
2223
|
+
return {
|
|
2224
|
+
id: asNumber(record['id']),
|
|
2225
|
+
name: asString(record['name']),
|
|
2226
|
+
active: asBoolean(record['active']),
|
|
2227
|
+
deleted_at: asNullableString(record['deleted_at'] ?? record['deletedAt']),
|
|
2228
|
+
created_at: asNullableString(record['created_at'] ?? record['createdAt']),
|
|
2229
|
+
updated_at: asNullableString(record['updated_at'] ?? record['updatedAt']),
|
|
2230
|
+
packages: mapArray(record['packages'], mapPlanPackage),
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
/**
|
|
2234
|
+
* Map a list (or single object) payload into {@link PlanModel}[].
|
|
2235
|
+
* @param raw - `data` payload from `/public/plan/read/{id|all}`.
|
|
2236
|
+
* @returns Mapped plan list.
|
|
2237
|
+
*/
|
|
2238
|
+
function mapPlanList(raw) {
|
|
2239
|
+
return mapList(raw, mapPlan);
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
/** In-memory GET cache TTL for plan reference dumps / by-id (5 minutes). */
|
|
2243
|
+
const PLAN_CACHE_TTL_MS = 5 * 60_000;
|
|
2244
|
+
/**
|
|
2245
|
+
* Public subscription-plan reads (`GET /public/plan/read/{id?}`).
|
|
2246
|
+
*
|
|
2247
|
+
* Uses the AFRICANIES {@link ResourceId} convention:
|
|
2248
|
+
* - `null` (default) → paginated page
|
|
2249
|
+
* - `'all'` → full list
|
|
2250
|
+
* - `number` → single {@link PlanModel}
|
|
2251
|
+
*
|
|
2252
|
+
* @example
|
|
2253
|
+
* ```ts
|
|
2254
|
+
* const plans = inject(PlanService);
|
|
2255
|
+
*
|
|
2256
|
+
* plans.readPage({ page: 1, order: 'desc' }).subscribe((res) => {
|
|
2257
|
+
* console.log(res.data, res.pagination);
|
|
2258
|
+
* });
|
|
2259
|
+
* plans.readById(1).subscribe((res) => {
|
|
2260
|
+
* console.log(res.data?.packages?.length);
|
|
2261
|
+
* });
|
|
2262
|
+
* ```
|
|
2263
|
+
*/
|
|
2264
|
+
/**
|
|
2265
|
+
* Public subscription-plan reads (`GET /public/plan/read/{id?}`).
|
|
2266
|
+
*/
|
|
2267
|
+
class PlanService {
|
|
2268
|
+
api = inject(ApiClient);
|
|
2269
|
+
read(id = null, params) {
|
|
2270
|
+
return this.api
|
|
2271
|
+
.get(buildResourcePath(PLAN_READ_PATH, id), {
|
|
2272
|
+
params: buildResourceQueryParams(id, params),
|
|
2273
|
+
cacheTtlMs: resourceCacheTtlMs(id, PLAN_CACHE_TTL_MS),
|
|
2274
|
+
})
|
|
2275
|
+
.pipe(map$1((res) => ({
|
|
2276
|
+
...res,
|
|
2277
|
+
data: mapResourcePayload(id, res.data, mapPlan, mapPlanList),
|
|
2278
|
+
})));
|
|
2279
|
+
}
|
|
2280
|
+
/**
|
|
2281
|
+
* Paginated page — alias for {@link read}(`null`, params).
|
|
2282
|
+
* @param params
|
|
2283
|
+
*/
|
|
2284
|
+
readPage(params) {
|
|
2285
|
+
return this.read(null, params);
|
|
2286
|
+
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Full list — alias for {@link read}(`'all'`).
|
|
2289
|
+
* @param params
|
|
2290
|
+
*/
|
|
2291
|
+
readAll(params) {
|
|
2292
|
+
return this.read('all', params);
|
|
2293
|
+
}
|
|
2294
|
+
/**
|
|
2295
|
+
* Single record — alias for {@link read}(id).
|
|
2296
|
+
* @param id
|
|
2297
|
+
* @param params
|
|
2298
|
+
*/
|
|
2299
|
+
readById(id, params) {
|
|
2300
|
+
return this.read(id, params);
|
|
2301
|
+
}
|
|
2302
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PlanService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2303
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PlanService, providedIn: 'root' });
|
|
2304
|
+
}
|
|
2305
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PlanService, decorators: [{
|
|
2306
|
+
type: Injectable,
|
|
2307
|
+
args: [{ providedIn: 'root' }]
|
|
2308
|
+
}] });
|
|
2309
|
+
|
|
2310
|
+
/**
|
|
2311
|
+
* Public plan catalog exports.
|
|
2312
|
+
*/
|
|
2313
|
+
|
|
2314
|
+
/** Public service read base path (relative to {@link AfricaniesSdkConfig.baseUrl}). */
|
|
2315
|
+
const SERVICE_READ_PATH = '/public/service/read';
|
|
2316
|
+
/**
|
|
2317
|
+
* Map a wire service into {@link ServiceModel} (snake_case preserved).
|
|
2318
|
+
* @param raw - Single service object from the API.
|
|
2319
|
+
* @returns Normalized {@link ServiceModel}.
|
|
2320
|
+
*/
|
|
2321
|
+
function mapService(raw) {
|
|
2322
|
+
const record = asRecord(raw) ?? {};
|
|
2323
|
+
return {
|
|
2324
|
+
id: asNumber(record['id']),
|
|
2325
|
+
name: asString(record['name']),
|
|
2326
|
+
description: asNullableString(record['description']),
|
|
2327
|
+
model: asString(record['model']),
|
|
2328
|
+
active: asBoolean(record['active']),
|
|
2329
|
+
deleted_at: asNullableString(record['deleted_at'] ?? record['deletedAt']),
|
|
2330
|
+
created_at: asNullableString(record['created_at'] ?? record['createdAt']),
|
|
2331
|
+
updated_at: asNullableString(record['updated_at'] ?? record['updatedAt']),
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
/**
|
|
2335
|
+
* Map a list (or single object) payload into {@link ServiceModel}[].
|
|
2336
|
+
* @param raw - `data` payload from `/public/service/read/{id|all}`.
|
|
2337
|
+
* @returns Mapped service list.
|
|
2338
|
+
*/
|
|
2339
|
+
function mapServiceList(raw) {
|
|
2340
|
+
return mapList(raw, mapService);
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
/** In-memory GET cache TTL for service reference dumps / by-id (5 minutes). */
|
|
2344
|
+
const SERVICE_CACHE_TTL_MS = 5 * 60_000;
|
|
2345
|
+
/**
|
|
2346
|
+
* Public subscription-service reads (`GET /public/service/read/{id?}`).
|
|
2347
|
+
*
|
|
2348
|
+
* Uses the AFRICANIES {@link ResourceId} convention:
|
|
2349
|
+
* - `null` (default) → paginated page
|
|
2350
|
+
* - `'all'` → full list
|
|
2351
|
+
* - `number` → single {@link ServiceModel}
|
|
2352
|
+
*
|
|
2353
|
+
* @example
|
|
2354
|
+
* ```ts
|
|
2355
|
+
* const services = inject(ServiceService);
|
|
2356
|
+
*
|
|
2357
|
+
* services.readPage({ page: 1, search: 'box' }).subscribe((res) => {
|
|
2358
|
+
* console.log(res.data, res.pagination);
|
|
2359
|
+
* });
|
|
2360
|
+
* services.readAll().subscribe((res) => console.log(res.data?.length));
|
|
2361
|
+
* services.readById(3).subscribe((res) => console.log(res.data?.name));
|
|
2362
|
+
* ```
|
|
2363
|
+
*/
|
|
2364
|
+
/**
|
|
2365
|
+
* Public subscription-service reads (`GET /public/service/read/{id?}`).
|
|
2366
|
+
*/
|
|
2367
|
+
class ServiceService {
|
|
2368
|
+
api = inject(ApiClient);
|
|
2369
|
+
read(id = null, params) {
|
|
2370
|
+
return this.api
|
|
2371
|
+
.get(buildResourcePath(SERVICE_READ_PATH, id), {
|
|
2372
|
+
params: buildResourceQueryParams(id, params),
|
|
2373
|
+
cacheTtlMs: resourceCacheTtlMs(id, SERVICE_CACHE_TTL_MS),
|
|
2374
|
+
})
|
|
2375
|
+
.pipe(map$1((res) => ({
|
|
2376
|
+
...res,
|
|
2377
|
+
data: mapResourcePayload(id, res.data, mapService, mapServiceList),
|
|
2378
|
+
})));
|
|
2379
|
+
}
|
|
2380
|
+
/**
|
|
2381
|
+
* Paginated page — alias for {@link read}(`null`, params).
|
|
2382
|
+
* @param params
|
|
2383
|
+
*/
|
|
2384
|
+
readPage(params) {
|
|
2385
|
+
return this.read(null, params);
|
|
2386
|
+
}
|
|
2387
|
+
/**
|
|
2388
|
+
* Full list — alias for {@link read}(`'all'`).
|
|
2389
|
+
* @param params
|
|
2390
|
+
*/
|
|
2391
|
+
readAll(params) {
|
|
2392
|
+
return this.read('all', params);
|
|
2393
|
+
}
|
|
2394
|
+
/**
|
|
2395
|
+
* Single record — alias for {@link read}(id).
|
|
2396
|
+
* @param id
|
|
2397
|
+
* @param params
|
|
2398
|
+
*/
|
|
2399
|
+
readById(id, params) {
|
|
2400
|
+
return this.read(id, params);
|
|
2401
|
+
}
|
|
2402
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ServiceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2403
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ServiceService, providedIn: 'root' });
|
|
2404
|
+
}
|
|
2405
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ServiceService, decorators: [{
|
|
2406
|
+
type: Injectable,
|
|
2407
|
+
args: [{ providedIn: 'root' }]
|
|
2408
|
+
}] });
|
|
2409
|
+
|
|
2410
|
+
/**
|
|
2411
|
+
* Public service catalog exports.
|
|
2412
|
+
*/
|
|
2413
|
+
|
|
2414
|
+
/** Currency read base path (relative to {@link AfricaniesSdkConfig.baseUrl}). */
|
|
2415
|
+
const CURRENCY_READ_PATH = '/currency/read';
|
|
2416
|
+
/** Create path (App Settings → Currencies). */
|
|
2417
|
+
const CURRENCY_CREATE_PATH = '/currency/create';
|
|
2418
|
+
/** Update path — name / short_code are not sent. */
|
|
2419
|
+
const CURRENCY_UPDATE_PATH = '/currency/update';
|
|
2420
|
+
/** Delete path — JSON body `{ id }`. */
|
|
2421
|
+
const CURRENCY_DELETE_PATH = '/currency/delete';
|
|
2422
|
+
/**
|
|
2423
|
+
* Serialize a boolean / `"1"` / `"0"` flag for currency create/update.
|
|
2424
|
+
* @param value - Host boolean or wire flag.
|
|
2425
|
+
* @returns `"1"` or `"0"`.
|
|
2426
|
+
*/
|
|
2427
|
+
function toCurrencyFlag01(value) {
|
|
2428
|
+
return toFlag01(value);
|
|
2429
|
+
}
|
|
2430
|
+
/**
|
|
2431
|
+
* Map a currency ↔ payment-method pivot (snake_case preserved).
|
|
2432
|
+
* @param raw - Pivot object from the wire.
|
|
2433
|
+
* @returns Normalized {@link CurrencyPaymentMethodPivotModel}.
|
|
2434
|
+
*/
|
|
2435
|
+
function mapCurrencyPaymentMethodPivot(raw) {
|
|
2436
|
+
const record = asRecord(raw) ?? {};
|
|
2437
|
+
return {
|
|
2438
|
+
currency_id: asNumber(record['currency_id'] ?? record['currencyId']),
|
|
2439
|
+
payment_method_id: asNumber(record['payment_method_id'] ?? record['paymentMethodId']),
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
/**
|
|
2443
|
+
* Map a wire payment method into {@link CurrencyPaymentMethodModel}.
|
|
2444
|
+
* @param raw - Payment-method object from the wire.
|
|
2445
|
+
* @returns Normalized {@link CurrencyPaymentMethodModel}.
|
|
2446
|
+
*/
|
|
2447
|
+
function mapCurrencyPaymentMethod(raw) {
|
|
2448
|
+
const record = asRecord(raw) ?? {};
|
|
2449
|
+
return {
|
|
2450
|
+
id: asNumber(record['id']),
|
|
2451
|
+
name: asString(record['name']),
|
|
2452
|
+
model: asString(record['model']),
|
|
2453
|
+
active: asBoolean(record['active']),
|
|
2454
|
+
deleted_at: asNullableString(record['deleted_at'] ?? record['deletedAt']),
|
|
2455
|
+
created_at: asNullableString(record['created_at'] ?? record['createdAt']),
|
|
2456
|
+
updated_at: asNullableString(record['updated_at'] ?? record['updatedAt']),
|
|
2457
|
+
pivot: mapCurrencyPaymentMethodPivot(record['pivot']),
|
|
2458
|
+
};
|
|
2459
|
+
}
|
|
2460
|
+
/**
|
|
2461
|
+
* Map a wire currency into {@link CurrencyModel} (snake_case preserved).
|
|
2462
|
+
* @param raw - Currency object from the API.
|
|
2463
|
+
* @returns Normalized {@link CurrencyModel}.
|
|
2464
|
+
*/
|
|
2465
|
+
function mapCurrency(raw) {
|
|
2466
|
+
const record = asRecord(raw) ?? {};
|
|
2467
|
+
const payment_methods = mapArray(record['payment_methods'] ?? record['paymentMethods'], mapCurrencyPaymentMethod);
|
|
2468
|
+
const nairaGreater = asBoolean(record['is_naira_greater'] ??
|
|
2469
|
+
record['isNairaGreater'] ??
|
|
2470
|
+
record['is_local_currency_greater'] ??
|
|
2471
|
+
record['isLocalCurrencyGreater']);
|
|
2472
|
+
return {
|
|
2473
|
+
id: asNumber(record['id']),
|
|
2474
|
+
name: asString(record['name']),
|
|
2475
|
+
short_code: asString(record['short_code'] ?? record['shortCode']),
|
|
2476
|
+
division_rate: asString(record['division_rate'] ?? record['divisionRate']),
|
|
2477
|
+
multiplication_rate: asString(record['multiplication_rate'] ?? record['multiplicationRate']),
|
|
2478
|
+
is_local_currency_greater: nairaGreater,
|
|
2479
|
+
is_naira_greater: nairaGreater,
|
|
2480
|
+
active: asBoolean(record['active']),
|
|
2481
|
+
deleted_at: asNullableString(record['deleted_at'] ?? record['deletedAt']),
|
|
2482
|
+
created_at: asNullableString(record['created_at'] ?? record['createdAt']),
|
|
2483
|
+
updated_at: asNullableString(record['updated_at'] ?? record['updatedAt']),
|
|
2484
|
+
payment_methods,
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
/**
|
|
2488
|
+
* Map a list (or single object) payload into {@link CurrencyModel}[].
|
|
2489
|
+
*
|
|
2490
|
+
* @param raw - `data` payload from `/currency/read/{id|all}`.
|
|
2491
|
+
* @returns Mapped currency list (empty when `raw` is null/undefined).
|
|
2492
|
+
*/
|
|
2493
|
+
function mapCurrencyList(raw) {
|
|
2494
|
+
return mapList(raw, mapCurrency);
|
|
2495
|
+
}
|
|
2496
|
+
function paymentMethodIds(raw) {
|
|
2497
|
+
return mapArray(raw, (entry) => asNumber(entry));
|
|
2498
|
+
}
|
|
2499
|
+
/**
|
|
2500
|
+
* Wire body for `POST /currency/create`.
|
|
2501
|
+
* @param body - Host create payload.
|
|
2502
|
+
* @returns JSON body with `"1"` / `"0"` flags.
|
|
2503
|
+
*/
|
|
2504
|
+
function toCurrencyCreateBody(body) {
|
|
2505
|
+
return {
|
|
2506
|
+
name: asString(body?.name).trim(),
|
|
2507
|
+
short_code: asString(body?.short_code).trim(),
|
|
2508
|
+
multiplication_rate: asString(body?.multiplication_rate),
|
|
2509
|
+
division_rate: asString(body?.division_rate),
|
|
2510
|
+
active: toCurrencyFlag01(body?.active),
|
|
2511
|
+
is_naira_greater: toCurrencyFlag01(body?.is_naira_greater),
|
|
2512
|
+
payment_method_ids: paymentMethodIds(body?.payment_method_ids),
|
|
2513
|
+
};
|
|
2514
|
+
}
|
|
2515
|
+
/**
|
|
2516
|
+
* Wire body for `PUT /currency/update` (no name / short_code).
|
|
2517
|
+
* @param body - Host update payload.
|
|
2518
|
+
* @returns JSON body with `"1"` / `"0"` flags.
|
|
2519
|
+
*/
|
|
2520
|
+
function toCurrencyUpdateBody(body) {
|
|
2521
|
+
return {
|
|
2522
|
+
id: asNumber(body?.id),
|
|
2523
|
+
multiplication_rate: asString(body?.multiplication_rate),
|
|
2524
|
+
division_rate: asString(body?.division_rate),
|
|
2525
|
+
active: toCurrencyFlag01(body?.active),
|
|
2526
|
+
is_naira_greater: toCurrencyFlag01(body?.is_naira_greater),
|
|
2527
|
+
payment_method_ids: paymentMethodIds(body?.payment_method_ids),
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
/**
|
|
2531
|
+
* Wire body for `DELETE /currency/delete`.
|
|
2532
|
+
* @param body - Currency id wrapper.
|
|
2533
|
+
* @returns `{ id }`.
|
|
2534
|
+
*/
|
|
2535
|
+
function toCurrencyDeleteBody(body) {
|
|
2536
|
+
if (typeof body === 'number') {
|
|
2537
|
+
return { id: asNumber(body) };
|
|
2538
|
+
}
|
|
2539
|
+
return { id: asNumber(body?.id) };
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
/** In-memory GET cache TTL for currency reference dumps / by-id (5 minutes). */
|
|
2543
|
+
const CURRENCY_CACHE_TTL_MS = 5 * 60_000;
|
|
2544
|
+
/**
|
|
2545
|
+
* Currency App Settings API (`GET /currency/read/{id?}` plus create / update / delete).
|
|
2546
|
+
*
|
|
2547
|
+
* Uses the AFRICANIES {@link ResourceId} convention for reads:
|
|
2548
|
+
* - `null` (default) → paginated page
|
|
2549
|
+
* - `'all'` → full list
|
|
2550
|
+
* - `number` → single {@link CurrencyModel}
|
|
2551
|
+
*
|
|
2552
|
+
* Paginated responses embed a Laravel paginator in `data`; {@link ApiClient}
|
|
2553
|
+
* flattens that to `data: CurrencyModel[]` plus `pagination`.
|
|
2554
|
+
*
|
|
2555
|
+
* Writes send `"1"` / `"0"` flags. Delete uses a JSON `{ id }` body (not query
|
|
2556
|
+
* params). After each write, show `res.message` and call {@link readPage} again.
|
|
2557
|
+
*
|
|
2558
|
+
* @example
|
|
2559
|
+
* ```ts
|
|
2560
|
+
* const currencies = inject(CurrencyService);
|
|
2561
|
+
*
|
|
2562
|
+
* currencies.readPage({ page: 1, order: 'desc' }).subscribe((res) => {
|
|
2563
|
+
* console.log(res.data, res.pagination);
|
|
2564
|
+
* });
|
|
2565
|
+
* currencies.readAll().subscribe((res) => console.log(res.data?.[0]?.short_code));
|
|
2566
|
+
* currencies.readById(5).subscribe((res) => console.log(res.data?.name));
|
|
2567
|
+
* ```
|
|
2568
|
+
*/
|
|
2569
|
+
/**
|
|
2570
|
+
* Currency App Settings API (`GET /currency/read/{id?}` plus create / update / delete).
|
|
2571
|
+
*/
|
|
2572
|
+
class CurrencyService {
|
|
2573
|
+
api = inject(ApiClient);
|
|
2574
|
+
read(id = null, params) {
|
|
2575
|
+
return this.api
|
|
2576
|
+
.get(buildResourcePath(CURRENCY_READ_PATH, id), {
|
|
2577
|
+
toast: false,
|
|
2578
|
+
params: buildResourceQueryParams(id, params),
|
|
2579
|
+
cacheTtlMs: resourceCacheTtlMs(id, CURRENCY_CACHE_TTL_MS),
|
|
2580
|
+
})
|
|
2581
|
+
.pipe(map$1((res) => ({
|
|
2582
|
+
...res,
|
|
2583
|
+
data: mapResourcePayload(id, res.data, mapCurrency, mapCurrencyList),
|
|
2584
|
+
})));
|
|
2585
|
+
}
|
|
2586
|
+
/**
|
|
2587
|
+
* Paginated page — alias for {@link read}(`null`, params).
|
|
2588
|
+
* @param params
|
|
2589
|
+
*/
|
|
2590
|
+
readPage(params) {
|
|
2591
|
+
return this.read(null, params);
|
|
2592
|
+
}
|
|
2593
|
+
/**
|
|
2594
|
+
* Full list — alias for {@link read}(`'all'`).
|
|
2595
|
+
* @param params
|
|
2596
|
+
*/
|
|
2597
|
+
readAll(params) {
|
|
2598
|
+
return this.read('all', params);
|
|
2599
|
+
}
|
|
2600
|
+
/**
|
|
2601
|
+
* Single record — alias for {@link read}(id).
|
|
2602
|
+
* @param id
|
|
2603
|
+
* @param params
|
|
2604
|
+
*/
|
|
2605
|
+
readById(id, params) {
|
|
2606
|
+
return this.read(id, params);
|
|
2607
|
+
}
|
|
2608
|
+
/**
|
|
2609
|
+
* Create a currency (`POST /currency/create`).
|
|
2610
|
+
*
|
|
2611
|
+
* `name` / `short_code` come from the host’s local currency list, not GET.
|
|
2612
|
+
* Flags may be boolean or `"1"` / `"0"`; they are serialized to `"1"` / `"0"`.
|
|
2613
|
+
*
|
|
2614
|
+
* @param body - Create payload.
|
|
2615
|
+
* @returns Normalized envelope — use {@link ApiResponseModel.message}.
|
|
2616
|
+
*/
|
|
2617
|
+
create(body) {
|
|
2618
|
+
return this.api
|
|
2619
|
+
.post(CURRENCY_CREATE_PATH, toCurrencyCreateBody(body))
|
|
2620
|
+
.pipe(tap$1(() => this.api.clearCache()));
|
|
2621
|
+
}
|
|
2622
|
+
/**
|
|
2623
|
+
* Update rates, flags, and payment methods (`PUT /currency/update`).
|
|
2624
|
+
*
|
|
2625
|
+
* Name and short code are not sent. Before edit, copy the row into the form
|
|
2626
|
+
* (`true` / `false` may stay booleans — this method serializes flags).
|
|
2627
|
+
*
|
|
2628
|
+
* @param body - Update payload including `id`.
|
|
2629
|
+
* @returns Normalized envelope — use {@link ApiResponseModel.message}.
|
|
2630
|
+
*/
|
|
2631
|
+
update(body) {
|
|
2632
|
+
return this.api
|
|
2633
|
+
.put(CURRENCY_UPDATE_PATH, toCurrencyUpdateBody(body))
|
|
2634
|
+
.pipe(tap$1(() => this.api.clearCache()));
|
|
2635
|
+
}
|
|
2636
|
+
/**
|
|
2637
|
+
* Delete a currency (`DELETE /currency/delete`) with JSON body `{ id }`.
|
|
2638
|
+
*
|
|
2639
|
+
* @param body - Currency id or `{ id }`.
|
|
2640
|
+
* @returns Normalized envelope — use {@link ApiResponseModel.message}.
|
|
2641
|
+
*/
|
|
2642
|
+
remove(body) {
|
|
2643
|
+
return this.api
|
|
2644
|
+
.delete(CURRENCY_DELETE_PATH, toCurrencyDeleteBody(body))
|
|
2645
|
+
.pipe(tap$1(() => this.api.clearCache()));
|
|
2646
|
+
}
|
|
2647
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CurrencyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2648
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CurrencyService, providedIn: 'root' });
|
|
2649
|
+
}
|
|
2650
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CurrencyService, decorators: [{
|
|
2651
|
+
type: Injectable,
|
|
2652
|
+
args: [{ providedIn: 'root' }]
|
|
2653
|
+
}] });
|
|
2654
|
+
|
|
2655
|
+
/**
|
|
2656
|
+
* Currency utility exports.
|
|
2657
|
+
*/
|
|
2658
|
+
|
|
2659
|
+
/** Payment-method read base path (relative to {@link AfricaniesSdkConfig.baseUrl}). */
|
|
2660
|
+
const PAYMENT_METHOD_READ_PATH = '/payment_method/read';
|
|
2661
|
+
/** Payment-method update path (`PUT`). */
|
|
2662
|
+
const PAYMENT_METHOD_UPDATE_PATH = '/payment_method/update';
|
|
2663
|
+
/**
|
|
2664
|
+
* Serialize a boolean / `"1"` / `"0"` flag for payment-method update.
|
|
2665
|
+
* @param value - Host boolean or wire flag.
|
|
2666
|
+
* @returns `"1"` or `"0"`.
|
|
2667
|
+
*/
|
|
2668
|
+
function toPaymentMethodFlag01(value) {
|
|
2669
|
+
return toFlag01(value);
|
|
2670
|
+
}
|
|
2671
|
+
/**
|
|
2672
|
+
* Build the wire body for `PUT /payment_method/update`.
|
|
2673
|
+
*
|
|
2674
|
+
* @param body - Host update payload (`active` may be boolean).
|
|
2675
|
+
* @returns Body with `active` as `"1"` / `"0"`.
|
|
2676
|
+
*/
|
|
2677
|
+
function toPaymentMethodUpdateBody(body) {
|
|
2678
|
+
return {
|
|
2679
|
+
id: asNumber(body?.id),
|
|
2680
|
+
name: asString(body?.name),
|
|
2681
|
+
model: asString(body?.model),
|
|
2682
|
+
active: toPaymentMethodFlag01(body?.active),
|
|
2683
|
+
};
|
|
2684
|
+
}
|
|
2685
|
+
/**
|
|
2686
|
+
* Map a currency nested on a payment method (rates + pivot, no processors).
|
|
2687
|
+
* @param raw - Currency object from `currencies[]`.
|
|
2688
|
+
* @returns Normalized {@link PaymentMethodCurrencyModel}.
|
|
2689
|
+
*/
|
|
2690
|
+
function mapPaymentMethodCurrency(raw) {
|
|
2691
|
+
const mapped = mapCurrency(raw);
|
|
2692
|
+
const record = asRecord(raw) ?? {};
|
|
2693
|
+
return {
|
|
2694
|
+
id: mapped.id,
|
|
2695
|
+
name: mapped.name,
|
|
2696
|
+
short_code: mapped.short_code,
|
|
2697
|
+
division_rate: mapped.division_rate,
|
|
2698
|
+
multiplication_rate: mapped.multiplication_rate,
|
|
2699
|
+
is_local_currency_greater: mapped.is_local_currency_greater,
|
|
2700
|
+
is_naira_greater: mapped.is_naira_greater,
|
|
2701
|
+
active: mapped.active,
|
|
2702
|
+
deleted_at: mapped.deleted_at,
|
|
2703
|
+
created_at: mapped.created_at,
|
|
2704
|
+
updated_at: mapped.updated_at,
|
|
2705
|
+
pivot: mapCurrencyPaymentMethodPivot(record['pivot']),
|
|
2706
|
+
};
|
|
2707
|
+
}
|
|
2708
|
+
/**
|
|
2709
|
+
* Map a wire payment method into {@link PaymentMethodModel}.
|
|
2710
|
+
* @param raw - Payment-method object from the API.
|
|
2711
|
+
* @returns Normalized {@link PaymentMethodModel}.
|
|
2712
|
+
*/
|
|
2713
|
+
function mapPaymentMethod(raw) {
|
|
2714
|
+
const record = asRecord(raw) ?? {};
|
|
2715
|
+
return {
|
|
2716
|
+
id: asNumber(record['id']),
|
|
2717
|
+
name: asString(record['name']),
|
|
2718
|
+
model: asString(record['model']),
|
|
2719
|
+
active: asBoolean(record['active']),
|
|
2720
|
+
deleted_at: asNullableString(record['deleted_at'] ?? record['deletedAt']),
|
|
2721
|
+
created_at: asNullableString(record['created_at'] ?? record['createdAt']),
|
|
2722
|
+
updated_at: asNullableString(record['updated_at'] ?? record['updatedAt']),
|
|
2723
|
+
currencies: mapList(record['currencies'], mapPaymentMethodCurrency),
|
|
2724
|
+
};
|
|
2725
|
+
}
|
|
2726
|
+
/**
|
|
2727
|
+
* Map a list (or single object) payload into {@link PaymentMethodModel}[].
|
|
2728
|
+
*
|
|
2729
|
+
* @param raw - `data` payload from `/payment_method/read/{id|all}`.
|
|
2730
|
+
* @returns Mapped payment-method list (empty when `raw` is null/undefined).
|
|
2731
|
+
*/
|
|
2732
|
+
function mapPaymentMethodList(raw) {
|
|
2733
|
+
return mapList(raw, mapPaymentMethod);
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
/** In-memory GET cache TTL for payment-method dumps / by-id (5 minutes). */
|
|
2737
|
+
const PAYMENT_METHOD_CACHE_TTL_MS = 5 * 60_000;
|
|
2738
|
+
/**
|
|
2739
|
+
* Payment-method App Settings API (`GET /payment_method/read/{id?}` plus update).
|
|
2740
|
+
*
|
|
2741
|
+
* Uses the AFRICANIES {@link ResourceId} convention for reads:
|
|
2742
|
+
* - `null` (default) → paginated page
|
|
2743
|
+
* - `'all'` → full list
|
|
2744
|
+
* - `number` → single {@link PaymentMethodModel}
|
|
2745
|
+
*
|
|
2746
|
+
* Paginated responses embed a Laravel paginator in `data`; {@link ApiClient}
|
|
2747
|
+
* flattens that to `data: PaymentMethodModel[]` plus `pagination`.
|
|
2748
|
+
*
|
|
2749
|
+
* The only write is {@link update} (active toggle). There is no create or
|
|
2750
|
+
* delete on this board — processors are linked from Currencies via
|
|
2751
|
+
* `payment_method_ids`. After update, show `res.message` and patch the row’s
|
|
2752
|
+
* local `updated_at` — do not reload the full list.
|
|
2753
|
+
*
|
|
2754
|
+
* @example
|
|
2755
|
+
* ```ts
|
|
2756
|
+
* const methods = inject(PaymentMethodService);
|
|
2757
|
+
*
|
|
2758
|
+
* methods.readPage({ page: 1, order: 'desc' }).subscribe((res) => {
|
|
2759
|
+
* console.log(res.data, res.pagination);
|
|
2760
|
+
* });
|
|
2761
|
+
* methods.readAll().subscribe((res) => console.log(res.data?.[0]?.name));
|
|
2762
|
+
* methods.readById(4).subscribe((res) => console.log(res.data?.currencies));
|
|
2763
|
+
*
|
|
2764
|
+
* methods.update({
|
|
2765
|
+
* id: row.id,
|
|
2766
|
+
* name: row.name,
|
|
2767
|
+
* model: row.model,
|
|
2768
|
+
* active: nextActive,
|
|
2769
|
+
* }).subscribe((res) => {
|
|
2770
|
+
* // toast res.message; patch row.updated_at locally
|
|
2771
|
+
* });
|
|
2772
|
+
* ```
|
|
2773
|
+
*/
|
|
2774
|
+
/**
|
|
2775
|
+
* Payment-method App Settings API (`GET /payment_method/read/{id?}` plus update).
|
|
2776
|
+
*/
|
|
2777
|
+
class PaymentMethodService {
|
|
2778
|
+
api = inject(ApiClient);
|
|
2779
|
+
read(id = null, params) {
|
|
2780
|
+
return this.api
|
|
2781
|
+
.get(buildResourcePath(PAYMENT_METHOD_READ_PATH, id), {
|
|
2782
|
+
toast: false,
|
|
2783
|
+
params: buildResourceQueryParams(id, params),
|
|
2784
|
+
cacheTtlMs: resourceCacheTtlMs(id, PAYMENT_METHOD_CACHE_TTL_MS),
|
|
2785
|
+
})
|
|
2786
|
+
.pipe(map$1((res) => ({
|
|
2787
|
+
...res,
|
|
2788
|
+
data: mapResourcePayload(id, res.data, mapPaymentMethod, mapPaymentMethodList),
|
|
2789
|
+
})));
|
|
2790
|
+
}
|
|
2791
|
+
/**
|
|
2792
|
+
* Paginated page — alias for {@link read}(`null`, params).
|
|
2793
|
+
* @param params
|
|
2794
|
+
*/
|
|
2795
|
+
readPage(params) {
|
|
2796
|
+
return this.read(null, params);
|
|
2797
|
+
}
|
|
2798
|
+
/**
|
|
2799
|
+
* Full list — alias for {@link read}(`'all'`).
|
|
2800
|
+
* @param params
|
|
2801
|
+
*/
|
|
2802
|
+
readAll(params) {
|
|
2803
|
+
return this.read('all', params);
|
|
2804
|
+
}
|
|
2805
|
+
/**
|
|
2806
|
+
* Single record — alias for {@link read}(id).
|
|
2807
|
+
* @param id
|
|
2808
|
+
* @param params
|
|
2809
|
+
*/
|
|
2810
|
+
readById(id, params) {
|
|
2811
|
+
return this.read(id, params);
|
|
2812
|
+
}
|
|
2813
|
+
/**
|
|
2814
|
+
* Toggle / update a payment method (`PUT /payment_method/update`).
|
|
2815
|
+
*
|
|
2816
|
+
* App Settings only edits `active` (status switch). Resend `name` and
|
|
2817
|
+
* `model` from the current row. `active` may be boolean or `"1"` / `"0"`;
|
|
2818
|
+
* this method serializes to `"1"` / `"0"`.
|
|
2819
|
+
*
|
|
2820
|
+
* On success: show {@link ApiResponseModel.message}, patch that row’s
|
|
2821
|
+
* local `updated_at`, and do **not** call {@link readPage} again.
|
|
2822
|
+
*
|
|
2823
|
+
* @param body - Update payload including `id`.
|
|
2824
|
+
* @returns Normalized envelope — use {@link ApiResponseModel.message}.
|
|
2825
|
+
*/
|
|
2826
|
+
update(body) {
|
|
2827
|
+
return this.api
|
|
2828
|
+
.put(PAYMENT_METHOD_UPDATE_PATH, toPaymentMethodUpdateBody(body))
|
|
2829
|
+
.pipe(tap$1(() => this.api.clearCache()));
|
|
2830
|
+
}
|
|
2831
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PaymentMethodService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2832
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PaymentMethodService, providedIn: 'root' });
|
|
2833
|
+
}
|
|
2834
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: PaymentMethodService, decorators: [{
|
|
2835
|
+
type: Injectable,
|
|
2836
|
+
args: [{ providedIn: 'root' }]
|
|
2837
|
+
}] });
|
|
2838
|
+
|
|
2839
|
+
/**
|
|
2840
|
+
* Payment-method utility exports.
|
|
2841
|
+
*/
|
|
2842
|
+
|
|
2843
|
+
/** Shipment-method read base path (relative to {@link AfricaniesSdkConfig.baseUrl}). */
|
|
2844
|
+
const SHIPMENT_METHOD_READ_PATH = '/shipment_method/read';
|
|
2845
|
+
function asYesNo(value) {
|
|
2846
|
+
if (typeof value === 'boolean') {
|
|
2847
|
+
return value;
|
|
2848
|
+
}
|
|
2849
|
+
return asString(value).trim().toLowerCase() === 'yes';
|
|
2850
|
+
}
|
|
2851
|
+
function asShippingMode(value) {
|
|
2852
|
+
return value === 'stn' ? 'stn' : 'sfn';
|
|
2853
|
+
}
|
|
2854
|
+
/**
|
|
2855
|
+
* Map a nested zone object into {@link ShipmentZoneModel}.
|
|
2856
|
+
* @param raw
|
|
2857
|
+
*/
|
|
2858
|
+
function mapShipmentZone(raw) {
|
|
2859
|
+
const record = asRecord(raw);
|
|
2860
|
+
if (record === null) {
|
|
2861
|
+
return null;
|
|
2862
|
+
}
|
|
2863
|
+
return {
|
|
2864
|
+
id: asNumber(record['id']),
|
|
2865
|
+
name: asString(record['name']),
|
|
2866
|
+
type: asString(record['type']),
|
|
2867
|
+
active: asBoolean(record['active']),
|
|
2868
|
+
};
|
|
2869
|
+
}
|
|
2870
|
+
/**
|
|
2871
|
+
* Map a method↔zone link into {@link ShipmentMethodZoneLinkModel}.
|
|
2872
|
+
* @param raw
|
|
2873
|
+
*/
|
|
2874
|
+
function mapShipmentMethodZoneLink(raw) {
|
|
2875
|
+
const record = asRecord(raw) ?? {};
|
|
2876
|
+
return {
|
|
2877
|
+
id: asNumber(record['id']),
|
|
2878
|
+
zone_id: asNumber(record['zone_id'] ?? record['zoneId']),
|
|
2879
|
+
shipment_method_id: asNumber(record['shipment_method_id'] ?? record['shipmentMethodId']),
|
|
2880
|
+
active: asBoolean(record['active']),
|
|
2881
|
+
mode: asShippingMode(record['mode']),
|
|
2882
|
+
zone: mapShipmentZone(record['zone']),
|
|
2883
|
+
};
|
|
2884
|
+
}
|
|
2885
|
+
/**
|
|
2886
|
+
* Map a Laravel-style `zone_values` page into {@link ShipmentMethodZonePageModel}.
|
|
2887
|
+
* @param raw
|
|
2888
|
+
*/
|
|
2889
|
+
function mapShipmentMethodZonePage(raw) {
|
|
2890
|
+
const record = asRecord(raw);
|
|
2891
|
+
if (record === null) {
|
|
2892
|
+
return {
|
|
2893
|
+
data: [],
|
|
2894
|
+
current_page: 0,
|
|
2895
|
+
per_page: 0,
|
|
2896
|
+
total: 0,
|
|
2897
|
+
last_page: 0,
|
|
2898
|
+
};
|
|
2899
|
+
}
|
|
2900
|
+
return {
|
|
2901
|
+
data: mapArray(record['data'] ?? record['items'], mapShipmentMethodZoneLink),
|
|
2902
|
+
current_page: asNumber(record['current_page'] ?? record['currentPage']),
|
|
2903
|
+
per_page: asNumber(record['per_page'] ?? record['perPage']),
|
|
2904
|
+
total: asNumber(record['total']),
|
|
2905
|
+
last_page: asNumber(record['last_page'] ?? record['lastPage']),
|
|
2906
|
+
};
|
|
2907
|
+
}
|
|
2908
|
+
/**
|
|
2909
|
+
* Map a wire shipment method into {@link ShipmentMethodModel} (snake_case).
|
|
2910
|
+
* @param raw
|
|
2911
|
+
*/
|
|
2912
|
+
function mapShipmentMethod(raw) {
|
|
2913
|
+
const record = asRecord(raw) ?? {};
|
|
2914
|
+
return {
|
|
2915
|
+
id: asNumber(record['id']),
|
|
2916
|
+
name: asString(record['name']),
|
|
2917
|
+
slug: asString(record['slug']),
|
|
2918
|
+
model: asString(record['model']),
|
|
2919
|
+
min_delivery_business_day: asNumber(record['min_delivery_business_day'] ?? record['minDeliveryBusinessDay']),
|
|
2920
|
+
max_delivery_business_day: asNumber(record['max_delivery_business_day'] ?? record['maxDeliveryBusinessDay']),
|
|
2921
|
+
notes: asString(record['notes']),
|
|
2922
|
+
blacklisted_words: asNullableString(record['blacklisted_words'] ?? record['blacklistedWords']),
|
|
2923
|
+
position: asNumber(record['position']),
|
|
2924
|
+
min_weight: asNumber(record['min_weight'] ?? record['minWeight']),
|
|
2925
|
+
max_weight: asNumber(record['max_weight'] ?? record['maxWeight']),
|
|
2926
|
+
max_length: asNumber(record['max_length'] ?? record['maxLength']),
|
|
2927
|
+
max_width: asNumber(record['max_width'] ?? record['maxWidth']),
|
|
2928
|
+
max_height: asNumber(record['max_height'] ?? record['maxHeight']),
|
|
2929
|
+
markup: asNumber(record['markup']),
|
|
2930
|
+
surcharge: asNumber(record['surcharge']),
|
|
2931
|
+
insurance_benchmark: asNumber(record['insurance_benchmark'] ?? record['insuranceBenchmark']),
|
|
2932
|
+
insurance: asNumber(record['insurance']),
|
|
2933
|
+
clearing_handling: asNumber(record['clearing_handling'] ?? record['clearingHandling']),
|
|
2934
|
+
destination: asString(record['destination']),
|
|
2935
|
+
sea_only: asYesNo(record['sea_only'] ?? record['seaOnly']),
|
|
2936
|
+
currency: asString(record['currency']),
|
|
2937
|
+
type: asString(record['type']),
|
|
2938
|
+
active: asBoolean(record['active']),
|
|
2939
|
+
multiple_rates: asBoolean(record['multiple_rates'] ?? record['multipleRates']),
|
|
2940
|
+
first_shipment_discount: asNumber(record['first_shipment_discount'] ?? record['firstShipmentDiscount']),
|
|
2941
|
+
discount_type: asString(record['discount_type'] ?? record['discountType']),
|
|
2942
|
+
discount_active: asBoolean(record['discount_active'] ?? record['discountActive']),
|
|
2943
|
+
mode: asShippingMode(record['mode']),
|
|
2944
|
+
deleted_at: asNullableString(record['deleted_at'] ?? record['deletedAt']),
|
|
2945
|
+
created_at: asNullableString(record['created_at'] ?? record['createdAt']),
|
|
2946
|
+
updated_at: asNullableString(record['updated_at'] ?? record['updatedAt']),
|
|
2947
|
+
markdown: asNumber(record['markdown']),
|
|
2948
|
+
zone_values: mapShipmentMethodZonePage(record['zone_values'] ?? record['zoneValues']),
|
|
2949
|
+
};
|
|
2950
|
+
}
|
|
2951
|
+
/**
|
|
2952
|
+
* Map a list (or single object) payload into {@link ShipmentMethodModel}[].
|
|
2953
|
+
* @param raw
|
|
2954
|
+
*/
|
|
2955
|
+
function mapShipmentMethodList(raw) {
|
|
2956
|
+
return mapList(raw, mapShipmentMethod);
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
/** In-memory GET cache TTL for method reference dumps / by-id (5 minutes). */
|
|
2960
|
+
const SHIPMENT_METHOD_CACHE_TTL_MS = 5 * 60_000;
|
|
2961
|
+
/**
|
|
2962
|
+
* Shipment method / carrier utility reads (`GET /shipment_method/read/{id?}`).
|
|
2963
|
+
*
|
|
2964
|
+
* Uses the AFRICANIES {@link ResourceId} convention:
|
|
2965
|
+
* - `null` (default) → paginated page
|
|
2966
|
+
* - `'all'` → full list
|
|
2967
|
+
* - `number` → single {@link ShipmentMethodModel}
|
|
2968
|
+
*
|
|
2969
|
+
* @example
|
|
2970
|
+
* ```ts
|
|
2971
|
+
* const methods = inject(ShipmentMethodService);
|
|
2972
|
+
*
|
|
2973
|
+
* methods.readPage({ page: 1 }).subscribe((res) => {
|
|
2974
|
+
* console.log(res.data, res.pagination);
|
|
2975
|
+
* });
|
|
2976
|
+
* methods.readAll().subscribe((res) => console.log(res.data?.[0]?.name));
|
|
2977
|
+
* methods.readById(12).subscribe((res) => console.log(res.data?.name));
|
|
2978
|
+
* ```
|
|
2979
|
+
*/
|
|
2980
|
+
/**
|
|
2981
|
+
* Shipment method / carrier utility reads (`GET /shipment_method/read/{id?}`).
|
|
2982
|
+
*/
|
|
2983
|
+
class ShipmentMethodService {
|
|
2984
|
+
api = inject(ApiClient);
|
|
2985
|
+
read(id = null, params) {
|
|
2986
|
+
return this.api
|
|
2987
|
+
.get(buildResourcePath(SHIPMENT_METHOD_READ_PATH, id), {
|
|
2988
|
+
params: buildResourceQueryParams(id, params),
|
|
2989
|
+
cacheTtlMs: resourceCacheTtlMs(id, SHIPMENT_METHOD_CACHE_TTL_MS),
|
|
2990
|
+
})
|
|
2991
|
+
.pipe(map$1((res) => ({
|
|
2992
|
+
...res,
|
|
2993
|
+
data: mapResourcePayload(id, res.data, mapShipmentMethod, mapShipmentMethodList),
|
|
2994
|
+
})));
|
|
2995
|
+
}
|
|
2996
|
+
/**
|
|
2997
|
+
* Paginated page — alias for {@link read}(`null`, params).
|
|
2998
|
+
* @param params
|
|
2999
|
+
*/
|
|
3000
|
+
readPage(params) {
|
|
3001
|
+
return this.read(null, params);
|
|
3002
|
+
}
|
|
3003
|
+
/**
|
|
3004
|
+
* Full list — alias for {@link read}(`'all'`).
|
|
3005
|
+
* @param params
|
|
3006
|
+
*/
|
|
3007
|
+
readAll(params) {
|
|
3008
|
+
return this.read('all', params);
|
|
3009
|
+
}
|
|
3010
|
+
/**
|
|
3011
|
+
* Single record — alias for {@link read}(id).
|
|
3012
|
+
* @param id
|
|
3013
|
+
* @param params
|
|
3014
|
+
*/
|
|
3015
|
+
readById(id, params) {
|
|
3016
|
+
return this.read(id, params);
|
|
3017
|
+
}
|
|
3018
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ShipmentMethodService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3019
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ShipmentMethodService, providedIn: 'root' });
|
|
3020
|
+
}
|
|
3021
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ShipmentMethodService, decorators: [{
|
|
3022
|
+
type: Injectable,
|
|
3023
|
+
args: [{ providedIn: 'root' }]
|
|
3024
|
+
}] });
|
|
3025
|
+
|
|
3026
|
+
/**
|
|
3027
|
+
* Shipment method / carrier utility exports.
|
|
3028
|
+
*/
|
|
3029
|
+
|
|
3030
|
+
/** Warehouse read base path (relative to {@link AfricaniesSdkConfig.baseUrl}). */
|
|
3031
|
+
const WAREHOUSE_READ_PATH = '/warehouse/read';
|
|
3032
|
+
/**
|
|
3033
|
+
* Map warehouse state into {@link WarehouseStateModel} (snake_case).
|
|
3034
|
+
* @param raw
|
|
3035
|
+
*/
|
|
3036
|
+
function mapWarehouseState(raw) {
|
|
3037
|
+
const record = asRecord(raw);
|
|
3038
|
+
if (record === null) {
|
|
3039
|
+
return null;
|
|
3040
|
+
}
|
|
3041
|
+
return {
|
|
3042
|
+
id: asNumber(record['id']),
|
|
3043
|
+
name: asString(record['name']),
|
|
3044
|
+
state_code: asString(record['state_code'] ?? record['stateCode']),
|
|
3045
|
+
country: asString(record['country']),
|
|
3046
|
+
country_code: asString(record['country_code'] ?? record['countryCode']),
|
|
3047
|
+
};
|
|
3048
|
+
}
|
|
3049
|
+
/**
|
|
3050
|
+
* Map nested country (same shape as public country utility).
|
|
3051
|
+
* @param raw
|
|
3052
|
+
*/
|
|
3053
|
+
function mapWarehouseCountry(raw) {
|
|
3054
|
+
if (raw == null) {
|
|
3055
|
+
return null;
|
|
3056
|
+
}
|
|
3057
|
+
return mapCountry(raw);
|
|
3058
|
+
}
|
|
3059
|
+
/**
|
|
3060
|
+
* Map a wire warehouse into {@link WarehouseModel} (snake_case preserved).
|
|
3061
|
+
* @param raw
|
|
3062
|
+
*/
|
|
3063
|
+
function mapWarehouse(raw) {
|
|
3064
|
+
const record = asRecord(raw) ?? {};
|
|
3065
|
+
return {
|
|
3066
|
+
id: asNumber(record['id']),
|
|
3067
|
+
partner_id: asNullableNumber(record['partner_id'] ?? record['partnerId']),
|
|
3068
|
+
name: asString(record['name']),
|
|
3069
|
+
phone: asString(record['phone']),
|
|
3070
|
+
email: asString(record['email']),
|
|
3071
|
+
country: mapWarehouseCountry(record['country']),
|
|
3072
|
+
api_enabled: asBoolean(record['api_enabled'] ?? record['apiEnabled']),
|
|
3073
|
+
state: mapWarehouseState(record['state']),
|
|
3074
|
+
city: asString(record['city']),
|
|
3075
|
+
address: asString(record['address']),
|
|
3076
|
+
longitude: asNumber(record['longitude']),
|
|
3077
|
+
latitude: asNumber(record['latitude']),
|
|
3078
|
+
zip_code: asString(record['zip_code'] ?? record['zipCode']),
|
|
3079
|
+
usage: asNumber(record['usage']),
|
|
3080
|
+
active: asBoolean(record['active']),
|
|
3081
|
+
deleted_at: asNullableString(record['deleted_at'] ?? record['deletedAt']),
|
|
3082
|
+
created_at: asNullableString(record['created_at'] ?? record['createdAt']),
|
|
3083
|
+
updated_at: asNullableString(record['updated_at'] ?? record['updatedAt']),
|
|
3084
|
+
storage_charge: asNumber(record['storage_charge'] ?? record['storageCharge']),
|
|
3085
|
+
storage_period: asNumber(record['storage_period'] ?? record['storagePeriod']),
|
|
3086
|
+
delivery_charge: asNumber(record['delivery_charge'] ?? record['deliveryCharge']),
|
|
3087
|
+
delivery_count: asNumber(record['delivery_count'] ?? record['deliveryCount']),
|
|
3088
|
+
currency: asString(record['currency']),
|
|
3089
|
+
etw_shipment_available: asBoolean(record['etw_shipment_available'] ?? record['etwShipmentAvailable']),
|
|
3090
|
+
local: asBoolean(record['local']),
|
|
3091
|
+
no_shippo: asBoolean(record['no_shippo'] ?? record['noShippo']),
|
|
3092
|
+
partner: mapApiJsonValue(record['partner']),
|
|
3093
|
+
};
|
|
3094
|
+
}
|
|
3095
|
+
/**
|
|
3096
|
+
* Map a list (or single object) payload into {@link WarehouseModel}[].
|
|
3097
|
+
* @param raw
|
|
3098
|
+
*/
|
|
3099
|
+
function mapWarehouseList(raw) {
|
|
3100
|
+
return mapList(raw, mapWarehouse);
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
/** In-memory GET cache TTL for warehouse reference dumps / by-id (5 minutes). */
|
|
3104
|
+
const WAREHOUSE_CACHE_TTL_MS = 5 * 60_000;
|
|
3105
|
+
/**
|
|
3106
|
+
* Warehouse utility reads (`GET /warehouse/read/{id?}`).
|
|
3107
|
+
*
|
|
3108
|
+
* Uses the AFRICANIES {@link ResourceId} convention:
|
|
3109
|
+
* - `null` (default) → paginated page
|
|
3110
|
+
* - `'all'` → full list
|
|
3111
|
+
* - `number` → single {@link WarehouseModel}
|
|
3112
|
+
*
|
|
3113
|
+
* @example
|
|
3114
|
+
* ```ts
|
|
3115
|
+
* const warehouses = inject(WarehouseService);
|
|
3116
|
+
*
|
|
3117
|
+
* warehouses.readPage({ page: 1 }).subscribe((res) => {
|
|
3118
|
+
* console.log(res.data, res.pagination);
|
|
3119
|
+
* });
|
|
3120
|
+
* warehouses.readAll().subscribe((res) => console.log(res.data?.length));
|
|
3121
|
+
* warehouses.readById(37).subscribe((res) => console.log(res.data?.name));
|
|
3122
|
+
* ```
|
|
3123
|
+
*/
|
|
3124
|
+
/**
|
|
3125
|
+
* Warehouse utility reads (`GET /warehouse/read/{id?}`).
|
|
3126
|
+
*/
|
|
3127
|
+
class WarehouseService {
|
|
3128
|
+
api = inject(ApiClient);
|
|
3129
|
+
read(id = null, params) {
|
|
3130
|
+
return this.api
|
|
3131
|
+
.get(buildResourcePath(WAREHOUSE_READ_PATH, id), {
|
|
3132
|
+
toast: false,
|
|
3133
|
+
params: buildResourceQueryParams(id, params),
|
|
3134
|
+
cacheTtlMs: resourceCacheTtlMs(id, WAREHOUSE_CACHE_TTL_MS),
|
|
3135
|
+
})
|
|
3136
|
+
.pipe(map$1((res) => ({
|
|
3137
|
+
...res,
|
|
3138
|
+
data: mapResourcePayload(id, res.data, mapWarehouse, mapWarehouseList),
|
|
3139
|
+
})));
|
|
3140
|
+
}
|
|
3141
|
+
/**
|
|
3142
|
+
* Paginated page — alias for {@link read}(`null`, params).
|
|
3143
|
+
* @param params
|
|
3144
|
+
*/
|
|
3145
|
+
readPage(params) {
|
|
3146
|
+
return this.read(null, params);
|
|
3147
|
+
}
|
|
3148
|
+
/**
|
|
3149
|
+
* Full list — alias for {@link read}(`'all'`).
|
|
3150
|
+
* @param params
|
|
3151
|
+
*/
|
|
3152
|
+
readAll(params) {
|
|
3153
|
+
return this.read('all', params);
|
|
3154
|
+
}
|
|
3155
|
+
/**
|
|
3156
|
+
* Single record — alias for {@link read}(id).
|
|
3157
|
+
* @param id
|
|
3158
|
+
* @param params
|
|
3159
|
+
*/
|
|
3160
|
+
readById(id, params) {
|
|
3161
|
+
return this.read(id, params);
|
|
3162
|
+
}
|
|
3163
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: WarehouseService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3164
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: WarehouseService, providedIn: 'root' });
|
|
3165
|
+
}
|
|
3166
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: WarehouseService, decorators: [{
|
|
3167
|
+
type: Injectable,
|
|
3168
|
+
args: [{ providedIn: 'root' }]
|
|
3169
|
+
}] });
|
|
3170
|
+
|
|
3171
|
+
/**
|
|
3172
|
+
* Warehouse utility exports.
|
|
3173
|
+
*/
|
|
3174
|
+
|
|
3175
|
+
/** Zone read base path (relative to {@link AfricaniesSdkConfig.baseUrl}). */
|
|
3176
|
+
const ZONE_READ_PATH = '/zone/read/records';
|
|
3177
|
+
/**
|
|
3178
|
+
* Map a wire zone into {@link ZoneModel} (snake_case preserved).
|
|
3179
|
+
* @param raw
|
|
3180
|
+
*/
|
|
3181
|
+
function mapZone(raw) {
|
|
3182
|
+
const record = asRecord(raw) ?? {};
|
|
3183
|
+
return {
|
|
3184
|
+
id: asNumber(record['id']),
|
|
3185
|
+
name: asString(record['name']),
|
|
3186
|
+
type: asString(record['type']),
|
|
3187
|
+
active: asBoolean(record['active']),
|
|
3188
|
+
deleted_at: asNullableString(record['deleted_at'] ?? record['deletedAt']),
|
|
3189
|
+
created_at: asNullableString(record['created_at'] ?? record['createdAt']),
|
|
3190
|
+
updated_at: asNullableString(record['updated_at'] ?? record['updatedAt']),
|
|
3191
|
+
};
|
|
3192
|
+
}
|
|
3193
|
+
/**
|
|
3194
|
+
* Map a list (or single object) payload into {@link ZoneModel}[].
|
|
3195
|
+
* @param raw
|
|
3196
|
+
*/
|
|
3197
|
+
function mapZoneList(raw) {
|
|
3198
|
+
return mapList(raw, mapZone);
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
/** In-memory GET cache TTL for zone reference dumps / by-id (5 minutes). */
|
|
3202
|
+
const ZONE_CACHE_TTL_MS = 5 * 60_000;
|
|
3203
|
+
/**
|
|
3204
|
+
* Zone utility reads (`GET /zone/read/records/{id?}`).
|
|
3205
|
+
*
|
|
3206
|
+
* Uses the AFRICANIES {@link ResourceId} convention:
|
|
3207
|
+
* - `null` (default) → paginated page
|
|
3208
|
+
* - `'all'` → full list
|
|
3209
|
+
* - `number` → single {@link ZoneModel}
|
|
3210
|
+
*
|
|
3211
|
+
* @example
|
|
3212
|
+
* ```ts
|
|
3213
|
+
* const zones = inject(ZoneService);
|
|
3214
|
+
*
|
|
3215
|
+
* zones.readPage({ page: 1 }).subscribe((res) => {
|
|
3216
|
+
* console.log(res.data, res.pagination);
|
|
3217
|
+
* });
|
|
3218
|
+
* zones.readAll().subscribe((res) => console.log(res.data?.length));
|
|
3219
|
+
* zones.readById(1).subscribe((res) => console.log(res.data?.name));
|
|
3220
|
+
* ```
|
|
3221
|
+
*/
|
|
3222
|
+
/**
|
|
3223
|
+
* Zone utility reads (`GET /zone/read/records/{id?}`).
|
|
3224
|
+
*/
|
|
3225
|
+
class ZoneService {
|
|
3226
|
+
api = inject(ApiClient);
|
|
3227
|
+
read(id = null, params) {
|
|
3228
|
+
return this.api
|
|
3229
|
+
.get(buildResourcePath(ZONE_READ_PATH, id), {
|
|
3230
|
+
params: buildResourceQueryParams(id, params),
|
|
3231
|
+
cacheTtlMs: resourceCacheTtlMs(id, ZONE_CACHE_TTL_MS),
|
|
3232
|
+
})
|
|
3233
|
+
.pipe(map$1((res) => ({
|
|
3234
|
+
...res,
|
|
3235
|
+
data: mapResourcePayload(id, res.data, mapZone, mapZoneList),
|
|
3236
|
+
})));
|
|
3237
|
+
}
|
|
3238
|
+
/**
|
|
3239
|
+
* Paginated page — alias for {@link read}(`null`, params).
|
|
3240
|
+
* @param params
|
|
3241
|
+
*/
|
|
3242
|
+
readPage(params) {
|
|
3243
|
+
return this.read(null, params);
|
|
3244
|
+
}
|
|
3245
|
+
/**
|
|
3246
|
+
* Full list — alias for {@link read}(`'all'`).
|
|
3247
|
+
* @param params
|
|
3248
|
+
*/
|
|
3249
|
+
readAll(params) {
|
|
3250
|
+
return this.read('all', params);
|
|
3251
|
+
}
|
|
3252
|
+
/**
|
|
3253
|
+
* Single record — alias for {@link read}(id).
|
|
3254
|
+
* @param id
|
|
3255
|
+
* @param params
|
|
3256
|
+
*/
|
|
3257
|
+
readById(id, params) {
|
|
3258
|
+
return this.read(id, params);
|
|
3259
|
+
}
|
|
3260
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ZoneService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3261
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ZoneService, providedIn: 'root' });
|
|
3262
|
+
}
|
|
3263
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ZoneService, decorators: [{
|
|
3264
|
+
type: Injectable,
|
|
3265
|
+
args: [{ providedIn: 'root' }]
|
|
3266
|
+
}] });
|
|
3267
|
+
|
|
3268
|
+
/**
|
|
3269
|
+
* Zone utility exports.
|
|
3270
|
+
*/
|
|
3271
|
+
|
|
3272
|
+
/** Current-user path (relative to {@link AfricaniesSdkConfig.baseUrl}). */
|
|
3273
|
+
const USER_PATH = '/user';
|
|
3274
|
+
/** Change-password path after a default-password first login. */
|
|
3275
|
+
const USER_CHANGE_PASSWORD_PATH = '/user/change/password';
|
|
3276
|
+
/** Invalidate every session for the signed-in user. */
|
|
3277
|
+
const USER_LOGOUT_FROM_ALL_SESSIONS_PATH = '/user/logout-from-all-sessions';
|
|
3278
|
+
const ACCOUNT_TYPES = new Set(['business', 'individual']);
|
|
3279
|
+
const SHIPPING_TYPES = new Set(['instant', 'consolidation']);
|
|
3280
|
+
const PLAN_TYPES = new Set([
|
|
3281
|
+
'monthly',
|
|
3282
|
+
'quarterly',
|
|
3283
|
+
'biannually',
|
|
3284
|
+
'annually',
|
|
3285
|
+
]);
|
|
3286
|
+
const USER_MODEL_TYPES = new Set([
|
|
3287
|
+
'App\\Models\\Customer',
|
|
3288
|
+
'App\\Models\\Admin',
|
|
3289
|
+
]);
|
|
3290
|
+
/**
|
|
3291
|
+
* @param value - Candidate union member.
|
|
3292
|
+
* @param allowed - Allowed set.
|
|
3293
|
+
* @returns Value when allowed; otherwise `null`.
|
|
3294
|
+
*/
|
|
3295
|
+
function asEnumMember(value, allowed) {
|
|
3296
|
+
if (typeof value !== 'string') {
|
|
3297
|
+
return null;
|
|
3298
|
+
}
|
|
3299
|
+
return allowed.has(value) ? value : null;
|
|
3300
|
+
}
|
|
3301
|
+
/**
|
|
3302
|
+
* Map a state row under country.states.
|
|
3303
|
+
* @param raw - State object from the wire.
|
|
3304
|
+
* @returns {@link UserStateModel}, or `null`.
|
|
3305
|
+
*/
|
|
3306
|
+
function mapUserCountryState(raw) {
|
|
3307
|
+
const record = asRecord(raw);
|
|
3308
|
+
if (record === null) {
|
|
3309
|
+
return null;
|
|
3310
|
+
}
|
|
3311
|
+
return {
|
|
3312
|
+
name: asNullableString(record['name']),
|
|
3313
|
+
state_code: asNullableString(record['state_code'] ?? record['stateCode']),
|
|
3314
|
+
};
|
|
3315
|
+
}
|
|
3316
|
+
/**
|
|
3317
|
+
* Map nested country on the user profile.
|
|
3318
|
+
* @param raw - Country object from the wire.
|
|
3319
|
+
* @returns {@link UserCountryModel}, or `null`.
|
|
3320
|
+
*/
|
|
3321
|
+
function mapUserCountry(raw) {
|
|
3322
|
+
const record = asRecord(raw);
|
|
3323
|
+
if (record === null) {
|
|
3324
|
+
return null;
|
|
3325
|
+
}
|
|
3326
|
+
const states = Array.isArray(record['states'])
|
|
3327
|
+
? mapArray(record['states'], mapUserCountryState).filter((entry) => entry !== null)
|
|
3328
|
+
: null;
|
|
3329
|
+
return {
|
|
3330
|
+
id: asNullableNumber(record['id']),
|
|
3331
|
+
name: asNullableString(record['name']),
|
|
3332
|
+
iso3: asNullableString(record['iso3']),
|
|
3333
|
+
iso2: asNullableString(record['iso2']),
|
|
3334
|
+
states,
|
|
3335
|
+
};
|
|
3336
|
+
}
|
|
3337
|
+
/**
|
|
3338
|
+
* Normalize `state` when the API returns a string (or an object with `name`).
|
|
3339
|
+
* @param raw - Wire `state` field.
|
|
3340
|
+
* @returns String label, or `null`.
|
|
3341
|
+
*/
|
|
3342
|
+
function mapUserStateLabel(raw) {
|
|
3343
|
+
if (raw == null) {
|
|
3344
|
+
return null;
|
|
3345
|
+
}
|
|
3346
|
+
if (typeof raw === 'string') {
|
|
3347
|
+
return raw;
|
|
3348
|
+
}
|
|
3349
|
+
const record = asRecord(raw);
|
|
3350
|
+
if (record !== null) {
|
|
3351
|
+
return asNullableString(record['name'] ?? record['state']);
|
|
3352
|
+
}
|
|
3353
|
+
return null;
|
|
3354
|
+
}
|
|
3355
|
+
/**
|
|
3356
|
+
* @param raw - Plan package object.
|
|
3357
|
+
* @returns {@link UserPlanPackageModel}, or `null`.
|
|
3358
|
+
*/
|
|
3359
|
+
function mapUserPlanPackage(raw) {
|
|
3360
|
+
const record = asRecord(raw);
|
|
3361
|
+
if (record === null) {
|
|
3362
|
+
return null;
|
|
3363
|
+
}
|
|
3364
|
+
return {
|
|
3365
|
+
id: asNullableNumber(record['id']),
|
|
3366
|
+
plan_id: asNullableNumber(record['plan_id'] ?? record['planId']),
|
|
3367
|
+
company_service_id: asNullableNumber(record['company_service_id'] ?? record['companyServiceId']),
|
|
3368
|
+
name: asNullableString(record['name']),
|
|
3369
|
+
metrics: asNullableString(record['metrics']),
|
|
3370
|
+
volume: asNullableNumber(record['volume']),
|
|
3371
|
+
discount: asNullableString(record['discount']),
|
|
3372
|
+
model: asNullableString(record['model']),
|
|
3373
|
+
monthly: asNullableString(record['monthly']),
|
|
3374
|
+
quarterly: asNullableString(record['quarterly']),
|
|
3375
|
+
biannually: asNullableString(record['biannually']),
|
|
3376
|
+
annually: asNullableString(record['annually']),
|
|
3377
|
+
active: asNullableBoolean(record['active']),
|
|
3378
|
+
deleted_at: asNullableString(record['deleted_at'] ?? record['deletedAt']),
|
|
3379
|
+
created_at: asNullableString(record['created_at'] ?? record['createdAt']),
|
|
3380
|
+
updated_at: asNullableString(record['updated_at'] ?? record['updatedAt']),
|
|
3381
|
+
};
|
|
3382
|
+
}
|
|
3383
|
+
/**
|
|
3384
|
+
* @param raw - Plan object.
|
|
3385
|
+
* @returns {@link UserPlanModel}, or `null`.
|
|
3386
|
+
*/
|
|
3387
|
+
function mapUserPlan(raw) {
|
|
3388
|
+
const record = asRecord(raw);
|
|
3389
|
+
if (record === null) {
|
|
3390
|
+
return null;
|
|
3391
|
+
}
|
|
3392
|
+
const packages = Array.isArray(record['packages'])
|
|
3393
|
+
? mapArray(record['packages'], mapUserPlanPackage).filter((entry) => entry !== null)
|
|
3394
|
+
: null;
|
|
3395
|
+
return {
|
|
3396
|
+
id: asNullableNumber(record['id']),
|
|
3397
|
+
name: asNullableString(record['name']),
|
|
3398
|
+
active: asNullableBoolean(record['active']),
|
|
3399
|
+
deleted_at: asNullableString(record['deleted_at'] ?? record['deletedAt']),
|
|
3400
|
+
created_at: asNullableString(record['created_at'] ?? record['createdAt']),
|
|
3401
|
+
updated_at: asNullableString(record['updated_at'] ?? record['updatedAt']),
|
|
3402
|
+
packages,
|
|
3403
|
+
};
|
|
3404
|
+
}
|
|
3405
|
+
/**
|
|
3406
|
+
* @param raw - Gateway payload object.
|
|
3407
|
+
* @returns {@link UserGatewayPayloadModel}, or `null`.
|
|
3408
|
+
*/
|
|
3409
|
+
function mapUserGatewayPayload(raw) {
|
|
3410
|
+
const record = asRecord(raw);
|
|
3411
|
+
if (record === null) {
|
|
3412
|
+
return null;
|
|
3413
|
+
}
|
|
3414
|
+
return {
|
|
3415
|
+
authorization_url: asNullableString(record['authorization_url'] ?? record['authorizationUrl']),
|
|
3416
|
+
access_code: asNullableString(record['access_code'] ?? record['accessCode']),
|
|
3417
|
+
reference: asNullableString(record['reference']),
|
|
3418
|
+
redirect_url: asNullableString(record['redirect_url'] ?? record['redirectUrl']),
|
|
3419
|
+
};
|
|
3420
|
+
}
|
|
3421
|
+
/**
|
|
3422
|
+
* @param raw - Payment payload object (already parsed).
|
|
3423
|
+
* @returns {@link UserPaymentPayloadModel}, or `null`.
|
|
3424
|
+
*/
|
|
3425
|
+
function mapUserPaymentPayload(raw) {
|
|
3426
|
+
const record = asRecord(raw);
|
|
3427
|
+
if (record === null) {
|
|
3428
|
+
return null;
|
|
3429
|
+
}
|
|
3430
|
+
return {
|
|
3431
|
+
url: asNullableString(record['url']),
|
|
3432
|
+
redirect_url: asNullableString(record['redirect_url'] ?? record['redirectUrl']),
|
|
3433
|
+
gateway_payload: mapUserGatewayPayload(record['gateway_payload'] ?? record['gatewayPayload']),
|
|
3434
|
+
reference: asNullableString(record['reference']),
|
|
3435
|
+
};
|
|
3436
|
+
}
|
|
3437
|
+
/**
|
|
3438
|
+
* @param raw - Subscription object.
|
|
3439
|
+
* @returns {@link UserSubscriptionModel}, or `null`.
|
|
3440
|
+
*/
|
|
3441
|
+
function mapUserSubscription(raw) {
|
|
3442
|
+
const record = asRecord(raw);
|
|
3443
|
+
if (record === null) {
|
|
3444
|
+
return null;
|
|
3445
|
+
}
|
|
3446
|
+
return {
|
|
3447
|
+
id: asNullableNumber(record['id']),
|
|
3448
|
+
user_id: asNullableNumber(record['user_id'] ?? record['userId']),
|
|
3449
|
+
plan_id: asNullableNumber(record['plan_id'] ?? record['planId']),
|
|
3450
|
+
account_id: asNullableNumber(record['account_id'] ?? record['accountId']),
|
|
3451
|
+
reference: asNullableString(record['reference']),
|
|
3452
|
+
process_url: asNullableString(record['process_url'] ?? record['processUrl']),
|
|
3453
|
+
reference_salt: asNullableString(record['reference_salt'] ?? record['referenceSalt']),
|
|
3454
|
+
amount: asNullableString(record['amount']),
|
|
3455
|
+
currency: asNullableString(record['currency']),
|
|
3456
|
+
payment_amount: asNullableString(record['payment_amount'] ?? record['paymentAmount']),
|
|
3457
|
+
payment_currency: asNullableString(record['payment_currency'] ?? record['paymentCurrency']),
|
|
3458
|
+
coupon_id: asNullableNumber(record['coupon_id'] ?? record['couponId']),
|
|
3459
|
+
coupon_discount: asNullableString(record['coupon_discount'] ?? record['couponDiscount']),
|
|
3460
|
+
coupon_amount: asNullableString(record['coupon_amount'] ?? record['couponAmount']),
|
|
3461
|
+
payment_payload: asNullableString(record['payment_payload'] ?? record['paymentPayload']),
|
|
3462
|
+
plan_type: asEnumMember(record['plan_type'] ?? record['planType'], PLAN_TYPES),
|
|
3463
|
+
used: asNullableBoolean(record['used']),
|
|
3464
|
+
deleted_at: asNullableString(record['deleted_at'] ?? record['deletedAt']),
|
|
3465
|
+
created_at: asNullableString(record['created_at'] ?? record['createdAt']),
|
|
3466
|
+
updated_at: asNullableString(record['updated_at'] ?? record['updatedAt']),
|
|
3467
|
+
};
|
|
3468
|
+
}
|
|
3469
|
+
/**
|
|
3470
|
+
* @param raw - Business account object.
|
|
3471
|
+
* @returns {@link UserBusinessAccountModel}, or `null`.
|
|
3472
|
+
*/
|
|
3473
|
+
function mapUserBusinessAccount(raw) {
|
|
3474
|
+
const record = asRecord(raw);
|
|
3475
|
+
if (record === null) {
|
|
3476
|
+
return null;
|
|
3477
|
+
}
|
|
3478
|
+
return {
|
|
3479
|
+
id: asNullableNumber(record['id']),
|
|
3480
|
+
user_id: asNullableNumber(record['user_id'] ?? record['userId']),
|
|
3481
|
+
plan_id: asNullableNumber(record['plan_id'] ?? record['planId']),
|
|
3482
|
+
name: asNullableString(record['name']),
|
|
3483
|
+
account_email: asNullableString(record['account_email'] ?? record['accountEmail']),
|
|
3484
|
+
plan_type: asEnumMember(record['plan_type'] ?? record['planType'], PLAN_TYPES),
|
|
3485
|
+
first_payment: asNullableBoolean(record['first_payment'] ?? record['firstPayment']),
|
|
3486
|
+
is_whitelisted: asNullableBoolean(record['is_whitelisted'] ?? record['isWhitelisted']),
|
|
3487
|
+
no_state_validation: asNullableBoolean(record['no_state_validation'] ?? record['noStateValidation']),
|
|
3488
|
+
show_waybill: asNullableBoolean(record['show_waybill'] ?? record['showWaybill']),
|
|
3489
|
+
notify_api_shipment: asNullableBoolean(record['notify_api_shipment'] ?? record['notifyApiShipment']),
|
|
3490
|
+
active: asNullableBoolean(record['active']),
|
|
3491
|
+
expires_at: asNullableString(record['expires_at'] ?? record['expiresAt']),
|
|
3492
|
+
deleted_at: asNullableString(record['deleted_at'] ?? record['deletedAt']),
|
|
3493
|
+
created_at: asNullableString(record['created_at'] ?? record['createdAt']),
|
|
3494
|
+
updated_at: asNullableString(record['updated_at'] ?? record['updatedAt']),
|
|
3495
|
+
type: asEnumMember(record['type'], ACCOUNT_TYPES),
|
|
3496
|
+
days_left: asNullableNumber(record['days_left'] ?? record['daysLeft']),
|
|
3497
|
+
plan: mapUserPlan(record['plan']),
|
|
3498
|
+
subscription: mapUserSubscription(record['subscription']),
|
|
3499
|
+
};
|
|
3500
|
+
}
|
|
3501
|
+
/**
|
|
3502
|
+
* @param raw - Account manager object.
|
|
3503
|
+
* @returns {@link UserAccountManagerModel}, or `null`.
|
|
3504
|
+
*/
|
|
3505
|
+
function mapUserAccountManager(raw) {
|
|
3506
|
+
const record = asRecord(raw);
|
|
3507
|
+
if (record === null) {
|
|
3508
|
+
return null;
|
|
3509
|
+
}
|
|
3510
|
+
return {
|
|
3511
|
+
id: asNullableNumber(record['id']),
|
|
3512
|
+
user_id: asNullableNumber(record['user_id'] ?? record['userId']),
|
|
3513
|
+
manager_id: asNullableNumber(record['manager_id'] ?? record['managerId']),
|
|
3514
|
+
account_id: asNullableNumber(record['account_id'] ?? record['accountId']),
|
|
3515
|
+
name: asNullableString(record['name']),
|
|
3516
|
+
email: asNullableString(record['email']),
|
|
3517
|
+
phone: asNullableString(record['phone']),
|
|
3518
|
+
created_at: asNullableString(record['created_at'] ?? record['createdAt']),
|
|
3519
|
+
updated_at: asNullableString(record['updated_at'] ?? record['updatedAt']),
|
|
3520
|
+
deleted_at: asNullableString(record['deleted_at'] ?? record['deletedAt']),
|
|
3521
|
+
};
|
|
3522
|
+
}
|
|
3523
|
+
/**
|
|
3524
|
+
* Map a bare wire user object into {@link UserModel} (snake_case preserved).
|
|
3525
|
+
* @param raw - User object from `GET /user` (unwrapped).
|
|
3526
|
+
* @returns Normalized {@link UserModel}.
|
|
3527
|
+
*/
|
|
3528
|
+
function mapUser(raw) {
|
|
3529
|
+
const record = asRecord(raw) ?? {};
|
|
3530
|
+
return {
|
|
3531
|
+
id: asNullableNumber(record['id']),
|
|
3532
|
+
central_id: asNullableString(record['central_id'] ?? record['centralId']),
|
|
3533
|
+
name: asNullableString(record['name']),
|
|
3534
|
+
first_name: asNullableString(record['first_name'] ?? record['firstName']),
|
|
3535
|
+
middle_name: asNullableString(record['middle_name'] ?? record['middleName']),
|
|
3536
|
+
last_name: asNullableString(record['last_name'] ?? record['lastName']),
|
|
3537
|
+
email: asNullableString(record['email']),
|
|
3538
|
+
phone: asNullableString(record['phone']),
|
|
3539
|
+
unit_number: asNullableString(record['unit_number'] ?? record['unitNumber']),
|
|
3540
|
+
referral_code: asNullableString(record['referral_code'] ?? record['referralCode']),
|
|
3541
|
+
old_unit_number: asNullableString(record['old_unit_number'] ?? record['oldUnitNumber']),
|
|
3542
|
+
account_email: asNullableString(record['account_email'] ?? record['accountEmail']),
|
|
3543
|
+
two_factor: asNullableBoolean(record['two_factor'] ?? record['twoFactor']),
|
|
3544
|
+
default_pin: asNullableBoolean(record['default_pin'] ?? record['defaultPin']),
|
|
3545
|
+
model: asEnumMember(record['model'], USER_MODEL_TYPES),
|
|
3546
|
+
country: mapUserCountry(record['country']),
|
|
3547
|
+
state: mapUserStateLabel(record['state']),
|
|
3548
|
+
email_verified_at: asNullableString(record['email_verified_at'] ?? record['emailVerifiedAt']),
|
|
3549
|
+
phone_verified_at: asNullableString(record['phone_verified_at'] ?? record['phoneVerifiedAt']),
|
|
3550
|
+
kyc_verified_at: asNullableString(record['kyc_verified_at'] ?? record['kycVerifiedAt']),
|
|
3551
|
+
passport_verified_at: asNullableString(record['passport_verified_at'] ?? record['passportVerifiedAt']),
|
|
3552
|
+
suspended_at: asNullableString(record['suspended_at'] ?? record['suspendedAt']),
|
|
3553
|
+
deactivated_at: asNullableString(record['deactivated_at'] ?? record['deactivatedAt']),
|
|
3554
|
+
active: asNullableBoolean(record['active']),
|
|
3555
|
+
default_password: asNullableBoolean(record['default_password'] ?? record['defaultPassword']),
|
|
3556
|
+
type: asEnumMember(record['type'], ACCOUNT_TYPES),
|
|
3557
|
+
deleted_at: asNullableString(record['deleted_at'] ?? record['deletedAt']),
|
|
3558
|
+
created_at: asNullableString(record['created_at'] ?? record['createdAt']),
|
|
3559
|
+
updated_at: asNullableString(record['updated_at'] ?? record['updatedAt']),
|
|
3560
|
+
last_login_at: asNullableString(record['last_login_at'] ?? record['lastLoginAt']),
|
|
3561
|
+
socialite_signup: asNullableFlag01(record['socialite_signup'] ?? record['socialiteSignup']),
|
|
3562
|
+
form_signup: asNullableFlag01(record['form_signup'] ?? record['formSignup']),
|
|
3563
|
+
main_region: asNullableString(record['main_region'] ?? record['mainRegion']),
|
|
3564
|
+
shipping_type: asEnumMember(record['shipping_type'] ?? record['shippingType'], SHIPPING_TYPES),
|
|
3565
|
+
accounts: Array.isArray(record['accounts']) ? record['accounts'] : null,
|
|
3566
|
+
business_account: mapUserBusinessAccount(record['business_account'] ?? record['businessAccount']),
|
|
3567
|
+
account_manager: mapUserAccountManager(record['account_manager'] ?? record['accountManager']),
|
|
3568
|
+
};
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3571
|
+
/**
|
|
3572
|
+
* Current authenticated user (`GET /user`).
|
|
3573
|
+
*
|
|
3574
|
+
* The backend returns a **bare** user object (no `{ success, data }` wrapper).
|
|
3575
|
+
* {@link ApiClient} normalizes that into {@link ApiResponseModel}; this service
|
|
3576
|
+
* maps wire fields once into {@link UserModel} (snake_case preserved).
|
|
3577
|
+
*
|
|
3578
|
+
* Requires an access token via {@link AuthTokenService.set}. Not cached — profile
|
|
3579
|
+
* data is auth-sensitive and can change per session.
|
|
3580
|
+
*
|
|
3581
|
+
* After login, if `user.default_password` is set, send the user to
|
|
3582
|
+
* `/onboarding/reset-password` and call {@link changePassword} (current → new).
|
|
3583
|
+
* That page is **not** the email-link forgot-password flow.
|
|
3584
|
+
*
|
|
3585
|
+
* On logout, call {@link logoutFromAllSessions} while the bearer token is still
|
|
3586
|
+
* set, then {@link AuthTokenService.clear}.
|
|
3587
|
+
*
|
|
3588
|
+
* @example
|
|
3589
|
+
* ```ts
|
|
3590
|
+
* const users = inject(UserService);
|
|
3591
|
+
*
|
|
3592
|
+
* users.me().subscribe((res) => {
|
|
3593
|
+
* if (res.success) console.log(res.data?.email);
|
|
3594
|
+
* });
|
|
3595
|
+
* ```
|
|
3596
|
+
*/
|
|
3597
|
+
/**
|
|
3598
|
+
* Current authenticated user (`GET /user`).
|
|
3599
|
+
*/
|
|
3600
|
+
class UserService {
|
|
3601
|
+
api = inject(ApiClient);
|
|
3602
|
+
/**
|
|
3603
|
+
* Fetch the current user.
|
|
3604
|
+
*
|
|
3605
|
+
* @returns Normalized envelope with mapped {@link UserModel} (or `null` data).
|
|
3606
|
+
*/
|
|
3607
|
+
me() {
|
|
3608
|
+
return this.api.get(USER_PATH).pipe(map$1((res) => ({
|
|
3609
|
+
...res,
|
|
3610
|
+
data: res.data == null ? null : mapUser(res.data),
|
|
3611
|
+
})));
|
|
3612
|
+
}
|
|
3613
|
+
/**
|
|
3614
|
+
* Change the signed-in user’s password (`POST /user/change/password`).
|
|
3615
|
+
*
|
|
3616
|
+
* Used on first login when {@link UserModel.default_password} is true —
|
|
3617
|
+
* not the email-only {@link AuthService.forgot} flow.
|
|
3618
|
+
*
|
|
3619
|
+
* @param body - Current password plus new password and confirmation.
|
|
3620
|
+
* @returns Normalized envelope (`data` is typically unused).
|
|
3621
|
+
*/
|
|
3622
|
+
changePassword(body) {
|
|
3623
|
+
return this.api.post(USER_CHANGE_PASSWORD_PATH, {
|
|
3624
|
+
current_password: asString(body?.current_password),
|
|
3625
|
+
password: asString(body?.password),
|
|
3626
|
+
password_confirmation: asString(body?.password_confirmation),
|
|
3627
|
+
});
|
|
3628
|
+
}
|
|
3629
|
+
/**
|
|
3630
|
+
* Sign out of this device and every other session
|
|
3631
|
+
* (`POST /user/logout-from-all-sessions`).
|
|
3632
|
+
*
|
|
3633
|
+
* Call while the bearer token is still set so the interceptor can attach
|
|
3634
|
+
* `Authorization`. Then {@link AuthTokenService.clear} locally.
|
|
3635
|
+
*
|
|
3636
|
+
* @returns Normalized envelope (`data` is typically unused).
|
|
3637
|
+
*/
|
|
3638
|
+
logoutFromAllSessions() {
|
|
3639
|
+
return this.api.post(USER_LOGOUT_FROM_ALL_SESSIONS_PATH, {});
|
|
3640
|
+
}
|
|
3641
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UserService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3642
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UserService, providedIn: 'root' });
|
|
3643
|
+
}
|
|
3644
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UserService, decorators: [{
|
|
3645
|
+
type: Injectable,
|
|
3646
|
+
args: [{ providedIn: 'root' }]
|
|
3647
|
+
}] });
|
|
3648
|
+
|
|
3649
|
+
/** User notifications read base path (relative to {@link AfricaniesSdkConfig.baseUrl}). */
|
|
3650
|
+
const NOTIFICATION_READ_PATH = '/user/notifications/read';
|
|
3651
|
+
/** Mark read endpoint (single `{ id }` or `{}` for all). */
|
|
3652
|
+
const NOTIFICATION_UPDATE_PATH = '/user/notifications/update';
|
|
3653
|
+
/**
|
|
3654
|
+
* Parse the Laravel `data` column (JSON string or object) into
|
|
3655
|
+
* {@link NotificationPayloadModel}.
|
|
3656
|
+
* @param raw
|
|
3657
|
+
*/
|
|
3658
|
+
function mapNotificationPayload(raw) {
|
|
3659
|
+
let record = asRecord(raw);
|
|
3660
|
+
if (typeof raw === 'string' && raw.trim()) {
|
|
3661
|
+
try {
|
|
3662
|
+
record = asRecord(JSON.parse(raw));
|
|
3663
|
+
}
|
|
3664
|
+
catch {
|
|
3665
|
+
record = null;
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
const payload = record ?? {};
|
|
3669
|
+
const userId = payload['user_id'] ?? payload['userId'];
|
|
3670
|
+
return {
|
|
3671
|
+
user_id: userId == null ? null : asNumber(userId),
|
|
3672
|
+
title: asString(payload['title']),
|
|
3673
|
+
body: asNullableString(payload['body']),
|
|
3674
|
+
link: asNullableString(payload['link']),
|
|
3675
|
+
image: asNullableString(payload['image']),
|
|
3676
|
+
external_link: asNullableBoolean(payload['external_link'] ?? payload['externalLink']),
|
|
3677
|
+
};
|
|
3678
|
+
}
|
|
3679
|
+
/**
|
|
3680
|
+
* Map a wire notification row into {@link NotificationModel}.
|
|
3681
|
+
* @param raw
|
|
3682
|
+
*/
|
|
3683
|
+
function mapNotification(raw) {
|
|
3684
|
+
const record = asRecord(raw) ?? {};
|
|
3685
|
+
return {
|
|
3686
|
+
id: asString(record['id']),
|
|
3687
|
+
type: asString(record['type']),
|
|
3688
|
+
notifiable_type: asString(record['notifiable_type'] ?? record['notifiableType']),
|
|
3689
|
+
notifiable_id: asNumber(record['notifiable_id'] ?? record['notifiableId']),
|
|
3690
|
+
data: mapNotificationPayload(record['data']),
|
|
3691
|
+
read_at: asNullableString(record['read_at'] ?? record['readAt']),
|
|
3692
|
+
created_at: asNullableString(record['created_at'] ?? record['createdAt']),
|
|
3693
|
+
updated_at: asNullableString(record['updated_at'] ?? record['updatedAt']),
|
|
3694
|
+
};
|
|
3695
|
+
}
|
|
3696
|
+
/**
|
|
3697
|
+
* Map wire list payloads into {@link NotificationModel}[].
|
|
3698
|
+
* @param raw
|
|
3699
|
+
*/
|
|
3700
|
+
function mapNotificationList(raw) {
|
|
3701
|
+
return mapList(raw, mapNotification).filter((row) => row.id !== '');
|
|
3702
|
+
}
|
|
3703
|
+
/**
|
|
3704
|
+
* Derive a header/drawer inbox item from a mapped notification.
|
|
3705
|
+
* @param notification
|
|
3706
|
+
*/
|
|
3707
|
+
function mapNotificationInboxItem(notification) {
|
|
3708
|
+
const row = notification ?? mapNotification(null);
|
|
3709
|
+
const data = row.data ?? mapNotificationPayload(null);
|
|
3710
|
+
return {
|
|
3711
|
+
id: asString(row.id),
|
|
3712
|
+
title: asString(data.title),
|
|
3713
|
+
body: data.body ?? undefined,
|
|
3714
|
+
timestamp: row.created_at ?? undefined,
|
|
3715
|
+
read: row.read_at != null && row.read_at !== '',
|
|
3716
|
+
link: data.link ?? undefined,
|
|
3717
|
+
external_link: data.external_link ?? undefined,
|
|
3718
|
+
image: data.image,
|
|
3719
|
+
};
|
|
3720
|
+
}
|
|
3721
|
+
|
|
3722
|
+
/**
|
|
3723
|
+
* Authenticated user notifications (`GET /user/notifications/read/{id?}`).
|
|
3724
|
+
*
|
|
3725
|
+
* Uses the AFRICANIES {@link ResourceId} convention for paginated / full-list reads.
|
|
3726
|
+
* Notification primary keys are UUID strings — use {@link readOne} for a single row.
|
|
3727
|
+
*
|
|
3728
|
+
* Requires an access token via {@link AuthTokenService.set}. Not cached — inbox
|
|
3729
|
+
* data is auth-sensitive and changes frequently.
|
|
3730
|
+
*
|
|
3731
|
+
* @example
|
|
3732
|
+
* ```ts
|
|
3733
|
+
* const notifications = inject(NotificationService);
|
|
3734
|
+
*
|
|
3735
|
+
* notifications.readAll().subscribe((res) => {
|
|
3736
|
+
* console.log(res.data?.length);
|
|
3737
|
+
* });
|
|
3738
|
+
* ```
|
|
3739
|
+
*/
|
|
3740
|
+
/**
|
|
3741
|
+
* Authenticated user notifications (`GET /user/notifications/read/{id?}`).
|
|
3742
|
+
*/
|
|
3743
|
+
class NotificationService {
|
|
3744
|
+
api = inject(ApiClient);
|
|
3745
|
+
read(id = null, params) {
|
|
3746
|
+
const query = id === null
|
|
3747
|
+
? { ...params, size: params?.size ?? NOTIFICATION_PAGE_SIZE }
|
|
3748
|
+
: params;
|
|
3749
|
+
return this.api
|
|
3750
|
+
.get(buildResourcePath(NOTIFICATION_READ_PATH, id), {
|
|
3751
|
+
params: buildResourceQueryParams(id, query),
|
|
3752
|
+
})
|
|
3753
|
+
.pipe(map$1((res) => ({
|
|
3754
|
+
...res,
|
|
3755
|
+
data: mapResourcePayload(id, res?.data, mapNotification, mapNotificationList),
|
|
3756
|
+
})), catchError(() => of({
|
|
3757
|
+
success: false,
|
|
3758
|
+
message: null,
|
|
3759
|
+
data: [],
|
|
3760
|
+
errors: null,
|
|
3761
|
+
pagination: null,
|
|
3762
|
+
status_code: null,
|
|
3763
|
+
})));
|
|
3764
|
+
}
|
|
3765
|
+
/**
|
|
3766
|
+
* Paginated page — alias for {@link read}(`null`, params).
|
|
3767
|
+
* Size defaults to {@link NOTIFICATION_PAGE_SIZE} (`30`).
|
|
3768
|
+
* @param params
|
|
3769
|
+
*/
|
|
3770
|
+
readPage(params) {
|
|
3771
|
+
return this.read(null, params);
|
|
3772
|
+
}
|
|
3773
|
+
/**
|
|
3774
|
+
* Full list — alias for {@link read}(`'all'`).
|
|
3775
|
+
* @param params
|
|
3776
|
+
*/
|
|
3777
|
+
readAll(params) {
|
|
3778
|
+
return this.read('all', params);
|
|
3779
|
+
}
|
|
3780
|
+
/**
|
|
3781
|
+
* Single notification by UUID (`GET /user/notifications/read/{uuid}`).
|
|
3782
|
+
* @param id
|
|
3783
|
+
*/
|
|
3784
|
+
readOne(id) {
|
|
3785
|
+
const trimmed = asString(id).trim();
|
|
3786
|
+
return this.api
|
|
3787
|
+
.get(`${NOTIFICATION_READ_PATH}/${encodeURIComponent(trimmed)}`)
|
|
3788
|
+
.pipe(map$1((res) => ({
|
|
3789
|
+
...res,
|
|
3790
|
+
data: res.data == null
|
|
3791
|
+
? null
|
|
3792
|
+
: mapNotification(Array.isArray(res.data) ? res.data[0] : res.data),
|
|
3793
|
+
})));
|
|
3794
|
+
}
|
|
3795
|
+
/**
|
|
3796
|
+
* Mark one or all notifications read (`PUT /user/notifications/update`).
|
|
3797
|
+
*
|
|
3798
|
+
* @param id - When set, marks that notification read; omit to mark all read.
|
|
3799
|
+
*/
|
|
3800
|
+
markRead(id) {
|
|
3801
|
+
const body = id ? { id: asString(id) } : {};
|
|
3802
|
+
return this.api.put(NOTIFICATION_UPDATE_PATH, body);
|
|
3803
|
+
}
|
|
3804
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: NotificationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3805
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: NotificationService, providedIn: 'root' });
|
|
3806
|
+
}
|
|
3807
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: NotificationService, decorators: [{
|
|
3808
|
+
type: Injectable,
|
|
3809
|
+
args: [{ providedIn: 'root' }]
|
|
3810
|
+
}] });
|
|
3811
|
+
|
|
3812
|
+
const SHIPMENT_APP_BY_MODE = {
|
|
3813
|
+
stn: 'shiptonaija',
|
|
3814
|
+
sfn: 'shipfromnaija',
|
|
3815
|
+
};
|
|
3816
|
+
const QUERY_SHIPMENT_APP = 'shipment_app';
|
|
3817
|
+
const LEGACY_PORTAL_HOST = /^(?:test-)?(?:admin|customer)-(?:export|import)\.africaniestest\.com$/i;
|
|
3818
|
+
/**
|
|
3819
|
+
* Signed export downloads and other hosts that must not be rewritten in-app.
|
|
3820
|
+
* @param link
|
|
3821
|
+
*/
|
|
3822
|
+
function isNotificationExternalLink(link) {
|
|
3823
|
+
const trimmed = decodeNotificationLink(link).trim();
|
|
3824
|
+
if (!trimmed) {
|
|
3825
|
+
return false;
|
|
3826
|
+
}
|
|
3827
|
+
try {
|
|
3828
|
+
const url = new URL(trimmed);
|
|
3829
|
+
if (/\.(xlsx|xls|csv)(\?|$)/i.test(url.pathname)) {
|
|
3830
|
+
return true;
|
|
3831
|
+
}
|
|
3832
|
+
if (/\.amazonaws\.com$/i.test(url.hostname)) {
|
|
3833
|
+
return true;
|
|
3834
|
+
}
|
|
3835
|
+
return !isLegacyPortalHost(url.hostname);
|
|
3836
|
+
}
|
|
3837
|
+
catch {
|
|
3838
|
+
return false;
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
/**
|
|
3842
|
+
* Rewrite a notification href for the active {@link ShippingMode}.
|
|
3843
|
+
*
|
|
3844
|
+
* - Export / download URLs (S3, spreadsheets) pass through unchanged (no `shipment_app`).
|
|
3845
|
+
* - Legacy portal hosts become relative `/portal/...` paths with query preserved.
|
|
3846
|
+
* - When the notification already includes `shipment_app`, it is kept as-is.
|
|
3847
|
+
* - Otherwise classic host / `shipment_app` rewriting applies for portal URLs.
|
|
3848
|
+
* @param link
|
|
3849
|
+
* @param mode
|
|
3850
|
+
*/
|
|
3851
|
+
function resolveNotificationLinkForMode(link, mode) {
|
|
3852
|
+
const trimmed = decodeNotificationLink(asString(link)).trim();
|
|
3853
|
+
if (!trimmed) {
|
|
3854
|
+
return trimmed;
|
|
3855
|
+
}
|
|
3856
|
+
if (isNotificationExternalLink(trimmed)) {
|
|
3857
|
+
return stripShipmentAppFromLink(trimmed);
|
|
3858
|
+
}
|
|
3859
|
+
const relativePortal = toRelativePortalNotificationLink(trimmed);
|
|
3860
|
+
if (relativePortal && hasShipmentAppQuery(relativePortal)) {
|
|
3861
|
+
return relativePortal;
|
|
3862
|
+
}
|
|
3863
|
+
const relative = normalizeRelativePortalLink(trimmed);
|
|
3864
|
+
if (relative && hasShipmentAppQuery(relative)) {
|
|
3865
|
+
return relative;
|
|
3866
|
+
}
|
|
3867
|
+
try {
|
|
3868
|
+
const url = new URL(trimmed);
|
|
3869
|
+
if (url.searchParams.has(QUERY_SHIPMENT_APP)) {
|
|
3870
|
+
return trimmed;
|
|
3871
|
+
}
|
|
3872
|
+
url.searchParams.set(QUERY_SHIPMENT_APP, SHIPMENT_APP_BY_MODE[mode]);
|
|
3873
|
+
url.hostname = rewriteNotificationHostForMode(url.hostname, mode);
|
|
3874
|
+
return url.toString();
|
|
3875
|
+
}
|
|
3876
|
+
catch {
|
|
3877
|
+
return rewriteNotificationLinkFallback(trimmed, mode);
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
function rewriteNotificationHostForMode(hostname, mode) {
|
|
3881
|
+
if (mode === 'stn') {
|
|
3882
|
+
return hostname.replace(/-export/i, '-import');
|
|
3883
|
+
}
|
|
3884
|
+
return hostname.replace(/-import/i, '-export');
|
|
3885
|
+
}
|
|
3886
|
+
function rewriteNotificationLinkFallback(link, mode) {
|
|
3887
|
+
if (hasShipmentAppQuery(link)) {
|
|
3888
|
+
return link;
|
|
3889
|
+
}
|
|
3890
|
+
let out = link.replace(/shipment_app=(?:shipfromnaija|shiptonaija)/gi, `shipment_app=${SHIPMENT_APP_BY_MODE[mode]}`);
|
|
3891
|
+
if (!/shipment_app=/i.test(out)) {
|
|
3892
|
+
const joiner = out.includes('?') ? '&' : '?';
|
|
3893
|
+
out = `${out}${joiner}shipment_app=${SHIPMENT_APP_BY_MODE[mode]}`;
|
|
3894
|
+
}
|
|
3895
|
+
if (mode === 'stn') {
|
|
3896
|
+
return out.replace(/-export/gi, '-import');
|
|
3897
|
+
}
|
|
3898
|
+
return out.replace(/-import/gi, '-export');
|
|
3899
|
+
}
|
|
3900
|
+
function toRelativePortalNotificationLink(link) {
|
|
3901
|
+
try {
|
|
3902
|
+
const url = new URL(link);
|
|
3903
|
+
if (!isLegacyPortalHost(url.hostname)) {
|
|
3904
|
+
return null;
|
|
3905
|
+
}
|
|
3906
|
+
let path = url.pathname.replace(/^\/+/, '');
|
|
3907
|
+
if (path.startsWith('ng/portal/')) {
|
|
3908
|
+
path = path.slice('ng/'.length);
|
|
3909
|
+
}
|
|
3910
|
+
if (!path.startsWith('portal/') && path !== 'portal') {
|
|
3911
|
+
return null;
|
|
3912
|
+
}
|
|
3913
|
+
const query = url.searchParams.toString();
|
|
3914
|
+
return `/${path}${query ? `?${query}` : ''}`;
|
|
3915
|
+
}
|
|
3916
|
+
catch {
|
|
3917
|
+
return null;
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
function normalizeRelativePortalLink(link) {
|
|
3921
|
+
let path = link.split('?')[0]?.split('#')[0] ?? '';
|
|
3922
|
+
const query = link.includes('?') ? link.slice(link.indexOf('?')) : '';
|
|
3923
|
+
path = path.replace(/^\/+/, '');
|
|
3924
|
+
if (path.startsWith('ng/portal/')) {
|
|
3925
|
+
path = path.slice('ng/'.length);
|
|
3926
|
+
}
|
|
3927
|
+
if (!path.startsWith('portal/') && path !== 'portal') {
|
|
3928
|
+
return null;
|
|
3929
|
+
}
|
|
3930
|
+
return `/${path}${query}`;
|
|
3931
|
+
}
|
|
3932
|
+
function stripShipmentAppFromLink(link) {
|
|
3933
|
+
const trimmed = link.trim();
|
|
3934
|
+
if (!trimmed) {
|
|
3935
|
+
return trimmed;
|
|
3936
|
+
}
|
|
3937
|
+
try {
|
|
3938
|
+
const url = new URL(trimmed);
|
|
3939
|
+
if (!url.searchParams.has(QUERY_SHIPMENT_APP)) {
|
|
3940
|
+
return trimmed;
|
|
3941
|
+
}
|
|
3942
|
+
url.searchParams.delete(QUERY_SHIPMENT_APP);
|
|
3943
|
+
return url.toString();
|
|
3944
|
+
}
|
|
3945
|
+
catch {
|
|
3946
|
+
const params = new URLSearchParams(trimmed.includes('?') ? trimmed.slice(trimmed.indexOf('?') + 1) : '');
|
|
3947
|
+
if (!params.has(QUERY_SHIPMENT_APP)) {
|
|
3948
|
+
return trimmed;
|
|
3949
|
+
}
|
|
3950
|
+
params.delete(QUERY_SHIPMENT_APP);
|
|
3951
|
+
const query = params.toString();
|
|
3952
|
+
const base = trimmed.split('?')[0] ?? trimmed;
|
|
3953
|
+
return query ? `${base}?${query}` : base;
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
function hasShipmentAppQuery(link) {
|
|
3957
|
+
try {
|
|
3958
|
+
const query = link.includes('?') ? link.slice(link.indexOf('?') + 1) : '';
|
|
3959
|
+
return new URLSearchParams(query).has(QUERY_SHIPMENT_APP);
|
|
3960
|
+
}
|
|
3961
|
+
catch {
|
|
3962
|
+
return /(?:^|[?&])shipment_app=/i.test(link);
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
function decodeNotificationLink(link) {
|
|
3966
|
+
let value = (link ?? '').trim();
|
|
3967
|
+
if (!value.includes('%')) {
|
|
3968
|
+
return value;
|
|
3969
|
+
}
|
|
3970
|
+
try {
|
|
3971
|
+
const decoded = decodeURIComponent(value);
|
|
3972
|
+
if (decoded !== value) {
|
|
3973
|
+
value = decoded;
|
|
3974
|
+
}
|
|
3975
|
+
}
|
|
3976
|
+
catch {
|
|
3977
|
+
// Keep original when not valid URI encoding.
|
|
3978
|
+
}
|
|
3979
|
+
return value;
|
|
3980
|
+
}
|
|
3981
|
+
function isLegacyPortalHost(hostname) {
|
|
3982
|
+
return LEGACY_PORTAL_HOST.test(hostname);
|
|
3983
|
+
}
|
|
3984
|
+
|
|
3985
|
+
/**
|
|
3986
|
+
* File utility reads (`POST /file/read`).
|
|
3987
|
+
*
|
|
3988
|
+
* Primary preview path when a record stores a `file_ref` string (shipments,
|
|
3989
|
+
* tracking items, waybills, KYC, etc.). Body `{ ref }` → `data` with
|
|
3990
|
+
* `mime_type` and `base_64` for `<img>` / PDF viewers.
|
|
3991
|
+
*
|
|
3992
|
+
* Document catalog previews use {@link DocumentService.readById} instead
|
|
3993
|
+
* (`GET /public/document/read/{id}` → `data.file_ref.base_64`).
|
|
3994
|
+
*
|
|
3995
|
+
* @example
|
|
3996
|
+
* ```ts
|
|
3997
|
+
* const files = inject(FileService);
|
|
3998
|
+
*
|
|
3999
|
+
* files.read(item.file_ref).subscribe((res) => {
|
|
4000
|
+
* if (res.success) console.log(res.data?.base_64);
|
|
4001
|
+
* });
|
|
4002
|
+
*
|
|
4003
|
+
* files.readMultiple(waybillRef).subscribe((res) => {
|
|
4004
|
+
* console.log(res.data?.map((f) => f.mime_type));
|
|
4005
|
+
* });
|
|
4006
|
+
* ```
|
|
4007
|
+
*/
|
|
4008
|
+
/**
|
|
4009
|
+
* File utility reads (`POST /file/read`).
|
|
4010
|
+
*/
|
|
4011
|
+
class FileService {
|
|
4012
|
+
api = inject(ApiClient);
|
|
4013
|
+
/**
|
|
4014
|
+
* Resolve one file reference (`POST /file/read`, body `{ ref }`).
|
|
4015
|
+
*
|
|
4016
|
+
* @param ref - Storage / document UUID from `file_ref` on a record.
|
|
4017
|
+
* @returns Normalized envelope with mapped {@link FileReadModel}.
|
|
4018
|
+
*/
|
|
4019
|
+
read(ref) {
|
|
4020
|
+
return this.readByBody({ ref: asString(ref) });
|
|
4021
|
+
}
|
|
4022
|
+
/**
|
|
4023
|
+
* Resolve multiple files for one ref (`POST /file/read?multiple=yes`).
|
|
4024
|
+
*
|
|
4025
|
+
* E-commerce waybill flows may return several pages in `data[]`.
|
|
4026
|
+
*
|
|
4027
|
+
* @param ref - Storage reference token.
|
|
4028
|
+
* @returns Normalized envelope with mapped {@link FileReadModel}[].
|
|
4029
|
+
*/
|
|
4030
|
+
readMultiple(ref) {
|
|
4031
|
+
return this.readByBodyMultiple({ ref: asString(ref) });
|
|
4032
|
+
}
|
|
4033
|
+
/**
|
|
4034
|
+
* Resolve a file from an explicit request body.
|
|
4035
|
+
*
|
|
4036
|
+
* @param body - Wire body (`{ ref }`).
|
|
4037
|
+
* @returns Normalized envelope with mapped {@link FileReadModel}.
|
|
4038
|
+
*/
|
|
4039
|
+
readByBody(body) {
|
|
4040
|
+
return this.api
|
|
4041
|
+
.post(FILE_READ_PATH, {
|
|
4042
|
+
ref: asString(body?.ref),
|
|
4043
|
+
})
|
|
4044
|
+
.pipe(map$1((res) => ({
|
|
4045
|
+
...res,
|
|
4046
|
+
data: res.data == null ? null : mapFileRead(res.data),
|
|
4047
|
+
})));
|
|
4048
|
+
}
|
|
4049
|
+
/**
|
|
4050
|
+
* Multi-file variant of {@link readByBody}.
|
|
4051
|
+
*
|
|
4052
|
+
* @param body - Wire body (`{ ref }`).
|
|
4053
|
+
* @returns Normalized envelope with mapped {@link FileReadModel}[].
|
|
4054
|
+
*/
|
|
4055
|
+
readByBodyMultiple(body) {
|
|
4056
|
+
return this.api
|
|
4057
|
+
.post(FILE_READ_PATH, { ref: asString(body?.ref) }, { params: { multiple: FILE_READ_MULTIPLE_PARAM } })
|
|
4058
|
+
.pipe(map$1((res) => ({
|
|
4059
|
+
...res,
|
|
4060
|
+
data: res.data == null ? null : mapFileReadList(res.data),
|
|
4061
|
+
})));
|
|
4062
|
+
}
|
|
4063
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FileService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4064
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FileService, providedIn: 'root' });
|
|
4065
|
+
}
|
|
4066
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FileService, decorators: [{
|
|
4067
|
+
type: Injectable,
|
|
4068
|
+
args: [{ providedIn: 'root' }]
|
|
4069
|
+
}] });
|
|
4070
|
+
|
|
4071
|
+
/**
|
|
4072
|
+
* File utility exports.
|
|
4073
|
+
*/
|
|
4074
|
+
|
|
4075
|
+
/** Product read base path (relative to {@link AfricaniesSdkConfig.baseUrl}). */
|
|
4076
|
+
const PRODUCT_READ_PATH = '/product/read';
|
|
4077
|
+
function asNullableNumberArray(value) {
|
|
4078
|
+
if (value == null || !Array.isArray(value)) {
|
|
4079
|
+
return null;
|
|
4080
|
+
}
|
|
4081
|
+
return mapArray(value, (entry) => asNumber(entry));
|
|
4082
|
+
}
|
|
4083
|
+
/**
|
|
4084
|
+
* Map a wire product into {@link ProductModel} (snake_case preserved).
|
|
4085
|
+
* @param raw - Single product object from the API.
|
|
4086
|
+
* @returns Normalized {@link ProductModel}.
|
|
4087
|
+
*/
|
|
4088
|
+
function mapProduct(raw) {
|
|
4089
|
+
const record = asRecord(raw) ?? {};
|
|
4090
|
+
return {
|
|
4091
|
+
id: asNumber(record['id']),
|
|
4092
|
+
account_id: asNullableNumber(record['account_id'] ?? record['accountId']),
|
|
4093
|
+
product_category_id: asNullableNumber(record['product_category_id'] ?? record['productCategoryId']),
|
|
4094
|
+
hs_code: asString(record['hs_code'] ?? record['hsCode']),
|
|
4095
|
+
hs_code_10: asNullableString(record['hs_code_10'] ?? record['hsCode10']),
|
|
4096
|
+
hs_code_8: asNullableString(record['hs_code_8'] ?? record['hsCode8']),
|
|
4097
|
+
hs_code_6: asNullableString(record['hs_code_6'] ?? record['hsCode6']),
|
|
4098
|
+
name: asString(record['name']),
|
|
4099
|
+
value: asNumber(record['value']),
|
|
4100
|
+
usage: asNumber(record['usage']),
|
|
4101
|
+
document_ids: asNullableNumberArray(record['document_ids'] ?? record['documentIds']),
|
|
4102
|
+
etw_ids: asNullableNumberArray(record['etw_ids'] ?? record['etwIds']),
|
|
4103
|
+
active: asBoolean(record['active']),
|
|
4104
|
+
is_external: asBoolean(record['is_external'] ?? record['isExternal']),
|
|
4105
|
+
deleted_at: asNullableString(record['deleted_at'] ?? record['deletedAt']),
|
|
4106
|
+
created_at: asNullableString(record['created_at'] ?? record['createdAt']),
|
|
4107
|
+
updated_at: asNullableString(record['updated_at'] ?? record['updatedAt']),
|
|
4108
|
+
document_details: mapArray(record['document_details'] ?? record['documentDetails'], (entry) => asString(entry)),
|
|
4109
|
+
etw_document_details: mapArray(record['etw_document_details'] ?? record['etwDocumentDetails'], (entry) => asString(entry)),
|
|
4110
|
+
zone_product_required_documents: mapApiJsonList(record['zone_product_required_documents'] ??
|
|
4111
|
+
record['zoneProductRequiredDocuments']),
|
|
4112
|
+
};
|
|
4113
|
+
}
|
|
4114
|
+
/**
|
|
4115
|
+
* Map a list (or single object) payload into {@link ProductModel}[].
|
|
4116
|
+
* @param raw - `data` payload from `/product/read/{id|all}`.
|
|
4117
|
+
* @returns Mapped product list.
|
|
4118
|
+
*/
|
|
4119
|
+
function mapProductList(raw) {
|
|
4120
|
+
return mapList(raw, mapProduct);
|
|
4121
|
+
}
|
|
4122
|
+
|
|
4123
|
+
/** In-memory GET cache TTL for product reference dumps / by-id (5 minutes). */
|
|
4124
|
+
const PRODUCT_CACHE_TTL_MS = 5 * 60_000;
|
|
4125
|
+
/**
|
|
4126
|
+
* Product utility reads (`GET /product/read/{id?}`).
|
|
4127
|
+
*
|
|
4128
|
+
* Uses the AFRICANIES {@link ResourceId} convention:
|
|
4129
|
+
* - `null` (default) → paginated page
|
|
4130
|
+
* - `'all'` → full list
|
|
4131
|
+
* - `number` → single {@link ProductModel}
|
|
4132
|
+
*
|
|
4133
|
+
* @example
|
|
4134
|
+
* ```ts
|
|
4135
|
+
* const products = inject(ProductService);
|
|
4136
|
+
*
|
|
4137
|
+
* products.readPage({ page: 1 }).subscribe((res) => {
|
|
4138
|
+
* console.log(res.data, res.pagination);
|
|
4139
|
+
* });
|
|
4140
|
+
* products.readAll().subscribe((res) => console.log(res.data?.[0]?.hs_code));
|
|
4141
|
+
* products.readById(6280).subscribe((res) => console.log(res.data?.name));
|
|
4142
|
+
* ```
|
|
4143
|
+
*/
|
|
4144
|
+
/**
|
|
4145
|
+
* Product utility reads (`GET /product/read/{id?}`).
|
|
4146
|
+
*/
|
|
4147
|
+
class ProductService {
|
|
4148
|
+
api = inject(ApiClient);
|
|
4149
|
+
read(id = null, params) {
|
|
4150
|
+
return this.api
|
|
4151
|
+
.get(buildResourcePath(PRODUCT_READ_PATH, id), {
|
|
4152
|
+
toast: false,
|
|
4153
|
+
params: buildResourceQueryParams(id, params),
|
|
4154
|
+
cacheTtlMs: resourceCacheTtlMs(id, PRODUCT_CACHE_TTL_MS),
|
|
4155
|
+
})
|
|
4156
|
+
.pipe(map$1((res) => ({
|
|
4157
|
+
...res,
|
|
4158
|
+
data: mapResourcePayload(id, res.data, mapProduct, mapProductList),
|
|
4159
|
+
})));
|
|
4160
|
+
}
|
|
4161
|
+
/**
|
|
4162
|
+
* Paginated page — alias for {@link read}(`null`, params).
|
|
4163
|
+
* @param params
|
|
4164
|
+
*/
|
|
4165
|
+
readPage(params) {
|
|
4166
|
+
return this.read(null, params);
|
|
4167
|
+
}
|
|
4168
|
+
/**
|
|
4169
|
+
* Full list — alias for {@link read}(`'all'`).
|
|
4170
|
+
* @param params
|
|
4171
|
+
*/
|
|
4172
|
+
readAll(params) {
|
|
4173
|
+
return this.read('all', params);
|
|
4174
|
+
}
|
|
4175
|
+
/**
|
|
4176
|
+
* Single record — alias for {@link read}(id).
|
|
4177
|
+
* @param id
|
|
4178
|
+
* @param params
|
|
4179
|
+
*/
|
|
4180
|
+
readById(id, params) {
|
|
4181
|
+
return this.read(id, params);
|
|
4182
|
+
}
|
|
4183
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ProductService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4184
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ProductService, providedIn: 'root' });
|
|
4185
|
+
}
|
|
4186
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ProductService, decorators: [{
|
|
4187
|
+
type: Injectable,
|
|
4188
|
+
args: [{ providedIn: 'root' }]
|
|
4189
|
+
}] });
|
|
4190
|
+
|
|
4191
|
+
/**
|
|
4192
|
+
* Product utility exports.
|
|
4193
|
+
*/
|
|
4194
|
+
|
|
4195
|
+
/**
|
|
4196
|
+
* Select fields that declare a non-static {@link FilterOptionsSource}.
|
|
4197
|
+
*
|
|
4198
|
+
* @param config - Module filter schema.
|
|
4199
|
+
* @returns Fields that need async option resolution.
|
|
4200
|
+
*/
|
|
4201
|
+
function collectResolvableSelectFields(config) {
|
|
4202
|
+
return asArray(config?.fields).filter((field) => field.type === 'select' &&
|
|
4203
|
+
field.optionsSource != null &&
|
|
4204
|
+
field.optionsSource !== 'static');
|
|
4205
|
+
}
|
|
4206
|
+
/**
|
|
4207
|
+
* Unique {@link FilterOptionsSource} values referenced by a config (excluding static).
|
|
4208
|
+
*
|
|
4209
|
+
* @param config - Module filter schema.
|
|
4210
|
+
*/
|
|
4211
|
+
function collectFilterOptionsSources(config) {
|
|
4212
|
+
const sources = new Set();
|
|
4213
|
+
for (const field of collectResolvableSelectFields(config)) {
|
|
4214
|
+
sources.add(field.optionsSource);
|
|
4215
|
+
}
|
|
4216
|
+
return [...sources];
|
|
4217
|
+
}
|
|
4218
|
+
/**
|
|
4219
|
+
* Map {@link WarehouseModel}[] into filter select options (`value` = id string).
|
|
4220
|
+
*
|
|
4221
|
+
* @param rows - Warehouses from {@link WarehouseService.readAll}.
|
|
4222
|
+
*/
|
|
4223
|
+
function mapWarehouseFilterOptions(rows) {
|
|
4224
|
+
if (!rows?.length) {
|
|
4225
|
+
return [];
|
|
4226
|
+
}
|
|
4227
|
+
return rows.map((row) => ({
|
|
4228
|
+
value: String(row.id),
|
|
4229
|
+
label: row.name,
|
|
4230
|
+
}));
|
|
4231
|
+
}
|
|
4232
|
+
/**
|
|
4233
|
+
* Map {@link ShipmentMethodModel}[] into filter select options.
|
|
4234
|
+
*
|
|
4235
|
+
* @param rows - Methods from {@link ShipmentMethodService.readAll}.
|
|
4236
|
+
*/
|
|
4237
|
+
function mapShipmentMethodFilterOptions(rows) {
|
|
4238
|
+
if (!rows?.length) {
|
|
4239
|
+
return [];
|
|
4240
|
+
}
|
|
4241
|
+
return rows.map((row) => ({
|
|
4242
|
+
value: String(row.id),
|
|
4243
|
+
label: row.name,
|
|
4244
|
+
}));
|
|
4245
|
+
}
|
|
4246
|
+
/**
|
|
4247
|
+
* Merge SDK-resolved lists with host overrides (host wins per field key).
|
|
4248
|
+
*
|
|
4249
|
+
* @param resolved - Output from {@link FilterOptionsResolver.resolve}.
|
|
4250
|
+
* @param overrides - Host catalogs (e.g. manifests without a built-in service).
|
|
4251
|
+
*/
|
|
4252
|
+
function mergeFilterOptionLists(resolved, overrides) {
|
|
4253
|
+
if (!overrides) {
|
|
4254
|
+
return { ...resolved };
|
|
4255
|
+
}
|
|
4256
|
+
return { ...resolved, ...overrides };
|
|
4257
|
+
}
|
|
4258
|
+
|
|
4259
|
+
/**
|
|
4260
|
+
* Built-in {@link FilterOptionsSource} keys wired to SDK catalog services.
|
|
4261
|
+
*
|
|
4262
|
+
* `shipmentManifests` has no built-in service yet — resolve to `[]` and pass
|
|
4263
|
+
* host `optionLists` for those field keys.
|
|
4264
|
+
*/
|
|
4265
|
+
const SDK_FILTER_OPTIONS_SOURCES = new Set([
|
|
4266
|
+
'warehouses',
|
|
4267
|
+
'shipmentMethods',
|
|
4268
|
+
]);
|
|
4269
|
+
/**
|
|
4270
|
+
* Resolves filter drawer select options from built-in SDK catalog services.
|
|
4271
|
+
*
|
|
4272
|
+
* Prefer opening {@link FilterDrawerService} immediately — the drawer lazy-loads
|
|
4273
|
+
* per field via {@link resolveField}. {@link resolve} remains for bulk prefetch.
|
|
4274
|
+
*
|
|
4275
|
+
* @example
|
|
4276
|
+
* ```ts
|
|
4277
|
+
* const resolver = inject(FilterOptionsResolver);
|
|
4278
|
+
*
|
|
4279
|
+
* filterDrawer.open({
|
|
4280
|
+
* config: updateShipmentsFilterConfig,
|
|
4281
|
+
* optionLists: { shipment_manifest_id: hostManifests },
|
|
4282
|
+
* });
|
|
4283
|
+
* ```
|
|
4284
|
+
*/
|
|
4285
|
+
/**
|
|
4286
|
+
* Resolves filter drawer select options from built-in SDK catalog services.
|
|
4287
|
+
*/
|
|
4288
|
+
class FilterOptionsResolver {
|
|
4289
|
+
warehouses = inject(WarehouseService);
|
|
4290
|
+
shipmentMethods = inject(ShipmentMethodService);
|
|
4291
|
+
/**
|
|
4292
|
+
* Resolve SDK-backed options for one select field (lazy drawer load).
|
|
4293
|
+
*
|
|
4294
|
+
* @param field - Filter schema field with `optionsSource`.
|
|
4295
|
+
* @returns Select rows or an error when the catalog HTTP call fails.
|
|
4296
|
+
*/
|
|
4297
|
+
resolveField(field) {
|
|
4298
|
+
if (field.type !== 'select' ||
|
|
4299
|
+
field.optionsSource == null ||
|
|
4300
|
+
field.optionsSource === 'static') {
|
|
4301
|
+
return of(this.staticFieldOptions(field));
|
|
4302
|
+
}
|
|
4303
|
+
if (!SDK_FILTER_OPTIONS_SOURCES.has(field.optionsSource)) {
|
|
4304
|
+
return of([]);
|
|
4305
|
+
}
|
|
4306
|
+
return this.fetchSource(field.optionsSource);
|
|
4307
|
+
}
|
|
4308
|
+
/**
|
|
4309
|
+
* Resolve all SDK-backed select options for a module config (bulk prefetch).
|
|
4310
|
+
*
|
|
4311
|
+
* @param config - Module filter schema.
|
|
4312
|
+
* @returns `optionLists` keyed by field.key.
|
|
4313
|
+
*/
|
|
4314
|
+
resolve(config) {
|
|
4315
|
+
const fields = collectResolvableSelectFields(config);
|
|
4316
|
+
if (fields.length === 0) {
|
|
4317
|
+
return of({});
|
|
4318
|
+
}
|
|
4319
|
+
const fetched = new Set();
|
|
4320
|
+
const fetches = {};
|
|
4321
|
+
for (const field of fields) {
|
|
4322
|
+
const source = field.optionsSource;
|
|
4323
|
+
if (!SDK_FILTER_OPTIONS_SOURCES.has(source) || fetched.has(source)) {
|
|
4324
|
+
continue;
|
|
4325
|
+
}
|
|
4326
|
+
fetched.add(source);
|
|
4327
|
+
fetches[source] = this.fetchSource(source).pipe(map$1((options) => {
|
|
4328
|
+
const lists = {};
|
|
4329
|
+
for (const f of fields) {
|
|
4330
|
+
if (f.optionsSource === source) {
|
|
4331
|
+
lists[f.key] = options;
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
return lists;
|
|
4335
|
+
}));
|
|
4336
|
+
}
|
|
4337
|
+
const keys = Object.keys(fetches);
|
|
4338
|
+
if (keys.length === 0) {
|
|
4339
|
+
return of({});
|
|
4340
|
+
}
|
|
4341
|
+
return forkJoin(keys.reduce((acc, key) => {
|
|
4342
|
+
acc[key] = fetches[key];
|
|
4343
|
+
return acc;
|
|
4344
|
+
}, {})).pipe(map$1((parts) => keys.reduce((merged, key) => {
|
|
4345
|
+
Object.assign(merged, parts[key]);
|
|
4346
|
+
return merged;
|
|
4347
|
+
}, {})));
|
|
4348
|
+
}
|
|
4349
|
+
staticFieldOptions(field) {
|
|
4350
|
+
return (field.options ?? []).map((o) => ({
|
|
4351
|
+
value: o.value,
|
|
4352
|
+
label: o.label,
|
|
4353
|
+
}));
|
|
4354
|
+
}
|
|
4355
|
+
fetchSource(source) {
|
|
4356
|
+
switch (source) {
|
|
4357
|
+
case 'warehouses':
|
|
4358
|
+
return this.warehouses.readAll().pipe(map$1((res) => res.success ? mapWarehouseFilterOptions(res.data) : []));
|
|
4359
|
+
case 'shipmentMethods':
|
|
4360
|
+
return this.shipmentMethods.readAll().pipe(map$1((res) => res.success ? mapShipmentMethodFilterOptions(res.data) : []));
|
|
4361
|
+
default:
|
|
4362
|
+
return of([]);
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FilterOptionsResolver, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4366
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FilterOptionsResolver, providedIn: 'root' });
|
|
4367
|
+
}
|
|
4368
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FilterOptionsResolver, decorators: [{
|
|
4369
|
+
type: Injectable,
|
|
4370
|
+
args: [{ providedIn: 'root' }]
|
|
4371
|
+
}] });
|
|
4372
|
+
|
|
4373
|
+
/**
|
|
4374
|
+
* Optional token for the app's modal opener (provided by `@africanies/africanies-ui`).
|
|
4375
|
+
*
|
|
4376
|
+
* {@link RouteOverlayService} injects this with `{ optional: true }` so apps
|
|
4377
|
+
* can register route overlays before UI is wired — opens are skipped until
|
|
4378
|
+
* a provider exists.
|
|
4379
|
+
*/
|
|
4380
|
+
const MODAL_SERVICE = new InjectionToken('AFRICANIES_MODAL_SERVICE');
|
|
4381
|
+
/**
|
|
4382
|
+
* Optional token for the app's drawer opener (provided by `@africanies/africanies-ui`).
|
|
4383
|
+
*
|
|
4384
|
+
* Same optional pattern as {@link MODAL_SERVICE}.
|
|
4385
|
+
*/
|
|
4386
|
+
const DRAWER_SERVICE = new InjectionToken('AFRICANIES_DRAWER_SERVICE');
|
|
4387
|
+
/**
|
|
4388
|
+
* DI token for the list of {@link OverlayRouteConfig} registered via
|
|
4389
|
+
* {@link provideOverlayRoutes}.
|
|
4390
|
+
*/
|
|
4391
|
+
const OVERLAY_ROUTE_CONFIGS = new InjectionToken('AFRICANIES_OVERLAY_ROUTE_CONFIGS');
|
|
4392
|
+
|
|
4393
|
+
/**
|
|
4394
|
+
* Keeps query-param state in sync with modal/drawer overlays.
|
|
4395
|
+
*
|
|
4396
|
+
* Instantiated eagerly via {@link provideOverlayRoutes}'s `provideAppInitializer`
|
|
4397
|
+
* so a hard refresh with `?modal=…` already present reopens the overlay.
|
|
4398
|
+
*
|
|
4399
|
+
* Bidirectional sync:
|
|
4400
|
+
* - Param appears / matches a route → open (passing sibling query params as data)
|
|
4401
|
+
* - Param removed (e.g. browser back) → close without navigating again
|
|
4402
|
+
* - Overlay closed by UI → strip the trigger param with `queryParamsHandling: 'merge'`
|
|
4403
|
+
*
|
|
4404
|
+
* Modal/drawer implementations live in `@africanies/africanies-ui` and are injected through
|
|
4405
|
+
* {@link MODAL_SERVICE} / {@link DRAWER_SERVICE} to avoid a circular dependency.
|
|
4406
|
+
*/
|
|
4407
|
+
/**
|
|
4408
|
+
* Keeps query-param state in sync with modal/drawer overlays.
|
|
4409
|
+
*/
|
|
4410
|
+
class RouteOverlayService {
|
|
4411
|
+
route = inject(ActivatedRoute);
|
|
4412
|
+
router = inject(Router);
|
|
4413
|
+
configs = inject(OVERLAY_ROUTE_CONFIGS);
|
|
4414
|
+
modal = inject(MODAL_SERVICE, { optional: true });
|
|
4415
|
+
drawer = inject(DRAWER_SERVICE, { optional: true });
|
|
4416
|
+
openByKey = new Map();
|
|
4417
|
+
constructor() {
|
|
4418
|
+
// Start immediately — provideAppInitializer only forces construction.
|
|
4419
|
+
this.route.queryParamMap.subscribe((params) => this.onQueryParams(params));
|
|
4420
|
+
}
|
|
4421
|
+
onQueryParams(params) {
|
|
4422
|
+
for (const config of this.configs) {
|
|
4423
|
+
this.syncConfig(config, params);
|
|
4424
|
+
}
|
|
4425
|
+
}
|
|
4426
|
+
syncConfig(config, params) {
|
|
4427
|
+
const value = params.get(config.paramKey);
|
|
4428
|
+
const current = this.openByKey.get(config.paramKey);
|
|
4429
|
+
if (value === null || value === '') {
|
|
4430
|
+
if (current) {
|
|
4431
|
+
current.closingFromUrl = true;
|
|
4432
|
+
current.handle.close();
|
|
4433
|
+
this.teardown(config.paramKey);
|
|
4434
|
+
}
|
|
4435
|
+
return;
|
|
4436
|
+
}
|
|
4437
|
+
const entry = config.routes[value];
|
|
4438
|
+
if (!entry) {
|
|
4439
|
+
return;
|
|
4440
|
+
}
|
|
4441
|
+
// Already showing the same route value — leave it alone.
|
|
4442
|
+
if (current?.value === value) {
|
|
4443
|
+
return;
|
|
4444
|
+
}
|
|
4445
|
+
if (current) {
|
|
4446
|
+
current.closingFromUrl = true;
|
|
4447
|
+
current.handle.close();
|
|
4448
|
+
this.teardown(config.paramKey);
|
|
4449
|
+
}
|
|
4450
|
+
const opener = entry.overlay === 'modal' ? this.modal : this.drawer;
|
|
4451
|
+
if (!opener) {
|
|
4452
|
+
// UI package has not provided the opener yet — skip until it does.
|
|
4453
|
+
return;
|
|
4454
|
+
}
|
|
4455
|
+
// Sibling query params (e.g. id=123) become OVERLAY_DATA in africanies-ui.
|
|
4456
|
+
const data = this.siblingParams(params, config.paramKey);
|
|
4457
|
+
const handle = opener.open(entry.component, { data });
|
|
4458
|
+
const state = {
|
|
4459
|
+
value,
|
|
4460
|
+
handle,
|
|
4461
|
+
closingFromUrl: false,
|
|
4462
|
+
afterClosedSub: handle.afterClosed().subscribe(() => {
|
|
4463
|
+
const open = this.openByKey.get(config.paramKey);
|
|
4464
|
+
if (!open || open.handle !== handle) {
|
|
4465
|
+
return;
|
|
4466
|
+
}
|
|
4467
|
+
const fromUrl = open.closingFromUrl;
|
|
4468
|
+
this.teardown(config.paramKey);
|
|
4469
|
+
if (!fromUrl) {
|
|
4470
|
+
// Manual / UI close — drop the trigger param, keep other params.
|
|
4471
|
+
void this.router.navigate([], {
|
|
4472
|
+
queryParams: { [config.paramKey]: null },
|
|
4473
|
+
queryParamsHandling: 'merge',
|
|
4474
|
+
replaceUrl: true,
|
|
4475
|
+
});
|
|
4476
|
+
}
|
|
4477
|
+
}),
|
|
4478
|
+
};
|
|
4479
|
+
this.openByKey.set(config.paramKey, state);
|
|
4480
|
+
}
|
|
4481
|
+
siblingParams(params, excludeKey) {
|
|
4482
|
+
const data = {};
|
|
4483
|
+
params.keys.forEach((key) => {
|
|
4484
|
+
if (key === excludeKey) {
|
|
4485
|
+
return;
|
|
4486
|
+
}
|
|
4487
|
+
const value = params.get(key);
|
|
4488
|
+
if (value !== null) {
|
|
4489
|
+
data[key] = value;
|
|
4490
|
+
}
|
|
4491
|
+
});
|
|
4492
|
+
return data;
|
|
4493
|
+
}
|
|
4494
|
+
teardown(paramKey) {
|
|
4495
|
+
const current = this.openByKey.get(paramKey);
|
|
4496
|
+
if (!current) {
|
|
4497
|
+
return;
|
|
4498
|
+
}
|
|
4499
|
+
current.afterClosedSub.unsubscribe();
|
|
4500
|
+
this.openByKey.delete(paramKey);
|
|
4501
|
+
}
|
|
4502
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: RouteOverlayService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4503
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: RouteOverlayService });
|
|
4504
|
+
}
|
|
4505
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: RouteOverlayService, decorators: [{
|
|
4506
|
+
type: Injectable
|
|
4507
|
+
}], ctorParameters: () => [] });
|
|
4508
|
+
|
|
4509
|
+
/**
|
|
4510
|
+
* Registers query-param → overlay maps and eagerly starts {@link RouteOverlayService}.
|
|
4511
|
+
*
|
|
4512
|
+
* Call beside {@link provideAfricaniesSdk} in `app.config.ts`. Modal/drawer openers
|
|
4513
|
+
* must still be provided by `@africanies/africanies-ui` (`MODAL_SERVICE` / `DRAWER_SERVICE`).
|
|
4514
|
+
*
|
|
4515
|
+
* @param configs - One or more param-key namespaces (e.g. `modal` and `drawer`).
|
|
4516
|
+
* @returns Environment providers including an app initializer.
|
|
4517
|
+
*
|
|
4518
|
+
* @example
|
|
4519
|
+
* ```ts
|
|
4520
|
+
* // app.config.ts
|
|
4521
|
+
* import { provideRouter, Routes } from '@angular/router';
|
|
4522
|
+
* import {
|
|
4523
|
+
* provideAfricaniesSdk,
|
|
4524
|
+
* provideOverlayRoutes,
|
|
4525
|
+
* } from '@africanies/africanies-core';
|
|
4526
|
+
* import { EditShipmentModal } from './edit-shipment.modal';
|
|
4527
|
+
*
|
|
4528
|
+
* export const appConfig = {
|
|
4529
|
+
* providers: [
|
|
4530
|
+
* provideAfricaniesSdk({ baseUrl: 'https://api.example.com' }),
|
|
4531
|
+
* provideRouter(routes),
|
|
4532
|
+
* provideOverlayRoutes([
|
|
4533
|
+
* {
|
|
4534
|
+
* paramKey: 'modal',
|
|
4535
|
+
* routes: {
|
|
4536
|
+
* 'edit-shipment': {
|
|
4537
|
+
* component: EditShipmentModal,
|
|
4538
|
+
* overlay: 'modal',
|
|
4539
|
+
* },
|
|
4540
|
+
* },
|
|
4541
|
+
* },
|
|
4542
|
+
* ]),
|
|
4543
|
+
* // From @africanies/africanies-ui — binds MODAL_SERVICE / DRAWER_SERVICE
|
|
4544
|
+
* // provideAfricaniesOverlays(),
|
|
4545
|
+
* ],
|
|
4546
|
+
* };
|
|
4547
|
+
*
|
|
4548
|
+
* // Template — open via query params (refresh with the same URL reopens):
|
|
4549
|
+
* // <a routerLink="." [queryParams]="{ modal: 'edit-shipment', id: row.id }">
|
|
4550
|
+
* // Edit
|
|
4551
|
+
* // </a>
|
|
4552
|
+
* ```
|
|
4553
|
+
*/
|
|
4554
|
+
function provideOverlayRoutes(configs) {
|
|
4555
|
+
return makeEnvironmentProviders([
|
|
4556
|
+
{ provide: OVERLAY_ROUTE_CONFIGS, useValue: configs },
|
|
4557
|
+
RouteOverlayService,
|
|
4558
|
+
provideAppInitializer(() => {
|
|
4559
|
+
// Force construction so queryParamMap subscription starts at bootstrap
|
|
4560
|
+
// (including hard refresh when the param is already present).
|
|
4561
|
+
inject(RouteOverlayService);
|
|
4562
|
+
}),
|
|
4563
|
+
]);
|
|
4564
|
+
}
|
|
4565
|
+
|
|
4566
|
+
/**
|
|
4567
|
+
* Copy plain text to the system clipboard.
|
|
4568
|
+
*
|
|
4569
|
+
* Prefers `navigator.clipboard.writeText`. Falls back to a temporary
|
|
4570
|
+
* `textarea` + `document.execCommand('copy')` when the Clipboard API is
|
|
4571
|
+
* missing or throws (older browsers, some embedded / non-secure contexts).
|
|
4572
|
+
*
|
|
4573
|
+
* Safe to call during SSR — returns `false` when `document` is unavailable.
|
|
4574
|
+
*
|
|
4575
|
+
* @param value - Text to place on the clipboard.
|
|
4576
|
+
* @returns `true` when the write succeeded; `false` otherwise.
|
|
4577
|
+
*
|
|
4578
|
+
* @example
|
|
4579
|
+
* ```ts
|
|
4580
|
+
* const ok = await copyToClipboard(iconName);
|
|
4581
|
+
* if (ok) {
|
|
4582
|
+
* toast.success(`Copied “${iconName}”`);
|
|
4583
|
+
* }
|
|
4584
|
+
* ```
|
|
4585
|
+
*/
|
|
4586
|
+
async function copyToClipboard(value) {
|
|
4587
|
+
if (typeof document === 'undefined') {
|
|
4588
|
+
return false;
|
|
4589
|
+
}
|
|
4590
|
+
try {
|
|
4591
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
|
|
4592
|
+
await navigator.clipboard.writeText(value);
|
|
4593
|
+
return true;
|
|
4594
|
+
}
|
|
4595
|
+
}
|
|
4596
|
+
catch {
|
|
4597
|
+
// Fall through to the legacy execCommand path.
|
|
4598
|
+
}
|
|
4599
|
+
try {
|
|
4600
|
+
const el = document.createElement('textarea');
|
|
4601
|
+
el.value = value;
|
|
4602
|
+
el.setAttribute('readonly', '');
|
|
4603
|
+
el.style.position = 'fixed';
|
|
4604
|
+
el.style.left = '-9999px';
|
|
4605
|
+
document.body.appendChild(el);
|
|
4606
|
+
el.select();
|
|
4607
|
+
const ok = document.execCommand('copy');
|
|
4608
|
+
document.body.removeChild(el);
|
|
4609
|
+
return ok;
|
|
4610
|
+
}
|
|
4611
|
+
catch {
|
|
4612
|
+
return false;
|
|
4613
|
+
}
|
|
4614
|
+
}
|
|
4615
|
+
|
|
4616
|
+
/**
|
|
4617
|
+
* Escape a single CSV field (RFC 4180).
|
|
4618
|
+
*
|
|
4619
|
+
* Quotes fields that contain commas, quotes, or line breaks. `null` /
|
|
4620
|
+
* `undefined` become an empty cell.
|
|
4621
|
+
*
|
|
4622
|
+
* @param value - Cell value.
|
|
4623
|
+
*/
|
|
4624
|
+
function csvCell(value) {
|
|
4625
|
+
const text = value == null ? '' : String(value);
|
|
4626
|
+
if (/[",\n\r]/.test(text)) {
|
|
4627
|
+
return `"${text.replace(/"/g, '""')}"`;
|
|
4628
|
+
}
|
|
4629
|
+
return text;
|
|
4630
|
+
}
|
|
4631
|
+
/**
|
|
4632
|
+
* Build a CSV document string (optional BOM + header + rows).
|
|
4633
|
+
*
|
|
4634
|
+
* @param options - Headers, rows, and BOM flag (filename is ignored).
|
|
4635
|
+
*/
|
|
4636
|
+
function toCsvString(options) {
|
|
4637
|
+
const lines = [];
|
|
4638
|
+
if (options.headers?.length) {
|
|
4639
|
+
lines.push(options.headers.map(csvCell).join(','));
|
|
4640
|
+
}
|
|
4641
|
+
for (const row of options.rows) {
|
|
4642
|
+
lines.push(row.map(csvCell).join(','));
|
|
4643
|
+
}
|
|
4644
|
+
const body = lines.length ? `${lines.join('\n')}\n` : '';
|
|
4645
|
+
const bom = options.bom === false ? '' : '\uFEFF';
|
|
4646
|
+
return `${bom}${body}`;
|
|
4647
|
+
}
|
|
4648
|
+
/**
|
|
4649
|
+
* Download a CSV file in the browser.
|
|
4650
|
+
*
|
|
4651
|
+
* Hosts supply filename, headers, and already-mapped row values. Safe during
|
|
4652
|
+
* SSR — returns `false` when `document` is unavailable.
|
|
4653
|
+
*
|
|
4654
|
+
* @param options - Filename, headers, and row cells.
|
|
4655
|
+
* @returns `true` when the download was triggered.
|
|
4656
|
+
*
|
|
4657
|
+
* @example
|
|
4658
|
+
* ```ts
|
|
4659
|
+
* downloadCsv({
|
|
4660
|
+
* filename: 'warehouses.csv',
|
|
4661
|
+
* headers: ['Name', 'Status'],
|
|
4662
|
+
* rows: warehouses.map((row) => [
|
|
4663
|
+
* row.name,
|
|
4664
|
+
* row.active ? 'Active' : 'In-Active',
|
|
4665
|
+
* ]),
|
|
4666
|
+
* });
|
|
4667
|
+
* ```
|
|
4668
|
+
*/
|
|
4669
|
+
function downloadCsv(options) {
|
|
4670
|
+
if (typeof document === 'undefined' || typeof URL === 'undefined') {
|
|
4671
|
+
return false;
|
|
4672
|
+
}
|
|
4673
|
+
const blob = new Blob([toCsvString(options)], {
|
|
4674
|
+
type: 'text/csv;charset=utf-8',
|
|
4675
|
+
});
|
|
4676
|
+
const url = URL.createObjectURL(blob);
|
|
4677
|
+
const link = document.createElement('a');
|
|
4678
|
+
link.href = url;
|
|
4679
|
+
link.download = options.filename;
|
|
4680
|
+
link.rel = 'noopener';
|
|
4681
|
+
document.body.appendChild(link);
|
|
4682
|
+
link.click();
|
|
4683
|
+
document.body.removeChild(link);
|
|
4684
|
+
URL.revokeObjectURL(url);
|
|
4685
|
+
return true;
|
|
4686
|
+
}
|
|
4687
|
+
|
|
4688
|
+
/**
|
|
4689
|
+
* Browser helpers (clipboard, CSV download, etc.) shared across host apps.
|
|
4690
|
+
*/
|
|
4691
|
+
|
|
4692
|
+
// Config
|
|
4693
|
+
|
|
4694
|
+
/**
|
|
4695
|
+
* Generated bundle index. Do not edit.
|
|
4696
|
+
*/
|
|
4697
|
+
|
|
4698
|
+
export { AFRICANIES_HTTP_TOAST, AFRICANIES_SDK_CONFIG, AUTH_FORGOT_PASSWORD_PATH, ApiClient, AuthService, AuthTokenService, COUNTRY_FLAG_CDN_BASE, COUNTRY_READ_PATH, CURRENCY_CREATE_PATH, CURRENCY_DELETE_PATH, CURRENCY_READ_PATH, CURRENCY_UPDATE_PATH, CountryService, CurrencyService, DOCUMENT_READ_PATH, DRAWER_SERVICE, DocumentService, FILE_READ_MULTIPLE_PARAM, FILE_READ_PATH, FileService, FilterOptionsResolver, HttpResponseCache, MODAL_SERVICE, MODE_CONFIG_PATH, ModeConfigService, NOTIFICATION_READ_PATH, NOTIFICATION_UPDATE_PATH, NotificationService, OVERLAY_ROUTE_CONFIGS, PAYMENT_METHOD_READ_PATH, PAYMENT_METHOD_UPDATE_PATH, PLAN_READ_PATH, PRODUCT_READ_PATH, PaymentMethodService, PlanService, ProductService, RouteOverlayService, SERVICE_READ_PATH, SHIPMENT_METHOD_READ_PATH, SHIPPING_MODE_OVERRIDE, ServiceService, ShipmentMethodService, ShippingModeService, TOAST_HTTP_OPTIONS, USER_CHANGE_PASSWORD_PATH, USER_LOGOUT_FROM_ALL_SESSIONS_PATH, USER_PATH, UserService, WAREHOUSE_READ_PATH, WarehouseService, ZONE_READ_PATH, ZoneService, asArray, asBoolean, asNullableBoolean, asNullableFlag01, asNullableNumber, asNullableString, asNumber, asRecord, asShippingMode$1 as asShippingMode, asString, authInterceptor, buildResourcePath, buildResourceQueryParams, collectFilterOptionsSources, collectResolvableSelectFields, copyToClipboard, countryFlagUrl, createAfricaniesQueryClientDefaults, csvCell, downloadCsv, fieldErrorsMap, formatApiErrorMessage, httpToastInterceptor, isLaravelValidationBag, isNotificationExternalLink, isValidEmail, joinApiErrorMessages, listFetchKind, mapApiJsonList, mapApiJsonValue, mapArray, mapCountry, mapCountryList, mapCountrySelectOptions, mapCountryState, mapCurrency, mapCurrencyList, mapCurrencyPaymentMethod, mapCurrencyPaymentMethodPivot, mapDocument, mapDocumentList, mapFileRead, mapFileReadList, mapLaravelValidationBag, mapList, mapModeConfigData, mapNotification, mapNotificationInboxItem, mapNotificationList, mapNotificationPayload, mapPaymentMethod, mapPaymentMethodCurrency, mapPaymentMethodList, mapPlan, mapPlanList, mapPlanPackage, mapProduct, mapProductList, mapResourcePayload, mapService, mapServiceList, mapShipmentMethod, mapShipmentMethodFilterOptions, mapShipmentMethodList, mapShipmentMethodZoneLink, mapShipmentMethodZonePage, mapShipmentZone, mapUser, mapUserAccountManager, mapUserBusinessAccount, mapUserCountry, mapUserCountryState, mapUserGatewayPayload, mapUserPaymentPayload, mapUserPlan, mapUserPlanPackage, mapUserStateLabel, mapUserSubscription, mapWarehouse, mapWarehouseCountry, mapWarehouseFilterOptions, mapWarehouseList, mapWarehouseState, mapZone, mapZoneList, mergeFilterOptionLists, normalize, normalizePagination, provideAfricaniesHttpClient, provideAfricaniesQueryDefaults, provideAfricaniesSdk, provideModeConfig, provideOverlayRoutes, resolveModeRegionConfig, resolveNotificationLinkForMode, resourceCacheTtlMs, shipmentModeInterceptor, toCsvString, toCurrencyCreateBody, toCurrencyDeleteBody, toCurrencyFlag01, toCurrencyUpdateBody, toFlag01, toPaymentMethodFlag01, toPaymentMethodUpdateBody, unwrapLaravelPaginator, withShippingMode, withToast };
|
|
4699
|
+
//# sourceMappingURL=africanies-africanies-core.mjs.map
|