fix mdp see

This commit is contained in:
David 2026-06-13 12:01:19 +02:00
parent c6eaaf354a
commit 5aed7d81ce
9 changed files with 280 additions and 108 deletions

View File

@ -4,17 +4,19 @@ import { useState } from 'react';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { Link, useRouter } from '@/i18n/navigation'; import { Link, useRouter } from '@/i18n/navigation';
import Image from 'next/image'; import Image from 'next/image';
import { ShieldCheck } from 'lucide-react'; import { ShieldCheck, Eye, EyeOff } from 'lucide-react';
import { login as apiLogin, getCurrentUser, logout as apiLogout } from '@/lib/api/auth'; import { login as apiLogin, getCurrentUser, logout as apiLogout } from '@/lib/api/auth';
import { useAuth } from '@/lib/context/auth-context'; import { useAuth } from '@/lib/context/auth-context';
export default function AdminLoginPage() { export default function AdminLoginPage() {
const t = useTranslations('admin.login'); const t = useTranslations('admin.login');
const tCommon = useTranslations('common');
const router = useRouter(); const router = useRouter();
const { refreshUser } = useAuth(); const { refreshUser } = useAuth();
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
@ -94,17 +96,28 @@ export default function AdminLoginPage() {
<label htmlFor="password" className="label"> <label htmlFor="password" className="label">
{t('passwordLabel')} {t('passwordLabel')}
</label> </label>
<input <div className="relative">
id="password" <input
type="password" id="password"
value={password} type={showPassword ? 'text' : 'password'}
onChange={e => setPassword(e.target.value)} value={password}
className="input w-full" onChange={e => setPassword(e.target.value)}
placeholder={t('passwordPlaceholder')} className="input w-full pr-12"
autoComplete="current-password" placeholder={t('passwordPlaceholder')}
disabled={isLoading} autoComplete="current-password"
required disabled={isLoading}
/> required
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-neutral-500 hover:text-neutral-700"
tabIndex={-1}
aria-label={showPassword ? tCommon('hidePassword') : tCommon('showPassword')}
>
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
</div>
</div> </div>
<button <button

View File

@ -2,6 +2,7 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { Eye, EyeOff } from 'lucide-react';
import { getAllUsers, updateAdminUser, deleteAdminUser } from '@/lib/api/admin'; import { getAllUsers, updateAdminUser, deleteAdminUser } from '@/lib/api/admin';
import { createUser } from '@/lib/api/users'; import { createUser } from '@/lib/api/users';
import { getAllOrganizations } from '@/lib/api/admin'; import { getAllOrganizations } from '@/lib/api/admin';
@ -27,8 +28,10 @@ interface Organization {
export default function AdminUsersPage() { export default function AdminUsersPage() {
const t = useTranslations('dashboard.admin.users'); const t = useTranslations('dashboard.admin.users');
const tCommon = useTranslations('common');
const [users, setUsers] = useState<User[]>([]); const [users, setUsers] = useState<User[]>([]);
const [showPassword, setShowPassword] = useState(false);
const [organizations, setOrganizations] = useState<Organization[]>([]); const [organizations, setOrganizations] = useState<Organization[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@ -349,12 +352,23 @@ export default function AdminUsersPage() {
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">
{t('modal.password')} {t('modal.password')}
</label> </label>
<input <div className="relative mt-1">
type="password" <input
value={formData.password} type={showPassword ? 'text' : 'password'}
onChange={e => setFormData({ ...formData, password: e.target.value })} value={formData.password}
className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:border-blue-500 focus:ring-blue-500 focus:outline-none" onChange={e => setFormData({ ...formData, password: e.target.value })}
/> className="block w-full px-3 py-2 pr-10 border border-gray-300 rounded-md shadow-sm focus:border-blue-500 focus:ring-blue-500 focus:outline-none"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
tabIndex={-1}
aria-label={showPassword ? tCommon('hidePassword') : tCommon('showPassword')}
>
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
</div> </div>
<div className="flex justify-end space-x-2 pt-4"> <div className="flex justify-end space-x-2 pt-4">
<button <button

View File

@ -7,15 +7,20 @@ import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod'; import { z } from 'zod';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { Eye, EyeOff } from 'lucide-react';
import { updateUser, changePassword } from '@/lib/api'; import { updateUser, changePassword } from '@/lib/api';
export default function ProfilePage() { export default function ProfilePage() {
const t = useTranslations('dashboard.profile'); const t = useTranslations('dashboard.profile');
const tCommon = useTranslations('common');
const { user, refreshUser, loading } = useAuth(); const { user, refreshUser, loading } = useAuth();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [activeTab, setActiveTab] = useState<'profile' | 'password'>('profile'); const [activeTab, setActiveTab] = useState<'profile' | 'password'>('profile');
const [successMessage, setSuccessMessage] = useState(''); const [successMessage, setSuccessMessage] = useState('');
const [errorMessage, setErrorMessage] = useState(''); const [errorMessage, setErrorMessage] = useState('');
const [showCurrentPassword, setShowCurrentPassword] = useState(false);
const [showNewPassword, setShowNewPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const passwordSchema = z const passwordSchema = z
.object({ .object({
@ -326,13 +331,30 @@ export default function ProfilePage() {
> >
{t('passwordForm.current')} {t('passwordForm.current')}
</label> </label>
<input <div className="relative">
{...passwordForm.register('currentPassword')} <input
type="password" {...passwordForm.register('currentPassword')}
id="currentPassword" type={showCurrentPassword ? 'text' : 'password'}
autoComplete="current-password" id="currentPassword"
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" autoComplete="current-password"
/> className="w-full px-4 py-2 pr-12 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<button
type="button"
onClick={() => setShowCurrentPassword(!showCurrentPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
tabIndex={-1}
aria-label={
showCurrentPassword ? tCommon('hidePassword') : tCommon('showPassword')
}
>
{showCurrentPassword ? (
<EyeOff className="w-5 h-5" />
) : (
<Eye className="w-5 h-5" />
)}
</button>
</div>
{passwordForm.formState.errors.currentPassword && ( {passwordForm.formState.errors.currentPassword && (
<p className="mt-1 text-sm text-red-600"> <p className="mt-1 text-sm text-red-600">
{passwordForm.formState.errors.currentPassword.message} {passwordForm.formState.errors.currentPassword.message}
@ -347,13 +369,24 @@ export default function ProfilePage() {
> >
{t('passwordForm.new')} {t('passwordForm.new')}
</label> </label>
<input <div className="relative">
{...passwordForm.register('newPassword')} <input
type="password" {...passwordForm.register('newPassword')}
id="newPassword" type={showNewPassword ? 'text' : 'password'}
autoComplete="new-password" id="newPassword"
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" autoComplete="new-password"
/> className="w-full px-4 py-2 pr-12 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<button
type="button"
onClick={() => setShowNewPassword(!showNewPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
tabIndex={-1}
aria-label={showNewPassword ? tCommon('hidePassword') : tCommon('showPassword')}
>
{showNewPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
</div>
{passwordForm.formState.errors.newPassword && ( {passwordForm.formState.errors.newPassword && (
<p className="mt-1 text-sm text-red-600"> <p className="mt-1 text-sm text-red-600">
{passwordForm.formState.errors.newPassword.message} {passwordForm.formState.errors.newPassword.message}
@ -369,13 +402,30 @@ export default function ProfilePage() {
> >
{t('passwordForm.confirm')} {t('passwordForm.confirm')}
</label> </label>
<input <div className="relative">
{...passwordForm.register('confirmPassword')} <input
type="password" {...passwordForm.register('confirmPassword')}
id="confirmPassword" type={showConfirmPassword ? 'text' : 'password'}
autoComplete="new-password" id="confirmPassword"
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" autoComplete="new-password"
/> className="w-full px-4 py-2 pr-12 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<button
type="button"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
tabIndex={-1}
aria-label={
showConfirmPassword ? tCommon('hidePassword') : tCommon('showPassword')
}
>
{showConfirmPassword ? (
<EyeOff className="w-5 h-5" />
) : (
<Eye className="w-5 h-5" />
)}
</button>
</div>
{passwordForm.formState.errors.confirmPassword && ( {passwordForm.formState.errors.confirmPassword && (
<p className="mt-1 text-sm text-red-600"> <p className="mt-1 text-sm text-red-600">
{passwordForm.formState.errors.confirmPassword.message} {passwordForm.formState.errors.confirmPassword.message}

View File

@ -5,6 +5,7 @@ import { Link } from '@/i18n/navigation';
import Image from 'next/image'; import Image from 'next/image';
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { Eye, EyeOff } from 'lucide-react';
import { useAuth } from '@/lib/context/auth-context'; import { useAuth } from '@/lib/context/auth-context';
function AnimatedCounter({ function AnimatedCounter({
@ -97,9 +98,11 @@ function LoginPageContent() {
const tLogin = useTranslations('auth.login'); const tLogin = useTranslations('auth.login');
const tPanel = useTranslations('auth.sidePanel'); const tPanel = useTranslations('auth.sidePanel');
const tFooter = useTranslations('auth.footerLinks'); const tFooter = useTranslations('auth.footerLinks');
const tCommon = useTranslations('common');
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [rememberMe, setRememberMe] = useState(false); const [rememberMe, setRememberMe] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
const [fieldErrors, setFieldErrors] = useState<FieldErrors>({}); const [fieldErrors, setFieldErrors] = useState<FieldErrors>({});
@ -262,22 +265,33 @@ function LoginPageContent() {
> >
{tLogin('passwordLabel')} {tLogin('passwordLabel')}
</label> </label>
<input <div className="relative">
id="password" <input
type="password" id="password"
value={password} type={showPassword ? 'text' : 'password'}
onChange={handlePasswordChange} value={password}
className={`input w-full ${ onChange={handlePasswordChange}
fieldErrors.password className={`input w-full pr-12 ${
? 'border-red-500 focus:border-red-500 focus:ring-red-500 bg-red-50' fieldErrors.password
: '' ? 'border-red-500 focus:border-red-500 focus:ring-red-500 bg-red-50'
}`} : ''
placeholder={tLogin('passwordPlaceholder')} }`}
autoComplete="current-password" placeholder={tLogin('passwordPlaceholder')}
disabled={isLoading} autoComplete="current-password"
aria-invalid={!!fieldErrors.password} disabled={isLoading}
aria-describedby={fieldErrors.password ? 'password-error' : undefined} aria-invalid={!!fieldErrors.password}
/> aria-describedby={fieldErrors.password ? 'password-error' : undefined}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-neutral-500 hover:text-neutral-700"
tabIndex={-1}
aria-label={showPassword ? tCommon('hidePassword') : tCommon('showPassword')}
>
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
</div>
{fieldErrors.password && ( {fieldErrors.password && (
<p <p
id="password-error" id="password-error"

View File

@ -5,6 +5,7 @@ import { useSearchParams } from 'next/navigation';
import { Link, useRouter } from '@/i18n/navigation'; import { Link, useRouter } from '@/i18n/navigation';
import Image from 'next/image'; import Image from 'next/image';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { Eye, EyeOff } from 'lucide-react';
import { register } from '@/lib/api'; import { register } from '@/lib/api';
import { verifyInvitation, type InvitationResponse } from '@/lib/api/invitations'; import { verifyInvitation, type InvitationResponse } from '@/lib/api/invitations';
import type { OrganizationType } from '@/types/api'; import type { OrganizationType } from '@/types/api';
@ -57,6 +58,7 @@ function RegisterPageContent() {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const t = useTranslations('auth.register'); const t = useTranslations('auth.register');
const tFooter = useTranslations('auth.footerLinks'); const tFooter = useTranslations('auth.footerLinks');
const tCommon = useTranslations('common');
const [step, setStep] = useState<1 | 2>(1); const [step, setStep] = useState<1 | 2>(1);
const [statsActive, setStatsActive] = useState(false); const [statsActive, setStatsActive] = useState(false);
@ -81,6 +83,8 @@ function RegisterPageContent() {
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [organizationName, setOrganizationName] = useState(''); const [organizationName, setOrganizationName] = useState('');
const [organizationType, setOrganizationType] = useState<OrganizationType>('FREIGHT_FORWARDER'); const [organizationType, setOrganizationType] = useState<OrganizationType>('FREIGHT_FORWARDER');
@ -513,17 +517,28 @@ function RegisterPageContent() {
<label htmlFor="password" className="label"> <label htmlFor="password" className="label">
{t('passwordLabel')} {t('passwordLabel')}
</label> </label>
<input <div className="relative">
id="password" <input
type="password" id="password"
required type={showPassword ? 'text' : 'password'}
value={password} required
onChange={e => setPassword(e.target.value)} value={password}
className="input w-full" onChange={e => setPassword(e.target.value)}
placeholder={t('passwordPlaceholder')} className="input w-full pr-12"
autoComplete="new-password" placeholder={t('passwordPlaceholder')}
disabled={isLoading} autoComplete="new-password"
/> disabled={isLoading}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-neutral-500 hover:text-neutral-700"
tabIndex={-1}
aria-label={showPassword ? tCommon('hidePassword') : tCommon('showPassword')}
>
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
</div>
<p className="mt-1.5 text-body-xs text-neutral-500">{t('passwordHint')}</p> <p className="mt-1.5 text-body-xs text-neutral-500">{t('passwordHint')}</p>
</div> </div>
@ -531,17 +546,34 @@ function RegisterPageContent() {
<label htmlFor="confirmPassword" className="label"> <label htmlFor="confirmPassword" className="label">
{t('confirmPasswordLabel')} {t('confirmPasswordLabel')}
</label> </label>
<input <div className="relative">
id="confirmPassword" <input
type="password" id="confirmPassword"
required type={showConfirmPassword ? 'text' : 'password'}
value={confirmPassword} required
onChange={e => setConfirmPassword(e.target.value)} value={confirmPassword}
className="input w-full" onChange={e => setConfirmPassword(e.target.value)}
placeholder={t('passwordPlaceholder')} className="input w-full pr-12"
autoComplete="new-password" placeholder={t('passwordPlaceholder')}
disabled={isLoading} autoComplete="new-password"
/> disabled={isLoading}
/>
<button
type="button"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-neutral-500 hover:text-neutral-700"
tabIndex={-1}
aria-label={
showConfirmPassword ? tCommon('hidePassword') : tCommon('showPassword')
}
>
{showConfirmPassword ? (
<EyeOff className="w-5 h-5" />
) : (
<Eye className="w-5 h-5" />
)}
</button>
</div>
</div> </div>
<button <button

View File

@ -5,16 +5,20 @@ import { useSearchParams } from 'next/navigation';
import { Link, useRouter } from '@/i18n/navigation'; import { Link, useRouter } from '@/i18n/navigation';
import Image from 'next/image'; import Image from 'next/image';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { Eye, EyeOff } from 'lucide-react';
import { resetPassword } from '@/lib/api/auth'; import { resetPassword } from '@/lib/api/auth';
function ResetPasswordContent() { function ResetPasswordContent() {
const t = useTranslations('auth.resetPassword'); const t = useTranslations('auth.resetPassword');
const tFooter = useTranslations('auth.footerLinks'); const tFooter = useTranslations('auth.footerLinks');
const tCommon = useTranslations('common');
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const router = useRouter(); const router = useRouter();
const [token, setToken] = useState(''); const [token, setToken] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [success, setSuccess] = useState(false); const [success, setSuccess] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -159,17 +163,28 @@ function ResetPasswordContent() {
<label htmlFor="password" className="label"> <label htmlFor="password" className="label">
{t('passwordLabel')} {t('passwordLabel')}
</label> </label>
<input <div className="relative">
id="password" <input
type="password" id="password"
required type={showPassword ? 'text' : 'password'}
value={password} required
onChange={e => setPassword(e.target.value)} value={password}
className="input w-full" onChange={e => setPassword(e.target.value)}
placeholder="••••••••••••" className="input w-full pr-12"
autoComplete="new-password" placeholder="••••••••••••"
disabled={loading} autoComplete="new-password"
/> disabled={loading}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-neutral-500 hover:text-neutral-700"
tabIndex={-1}
aria-label={showPassword ? tCommon('hidePassword') : tCommon('showPassword')}
>
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
</div>
<p className="mt-1.5 text-body-xs text-neutral-500">{t('passwordHint')}</p> <p className="mt-1.5 text-body-xs text-neutral-500">{t('passwordHint')}</p>
</div> </div>
@ -177,17 +192,34 @@ function ResetPasswordContent() {
<label htmlFor="confirmPassword" className="label"> <label htmlFor="confirmPassword" className="label">
{t('confirmPasswordLabel')} {t('confirmPasswordLabel')}
</label> </label>
<input <div className="relative">
id="confirmPassword" <input
type="password" id="confirmPassword"
required type={showConfirmPassword ? 'text' : 'password'}
value={confirmPassword} required
onChange={e => setConfirmPassword(e.target.value)} value={confirmPassword}
className="input w-full" onChange={e => setConfirmPassword(e.target.value)}
placeholder="••••••••••••" className="input w-full pr-12"
autoComplete="new-password" placeholder="••••••••••••"
disabled={loading} autoComplete="new-password"
/> disabled={loading}
/>
<button
type="button"
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-neutral-500 hover:text-neutral-700"
tabIndex={-1}
aria-label={
showConfirmPassword ? tCommon('hidePassword') : tCommon('showPassword')
}
>
{showConfirmPassword ? (
<EyeOff className="w-5 h-5" />
) : (
<Eye className="w-5 h-5" />
)}
</button>
</div>
</div> </div>
<button <button

View File

@ -25,7 +25,9 @@
"discover": "Discover", "discover": "Discover",
"tryNow": "Try now", "tryNow": "Try now",
"continue": "Continue", "continue": "Continue",
"submit": "Submit" "submit": "Submit",
"showPassword": "Show password",
"hidePassword": "Hide password"
}, },
"admin": { "admin": {
"title": "Administration", "title": "Administration",

View File

@ -25,7 +25,9 @@
"discover": "Découvrir", "discover": "Découvrir",
"tryNow": "Essayer maintenant", "tryNow": "Essayer maintenant",
"continue": "Continuer", "continue": "Continuer",
"submit": "Envoyer" "submit": "Envoyer",
"showPassword": "Afficher le mot de passe",
"hidePassword": "Masquer le mot de passe"
}, },
"admin": { "admin": {
"title": "Espace administration", "title": "Espace administration",

View File

@ -3,6 +3,7 @@
*/ */
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Eye, EyeOff } from 'lucide-react';
import { Carrier, CarrierStatus, CreateCarrierInput, UpdateCarrierInput } from '@/types/carrier'; import { Carrier, CarrierStatus, CreateCarrierInput, UpdateCarrierInput } from '@/types/carrier';
interface CarrierFormProps { interface CarrierFormProps {
@ -25,6 +26,7 @@ export const CarrierForm: React.FC<CarrierFormProps> = ({ carrier, onSubmit, onC
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [showApiKey, setShowApiKey] = useState(false);
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
@ -129,13 +131,24 @@ export const CarrierForm: React.FC<CarrierFormProps> = ({ carrier, onSubmit, onC
{/* API Key */} {/* API Key */}
<div className="md:col-span-2"> <div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-1">API Key</label> <label className="block text-sm font-medium text-gray-700 mb-1">API Key</label>
<input <div className="relative">
type="password" <input
value={formData.apiKey} type={showApiKey ? 'text' : 'password'}
onChange={e => setFormData({ ...formData, apiKey: e.target.value })} value={formData.apiKey}
placeholder="Enter API key" onChange={e => setFormData({ ...formData, apiKey: e.target.value })}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Enter API key"
/> className="w-full px-3 py-2 pr-10 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
type="button"
onClick={() => setShowApiKey(!showApiKey)}
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
tabIndex={-1}
aria-label={showApiKey ? 'Hide API key' : 'Show API key'}
>
{showApiKey ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
</div> </div>
{/* Rate Limit */} {/* Rate Limit */}