Add src/pages/Activate.jsx

This commit is contained in:
Eric Lay 2026-03-11 08:50:22 -05:00
parent 502d00d1d4
commit e9dab789b8
1 changed files with 198 additions and 0 deletions

198
src/pages/Activate.jsx Normal file
View File

@ -0,0 +1,198 @@
import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
import { base44 } from "@/api/base44Client";
import { createPageUrl } from "@/utils";
import { ShieldCheck, Loader2, AlertCircle } from "lucide-react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import ActivationSuccessModal from "@/components/onboarding/ActivationSuccessModal";
import DemoControlsPanel from "@/components/demo/DemoControlsPanel";
import { getDemoControls } from "@/components/demo/useDemoControls";
export default function Activate() {
const urlParams = new URLSearchParams(window.location.search);
const userId = urlParams.get("uid");
const codeFromUrl = urlParams.get("code") || "";
const [code, setCode] = useState(codeFromUrl);
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState("");
const [showSuccess, setShowSuccess] = useState(false);
useEffect(() => {
const loadUser = async () => {
if (!userId) {
setError("Invalid activation link. Please check your email and try again.");
setLoading(false);
return;
}
const results = await base44.entities.BoUser.filter({ id: userId });
if (!results || results.length === 0) {
setError("Account not found. Please contact support.");
setLoading(false);
return;
}
const foundUser = results[0];
if (foundUser.is_activated) {
// Already activated send to dashboard
restoreSessionAndRedirect(foundUser);
return;
}
setUser(foundUser);
setLoading(false);
};
loadUser();
}, [userId]);
const restoreSessionAndRedirect = (u) => {
sessionStorage.setItem("demo_user", JSON.stringify({
email: u.email,
store_id: u.store_id,
store_name: u.store_name,
store_address: u.store_address,
full_name: u.full_name,
first_login_completed: u.first_login_completed
}));
window.location.href = createPageUrl("Dashboard") + "?first_login=true";
};
const handleSubmit = async (e) => {
e.preventDefault();
setError("");
if (!code.trim()) {
setError("Please enter the verification code from your email.");
return;
}
// Demo: simulate invalid code
const demoControls = getDemoControls();
if (!demoControls.activationCodeValid) {
setError("The verification code you entered is invalid. Please check your email and try again.");
return;
}
if (code.trim().toUpperCase() !== user.activation_code) {
setError("That code doesn't match. Please check your email and try again.");
return;
}
setSubmitting(true);
await base44.entities.BoUser.update(user.id, {
is_activated: true,
activation_completed_at: new Date().toISOString()
});
setSubmitting(false);
setShowSuccess(true);
};
const handleSuccessConfirm = () => {
restoreSessionAndRedirect(user);
};
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center bg-[#f0f2f5]">
<Loader2 className="w-8 h-8 animate-spin text-[#1a5276]" />
</div>
);
}
return (
<div className="min-h-screen bg-[#f0f2f5] flex items-center justify-center px-4">
<ActivationSuccessModal
open={showSuccess}
storeName={user?.store_name}
storeAddress={user?.store_address}
onConfirm={handleSuccessConfirm}
/>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="w-full max-w-md"
>
<div className="bg-white rounded-2xl shadow-xl border border-gray-200 overflow-hidden">
{/* Header — logo + title, plain white like production */}
<div className="px-8 pt-8 pb-2 text-center">
<div className="flex justify-center mb-4">
<img
src="https://qtrypzzcjebvfcihiynt.supabase.co/storage/v1/object/public/base44-prod/public/69ac537c5138f01ec706f4bc/9c287bb3b_Logo_PBC_horz_color.png"
alt="PackageHub Business Centers"
className="h-16 w-auto object-contain"
/>
</div>
<div className="w-12 h-12 bg-blue-50 rounded-full flex items-center justify-center mx-auto mb-3">
<ShieldCheck className="w-6 h-6 text-[#2980b9]" />
</div>
<h2 className="text-2xl font-bold text-gray-900 mb-1">Verify Your Account</h2>
<p className="text-sm text-[#2980b9]">Enter the code from your email</p>
</div>
{/* Body */}
<div className="px-8 py-6">
{error && !user ? (
<div className="flex items-start gap-3 bg-red-50 border border-red-100 rounded-xl p-4">
<AlertCircle className="w-5 h-5 text-red-500 mt-0.5 shrink-0" />
<p className="text-sm text-red-700">{error}</p>
</div>
) : (
<>
<p className="text-gray-500 text-sm mb-6 leading-relaxed">
We sent a verification code to{" "}
<span className="font-semibold text-gray-800">{user?.email}</span>.
Enter it below to complete your account activation.
</p>
<form onSubmit={handleSubmit} className="space-y-5">
<div className="space-y-1.5">
<Label htmlFor="code" className="text-sm font-medium text-gray-700">
Verification Code <span className="text-red-500">*</span>
</Label>
<Input
id="code"
type="text"
placeholder="Enter your verification code"
value={code}
onChange={(e) => setCode(e.target.value.toUpperCase())}
maxLength={8}
className={`h-11 text-center text-lg tracking-[0.3em] font-semibold border-gray-200 focus:border-[#2980b9] focus:ring-[#2980b9] ${
error ? "border-red-400" : ""
}`}
/>
{error && (
<p className="text-xs text-red-500 flex items-center gap-1">
<AlertCircle className="w-3 h-3" /> {error}
</p>
)}
</div>
<Button
type="submit"
disabled={submitting}
className="w-full h-11 bg-[#2980b9] hover:bg-[#2471a3] text-white font-semibold rounded-lg"
>
{submitting ? (
<Loader2 className="w-5 h-5 animate-spin" />
) : (
"Verify & Activate Account"
)}
</Button>
</form>
<p className="text-xs text-gray-400 text-center mt-5">
Need help? Contact{" "}
<a href="mailto:support@packagehub360.com" className="text-[#1a5276] underline">
support@packagehub360.com
</a>
</p>
</>
)}
</div>
</div>
<DemoControlsPanel />
</motion.div>
</div>
);
}