Template
Modelo do encurtador em Next.js
Gerado a partir de deploy-templates/nextjs (dado.cloud 4c2a882).
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
'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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user