fix: BUG-012 remove email requirement from free signup + fix 429 handling
This commit is contained in:
parent
73bb041513
commit
3c0bac889a
4 changed files with 35 additions and 37 deletions
|
|
@ -2,9 +2,9 @@ function openSignup() {
|
||||||
document.getElementById('signupModal').classList.add('active');
|
document.getElementById('signupModal').classList.add('active');
|
||||||
document.getElementById('signupForm').style.display = 'block';
|
document.getElementById('signupForm').style.display = 'block';
|
||||||
document.getElementById('keyResult').style.display = 'none';
|
document.getElementById('keyResult').style.display = 'none';
|
||||||
document.getElementById('signupEmail').value = '';
|
|
||||||
document.getElementById('signupError').style.display = 'none';
|
document.getElementById('signupError').style.display = 'none';
|
||||||
setTimeout(function() { document.getElementById('signupEmail').focus(); }, 100);
|
document.getElementById('signupBtn').textContent = 'Get API Key';
|
||||||
|
document.getElementById('signupBtn').disabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeSignup() {
|
function closeSignup() {
|
||||||
|
|
@ -16,35 +16,33 @@ document.getElementById('signupModal').addEventListener('click', function(e) {
|
||||||
if (e.target === this) closeSignup();
|
if (e.target === this) closeSignup();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Submit on Enter
|
|
||||||
document.getElementById('signupEmail').addEventListener('keydown', function(e) {
|
|
||||||
if (e.key === 'Enter') submitSignup();
|
|
||||||
});
|
|
||||||
|
|
||||||
async function submitSignup() {
|
async function submitSignup() {
|
||||||
var email = document.getElementById('signupEmail').value.trim();
|
|
||||||
var errEl = document.getElementById('signupError');
|
var errEl = document.getElementById('signupError');
|
||||||
var btn = document.getElementById('signupBtn');
|
var btn = document.getElementById('signupBtn');
|
||||||
|
|
||||||
if (!email) {
|
|
||||||
errEl.textContent = 'Please enter your email.';
|
|
||||||
errEl.style.display = 'block';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
btn.textContent = 'Creating...';
|
btn.textContent = 'Creating...';
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
|
errEl.style.display = 'none';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var res = await fetch('/v1/signup/free', {
|
var res = await fetch('/v1/signup/free', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ email: email })
|
body: JSON.stringify({})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (res.status === 429) {
|
||||||
|
errEl.textContent = 'Too many requests. Please try again in a few minutes.';
|
||||||
|
errEl.style.display = 'block';
|
||||||
|
btn.textContent = 'Get API Key';
|
||||||
|
btn.disabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var data = await res.json();
|
var data = await res.json();
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
errEl.textContent = data.error || 'Something went wrong.';
|
errEl.textContent = data.error || 'Something went wrong. Please try again.';
|
||||||
errEl.style.display = 'block';
|
errEl.style.display = 'block';
|
||||||
btn.textContent = 'Get API Key';
|
btn.textContent = 'Get API Key';
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
|
|
@ -72,7 +70,6 @@ function copyKey() {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
navigator.clipboard.writeText(key).then(showCopied).catch(function() {
|
navigator.clipboard.writeText(key).then(showCopied).catch(function() {
|
||||||
// Fallback for older browsers / non-secure contexts
|
|
||||||
var ta = document.createElement('textarea');
|
var ta = document.createElement('textarea');
|
||||||
ta.value = key; ta.style.position = 'fixed'; ta.style.opacity = '0';
|
ta.value = key; ta.style.position = 'fixed'; ta.style.opacity = '0';
|
||||||
document.body.appendChild(ta); ta.select();
|
document.body.appendChild(ta); ta.select();
|
||||||
|
|
|
||||||
|
|
@ -223,17 +223,17 @@ footer { padding: 40px 0; text-align: center; color: var(--muted); font-size: 0.
|
||||||
<button class="close" id="btn-close-signup">×</button>
|
<button class="close" id="btn-close-signup">×</button>
|
||||||
<div id="signupForm">
|
<div id="signupForm">
|
||||||
<h2>Get Your Free API Key</h2>
|
<h2>Get Your Free API Key</h2>
|
||||||
<p>Enter your email and get an API key instantly. No credit card required.</p>
|
<p>One click. No email, no credit card.</p>
|
||||||
<div class="error" id="signupError"></div>
|
<div class="error" id="signupError"></div>
|
||||||
<input type="email" id="signupEmail" placeholder="you@example.com" autofocus>
|
|
||||||
<button class="btn btn-primary" style="width:100%" id="signupBtn">Get API Key</button>
|
<button class="btn btn-primary" style="width:100%" id="signupBtn">Get API Key</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="key-result" id="keyResult">
|
<div class="key-result" id="keyResult">
|
||||||
<h2>🚀 You're in!</h2>
|
<h2>🚀 You're in!</h2>
|
||||||
<p>Here's your API key. <strong>Save it now</strong> — it won't be shown again.</p>
|
<p>Here's your API key:</p>
|
||||||
<div class="key-box" id="apiKeyDisplay" title="Click to copy"></div>
|
<div class="key-box" id="apiKeyDisplay" title="Click to copy"></div>
|
||||||
<div class="copy-hint">Click to copy</div>
|
<div class="copy-hint">Click to copy</div>
|
||||||
<p style="margin-top:24px;color:var(--muted);font-size:0.9rem;">100 free PDFs/month • All endpoints • <a href="/docs">View docs →</a></p>
|
<p style="margin-top:12px;color:#f66;font-size:0.9rem;font-weight:600;">⚠️ Save your API key — we can't recover it later.</p>
|
||||||
|
<p style="margin-top:16px;color:var(--muted);font-size:0.9rem;">100 free PDFs/month • All endpoints • <a href="/docs">View docs →</a></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -20,20 +20,19 @@ const signupLimiter = rateLimit({
|
||||||
|
|
||||||
// Self-service free API key signup
|
// Self-service free API key signup
|
||||||
router.post("/free", signupLimiter, (req: Request, res: Response) => {
|
router.post("/free", signupLimiter, (req: Request, res: Response) => {
|
||||||
const { email } = req.body;
|
const { email } = req.body || {};
|
||||||
|
|
||||||
if (!email || typeof email !== "string") {
|
// Email is optional — validate only if provided
|
||||||
res.status(400).json({ error: "Email is required" });
|
let cleanEmail: string | undefined;
|
||||||
return;
|
if (email && typeof email === "string") {
|
||||||
}
|
|
||||||
|
|
||||||
// Basic email validation
|
|
||||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||||
res.status(400).json({ error: "Invalid email address" });
|
res.status(400).json({ error: "Invalid email address" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
cleanEmail = email.trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
const keyInfo = createFreeKey(email.trim().toLowerCase());
|
const keyInfo = createFreeKey(cleanEmail);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
message: "Welcome to DocFast! 🚀",
|
message: "Welcome to DocFast! 🚀",
|
||||||
|
|
|
||||||
|
|
@ -67,15 +67,17 @@ function generateKey(prefix: string): string {
|
||||||
return `${prefix}_${randomBytes(24).toString("hex")}`;
|
return `${prefix}_${randomBytes(24).toString("hex")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createFreeKey(email: string): ApiKey {
|
export function createFreeKey(email?: string): ApiKey {
|
||||||
// Check if email already has a free key
|
// If email provided, check if it already has a free key
|
||||||
|
if (email) {
|
||||||
const existing = store.keys.find((k) => k.email === email && k.tier === "free");
|
const existing = store.keys.find((k) => k.email === email && k.tier === "free");
|
||||||
if (existing) return existing;
|
if (existing) return existing;
|
||||||
|
}
|
||||||
|
|
||||||
const entry: ApiKey = {
|
const entry: ApiKey = {
|
||||||
key: generateKey("df_free"),
|
key: generateKey("df_free"),
|
||||||
tier: "free",
|
tier: "free",
|
||||||
email,
|
email: email || "",
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
store.keys.push(entry);
|
store.keys.push(entry);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue