Add src/pages/Onboarding.jsx

This commit is contained in:
Eric Lay 2026-03-11 08:51:39 -05:00
parent 3f6b112274
commit 523ca30de8
1 changed files with 236 additions and 0 deletions

236
src/pages/Onboarding.jsx Normal file
View File

@ -0,0 +1,236 @@
import React, { useState } from "react";
import { motion } from "framer-motion";
import { base44 } from "@/api/base44Client";
import { Store, MapPin, Hash, ArrowRight } from "lucide-react";
import WarningModal from "@/components/onboarding/WarningModal";
import SignupForm from "@/components/onboarding/SignupForm";
import ActivationPending from "@/components/onboarding/ActivationPending";
// Demo token resolver simulates decoding the onboarding token
function resolveToken(token) {
return {
store_id: "AA1112",
store_name: "Radi Box N Ship",
store_address: "1939 W. Manchester Ave., Los Angeles, CA 90047",
onboarding_token: token || "demo-token-abc123"
};
}
export default function Onboarding() {
const urlParams = new URLSearchParams(window.location.search);
const token = urlParams.get("token") || "demo-token-abc123";
const [storeData] = useState(() => resolveToken(token));
const [showWarning, setShowWarning] = useState(false);
const [warningDismissed, setWarningDismissed] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [activationPending, setActivationPending] = useState(false);
const [pendingEmail, setPendingEmail] = useState("");
const [pendingUserId, setPendingUserId] = useState("");
const [pendingCode, setPendingCode] = useState("");
const [pendingUserData, setPendingUserData] = useState(null);
// Show landing first, then modal on CTA click
const [showLanding, setShowLanding] = useState(true);
const handleBeginOnboarding = () => {
setShowLanding(false);
setShowWarning(true);
};
const handleWarningContinue = () => {
setShowWarning(false);
setWarningDismissed(true);
};
const generateCode = () => {
const chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
let code = "";
for (let i = 0; i < 6; i++) code += chars[Math.floor(Math.random() * chars.length)];
return code;
};
const sendActivationEmail = async (email, userId, code) => {
await base44.integrations.Core.SendEmail({
to: email,
subject: "Final Step: Verify your PackageHub360 account",
body: `<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; color: #222;">
<div style="background: linear-gradient(135deg, #1a5276, #2980b9); padding: 32px 40px; border-radius: 12px 12px 0 0; text-align: center;">
<h1 style="color: #ffffff; font-size: 22px; margin: 0;">PackageHub<span style="color: #e67e22;">360</span></h1>
<p style="color: #bde0f5; font-size: 13px; margin: 6px 0 0;">Business Centers</p>
</div>
<div style="background: #ffffff; padding: 36px 40px; border: 1px solid #e5e7eb; border-top: none; border-radius: 0 0 12px 12px;">
<h2 style="font-size: 20px; color: #1a5276; margin-top: 0;">Verify Your Email Address</h2>
<p style="font-size: 15px; line-height: 1.6; color: #444;">
Thank you for creating your PackageHub360 account. To complete your registration, please verify your email address using the code below.
</p>
<div style="background: #f0f6fc; border: 1px solid #c5dff5; border-radius: 10px; padding: 20px; text-align: center; margin: 28px 0;">
<p style="font-size: 13px; color: #666; margin: 0 0 8px;">Your verification code is:</p>
<p style="font-size: 36px; font-weight: bold; letter-spacing: 10px; color: #1a5276; margin: 0;">${code}</p>
</div>
<hr style="border: none; border-top: 1px solid #e5e7eb; margin: 28px 0;" />
<p style="font-size: 13px; color: #888; line-height: 1.6;">
If you did not create a PackageHub360 account, please ignore this email or contact us at
<a href="mailto:support@packagehub360.com" style="color: #1a5276;">support@packagehub360.com</a>.
</p>
<p style="font-size: 14px; color: #555; margin-top: 20px;">
Thanks,<br />
<strong>The PackageHub Team</strong>
</p>
</div>
</div>`
});
};
const handleSignup = async ({ email, password, mobile, username }) => {
setIsLoading(true);
const activationCode = generateCode();
const newUser = await base44.entities.BoUser.create({
email,
username,
password_hash: password,
store_id: storeData.store_id,
store_name: storeData.store_name,
store_address: storeData.store_address,
onboarding_token: storeData.onboarding_token,
first_login_completed: false,
stripe_onboarding_started: false,
full_name: "New Member",
phone: mobile,
city: "Los Angeles",
state: "CA",
zip_code: "90047",
street_address: "1939 W. Manchester Ave.",
status: "active",
activation_code: activationCode,
activation_email_sent: true,
is_activated: false
});
await sendActivationEmail(email, newUser.id, activationCode);
setPendingEmail(email);
setPendingUserId(newUser.id);
setPendingCode(activationCode);
setPendingUserData({
email,
username,
store_id: storeData.store_id,
store_name: storeData.store_name,
full_name: "New Member",
is_activated: false,
});
setIsLoading(false);
setActivationPending(true);
};
const handleResendEmail = async () => {
await sendActivationEmail(pendingEmail, pendingUserId, pendingCode);
};
return (
<div className="min-h-screen bg-[#f0f2f5]">
{/* Warning Modal */}
<WarningModal open={showWarning} onContinue={handleWarningContinue} />
{showLanding && !warningDismissed &&
<div className="min-h-screen flex flex-col items-center justify-center px-4">
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
className="max-w-lg w-full">
{/* Welcome Card */}
<div className="bg-white rounded-2xl shadow-xl border border-gray-100 overflow-hidden text-left">
{/* Card Header with logo */}
<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>
<h1 className="text-2xl font-bold text-gray-900 mb-1">Welcome</h1>
<p className="text-sm text-[#2980b9]">You've been invited to set up your PackageHub360 store account.</p>
</div>
{/* Card Body */}
<div className="px-8 py-7">
<div className="space-y-3 mb-7">
<div className="flex items-center gap-3 p-3 bg-blue-50 rounded-xl">
<div className="w-10 h-10 rounded-lg bg-[#1a5276] flex items-center justify-center shrink-0">
<Store className="w-5 h-5 text-white" />
</div>
<div>
<p className="text-xs font-medium text-gray-400 uppercase tracking-wide">Store Name</p>
<p className="text-sm font-semibold text-gray-800">{storeData.store_name}</p>
</div>
</div>
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-xl">
<div className="w-10 h-10 rounded-lg bg-gray-200 flex items-center justify-center shrink-0">
<Hash className="w-5 h-5 text-gray-500" />
</div>
<div>
<p className="text-xs font-medium text-gray-400 uppercase tracking-wide">Store ID</p>
<p className="text-sm font-semibold text-gray-800">{storeData.store_id}</p>
</div>
</div>
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-xl">
<div className="w-10 h-10 rounded-lg bg-gray-200 flex items-center justify-center shrink-0">
<MapPin className="w-5 h-5 text-gray-500" />
</div>
<div>
<p className="text-xs font-medium text-gray-400 uppercase tracking-wide">Store Address</p>
<p className="text-sm font-semibold text-gray-800">{storeData.store_address}</p>
</div>
</div>
</div>
<button
onClick={handleBeginOnboarding}
className="w-full h-12 bg-[#1a5276] hover:bg-[#154360] text-white text-sm font-semibold rounded-xl transition-all duration-200 shadow-md flex items-center justify-center gap-2">
Begin Account Setup
<ArrowRight className="w-4 h-4" />
</button>
<p className="text-red-600 mt-5 text-xs font-medium text-center leading-relaxed">
This one-time link was generated specifically for your store. Do not share it.
</p>
</div>
</div>
</motion.div>
</div>
}
{/* Signup Form */}
{warningDismissed && !activationPending &&
<div className="min-h-screen flex items-center justify-center px-4 py-12">
<SignupForm storeData={storeData} onSubmit={handleSignup} isLoading={isLoading} />
</div>
}
{/* Activation Pending Screen */}
{activationPending &&
<div className="min-h-screen flex items-center justify-center px-4 py-12">
<ActivationPending
email={pendingEmail}
userId={pendingUserId}
expectedCode={pendingCode}
onResend={handleResendEmail}
userData={pendingUserData}
/>
</div>
}
</div>);
}