@africanies/angular-web-sdk 0.1.8 → 0.1.9

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.
Files changed (53) hide show
  1. package/.angular/cache/22.0.9/playground/.tsbuildinfo +1 -1
  2. package/.nx/cache/17289624717336768573/dist/libs/africanies-core/README.md +85 -0
  3. package/.nx/cache/17289624717336768573/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs +4639 -0
  4. package/.nx/cache/17289624717336768573/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +1 -0
  5. package/.nx/cache/17289624717336768573/dist/libs/africanies-core/package.json +31 -0
  6. package/.nx/cache/17289624717336768573/dist/libs/africanies-core/types/africanies-africanies-core.d.ts +2748 -0
  7. package/.nx/cache/3953970051755345887/dist/libs/africanies-ui/README.md +68 -0
  8. package/.nx/cache/3953970051755345887/dist/libs/africanies-ui/assets/brand/africanies-logo-mini.png +0 -0
  9. package/.nx/cache/3953970051755345887/dist/libs/africanies-ui/assets/brand/africanies-logo.png +0 -0
  10. package/.nx/cache/3953970051755345887/dist/libs/africanies-ui/assets/brand/africanies-logo.svg +5 -0
  11. package/.nx/cache/3953970051755345887/dist/libs/africanies-ui/assets/carriers/dhl.svg +7 -0
  12. package/.nx/cache/3953970051755345887/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +19500 -0
  13. package/.nx/cache/3953970051755345887/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -0
  14. package/.nx/cache/3953970051755345887/dist/libs/africanies-ui/package.json +35 -0
  15. package/.nx/cache/3953970051755345887/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +5521 -0
  16. package/.nx/cache/4225324930345532464/dist/libs/africanies-theme/README.md +46 -0
  17. package/.nx/cache/4225324930345532464/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs +243 -0
  18. package/.nx/cache/4225324930345532464/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +1 -0
  19. package/.nx/cache/4225324930345532464/dist/libs/africanies-theme/package.json +35 -0
  20. package/.nx/cache/4225324930345532464/dist/libs/africanies-theme/tailwind-preset.cjs +330 -0
  21. package/.nx/cache/4225324930345532464/dist/libs/africanies-theme/types/africanies-africanies-theme.d.ts +176 -0
  22. package/.nx/cache/run.json +25 -25
  23. package/.nx/cache/terminalOutputs/12565224028063674529 +37 -0
  24. package/.nx/cache/terminalOutputs/13478445034325959094 +176 -0
  25. package/.nx/cache/terminalOutputs/15835338512221847842 +184 -182
  26. package/.nx/cache/terminalOutputs/17289624717336768573 +21 -0
  27. package/.nx/cache/terminalOutputs/17571479482482478257 +35 -0
  28. package/.nx/cache/terminalOutputs/3088264223473143358 +8 -0
  29. package/.nx/cache/terminalOutputs/3953970051755345887 +21 -0
  30. package/.nx/cache/terminalOutputs/4225324930345532464 +21 -0
  31. package/.nx/cache/terminalOutputs/4363587767476846758 +176 -0
  32. package/.nx/cache/terminalOutputs/8193462679279018087 +39 -0
  33. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db +0 -0
  34. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db-shm +0 -0
  35. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db-wal +0 -0
  36. package/.nx/workspace-data/d/daemon.log +4460 -0
  37. package/.nx/workspace-data/d/server-process.json +2 -2
  38. package/.nx/workspace-data/file-map.json +2998 -3006
  39. package/.nx/workspace-data/nx_files.nxt +0 -0
  40. package/.nx/workspace-data/project-graph.json +2 -2
  41. package/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs +6 -1
  42. package/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +1 -1
  43. package/dist/libs/africanies-core/types/africanies-africanies-core.d.ts +6 -1
  44. package/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +30 -55
  45. package/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -1
  46. package/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +14 -16
  47. package/libs/africanies-core/src/index.ts +1 -1
  48. package/libs/africanies-core/src/lib/notification/notification-link.ts +6 -1
  49. package/libs/africanies-ui/src/lib/layout/app-shell-header.component.ts +9 -9
  50. package/libs/africanies-ui/src/lib/layout/header-greeting.util.ts +0 -2
  51. package/libs/africanies-ui/src/lib/layout/header-weather.spec.ts +11 -40
  52. package/libs/africanies-ui/src/lib/layout/header-weather.ts +22 -63
  53. package/package.json +1 -1
@@ -210,9 +210,9 @@ export {
210
210
 
211
211
  // User notifications (auth required)
212
212
  export {
213
+ isNotificationExternalLink,
213
214
  mapNotification,
214
215
  mapNotificationInboxItem,
215
- isNotificationExternalLink,
216
216
  mapNotificationList,
217
217
  mapNotificationPayload,
218
218
  NOTIFICATION_READ_PATH,
@@ -12,7 +12,10 @@ const QUERY_SHIPMENT_APP = 'shipment_app';
12
12
  const LEGACY_PORTAL_HOST =
13
13
  /^(?:test-)?(?:admin|customer)-(?:export|import)\.africaniestest\.com$/i;
14
14
 
15
- /** Signed export downloads and other hosts that must not be rewritten in-app. */
15
+ /**
16
+ * Signed export downloads and other hosts that must not be rewritten in-app.
17
+ * @param link
18
+ */
16
19
  export function isNotificationExternalLink(link: string): boolean {
17
20
  const trimmed = decodeNotificationLink(link).trim();
18
21
  if (!trimmed) {
@@ -39,6 +42,8 @@ export function isNotificationExternalLink(link: string): boolean {
39
42
  * - Legacy portal hosts become relative `/portal/...` paths with query preserved.
40
43
  * - When the notification already includes `shipment_app`, it is kept as-is.
41
44
  * - Otherwise classic host / `shipment_app` rewriting applies for portal URLs.
45
+ * @param link
46
+ * @param mode
42
47
  */
43
48
  export function resolveNotificationLinkForMode(
44
49
  link: string | null | undefined,
@@ -113,13 +113,13 @@ export class AppShellHeaderEndDirective {}
113
113
  <span
114
114
  class="text-body-sm font-medium tabular-nums tracking-tight text-ink dark:text-white"
115
115
  >
116
- {{ temp }}°
116
+ {{ temp }}°C
117
117
  </span>
118
118
  }
119
119
  <span
120
120
  class="hidden text-caption text-neutral-600 md:inline dark:text-neutral-400"
121
121
  >
122
- {{ weatherPlace() }}
122
+ {{ weatherCondition() }}
123
123
  </span>
124
124
  </div>
125
125
  }
@@ -274,15 +274,15 @@ export class AppShellHeaderComponent {
274
274
  if (temp === undefined || !Number.isFinite(temp)) {
275
275
  return null;
276
276
  }
277
- return temp.toFixed(1);
277
+ return String(Math.round(temp));
278
278
  });
279
279
 
280
- protected readonly weatherPlace = computed(() => {
280
+ protected readonly weatherCondition = computed(() => {
281
281
  const forecast = this.weather();
282
282
  if (!forecast) {
283
283
  return '';
284
284
  }
285
- return forecast.city?.trim() || headerWeatherLabel(forecast.kind);
285
+ return headerWeatherLabel(forecast.kind, this.now().getHours());
286
286
  });
287
287
 
288
288
  protected readonly weatherAriaLabel = computed(() => {
@@ -290,11 +290,11 @@ export class AppShellHeaderComponent {
290
290
  if (!forecast) {
291
291
  return 'Weather';
292
292
  }
293
- const condition = headerWeatherLabel(forecast.kind).toLowerCase();
293
+ const condition = this.weatherCondition().toLowerCase();
294
294
  const temp = this.weatherTemp();
295
- const city = forecast.city?.trim();
296
- const degrees = temp === null ? condition : `${temp} degrees, ${condition}`;
297
- return city ? `Weather in ${city}: ${degrees}` : `Weather: ${degrees}`;
295
+ return temp === null
296
+ ? `Weather: ${condition}`
297
+ : `Weather: ${temp} degrees Celsius, ${condition}`;
298
298
  });
299
299
 
300
300
  protected readonly unreadCount = computed(
@@ -33,8 +33,6 @@ export interface HeaderWeather {
33
33
  kind: HeaderWeatherKind;
34
34
  /** Celsius, when the forecast includes it. */
35
35
  temperatureC?: number;
36
- /** City from IP geolocation, when known. */
37
- city?: string;
38
36
  }
39
37
 
40
38
  const PERIOD_KICKERS: Record<HeaderGreetingPeriod, readonly string[]> = {
@@ -66,7 +66,7 @@ describe('loadHeaderWeather', () => {
66
66
  ).resolves.toBeNull();
67
67
  });
68
68
 
69
- it('reads Open-Meteo after IP geolocation and reverse-geocodes the city', async () => {
69
+ it('reads Open-Meteo after IP coordinates', async () => {
70
70
  const fetchFn = jest.fn(async (url: string) => {
71
71
  if (String(url).includes('geojs')) {
72
72
  return jsonResponse({
@@ -75,9 +75,6 @@ describe('loadHeaderWeather', () => {
75
75
  city: 'Lagos',
76
76
  });
77
77
  }
78
- if (String(url).includes('bigdatacloud')) {
79
- return jsonResponse({ city: 'Abuja', locality: 'Abuja' });
80
- }
81
78
  return jsonResponse({
82
79
  current: { weather_code: 61, temperature_2m: 27.4 },
83
80
  });
@@ -88,29 +85,8 @@ describe('loadHeaderWeather', () => {
88
85
  ).resolves.toEqual({
89
86
  kind: 'rain',
90
87
  temperatureC: 27.4,
91
- city: 'Abuja',
92
- });
93
- expect(fetchFn).toHaveBeenCalledTimes(3);
94
- });
95
-
96
- it('falls back to the IP city when reverse geocode is empty', async () => {
97
- const fetchFn = jest.fn(async (url: string) => {
98
- if (String(url).includes('geojs')) {
99
- return jsonResponse({ latitude: 1, longitude: 2, city: 'Lagos' });
100
- }
101
- if (String(url).includes('bigdatacloud')) {
102
- return jsonResponse({});
103
- }
104
- return jsonResponse({ current: { weather_code: 0, temperature_2m: 30 } });
105
- });
106
-
107
- await expect(
108
- loadHeaderWeather(fetchFn as unknown as typeof fetch),
109
- ).resolves.toEqual({
110
- kind: 'clear',
111
- temperatureC: 30,
112
- city: 'Lagos',
113
88
  });
89
+ expect(fetchFn).toHaveBeenCalledTimes(2);
114
90
  });
115
91
 
116
92
  it('reuses the same-hour session cache', async () => {
@@ -118,9 +94,6 @@ describe('loadHeaderWeather', () => {
118
94
  if (String(url).includes('geojs')) {
119
95
  return jsonResponse({ latitude: 1, longitude: 2 });
120
96
  }
121
- if (String(url).includes('bigdatacloud')) {
122
- return jsonResponse({ city: 'Accra' });
123
- }
124
97
  return jsonResponse({ current: { weather_code: 0, temperature_2m: 30 } });
125
98
  });
126
99
 
@@ -128,16 +101,13 @@ describe('loadHeaderWeather', () => {
128
101
  const second = await loadHeaderWeather(fetchFn as unknown as typeof fetch);
129
102
 
130
103
  expect(first).toEqual(second);
131
- expect(fetchFn).toHaveBeenCalledTimes(3);
104
+ expect(fetchFn).toHaveBeenCalledTimes(2);
132
105
  });
133
106
 
134
107
  it('fetches again when the cached hour no longer matches', async () => {
135
108
  const fetchFn = jest.fn(async (url: string) => {
136
109
  if (String(url).includes('geojs')) {
137
- return jsonResponse({ latitude: 1, longitude: 2, city: 'Lagos' });
138
- }
139
- if (String(url).includes('bigdatacloud')) {
140
- return jsonResponse({ city: 'Lagos' });
110
+ return jsonResponse({ latitude: 1, longitude: 2 });
141
111
  }
142
112
  return jsonResponse({ current: { weather_code: 0, temperature_2m: 30.2 } });
143
113
  });
@@ -154,7 +124,7 @@ describe('loadHeaderWeather', () => {
154
124
  sessionStorage.setItem(cacheKey!, JSON.stringify(stored));
155
125
 
156
126
  await loadHeaderWeather(fetchFn as unknown as typeof fetch);
157
- expect(fetchFn).toHaveBeenCalledTimes(6);
127
+ expect(fetchFn).toHaveBeenCalledTimes(4);
158
128
  });
159
129
 
160
130
  it('uses browser coordinates when geolocation succeeds', async () => {
@@ -181,9 +151,6 @@ describe('loadHeaderWeather', () => {
181
151
  });
182
152
 
183
153
  const fetchFn = jest.fn(async (url: string) => {
184
- if (String(url).includes('bigdatacloud')) {
185
- return jsonResponse({ city: 'Abuja' });
186
- }
187
154
  if (String(url).includes('geojs')) {
188
155
  throw new Error('should not call IP geo');
189
156
  }
@@ -195,7 +162,6 @@ describe('loadHeaderWeather', () => {
195
162
  ).resolves.toEqual({
196
163
  kind: 'clear',
197
164
  temperatureC: 31,
198
- city: 'Abuja',
199
165
  });
200
166
  expect(
201
167
  fetchFn.mock.calls.some((call) => String(call[0]).includes('geojs')),
@@ -206,7 +172,12 @@ describe('loadHeaderWeather', () => {
206
172
  describe('headerWeatherLabel', () => {
207
173
  it('returns a short condition', () => {
208
174
  expect(headerWeatherLabel('rain')).toBe('Rain');
209
- expect(headerWeatherLabel('clear')).toBe('Clear');
175
+ expect(headerWeatherLabel('cloudy')).toBe('Cloudy');
176
+ });
177
+
178
+ it('uses Sunny by day and Clear at night', () => {
179
+ expect(headerWeatherLabel('clear', 10)).toBe('Sunny');
180
+ expect(headerWeatherLabel('clear', 22)).toBe('Clear');
210
181
  });
211
182
  });
212
183
 
@@ -2,15 +2,12 @@ import type { IconName } from '@africanies/africanies-icons';
2
2
 
3
3
  import type { HeaderWeather, HeaderWeatherKind } from './header-greeting.util';
4
4
 
5
- /** Bump when cache shape or city resolution strategy changes. */
6
- const CACHE_KEY = 'africanies-header-weather-v4';
5
+ /** Bump when cache shape or lookup strategy changes. */
6
+ const CACHE_KEY = 'africanies-header-weather-v5';
7
7
  const FETCH_MS = 4_000;
8
8
  const BROWSER_GEO_MS = 3_500;
9
- /** IP fallback — city string is often wrong (e.g. NG IPs labelled Lagos). */
9
+ /** IP fallback for coordinates only — city strings are often wrong. */
10
10
  const GEO_URL = 'https://get.geojs.io/v1/ip/geo.json';
11
- /** Client reverse-geocode so the label matches the forecast coordinates. */
12
- const REVERSE_GEO_URL =
13
- 'https://api.bigdatacloud.net/data/reverse-geocode-client';
14
11
 
15
12
  const WEATHER_LABELS: Record<HeaderWeatherKind, string> = {
16
13
  clear: 'Clear',
@@ -26,21 +23,13 @@ interface CachedWeather {
26
23
  hour: string;
27
24
  kind: HeaderWeatherKind;
28
25
  temperatureC?: number;
29
- city?: string;
30
26
  }
31
27
 
32
28
  interface GeoJsPayload {
33
- city?: string;
34
29
  latitude?: string | number;
35
30
  longitude?: string | number;
36
31
  }
37
32
 
38
- interface ReverseGeoPayload {
39
- city?: string;
40
- locality?: string;
41
- principalSubdivision?: string;
42
- }
43
-
44
33
  interface OpenMeteoPayload {
45
34
  current?: {
46
35
  weather_code?: number;
@@ -88,9 +77,16 @@ export function mapWmoWeatherCode(code: number): HeaderWeatherKind | null {
88
77
  * Short condition label for the header weather chip.
89
78
  *
90
79
  * @param kind - Coarse forecast kind.
91
- * @returns Display label such as `Rain` or `Clear`.
80
+ * @param hour - Local hour; clear days show as Sunny, nights as Clear.
81
+ * @returns Display label such as `Rain` or `Sunny`.
92
82
  */
93
- export function headerWeatherLabel(kind: HeaderWeatherKind): string {
83
+ export function headerWeatherLabel(
84
+ kind: HeaderWeatherKind,
85
+ hour = new Date().getHours(),
86
+ ): string {
87
+ if (kind === 'clear') {
88
+ return hour >= 19 || hour < 6 ? 'Clear' : 'Sunny';
89
+ }
94
90
  return WEATHER_LABELS[kind];
95
91
  }
96
92
 
@@ -112,12 +108,12 @@ export function headerWeatherIcon(kind: HeaderWeatherKind, hour: number): IconNa
112
108
  }
113
109
 
114
110
  /**
115
- * City-level forecast via browser / IP geolocation + Open-Meteo (no API key).
111
+ * Local forecast via browser / IP coordinates + Open-Meteo (no API key).
116
112
  *
117
- * Coordinates prefer the browser Geolocation API (accurate for travellers;
118
- * may prompt once). Otherwise IP geo is used. The city label is reverse-
119
- * geocoded from those coordinates IP `city` strings are often wrong for
120
- * Nigeria (many networks resolve as Lagos).
113
+ * Coordinates prefer the browser Geolocation API (may prompt once).
114
+ * Otherwise IP geo is used for lat/lon only — city labels are not shown
115
+ * because IP and reverse-geocode cities are often wrong (e.g. NG IPs
116
+ * labelled Lagos).
121
117
  *
122
118
  * Fails closed: missing browser APIs, timeouts, and HTTP errors all return
123
119
  * `null` so the greeting can stay time-of-day only. Cached per local hour
@@ -162,11 +158,8 @@ export async function loadHeaderWeather(
162
158
  }
163
159
 
164
160
  const temperatureC = asNumber(forecast?.current?.temperature_2m) ?? undefined;
165
- const city =
166
- (await reverseGeocodeCity(run, coords.latitude, coords.longitude)) ??
167
- coords.fallbackCity;
168
- const weather: HeaderWeather = { kind, temperatureC, city };
169
- writeCache({ hour, kind, temperatureC, city });
161
+ const weather: HeaderWeather = { kind, temperatureC };
162
+ writeCache({ hour, kind, temperatureC });
170
163
  return weather;
171
164
  } catch {
172
165
  return null;
@@ -194,7 +187,6 @@ function readCache(hour: string): HeaderWeather | null {
194
187
  return {
195
188
  kind: parsed.kind,
196
189
  temperatureC: parsed.temperatureC,
197
- city: parsed.city,
198
190
  };
199
191
  } catch {
200
192
  return null;
@@ -211,12 +203,11 @@ function writeCache(value: CachedWeather): void {
211
203
 
212
204
  /**
213
205
  * Prefer device coordinates when available (may prompt once); otherwise fall
214
- * back to IP geo. IP city labels for Nigeria are often wrong — reverse
215
- * geocoding from these coordinates still depends on accurate lat/lon.
206
+ * back to IP lat/lon for the forecast only.
216
207
  */
217
208
  async function resolveCoordinates(
218
209
  fetchFn: typeof fetch,
219
- ): Promise<(Coordinates & { fallbackCity?: string }) | null> {
210
+ ): Promise<Coordinates | null> {
220
211
  const browser = await tryBrowserGeolocation();
221
212
  if (browser) {
222
213
  return browser;
@@ -228,11 +219,7 @@ async function resolveCoordinates(
228
219
  if (latitude === null || longitude === null) {
229
220
  return null;
230
221
  }
231
- return {
232
- latitude,
233
- longitude,
234
- fallbackCity: asCity(geo?.city),
235
- };
222
+ return { latitude, longitude };
236
223
  }
237
224
 
238
225
  async function tryBrowserGeolocation(): Promise<Coordinates | null> {
@@ -266,23 +253,6 @@ async function tryBrowserGeolocation(): Promise<Coordinates | null> {
266
253
  });
267
254
  }
268
255
 
269
- async function reverseGeocodeCity(
270
- fetchFn: typeof fetch,
271
- latitude: number,
272
- longitude: number,
273
- ): Promise<string | undefined> {
274
- const url =
275
- `${REVERSE_GEO_URL}?latitude=${encodeURIComponent(String(latitude))}` +
276
- `&longitude=${encodeURIComponent(String(longitude))}` +
277
- `&localityLanguage=en`;
278
- const payload = (await fetchJson(fetchFn, url)) as ReverseGeoPayload | null;
279
- return (
280
- asCity(payload?.city) ??
281
- asCity(payload?.locality) ??
282
- asCity(payload?.principalSubdivision)
283
- );
284
- }
285
-
286
256
  function asNumber(value: unknown): number | null {
287
257
  if (typeof value === 'number' && Number.isFinite(value)) {
288
258
  return value;
@@ -294,17 +264,6 @@ function asNumber(value: unknown): number | null {
294
264
  return null;
295
265
  }
296
266
 
297
- function asCity(value: unknown): string | undefined {
298
- if (typeof value !== 'string') {
299
- return undefined;
300
- }
301
- const trimmed = value.trim();
302
- if (!trimmed || trimmed.toLowerCase() === 'n/a') {
303
- return undefined;
304
- }
305
- return trimmed;
306
- }
307
-
308
267
  async function fetchJson(
309
268
  fetchFn: typeof fetch,
310
269
  url: string,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@africanies/angular-web-sdk",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "packageManager": "pnpm@11.24.0",
5
5
  "license": "MIT",
6
6
  "contributors": [