gohorsejobs/frontend/src/components/phone-input.tsx
NANDO9322 ddc2f5dd03 feat: atualiza fluxo de cadastro de candidatos com persistência completa de dados e máscara de telefone
Frontend:
- Implementar máscara de entrada de telefone para números BR ((XX) XXXXX-XXXX).
- Atualizar formulário de cadastro para enviar dados completos do perfil do candidato (endereço, formação, habilidades, etc.).
- Corrigir problemas de idioma misto na página de Detalhes da Vaga e adicionar traduções faltantes.

Backend:
- Atualizar modelo de Usuário, Entidade e DTOs para incluir campos de perfil (Data de Nascimento, Endereço, Formação, etc.).
- Atualizar UserRepository para persistir e recuperar os dados estendidos do usuário no PostgreSQL.
- Atualizar RegisterCandidateUseCase para mapear campos de entrada para a entidade Usuário.
2026-01-06 18:19:47 -03:00

122 lines
4.5 KiB
TypeScript

"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
import { Input } from "@/components/ui/input"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const countries = [
{ value: "55", label: "Brazil (+55)", flag: "🇧🇷" },
{ value: "1", label: "USA (+1)", flag: "🇺🇸" },
{ value: "351", label: "Portugal (+351)", flag: "🇵🇹" },
{ value: "44", label: "UK (+44)", flag: "🇬🇧" },
{ value: "33", label: "France (+33)", flag: "🇫🇷" },
{ value: "49", label: "Germany (+49)", flag: "🇩🇪" },
{ value: "34", label: "Spain (+34)", flag: "🇪🇸" },
{ value: "39", label: "Italy (+39)", flag: "🇮🇹" },
{ value: "81", label: "Japan (+81)", flag: "🇯🇵" },
{ value: "86", label: "China (+86)", flag: "🇨🇳" },
{ value: "91", label: "India (+91)", flag: "🇮🇳" },
{ value: "54", label: "Argentina (+54)", flag: "🇦🇷" },
{ value: "52", label: "Mexico (+52)", flag: "🇲🇽" },
{ value: "598", label: "Uruguay (+598)", flag: "🇺🇾" },
]
interface PhoneInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> {
value?: string
onChangeValue?: (value: string) => void
}
export function PhoneInput({ className, value, onChangeValue, ...props }: PhoneInputProps) {
const [countryCode, setCountryCode] = React.useState("55")
const [phoneNumber, setPhoneNumber] = React.useState("")
const maskPhone = (value: string, code: string) => {
if (code === "55") {
return value
.replace(/\D/g, "")
.replace(/^(\d{2})(\d)/, "($1) $2")
.replace(/(\d)(\d{4})$/, "$1-$2");
}
return value;
}
// Parse initial value
React.useEffect(() => {
if (value) {
const country = countries.find((c) => value.startsWith("+" + c.value) || value.startsWith(c.value)) // Handle with or without +
if (country) {
setCountryCode(country.value)
// Remove code and +, keep only numbers
const cleanNumber = value.replace(new RegExp(`^\\+?${country.value}`), "")
const masked = maskPhone(cleanNumber, country.value)
setPhoneNumber(masked)
} else {
setPhoneNumber(value)
}
}
}, [value])
const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const rawInput = e.target.value;
const onlyNums = rawInput.replace(/\D/g, "");
// Limit length for Brazil (11 digits max)
if (countryCode === "55" && onlyNums.length > 11) return;
const masked = maskPhone(onlyNums, countryCode);
setPhoneNumber(masked)
if (onChangeValue) {
onChangeValue(`${countryCode}${onlyNums}`)
}
}
const handleCountryChange = (newCode: string) => {
setCountryCode(newCode)
if (onChangeValue) {
onChangeValue(`${newCode}${phoneNumber}`)
}
}
return (
<div className={cn("flex rounded-md shadow-sm", className)}>
<Select value={countryCode} onValueChange={handleCountryChange}>
<SelectTrigger className="w-[100px] border-r-0 rounded-r-none focus:ring-0">
<SelectValue placeholder="Code">
<span className="flex items-center gap-1">
<span>{countries.find((c) => c.value === countryCode)?.flag}</span>
<span>+{countryCode}</span>
</span>
</SelectValue>
</SelectTrigger>
<SelectContent>
{countries.map((country) => (
<SelectItem key={country.value} value={country.value}>
<span className="flex items-center gap-2">
<span>{country.flag}</span>
<span>{country.label}</span>
</span>
</SelectItem>
))}
</SelectContent>
</Select>
<Input
{...props}
className="flex-1 rounded-l-none focus-visible:ring-0 focus-visible:ring-offset-0" // Remove ring collision
placeholder="Phone number"
type="tel"
value={phoneNumber}
onChange={handlePhoneChange}
maxLength={15}
/>
</div>
)
}