Files
MyClub/frontend/src/pages/ForgotPasswordPage.tsx
T
Tomáš Dvořák 12cba639b9 upload
2025-10-16 13:32:05 +02:00

274 lines
8.1 KiB
TypeScript

import { useState } from 'react';
import { Box, Button, FormControl, FormLabel, Input, VStack, Heading, useToast, Text, HStack, PinInput, PinInputField } from '@chakra-ui/react';
import api from '../services/api';
type ResetStep = 'email' | 'code' | 'new_password' | 'success';
const ForgotPasswordPage: React.FC = () => {
const [email, setEmail] = useState('');
const [code, setCode] = useState('');
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [step, setStep] = useState<ResetStep>('email');
const toast = useToast();
const handleInitiateReset = async (e: React.FormEvent) => {
e.preventDefault();
if (!email) return;
setIsLoading(true);
try {
await api.post('/auth/initiate-password-reset', { email });
setStep('code');
toast({
title: 'Kód odeslán',
description: 'Ověřovací kód byl odeslán na váš e-mail.',
status: 'success',
duration: 5000,
isClosable: true,
});
} catch (error: any) {
toast({
title: 'Chyba',
description: error.response?.data?.message || 'Nepodařilo se odeslat ověřovací kód',
status: 'error',
duration: 5000,
isClosable: true,
});
} finally {
setIsLoading(false);
}
};
const handleVerifyCode = async (e: React.FormEvent) => {
e.preventDefault();
if (code.length !== 6) return;
setIsLoading(true);
try {
await api.post('/auth/verify-reset-code', { email, code });
setStep('new_password');
} catch (error: any) {
toast({
title: 'Chyba',
description: error.response?.data?.message || 'Neplatný ověřovací kód',
status: 'error',
duration: 5000,
isClosable: true,
});
} finally {
setIsLoading(false);
}
};
const handleCompleteReset = async (e: React.FormEvent) => {
e.preventDefault();
if (newPassword !== confirmPassword) {
toast({
title: 'Chyba',
description: 'Hesla se neshodují',
status: 'error',
duration: 5000,
isClosable: true,
});
return;
}
if (newPassword.length < 8) {
toast({
title: 'Chyba',
description: 'Heslo musí mít alespoň 8 znaků',
status: 'error',
duration: 5000,
isClosable: true,
});
return;
}
setIsLoading(true);
try {
await api.post('/auth/complete-password-reset', {
email,
code,
new_password: newPassword,
});
setStep('success');
toast({
title: 'Úspěch',
description: 'Vaše heslo bylo úspěšně změněno. Nyní se můžete přihlásit.',
status: 'success',
duration: 5000,
isClosable: true,
});
} catch (error: any) {
toast({
title: 'Chyba',
description: error.response?.data?.message || 'Nepodařilo se změnit heslo',
status: 'error',
duration: 5000,
isClosable: true,
});
} finally {
setIsLoading(false);
}
};
const renderStep = () => {
switch (step) {
case 'email':
return (
<VStack as="form" onSubmit={handleInitiateReset} spacing={4} align="stretch">
<Heading as="h2" size="lg" textAlign="center" mb={4}>Obnova hesla</Heading>
<Text textAlign="center" mb={4}>
Zadejte e-mailovou adresu vašeho účtu. Pošleme vám ověřovací kód pro obnovu hesla.
</Text>
<FormControl id="email" isRequired>
<FormLabel>E-mail</FormLabel>
<Input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="vas@email.cz"
size="lg"
/>
</FormControl>
<Button
type="submit"
colorScheme="blue"
size="lg"
isLoading={isLoading}
loadingText="Odesílání..."
>
Odeslat ověřovací kód
</Button>
</VStack>
);
case 'code':
return (
<VStack as="form" onSubmit={handleVerifyCode} spacing={6} align="center">
<Heading as="h2" size="lg" textAlign="center">Ověřte svou identitu</Heading>
<Text textAlign="center">
Zadejte 6místný ověřovací kód, který jsme zaslali na adresu {email}
</Text>
<HStack spacing={3} justify="center">
<PinInput
value={code}
onChange={(value) => setCode(value)}
isDisabled={isLoading}
autoFocus
otp
>
{[...Array(6)].map((_, i) => (
<PinInputField key={i} />
))}
</PinInput>
</HStack>
<Button
type="submit"
colorScheme="blue"
size="lg"
isDisabled={code.length !== 6}
isLoading={isLoading}
loadingText="Ověřování..."
width="100%"
>
Ověřit kód
</Button>
<Button
variant="link"
onClick={() => setStep('email')}
isDisabled={isLoading}
>
Změnit e-mail
</Button>
</VStack>
);
case 'new_password':
return (
<VStack as="form" onSubmit={handleCompleteReset} spacing={4} align="stretch">
<Heading as="h2" size="lg" textAlign="center">Nastavení nového hesla</Heading>
<Text textAlign="center" mb={4}>
Zadejte nové heslo pro váš účet {email}
</Text>
<FormControl id="newPassword" isRequired>
<FormLabel>Nové heslo</FormLabel>
<Input
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
placeholder="Zadejte nové heslo"
size="lg"
/>
</FormControl>
<FormControl id="confirmPassword" isRequired>
<FormLabel>Potvrzení hesla</FormLabel>
<Input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="Zadejte heslo znovu"
size="lg"
/>
</FormControl>
<Button
type="submit"
colorScheme="blue"
size="lg"
isLoading={isLoading}
loadingText="Ukládání..."
isDisabled={!newPassword || newPassword !== confirmPassword}
>
Nastavit nové heslo
</Button>
</VStack>
);
case 'success':
return (
<VStack spacing={6} textAlign="center">
<Box p={4} bg="green.50" borderRadius="md" width="100%">
<Text color="green.800" fontWeight="medium">
Vaše heslo bylo úspěšně změněno!
</Text>
</Box>
<Text>
Nyní se můžete přihlásit pomocí svého nového hesla.
</Text>
<Button
as="a"
href="/login"
colorScheme="blue"
size="lg"
width="100%"
>
Přejít na přihlášení
</Button>
</VStack>
);
default:
return null;
}
};
return (
<Box minH="100vh" display="flex" alignItems="center" justifyContent="center" bg="gray.50" p={4}>
<Box w="100%" maxW="md" p={8} bg="white" borderWidth={1} borderRadius={8} boxShadow="lg">
{renderStep()}
</Box>
</Box>
);
};
export default ForgotPasswordPage;