332 lines
12 KiB
TypeScript
332 lines
12 KiB
TypeScript
'use client'
|
||
|
||
import { useEffect, useState, type CSSProperties } from 'react'
|
||
import { useSearchParams, useRouter } from 'next/navigation'
|
||
import useAuthStore from '../store/authStore'
|
||
import RegisterForm from './components/RegisterForm'
|
||
import PageLayout from '../components/PageLayout'
|
||
import SessionDetectedModal from './components/SessionDetectedModal'
|
||
import InvalidRefLinkModal from './components/invalidRefLinkModal'
|
||
import { ToastProvider, useToast } from '../components/toast/toastComponent'
|
||
import Waves from '../components/background/waves'
|
||
import BlueBlurryBackground from '../components/background/blueblurry' // NEW
|
||
|
||
// NEW: inner component that actually uses useToast and all the logic
|
||
function RegisterPageInner() {
|
||
const searchParams = useSearchParams()
|
||
const refToken = searchParams.get('ref')
|
||
const [registered, setRegistered] = useState(false)
|
||
const [mode, setMode] = useState<'personal' | 'company'>('personal')
|
||
const router = useRouter()
|
||
const { showToast } = useToast()
|
||
|
||
// Auth state
|
||
const user = useAuthStore(state => state.user)
|
||
const logout = useAuthStore(state => state.logout)
|
||
|
||
// Session management
|
||
const [showSessionModal, setShowSessionModal] = useState(false)
|
||
const [sessionCleared, setSessionCleared] = useState(false)
|
||
|
||
// Referral validation state
|
||
const [isRefChecked, setIsRefChecked] = useState(false)
|
||
const [invalidRef, setInvalidRef] = useState(false)
|
||
const [refInfo, setRefInfo] = useState<{
|
||
referrerName?: string
|
||
referrerEmail?: string
|
||
isUnlimited?: boolean
|
||
usesRemaining?: number
|
||
} | null>(null)
|
||
|
||
// Redirect after registration
|
||
useEffect(() => {
|
||
if (registered) {
|
||
const t = setTimeout(() => router.push('/login'), 4000)
|
||
return () => clearTimeout(t)
|
||
}
|
||
}, [registered, router])
|
||
|
||
// Validate referral token
|
||
useEffect(() => {
|
||
let cancelled = false
|
||
|
||
const validateRef = async () => {
|
||
if (!refToken) {
|
||
if (!cancelled) {
|
||
setInvalidRef(true)
|
||
setIsRefChecked(true)
|
||
}
|
||
showToast({
|
||
variant: 'error',
|
||
title: 'Invitation error',
|
||
message: 'No invitation token found in the link.'
|
||
})
|
||
return
|
||
}
|
||
|
||
const base = process.env.NEXT_PUBLIC_API_BASE_URL || ''
|
||
const url = `${base}/api/referral/info/${encodeURIComponent(refToken)}`
|
||
|
||
try {
|
||
const res = await fetch(url, { method: 'GET', credentials: 'include' })
|
||
const body = await res.json().catch(() => null)
|
||
|
||
const success = !!body?.success
|
||
const isUnlimited = !!body?.isUnlimited
|
||
const usesRemaining =
|
||
typeof body?.usesRemaining === 'number' ? body.usesRemaining : 0
|
||
const isActive = success && (isUnlimited || usesRemaining > 0)
|
||
|
||
if (!cancelled) {
|
||
if (isActive) {
|
||
setRefInfo({
|
||
referrerName: body?.referrerName,
|
||
referrerEmail: body?.referrerEmail,
|
||
isUnlimited,
|
||
usesRemaining
|
||
})
|
||
setInvalidRef(false)
|
||
showToast({
|
||
variant: 'success',
|
||
title: 'Invitation verified',
|
||
message: 'Your invitation link is valid. You can register now.'
|
||
})
|
||
} else {
|
||
setInvalidRef(true)
|
||
showToast({
|
||
variant: 'error',
|
||
title: 'Invalid invitation',
|
||
message: 'This invitation link is invalid or no longer active.'
|
||
})
|
||
}
|
||
setIsRefChecked(true)
|
||
}
|
||
} catch {
|
||
if (!cancelled) {
|
||
setInvalidRef(true)
|
||
setIsRefChecked(true)
|
||
}
|
||
showToast({
|
||
variant: 'error',
|
||
title: 'Network error',
|
||
message: 'Could not validate the invitation link. Please try again.'
|
||
})
|
||
}
|
||
}
|
||
|
||
validateRef()
|
||
return () => {
|
||
cancelled = true
|
||
}
|
||
// showToast intentionally omitted to avoid effect re-run loops (provider value can change)
|
||
}, [refToken]) // note: showToast intentionally omitted to avoid effect re-run loops
|
||
|
||
// Detect existing logged-in session
|
||
useEffect(() => {
|
||
if (isRefChecked && !invalidRef && user && !sessionCleared) {
|
||
setShowSessionModal(true)
|
||
}
|
||
}, [isRefChecked, invalidRef, user, sessionCleared])
|
||
|
||
const handleLogout = async () => {
|
||
await logout()
|
||
setSessionCleared(true)
|
||
setShowSessionModal(false)
|
||
}
|
||
|
||
const handleCancel = () => {
|
||
setShowSessionModal(false)
|
||
router.push('/dashboard')
|
||
}
|
||
|
||
const [isMobile, setIsMobile] = useState(false)
|
||
|
||
useEffect(() => {
|
||
const mq = window.matchMedia('(max-width: 768px)')
|
||
const apply = () => setIsMobile(mq.matches)
|
||
apply()
|
||
mq.addEventListener?.('change', apply)
|
||
window.addEventListener('resize', apply, { passive: true })
|
||
return () => {
|
||
mq.removeEventListener?.('change', apply)
|
||
window.removeEventListener('resize', apply)
|
||
}
|
||
}, [])
|
||
|
||
const mainStyle: CSSProperties = {
|
||
paddingTop: isMobile
|
||
? 'calc(var(--pp-header-spacer, 0px) + clamp(1.25rem, 3.5vh, 2.25rem))'
|
||
: 'calc(var(--pp-header-spacer, 0px) + clamp(5rem, 8vh, 7rem))',
|
||
transition: 'padding-top 260ms ease, opacity 260ms ease',
|
||
willChange: 'padding-top, opacity',
|
||
opacity: 'var(--pp-page-shift-opacity, 1)',
|
||
}
|
||
|
||
const BackgroundShell = ({ children }: { children: React.ReactNode }) => {
|
||
return isMobile ? (
|
||
<BlueBlurryBackground className="min-h-screen w-full">{children}</BlueBlurryBackground>
|
||
) : (
|
||
<div className="relative w-full flex flex-col min-h-screen overflow-hidden" style={{ backgroundImage: 'none', background: 'none' }}>
|
||
<Waves
|
||
className="pointer-events-none"
|
||
lineColor="#0f172a"
|
||
backgroundColor="rgba(245, 245, 240, 1)"
|
||
waveSpeedX={0.02}
|
||
waveSpeedY={0.01}
|
||
waveAmpX={40}
|
||
waveAmpY={20}
|
||
friction={0.9}
|
||
tension={0.01}
|
||
maxCursorMove={120}
|
||
xGap={12}
|
||
yGap={36}
|
||
/>
|
||
{children}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// --- Render branches (unchanged except classNames) ---
|
||
|
||
if (!isRefChecked) {
|
||
return (
|
||
<PageLayout>
|
||
<BackgroundShell>
|
||
<main
|
||
className="relative z-10 flex flex-col flex-1 items-center justify-start sm:justify-center pb-10 sm:pb-20"
|
||
style={mainStyle}
|
||
>
|
||
<div className="mx-auto max-w-7xl px-6 lg:px-10 flex flex-col w-full">
|
||
<div
|
||
className="mx-auto w-full max-w-md rounded-3xl shadow-2xl relative border border-white/35 overflow-hidden p-6 sm:p-8"
|
||
style={{
|
||
backgroundColor: 'rgba(255,255,255,0.55)',
|
||
backdropFilter: 'blur(18px)',
|
||
WebkitBackdropFilter: 'blur(18px)',
|
||
boxShadow: '0 18px 45px rgba(15,23,42,0.45)',
|
||
}}
|
||
>
|
||
<div className="absolute -top-24 -right-24 size-56 rounded-full bg-gradient-to-br from-indigo-500/10 to-fuchsia-500/10 blur-3xl pointer-events-none" />
|
||
<div className="relative text-center">
|
||
<div className="animate-spin rounded-full h-10 w-10 border-2 border-b-transparent border-[#8D6B1D] mx-auto mb-4" />
|
||
<p className="text-slate-700">Checking invitation link…</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</BackgroundShell>
|
||
</PageLayout>
|
||
)
|
||
}
|
||
|
||
if (invalidRef) {
|
||
return (
|
||
<PageLayout>
|
||
<BackgroundShell>
|
||
<main
|
||
className="relative z-10 flex flex-col flex-1 items-center justify-start sm:justify-center pb-10 sm:pb-20"
|
||
style={mainStyle}
|
||
>
|
||
<div className="mx-auto max-w-7xl px-6 lg:px-10 flex flex-col w-full">
|
||
<div
|
||
className="mx-auto w-full max-w-3xl min-h-[520px] sm:min-h-[560px] rounded-3xl shadow-2xl relative border border-white/35 overflow-hidden p-6 sm:p-10 md:py-12 md:px-14"
|
||
style={{
|
||
backgroundColor: 'rgba(255,255,255,0.55)', // Use a translucent white for glass effect
|
||
backdropFilter: 'blur(18px)', // Glass blur
|
||
WebkitBackdropFilter: 'blur(18px)',
|
||
boxShadow: '0 18px 45px rgba(15,23,42,0.45)',
|
||
}}
|
||
>
|
||
<div className="absolute -top-24 -right-24 size-56 rounded-full bg-gradient-to-br from-indigo-500/10 to-fuchsia-500/10 blur-3xl pointer-events-none" />
|
||
<div className="relative flex items-center justify-center">
|
||
<InvalidRefLinkModal
|
||
inline
|
||
open
|
||
token={refToken}
|
||
onGoHome={() => router.push('/')}
|
||
onClose={() => router.push('/')}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</BackgroundShell>
|
||
</PageLayout>
|
||
)
|
||
}
|
||
|
||
// normal register
|
||
return (
|
||
<PageLayout>
|
||
<BackgroundShell>
|
||
<main
|
||
className="relative z-10 flex flex-col flex-1 items-center justify-start sm:justify-center pb-10 sm:pb-20"
|
||
style={mainStyle}
|
||
>
|
||
<div className="mx-auto max-w-7xl px-6 lg:px-10 flex flex-col w-full">
|
||
<div
|
||
className="mx-auto w-full max-w-4xl min-h-[520px] sm:min-h-[560px] rounded-3xl shadow-2xl relative border border-white/35 overflow-hidden p-6 sm:p-10 md:py-12 md:px-14"
|
||
style={{
|
||
backgroundColor: 'rgba(255, 255, 255, 0.9)', // Use a translucent white for glass effect
|
||
backdropFilter: 'blur(40px)', // Glass blur
|
||
WebkitBackdropFilter: 'blur(40px)',
|
||
boxShadow: '0 18px 45px rgba(15,23,42,0.45)',
|
||
}}
|
||
>
|
||
<div className="absolute -top-24 -right-24 size-56 rounded-full bg-gradient-to-br from-indigo-500/10 to-fuchsia-500/10 blur-3xl pointer-events-none" />
|
||
<div className="relative">
|
||
<div className="mx-auto max-w-2xl text-center mb-8">
|
||
<h1 className="text-3xl sm:text-4xl font-extrabold tracking-tight text-[#0F172A]">
|
||
Register now
|
||
</h1>
|
||
<p className="mt-3 text-slate-700 text-base sm:text-lg">
|
||
Create your personal or company account with Profit Planet.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="mt-2">
|
||
{showSessionModal ? (
|
||
<div className="flex items-center justify-center">
|
||
<SessionDetectedModal
|
||
inline
|
||
open
|
||
onLogout={handleLogout}
|
||
onCancel={handleCancel}
|
||
/>
|
||
</div>
|
||
) : (
|
||
<>
|
||
{(!user || sessionCleared) && (
|
||
<RegisterForm
|
||
mode={mode}
|
||
setMode={setMode}
|
||
refToken={refToken}
|
||
onRegistered={() => setRegistered(true)}
|
||
referrerEmail={refInfo?.referrerEmail}
|
||
/>
|
||
)}
|
||
{registered && (
|
||
<div className="mt-6 mx-auto text-center text-sm text-gray-700">
|
||
Registration successful – redirecting...
|
||
</div>
|
||
)}
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</BackgroundShell>
|
||
</PageLayout>
|
||
)
|
||
}
|
||
|
||
// NEW: default export only provides the ToastProvider wrapper
|
||
export default function RegisterPage() {
|
||
return (
|
||
<ToastProvider>
|
||
<RegisterPageInner />
|
||
</ToastProvider>
|
||
)
|
||
} |