Files
edr-platform/apps/edr-freight-web/portal/src/components/PhoneField.tsx

125 lines
3.3 KiB
TypeScript

import { Input, TextInput } from "@mantine/core";
import {
Controller,
type Control,
type FieldValues,
type Path,
} from "react-hook-form";
import RPNInput, { isValidPhoneNumber } from "react-phone-number-input";
import "react-phone-number-input/style.css";
import "./phone-field.css";
/** Re-exported for zod `.refine()` checks on phone fields. */
export const isValidPhone = (value?: string | null): boolean =>
!!value && isValidPhoneNumber(value);
/**
* Normalize a raw (often eTrade) phone string to Ethiopian E.164 (+251…).
* eTrade returns local numbers like "0912345678" / "0355235416"; the phone
* input needs +251… to parse, so we drop a leading 0 and prepend +251. Numbers
* already in +… form, or that can't be coerced, are returned trimmed/as-is.
*/
export const toEthiopianE164 = (raw?: string | null): string => {
if (!raw) return "";
const trimmed = raw.trim();
if (trimmed.startsWith("+")) return trimmed.replace(/[^\d+]/g, "");
// Keep digits only, drop a single leading zero (national trunk prefix).
const digits = trimmed.replace(/\D/g, "").replace(/^0/, "");
if (!digits) return "";
// Already includes the 251 country code.
if (digits.startsWith("251")) return `+${digits}`;
return `+251${digits}`;
};
export interface PhoneFieldProps {
label?: string;
value?: string;
onChange: (value: string | undefined) => void;
onBlur?: () => void;
error?: string;
required?: boolean;
disabled?: boolean;
placeholder?: string;
}
/**
* Professional phone input: searchable country selector (all countries, default
* Ethiopia), live formatting, emits a single E.164 value (e.g. +251912345678).
* Visually aligned with the portal's Mantine form fields.
*/
export function PhoneField({
label,
value,
onChange,
// onBlur,
error,
required,
disabled,
placeholder = "912 345 678",
}: PhoneFieldProps) {
return (
<Input.Wrapper
label={label}
required={required}
error={error}
styles={{
label: { fontWeight: 600, fontSize: 14, color: "#10202F", },
}}
>
<div className={`edr-phone-wrapper ${error ? " edr-phone-wrapper--error" : ""}`}>
<RPNInput
international
defaultCountry="ET"
countryCallingCodeEditable={false}
addInternationalOption
value={value}
onChange={onChange}
inputComponent={TextInput}
disabled={disabled}
placeholder={placeholder}
/>
</div>
</Input.Wrapper>
);
}
interface ControlledPhoneFieldProps<T extends FieldValues> {
control: Control<T>;
name: Path<T>;
label?: string;
required?: boolean;
disabled?: boolean;
placeholder?: string;
}
/** RHF Controller wrapper so forms drop in one line. */
export function ControlledPhoneField<T extends FieldValues>({
control,
name,
label,
required,
disabled,
placeholder,
}: ControlledPhoneFieldProps<T>) {
return (
<Controller
control={control}
name={name}
render={({ field, fieldState }) => (
<PhoneField
label={label}
required={required}
disabled={disabled}
placeholder={placeholder}
value={(field.value as string) ?? ""}
onChange={(v) => field.onChange(v ?? "")}
onBlur={field.onBlur}
error={fieldState.error?.message}
/>
)}
/>
);
}
export default PhoneField;