feat: enhance user flow for guest and regular users in quick action and dashboard pages

This commit is contained in:
Seazn 2026-03-15 01:11:57 +01:00
parent 6a9b9f851d
commit 60057b6c94
3 changed files with 200 additions and 133 deletions

View File

@ -78,16 +78,20 @@ export default function DashboardPage() {
}
}, [isAuthReady, user, router])
// NEW: block dashboard unless all 4 quickaction steps are completed
// NEW: block dashboard unless all quickaction steps are completed
// For guest users: only email verification is required
// For regular users: all 4 steps must be completed
useEffect(() => {
if (!isAuthReady || !user) return
if (statusLoading || !userStatus) return
const allDone =
!!userStatus.email_verified &&
!!userStatus.documents_uploaded &&
!!userStatus.profile_completed &&
!!userStatus.contract_signed
const isGuest = user?.role === 'guest'
const allDone = isGuest
? !!userStatus.email_verified
: !!userStatus.email_verified &&
!!userStatus.documents_uploaded &&
!!userStatus.profile_completed &&
!!userStatus.contract_signed
if (!allDone) smoothReplace('/quickaction-dashboard')
}, [isAuthReady, user, statusLoading, userStatus, smoothReplace])
@ -116,13 +120,15 @@ export default function DashboardPage() {
)
}
// NEW: final guard (dont render dashboard if not all done)
// Final guard (don't render dashboard if not all done)
if (!userStatus) return null
const allDone =
!!userStatus.email_verified &&
!!userStatus.documents_uploaded &&
!!userStatus.profile_completed &&
!!userStatus.contract_signed
const isGuestUser = user?.role === 'guest'
const allDone = isGuestUser
? !!userStatus.email_verified
: !!userStatus.email_verified &&
!!userStatus.documents_uploaded &&
!!userStatus.profile_completed &&
!!userStatus.contract_signed
if (!allDone) return null
// Get user name

View File

@ -90,8 +90,13 @@ export default function QuickActionDashboardPage() {
const additionalInfo = userStatus?.profile_completed || false
const contractSigned = userStatus?.contract_signed || false
// NEW: if everything is done, quickaction-dashboard is no longer accessible
const allDone = emailVerified && idUploaded && additionalInfo && contractSigned
// Detect guest user — guests only need email verification
const isGuest = isClient && user?.role === 'guest'
// For guests: only email verification matters. For regular users: all 4 steps.
const allDone = isGuest
? emailVerified
: emailVerified && idUploaded && additionalInfo && contractSigned
// NEW: smooth redirect (prevents snappy double navigation)
const [redirectTo, setRedirectTo] = useState<string | null>(null)
const redirectOnceRef = useRef(false)
@ -167,36 +172,47 @@ export default function QuickActionDashboardPage() {
sessionStorage.setItem(sessionKey, '1')
}, [isClient, loading, userStatus, isTutorialOpen, user, accessToken, getNextTutorialStep])
const statusItems: StatusItem[] = [
{
key: 'email',
label: 'Email Verification',
description: emailVerified ? 'Verified' : 'Missing',
complete: emailVerified,
icon: EnvelopeOpenIcon
},
{
key: 'id',
label: 'ID Document',
description: idUploaded ? 'Uploaded' : 'Missing',
complete: idUploaded,
icon: IdentificationIcon
},
{
key: 'info',
label: 'Additional Info',
description: additionalInfo ? 'Completed' : 'Missing',
complete: additionalInfo,
icon: InformationCircleIcon
},
{
key: 'contract',
label: 'Contract',
description: contractSigned ? 'Signed' : 'Missing',
complete: contractSigned,
icon: DocumentCheckIcon
}
]
// For guests: only show email verification step. For regular users: all 4 steps.
const statusItems: StatusItem[] = isGuest
? [
{
key: 'email',
label: 'Email Verification',
description: emailVerified ? 'Verified' : 'Missing',
complete: emailVerified,
icon: EnvelopeOpenIcon
}
]
: [
{
key: 'email',
label: 'Email Verification',
description: emailVerified ? 'Verified' : 'Missing',
complete: emailVerified,
icon: EnvelopeOpenIcon
},
{
key: 'id',
label: 'ID Document',
description: idUploaded ? 'Uploaded' : 'Missing',
complete: idUploaded,
icon: IdentificationIcon
},
{
key: 'info',
label: 'Additional Info',
description: additionalInfo ? 'Completed' : 'Missing',
complete: additionalInfo,
icon: InformationCircleIcon
},
{
key: 'contract',
label: 'Contract',
description: contractSigned ? 'Signed' : 'Missing',
complete: contractSigned,
icon: DocumentCheckIcon
}
]
// Action handlers - navigate to proper QuickAction pages with tutorial callback
const handleVerifyEmail = useCallback(() => {
@ -313,7 +329,11 @@ export default function QuickActionDashboardPage() {
Welcome{isClient && user?.firstName ? `, ${user.firstName}` : ''}!
</h1>
<p className="text-sm sm:text-base text-gray-600 mt-2">
{isClient && user?.userType === 'company' ? 'Company Account' : 'Personal Account'}
{isGuest
? 'Guest Account'
: isClient && user?.userType === 'company'
? 'Company Account'
: 'Personal Account'}
</p>
{loading && <p className="text-xs text-gray-500 mt-1">Loading status...</p>}
{error && (
@ -350,11 +370,14 @@ export default function QuickActionDashboardPage() {
{/* Status Overview */}
<div className="bg-white rounded-xl shadow-sm ring-1 ring-gray-200 p-5 sm:p-8">
<h2 className="text-sm sm:text-base font-semibold text-gray-900 mb-5">
Status Overview
{isGuest ? 'Email Verification Status' : 'Status Overview'}
</h2>
{/* CHANGED: mobile 2x2 grid */}
<div className="grid grid-cols-2 gap-3 sm:gap-6 md:grid-cols-2 xl:grid-cols-4">
{/* Guest: single centered card. Regular: 2x2 / 4-col grid */}
<div className={isGuest
? 'flex justify-center'
: 'grid grid-cols-2 gap-3 sm:gap-6 md:grid-cols-2 xl:grid-cols-4'
}>
{statusItems.map(item => {
const CompleteIcon = item.complete ? CheckCircleIcon : XCircleIcon
return (
@ -362,7 +385,7 @@ export default function QuickActionDashboardPage() {
key={item.key}
className={`rounded-lg px-3 py-4 sm:px-4 sm:py-6 flex flex-col items-center text-center border transition-colors ${
item.complete ? 'bg-emerald-50 border-emerald-100' : 'bg-rose-50 border-rose-100'
}`}
} ${isGuest ? 'w-full max-w-xs' : ''}`}
>
<CompleteIcon
className={`h-5 w-5 sm:h-6 sm:w-6 mb-3 sm:mb-4 ${
@ -397,98 +420,130 @@ export default function QuickActionDashboardPage() {
i
</span>
<h2 className="text-sm sm:text-base font-semibold text-gray-900">
Quick Actions
{isGuest ? 'Action Required' : 'Quick Actions'}
</h2>
</div>
<button
onClick={startTutorial}
className="relative inline-flex items-center gap-2 rounded-md bg-blue-50 px-3 py-2 text-sm font-medium text-blue-700 hover:bg-blue-100 transition-colors"
>
<AcademicCapIcon className="h-4 w-4" />
Tutorial
{!hasSeenTutorial && (
<span className="absolute -top-1 -right-1 h-3 w-3 bg-red-500 rounded-full animate-pulse" />
)}
</button>
{/* Tutorial button — only for regular users */}
{!isGuest && (
<button
onClick={startTutorial}
className="relative inline-flex items-center gap-2 rounded-md bg-blue-50 px-3 py-2 text-sm font-medium text-blue-700 hover:bg-blue-100 transition-colors"
>
<AcademicCapIcon className="h-4 w-4" />
Tutorial
{!hasSeenTutorial && (
<span className="absolute -top-1 -right-1 h-3 w-3 bg-red-500 rounded-full animate-pulse" />
)}
</button>
)}
</div>
{/* CHANGED: mobile 2x2 grid (order already matches desired layout) */}
<div className="grid grid-cols-2 gap-3 sm:gap-6 md:grid-cols-2 xl:grid-cols-4">
{/* Email Verification */}
<div className="flex flex-col">
{isGuest ? (
/* ── Guest view: single email verification action ── */
<div className="flex flex-col items-center text-center">
<div className="max-w-sm w-full">
<p className="text-sm text-gray-600 mb-6">
Please verify your email address to activate your guest account and access your subscriptions.
</p>
<button
onClick={handleVerifyEmail}
disabled={emailVerified}
className={`w-full relative flex flex-col items-center justify-center rounded-lg px-4 py-8 text-center border font-medium text-sm transition-all md:transition-transform md:duration-200 ${
emailVerified
? 'bg-emerald-50 border-emerald-100 text-emerald-600 cursor-default'
: 'bg-blue-600 text-white border-blue-600 shadow md:hover:-translate-y-0.5 md:hover:shadow-lg md:hover:bg-blue-500 md:active:translate-y-0'
}`}
>
<EnvelopeOpenIcon className="h-6 w-6 sm:h-8 sm:w-8 mb-3" />
{emailVerified ? 'Email Verified ✓' : 'Verify Email'}
</button>
{!emailVerified && (
<p className="mt-3 text-xs text-[#112c55]">
{resendRemainingSec > 0
? `Resend available in ${formatMmSs(resendRemainingSec)}`
: 'You can request a new code now'}
</p>
)}
</div>
</div>
) : (
/* ── Regular user view: all 4 quick action buttons ── */
<div className="grid grid-cols-2 gap-3 sm:gap-6 md:grid-cols-2 xl:grid-cols-4">
{/* Email Verification */}
<div className="flex flex-col">
<button
onClick={handleVerifyEmail}
disabled={emailVerified}
className={`relative flex flex-col items-center justify-center rounded-lg px-3 py-5 sm:px-4 sm:py-8 text-center border font-medium text-xs sm:text-sm transition-all md:transition-transform md:duration-200 ${
emailVerified
? 'bg-emerald-50 border-emerald-100 text-emerald-600 cursor-default'
: 'bg-blue-600 text-white border-blue-600 shadow md:hover:-translate-y-0.5 md:hover:shadow-lg md:hover:bg-blue-500 md:active:translate-y-0'
}`}
>
<EnvelopeOpenIcon className="h-5 w-5 sm:h-6 sm:w-6 mb-2" />
{emailVerified ? 'Email Verified' : 'Verify Email'}
</button>
{!emailVerified && (
<p className="mt-2 text-[11px] text-[#112c55] text-center">
{resendRemainingSec > 0
? `Resend available in ${formatMmSs(resendRemainingSec)}`
: 'You can request a new code now'}
</p>
)}
</div>
{/* ID Upload */}
<button
onClick={handleVerifyEmail}
disabled={emailVerified}
className={`relative flex flex-col items-center justify-center rounded-lg px-3 py-5 sm:px-4 sm:py-8 text-center border font-medium text-xs sm:text-sm transition-all md:transition-transform md:duration-200 ${
emailVerified
onClick={handleUploadId}
disabled={idUploaded}
className={`relative flex flex-col items-center justify-center rounded-lg px-3 py-5 sm:px-4 sm:py-5 text-center border font-medium text-xs sm:text-sm transition-all md:transition-transform md:duration-200 ${
idUploaded
? 'bg-emerald-50 border-emerald-100 text-emerald-600 cursor-default'
: 'bg-blue-600 text-white border-blue-600 shadow md:hover:-translate-y-0.5 md:hover:shadow-lg md:hover:bg-blue-500 md:active:translate-y-0'
}`}
>
<EnvelopeOpenIcon className="h-5 w-5 sm:h-6 sm:w-6 mb-2" />
{emailVerified ? 'Email Verified' : 'Verify Email'}
<ArrowUpOnSquareIcon className="h-5 w-5 sm:h-6 sm:w-6 mb-2" />
{idUploaded ? 'ID Uploaded' : 'Upload ID Document'}
</button>
{/* NEW: resend feedback (only when not verified) */}
{!emailVerified && (
<p className="mt-2 text-[11px] text-[#112c55] text-center">
{resendRemainingSec > 0
? `Resend available in ${formatMmSs(resendRemainingSec)}`
: 'You can request a new code now'}
</p>
)}
</div>
{/* ID Upload */}
<button
onClick={handleUploadId}
disabled={idUploaded}
className={`relative flex flex-col items-center justify-center rounded-lg px-3 py-5 sm:px-4 sm:py-5 text-center border font-medium text-xs sm:text-sm transition-all md:transition-transform md:duration-200 ${
idUploaded
? 'bg-emerald-50 border-emerald-100 text-emerald-600 cursor-default'
: 'bg-blue-600 text-white border-blue-600 shadow md:hover:-translate-y-0.5 md:hover:shadow-lg md:hover:bg-blue-500 md:active:translate-y-0'
}`}
>
<ArrowUpOnSquareIcon className="h-5 w-5 sm:h-6 sm:w-6 mb-2" />
{idUploaded ? 'ID Uploaded' : 'Upload ID Document'}
</button>
{/* Additional Info */}
<button
onClick={handleCompleteInfo}
disabled={additionalInfo}
className={`relative flex flex-col items-center justify-center rounded-lg px-3 py-5 sm:px-4 sm:py-5 text-center border font-medium text-xs sm:text-sm transition-all md:transition-transform md:duration-200 ${
additionalInfo
? 'bg-emerald-50 border-emerald-100 text-emerald-600 cursor-default'
: 'bg-blue-600 text-white border-blue-600 shadow md:hover:-translate-y-0.5 md:hover:shadow-lg md:hover:bg-blue-500 md:active:translate-y-0'
}`}
>
<PencilSquareIcon className="h-5 w-5 sm:h-6 sm:w-6 mb-2" />
{additionalInfo ? 'Profile Completed' : 'Complete Profile'}
</button>
{/* Sign Contract */}
<div className="flex flex-col">
{/* Additional Info */}
<button
onClick={handleSignContract}
disabled={!canSignContract || contractSigned}
className={`flex flex-col flex-1 items-center justify-center rounded-lg px-3 py-5 sm:px-4 sm:py-5 text-center border font-medium text-xs sm:text-sm transition-all md:transition-transform md:duration-200 ${
contractSigned
onClick={handleCompleteInfo}
disabled={additionalInfo}
className={`relative flex flex-col items-center justify-center rounded-lg px-3 py-5 sm:px-4 sm:py-5 text-center border font-medium text-xs sm:text-sm transition-all md:transition-transform md:duration-200 ${
additionalInfo
? 'bg-emerald-50 border-emerald-100 text-emerald-600 cursor-default'
: canSignContract
? 'bg-blue-600 text-white border-blue-600 shadow md:hover:-translate-y-0.5 md:hover:shadow-lg md:hover:bg-blue-500 md:active:translate-y-0'
: 'bg-gray-300 text-gray-600 border-gray-300 cursor-not-allowed'
: 'bg-blue-600 text-white border-blue-600 shadow md:hover:-translate-y-0.5 md:hover:shadow-lg md:hover:bg-blue-500 md:active:translate-y-0'
}`}
>
<ClipboardDocumentCheckIcon className="h-5 w-5 sm:h-6 sm:w-6 mb-2" />
{contractSigned ? 'Contract Signed' : 'Sign Contract'}
<PencilSquareIcon className="h-5 w-5 sm:h-6 sm:w-6 mb-2" />
{additionalInfo ? 'Profile Completed' : 'Complete Profile'}
</button>
{!canSignContract && !contractSigned && (
<p className="mt-2 text-[11px] text-red-600 leading-snug text-center">
Complete previous steps (email, ID, profile) before signing the contract.
</p>
)}
{/* Sign Contract */}
<div className="flex flex-col">
<button
onClick={handleSignContract}
disabled={!canSignContract || contractSigned}
className={`flex flex-col flex-1 items-center justify-center rounded-lg px-3 py-5 sm:px-4 sm:py-5 text-center border font-medium text-xs sm:text-sm transition-all md:transition-transform md:duration-200 ${
contractSigned
? 'bg-emerald-50 border-emerald-100 text-emerald-600 cursor-default'
: canSignContract
? 'bg-blue-600 text-white border-blue-600 shadow md:hover:-translate-y-0.5 md:hover:shadow-lg md:hover:bg-blue-500 md:active:translate-y-0'
: 'bg-gray-300 text-gray-600 border-gray-300 cursor-not-allowed'
}`}
>
<ClipboardDocumentCheckIcon className="h-5 w-5 sm:h-6 sm:w-6 mb-2" />
{contractSigned ? 'Contract Signed' : 'Sign Contract'}
</button>
{!canSignContract && !contractSigned && (
<p className="mt-2 text-[11px] text-red-600 leading-snug text-center">
Complete previous steps (email, ID, profile) before signing the contract.
</p>
)}
</div>
</div>
</div>
)}
</div>
{/* Latest News */}

View File

@ -236,7 +236,9 @@ export default function EmailVerifyPage() {
message: 'Your email has been verified successfully.'
})
await refreshStatus()
window.location.href = '/quickaction-dashboard?tutorial=true'
// Guests go directly to dashboard after email verification
const isGuest = user?.role === 'guest'
window.location.href = isGuest ? '/dashboard' : '/quickaction-dashboard?tutorial=true'
} else {
const msg = data.error || 'Verification failed. Please try again.'
setError(msg)
@ -345,18 +347,22 @@ export default function EmailVerifyPage() {
useEffect(() => {
if (statusLoading || !userStatus) return
const allDone =
!!userStatus.email_verified &&
!!userStatus.documents_uploaded &&
!!userStatus.profile_completed &&
!!userStatus.contract_signed
const isGuest = user?.role === 'guest'
const allDone = isGuest
? !!userStatus.email_verified
: !!userStatus.email_verified &&
!!userStatus.documents_uploaded &&
!!userStatus.profile_completed &&
!!userStatus.contract_signed
if (allDone) {
smoothReplace('/dashboard') // CHANGED
smoothReplace('/dashboard')
} else if (userStatus.email_verified) {
smoothReplace('/quickaction-dashboard') // CHANGED
// Regular users go back to quickaction dashboard for remaining steps
// Guests should never reach here since allDone covers them
smoothReplace('/quickaction-dashboard')
}
}, [statusLoading, userStatus, smoothReplace])
}, [statusLoading, userStatus, user, smoothReplace])
// NEW: must be logged in
useEffect(() => {