This repository has been archived on 2026-06-09. You can view files and clone it, but cannot push or open issues or pull requests.
wedding-app/infra/wedding_photo/apps/web/src/routes/admin/Login.tsx
Claude 5a19753013
feat: split into 3 docker-compose stacks (main / gitea / wedding_photo)
Repo passa a viver dentro de infra/, com três stacks isoladas que
compartilham uma network Docker externa ('infra-net'):

main/        infra base: postgres + redis + minio + minio-init + pgadmin
             + caddy. Postgres roda em modo multi-banco; o init script
             cria os DBs 'wedding' e 'gitea' com roles dedicadas. MinIO
             tem um bucket inicial criado pelo minio-init com anonymous
             download + CORS. Caddy é o único container expondo 80/443
             e roteia por hostname: gitea.{DOMAIN_BASE} / wedding.* /
             pgadmin.* / minio.* / media.* (rewrite de bucket).

gitea/       gitea + act_runner. Gitea liga no postgres compartilhado e
             usa redis pra cache+sessões. O runner ganha um Dockerfile
             pequeno que adiciona docker CLI por cima do
             gitea/act_runner pra workflows poderem chamar 'docker
             build'. Bootstrap do token de runner documentado no
             .env.example.

wedding_photo/  Só a aplicação: 'wedding_app' (FastAPI + SPA) +
             postgres-backup + media-backup. Os bancos e o MinIO vêm
             da stack main/. A app usa extra_hosts: host-gateway pra
             alcançar media.{DOMAIN_BASE} via Caddy mesmo em dev local
             — assim a assinatura S3 fecha com o host que o browser
             usa pra fazer PUT.

Orquestração:
- Makefile no root: 'make up' sobe tudo na ordem (main -> gitea ->
  wedding_photo). 'make up-{main,gitea,wedding}' pra controle
  granular. 'make logs-*', 'make down', 'make status', 'make pull-*'.
- network.sh cria a 'infra-net' antes de qualquer up; idempotente.
- Cada stack tem seu próprio .env.example. As creds compartilhadas
  (DOMAIN_BASE, MINIO_ROOT_*, WEDDING_DB_*) precisam casar entre
  main/.env e o consumidor (gitea/.env ou wedding_photo/.env).
- .gitignore ignora todas as pastas data/ dos volumes.
2026-06-08 23:34:14 +00:00

98 lines
2.9 KiB
TypeScript

import { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
export default function AdminLogin() {
const navigate = useNavigate();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
async function submit(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
setError(null);
try {
const r = await fetch('/api/admin/login', {
method: 'POST',
credentials: 'include',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ email: email.trim(), password }),
});
if (r.status === 401) {
setError('Email ou senha incorretos.');
return;
}
if (!r.ok) {
setError('Erro inesperado. Tenta de novo.');
return;
}
navigate('/admin', { replace: true });
} catch {
setError('Falha de rede.');
} finally {
setLoading(false);
}
}
return (
<main className="min-h-full flex items-center justify-center px-6 py-12">
<form
onSubmit={submit}
className="w-full max-w-sm bg-white border border-stone-200 rounded-lg p-6 space-y-4"
>
<h1 className="font-display text-2xl text-stone-800 text-center">
Painel dos noivos
</h1>
<p className="text-sm text-stone-500 text-center -mt-2">
Acesso restrito a Stefanie & Leandro.
</p>
<label className="block">
<span className="block text-xs font-medium text-stone-600 mb-1">Email</span>
<input
type="email"
required
autoFocus
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full rounded border border-stone-300 px-3 py-2"
/>
</label>
<label className="block">
<span className="block text-xs font-medium text-stone-600 mb-1">Senha</span>
<input
type="password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full rounded border border-stone-300 px-3 py-2"
/>
</label>
{error && (
<p className="text-sm text-rose-700 bg-rose-50 border border-rose-200 rounded p-2">
{error}
</p>
)}
<button
type="submit"
disabled={loading || !email || !password}
className="w-full bg-stone-800 text-cream rounded-full py-3 disabled:bg-stone-400 hover:bg-stone-700 transition"
>
{loading ? 'Entrando…' : 'Entrar'}
</button>
<Link
to="/"
className="block text-center text-xs text-stone-500 hover:text-stone-700"
>
Voltar pra página pública
</Link>
</form>
</main>
);
}