Files
hope-events/frontend/src/components/auth/RegisterForm.tsx
T
joshuaandClaude Sonnet 5 8e6cb542d9 Full site redesign, help system, and dashboard stats fixes
Multi-phase visual facelift (design tokens, dashboards, sidebar/navbar
shell, per-page help guides, and a layout/content pass across every
remaining page) plus backend fixes to the dashboard KPI stats:

- Admin/Supervisor dashboard KPIs (revenue, donations, registrations,
  tickets sold) now use a rolling trailing-month window (today back one
  calendar month, e.g. 9 May - 8 June if today is 8 June) instead of
  calendar month-to-date, which under-counted for most of the month.
  The comparison window shifts the same way, so like is still compared
  with like.
- Reports deep-links from those stat tiles now match the same window
  (range=trailing_month, replacing range=this_month).
- Design tokens (brand-* Tailwind scale + shadcn CSS variables), a
  site-wide contextual help button, fixed dashboard sidebar/navbar,
  Admin/Supervisor/Staff/User dashboard rebuilds backed by a new
  GET /api/stats/overview endpoint, a dedicated Contact page, Site
  Settings restyle with WhatsApp config folded in, and an Account
  activity feed backed by a new SecurityEvent model.
- Every remaining page (home, events, registration flow, auth, legal,
  payment results, and every Admin/Supervisor/Staff/User tool page)
  restyled onto the same design tokens, several with real layout
  upgrades (home hero, events list/detail, donate page, auth pages).
- 20+ new dedicated help guides so the whole site has page-specific
  help content instead of falling back to a generic guide.
- Assorted fixes surfaced along the way: donation-leg double-counting
  in payment stats, donations not counting toward revenue, refund
  netting in per-method report breakdowns, and donation
  over-allocation after a refund.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 15:00:10 +02:00

185 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import React, { useState } from "react";
import { useAuth } from "@/hooks/useAuth";
import { useSearchParams } from "next/navigation";
import Link from "next/link";
import { isValidZAPhone } from "@/lib/phone";
export function RegisterForm() {
const { register } = useAuth();
const searchParams = useSearchParams();
const raw = searchParams.get("redirect") || "";
const redirectTo = raw.startsWith("/") && !raw.startsWith("//") ? raw : "/dashboard";
const loginHref = redirectTo !== "/dashboard" ? `/login?redirect=${encodeURIComponent(redirectTo)}` : "/login";
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [phoneNumber, setPhoneNumber] = useState("");
const [notificationPreference, setNotificationPreference] = useState<"email" | "whatsapp" | "both">("email");
const [accepted, setAccepted] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const hasValidPhone = isValidZAPhone(phoneNumber);
const getErrorMessage = (err: any) => {
if (!err) return "Login failed";
// If it's already a normal Error
if (err.message && typeof err.message === "string") {
// Handle case where message is accidentally JSON string
try {
const parsed = JSON.parse(err.message);
return parsed.message || err.message;
} catch {
return err.message;
}
}
// If API response object was thrown directly
if (err.message) return err.message;
return "Registration failed. Please try again.";
};
const onSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!accepted) {
setError("Please accept the Terms of Use and Privacy Policy before continuing.");
return;
}
setError(null);
setLoading(true);
try {
await register({
name,
email,
password,
phoneNumber: phoneNumber || undefined,
notificationPreference: hasValidPhone ? notificationPreference : "email",
});
window.location.href = redirectTo;
} catch (err: any) {
setError(getErrorMessage(err) || "Registration failed. Please try again.");
} finally {
setLoading(false);
}
};
return (
<form onSubmit={onSubmit} className="space-y-4 max-w-sm w-full">
<div>
<label className="block text-sm font-medium mb-1">Full Name</label>
<input
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full border rounded px-3 py-2"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full border rounded px-3 py-2"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full border rounded px-3 py-2"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Phone Number (optional)</label>
<input
value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)}
placeholder="e.g. 082 123 4567"
className="w-full border rounded px-3 py-2"
/>
</div>
{hasValidPhone && (
<div>
<label className="block text-sm font-medium mb-1">Notification Preference</label>
<select
value={notificationPreference}
onChange={(e) => setNotificationPreference(e.target.value as "email" | "whatsapp" | "both")}
className="w-full border rounded px-3 py-2"
>
<option value="email">Email only</option>
<option value="whatsapp">WhatsApp only</option>
<option value="both">Email &amp; WhatsApp</option>
</select>
</div>
)}
<div className="flex items-start gap-2">
<input
id="terms"
type="checkbox"
checked={accepted}
onChange={(e) => setAccepted(e.target.checked)}
className="mt-1 h-4 w-4 border-gray-300 rounded accent-brand-600"
required
/>
<label htmlFor="terms" className="text-sm text-gray-700 leading-snug">
I confirm that Ive read and agree to the{" "}
<Link
href="/legal/terms"
target="_blank"
className="text-brand-600 hover:underline"
>
Terms of Use
</Link>{" "}
and{" "}
<Link
href="/legal/privacy"
target="_blank"
className="text-brand-600 hover:underline"
>
Privacy Policy
</Link>.
</label>
</div>
<div className="mt-1">
<a
href={loginHref}
className="text-xs text-brand-600 hover:underline"
>
Already have an account? Sign in here.
</a>
</div>
{error && <p className="text-sm text-red-600">{error}</p>}
<button
type="submit"
disabled={loading || !accepted}
className={`w-full rounded py-2 text-white transition ${
accepted
? "bg-brand-600 hover:bg-brand-700"
: "bg-gray-400 cursor-not-allowed"
}`}
>
{loading ? "Creating account..." : "Create account"}
</button>
</form>
);
}