mirror of
https://github.com/multipleof4/4ev.link.git
synced 2026-01-13 15:57:53 +00:00
Feat: Add reCAPTCHA v2 checkbox to the form
This commit is contained in:
121
index.html
121
index.html
@@ -10,6 +10,7 @@
|
||||
<meta name="theme-color" content="#0a0a0a" media="(prefers-color-scheme: dark)">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
|
||||
</head>
|
||||
<body class="bg-gray-50 dark:bg-neutral-950 text-gray-800 dark:text-gray-300 antialiased">
|
||||
<div class="flex flex-col min-h-screen">
|
||||
@@ -31,14 +32,17 @@
|
||||
A simple, fast, and reliable URL shortener for everyone.
|
||||
</p>
|
||||
|
||||
<form id="shorten-form" class="mt-8 max-w-xl mx-auto flex flex-col sm:flex-row gap-3">
|
||||
<label class="relative flex-1">
|
||||
<i data-lucide="link" class="absolute top-1/2 left-4 -translate-y-1/2 w-5 h-5 text-gray-400 dark:text-neutral-500"></i>
|
||||
<input id="url-input" type="text" placeholder="your-long-link.com" autocapitalize="off" spellcheck="false" class="w-full pl-11 pr-4 py-3 bg-white dark:bg-neutral-900 border border-gray-200 dark:border-neutral-800 rounded-lg focus:ring-2 focus:ring-black dark:focus:ring-white focus:outline-none transition">
|
||||
</label>
|
||||
<button id="submit-button" type="submit" class="px-6 py-3 font-semibold bg-black dark:bg-white text-white dark:text-black rounded-lg hover:opacity-90 transition-opacity disabled:opacity-50 disabled:cursor-wait">
|
||||
Shorten
|
||||
</button>
|
||||
<form id="shorten-form" class="mt-8 max-w-xl mx-auto flex flex-col gap-4">
|
||||
<div class="flex flex-col sm:flex-row gap-3">
|
||||
<label class="relative flex-1">
|
||||
<i data-lucide="link" class="absolute top-1/2 left-4 -translate-y-1/2 w-5 h-5 text-gray-400 dark:text-neutral-500"></i>
|
||||
<input id="url-input" type="text" placeholder="your-long-link.com" autocapitalize="off" spellcheck="false" class="w-full pl-11 pr-4 py-3 bg-white dark:bg-neutral-900 border border-gray-200 dark:border-neutral-800 rounded-lg focus:ring-2 focus:ring-black dark:focus:ring-white focus:outline-none transition">
|
||||
</label>
|
||||
<button id="submit-button" type="submit" class="px-6 py-3 font-semibold bg-black dark:bg-white text-white dark:text-black rounded-lg hover:opacity-90 transition-opacity disabled:opacity-50 disabled:cursor-wait">
|
||||
Shorten
|
||||
</button>
|
||||
</div>
|
||||
<div class="g-recaptcha self-center" data-sitekey="6LeXhdYrAAAAALW6DdgxNeHU0kwBncdicLnVYvXT"></div>
|
||||
</form>
|
||||
|
||||
<div id="result-container" hidden class="mt-4 max-w-xl mx-auto flex gap-2">
|
||||
@@ -61,85 +65,48 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const $ = q => document.querySelector(q);
|
||||
const $=q=>document.querySelector(q),form=$('#shorten-form'),urlInput=$('#url-input'),submitButton=$('#submit-button'),messageArea=$('#message-area'),resultContainer=$('#result-container'),resultOutput=$('#result-output'),copyButton=$('#copy-button'),openLinkButton=$('#open-link-button'),normalizeUrl=s=>{try{return new URL(s).href}catch(_){try{return new URL('https://'+s).href}catch(_){}}};
|
||||
|
||||
const form = $('#shorten-form');
|
||||
const urlInput = $('#url-input');
|
||||
const submitButton = $('#submit-button');
|
||||
const messageArea = $('#message-area');
|
||||
|
||||
const resultContainer = $('#result-container');
|
||||
const resultOutput = $('#result-output');
|
||||
const copyButton = $('#copy-button');
|
||||
const openLinkButton = $('#open-link-button');
|
||||
|
||||
const normalizeUrl = (str) => {
|
||||
try { return new URL(str).href; }
|
||||
catch (_) {
|
||||
try { return new URL('https://' + str).href; }
|
||||
catch (_) {}
|
||||
}
|
||||
};
|
||||
|
||||
form.onsubmit = async (e) => {
|
||||
form.onsubmit=async e=>{
|
||||
e.preventDefault();
|
||||
messageArea.textContent = '';
|
||||
|
||||
const url = normalizeUrl(urlInput.value.trim());
|
||||
if (!url) {
|
||||
messageArea.textContent = 'Please enter a valid URL.';
|
||||
return;
|
||||
messageArea.textContent='';
|
||||
const u=normalizeUrl(urlInput.value.trim());
|
||||
if(!u)return messageArea.textContent='Please enter a valid URL.';
|
||||
const k=grecaptcha.getResponse();
|
||||
if(!k)return messageArea.textContent='Please complete the CAPTCHA.';
|
||||
submitButton.disabled=true;submitButton.textContent='…';
|
||||
try{
|
||||
const r=await fetch('/api/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:u,token:k})});
|
||||
if(!r.ok)throw new Error(await r.text()||r.statusText);
|
||||
const d=await r.json();
|
||||
resultContainer.hidden=false;
|
||||
resultOutput.value=d.shortUrl;
|
||||
openLinkButton.href=d.shortUrl
|
||||
}catch(err){
|
||||
resultContainer.hidden=true;
|
||||
messageArea.textContent=err.message||'Something went wrong.'
|
||||
}finally{
|
||||
grecaptcha.reset();
|
||||
submitButton.disabled=false;
|
||||
submitButton.textContent='Shorten'
|
||||
}
|
||||
|
||||
submitButton.disabled = true;
|
||||
submitButton.textContent = '…';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/create', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text() || response.statusText);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
resultContainer.hidden = false;
|
||||
resultOutput.value = data.shortUrl;
|
||||
openLinkButton.href = data.shortUrl;
|
||||
} catch (err) {
|
||||
resultContainer.hidden = true;
|
||||
messageArea.textContent = err.message || 'Something went wrong.';
|
||||
}
|
||||
|
||||
submitButton.disabled = false;
|
||||
submitButton.textContent = 'Shorten';
|
||||
};
|
||||
|
||||
copyButton.onclick = async () => {
|
||||
copyButton.onclick=async()=>{
|
||||
resultOutput.select();
|
||||
try {
|
||||
try{
|
||||
await navigator.clipboard.writeText(resultOutput.value);
|
||||
copyButton.innerHTML = '<i data-lucide="check" class="w-5 h-5 text-green-500"></i>';
|
||||
copyButton.innerHTML='<i data-lucide="check" class="w-5 h-5 text-green-500"></i>';
|
||||
lucide.createIcons();
|
||||
setTimeout(() => {
|
||||
copyButton.innerHTML = '<i data-lucide="copy" class="w-5 h-5"></i>';
|
||||
lucide.createIcons();
|
||||
}, 1000);
|
||||
} catch (err) {
|
||||
// Handle clipboard write error
|
||||
}
|
||||
};
|
||||
|
||||
resultOutput.onclick = () => resultOutput.select();
|
||||
|
||||
urlInput.oninput = () => {
|
||||
resultContainer.hidden = true;
|
||||
messageArea.textContent = '';
|
||||
setTimeout(()=>{
|
||||
copyButton.innerHTML='<i data-lucide="copy" class="w-5 h-5"></i>';
|
||||
lucide.createIcons()
|
||||
},1e3)
|
||||
}catch(err){}
|
||||
};
|
||||
|
||||
resultOutput.onclick=()=>resultOutput.select();
|
||||
urlInput.oninput=()=>{resultContainer.hidden=true;messageArea.textContent=''};
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user