Skip to content

Commit c66fb2d

Browse files
fix: resolve mock portal hydration error on staging (#104)
1 parent 2aa3fc9 commit c66fb2d

2 files changed

Lines changed: 228 additions & 223 deletions

File tree

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import Image from "next/image";
5+
import { mockMembers, isMockAdmin } from "@/lib/mock-members";
6+
7+
export default function MockPortalContent() {
8+
const [selectedMember, setSelectedMember] = useState(mockMembers[0]);
9+
const [email, setEmail] = useState(mockMembers[0].owneremail);
10+
const [password, setPassword] = useState("password");
11+
const [loading, setLoading] = useState(false);
12+
const [error, setError] = useState("");
13+
14+
function handleMemberSelect(ownerid: number) {
15+
const member = mockMembers.find((m) => m.ownerid === ownerid);
16+
if (member) {
17+
setSelectedMember(member);
18+
setEmail(member.owneremail);
19+
setError("");
20+
}
21+
}
22+
23+
async function handleLogin(e: React.FormEvent<HTMLFormElement>) {
24+
e.preventDefault();
25+
setError("");
26+
27+
if (!email || !password) {
28+
setError("You must enter an email address and password.");
29+
return;
30+
}
31+
32+
const member = mockMembers.find((m) => m.owneremail.toLowerCase() === email.toLowerCase());
33+
if (!member) {
34+
setError("No member found with that email address.");
35+
return;
36+
}
37+
38+
setLoading(true);
39+
40+
const res = await fetch("/api/dev/token", {
41+
method: "POST",
42+
headers: { "Content-Type": "application/json" },
43+
body: JSON.stringify({ ownerid: member.ownerid, isAdmin: isMockAdmin(member.ownerid) }),
44+
});
45+
46+
if (!res.ok) {
47+
setLoading(false);
48+
setError("Failed to generate authentication token.");
49+
return;
50+
}
51+
52+
const { token } = await res.json();
53+
54+
const form = document.createElement("form");
55+
form.method = "POST";
56+
form.action = "/api/auth/callback";
57+
58+
const input = document.createElement("input");
59+
input.type = "hidden";
60+
input.name = "token";
61+
input.value = token;
62+
form.appendChild(input);
63+
64+
document.body.appendChild(form);
65+
form.submit();
66+
}
67+
68+
const navItems = ["The Co-op", "News & Events", "Get Involved", "Join Now", "Donate", "Contact Us", "FAQ", "Login"];
69+
70+
return (
71+
<div className="flex min-h-screen flex-col bg-white">
72+
<header className="border-b border-paso-light-brown bg-white px-6 py-4">
73+
<div className="mx-auto flex max-w-4xl items-center justify-between">
74+
<Image
75+
src="/assets/logo.png"
76+
alt="Paso Robles Food Co-op Logo"
77+
className="h-14 w-auto"
78+
width={284}
79+
height={100}
80+
priority
81+
/>
82+
<div className="text-right text-sm text-paso-accent-black">
83+
<p className="font-bold">For help or info, please contact us:</p>
84+
<p>
85+
E-Mail:{" "}
86+
<a href="mailto:info@pasofoodcooperative.com" className="font-bold text-prfc-blue underline">
87+
info@pasofoodcooperative.com
88+
</a>
89+
</p>
90+
</div>
91+
</div>
92+
</header>
93+
94+
<nav className="bg-prfc-brown">
95+
<ul className="mx-auto flex max-w-4xl flex-wrap justify-center gap-1 px-6 py-2 text-sm font-semibold text-white">
96+
{navItems.map((item) => (
97+
<li key={item}>
98+
<span className="cursor-default rounded px-3 py-1.5 hover:bg-prfc-dark-brown">{item}</span>
99+
</li>
100+
))}
101+
</ul>
102+
</nav>
103+
104+
<main className="flex flex-1 flex-col items-center px-4 py-10">
105+
<div className="w-full max-w-sm">
106+
<p className="mb-3 text-center text-sm font-semibold text-prfc-red">
107+
You must be logged in to view this page.
108+
</p>
109+
110+
<h1 className="mb-6 text-center text-xl font-bold text-paso-accent-black">
111+
Login to your Paso Food Co-op Member Account
112+
</h1>
113+
114+
<form onSubmit={handleLogin} className="space-y-4">
115+
<div>
116+
<label htmlFor="email" className="mb-1 block text-sm text-paso-accent-black">
117+
Email Address:
118+
</label>
119+
<input
120+
type="text"
121+
id="email"
122+
value={email}
123+
onChange={(e) => {
124+
setEmail(e.target.value);
125+
if (error) setError("");
126+
}}
127+
className="w-full rounded border border-prfc-border px-3 py-2 text-sm focus:border-prfc-brown focus:outline-none focus:ring-1 focus:ring-prfc-brown"
128+
/>
129+
</div>
130+
131+
<div>
132+
<label htmlFor="password" className="mb-1 block text-sm text-paso-accent-black">
133+
Password:
134+
</label>
135+
<input
136+
type="password"
137+
id="password"
138+
value={password}
139+
onChange={(e) => setPassword(e.target.value)}
140+
className="w-full rounded border border-prfc-border px-3 py-2 text-sm focus:border-prfc-brown focus:outline-none focus:ring-1 focus:ring-prfc-brown"
141+
/>
142+
</div>
143+
144+
{error ? <p className="text-center text-sm text-prfc-red">{error}</p> : null}
145+
146+
<button
147+
type="submit"
148+
disabled={loading}
149+
className="w-full rounded bg-prfc-brown py-2 text-sm font-semibold text-white hover:bg-prfc-dark-brown disabled:opacity-50"
150+
>
151+
{loading ? "Logging in..." : "Login"}
152+
</button>
153+
</form>
154+
155+
<div className="mt-5 flex flex-col items-center gap-1 text-sm">
156+
<a href="#" className="font-bold text-prfc-blue underline">
157+
Need an Account?
158+
</a>
159+
<a href="#" className="font-bold text-prfc-blue underline">
160+
Forgot Your Password?
161+
</a>
162+
<a href="#" className="font-bold text-prfc-blue underline">
163+
Contact Paso Food Co-op
164+
</a>
165+
</div>
166+
</div>
167+
168+
<div className="mt-10 w-full max-w-sm">
169+
<details className="rounded border border-prfc-border bg-paso-grey p-4">
170+
<summary className="cursor-pointer text-sm font-semibold text-prfc-brown">
171+
Dev Tools - Quick Select Member
172+
</summary>
173+
174+
<div className="mt-4 flex flex-col gap-3">
175+
<label className="flex flex-col gap-1">
176+
<span className="text-xs font-medium text-prfc-dark-brown">Select Member</span>
177+
<select
178+
value={selectedMember.ownerid}
179+
onChange={(e) => handleMemberSelect(parseInt(e.target.value))}
180+
className="rounded border border-prfc-border bg-white px-3 py-1.5 text-sm"
181+
>
182+
{mockMembers.map((member) => (
183+
<option key={member.ownerid} value={member.ownerid}>
184+
{member.ownerid} - {member.ownername}
185+
</option>
186+
))}
187+
</select>
188+
</label>
189+
190+
<div className="rounded bg-white p-3 text-xs text-prfc-dark-brown">
191+
<div>
192+
<strong>Name:</strong> {selectedMember.ownername}
193+
</div>
194+
<div>
195+
<strong>Email:</strong> {selectedMember.owneremail}
196+
</div>
197+
<div>
198+
<strong>Owner ID:</strong> {selectedMember.ownerid}
199+
</div>
200+
<div>
201+
<strong>Role:</strong> {isMockAdmin(selectedMember.ownerid) ? "Admin" : "Member"}
202+
</div>
203+
</div>
204+
</div>
205+
</details>
206+
</div>
207+
</main>
208+
209+
<footer className="border-t border-prfc-border bg-white px-6 py-4">
210+
<p className="text-center text-sm text-paso-accent-black">
211+
{navItems.map((item, i) => (
212+
<span key={item}>
213+
<a href="#" className="hover:underline">
214+
{item}
215+
</a>
216+
{i < navItems.length - 1 ? " | " : ""}
217+
</span>
218+
))}
219+
</p>
220+
<p className="mt-2 text-center text-xs text-prfc-border">
221+
Copyright &copy; 2013-2026 by Paso Robles Food Cooperative, Inc. All Rights Reserved.
222+
</p>
223+
</footer>
224+
</div>
225+
);
226+
}

0 commit comments

Comments
 (0)