feat: enhance user flow for guest and regular users in quick action and dashboard pages
This commit is contained in:
parent
6a9b9f851d
commit
60057b6c94
@ -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 (don’t 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
|
||||
|
||||
@ -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 */}
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user