Skip to content

Commit 043cb82

Browse files
author
Alexia Michelle
committed
captcha adicional para la newsletter
1 parent 4984aaa commit 043cb82

File tree

3 files changed

+129
-5
lines changed

3 files changed

+129
-5
lines changed

i18n/es/code.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,5 +422,8 @@
422422
},
423423
"newsletter.list.security.uuid": {
424424
"message": "8a189f90-84b7-4f2c-8784-49509c895c54"
425+
},
426+
"newsletter.error.captcha": {
427+
"message": "Respuesta incorrecta. Por favor, inténtalo de nuevo."
425428
}
426429
}

src/components/NewsletterForm/index.js

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState } from 'react';
1+
import React, { useState, useEffect } from 'react';
22
import styles from './styles.module.css';
33
import Translate, { translate } from '@docusaurus/Translate';
44

@@ -11,6 +11,21 @@ export default function NewsletterForm() {
1111
const [status, setStatus] = useState('idle'); // idle, loading, success, error
1212
const [message, setMessage] = useState('');
1313

14+
// Bot Protection State
15+
const [honeypot, setHoneypot] = useState('');
16+
const [captcha, setCaptcha] = useState({ a: 0, b: 0, answer: '' });
17+
18+
// Generate a new math challenge
19+
const generateCaptcha = () => {
20+
const a = Math.floor(Math.random() * 10) + 1;
21+
const b = Math.floor(Math.random() * 10) + 1;
22+
setCaptcha({ a, b, answer: '' });
23+
};
24+
25+
useEffect(() => {
26+
generateCaptcha();
27+
}, []);
28+
1429
const lists = {
1530
news: {
1631
uuid: translate({
@@ -38,6 +53,26 @@ export default function NewsletterForm() {
3853
const handleSubmit = async (e) => {
3954
e.preventDefault();
4055

56+
// 1. Honeypot check (Bots fill hidden fields)
57+
if (honeypot) {
58+
console.warn('Bot detected via honeypot');
59+
setStatus('success'); // Fake success for bots
60+
return;
61+
}
62+
63+
// 2. CAPTCHA check
64+
if (parseInt(captcha.answer) !== captcha.a + captcha.b) {
65+
setStatus('error');
66+
setMessage(
67+
translate({
68+
id: 'newsletter.error.captcha',
69+
message: 'Incorrect answer. Please try again.',
70+
})
71+
);
72+
generateCaptcha();
73+
return;
74+
}
75+
4176
const uuids = Object.keys(selectedLists)
4277
.filter((key) => selectedLists[key])
4378
.map((key) => lists[key].uuid);
@@ -88,6 +123,7 @@ export default function NewsletterForm() {
88123
message: 'Something went wrong. Please try again.',
89124
})
90125
);
126+
generateCaptcha();
91127
}
92128
} catch (err) {
93129
setStatus('error');
@@ -97,6 +133,7 @@ export default function NewsletterForm() {
97133
message: 'Failed to connect to the subscription service. Please check your connection or CORS settings.',
98134
})
99135
);
136+
generateCaptcha();
100137
}
101138
};
102139

@@ -112,6 +149,18 @@ export default function NewsletterForm() {
112149
</Translate>
113150
</p>
114151
<form onSubmit={handleSubmit} className={styles.form}>
152+
{/* Honeypot Field (Hidden) */}
153+
<div className={styles.hidden} aria-hidden="true">
154+
<input
155+
type="text"
156+
name="website"
157+
tabIndex="-1"
158+
autoComplete="off"
159+
value={honeypot}
160+
onChange={(e) => setHoneypot(e.target.value)}
161+
/>
162+
</div>
163+
115164
<div className={styles.inputGroup}>
116165
<input
117166
type="email"
@@ -125,6 +174,23 @@ export default function NewsletterForm() {
125174
className={styles.input}
126175
disabled={status === 'loading' || status === 'success'}
127176
/>
177+
178+
{/* Inline CAPTCHA for desktop/tablet */}
179+
<div className={styles.captchaGroup}>
180+
<span className={styles.captchaQuestion}>
181+
{captcha.a} + {captcha.b} =
182+
</span>
183+
<input
184+
type="number"
185+
placeholder="..."
186+
value={captcha.answer}
187+
onChange={(e) => setCaptcha({ ...captcha, answer: e.target.value })}
188+
required
189+
className={styles.captchaInput}
190+
disabled={status === 'loading' || status === 'success'}
191+
/>
192+
</div>
193+
128194
<button
129195
type="submit"
130196
className={styles.button}

src/components/NewsletterForm/styles.module.css

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
border-radius: 12px;
1212
padding: 2rem;
1313
width: 100%;
14-
max-width: 500px;
14+
max-width: 600px;
1515
text-align: center;
1616
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
1717
}
@@ -37,10 +37,11 @@
3737
.inputGroup {
3838
display: flex;
3939
gap: 0.5rem;
40+
align-items: center;
4041
}
4142

4243
.input {
43-
flex: 1;
44+
flex: 2;
4445
padding: 0.75rem 1rem;
4546
border-radius: 8px;
4647
border: 1px solid rgba(255, 255, 255, 0.2);
@@ -49,21 +50,61 @@
4950
font-size: 1rem;
5051
outline: none;
5152
transition: border-color 0.2s;
53+
min-width: 0;
5254
}
5355

5456
.input:focus {
5557
border-color: var(--ifm-color-primary);
5658
}
5759

60+
.captchaGroup {
61+
display: flex;
62+
align-items: center;
63+
gap: 0.4rem;
64+
background: rgba(255, 255, 255, 0.1);
65+
padding: 0.45rem 0.75rem;
66+
border-radius: 8px;
67+
border: 1px solid rgba(255, 255, 255, 0.1);
68+
white-space: nowrap;
69+
}
70+
71+
.captchaQuestion {
72+
font-family: monospace;
73+
font-weight: bold;
74+
font-size: 0.95rem;
75+
color: var(--ifm-color-primary-light);
76+
}
77+
78+
.captchaInput {
79+
width: 45px;
80+
background: transparent;
81+
border: none;
82+
border-bottom: 2px solid var(--ifm-color-primary);
83+
color: white;
84+
text-align: center;
85+
font-weight: bold;
86+
font-size: 1rem;
87+
outline: none;
88+
padding: 0;
89+
}
90+
91+
/* Remove arrows from number input */
92+
.captchaInput::-webkit-outer-spin-button,
93+
.captchaInput::-webkit-inner-spin-button {
94+
-webkit-appearance: none;
95+
margin: 0;
96+
}
97+
5898
.button {
59-
padding: 0.75rem 1.5rem;
99+
padding: 0.75rem 1.25rem;
60100
border-radius: 8px;
61101
border: none;
62102
background: var(--ifm-color-primary);
63103
color: white;
64104
font-weight: bold;
65105
cursor: pointer;
66106
transition: filter 0.2s, transform 0.1s;
107+
flex: 1;
67108
}
68109

69110
.button:hover:not(:disabled) {
@@ -84,6 +125,7 @@
84125
justify-content: center;
85126
gap: 1.5rem;
86127
flex-wrap: wrap;
128+
margin-top: 0.5rem;
87129
}
88130

89131
.checkboxLabel {
@@ -117,8 +159,21 @@
117159
font-weight: 500;
118160
}
119161

120-
@media (max-width: 600px) {
162+
.hidden {
163+
display: none !important;
164+
}
165+
166+
@media (max-width: 768px) {
121167
.inputGroup {
122168
flex-direction: column;
169+
align-items: stretch;
170+
}
171+
172+
.captchaGroup {
173+
justify-content: center;
174+
}
175+
176+
.button {
177+
width: 100%;
123178
}
124179
}

0 commit comments

Comments
 (0)