Files
dado.cloud b0ba007020 Modelo do encurtador em Next.js
Gerado a partir de deploy-templates/nextjs (dado.cloud 4c2a882).
2026-10-21 12:26:50 +00:00

140 lines
4.5 KiB
TypeScript

'use client';
import { useCallback, useEffect, useState } from 'react';
import type { LinkJson } from '@/lib/links';
const semProtocolo = (url: string) => url.replace(/^https?:\/\//, '');
export default function Encurtador({ linksIniciais }: { linksIniciais: LinkJson[] }) {
const [url, setUrl] = useState('');
const [enviando, setEnviando] = useState(false);
const [erro, setErro] = useState<string | null>(null);
const [criado, setCriado] = useState<LinkJson | null>(null);
const [copiado, setCopiado] = useState(false);
const [links, setLinks] = useState(linksIniciais);
const carregarLinks = useCallback(async () => {
try {
const resposta = await fetch('/api/links', { cache: 'no-store' });
if (resposta.ok) setLinks(((await resposta.json()) as { links: LinkJson[] }).links);
} catch {
// Sem rede: a lista fica como estava.
}
}, []);
// Voltou para a aba depois de testar um link? Atualiza a contagem de cliques.
useEffect(() => {
const aoVoltar = () => {
if (document.visibilityState === 'visible') void carregarLinks();
};
document.addEventListener('visibilitychange', aoVoltar);
return () => document.removeEventListener('visibilitychange', aoVoltar);
}, [carregarLinks]);
async function encurtar(evento: React.FormEvent<HTMLFormElement>) {
evento.preventDefault();
setErro(null);
let alvo = url.trim();
if (!alvo) return setErro('Cole um link para encurtar.');
// Quem digita "exemplo.com.br" quer dizer https://exemplo.com.br.
if (!/^[a-z][a-z0-9+.-]*:/i.test(alvo)) alvo = `https://${alvo}`;
setEnviando(true);
try {
const resposta = await fetch('/api/links', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: alvo }),
});
const dados = await resposta.json().catch(() => ({}));
if (!resposta.ok) return setErro(dados.erro || 'Não deu para encurtar esse link. Tente de novo.');
setCriado(dados as LinkJson);
setCopiado(false);
setUrl('');
await carregarLinks();
} catch {
setErro('Sem conexão com o servidor. Tente de novo.');
} finally {
setEnviando(false);
}
}
async function copiar() {
if (!criado) return;
try {
await navigator.clipboard.writeText(criado.curto);
setCopiado(true);
} catch {
// Navegador sem permissão de área de transferência: seleciona o link.
const alvo = document.getElementById('curto');
if (alvo) getSelection()?.selectAllChildren(alvo);
}
}
return (
<>
<section className="cartao">
<form className="formulario" onSubmit={encurtar} noValidate>
<label htmlFor="url" className="so-leitor">
Link para encurtar
</label>
<input
id="url"
name="url"
type="url"
inputMode="url"
maxLength={2048}
placeholder="Cole seu link aqui"
autoComplete="off"
required
value={url}
onChange={(e) => setUrl(e.target.value)}
/>
<button type="submit" disabled={enviando}>
Encurtar
</button>
</form>
{erro && (
<p className="erro" role="alert">
{erro}
</p>
)}
{criado && (
<div className="resultado">
<a id="curto" href={criado.curto} target="_blank" rel="noopener">
{semProtocolo(criado.curto)}
</a>
<button type="button" className="secundario" onClick={copiar}>
{copiado ? 'Copiado!' : 'Copiar'}
</button>
</div>
)}
</section>
<h2>Últimos links</h2>
<section className="cartao">
{links.length === 0 ? (
<p className="vazio">Nenhum link ainda. Encurte o primeiro aí em cima.</p>
) : (
<ul className="lista">
{links.map((link) => (
<li key={link.codigo}>
<div className="link-info">
<a href={link.curto} target="_blank" rel="noopener">
{semProtocolo(link.curto)}
</a>
<span className="destino" title={link.url}>
{link.url}
</span>
</div>
<span className="cliques">{link.cliques === 1 ? '1 clique' : `${link.cliques} cliques`}</span>
</li>
))}
</ul>
)}
</section>
</>
);
}