Add database integration, auth API, admin/user portals, and deploy-db.sh

This commit is contained in:
2026-06-27 16:40:58 -05:00
parent 26559d4cf8
commit cc59177a74
25 changed files with 1423 additions and 118 deletions
+56 -42
View File
@@ -1,48 +1,62 @@
'use client';
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { useAuth } from '@/hooks/useAuth';
const reservations = [
{ id: 1, date: '2026-07-12', time: '19:00', guests: 4, table: 'Terrace 3' },
{ id: 2, date: '2026-07-25', time: '20:30', guests: 2, table: 'Barrel 7' },
];
interface Reservation {
id: number;
date: string;
time: string;
guests: number;
table_name: string;
}
const coupons = [
{ code: 'WELCOME20', discount: '20% off your next stay' },
{ code: 'COFFEE5', discount: '$5 off any coffee tasting' },
];
interface Coupon {
id: number;
code: string;
discount: string;
}
const offers = [
{ title: 'Summer Wine Weekend', desc: 'Complimentary wine tasting with every two-night stay.' },
{ title: 'Sunset Dinner', desc: 'Three-course menu on the terrace every Friday.' },
];
interface Offer {
id: number;
title: string;
description: string;
}
const benefits = [
'Priority booking for peak weekends',
'Late checkout on availability',
'Free welcome cocktail',
'Member-only events and tastings',
];
interface PortalData {
reservations: Reservation[];
coupons: Coupon[];
offers: Offer[];
benefits: string[];
wifiPassword: string;
}
export default function UserPage() {
const { isUser, hydrated } = useAuth();
const router = useRouter();
const [data, setData] = useState<PortalData | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (!hydrated) return;
if (!isUser) {
router.push('/login');
}
}, [isUser, hydrated, router]);
fetch('/api/user/portal', { credentials: 'same-origin' })
.then(async (res) => {
if (!res.ok) {
if (res.status === 401) {
router.push('/login');
return;
}
throw new Error('Failed to load portal data');
}
return res.json();
})
.then((json) => setData(json))
.catch((err) => console.error(err))
.finally(() => setLoading(false));
}, [router]);
if (!hydrated || !isUser) {
return (
<main style={{ padding: '2rem' }}>
<p>Please log in as user to access this page.</p>
</main>
);
if (loading) return <main style={{ padding: '2rem' }}><p>Loading...</p></main>;
if (!data) {
return <main style={{ padding: '2rem' }}><p>Please log in as user to access this page.</p></main>;
}
return (
@@ -51,11 +65,11 @@ export default function UserPage() {
<section style={{ marginBottom: '2rem' }}>
<h2>Future Reservations</h2>
{reservations.length === 0 ? (
{data.reservations.length === 0 ? (
<p style={{ color: 'dimgray' }}>No upcoming reservations.</p>
) : (
<ul style={{ padding: 0, listStyle: 'none' }}>
{reservations.map((r) => (
{data.reservations.map((r) => (
<li
key={r.id}
style={{
@@ -65,7 +79,7 @@ export default function UserPage() {
borderRadius: '8px',
}}
>
{r.date} at {r.time} &middot; {r.guests} guests &middot; {r.table}
{r.date} at {r.time} · {r.guests} guests · {r.table_name}
</li>
))}
</ul>
@@ -75,9 +89,9 @@ export default function UserPage() {
<section style={{ marginBottom: '2rem' }}>
<h2>Discount Coupons</h2>
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
{coupons.map((c) => (
{data.coupons.map((c) => (
<div
key={c.code}
key={c.id}
style={{
padding: '1rem',
border: '2px dashed #1a1a1a',
@@ -95,9 +109,9 @@ export default function UserPage() {
<section style={{ marginBottom: '2rem' }}>
<h2>Offers</h2>
<ul>
{offers.map((o) => (
<li key={o.title}>
<strong>{o.title}</strong> {o.desc}
{data.offers.map((o) => (
<li key={o.id}>
<strong>{o.title}</strong> {o.description}
</li>
))}
</ul>
@@ -115,14 +129,14 @@ export default function UserPage() {
fontSize: '1.25rem',
}}
>
LaHuascaGuest2026
{data.wifiPassword}
</p>
</section>
<section>
<h2>Member Benefits</h2>
<ul>
{benefits.map((b) => (
{data.benefits.map((b) => (
<li key={b}>{b}</li>
))}
</ul>