Skip to content

Commit b28c186

Browse files
committed
Updated email validation in footer
1 parent 2baeb04 commit b28c186

2 files changed

Lines changed: 218 additions & 15 deletions

File tree

src/theme/Footer/Layout/enhanced-footer.css

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,12 @@ html[data-theme='light'] .enhanced-footer {
964964
margin-bottom: 16px;
965965
}
966966

967+
.newsletter-input-wrapper {
968+
position: relative;
969+
display: flex;
970+
align-items: center;
971+
}
972+
967973
.newsletter-input {
968974
padding: 12px 16px;
969975
border: 1px solid rgba(255, 255, 255, 0.1);
@@ -974,6 +980,29 @@ html[data-theme='light'] .enhanced-footer {
974980
font-weight: 400;
975981
backdrop-filter: blur(20px);
976982
transition: all 0.3s ease;
983+
width: 100%;
984+
}
985+
986+
/* Enhanced specificity for input error state */
987+
.enhanced-footer .newsletter-input.error,
988+
[data-theme='dark'] .enhanced-footer .newsletter-input.error,
989+
[data-theme='light'] .enhanced-footer .newsletter-input.error {
990+
border-color: #ef4444 !important;
991+
background: rgba(239, 68, 68, 0.15) !important;
992+
color: #fca5a5 !important;
993+
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.3) !important;
994+
}
995+
996+
.enhanced-footer .newsletter-input.error::placeholder,
997+
[data-theme='dark'] .enhanced-footer .newsletter-input.error::placeholder,
998+
[data-theme='light'] .enhanced-footer .newsletter-input.error::placeholder {
999+
color: #fca5a5 !important;
1000+
opacity: 0.8;
1001+
}
1002+
1003+
.newsletter-input.validating {
1004+
border-color: #f59e0b !important;
1005+
box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.3) !important;
9771006
}
9781007

9791008
.newsletter-input:focus {
@@ -987,6 +1016,82 @@ html[data-theme='light'] .enhanced-footer {
9871016
color: #94a3b8;
9881017
}
9891018

1019+
.validation-spinner {
1020+
position: absolute;
1021+
right: 12px;
1022+
display: flex;
1023+
align-items: center;
1024+
justify-content: center;
1025+
width: 20px;
1026+
height: 20px;
1027+
}
1028+
1029+
.spinner {
1030+
width: 16px;
1031+
height: 16px;
1032+
border: 2px solid rgba(255, 255, 255, 0.2);
1033+
border-top: 2px solid #6366f1;
1034+
border-radius: 50%;
1035+
animation: spin 1s linear infinite;
1036+
}
1037+
1038+
@keyframes spin {
1039+
0% { transform: rotate(0deg); }
1040+
100% { transform: rotate(360deg); }
1041+
}
1042+
1043+
/* Enhanced specificity for error message to override footer protection */
1044+
.enhanced-footer .error-message,
1045+
[data-theme='dark'] .enhanced-footer .error-message,
1046+
[data-theme='light'] .enhanced-footer .error-message {
1047+
color: #ef4444 !important;
1048+
font-size: 13px !important;
1049+
font-weight: 600 !important;
1050+
margin-top: 4px !important;
1051+
padding: 8px 12px !important;
1052+
background: rgba(239, 68, 68, 0.2) !important;
1053+
border-radius: 8px !important;
1054+
border: 1px solid rgba(239, 68, 68, 0.5) !important;
1055+
animation: fadeIn 0.3s ease !important;
1056+
box-shadow: 0 4px 6px rgba(239, 68, 68, 0.1) !important;
1057+
text-align: left !important;
1058+
}
1059+
1060+
@keyframes fadeIn {
1061+
from { opacity: 0; transform: translateY(-10px); }
1062+
to { opacity: 1; transform: translateY(0); }
1063+
}
1064+
1065+
.newsletter-button {
1066+
padding: 14px 24px;
1067+
background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
1068+
color: white;
1069+
border: none;
1070+
border-radius: 12px;
1071+
font-size: 14px;
1072+
font-weight: 700;
1073+
cursor: pointer;
1074+
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
1075+
box-shadow:
1076+
0 8px 24px rgba(102, 126, 234, 0.4),
1077+
0 0 0 1px rgba(255, 255, 255, 0.1),
1078+
inset 0 1px 0 rgba(255, 255, 255, 0.2);
1079+
position: relative;
1080+
overflow: hidden;
1081+
text-transform: uppercase;
1082+
letter-spacing: 0.5px;
1083+
}
1084+
1085+
.newsletter-button:disabled {
1086+
opacity: 0.5;
1087+
cursor: not-allowed;
1088+
transform: none !important;
1089+
box-shadow:
1090+
0 4px 12px rgba(102, 126, 234, 0.1),
1091+
0 0 0 1px rgba(255, 255, 255, 0.05),
1092+
inset 0 1px 0 rgba(255, 255, 255, 0.05);
1093+
}
1094+
9901095
.newsletter-button {
9911096
padding: 14px 24px;
9921097
background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);

src/theme/Footer/Layout/index.tsx

Lines changed: 113 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export default function FooterLayout({
2828
});
2929
const [email, setEmail] = useState('');
3030
const [isSubscribed, setIsSubscribed] = useState(false);
31+
const [emailError, setEmailError] = useState<string | null>(null);
32+
const [isValidating, setIsValidating] = useState(false);
3133

3234
useEffect(() => {
3335
// Simulate real-time stats updates
@@ -55,15 +57,99 @@ export default function FooterLayout({
5557
return () => clearInterval(interval);
5658
}, []);
5759

60+
// Advanced email validation function
61+
const validateEmail = (email: string): string | null => {
62+
// Check basic format
63+
const basicRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
64+
if (!basicRegex.test(email)) {
65+
return 'Please enter a valid email address.';
66+
}
67+
68+
// Check for common disposable email patterns
69+
const disposableDomains = [
70+
'10minutemail.com', 'tempmail.org', 'guerrillamail.com',
71+
'mailinator.com', 'throwaway.email', 'disposablemail.com',
72+
'sharklasers.com', 'trashmail.com', 'yopmail.com'
73+
];
74+
75+
const domain = email.split('@')[1].toLowerCase();
76+
if (disposableDomains.includes(domain)) {
77+
return 'Disposable email addresses are not allowed.';
78+
}
79+
80+
// Check for common free email domains with additional validation
81+
const freeEmailDomains = [
82+
'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com',
83+
'protonmail.com', 'icloud.com', 'aol.com', 'live.com'
84+
];
85+
86+
if (!freeEmailDomains.includes(domain) && domain.length < 3) {
87+
return 'Email domain appears to be invalid.';
88+
}
89+
90+
// Check for common patterns that indicate invalid emails
91+
if (email.length > 254) {
92+
return 'Email address is too long.';
93+
}
94+
95+
if (email.startsWith('.') || email.endsWith('.')) {
96+
return 'Email address cannot start or end with a dot.';
97+
}
98+
99+
if (email.includes('..')) {
100+
return 'Email address cannot contain consecutive dots.';
101+
}
102+
103+
// Check for valid characters in local part
104+
const localPart = email.split('@')[0];
105+
if (localPart.length > 64) {
106+
return 'Email local part is too long.';
107+
}
108+
109+
const validLocalRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+$/;
110+
if (!validLocalRegex.test(localPart)) {
111+
return 'Email contains invalid characters.';
112+
}
113+
114+
return null; // Valid email
115+
};
116+
117+
// Handle real-time validation
118+
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
119+
const value = e.target.value;
120+
setEmail(value);
121+
122+
// Clear error if field is empty
123+
if (value === '') {
124+
setEmailError(null);
125+
return;
126+
}
127+
128+
// Perform validation with a slight delay to avoid excessive checks
129+
setIsValidating(true);
130+
setTimeout(() => {
131+
const error = validateEmail(value);
132+
setEmailError(error);
133+
setIsValidating(false);
134+
}, 300);
135+
};
136+
58137
const handleSubscribe = (e: React.FormEvent) => {
59138
e.preventDefault();
60-
if (email) {
61-
setIsSubscribed(true);
62-
setTimeout(() => {
63-
setIsSubscribed(false);
64-
setEmail('');
65-
}, 3000);
139+
140+
// Final validation before submission
141+
const error = validateEmail(email);
142+
if (error) {
143+
setEmailError(error);
144+
return;
66145
}
146+
147+
setIsSubscribed(true);
148+
setTimeout(() => {
149+
setIsSubscribed(false);
150+
setEmail('');
151+
setEmailError(null); // Clear error on successful subscription
152+
}, 3000);
67153
};
68154
return (
69155
<footer className="enhanced-footer">
@@ -284,18 +370,30 @@ export default function FooterLayout({
284370
Join {stats.activeUsers} developers getting weekly insights, tutorials, and exclusive content.
285371
</p>
286372
<form className="newsletter-form" onSubmit={handleSubscribe}>
287-
<input
288-
type="email"
289-
placeholder="your@email.com"
290-
className="newsletter-input"
291-
value={email}
292-
onChange={(e) => setEmail(e.target.value)}
293-
required
294-
/>
373+
<div className="newsletter-input-wrapper">
374+
<input
375+
type="email"
376+
placeholder="your@email.com"
377+
className={`newsletter-input ${emailError ? 'error' : ''} ${isValidating ? 'validating' : ''}`}
378+
value={email}
379+
onChange={handleEmailChange}
380+
required
381+
/>
382+
{isValidating && (
383+
<div className="validation-spinner">
384+
<div className="spinner"></div>
385+
</div>
386+
)}
387+
</div>
388+
{emailError && (
389+
<div className="error-message">
390+
{emailError}
391+
</div>
392+
)}
295393
<button
296394
type="submit"
297395
className={`newsletter-button ${isSubscribed ? 'subscribed' : ''}`}
298-
disabled={isSubscribed}
396+
disabled={isSubscribed || !!emailError || email === ''}
299397
>
300398
{isSubscribed ? '✓ Subscribed!' : 'Subscribe Now →'}
301399
</button>

0 commit comments

Comments
 (0)