@ews-admin/global-design-system 1.1.26 → 1.5.0
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/README.md +2 -2
- package/dist/components/PhoneInput/PhoneInput.d.ts +19 -0
- package/dist/components/PhoneInput/PhoneInput.d.ts.map +1 -0
- package/dist/components/PhoneInput/index.d.ts +3 -0
- package/dist/components/PhoneInput/index.d.ts.map +1 -0
- package/dist/index.css +2 -2
- package/dist/index.d.ts +71 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.css +2 -2
- package/dist/index.esm.js +198 -68
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +202 -67
- package/dist/index.js.map +1 -1
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/phone.d.ts +48 -0
- package/dist/utils/phone.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/PhoneInput/PhoneInput.tsx +127 -0
- package/src/components/PhoneInput/index.ts +2 -0
- package/src/index.ts +14 -1
- package/src/utils/index.ts +7 -0
- package/src/utils/phone.ts +109 -0
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type ClassValue } from "clsx";
|
|
2
2
|
export { createEnvConfig } from "./env-config";
|
|
3
|
+
export { COUNTRY_CODES, extractCountryCodeFromPhoneNumber, formatPhoneNumberWithCountryCode, getDefaultCountryCode, } from "./phone";
|
|
4
|
+
export type { CountryCodeOption, CountryCodeSelectProps } from "./phone";
|
|
3
5
|
export type { EnvConfig, EnvConfigOverrides, Environment, } from "./env-config";
|
|
4
6
|
/**
|
|
5
7
|
* Default currency for price formatting
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAE7C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,YAAY,EACV,SAAS,EACT,kBAAkB,EAClB,WAAW,GACZ,MAAM,cAAc,CAAC;AAEtB;;GAEG;AACH,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAE9B;;;;GAIG;AACH,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,SAAW,GAAG,MAAM,CAK1E;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,EAC5B,OAAO,CAAC,EAAE,IAAI,CAAC,qBAAqB,GACnC,MAAM,CAQR;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EAChE,IAAI,EAAE,CAAC,EACP,IAAI,EAAE,MAAM,GACX,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,CAMlC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,SAAQ,GAAG,MAAM,CAEjD;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,KAAG,MAE7C,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAMzD;AAED;;GAEG;AACH,oBAAY,SAAS;IACnB,UAAU,OAAO;IACjB,UAAU,OAAO;IACjB,UAAU,OAAO;IACjB,UAAU,OAAO;IACjB,WAAW,QAAQ;IACnB,WAAW,QAAQ;IACnB,UAAU,OAAO;IACjB,UAAU,OAAO;IACjB,OAAO,YAAY;CACpB;AAED;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,SAAS,EAUlC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAE7C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EACL,aAAa,EACb,iCAAiC,EACjC,gCAAgC,EAChC,qBAAqB,GACtB,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AACzE,YAAY,EACV,SAAS,EACT,kBAAkB,EAClB,WAAW,GACZ,MAAM,cAAc,CAAC;AAEtB;;GAEG;AACH,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAE9B;;;;GAIG;AACH,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,SAAW,GAAG,MAAM,CAK1E;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,EAC5B,OAAO,CAAC,EAAE,IAAI,CAAC,qBAAqB,GACnC,MAAM,CAQR;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EAChE,IAAI,EAAE,CAAC,EACP,IAAI,EAAE,MAAM,GACX,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,CAMlC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,SAAQ,GAAG,MAAM,CAEjD;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,KAAG,MAE7C,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAMzD;AAED;;GAEG;AACH,oBAAY,SAAS;IACnB,UAAU,OAAO;IACjB,UAAU,OAAO;IACjB,UAAU,OAAO;IACjB,UAAU,OAAO;IACjB,WAAW,QAAQ;IACnB,WAAW,QAAQ;IACnB,UAAU,OAAO;IACjB,UAAU,OAAO;IACjB,OAAO,YAAY;CACpB;AAED;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,SAAS,EAUlC,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Country code option for phone number selection
|
|
3
|
+
*/
|
|
4
|
+
export interface CountryCodeOption {
|
|
5
|
+
code: string;
|
|
6
|
+
country: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Props for country code select component
|
|
10
|
+
*/
|
|
11
|
+
export interface CountryCodeSelectProps {
|
|
12
|
+
options: CountryCodeOption[];
|
|
13
|
+
value: string;
|
|
14
|
+
onChange: (code: string) => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Available country codes supported by the platform
|
|
18
|
+
*/
|
|
19
|
+
export declare const COUNTRY_CODES: CountryCodeOption[];
|
|
20
|
+
/**
|
|
21
|
+
* Determines the default country code based on phone number prefix or country ISO code.
|
|
22
|
+
* Falls back to "+221" (Senegal) when no match is found.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getDefaultCountryCode(phoneNumber?: string, country?: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Extracts the country code prefix from a phone number and returns both
|
|
27
|
+
* the detected country code and the local number without the prefix.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* extractCountryCodeFromPhoneNumber("+22177123456") // { countryCode: "+221", cleanedPhoneNumber: "77123456" }
|
|
31
|
+
* extractCountryCodeFromPhoneNumber("77123456") // { countryCode: "+221", cleanedPhoneNumber: "77123456" }
|
|
32
|
+
*/
|
|
33
|
+
export declare function extractCountryCodeFromPhoneNumber(phoneNumber: string, country?: string): {
|
|
34
|
+
countryCode: string;
|
|
35
|
+
cleanedPhoneNumber: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Formats a phone number by prepending the country code if not already present.
|
|
39
|
+
* If the number already starts with a known country code it is returned as-is.
|
|
40
|
+
* Leading zeros, spaces and dashes are stripped from the local part before formatting.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* formatPhoneNumberWithCountryCode("77123456", "+221") // "+22177123456"
|
|
44
|
+
* formatPhoneNumberWithCountryCode("+22177123456", "+221") // "+22177123456" (unchanged)
|
|
45
|
+
* formatPhoneNumberWithCountryCode("077123456", "+221") // "+22177123456" (leading 0 stripped)
|
|
46
|
+
*/
|
|
47
|
+
export declare function formatPhoneNumberWithCountryCode(phoneNumber: string, countryCode?: string): string;
|
|
48
|
+
//# sourceMappingURL=phone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phone.d.ts","sourceRoot":"","sources":["../../src/utils/phone.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,iBAAiB,EAG5C,CAAC;AAEF;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,CAgBR;AAED;;;;;;;GAOG;AACH,wBAAgB,iCAAiC,CAC/C,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,GACf;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,CAgBrD;AAED;;;;;;;;;GASG;AACH,wBAAgB,gCAAgC,CAC9C,WAAW,EAAE,MAAM,EACnB,WAAW,GAAE,MAAe,GAC3B,MAAM,CAgBR"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React, { MutableRefObject, useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
COUNTRY_CODES,
|
|
4
|
+
extractCountryCodeFromPhoneNumber,
|
|
5
|
+
} from "../../utils/phone";
|
|
6
|
+
import { formatNumeric } from "../../utils";
|
|
7
|
+
import { Input } from "../Input/Input";
|
|
8
|
+
import type { InputProps } from "../Input/Input";
|
|
9
|
+
|
|
10
|
+
const DEFAULT_COUNTRY_CODE = "+221";
|
|
11
|
+
|
|
12
|
+
export interface PhoneInputProps
|
|
13
|
+
extends Omit<InputProps, "countryCodeSelect" | "leftIcon" | "type"> {
|
|
14
|
+
/** Currently selected country code (controlled). Defaults to +221 if omitted. */
|
|
15
|
+
countryCode?: string;
|
|
16
|
+
/** Called when the user changes the country code dropdown. */
|
|
17
|
+
onCountryCodeChange?: (code: string) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Phone number input with integrated country code selector.
|
|
22
|
+
*
|
|
23
|
+
* The component stores the **full** phone number (e.g. "+22177123456") as its
|
|
24
|
+
* form value so that the country code prefix is always preserved on save.
|
|
25
|
+
* The text field displays only the local part ("77123456") for a clean UX.
|
|
26
|
+
*
|
|
27
|
+
* Works with React Hook Form via `Controller` — no extra setup needed.
|
|
28
|
+
*/
|
|
29
|
+
export const PhoneInput = React.forwardRef<HTMLInputElement, PhoneInputProps>(
|
|
30
|
+
({ countryCode, onCountryCodeChange, onChange, value, ...props }, ref) => {
|
|
31
|
+
// internalCode tracks user's explicit dropdown selection when no countryCode
|
|
32
|
+
// prop is provided and value doesn't carry a recognised prefix.
|
|
33
|
+
const [internalCode, setInternalCode] = useState(DEFAULT_COUNTRY_CODE);
|
|
34
|
+
const nativeRef = useRef<HTMLInputElement>(null);
|
|
35
|
+
|
|
36
|
+
// Derive the country code directly from value on every render — no state
|
|
37
|
+
// sync needed. Falls back to internalCode when value is empty or unrecognised.
|
|
38
|
+
const codeFromValue = (value as string)
|
|
39
|
+
? COUNTRY_CODES.find(({ code }) =>
|
|
40
|
+
(value as string).startsWith(code)
|
|
41
|
+
)?.code ?? null
|
|
42
|
+
: null;
|
|
43
|
+
|
|
44
|
+
const activeCode = countryCode ?? codeFromValue ?? internalCode;
|
|
45
|
+
|
|
46
|
+
const handleCodeChange = (newCode: string) => {
|
|
47
|
+
// Update internal or external country code state.
|
|
48
|
+
if (onCountryCodeChange) {
|
|
49
|
+
onCountryCodeChange(newCode);
|
|
50
|
+
} else {
|
|
51
|
+
setInternalCode(newCode);
|
|
52
|
+
}
|
|
53
|
+
// Re-emit the current phone value with the new country code so that
|
|
54
|
+
// RHF (Controller) and any other onChange listeners stay in sync even
|
|
55
|
+
// when the user changes the dropdown without retyping the number.
|
|
56
|
+
const el = nativeRef.current;
|
|
57
|
+
if (el && onChange) {
|
|
58
|
+
const numeric = formatNumeric(el.value);
|
|
59
|
+
const fullValue = numeric ? `${newCode}${numeric}` : "";
|
|
60
|
+
// Pass the value string directly — RHF's Controller field.onChange
|
|
61
|
+
// accepts raw values, avoiding unreliable fake-event parsing.
|
|
62
|
+
(onChange as unknown as (value: string) => void)(fullValue);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const toLocalPart = (v: unknown) =>
|
|
67
|
+
extractCountryCodeFromPhoneNumber((v as string) || "").cleanedPhoneNumber;
|
|
68
|
+
|
|
69
|
+
// When value prop changes (Controller / direct value prop usage),
|
|
70
|
+
// update the DOM input directly so the display stays in sync.
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
const el = nativeRef.current;
|
|
73
|
+
if (el && value !== undefined) {
|
|
74
|
+
el.value = toLocalPart(value);
|
|
75
|
+
}
|
|
76
|
+
}, [value]);
|
|
77
|
+
|
|
78
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
79
|
+
// Enforce digits-only in the displayed field.
|
|
80
|
+
const numeric = formatNumeric(e.target.value);
|
|
81
|
+
if (e.target.value !== numeric) {
|
|
82
|
+
e.target.value = numeric;
|
|
83
|
+
}
|
|
84
|
+
// Always emit the full phone number (country code + local part) to the form.
|
|
85
|
+
const fullValue = numeric ? `${activeCode}${numeric}` : "";
|
|
86
|
+
const syntheticEvent = {
|
|
87
|
+
...e,
|
|
88
|
+
target: { ...e.target, value: fullValue, name: e.target.name },
|
|
89
|
+
} as React.ChangeEvent<HTMLInputElement>;
|
|
90
|
+
onChange?.(syntheticEvent);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Merge the forwarded ref (used by RHF register) with our internal ref.
|
|
94
|
+
const composedRef = useCallback(
|
|
95
|
+
(node: HTMLInputElement | null) => {
|
|
96
|
+
(nativeRef as MutableRefObject<HTMLInputElement | null>).current = node;
|
|
97
|
+
if (typeof ref === "function") ref(node);
|
|
98
|
+
else if (ref)
|
|
99
|
+
(ref as MutableRefObject<HTMLInputElement | null>).current = node;
|
|
100
|
+
},
|
|
101
|
+
[ref]
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Initial display: strip country code so only the local part is shown.
|
|
105
|
+
const initialDisplay =
|
|
106
|
+
value !== undefined ? toLocalPart(value) : undefined;
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<Input
|
|
110
|
+
ref={composedRef}
|
|
111
|
+
type="tel"
|
|
112
|
+
// Use defaultValue (uncontrolled) so React never resets what the user types.
|
|
113
|
+
// Updates from the value prop are applied imperatively via the useEffect above.
|
|
114
|
+
defaultValue={initialDisplay}
|
|
115
|
+
countryCodeSelect={{
|
|
116
|
+
options: COUNTRY_CODES,
|
|
117
|
+
value: activeCode,
|
|
118
|
+
onChange: handleCodeChange,
|
|
119
|
+
}}
|
|
120
|
+
onChange={handleChange}
|
|
121
|
+
{...props}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
PhoneInput.displayName = "PhoneInput";
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,9 @@ export type { ProfileImageUploadProps } from "./components/ProfileImageUpload";
|
|
|
22
22
|
export { DropdownMultiSelect } from "./components/DropdownMultiSelect";
|
|
23
23
|
export type { DropdownMultiSelectProps } from "./components/DropdownMultiSelect";
|
|
24
24
|
|
|
25
|
+
export { PhoneInput } from "./components/PhoneInput";
|
|
26
|
+
export type { PhoneInputProps } from "./components/PhoneInput";
|
|
27
|
+
|
|
25
28
|
export { Logo } from "./components/Logo";
|
|
26
29
|
export type { LogoProps } from "./components/Logo";
|
|
27
30
|
|
|
@@ -44,15 +47,25 @@ export {
|
|
|
44
47
|
BLOOD_TYPES,
|
|
45
48
|
BloodType,
|
|
46
49
|
cn,
|
|
50
|
+
COUNTRY_CODES,
|
|
47
51
|
createEnvConfig,
|
|
48
52
|
debounce,
|
|
53
|
+
extractCountryCodeFromPhoneNumber,
|
|
49
54
|
formatCurrency,
|
|
50
55
|
formatDate,
|
|
51
56
|
formatNumeric,
|
|
57
|
+
formatPhoneNumberWithCountryCode,
|
|
52
58
|
generateId,
|
|
59
|
+
getDefaultCountryCode,
|
|
53
60
|
isValidPhoneNumber,
|
|
54
61
|
} from "./utils";
|
|
55
|
-
export type {
|
|
62
|
+
export type {
|
|
63
|
+
CountryCodeOption,
|
|
64
|
+
CountryCodeSelectProps,
|
|
65
|
+
EnvConfig,
|
|
66
|
+
EnvConfigOverrides,
|
|
67
|
+
Environment,
|
|
68
|
+
} from "./utils";
|
|
56
69
|
|
|
57
70
|
// Hooks
|
|
58
71
|
export { useDebounce, useDebouncedCallback, useSelectField } from "./hooks";
|
package/src/utils/index.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { clsx, type ClassValue } from "clsx";
|
|
2
2
|
|
|
3
3
|
export { createEnvConfig } from "./env-config";
|
|
4
|
+
export {
|
|
5
|
+
COUNTRY_CODES,
|
|
6
|
+
extractCountryCodeFromPhoneNumber,
|
|
7
|
+
formatPhoneNumberWithCountryCode,
|
|
8
|
+
getDefaultCountryCode,
|
|
9
|
+
} from "./phone";
|
|
10
|
+
export type { CountryCodeOption, CountryCodeSelectProps } from "./phone";
|
|
4
11
|
export type {
|
|
5
12
|
EnvConfig,
|
|
6
13
|
EnvConfigOverrides,
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Country code option for phone number selection
|
|
3
|
+
*/
|
|
4
|
+
export interface CountryCodeOption {
|
|
5
|
+
code: string;
|
|
6
|
+
country: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Props for country code select component
|
|
11
|
+
*/
|
|
12
|
+
export interface CountryCodeSelectProps {
|
|
13
|
+
options: CountryCodeOption[];
|
|
14
|
+
value: string;
|
|
15
|
+
onChange: (code: string) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Available country codes supported by the platform
|
|
20
|
+
*/
|
|
21
|
+
export const COUNTRY_CODES: CountryCodeOption[] = [
|
|
22
|
+
{ code: "+221", country: "SN" }, // Senegal
|
|
23
|
+
{ code: "+235", country: "TD" }, // Chad
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Determines the default country code based on phone number prefix or country ISO code.
|
|
28
|
+
* Falls back to "+221" (Senegal) when no match is found.
|
|
29
|
+
*/
|
|
30
|
+
export function getDefaultCountryCode(
|
|
31
|
+
phoneNumber?: string,
|
|
32
|
+
country?: string
|
|
33
|
+
): string {
|
|
34
|
+
if (phoneNumber) {
|
|
35
|
+
const match = COUNTRY_CODES.find(({ code }) =>
|
|
36
|
+
phoneNumber.startsWith(code)
|
|
37
|
+
);
|
|
38
|
+
if (match) return match.code;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (country) {
|
|
42
|
+
const match = COUNTRY_CODES.find(
|
|
43
|
+
({ country: c }) => c.toUpperCase() === country.toUpperCase()
|
|
44
|
+
);
|
|
45
|
+
if (match) return match.code;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return "+221";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Extracts the country code prefix from a phone number and returns both
|
|
53
|
+
* the detected country code and the local number without the prefix.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* extractCountryCodeFromPhoneNumber("+22177123456") // { countryCode: "+221", cleanedPhoneNumber: "77123456" }
|
|
57
|
+
* extractCountryCodeFromPhoneNumber("77123456") // { countryCode: "+221", cleanedPhoneNumber: "77123456" }
|
|
58
|
+
*/
|
|
59
|
+
export function extractCountryCodeFromPhoneNumber(
|
|
60
|
+
phoneNumber: string,
|
|
61
|
+
country?: string
|
|
62
|
+
): { countryCode: string; cleanedPhoneNumber: string } {
|
|
63
|
+
const matched = COUNTRY_CODES.find(({ code }) =>
|
|
64
|
+
phoneNumber.startsWith(code)
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
if (matched) {
|
|
68
|
+
const cleaned = phoneNumber
|
|
69
|
+
.replace(new RegExp(`^\\${matched.code}\\s*`), "")
|
|
70
|
+
.trim();
|
|
71
|
+
return { countryCode: matched.code, cleanedPhoneNumber: cleaned };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
countryCode: getDefaultCountryCode(phoneNumber, country),
|
|
76
|
+
cleanedPhoneNumber: phoneNumber.trim(),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Formats a phone number by prepending the country code if not already present.
|
|
82
|
+
* If the number already starts with a known country code it is returned as-is.
|
|
83
|
+
* Leading zeros, spaces and dashes are stripped from the local part before formatting.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* formatPhoneNumberWithCountryCode("77123456", "+221") // "+22177123456"
|
|
87
|
+
* formatPhoneNumberWithCountryCode("+22177123456", "+221") // "+22177123456" (unchanged)
|
|
88
|
+
* formatPhoneNumberWithCountryCode("077123456", "+221") // "+22177123456" (leading 0 stripped)
|
|
89
|
+
*/
|
|
90
|
+
export function formatPhoneNumberWithCountryCode(
|
|
91
|
+
phoneNumber: string,
|
|
92
|
+
countryCode: string = "+221"
|
|
93
|
+
): string {
|
|
94
|
+
const trimmed = phoneNumber?.trim() || "";
|
|
95
|
+
|
|
96
|
+
if (!trimmed) return "";
|
|
97
|
+
|
|
98
|
+
// Already has a known country code — return as-is
|
|
99
|
+
if (COUNTRY_CODES.some(({ code }) => trimmed.startsWith(code))) {
|
|
100
|
+
return trimmed;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Strip leading zeros, spaces, dashes and + signs from the local part
|
|
104
|
+
const cleaned = trimmed.replace(/^[\s+0-]*/, "").trim();
|
|
105
|
+
|
|
106
|
+
if (!cleaned) return "";
|
|
107
|
+
|
|
108
|
+
return `${countryCode}${cleaned}`;
|
|
109
|
+
}
|