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;
}
const gold = '#E8A849';
const navy = '#010D1E';
const warm = '#a6683c';
export default function AdminPage() {
const router = useRouter();
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 (
<main style={{ padding: '2rem' }}>
<h1>Admin Portal</h1>
<p>Total uploaded images: <strong>{count}</strong></p>
{error && <p style={{ color: 'crimson' }}>{error}</p>}
<main style={{ padding: '2rem', maxWidth: '72rem', margin: '0 auto' }}>
<h1 style={{ fontSize: 'clamp(28px, 4vw, 42px)', fontWeight: 400, fontStyle: 'italic', color: navy, margin: '0 0 0.5rem' }}>
Admin Portal
</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
style={{
display: 'inline-block',
padding: '0.75rem 1rem',
background: '#1a1a1a',
color: '#fff',
borderRadius: '4px',
display: 'inline-flex',
alignItems: 'center',
gap: '0.5rem',
padding: '0.75rem 1.25rem',
background: gold,
color: navy,
borderRadius: '999px',
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
type="file"
accept="image/*"
@@ -101,39 +127,51 @@ export default function AdminPage() {
/>
</label>
{images.length === 0 ? (
<p style={{ color: 'dimgray' }}>No images uploaded yet.</p>
<p style={{ color: '#777' }}>No images uploaded yet.</p>
) : (
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))',
gap: '1rem',
gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
gap: '1.25rem',
}}
>
{images.map((img) => (
<div
key={img.id}
style={{
border: '1px solid #ddd',
borderRadius: '8px',
borderRadius: '16px',
overflow: 'hidden',
display: 'flex',
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
src={img.data}
alt={img.filename}
style={{ width: '100%', height: '160px', objectFit: 'cover' }}
style={{ width: '100%', height: '180px', objectFit: 'cover' }}
/>
<button
onClick={() => handleDelete(img.id)}
style={{
padding: '0.5rem',
background: 'crimson',
padding: '0.65rem',
background: '#c23b22',
color: '#fff',
border: 'none',
cursor: 'pointer',
fontSize: '14px',
fontWeight: 600,
}}
>
Delete
+1 -1
View File
@@ -12,7 +12,7 @@ export default function RootLayout({
}) {
return (
<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 />
{children}
</body>
+73 -18
View File
@@ -30,58 +30,113 @@ export default function LoginPage() {
router.push(data.role === 'admin' ? '/admin' : '/user');
};
const accent = '#E8A849';
const navy = '#010D1E';
const cream = '#f5f0e8';
return (
<main style={{ padding: '2rem', maxWidth: '28rem', margin: '0 auto' }}>
<h1 style={{ marginBottom: '1rem' }}>Login</h1>
<main
style={{
minHeight: 'calc(100vh - 56px)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '2rem',
background: '#FAF7F0',
}}
>
<form
onSubmit={handleSubmit}
style={{
width: '100%',
maxWidth: '26rem',
display: 'flex',
flexDirection: 'column',
gap: '1rem',
padding: '1.5rem',
border: '1px solid #ddd',
borderRadius: '8px',
gap: '1.25rem',
padding: '2.5rem',
background: cream,
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
<input
type="text"
value={username}
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
/>
</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
<input
type="password"
value={password}
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
/>
</label>
{error && <p style={{ color: 'crimson', margin: 0 }}>{error}</p>}
{error && <p style={{ color: '#c23b22', margin: 0, fontSize: '14px' }}>{error}</p>}
<button
type="submit"
style={{
padding: '0.75rem',
background: '#1a1a1a',
color: '#fff',
padding: '0.85rem',
background: accent,
color: navy,
border: 'none',
borderRadius: '4px',
borderRadius: '999px',
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
</button>
</form>
<p style={{ marginTop: '1rem', color: 'dimgray' }}>
Demo accounts: <strong>admin</strong> / admin and <strong>user</strong> / user.
<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>
</main>
);
}
+43 -3
View File
@@ -1,10 +1,50 @@
import MeasuredText from '@/components/MeasuredText';
const gold = '#E8A849';
const navy = '#010D1E';
const cream = '#f5f0e8';
export default function HomePage() {
return (
<main style={{ padding: '2rem' }}>
<h1>La Huasca</h1>
<p>Welcome to the TypeScript + Next.js version of La Huasca.</p>
<main
style={{
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!" />
</main>
);
+97 -42
View File
@@ -31,6 +31,11 @@ interface PortalData {
wifiPassword: string;
}
const gold = '#E8A849';
const navy = '#010D1E';
const cream = '#f5f0e8';
const warm = '#a6683c';
export default function UserPage() {
const router = useRouter();
const [data, setData] = useState<PortalData | null>(null);
@@ -53,94 +58,144 @@ export default function UserPage() {
.finally(() => setLoading(false));
}, [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) {
return <main style={{ padding: '2rem' }}><p>Please log in as user to access this page.</p></main>;
}
return (
<main style={{ padding: '2rem', maxWidth: '48rem', margin: '0 auto' }}>
<h1>User Portal</h1>
<main style={{ padding: '2rem', maxWidth: '56rem', margin: '0 auto' }}>
<h1 style={{ fontSize: 'clamp(28px, 4vw, 42px)', fontWeight: 400, fontStyle: 'italic', color: navy, margin: '0 0 2rem' }}>
Member Portal
</h1>
<section style={{ marginBottom: '2rem' }}>
<h2>Future Reservations</h2>
<Section title="Future Reservations">
{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) => (
<li
key={r.id}
style={{
padding: '1rem',
marginBottom: '0.5rem',
border: '1px solid #ddd',
borderRadius: '8px',
padding: '1rem 1.25rem',
background: cream,
borderRadius: '16px',
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>
))}
</ul>
)}
</section>
</Section>
<section style={{ marginBottom: '2rem' }}>
<h2>Discount Coupons</h2>
<Section title="Discount Coupons">
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
{data.coupons.map((c) => (
<div
key={c.id}
style={{
padding: '1rem',
border: '2px dashed #1a1a1a',
borderRadius: '8px',
minWidth: '12rem',
padding: '1.25rem',
background: cream,
border: '2px dashed ' + warm,
borderRadius: '16px',
minWidth: '13rem',
color: navy,
}}
>
<strong>{c.code}</strong>
<p style={{ margin: '0.5rem 0 0', color: 'dimgray' }}>{c.discount}</p>
<strong style={{ fontSize: '18px', color: warm }}>{c.code}</strong>
<p style={{ margin: '0.5rem 0 0', color: '#555', fontSize: '14px' }}>{c.discount}</p>
</div>
))}
</div>
</section>
</Section>
<section style={{ marginBottom: '2rem' }}>
<h2>Offers</h2>
<ul>
<Section title="Offers">
<ul style={{ padding: 0, listStyle: 'none', display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
{data.offers.map((o) => (
<li key={o.id}>
<strong>{o.title}</strong> {o.description}
<li
key={o.id}
style={{
padding: '1rem 1.25rem',
background: cream,
borderRadius: '16px',
color: navy,
}}
>
<strong style={{ color: warm }}>{o.title}</strong> {o.description}
</li>
))}
</ul>
</section>
</Section>
<section style={{ marginBottom: '2rem' }}>
<h2>WiFi Password</h2>
<p
<Section title="WiFi Password">
<div
style={{
display: 'inline-block',
padding: '0.75rem 1.5rem',
background: '#f4f4f4',
borderRadius: '8px',
padding: '0.9rem 2rem',
background: navy,
color: cream,
borderRadius: '12px',
fontFamily: 'monospace',
fontSize: '1.25rem',
fontSize: '1.4rem',
letterSpacing: '0.04em',
boxShadow: '0 4px 12px rgba(1,13,30,0.2)',
}}
>
{data.wifiPassword}
</p>
</section>
</div>
</Section>
<section>
<h2>Member Benefits</h2>
<ul>
<Section title="Member Benefits">
<ul style={{ padding: 0, listStyle: 'none', display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))', gap: '0.75rem' }}>
{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>
</section>
</Section>
</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 (
<div>
<span ref={ref} style={{ fontSize: '2rem', fontWeight: 700 }}>
<span ref={ref} style={{ fontSize: '2rem', fontWeight: 700, color: '#E8A849' }}>
{text}
</span>
{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>
);
+43 -19
View File
@@ -1,6 +1,7 @@
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
@@ -15,6 +16,7 @@ const links = [
export default function Navbar() {
const [auth, setAuth] = useState<{ authenticated: boolean; role?: string } | null>(null);
const router = useRouter();
const pathname = usePathname();
useEffect(() => {
fetch('/api/auth/me', { credentials: 'same-origin' })
@@ -29,28 +31,46 @@ export default function Navbar() {
router.push('/login');
};
return (
<nav
style={{
const navStyle: React.CSSProperties = {
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' }}>
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 (
<nav style={navStyle}>
<Link href="/" style={{ fontWeight: 700, color: '#E8A849', textDecoration: 'none', fontSize: '20px', fontStyle: 'italic' }}>
La Huasca
</Link>
{links.map((link) => (
<Link
key={link.href}
href={link.href}
style={{ color: '#e5e5e5', textDecoration: 'none' }}
style={{
...linkBase,
...(pathname === link.href ? activeLink : {}),
}}
>
{link.label}
</Link>
@@ -61,11 +81,13 @@ export default function Navbar() {
style={{
marginLeft: 'auto',
background: 'transparent',
border: '1px solid #fff',
color: '#fff',
padding: '0.4rem 0.75rem',
borderRadius: '4px',
border: '1px solid #E8A849',
color: '#E8A849',
padding: '0.4rem 0.9rem',
borderRadius: '999px',
cursor: 'pointer',
fontSize: '13px',
fontWeight: 600,
}}
>
Logout
@@ -75,11 +97,13 @@ export default function Navbar() {
href="/login"
style={{
marginLeft: 'auto',
color: '#e5e5e5',
color: '#010D1E',
textDecoration: 'none',
border: '1px solid #e5e5e5',
padding: '0.4rem 0.75rem',
borderRadius: '4px',
background: '#E8A849',
padding: '0.45rem 0.9rem',
borderRadius: '999px',
fontSize: '13px',
fontWeight: 600,
}}
>
Login