feat: sessão #3 — lib (db/auth/email/validations), API routes, NextAuth v5, middleware, páginas account/shelters/shelter-dashboard, Prisma v7 fix
This commit is contained in:
176
app/main/shelters/page.tsx
Normal file
176
app/main/shelters/page.tsx
Normal file
@@ -0,0 +1,176 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { MapPin, Phone, Mail, Clock, PawPrint, ChevronRight } from 'lucide-react';
|
||||
import Header from '@/components/layout/Header';
|
||||
import Footer from '@/components/layout/Footer';
|
||||
|
||||
interface Shelter {
|
||||
id: string;
|
||||
name: string;
|
||||
district: string;
|
||||
address: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
description: string | null;
|
||||
website: string | null;
|
||||
openHours: Record<string, string | null>;
|
||||
_count: { animals: number };
|
||||
}
|
||||
|
||||
const DAY_LABELS: Record<string, string> = {
|
||||
mon: 'Seg', tue: 'Ter', wed: 'Qua',
|
||||
thu: 'Qui', fri: 'Sex', sat: 'Sáb', sun: 'Dom',
|
||||
};
|
||||
|
||||
export default function SheltersPage() {
|
||||
const [shelters, setShelters] = useState<Shelter[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/shelters')
|
||||
.then((r) => r.json())
|
||||
.then((data) => setShelters(Array.isArray(data) ? data : []))
|
||||
.catch(console.error)
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main style={{ flex: 1 }}>
|
||||
{/* Hero */}
|
||||
<section style={{ padding: 'var(--space-9) 0 var(--space-7)', background: 'var(--cream)', borderBottom: '1px solid var(--parchment)' }}>
|
||||
<div className="container">
|
||||
<p className="eyebrow" style={{ marginBottom: '10px' }}>Portugal</p>
|
||||
<h1 style={{ fontFamily: 'var(--font-display)', fontWeight: 900, fontSize: 'clamp(36px, 6vw, 60px)', color: 'var(--soil)', letterSpacing: '-0.03em', lineHeight: 1.05, marginBottom: 'var(--space-4)' }}>
|
||||
Canis <em style={{ fontStyle: 'italic', color: 'var(--terra)' }}>parceiros.</em>
|
||||
</h1>
|
||||
<p style={{ fontFamily: 'var(--font-body)', fontSize: '18px', color: 'var(--soil-mid)', maxWidth: '520px', lineHeight: 1.65 }}>
|
||||
Organizações verificadas em todo o país que trabalham diariamente para encontrar lares a animais abandonados.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Listagem */}
|
||||
<section style={{ padding: 'var(--space-8) 0', background: 'var(--cream)' }}>
|
||||
<div className="container">
|
||||
{loading ? (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))', gap: 'var(--space-5)' }}>
|
||||
{[1,2,3,4].map(i => (
|
||||
<div key={i} className="skeleton-card">
|
||||
<div className="skeleton skeleton-image" style={{ height: '120px' }} />
|
||||
<div className="skeleton skeleton-title" />
|
||||
<div className="skeleton skeleton-sub" />
|
||||
<div className="skeleton skeleton-location" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : shelters.length === 0 ? (
|
||||
<div style={{ textAlign: 'center', padding: 'var(--space-9) 0' }}>
|
||||
<PawPrint size={48} style={{ margin: '0 auto 16px', opacity: 0.2, color: 'var(--soil)' }} />
|
||||
<p style={{ fontFamily: 'var(--font-body)', fontSize: '18px', color: 'var(--soil-mid)' }}>
|
||||
Nenhum canil disponível de momento.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(340px, 1fr))', gap: 'var(--space-5)' }}>
|
||||
{shelters.map((shelter) => (
|
||||
<article
|
||||
key={shelter.id}
|
||||
style={{
|
||||
background: 'var(--linen)',
|
||||
border: '1px solid var(--parchment)',
|
||||
borderRadius: 'var(--radius-lg)',
|
||||
overflow: 'hidden',
|
||||
boxShadow: 'var(--shadow-warm-sm)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
transition: 'transform 220ms var(--ease-spring), box-shadow 220ms ease',
|
||||
animation: 'cardReveal 500ms cubic-bezier(0.22, 1, 0.36, 1) both',
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
const el = e.currentTarget as HTMLElement;
|
||||
el.style.transform = 'translateY(-4px)';
|
||||
el.style.boxShadow = 'var(--shadow-warm-lg)';
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
const el = e.currentTarget as HTMLElement;
|
||||
el.style.transform = 'translateY(0)';
|
||||
el.style.boxShadow = 'var(--shadow-warm-sm)';
|
||||
}}
|
||||
>
|
||||
{/* Header colorido */}
|
||||
<div style={{ background: 'var(--terra-glow)', borderBottom: '1px solid var(--parchment)', padding: '20px 20px 16px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
||||
<div style={{ width: '40px', height: '40px', borderRadius: '12px', background: 'var(--terra)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
||||
<PawPrint size={18} style={{ color: 'white' }} />
|
||||
</div>
|
||||
<div>
|
||||
<h2 style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: '16px', color: 'var(--soil)', lineHeight: 1.2 }}>{shelter.name}</h2>
|
||||
<p style={{ fontFamily: 'var(--font-accent)', fontSize: '10px', color: 'var(--soil-faint)', letterSpacing: '0.08em', textTransform: 'uppercase', marginTop: '2px' }}>{shelter.district}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ textAlign: 'right' }}>
|
||||
<div style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: '22px', color: 'var(--terra)', lineHeight: 1 }}>{shelter._count.animals}</div>
|
||||
<div style={{ fontFamily: 'var(--font-accent)', fontSize: '9px', color: 'var(--soil-faint)', textTransform: 'uppercase', letterSpacing: '0.08em' }}>disponíveis</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div style={{ padding: '16px 20px', display: 'flex', flexDirection: 'column', gap: '10px', flex: 1 }}>
|
||||
{shelter.description && (
|
||||
<p style={{ fontFamily: 'var(--font-body)', fontStyle: 'italic', fontSize: '14px', color: 'var(--soil-mid)', lineHeight: 1.6 }}>
|
||||
{shelter.description.length > 120 ? `${shelter.description.slice(0, 120)}…` : shelter.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<MapPin size={13} style={{ color: 'var(--terra)', flexShrink: 0 }} />
|
||||
<span style={{ fontFamily: 'var(--font-body)', fontStyle: 'italic', fontSize: '13px', color: 'var(--soil-mid)' }}>{shelter.address}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<Phone size={13} style={{ color: 'var(--soil-faint)', flexShrink: 0 }} />
|
||||
<a href={`tel:${shelter.phone}`} style={{ fontFamily: 'var(--font-body)', fontSize: '13px', color: 'var(--soil-mid)', textDecoration: 'none' }}>{shelter.phone}</a>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<Mail size={13} style={{ color: 'var(--soil-faint)', flexShrink: 0 }} />
|
||||
<a href={`mailto:${shelter.email}`} style={{ fontFamily: 'var(--font-body)', fontSize: '13px', color: 'var(--terra)', textDecoration: 'none' }}>{shelter.email}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1 }} />
|
||||
|
||||
<hr style={{ border: 'none', borderTop: '1px solid var(--parchment)', margin: '4px 0' }} />
|
||||
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<Link
|
||||
href={`/main/animals?district=${encodeURIComponent(shelter.district)}`}
|
||||
className="btn btn-secondary"
|
||||
style={{ flex: 1, justifyContent: 'center', padding: '10px 12px', fontSize: '11px', minHeight: '40px' }}
|
||||
>
|
||||
Ver animais
|
||||
</Link>
|
||||
<Link
|
||||
href={`/main/donate?shelterId=${shelter.id}`}
|
||||
className="btn btn-primary"
|
||||
style={{ flex: 1, justifyContent: 'center', padding: '10px 12px', fontSize: '11px', minHeight: '40px', gap: '4px' }}
|
||||
>
|
||||
Doar
|
||||
<ChevronRight size={12} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user