Backend changes: - Add password reset token database migration (V011) - Create email service with Mailgun integration (EU/US regions) - Implement password reset flow with secure token generation - Add email verification endpoints and logic - Create beautiful HTML email templates for reset and verification - Add password reset DTOs with validation - Update User entity with email verification fields Frontend changes: - Create forgot password page with email submission - Create reset password page with token validation - Add email verification banner component - Integrate verification banner into main dashboard - Add password requirements and validation UI Features: - Mailgun API ready for EU and US regions - Secure token expiration (1h for reset, 24h for verification) - Rate limiting on resend (2min interval) - Protection against email enumeration - IP address and user agent tracking - Token reuse prevention 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
118 lines
3.3 KiB
TypeScript
118 lines
3.3 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { Alert, Button, Snackbar, Box } from '@mui/material';
|
|
import { Email } from '@mui/icons-material';
|
|
import { useAuth } from '@/lib/auth/AuthContext';
|
|
import apiClient from '@/lib/api/client';
|
|
|
|
export const EmailVerificationBanner: React.FC = () => {
|
|
const { user } = useAuth();
|
|
const [dismissed, setDismissed] = useState(false);
|
|
const [loading, setLoading] = useState(false);
|
|
const [snackbar, setSnackbar] = useState<{ open: boolean; message: string; severity: 'success' | 'error' }>({
|
|
open: false,
|
|
message: '',
|
|
severity: 'success',
|
|
});
|
|
|
|
// Don't show if user is not logged in, email is verified, or banner was dismissed
|
|
if (!user || user.emailVerified || dismissed) {
|
|
return null;
|
|
}
|
|
|
|
const handleResendVerification = async () => {
|
|
setLoading(true);
|
|
try {
|
|
await apiClient.post('/api/v1/auth/email/send-verification');
|
|
setSnackbar({
|
|
open: true,
|
|
message: 'Verification email sent! Please check your inbox.',
|
|
severity: 'success',
|
|
});
|
|
} catch (error: any) {
|
|
console.error('Failed to resend verification email:', error);
|
|
setSnackbar({
|
|
open: true,
|
|
message: error.response?.data?.message || 'Failed to send verification email. Please try again.',
|
|
severity: 'error',
|
|
});
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleDismiss = () => {
|
|
setDismissed(true);
|
|
// Store dismissal in localStorage to persist across sessions
|
|
localStorage.setItem('emailVerificationBannerDismissed', 'true');
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Alert
|
|
severity="warning"
|
|
icon={<Email />}
|
|
onClose={handleDismiss}
|
|
sx={{
|
|
borderRadius: 2,
|
|
mb: 2,
|
|
'& .MuiAlert-message': {
|
|
width: '100%',
|
|
},
|
|
}}
|
|
>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
gap: 2,
|
|
flexWrap: 'wrap',
|
|
}}
|
|
>
|
|
<Box>
|
|
<strong>Verify your email address</strong>
|
|
<br />
|
|
Please check your inbox and click the verification link to access all features.
|
|
</Box>
|
|
<Button
|
|
variant="outlined"
|
|
size="small"
|
|
onClick={handleResendVerification}
|
|
disabled={loading}
|
|
sx={{
|
|
borderRadius: 2,
|
|
textTransform: 'none',
|
|
fontWeight: 600,
|
|
borderColor: 'warning.main',
|
|
color: 'warning.dark',
|
|
'&:hover': {
|
|
borderColor: 'warning.dark',
|
|
bgcolor: 'warning.light',
|
|
},
|
|
}}
|
|
>
|
|
{loading ? 'Sending...' : 'Resend Email'}
|
|
</Button>
|
|
</Box>
|
|
</Alert>
|
|
|
|
<Snackbar
|
|
open={snackbar.open}
|
|
autoHideDuration={6000}
|
|
onClose={() => setSnackbar({ ...snackbar, open: false })}
|
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
|
>
|
|
<Alert
|
|
severity={snackbar.severity}
|
|
onClose={() => setSnackbar({ ...snackbar, open: false })}
|
|
sx={{ borderRadius: 2 }}
|
|
>
|
|
{snackbar.message}
|
|
</Alert>
|
|
</Snackbar>
|
|
</>
|
|
);
|
|
};
|