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
+8 -12
View File
@@ -1,7 +1,7 @@
'use client';
import { useEffect, useRef, useState } from 'react';
import { measureText } from '@chenglou/pretext';
import { prepare, layout } from '@chenglou/pretext';
interface MeasuredTextProps {
text: string;
@@ -9,21 +9,17 @@ interface MeasuredTextProps {
export default function MeasuredText({ text }: MeasuredTextProps) {
const ref = useRef<HTMLSpanElement>(null);
const [width, setWidth] = useState<number | null>(null);
const [height, setHeight] = useState<number | null>(null);
useEffect(() => {
if (!ref.current) return;
const style = window.getComputedStyle(ref.current);
const measured = measureText(text, {
font: {
family: style.fontFamily.split(',')[0].replace(/['"]/g, ''),
size: parseFloat(style.fontSize),
weight: style.fontWeight,
},
});
const font = `${style.fontWeight} ${style.fontSize} ${style.fontFamily.split(',')[0].replace(/['"]/g, '')}`;
const prepared = prepare(text, font);
const { height } = layout(prepared, ref.current.offsetWidth, parseFloat(style.lineHeight) || parseFloat(style.fontSize) * 1.2);
setWidth(measured.width);
setHeight(height);
}, [text]);
return (
@@ -31,8 +27,8 @@ export default function MeasuredText({ text }: MeasuredTextProps) {
<span ref={ref} style={{ fontSize: '2rem', fontWeight: 700 }}>
{text}
</span>
{width !== null && (
<p style={{ color: 'dimgray' }}>Measured width: {width.toFixed(2)}px</p>
{height !== null && (
<p style={{ color: 'dimgray' }}>Measured height: {height.toFixed(2)}px</p>
)}
</div>
);
+13 -5
View File
@@ -2,7 +2,7 @@
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { useAuth } from '@/hooks/useAuth';
import { useEffect, useState } from 'react';
const links = [
{ href: '/', label: 'Home' },
@@ -13,11 +13,19 @@ const links = [
];
export default function Navbar() {
const { isLoggedIn, logout } = useAuth();
const [auth, setAuth] = useState<{ authenticated: boolean; role?: string } | null>(null);
const router = useRouter();
const handleLogout = () => {
logout();
useEffect(() => {
fetch('/api/auth/me', { credentials: 'same-origin' })
.then((res) => (res.ok ? res.json() : { authenticated: false }))
.then((data) => setAuth(data))
.catch(() => setAuth({ authenticated: false }));
}, []);
const handleLogout = async () => {
await fetch('/api/auth/logout', { method: 'POST', credentials: 'same-origin' });
setAuth({ authenticated: false });
router.push('/login');
};
@@ -47,7 +55,7 @@ export default function Navbar() {
{link.label}
</Link>
))}
{isLoggedIn ? (
{auth?.authenticated ? (
<button
onClick={handleLogout}
style={{