Files
avbeat-com/src/app/forum/login/page.tsx
Ryan Salazar a8b75593c2 forum: require auth for posting + add login/register/edit pages
- API: require auth on thread+reply POST (401 if !user); lazy-create
  forum_user_profile via service-role; author_name = display_name
- UI: surface API errors (kill silent catch); auto-redirect to login
  on 401; gate new-thread + reply forms with "Sign in to post" CTA
- /forum/login + /forum/register: forum-themed Supabase Auth pages
  with ?next= redirect support
- /forum/profile/edit + /api/forum/profile/me: edit own forum profile
  (username, display_name, bio, role, company, location, avatar)
- Header: logout button + sign-in/join links when not authed
- forum/user/[username]: show Edit Profile button when viewing own

Fixes posting bug: RLS rejected anon inserts with cryptic message
that the UI silently swallowed; now blocked with friendly 401 + redirect.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 22:41:28 +00:00

127 lines
4.2 KiB
TypeScript

'use client';
import { Suspense, useState, useEffect } from 'react';
import Link from 'next/link';
import { useRouter, useSearchParams } from 'next/navigation';
import Header from '@/components/Header';
import Footer from '@/components/Footer';
import { createClient } from '@/lib/supabase/client';
function safeNext(next: string | null): string {
if (!next) return '/forum';
if (!next.startsWith('/') || next.startsWith('//')) return '/forum';
return next;
}
function ForumLoginInner() {
const router = useRouter();
const params = useSearchParams();
const next = safeNext(params.get('next'));
const supabase = createClient();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
supabase.auth.getUser().then(({ data: { user } }) => {
if (user) router.replace(next);
});
}, [next, router, supabase]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
if (!email || !password) {
setError('Email and password are required.');
return;
}
setLoading(true);
const { error: signInError } = await supabase.auth.signInWithPassword({ email, password });
setLoading(false);
if (signInError) {
setError(signInError.message);
return;
}
window.location.href = next;
};
return (
<>
<Header />
<main className="min-h-screen bg-[#111111]">
<div className="max-w-md mx-auto px-4 py-12">
<div className="text-center mb-6">
<h1 className="text-2xl font-heading font-bold text-white">Sign in to the Forum</h1>
<p className="text-[#888] font-body text-sm mt-1">
Use your Broadcast Beat account to post, reply, and vote.
</p>
</div>
<form
onSubmit={handleSubmit}
className="bg-[#1a1a1a] border border-[#252525] rounded-lg p-6 space-y-4"
>
{error && (
<div className="bg-[#2a0a0a] border border-[#cc0000] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
{error}
</div>
)}
<div>
<label className="block text-xs font-body uppercase tracking-wider text-[#888] mb-1.5">
Email
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
autoComplete="email"
required
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#3b82f6]"
/>
</div>
<div>
<label className="block text-xs font-body uppercase tracking-wider text-[#888] mb-1.5">
Password
</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
autoComplete="current-password"
required
className="w-full bg-[#0d0d0d] border border-[#2a2a2a] rounded-lg px-3 py-2 text-white font-body text-sm focus:outline-none focus:border-[#3b82f6]"
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full bg-[#3b82f6] hover:bg-[#2563eb] disabled:opacity-50 text-white font-body font-semibold text-sm px-5 py-2.5 rounded-lg transition-colors"
>
{loading ? 'Signing in…' : 'Sign In'}
</button>
<div className="text-center text-xs font-body text-[#888] pt-2">
No account yet?{' '}
<Link
href={`/forum/register?next=${encodeURIComponent(next)}`}
className="text-[#3b82f6] hover:underline"
>
Create one
</Link>
</div>
</form>
</div>
</main>
<Footer />
</>
);
}
export default function ForumLoginPage() {
return (
<Suspense fallback={null}>
<ForumLoginInner />
</Suspense>
);
}