|
1 | 1 | import { useState, useEffect } from 'react'; |
2 | 2 | import { Link, useNavigate } from 'react-router-dom'; |
3 | 3 | import { useMutation, gql } from '@apollo/client'; |
4 | | -import { Eye, EyeOff, ArrowRight, CheckCircle, XCircle, Github, Mail, Info } from 'lucide-react'; |
| 4 | +import { Eye, EyeOff, ArrowRight, CheckCircle, XCircle, Github, Mail, Info, Shield } from 'lucide-react'; |
5 | 5 | import { TlsStatusIndicator } from '../components/TlsStatusIndicator'; |
6 | 6 | import { PasswordRequirements } from '../components/PasswordRequirements'; |
7 | 7 | import { isValidEmail, getPasswordStrength } from '../utils/validation'; |
@@ -62,13 +62,21 @@ export function Signup() { |
62 | 62 | const [resendLoading, setResendLoading] = useState(false); |
63 | 63 | const [resendMessage, setResendMessage] = useState(''); |
64 | 64 | const [resendCooldown, setResendCooldown] = useState(0); |
| 65 | + const [rateLimitError, setRateLimitError] = useState<string | null>(null); |
| 66 | + const [rateLimitRetryAfter, setRateLimitRetryAfter] = useState<number | null>(null); |
65 | 67 |
|
66 | 68 | const [signup, { loading }] = useMutation(SIGNUP_MUTATION, { |
67 | 69 | onCompleted: (data) => { |
68 | 70 | setSignupComplete(true); |
69 | 71 | }, |
70 | 72 | onError: (error) => { |
71 | | - setErrors({ submit: error.message }); |
| 73 | + if (error.graphQLErrors?.[0]?.extensions?.rateLimitExceeded) { |
| 74 | + const retryAfter = error.graphQLErrors[0].extensions.retryAfter as number; |
| 75 | + setRateLimitError(error.message); |
| 76 | + setRateLimitRetryAfter(retryAfter); |
| 77 | + } else { |
| 78 | + setErrors({ submit: error.message }); |
| 79 | + } |
72 | 80 | } |
73 | 81 | }); |
74 | 82 |
|
@@ -588,8 +596,30 @@ export function Signup() { |
588 | 596 | )} |
589 | 597 | </div> |
590 | 598 |
|
| 599 | + {/* Rate Limit Error */} |
| 600 | + {rateLimitError && ( |
| 601 | + <div className="p-4 bg-red-900/20 border border-red-500/30 rounded-xl"> |
| 602 | + <div className="flex items-start space-x-2"> |
| 603 | + <Shield className="h-5 w-5 text-red-400 flex-shrink-0 mt-0.5" /> |
| 604 | + <div className="flex-1"> |
| 605 | + <p className="text-sm text-red-300 font-medium mb-2"> |
| 606 | + 🛡️ Rate Limit Exceeded |
| 607 | + </p> |
| 608 | + <p className="text-xs text-red-200/80 mb-2"> |
| 609 | + {rateLimitError} |
| 610 | + </p> |
| 611 | + {rateLimitRetryAfter && ( |
| 612 | + <p className="text-xs text-red-300/60"> |
| 613 | + Please try again in {Math.ceil(rateLimitRetryAfter / 60)} minute(s). |
| 614 | + </p> |
| 615 | + )} |
| 616 | + </div> |
| 617 | + </div> |
| 618 | + </div> |
| 619 | + )} |
| 620 | + |
591 | 621 | {/* Submit Error */} |
592 | | - {errors.submit && ( |
| 622 | + {errors.submit && !rateLimitError && ( |
593 | 623 | <div className="p-3 bg-red-900 border border-red-700 rounded-lg"> |
594 | 624 | <p className="text-sm text-red-300">{errors.submit}</p> |
595 | 625 | </div> |
|
0 commit comments