Apply La Huasca brand colors across layout, nav, login, admin, user and home

This commit is contained in:
2026-06-27 16:42:34 -05:00
parent cc59177a74
commit 8952bbf555
7 changed files with 321 additions and 109 deletions
+58 -20
View File
@@ -10,6 +10,10 @@ interface ImageRecord {
uploaded_at: string; uploaded_at: string;
} }
const gold = '#E8A849';
const navy = '#010D1E';
const warm = '#a6683c';
export default function AdminPage() { export default function AdminPage() {
const router = useRouter(); const router = useRouter();
const [images, setImages] = useState<ImageRecord[]>([]); const [images, setImages] = useState<ImageRecord[]>([]);
@@ -73,25 +77,47 @@ export default function AdminPage() {
} }
}; };
if (loading) return <main style={{ padding: '2rem' }}><p>Loading...</p></main>; if (loading) {
return (
<main style={{ padding: '2rem', color: warm, fontSize: '18px' }}>
<p>Loading gallery...</p>
</main>
);
}
return ( return (
<main style={{ padding: '2rem' }}> <main style={{ padding: '2rem', maxWidth: '72rem', margin: '0 auto' }}>
<h1>Admin Portal</h1> <h1 style={{ fontSize: 'clamp(28px, 4vw, 42px)', fontWeight: 400, fontStyle: 'italic', color: navy, margin: '0 0 0.5rem' }}>
<p>Total uploaded images: <strong>{count}</strong></p> Admin Portal
{error && <p style={{ color: 'crimson' }}>{error}</p>} </h1>
<p style={{ color: '#555', marginBottom: '1.5rem' }}>
Total uploaded images: <strong style={{ color: warm }}>{count}</strong>
</p>
{error && <p style={{ color: '#c23b22', marginBottom: '1rem' }}>{error}</p>}
<label <label
style={{ style={{
display: 'inline-block', display: 'inline-flex',
padding: '0.75rem 1rem', alignItems: 'center',
background: '#1a1a1a', gap: '0.5rem',
color: '#fff', padding: '0.75rem 1.25rem',
borderRadius: '4px', background: gold,
color: navy,
borderRadius: '999px',
cursor: 'pointer', cursor: 'pointer',
marginBottom: '1rem', fontWeight: 600,
marginBottom: '1.5rem',
transition: 'transform .12s, box-shadow .15s',
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = 'translateY(-1px)';
e.currentTarget.style.boxShadow = '0 6px 18px rgba(232,168,73,0.35)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'translateY(0)';
e.currentTarget.style.boxShadow = 'none';
}} }}
> >
Upload images + Upload images
<input <input
type="file" type="file"
accept="image/*" accept="image/*"
@@ -101,39 +127,51 @@ export default function AdminPage() {
/> />
</label> </label>
{images.length === 0 ? ( {images.length === 0 ? (
<p style={{ color: 'dimgray' }}>No images uploaded yet.</p> <p style={{ color: '#777' }}>No images uploaded yet.</p>
) : ( ) : (
<div <div
style={{ style={{
display: 'grid', display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
gap: '1rem', gap: '1.25rem',
}} }}
> >
{images.map((img) => ( {images.map((img) => (
<div <div
key={img.id} key={img.id}
style={{ style={{
border: '1px solid #ddd', borderRadius: '16px',
borderRadius: '8px',
overflow: 'hidden', overflow: 'hidden',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
background: '#f5f0e8',
boxShadow: '0 1px 2px rgba(1,13,30,0.08)',
transition: 'transform .2s, box-shadow .2s',
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = 'translateY(-2px)';
e.currentTarget.style.boxShadow = '0 6px 18px rgba(14,47,71,0.16)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'translateY(0)';
e.currentTarget.style.boxShadow = '0 1px 2px rgba(1,13,30,0.08)';
}} }}
> >
<img <img
src={img.data} src={img.data}
alt={img.filename} alt={img.filename}
style={{ width: '100%', height: '160px', objectFit: 'cover' }} style={{ width: '100%', height: '180px', objectFit: 'cover' }}
/> />
<button <button
onClick={() => handleDelete(img.id)} onClick={() => handleDelete(img.id)}
style={{ style={{
padding: '0.5rem', padding: '0.65rem',
background: 'crimson', background: '#c23b22',
color: '#fff', color: '#fff',
border: 'none', border: 'none',
cursor: 'pointer', cursor: 'pointer',
fontSize: '14px',
fontWeight: 600,
}} }}
> >
Delete Delete
+1 -1
View File
@@ -12,7 +12,7 @@ export default function RootLayout({
}) { }) {
return ( return (
<html lang="en"> <html lang="en">
<body style={{ margin: 0, fontFamily: 'system-ui, sans-serif' }}> <body style={{ margin: 0, fontFamily: 'system-ui, sans-serif', background: '#FAF7F0', color: '#1a1a1a' }}>
<Navbar /> <Navbar />
{children} {children}
</body> </body>
+73 -18
View File
@@ -30,58 +30,113 @@ export default function LoginPage() {
router.push(data.role === 'admin' ? '/admin' : '/user'); router.push(data.role === 'admin' ? '/admin' : '/user');
}; };
const accent = '#E8A849';
const navy = '#010D1E';
const cream = '#f5f0e8';
return ( return (
<main style={{ padding: '2rem', maxWidth: '28rem', margin: '0 auto' }}> <main
<h1 style={{ marginBottom: '1rem' }}>Login</h1> style={{
minHeight: 'calc(100vh - 56px)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '2rem',
background: '#FAF7F0',
}}
>
<form <form
onSubmit={handleSubmit} onSubmit={handleSubmit}
style={{ style={{
width: '100%',
maxWidth: '26rem',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: '1rem', gap: '1.25rem',
padding: '1.5rem', padding: '2.5rem',
border: '1px solid #ddd', background: cream,
borderRadius: '8px', borderRadius: '16px',
boxShadow: '0 6px 18px rgba(14,47,71,0.12)',
}} }}
> >
<label style={{ display: 'flex', flexDirection: 'column', gap: '0.25rem' }}> <h1
style={{
margin: '0 0 0.25rem',
fontSize: 'clamp(28px, 4vw, 40px)',
fontWeight: 400,
fontStyle: 'italic',
color: navy,
}}
>
Welcome back
</h1>
<p style={{ margin: '0 0 1rem', color: '#555' }}>Log in to your portal.</p>
<label style={{ display: 'flex', flexDirection: 'column', gap: '0.35rem', fontSize: '12px', letterSpacing: '0.06em', textTransform: 'uppercase', color: '#555' }}>
Username Username
<input <input
type="text" type="text"
value={username} value={username}
onChange={(e) => setUsername(e.target.value)} onChange={(e) => setUsername(e.target.value)}
style={{ padding: '0.5rem', borderRadius: '4px', border: '1px solid #ccc' }} style={{
padding: '0.75rem 0.9rem',
borderRadius: '12px',
border: '1px solid rgba(1,13,30,0.18)',
background: '#fff',
fontSize: '15px',
color: navy,
outline: 'none',
}}
required required
/> />
</label> </label>
<label style={{ display: 'flex', flexDirection: 'column', gap: '0.25rem' }}> <label style={{ display: 'flex', flexDirection: 'column', gap: '0.35rem', fontSize: '12px', letterSpacing: '0.06em', textTransform: 'uppercase', color: '#555' }}>
Password Password
<input <input
type="password" type="password"
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
style={{ padding: '0.5rem', borderRadius: '4px', border: '1px solid #ccc' }} style={{
padding: '0.75rem 0.9rem',
borderRadius: '12px',
border: '1px solid rgba(1,13,30,0.18)',
background: '#fff',
fontSize: '15px',
color: navy,
outline: 'none',
}}
required required
/> />
</label> </label>
{error && <p style={{ color: 'crimson', margin: 0 }}>{error}</p>} {error && <p style={{ color: '#c23b22', margin: 0, fontSize: '14px' }}>{error}</p>}
<button <button
type="submit" type="submit"
style={{ style={{
padding: '0.75rem', padding: '0.85rem',
background: '#1a1a1a', background: accent,
color: '#fff', color: navy,
border: 'none', border: 'none',
borderRadius: '4px', borderRadius: '999px',
cursor: 'pointer', cursor: 'pointer',
fontSize: '15px',
fontWeight: 600,
transition: 'transform .12s, box-shadow .15s',
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = 'translateY(-1px)';
e.currentTarget.style.boxShadow = '0 6px 18px rgba(232,168,73,0.35)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'translateY(0)';
e.currentTarget.style.boxShadow = 'none';
}} }}
> >
Log in Log in
</button> </button>
<p style={{ margin: '0.5rem 0 0', fontSize: '13px', color: '#777', textAlign: 'center' }}>
Demo accounts: <strong style={{ color: navy }}>admin</strong> / admin and <strong style={{ color: navy }}>user</strong> / user.
</p>
</form> </form>
<p style={{ marginTop: '1rem', color: 'dimgray' }}>
Demo accounts: <strong>admin</strong> / admin and <strong>user</strong> / user.
</p>
</main> </main>
); );
} }
+43 -3
View File
@@ -1,10 +1,50 @@
import MeasuredText from '@/components/MeasuredText'; import MeasuredText from '@/components/MeasuredText';
const gold = '#E8A849';
const navy = '#010D1E';
const cream = '#f5f0e8';
export default function HomePage() { export default function HomePage() {
return ( return (
<main style={{ padding: '2rem' }}> <main
<h1>La Huasca</h1> style={{
<p>Welcome to the TypeScript + Next.js version of La Huasca.</p> padding: 'clamp(3rem, 9vw, 7rem) 2rem',
minHeight: 'calc(100vh - 56px)',
background: navy,
color: cream,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
textAlign: 'center',
}}
>
<p
style={{
fontFamily: 'monospace',
fontSize: '12px',
letterSpacing: '0.18em',
textTransform: 'uppercase',
color: gold,
margin: '0 0 1rem',
}}
>
Hotel · Restaurant · Vineyard
</p>
<h1
style={{
fontSize: 'clamp(44px, 7vw, 84px)',
fontWeight: 400,
fontStyle: 'italic',
lineHeight: 1.08,
margin: '0 0 1.25rem',
color: cream,
}}
>
La Huasca
</h1>
<p style={{ fontSize: '18px', lineHeight: 1.55, color: 'rgba(245,240,232,0.78)', maxWidth: '52ch', margin: '0 0 2rem' }}>
Welcome to the TypeScript + Next.js version of La Huasca.
</p>
<MeasuredText text="Hello, Pretext!" /> <MeasuredText text="Hello, Pretext!" />
</main> </main>
); );
+97 -42
View File
@@ -31,6 +31,11 @@ interface PortalData {
wifiPassword: string; wifiPassword: string;
} }
const gold = '#E8A849';
const navy = '#010D1E';
const cream = '#f5f0e8';
const warm = '#a6683c';
export default function UserPage() { export default function UserPage() {
const router = useRouter(); const router = useRouter();
const [data, setData] = useState<PortalData | null>(null); const [data, setData] = useState<PortalData | null>(null);
@@ -53,94 +58,144 @@ export default function UserPage() {
.finally(() => setLoading(false)); .finally(() => setLoading(false));
}, [router]); }, [router]);
if (loading) return <main style={{ padding: '2rem' }}><p>Loading...</p></main>; if (loading) {
return (
<main style={{ padding: '2rem', color: warm, fontSize: '18px' }}>
<p>Loading your portal...</p>
</main>
);
}
if (!data) { if (!data) {
return <main style={{ padding: '2rem' }}><p>Please log in as user to access this page.</p></main>; return <main style={{ padding: '2rem' }}><p>Please log in as user to access this page.</p></main>;
} }
return ( return (
<main style={{ padding: '2rem', maxWidth: '48rem', margin: '0 auto' }}> <main style={{ padding: '2rem', maxWidth: '56rem', margin: '0 auto' }}>
<h1>User Portal</h1> <h1 style={{ fontSize: 'clamp(28px, 4vw, 42px)', fontWeight: 400, fontStyle: 'italic', color: navy, margin: '0 0 2rem' }}>
Member Portal
</h1>
<section style={{ marginBottom: '2rem' }}> <Section title="Future Reservations">
<h2>Future Reservations</h2>
{data.reservations.length === 0 ? ( {data.reservations.length === 0 ? (
<p style={{ color: 'dimgray' }}>No upcoming reservations.</p> <p style={{ color: '#777' }}>No upcoming reservations.</p>
) : ( ) : (
<ul style={{ padding: 0, listStyle: 'none' }}> <ul style={{ padding: 0, listStyle: 'none', display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
{data.reservations.map((r) => ( {data.reservations.map((r) => (
<li <li
key={r.id} key={r.id}
style={{ style={{
padding: '1rem', padding: '1rem 1.25rem',
marginBottom: '0.5rem', background: cream,
border: '1px solid #ddd', borderRadius: '16px',
borderRadius: '8px', boxShadow: '0 1px 2px rgba(1,13,30,0.08)',
color: navy,
}} }}
> >
{r.date} at {r.time} · {r.guests} guests · {r.table_name} <strong style={{ color: warm }}>{r.date}</strong> at {r.time} · {r.guests} guests · {r.table_name}
</li> </li>
))} ))}
</ul> </ul>
)} )}
</section> </Section>
<section style={{ marginBottom: '2rem' }}> <Section title="Discount Coupons">
<h2>Discount Coupons</h2>
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}> <div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
{data.coupons.map((c) => ( {data.coupons.map((c) => (
<div <div
key={c.id} key={c.id}
style={{ style={{
padding: '1rem', padding: '1.25rem',
border: '2px dashed #1a1a1a', background: cream,
borderRadius: '8px', border: '2px dashed ' + warm,
minWidth: '12rem', borderRadius: '16px',
minWidth: '13rem',
color: navy,
}} }}
> >
<strong>{c.code}</strong> <strong style={{ fontSize: '18px', color: warm }}>{c.code}</strong>
<p style={{ margin: '0.5rem 0 0', color: 'dimgray' }}>{c.discount}</p> <p style={{ margin: '0.5rem 0 0', color: '#555', fontSize: '14px' }}>{c.discount}</p>
</div> </div>
))} ))}
</div> </div>
</section> </Section>
<section style={{ marginBottom: '2rem' }}> <Section title="Offers">
<h2>Offers</h2> <ul style={{ padding: 0, listStyle: 'none', display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
<ul>
{data.offers.map((o) => ( {data.offers.map((o) => (
<li key={o.id}> <li
<strong>{o.title}</strong> {o.description} key={o.id}
style={{
padding: '1rem 1.25rem',
background: cream,
borderRadius: '16px',
color: navy,
}}
>
<strong style={{ color: warm }}>{o.title}</strong> {o.description}
</li> </li>
))} ))}
</ul> </ul>
</section> </Section>
<section style={{ marginBottom: '2rem' }}> <Section title="WiFi Password">
<h2>WiFi Password</h2> <div
<p
style={{ style={{
display: 'inline-block', display: 'inline-block',
padding: '0.75rem 1.5rem', padding: '0.9rem 2rem',
background: '#f4f4f4', background: navy,
borderRadius: '8px', color: cream,
borderRadius: '12px',
fontFamily: 'monospace', fontFamily: 'monospace',
fontSize: '1.25rem', fontSize: '1.4rem',
letterSpacing: '0.04em',
boxShadow: '0 4px 12px rgba(1,13,30,0.2)',
}} }}
> >
{data.wifiPassword} {data.wifiPassword}
</p> </div>
</section> </Section>
<section> <Section title="Member Benefits">
<h2>Member Benefits</h2> <ul style={{ padding: 0, listStyle: 'none', display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))', gap: '0.75rem' }}>
<ul>
{data.benefits.map((b) => ( {data.benefits.map((b) => (
<li key={b}>{b}</li> <li
key={b}
style={{
padding: '0.9rem 1.1rem',
background: '#e8eff3',
borderLeft: '4px solid ' + gold,
borderRadius: '0 12px 12px 0',
color: navy,
}}
>
{b}
</li>
))} ))}
</ul> </ul>
</section> </Section>
</main> </main>
); );
} }
function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
<section style={{ marginBottom: '2.5rem' }}>
<h2
style={{
fontSize: '20px',
fontWeight: 600,
letterSpacing: '-0.01em',
color: navy,
margin: '0 0 1rem',
paddingBottom: '0.4rem',
borderBottom: '1.5px solid ' + warm,
display: 'inline-block',
}}
>
{title}
</h2>
{children}
</section>
);
}
+2 -2
View File
@@ -24,11 +24,11 @@ export default function MeasuredText({ text }: MeasuredTextProps) {
return ( return (
<div> <div>
<span ref={ref} style={{ fontSize: '2rem', fontWeight: 700 }}> <span ref={ref} style={{ fontSize: '2rem', fontWeight: 700, color: '#E8A849' }}>
{text} {text}
</span> </span>
{height !== null && ( {height !== null && (
<p style={{ color: 'dimgray' }}>Measured height: {height.toFixed(2)}px</p> <p style={{ color: 'rgba(245,240,232,0.6)' }}>Measured height: {height.toFixed(2)}px</p>
)} )}
</div> </div>
); );
+47 -23
View File
@@ -1,6 +1,7 @@
'use client'; 'use client';
import Link from 'next/link'; import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -15,6 +16,7 @@ const links = [
export default function Navbar() { export default function Navbar() {
const [auth, setAuth] = useState<{ authenticated: boolean; role?: string } | null>(null); const [auth, setAuth] = useState<{ authenticated: boolean; role?: string } | null>(null);
const router = useRouter(); const router = useRouter();
const pathname = usePathname();
useEffect(() => { useEffect(() => {
fetch('/api/auth/me', { credentials: 'same-origin' }) fetch('/api/auth/me', { credentials: 'same-origin' })
@@ -29,28 +31,46 @@ export default function Navbar() {
router.push('/login'); router.push('/login');
}; };
const navStyle: React.CSSProperties = {
position: 'sticky',
top: 0,
display: 'flex',
gap: '1.5rem',
alignItems: 'center',
padding: '0.75rem 1.5rem',
background: '#010D1E',
color: '#f5f0e8',
boxShadow: '0 2px 8px rgba(1,13,30,0.25)',
zIndex: 50,
};
const linkBase: React.CSSProperties = {
color: 'rgba(245,240,232,0.85)',
textDecoration: 'none',
fontSize: '14px',
padding: '6px 0',
borderBottom: '1.5px solid transparent',
transition: 'color .15s, border-color .15s',
};
const activeLink: React.CSSProperties = {
color: '#E8A849',
borderBottomColor: '#E8A849',
};
return ( return (
<nav <nav style={navStyle}>
style={{ <Link href="/" style={{ fontWeight: 700, color: '#E8A849', textDecoration: 'none', fontSize: '20px', fontStyle: 'italic' }}>
position: 'sticky',
top: 0,
display: 'flex',
gap: '1.5rem',
alignItems: 'center',
padding: '1rem 2rem',
background: '#1a1a1a',
color: '#fff',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
}}
>
<Link href="/" style={{ fontWeight: 700, color: '#fff', textDecoration: 'none' }}>
La Huasca La Huasca
</Link> </Link>
{links.map((link) => ( {links.map((link) => (
<Link <Link
key={link.href} key={link.href}
href={link.href} href={link.href}
style={{ color: '#e5e5e5', textDecoration: 'none' }} style={{
...linkBase,
...(pathname === link.href ? activeLink : {}),
}}
> >
{link.label} {link.label}
</Link> </Link>
@@ -61,11 +81,13 @@ export default function Navbar() {
style={{ style={{
marginLeft: 'auto', marginLeft: 'auto',
background: 'transparent', background: 'transparent',
border: '1px solid #fff', border: '1px solid #E8A849',
color: '#fff', color: '#E8A849',
padding: '0.4rem 0.75rem', padding: '0.4rem 0.9rem',
borderRadius: '4px', borderRadius: '999px',
cursor: 'pointer', cursor: 'pointer',
fontSize: '13px',
fontWeight: 600,
}} }}
> >
Logout Logout
@@ -75,11 +97,13 @@ export default function Navbar() {
href="/login" href="/login"
style={{ style={{
marginLeft: 'auto', marginLeft: 'auto',
color: '#e5e5e5', color: '#010D1E',
textDecoration: 'none', textDecoration: 'none',
border: '1px solid #e5e5e5', background: '#E8A849',
padding: '0.4rem 0.75rem', padding: '0.45rem 0.9rem',
borderRadius: '4px', borderRadius: '999px',
fontSize: '13px',
fontWeight: 600,
}} }}
> >
Login Login