Add database integration, auth API, admin/user portals, and deploy-db.sh
This commit is contained in:
+56
-42
@@ -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} · {r.guests} guests · {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>
|
||||
|
||||
Reference in New Issue
Block a user