@africanies/angular-web-sdk 0.1.11 → 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.
Files changed (228) hide show
  1. package/.nx/cache/{13466509747676776487 → 13293303228070520113}/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs +66 -1
  2. package/.nx/cache/13293303228070520113/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +1 -0
  3. package/.nx/cache/{13466509747676776487 → 13293303228070520113}/dist/libs/africanies-core/types/africanies-africanies-core.d.ts +6 -1
  4. package/.nx/cache/{6673686859704879504 → 13387631073062967069}/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs +31 -13
  5. package/.nx/cache/13387631073062967069/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +1 -0
  6. package/.nx/cache/{6673686859704879504 → 13387631073062967069}/dist/libs/africanies-theme/tailwind-preset.cjs +85 -17
  7. package/.nx/cache/{6673686859704879504 → 13387631073062967069}/dist/libs/africanies-theme/types/africanies-africanies-theme.d.ts +7 -3
  8. package/.nx/cache/16704640000606796352/dist/libs/africanies-storage/README.md +31 -0
  9. package/.nx/cache/16704640000606796352/dist/libs/africanies-storage/fesm2022/africanies-africanies-storage.mjs +249 -0
  10. package/.nx/cache/16704640000606796352/dist/libs/africanies-storage/fesm2022/africanies-africanies-storage.mjs.map +1 -0
  11. package/.nx/cache/16704640000606796352/dist/libs/africanies-storage/package.json +26 -0
  12. package/.nx/cache/16704640000606796352/dist/libs/africanies-storage/types/africanies-africanies-storage.d.ts +210 -0
  13. package/.nx/cache/{14876618240301288353 → 2427575433215940812}/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +2998 -1122
  14. package/.nx/cache/2427575433215940812/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -0
  15. package/.nx/cache/{14995846848874973576 → 2427575433215940812}/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +450 -140
  16. package/.nx/cache/{6664915487114632770 → 7730350229628359718}/dist/libs/africanies-icons/assets/icons.sprite.svg +7 -0
  17. package/.nx/cache/{6664915487114632770 → 7730350229628359718}/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs +7 -0
  18. package/.nx/cache/7730350229628359718/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs.map +1 -0
  19. package/.nx/cache/{6664915487114632770 → 7730350229628359718}/dist/libs/africanies-icons/types/africanies-africanies-icons.d.ts +2 -2
  20. package/.nx/cache/{14995846848874973576 → 7821397806208783174}/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +2572 -962
  21. package/.nx/cache/7821397806208783174/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -0
  22. package/.nx/cache/{14876618240301288353 → 7821397806208783174}/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +460 -147
  23. package/.nx/cache/run.json +35 -35
  24. package/.nx/cache/terminalOutputs/10603154579345867998 +397 -0
  25. package/.nx/cache/terminalOutputs/12354163057083828162 +80 -0
  26. package/.nx/cache/terminalOutputs/{9040451482998546630 → 13293303228070520113} +1 -1
  27. package/.nx/cache/terminalOutputs/{6673686859704879504 → 13387631073062967069} +1 -1
  28. package/.nx/cache/terminalOutputs/{14061940909886036014 → 14876351543379349919} +46 -43
  29. package/.nx/cache/terminalOutputs/15336883221450327589 +21 -0
  30. package/.nx/cache/terminalOutputs/16208231681037145050 +87 -0
  31. package/.nx/cache/terminalOutputs/{13466509747676776487 → 16704640000606796352} +6 -6
  32. package/.nx/cache/terminalOutputs/1811006905937373707 +74 -0
  33. package/.nx/cache/terminalOutputs/2005781115771702035 +8 -0
  34. package/.nx/cache/terminalOutputs/2360743690449633551 +21 -0
  35. package/.nx/cache/terminalOutputs/{14876618240301288353 → 2427575433215940812} +2 -2
  36. package/.nx/cache/terminalOutputs/2981230413857865352 +8 -0
  37. package/.nx/cache/terminalOutputs/3243550283886864639 +8 -0
  38. package/.nx/cache/terminalOutputs/3380188914363501109 +231 -0
  39. package/.nx/cache/terminalOutputs/3699068646900552690 +8 -0
  40. package/.nx/cache/terminalOutputs/5175669339257931410 +8 -0
  41. package/.nx/cache/terminalOutputs/6040230831125637867 +8 -0
  42. package/.nx/cache/terminalOutputs/7730350229628359718 +21 -0
  43. package/.nx/cache/terminalOutputs/{14995846848874973576 → 7821397806208783174} +2 -2
  44. package/.nx/cache/terminalOutputs/8098730470212963224 +87 -0
  45. package/.nx/cache/terminalOutputs/8928690901537686687 +21 -0
  46. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db +0 -0
  47. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db-shm +0 -0
  48. package/.nx/workspace-data/0091ABA9-5C90-580D-953C-8C94E763ADB5-v3.db-wal +0 -0
  49. package/.nx/workspace-data/d/daemon.log +4176 -0
  50. package/.nx/workspace-data/d/server-process.json +2 -2
  51. package/.nx/workspace-data/file-map.json +2995 -2989
  52. package/.nx/workspace-data/nx_files.nxt +0 -0
  53. package/.nx/workspace-data/project-graph.json +2 -2
  54. package/apps/playground/src/app/pages/forms-page.ts +70 -14
  55. package/apps/playground/src/app/snippets/forms.snippets.ts +41 -6
  56. package/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs +60 -0
  57. package/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +1 -1
  58. package/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs +6 -0
  59. package/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs.map +1 -1
  60. package/dist/libs/africanies-storage/fesm2022/africanies-africanies-storage.mjs +6 -0
  61. package/dist/libs/africanies-storage/fesm2022/africanies-africanies-storage.mjs.map +1 -1
  62. package/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs +6 -0
  63. package/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +1 -1
  64. package/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs +1323 -85
  65. package/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +1 -1
  66. package/dist/libs/africanies-ui/types/africanies-africanies-ui.d.ts +201 -15
  67. package/libs/africanies-core/src/lib/auth/auth-token.service.ts +3 -0
  68. package/libs/africanies-core/src/lib/auth/auth.service.ts +3 -0
  69. package/libs/africanies-core/src/lib/country/country.service.ts +3 -0
  70. package/libs/africanies-core/src/lib/currency/currency.service.ts +3 -0
  71. package/libs/africanies-core/src/lib/document/document.service.ts +3 -0
  72. package/libs/africanies-core/src/lib/file/file.service.ts +3 -0
  73. package/libs/africanies-core/src/lib/filters/filter-options.resolver.ts +3 -0
  74. package/libs/africanies-core/src/lib/http/api-client.ts +3 -0
  75. package/libs/africanies-core/src/lib/mode/mode-config.service.ts +3 -0
  76. package/libs/africanies-core/src/lib/notification/notification.service.ts +3 -0
  77. package/libs/africanies-core/src/lib/overlay/route-overlay.service.ts +3 -0
  78. package/libs/africanies-core/src/lib/payment-method/payment-method.service.ts +3 -0
  79. package/libs/africanies-core/src/lib/plan/plan.service.ts +3 -0
  80. package/libs/africanies-core/src/lib/product/product.service.ts +3 -0
  81. package/libs/africanies-core/src/lib/service/service.service.ts +3 -0
  82. package/libs/africanies-core/src/lib/shipment-method/shipment-method.service.ts +3 -0
  83. package/libs/africanies-core/src/lib/shipping/shipping-mode.service.ts +3 -0
  84. package/libs/africanies-core/src/lib/user/user.service.ts +3 -0
  85. package/libs/africanies-core/src/lib/warehouse/warehouse.service.ts +3 -0
  86. package/libs/africanies-core/src/lib/zone/zone.service.ts +3 -0
  87. package/libs/africanies-icons/src/lib/africanies-icon.component.ts +3 -0
  88. package/libs/africanies-icons/src/lib/icon-registry.service.ts +3 -0
  89. package/libs/africanies-storage/src/lib/local-storage.service.ts +3 -0
  90. package/libs/africanies-storage/src/lib/session-storage.service.ts +3 -0
  91. package/libs/africanies-theme/src/lib/mode-color.service.ts +3 -0
  92. package/libs/africanies-theme/src/lib/theme.service.ts +3 -0
  93. package/libs/africanies-ui/src/index.ts +18 -0
  94. package/libs/africanies-ui/src/lib/accordion/accordion.component.ts +3 -0
  95. package/libs/africanies-ui/src/lib/action-menu/action-menu-trigger.directive.ts +3 -0
  96. package/libs/africanies-ui/src/lib/action-menu/action-menu.component.ts +3 -0
  97. package/libs/africanies-ui/src/lib/alert/alert.component.ts +3 -0
  98. package/libs/africanies-ui/src/lib/avatar/avatar-menu.component.ts +3 -0
  99. package/libs/africanies-ui/src/lib/avatar/avatar.component.ts +3 -0
  100. package/libs/africanies-ui/src/lib/brand/brand-logo.component.ts +3 -0
  101. package/libs/africanies-ui/src/lib/button/button.component.ts +3 -0
  102. package/libs/africanies-ui/src/lib/carrier/carrier-logo.component.ts +3 -0
  103. package/libs/africanies-ui/src/lib/chip/chip.component.ts +3 -0
  104. package/libs/africanies-ui/src/lib/content-stack/content-stack.component.ts +3 -0
  105. package/libs/africanies-ui/src/lib/copy-button/copy-button.component.ts +3 -0
  106. package/libs/africanies-ui/src/lib/feedback/async-state.component.ts +3 -0
  107. package/libs/africanies-ui/src/lib/feedback/empty-state.component.ts +3 -0
  108. package/libs/africanies-ui/src/lib/feedback/error-indicator.component.ts +3 -0
  109. package/libs/africanies-ui/src/lib/feedback/error-state.component.ts +3 -0
  110. package/libs/africanies-ui/src/lib/feedback/loading-state.component.ts +3 -0
  111. package/libs/africanies-ui/src/lib/filters/filter-drawer.panel.ts +3 -0
  112. package/libs/africanies-ui/src/lib/filters/filter-drawer.service.ts +3 -0
  113. package/libs/africanies-ui/src/lib/filters/filter-query.service.ts +3 -0
  114. package/libs/africanies-ui/src/lib/forms/address-input/address-input.component.ts +3 -0
  115. package/libs/africanies-ui/src/lib/forms/address-input/google-places.service.ts +3 -0
  116. package/libs/africanies-ui/src/lib/forms/checkbox/checkbox.component.ts +3 -0
  117. package/libs/africanies-ui/src/lib/forms/date-picker/date-picker.component.ts +3 -0
  118. package/libs/africanies-ui/src/lib/forms/file-upload/camera-capture.dialog.ts +3 -0
  119. package/libs/africanies-ui/src/lib/forms/file-upload/file-preview.dialog.ts +3 -0
  120. package/libs/africanies-ui/src/lib/forms/file-upload/file-upload.component.ts +3 -0
  121. package/libs/africanies-ui/src/lib/forms/index.ts +20 -0
  122. package/libs/africanies-ui/src/lib/forms/number-input/number-input.component.ts +3 -0
  123. package/libs/africanies-ui/src/lib/forms/otp-input/otp-input.component.ts +3 -0
  124. package/libs/africanies-ui/src/lib/forms/phone-input/index.ts +21 -0
  125. package/libs/africanies-ui/src/lib/forms/phone-input/phone-codes.ts +264 -0
  126. package/libs/africanies-ui/src/lib/forms/phone-input/phone-input.component.ts +480 -0
  127. package/libs/africanies-ui/src/lib/forms/phone-input/phone-input.types.ts +35 -0
  128. package/libs/africanies-ui/src/lib/forms/phone-input/phone-input.utils.spec.ts +81 -0
  129. package/libs/africanies-ui/src/lib/forms/phone-input/phone-input.utils.ts +275 -0
  130. package/libs/africanies-ui/src/lib/forms/radio/radio.component.ts +3 -0
  131. package/libs/africanies-ui/src/lib/forms/search-combobox/search-combobox.component.ts +3 -0
  132. package/libs/africanies-ui/src/lib/forms/select/select.component.ts +3 -0
  133. package/libs/africanies-ui/src/lib/forms/text-input/text-input.component.ts +3 -0
  134. package/libs/africanies-ui/src/lib/forms/textarea/textarea.component.ts +3 -0
  135. package/libs/africanies-ui/src/lib/forms/toggle/toggle.component.ts +3 -0
  136. package/libs/africanies-ui/src/lib/image/image.component.ts +3 -0
  137. package/libs/africanies-ui/src/lib/info-popover/info-popover-content.directive.ts +3 -0
  138. package/libs/africanies-ui/src/lib/info-popover/info-popover-trigger.directive.ts +3 -0
  139. package/libs/africanies-ui/src/lib/info-popover/info-popover.component.ts +3 -0
  140. package/libs/africanies-ui/src/lib/layout/app-shell-content-header.component.ts +3 -0
  141. package/libs/africanies-ui/src/lib/layout/app-shell-header-slot.directive.ts +3 -0
  142. package/libs/africanies-ui/src/lib/layout/app-shell-header.component.ts +9 -0
  143. package/libs/africanies-ui/src/lib/layout/app-shell.component.ts +3 -0
  144. package/libs/africanies-ui/src/lib/layout/header-weather.spec.ts +62 -94
  145. package/libs/africanies-ui/src/lib/layout/header-weather.ts +54 -61
  146. package/libs/africanies-ui/src/lib/layout/page-header.component.ts +3 -0
  147. package/libs/africanies-ui/src/lib/modules/africanies-actions.module.ts +3 -0
  148. package/libs/africanies-ui/src/lib/modules/africanies-feedback.module.ts +3 -0
  149. package/libs/africanies-ui/src/lib/modules/africanies-forms.module.ts +5 -0
  150. package/libs/africanies-ui/src/lib/modules/africanies-info-popover.module.ts +3 -0
  151. package/libs/africanies-ui/src/lib/modules/africanies-navigation.module.ts +3 -0
  152. package/libs/africanies-ui/src/lib/modules/africanies-stepper.module.ts +3 -0
  153. package/libs/africanies-ui/src/lib/modules/africanies-table.module.ts +3 -0
  154. package/libs/africanies-ui/src/lib/modules/africanies-tooltip.module.ts +3 -0
  155. package/libs/africanies-ui/src/lib/navigation/breadcrumb/breadcrumb.component.ts +3 -0
  156. package/libs/africanies-ui/src/lib/navigation/segment/segment.component.ts +5 -1
  157. package/libs/africanies-ui/src/lib/navigation/shipping-mode-switch/shipping-mode-switch.component.ts +3 -0
  158. package/libs/africanies-ui/src/lib/navigation/side-nav/africanies-side-nav.component.ts +3 -0
  159. package/libs/africanies-ui/src/lib/navigation/tabs/tab-def.directive.ts +3 -0
  160. package/libs/africanies-ui/src/lib/navigation/tabs/tabs.component.ts +3 -0
  161. package/libs/africanies-ui/src/lib/notifications/notification-drawer.panel.ts +3 -0
  162. package/libs/africanies-ui/src/lib/notifications/notification-drawer.service.ts +3 -0
  163. package/libs/africanies-ui/src/lib/overlay/confirm-dialog.component.ts +3 -0
  164. package/libs/africanies-ui/src/lib/overlay/confirm.service.ts +3 -0
  165. package/libs/africanies-ui/src/lib/overlay/drawer.service.ts +3 -0
  166. package/libs/africanies-ui/src/lib/overlay/modal.service.ts +3 -0
  167. package/libs/africanies-ui/src/lib/overlay/overlay-frame.component.ts +9 -0
  168. package/libs/africanies-ui/src/lib/pagination/pagination.component.ts +3 -0
  169. package/libs/africanies-ui/src/lib/stepper/step-def.directive.ts +3 -0
  170. package/libs/africanies-ui/src/lib/stepper/stepper.component.ts +3 -0
  171. package/libs/africanies-ui/src/lib/table/cell-def.directive.ts +3 -0
  172. package/libs/africanies-ui/src/lib/table/header-cell-def.directive.ts +3 -0
  173. package/libs/africanies-ui/src/lib/table/row-detail-def.directive.ts +3 -0
  174. package/libs/africanies-ui/src/lib/table/table.component.ts +3 -0
  175. package/libs/africanies-ui/src/lib/toast/toast-host.component.ts +4 -1
  176. package/libs/africanies-ui/src/lib/toast/toast-item.component.ts +3 -0
  177. package/libs/africanies-ui/src/lib/toast/toast.service.ts +3 -0
  178. package/libs/africanies-ui/src/lib/tooltip/tooltip-trigger.directive.ts +3 -0
  179. package/libs/africanies-ui/src/lib/tooltip/tooltip.component.ts +3 -0
  180. package/package.json +1 -1
  181. package/.nx/cache/13466509747676776487/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +0 -1
  182. package/.nx/cache/14416694322692463866/dist/libs/africanies-models/README.md +0 -60
  183. package/.nx/cache/14416694322692463866/dist/libs/africanies-models/fesm2022/africanies-africanies-models.mjs +0 -599
  184. package/.nx/cache/14416694322692463866/dist/libs/africanies-models/fesm2022/africanies-africanies-models.mjs.map +0 -1
  185. package/.nx/cache/14416694322692463866/dist/libs/africanies-models/package.json +0 -23
  186. package/.nx/cache/14416694322692463866/dist/libs/africanies-models/types/africanies-africanies-models.d.ts +0 -1452
  187. package/.nx/cache/14876618240301288353/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +0 -1
  188. package/.nx/cache/14995846848874973576/dist/libs/africanies-ui/fesm2022/africanies-africanies-ui.mjs.map +0 -1
  189. package/.nx/cache/17626862048742598274/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs +0 -221
  190. package/.nx/cache/17626862048742598274/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +0 -1
  191. package/.nx/cache/17626862048742598274/dist/libs/africanies-theme/tailwind-preset.cjs +0 -253
  192. package/.nx/cache/17626862048742598274/dist/libs/africanies-theme/types/africanies-africanies-theme.d.ts +0 -166
  193. package/.nx/cache/6664915487114632770/dist/libs/africanies-icons/fesm2022/africanies-africanies-icons.mjs.map +0 -1
  194. package/.nx/cache/6673686859704879504/dist/libs/africanies-theme/README.md +0 -46
  195. package/.nx/cache/6673686859704879504/dist/libs/africanies-theme/fesm2022/africanies-africanies-theme.mjs.map +0 -1
  196. package/.nx/cache/6673686859704879504/dist/libs/africanies-theme/package.json +0 -35
  197. package/.nx/cache/9040451482998546630/dist/libs/africanies-core/README.md +0 -85
  198. package/.nx/cache/9040451482998546630/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs +0 -4511
  199. package/.nx/cache/9040451482998546630/dist/libs/africanies-core/fesm2022/africanies-africanies-core.mjs.map +0 -1
  200. package/.nx/cache/9040451482998546630/dist/libs/africanies-core/package.json +0 -31
  201. package/.nx/cache/9040451482998546630/dist/libs/africanies-core/types/africanies-africanies-core.d.ts +0 -2741
  202. package/.nx/cache/terminalOutputs/13555699813138069787 +0 -6
  203. package/.nx/cache/terminalOutputs/14416694322692463866 +0 -108
  204. package/.nx/cache/terminalOutputs/1446890025183346611 +0 -32
  205. package/.nx/cache/terminalOutputs/17394769338046050914 +0 -8
  206. package/.nx/cache/terminalOutputs/17626862048742598274 +0 -21
  207. package/.nx/cache/terminalOutputs/6337357866566791975 +0 -6
  208. package/.nx/cache/terminalOutputs/6664915487114632770 +0 -44
  209. package/.nx/cache/terminalOutputs/783228502946611950 +0 -19
  210. package/.nx/cache/terminalOutputs/7978448102466861029 +0 -32
  211. /package/.nx/cache/{13466509747676776487 → 13293303228070520113}/dist/libs/africanies-core/README.md +0 -0
  212. /package/.nx/cache/{13466509747676776487 → 13293303228070520113}/dist/libs/africanies-core/package.json +0 -0
  213. /package/.nx/cache/{17626862048742598274 → 13387631073062967069}/dist/libs/africanies-theme/README.md +0 -0
  214. /package/.nx/cache/{17626862048742598274 → 13387631073062967069}/dist/libs/africanies-theme/package.json +0 -0
  215. /package/.nx/cache/{14876618240301288353 → 2427575433215940812}/dist/libs/africanies-ui/README.md +0 -0
  216. /package/.nx/cache/{14876618240301288353 → 2427575433215940812}/dist/libs/africanies-ui/assets/brand/africanies-logo-mini.png +0 -0
  217. /package/.nx/cache/{14876618240301288353 → 2427575433215940812}/dist/libs/africanies-ui/assets/brand/africanies-logo.png +0 -0
  218. /package/.nx/cache/{14876618240301288353 → 2427575433215940812}/dist/libs/africanies-ui/assets/brand/africanies-logo.svg +0 -0
  219. /package/.nx/cache/{14876618240301288353 → 2427575433215940812}/dist/libs/africanies-ui/assets/carriers/dhl.svg +0 -0
  220. /package/.nx/cache/{14876618240301288353 → 2427575433215940812}/dist/libs/africanies-ui/package.json +0 -0
  221. /package/.nx/cache/{6664915487114632770 → 7730350229628359718}/dist/libs/africanies-icons/README.md +0 -0
  222. /package/.nx/cache/{6664915487114632770 → 7730350229628359718}/dist/libs/africanies-icons/package.json +0 -0
  223. /package/.nx/cache/{14995846848874973576 → 7821397806208783174}/dist/libs/africanies-ui/README.md +0 -0
  224. /package/.nx/cache/{14995846848874973576 → 7821397806208783174}/dist/libs/africanies-ui/assets/brand/africanies-logo-mini.png +0 -0
  225. /package/.nx/cache/{14995846848874973576 → 7821397806208783174}/dist/libs/africanies-ui/assets/brand/africanies-logo.png +0 -0
  226. /package/.nx/cache/{14995846848874973576 → 7821397806208783174}/dist/libs/africanies-ui/assets/brand/africanies-logo.svg +0 -0
  227. /package/.nx/cache/{14995846848874973576 → 7821397806208783174}/dist/libs/africanies-ui/assets/carriers/dhl.svg +0 -0
  228. /package/.nx/cache/{14995846848874973576 → 7821397806208783174}/dist/libs/africanies-ui/package.json +0 -0
@@ -0,0 +1,275 @@
1
+ import { countryFlagUrl } from '@africanies/africanies-core';
2
+
3
+ import {
4
+ PHONE_DIAL_CODES,
5
+ PHONE_MAX_NATIONAL_DIGITS,
6
+ PHONE_MIN_NATIONAL_DIGITS,
7
+ } from './phone-codes';
8
+ import type { PhoneCountryOption, PhoneNumberValue } from './phone-input.types';
9
+
10
+ const regionNames =
11
+ typeof Intl !== 'undefined' && 'DisplayNames' in Intl
12
+ ? new Intl.DisplayNames(['en'], { type: 'region' })
13
+ : null;
14
+
15
+ /** Digits only from a phone draft. */
16
+ export function phoneDigitsOnly(value: string | null | undefined): string {
17
+ return (value ?? '').replace(/\D/g, '');
18
+ }
19
+
20
+ /** Normalize ISO2 to uppercase two letters, or `''`. */
21
+ export function normalizePhoneIso2(iso2: string | null | undefined): string {
22
+ const code = (iso2 ?? '').trim().toUpperCase();
23
+ return /^[A-Z]{2}$/.test(code) ? code : '';
24
+ }
25
+
26
+ /** Dial code for an ISO2 (with `+`), or `''` when unknown. */
27
+ export function dialCodeForIso2(iso2: string | null | undefined): string {
28
+ const code = normalizePhoneIso2(iso2);
29
+ return code ? (PHONE_DIAL_CODES[code] ?? '') : '';
30
+ }
31
+
32
+ /** English country name from ISO2 (falls back to the code). */
33
+ export function phoneCountryName(iso2: string): string {
34
+ const code = normalizePhoneIso2(iso2);
35
+ if (!code) {
36
+ return '';
37
+ }
38
+ try {
39
+ return regionNames?.of(code) ?? code;
40
+ } catch {
41
+ return code;
42
+ }
43
+ }
44
+
45
+ /** All dial-code countries for the picker (sorted by name). */
46
+ export function phoneCountryOptions(): PhoneCountryOption[] {
47
+ return Object.keys(PHONE_DIAL_CODES)
48
+ .map((iso2) => {
49
+ const dialCode = PHONE_DIAL_CODES[iso2]!;
50
+ return {
51
+ iso2,
52
+ dialCode,
53
+ name: phoneCountryName(iso2),
54
+ flagUrl: countryFlagUrl(iso2, { width: 40 }),
55
+ };
56
+ })
57
+ .sort((a, b) => a.name.localeCompare(b.name));
58
+ }
59
+
60
+ /**
61
+ * Filter dial-code countries by name, ISO2, or dial digits.
62
+ * Digit matching is skipped when the query has no digits — otherwise
63
+ * `''.includes('')` would keep every country for letter-only searches.
64
+ */
65
+ export function filterPhoneCountries(
66
+ countries: readonly PhoneCountryOption[],
67
+ query: string,
68
+ ): PhoneCountryOption[] {
69
+ const q = query.trim().toLowerCase();
70
+ if (!q) {
71
+ return [...countries];
72
+ }
73
+ const qDigits = phoneDigitsOnly(q);
74
+ return countries.filter((row) => {
75
+ if (
76
+ row.name.toLowerCase().includes(q) ||
77
+ row.iso2.toLowerCase().includes(q) ||
78
+ row.dialCode.toLowerCase().includes(q)
79
+ ) {
80
+ return true;
81
+ }
82
+ return qDigits.length > 0
83
+ ? phoneDigitsOnly(row.dialCode).includes(qDigits)
84
+ : false;
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Match an E.164 / international string to the longest known dial code.
90
+ * Prefers `hintIso2` when several countries share the same dial code (e.g. `+1`).
91
+ */
92
+ export function matchDialCode(
93
+ e164OrInternational: string,
94
+ hintIso2?: string | null,
95
+ ): { iso2: string; dialCode: string; nationalDigits: string } | null {
96
+ const digits = phoneDigitsOnly(e164OrInternational);
97
+ if (!digits) {
98
+ return null;
99
+ }
100
+
101
+ const hint = normalizePhoneIso2(hintIso2);
102
+ if (hint) {
103
+ const hintDial = PHONE_DIAL_CODES[hint];
104
+ if (hintDial) {
105
+ const hintDigits = phoneDigitsOnly(hintDial);
106
+ if (digits.startsWith(hintDigits)) {
107
+ return {
108
+ iso2: hint,
109
+ dialCode: hintDial,
110
+ nationalDigits: digits.slice(hintDigits.length),
111
+ };
112
+ }
113
+ }
114
+ }
115
+
116
+ let best: { iso2: string; dialCode: string; nationalDigits: string } | null =
117
+ null;
118
+ for (const [iso2, dialCode] of Object.entries(PHONE_DIAL_CODES)) {
119
+ const dialDigits = phoneDigitsOnly(dialCode);
120
+ if (!digits.startsWith(dialDigits)) {
121
+ continue;
122
+ }
123
+ const nationalDigits = digits.slice(dialDigits.length);
124
+ if (
125
+ !best ||
126
+ dialDigits.length > phoneDigitsOnly(best.dialCode).length
127
+ ) {
128
+ best = { iso2, dialCode, nationalDigits };
129
+ }
130
+ }
131
+ return best;
132
+ }
133
+
134
+ /**
135
+ * Strip a leading dial code from a stored phone (classic `removeDialCode`).
136
+ * Returns national digits for prefilling the input.
137
+ */
138
+ export function removeDialCode(
139
+ phone: string | null | undefined,
140
+ hintIso2?: string | null,
141
+ ): string {
142
+ const raw = (phone ?? '').trim();
143
+ if (!raw) {
144
+ return '';
145
+ }
146
+ const matched = matchDialCode(raw, hintIso2);
147
+ if (matched) {
148
+ return matched.nationalDigits;
149
+ }
150
+ return phoneDigitsOnly(raw);
151
+ }
152
+
153
+ /** Group national digits for readable display (… + last 4). */
154
+ export function formatNationalDigits(digits: string): string {
155
+ const clean = phoneDigitsOnly(digits).slice(0, PHONE_MAX_NATIONAL_DIGITS);
156
+ if (!clean) {
157
+ return '';
158
+ }
159
+ if (clean.length <= 4) {
160
+ return clean;
161
+ }
162
+ const end = clean.slice(-4);
163
+ const head = clean.slice(0, -4).replace(/(\d{3})(?=\d)/g, '$1 ').trim();
164
+ return `${head} ${end}`.trim();
165
+ }
166
+
167
+ /**
168
+ * Build the structured value (or `null` when empty).
169
+ */
170
+ export function buildPhoneNumberValue(
171
+ iso2: string,
172
+ nationalDraft: string,
173
+ ): PhoneNumberValue | null {
174
+ const countryCode = normalizePhoneIso2(iso2);
175
+ const dialCode = dialCodeForIso2(countryCode);
176
+ if (!countryCode || !dialCode) {
177
+ return null;
178
+ }
179
+
180
+ const nationalDigits = phoneDigitsOnly(nationalDraft).slice(
181
+ 0,
182
+ PHONE_MAX_NATIONAL_DIGITS,
183
+ );
184
+ if (!nationalDigits) {
185
+ return null;
186
+ }
187
+
188
+ const number = formatNationalDigits(nationalDigits);
189
+ const e164Number = `${dialCode}${nationalDigits}`;
190
+ const internationalNumber = `${dialCode} ${number}`.trim();
191
+ // Trunk-prefix `0` for national display when digits omit it (classic ngx shape).
192
+ const nationalNumber = nationalDigits.startsWith('0')
193
+ ? number
194
+ : `0${number}`;
195
+
196
+ return {
197
+ number,
198
+ internationalNumber,
199
+ nationalNumber,
200
+ e164Number,
201
+ countryCode,
202
+ dialCode,
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Canonical outbound phone string — prefer E.164 from a structured value.
208
+ */
209
+ export function phoneToE164(
210
+ phone: PhoneNumberValue | string | null | undefined,
211
+ ): string {
212
+ if (phone == null) {
213
+ return '';
214
+ }
215
+ if (typeof phone === 'string') {
216
+ const digits = phoneDigitsOnly(phone);
217
+ if (!digits) {
218
+ return '';
219
+ }
220
+ return phone.trim().startsWith('+') ? `+${digits}` : phone.trim();
221
+ }
222
+ return phone.e164Number?.trim() || '';
223
+ }
224
+
225
+ /**
226
+ * Soft validity: has dial code + enough national digits (not full libphonenumber).
227
+ */
228
+ export function isPhoneNumberComplete(
229
+ phone: PhoneNumberValue | null | undefined,
230
+ ): boolean {
231
+ if (!phone?.e164Number) {
232
+ return false;
233
+ }
234
+ const national = phoneDigitsOnly(phone.number);
235
+ return (
236
+ national.length >= PHONE_MIN_NATIONAL_DIGITS &&
237
+ national.length <= PHONE_MAX_NATIONAL_DIGITS
238
+ );
239
+ }
240
+
241
+ /**
242
+ * Split an incoming control value (E.164 string or structured) into UI state.
243
+ */
244
+ export function parsePhoneControlValue(
245
+ value: string | PhoneNumberValue | null | undefined,
246
+ fallbackIso2: string,
247
+ ): { iso2: string; nationalDigits: string } {
248
+ const fallback = normalizePhoneIso2(fallbackIso2) || 'US';
249
+
250
+ if (value == null || value === '') {
251
+ return { iso2: fallback, nationalDigits: '' };
252
+ }
253
+
254
+ if (typeof value === 'object') {
255
+ const iso2 = normalizePhoneIso2(value.countryCode) || fallback;
256
+ const national =
257
+ phoneDigitsOnly(value.number) ||
258
+ phoneDigitsOnly(value.nationalNumber) ||
259
+ removeDialCode(value.e164Number, iso2);
260
+ return { iso2, nationalDigits: national };
261
+ }
262
+
263
+ const matched = matchDialCode(value, fallback);
264
+ if (matched) {
265
+ return {
266
+ iso2: matched.iso2,
267
+ nationalDigits: matched.nationalDigits,
268
+ };
269
+ }
270
+
271
+ return {
272
+ iso2: fallback,
273
+ nationalDigits: phoneDigitsOnly(value),
274
+ };
275
+ }
@@ -118,6 +118,9 @@ let nextRadioId = 0;
118
118
  </fieldset>
119
119
  `,
120
120
  })
121
+ /**
122
+ * Radio group following the shared AFRICANIES form field pattern.
123
+ */
121
124
  export class RadioComponent<T = string> implements ControlValueAccessor {
122
125
  private readonly modeColor = inject(ModeColorService);
123
126
 
@@ -269,6 +269,9 @@ const PANEL_POSITIONS: ConnectedPosition[] = [
269
269
  }
270
270
  `,
271
271
  })
272
+ /**
273
+ * Generic type-as-you-search combobox with a debounced async lookup and overlay result list.
274
+ */
272
275
  export class SearchComboboxComponent<T> implements ControlValueAccessor {
273
276
  private readonly destroyRef = inject(DestroyRef);
274
277
 
@@ -443,6 +443,9 @@ const SELECT_PANEL_POSITIONS: ConnectedPosition[] = [
443
443
  }
444
444
  `,
445
445
  })
446
+ /**
447
+ * Option select following the shared AFRICANIES form field pattern.
448
+ */
446
449
  export class SelectComponent<T = string> implements ControlValueAccessor {
447
450
  private readonly modal = inject(ModalService, { optional: true });
448
451
  protected readonly modeColor = inject(ModeColorService);
@@ -115,6 +115,9 @@ export type TextInputType =
115
115
  }
116
116
  `,
117
117
  })
118
+ /**
119
+ * Single-line text field — **reference** form control for `@africanies/africanies-ui`.
120
+ */
118
121
  export class TextInputComponent implements ControlValueAccessor {
119
122
  /** Stable ids so label / aria-describedby stay paired across re-renders. */
120
123
  protected readonly controlId = `africanies-text-input-${++nextTextInputId}`;
@@ -82,6 +82,9 @@ let nextTextareaId = 0;
82
82
  }
83
83
  `,
84
84
  })
85
+ /**
86
+ * Multi-line text field following the shared form-control pattern (`shared AFRICANIES form field pattern`). Supports prefix/suffix slots, field-level `error`, and {@link ControlValueAccessor}.
87
+ */
85
88
  export class TextareaComponent implements ControlValueAccessor {
86
89
  protected readonly controlId = `africanies-textarea-${++nextTextareaId}`;
87
90
  protected readonly hintId = `${this.controlId}-hint`;
@@ -105,6 +105,9 @@ let nextToggleId = 0;
105
105
  </div>
106
106
  `,
107
107
  })
108
+ /**
109
+ * Boolean switch following the shared AFRICANIES form field pattern.
110
+ */
108
111
  export class ToggleComponent implements ControlValueAccessor {
109
112
  private readonly modeColor = inject(ModeColorService);
110
113
 
@@ -95,6 +95,9 @@ export type ImageFit = 'cover' | 'contain';
95
95
  </div>
96
96
  `,
97
97
  })
98
+ /**
99
+ * Image with a shaped loading frame and fallback while the asset fetches or if it fails to render.
100
+ */
98
101
  export class ImageComponent {
99
102
  private readonly imgRef = viewChild<ElementRef<HTMLImageElement>>('img');
100
103
 
@@ -20,4 +20,7 @@ import { Directive } from '@angular/core';
20
20
  selector: '[africaniesInfoPopoverContent]',
21
21
  standalone: true,
22
22
  })
23
+ /**
24
+ * Marks projected content as the panel body for {@link InfoPopoverComponent}.
25
+ */
23
26
  export class InfoPopoverContentDirective {}
@@ -18,4 +18,7 @@ import { Directive } from '@angular/core';
18
18
  selector: '[africaniesInfoPopoverTrigger]',
19
19
  standalone: true,
20
20
  })
21
+ /**
22
+ * Marks projected content as the custom trigger for {@link InfoPopoverComponent}.
23
+ */
21
24
  export class InfoPopoverTriggerDirective {}
@@ -205,6 +205,9 @@ const PLACEMENT_POSITIONS: Record<InfoPopoverPlacement, ConnectedPosition[]> = {
205
205
  </ng-template>
206
206
  `,
207
207
  })
208
+ /**
209
+ * Hover / focus / click info panel with a white card, optional title, and fully projected body content (any child component or markup).
210
+ */
208
211
  export class InfoPopoverComponent {
209
212
  protected readonly panelId = `africanies-info-popover-${++nextInfoPopoverId}`;
210
213
 
@@ -71,6 +71,9 @@ import { PageHeaderComponent } from './page-header.component';
71
71
  }
72
72
  `,
73
73
  })
74
+ /**
75
+ * Content chrome above the app-shell body: Back, breadcrumbs, then the page title and optional subtitle.
76
+ */
74
77
  export class AppShellContentHeaderComponent {
75
78
  private readonly router = inject(Router);
76
79
 
@@ -9,4 +9,7 @@ import { Directive } from '@angular/core';
9
9
  selector: '[africaniesAppShellHeader]',
10
10
  standalone: true,
11
11
  })
12
+ /**
13
+ * Marks projected content as the custom header for {@link AppShellComponent}.
14
+ */
12
15
  export class AppShellHeaderSlotDirective {}
@@ -38,6 +38,9 @@ export type AppShellHeaderDensity = 'mobile' | 'tablet' | 'desktop';
38
38
  selector: '[africaniesAppShellHeaderStart]',
39
39
  standalone: true,
40
40
  })
41
+ /**
42
+ * Marks projected content as extra leading chrome in {@link AppShellHeaderComponent}.
43
+ */
41
44
  export class AppShellHeaderStartDirective {}
42
45
 
43
46
  /**
@@ -47,6 +50,9 @@ export class AppShellHeaderStartDirective {}
47
50
  selector: '[africaniesAppShellHeaderEnd]',
48
51
  standalone: true,
49
52
  })
53
+ /**
54
+ * Marks projected content as extra trailing chrome in {@link AppShellHeaderComponent}.
55
+ */
50
56
  export class AppShellHeaderEndDirective {}
51
57
 
52
58
  /**
@@ -206,6 +212,9 @@ export class AppShellHeaderEndDirective {}
206
212
  </div>
207
213
  `,
208
214
  })
215
+ /**
216
+ * Product header bar for {@link AppShellComponent}: a short time-of-day kicker plus the given name, local weather, live clock, notification inbox drawer, and avatar menu. Breadcrumbs and Back belong in the content column via {@link AppShellContentHeaderComponent}.
217
+ */
209
218
  export class AppShellHeaderComponent {
210
219
  private readonly notificationsDrawer = inject(NotificationDrawerService);
211
220
  private readonly destroyRef = inject(DestroyRef);
@@ -85,6 +85,9 @@ export type AppShellLayoutPreview = 'mobile' | 'tablet' | 'desktop';
85
85
  },
86
86
  templateUrl: './app-shell.component.html',
87
87
  })
88
+ /**
89
+ * Application chrome scaffold: side nav + sticky header + constrained content.
90
+ */
88
91
  export class AppShellComponent {
89
92
  protected readonly modeColor = inject(ModeColorService);
90
93
  protected readonly logoMiniUrl = inject(AFRICANIES_BRAND_LOGO_MINI_URL);
@@ -66,18 +66,15 @@ 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('does not label the device with the IP city', async () => {
70
70
  const fetchFn = jest.fn(async (url: string) => {
71
71
  if (String(url).includes('geojs')) {
72
72
  return jsonResponse({
73
- latitude: '9.0765',
74
- longitude: '7.3986',
73
+ latitude: '6.45',
74
+ longitude: '3.4',
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,38 +85,18 @@ 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',
88
+ city: undefined,
113
89
  });
90
+ expect(fetchFn).not.toHaveBeenCalledWith(
91
+ expect.stringContaining('geocoding-api'),
92
+ );
114
93
  });
115
94
 
116
- it('reuses the same-hour session cache', async () => {
95
+ it('reuses the same-hour session cache when the device city was resolved', async () => {
96
+ stubDeviceAt(9.0765, 7.3986);
117
97
  const fetchFn = jest.fn(async (url: string) => {
118
- if (String(url).includes('geojs')) {
119
- return jsonResponse({ latitude: 1, longitude: 2 });
120
- }
121
- if (String(url).includes('bigdatacloud')) {
122
- return jsonResponse({ city: 'Accra' });
98
+ if (String(url).includes('geocoding-api')) {
99
+ return jsonResponse({ results: [{ name: 'Abuja' }] });
123
100
  }
124
101
  return jsonResponse({ current: { weather_code: 0, temperature_2m: 30 } });
125
102
  });
@@ -127,17 +104,16 @@ describe('loadHeaderWeather', () => {
127
104
  const first = await loadHeaderWeather(fetchFn as unknown as typeof fetch);
128
105
  const second = await loadHeaderWeather(fetchFn as unknown as typeof fetch);
129
106
 
130
- expect(first).toEqual(second);
131
- expect(fetchFn).toHaveBeenCalledTimes(3);
107
+ expect(first).toEqual({ kind: 'clear', temperatureC: 30, city: 'Abuja' });
108
+ expect(second).toEqual(first);
109
+ expect(fetchFn).toHaveBeenCalledTimes(2);
132
110
  });
133
111
 
134
112
  it('fetches again when the cached hour no longer matches', async () => {
113
+ stubDeviceAt(9.0765, 7.3986);
135
114
  const fetchFn = jest.fn(async (url: string) => {
136
- 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' });
115
+ if (String(url).includes('geocoding-api')) {
116
+ return jsonResponse({ results: [{ name: 'Abuja' }] });
141
117
  }
142
118
  return jsonResponse({ current: { weather_code: 0, temperature_2m: 30.2 } });
143
119
  });
@@ -146,7 +122,7 @@ describe('loadHeaderWeather', () => {
146
122
  const cacheKey = Object.keys(sessionStorage).find((key) =>
147
123
  key.startsWith('africanies-header-weather'),
148
124
  );
149
- expect(cacheKey).toBeDefined();
125
+ expect(cacheKey).toBe('africanies-header-weather-v8');
150
126
  const stored = JSON.parse(sessionStorage.getItem(cacheKey!) ?? '{}') as {
151
127
  hour?: string;
152
128
  };
@@ -154,35 +130,15 @@ describe('loadHeaderWeather', () => {
154
130
  sessionStorage.setItem(cacheKey!, JSON.stringify(stored));
155
131
 
156
132
  await loadHeaderWeather(fetchFn as unknown as typeof fetch);
157
- expect(fetchFn).toHaveBeenCalledTimes(6);
133
+ expect(fetchFn).toHaveBeenCalledTimes(4);
158
134
  });
159
135
 
160
- it('uses browser coordinates when geolocation succeeds', async () => {
161
- Object.defineProperty(navigator, 'geolocation', {
162
- configurable: true,
163
- value: {
164
- getCurrentPosition: (success: PositionCallback) => {
165
- success({
166
- coords: {
167
- latitude: 9.0765,
168
- longitude: 7.3986,
169
- accuracy: 10,
170
- altitude: null,
171
- altitudeAccuracy: null,
172
- heading: null,
173
- speed: null,
174
- toJSON: () => ({}),
175
- },
176
- timestamp: Date.now(),
177
- toJSON: () => ({}),
178
- } as GeolocationPosition);
179
- },
180
- },
181
- });
136
+ it('uses device coordinates and ignores the IP city', async () => {
137
+ stubDeviceAt(9.0765, 7.3986);
182
138
 
183
139
  const fetchFn = jest.fn(async (url: string) => {
184
- if (String(url).includes('bigdatacloud')) {
185
- return jsonResponse({ city: 'Abuja' });
140
+ if (String(url).includes('geocoding-api')) {
141
+ return jsonResponse({ results: [{ name: 'Abuja' }] });
186
142
  }
187
143
  if (String(url).includes('geojs')) {
188
144
  return jsonResponse({
@@ -201,34 +157,15 @@ describe('loadHeaderWeather', () => {
201
157
  temperatureC: 31,
202
158
  city: 'Abuja',
203
159
  });
160
+ expect(fetchFn).not.toHaveBeenCalledWith(expect.stringContaining('geojs'));
204
161
  });
205
162
 
206
- it('uses the IP city when browser geo succeeds but reverse geocode is empty', async () => {
207
- Object.defineProperty(navigator, 'geolocation', {
208
- configurable: true,
209
- value: {
210
- getCurrentPosition: (success: PositionCallback) => {
211
- success({
212
- coords: {
213
- latitude: 9.0765,
214
- longitude: 7.3986,
215
- accuracy: 10,
216
- altitude: null,
217
- altitudeAccuracy: null,
218
- heading: null,
219
- speed: null,
220
- toJSON: () => ({}),
221
- },
222
- timestamp: Date.now(),
223
- toJSON: () => ({}),
224
- } as GeolocationPosition);
225
- },
226
- },
227
- });
163
+ it('omits the city when the device fix has no place name', async () => {
164
+ stubDeviceAt(9.0765, 7.3986);
228
165
 
229
166
  const fetchFn = jest.fn(async (url: string) => {
230
- if (String(url).includes('bigdatacloud')) {
231
- return jsonResponse({ city: '' });
167
+ if (String(url).includes('geocoding-api')) {
168
+ return jsonResponse({ results: [{ name: '' }] });
232
169
  }
233
170
  if (String(url).includes('geojs')) {
234
171
  return jsonResponse({
@@ -245,13 +182,14 @@ describe('loadHeaderWeather', () => {
245
182
  ).resolves.toEqual({
246
183
  kind: 'clear',
247
184
  temperatureC: 31,
248
- city: 'Lagos',
185
+ city: undefined,
249
186
  });
187
+ expect(fetchFn).not.toHaveBeenCalledWith(expect.stringContaining('geojs'));
250
188
  });
251
189
 
252
190
  it('refetches when the hour cache has weather but no city', async () => {
253
191
  sessionStorage.setItem(
254
- 'africanies-header-weather-v7',
192
+ 'africanies-header-weather-v8',
255
193
  JSON.stringify({
256
194
  hour: `${new Date().getFullYear()}-${new Date().getMonth()}-${new Date().getDate()}-${new Date().getHours()}`,
257
195
  kind: 'cloudy',
@@ -274,7 +212,7 @@ describe('loadHeaderWeather', () => {
274
212
  ).resolves.toEqual({
275
213
  kind: 'cloudy',
276
214
  temperatureC: 25,
277
- city: 'Lagos',
215
+ city: undefined,
278
216
  });
279
217
  expect(fetchFn).toHaveBeenCalled();
280
218
  });
@@ -304,6 +242,36 @@ describe('headerWeatherIcon', () => {
304
242
  });
305
243
  });
306
244
 
245
+ function stubDeviceAt(latitude: number, longitude: number): void {
246
+ Object.defineProperty(navigator, 'permissions', {
247
+ configurable: true,
248
+ value: {
249
+ query: async () => ({ state: 'granted' }),
250
+ },
251
+ });
252
+ Object.defineProperty(navigator, 'geolocation', {
253
+ configurable: true,
254
+ value: {
255
+ getCurrentPosition: (success: PositionCallback) => {
256
+ success({
257
+ coords: {
258
+ latitude,
259
+ longitude,
260
+ accuracy: 10,
261
+ altitude: null,
262
+ altitudeAccuracy: null,
263
+ heading: null,
264
+ speed: null,
265
+ toJSON: () => ({}),
266
+ },
267
+ timestamp: Date.now(),
268
+ toJSON: () => ({}),
269
+ } as GeolocationPosition);
270
+ },
271
+ },
272
+ });
273
+ }
274
+
307
275
  function jsonResponse(body: unknown): Pick<Response, 'ok' | 'json'> {
308
276
  return {
309
277
  ok: true,