photum/frontend/components/Input.tsx
João Vitor 7fc96d77d2 feat: add photographer finance page and UI improvements
- Add photographer finance page at /meus-pagamentos with payment history table
- Remove university management page and related routes
- Update Finance and UserApproval pages with consistent spacing and typography
- Fix Dashboard background color to match other pages (bg-gray-50)
- Standardize navbar logo sizing across all pages
- Change institution field in course form from dropdown to text input
- Add year and semester fields for university graduation dates
- Improve header spacing on all pages to pt-20 sm:pt-24 md:pt-28 lg:pt-32
- Apply font-serif styling consistently across page headers
2025-12-12 16:26:12 -03:00

153 lines
4.4 KiB
TypeScript

import React, { useState } from "react";
import { Eye, EyeOff } from "lucide-react";
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label: string;
error?: string;
mask?: "phone" | "cnpj" | "cep";
}
export const Input: React.FC<InputProps> = ({
label,
error,
className = "",
type,
mask,
onChange,
...props
}) => {
const [showPassword, setShowPassword] = useState(false);
const isPassword = type === "password";
const inputType = isPassword && showPassword ? "text" : type;
const applyMask = (value: string, maskType?: "phone" | "cnpj" | "cep") => {
if (!maskType) return value;
const numbers = value.replace(/\D/g, "");
switch (maskType) {
case "phone":
// Limita a 11 dígitos (celular)
const phoneNumbers = numbers.slice(0, 11);
if (phoneNumbers.length <= 10) {
return phoneNumbers.replace(/(\d{2})(\d{4})(\d{0,4})/, "($1) $2-$3");
}
return phoneNumbers.replace(/(\d{2})(\d{5})(\d{0,4})/, "($1) $2-$3");
case "cnpj":
// Limita a 14 dígitos
const cnpjNumbers = numbers.slice(0, 14);
return cnpjNumbers.replace(
/(\d{2})(\d{3})(\d{3})(\d{4})(\d{0,2})/,
"$1.$2.$3/$4-$5"
);
case "cep":
// Limita a 8 dígitos
const cepNumbers = numbers.slice(0, 8);
return cepNumbers.replace(/(\d{5})(\d{0,3})/, "$1-$2");
default:
return value;
}
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (mask) {
const maskedValue = applyMask(e.target.value, mask);
e.target.value = maskedValue;
}
onChange?.(e);
};
// Define maxLength baseado na máscara
const getMaxLength = () => {
if (!mask) return undefined;
switch (mask) {
case "phone":
return 15; // (00) 00000-0000
case "cnpj":
return 18; // 00.000.000/0000-00
case "cep":
return 9; // 00000-000
default:
return undefined;
}
};
return (
<div className="w-full">
<label className="block text-[10px] sm:text-xs font-medium text-gray-700 mb-1 tracking-wide uppercase">
{label}
</label>
<div className="relative">
<input
className={`w-full px-2.5 sm:px-3 md:px-4 py-1.5 sm:py-2 text-xs sm:text-sm border rounded-sm focus:outline-none focus:ring-1 focus:ring-brand-gold focus:border-brand-gold transition-colors
${error ? "border-red-500" : "border-gray-300"}
${isPassword ? "pr-9 sm:pr-10" : ""}
${className}`}
type={inputType}
onChange={handleChange}
maxLength={getMaxLength()}
{...props}
/>
{isPassword && (
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-2 sm:right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 transition-colors"
>
{showPassword ? (
<EyeOff size={16} className="sm:w-[18px] sm:h-[18px]" />
) : (
<Eye size={16} className="sm:w-[18px] sm:h-[18px]" />
)}
</button>
)}
</div>
{error && (
<span className="text-[10px] sm:text-xs text-red-500 mt-1">
{error}
</span>
)}
</div>
);
};
interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
label: string;
options: { value: string; label: string }[];
error?: string;
}
export const Select: React.FC<SelectProps> = ({
label,
options,
error,
className = "",
...props
}) => {
return (
<div className="w-full">
<label className="block text-sm font-medium text-gray-700 mb-1 tracking-wide uppercase text-xs">
{label}
</label>
<select
className={`w-full px-4 py-2 border rounded-sm focus:outline-none focus:ring-1 focus:ring-brand-gold focus:border-brand-gold transition-colors appearance-none bg-white
${error ? "border-red-500" : "border-gray-300"}
${className}`}
{...props}
>
<option value="" disabled>
Selecione uma opção
</option>
{options.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
{error && <span className="text-xs text-red-500 mt-1">{error}</span>}
</div>
);
};