profit-planet-frontend/src/app/quickaction-dashboard/page.tsx

387 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useState, useCallback, useEffect } from 'react'
import { useRouter } from 'next/navigation'
import PageLayout from '../components/PageLayout'
import TutorialModal, { createTutorialSteps } from '../components/TutorialModal'
import useAuthStore from '../store/authStore'
import { useUserStatus } from '../hooks/useUserStatus'
import {
CheckCircleIcon,
XCircleIcon,
EnvelopeOpenIcon,
IdentificationIcon,
InformationCircleIcon,
DocumentCheckIcon,
ArrowUpOnSquareIcon,
PencilSquareIcon,
ClipboardDocumentCheckIcon,
AcademicCapIcon
} from '@heroicons/react/24/outline'
interface StatusItem {
key: string
label: string
description: string
complete: boolean
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>
}
export default function QuickActionDashboardPage() {
const router = useRouter()
const user = useAuthStore(s => s.user)
const { userStatus, loading, error, refreshStatus } = useUserStatus()
const [isClient, setIsClient] = useState(false)
// Tutorial state
const [isTutorialOpen, setIsTutorialOpen] = useState(false)
const [currentTutorialStep, setCurrentTutorialStep] = useState(1)
const [hasSeenTutorial, setHasSeenTutorial] = useState(false)
useEffect(() => {
setIsClient(true)
// Check if user has seen tutorial before
const tutorialSeen = localStorage.getItem('tutorial_seen')
setHasSeenTutorial(!!tutorialSeen)
}, [])
// Derive status from real backend data
const emailVerified = userStatus?.email_verified || false
const idUploaded = userStatus?.documents_uploaded || false
const additionalInfo = userStatus?.profile_completed || false
const contractSigned = userStatus?.contract_signed || false
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
}
]
// Action handlers - navigate to proper QuickAction pages
const handleVerifyEmail = useCallback(() => {
router.push('/quickaction-dashboard/register-email-verify')
}, [router])
const handleUploadId = useCallback(() => {
const userType = user?.userType || 'personal'
router.push(`/quickaction-dashboard/register-upload-id/${userType}`)
}, [router, user])
const handleCompleteInfo = useCallback(() => {
const userType = user?.userType || 'personal'
router.push(`/quickaction-dashboard/register-additional-information/${userType}`)
}, [router, user])
const handleSignContract = useCallback(() => {
const userType = user?.userType || 'personal'
router.push(`/quickaction-dashboard/register-sign-contract/${userType}`)
}, [router, user])
// Tutorial handlers
const startTutorial = useCallback(() => {
setCurrentTutorialStep(1)
setIsTutorialOpen(true)
}, [])
const closeTutorial = useCallback(() => {
setIsTutorialOpen(false)
localStorage.setItem('tutorial_seen', 'true')
setHasSeenTutorial(true)
}, [])
const nextTutorialStep = useCallback(() => {
setCurrentTutorialStep(prev => prev + 1)
}, [])
const previousTutorialStep = useCallback(() => {
setCurrentTutorialStep(prev => Math.max(1, prev - 1))
}, [])
// Auto-start tutorial for new users
useEffect(() => {
if (isClient && !hasSeenTutorial && !loading && userStatus) {
// Auto-start tutorial if user hasn't completed first step
if (!emailVerified) {
setTimeout(() => setIsTutorialOpen(true), 1000)
}
}
}, [isClient, hasSeenTutorial, loading, userStatus, emailVerified])
// Create tutorial steps
const tutorialSteps = createTutorialSteps(
emailVerified,
idUploaded,
additionalInfo,
contractSigned,
user?.userType || 'personal',
handleVerifyEmail,
handleUploadId,
handleCompleteInfo,
handleSignContract,
closeTutorial
)
const canSignContract = emailVerified && idUploaded && additionalInfo
// NEW: resend cooldown tracking (10 minutes like verify page)
const RESEND_INTERVAL_MS = 10 * 60 * 1000
const getStorageKey = (email?: string | null) => `emailVerify:lastSent:${email || 'anon'}`
const getLastSentAt = (email?: string | null) => {
if (typeof window === 'undefined') return 0
try { return parseInt(localStorage.getItem(getStorageKey(email)) || '0', 10) || 0 } catch { return 0 }
}
const [resendRemainingSec, setResendRemainingSec] = useState(0)
const formatMmSs = (total: number) => {
const m = Math.floor(total / 60)
const s = total % 60
return `${m}:${String(s).padStart(2, '0')}`
}
useEffect(() => {
if (!isClient || emailVerified) return
const last = getLastSentAt(user?.email)
const remainingMs = Math.max(0, RESEND_INTERVAL_MS - (Date.now() - last))
setResendRemainingSec(Math.ceil(remainingMs / 1000))
if (remainingMs <= 0) return
const id = setInterval(() => {
setResendRemainingSec(s => (s > 0 ? s - 1 : 0))
}, 1000)
return () => clearInterval(id)
}, [isClient, emailVerified, user?.email])
return (
<PageLayout>
<div className="min-h-screen bg-gray-50">
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Title */}
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900">
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'}
</p>
{loading && <p className="text-xs text-gray-500 mt-1">Loading status...</p>}
{error && (
<div className="mt-4 max-w-md rounded-md bg-red-50 border border-red-200 px-4 py-3">
<div className="flex">
<div className="flex-shrink-0">
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clipRule="evenodd" />
</svg>
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-red-800">
Error loading account status
</h3>
<div className="mt-2 text-sm text-red-700">
<p>{error}</p>
</div>
<div className="mt-4">
<button
onClick={() => refreshStatus()}
className="text-sm bg-red-100 text-red-800 px-3 py-1 rounded-md hover:bg-red-200 transition-colors"
>
Try again
</button>
</div>
</div>
</div>
</div>
)}
</div>
{/* Cards */}
<div className="space-y-8">
{/* 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
</h2>
<div className="grid gap-4 sm:gap-6 grid-cols-1 md:grid-cols-2 xl:grid-cols-4">
{statusItems.map(item => {
const CompleteIcon = item.complete ? CheckCircleIcon : XCircleIcon
return (
<div
key={item.key}
className={`rounded-lg px-4 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'
}`}
>
<CompleteIcon
className={`h-6 w-6 mb-4 ${item.complete ? 'text-emerald-600' : 'text-rose-600'}`}
/>
<span
className={`text-xs font-medium uppercase tracking-wide ${
item.complete ? 'text-emerald-700' : 'text-rose-700'
}`}
>
{item.label}
</span>
<span
className={`mt-1 text-xs font-semibold ${
item.complete ? 'text-emerald-600' : 'text-rose-600'
}`}
>
{item.description}
</span>
</div>
)
})}
</div>
</div>
{/* Quick Actions */}
<div className="bg-white rounded-xl shadow-sm ring-1 ring-gray-200 p-5 sm:p-8">
<div className="flex items-center justify-between mb-5">
<div className="flex items-center gap-2">
<span className="inline-flex items-center justify-center h-7 w-7 rounded-full bg-blue-100 text-blue-600 text-sm font-semibold">
i
</span>
<h2 className="text-sm sm:text-base font-semibold text-gray-900">
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>
</div>
<div className="grid gap-4 sm:gap-6 grid-cols-1 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-4 py-5 text-center border font-medium text-sm transition-all ${
emailVerified
? 'bg-emerald-50 border-emerald-100 text-emerald-600 cursor-default'
: 'bg-blue-600 hover:bg-blue-500 text-white border-blue-600 shadow'
}`}
>
<EnvelopeOpenIcon className="h-6 w-6 mb-2" />
{emailVerified ? 'Email Verified' : 'Verify Email'}
</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}
className={`relative flex flex-col items-center justify-center rounded-lg px-4 py-5 text-center border font-medium text-sm transition-all ${
idUploaded
? 'bg-emerald-50 border-emerald-100 text-emerald-600 cursor-default'
: 'bg-blue-600 hover:bg-blue-500 text-white border-blue-600 shadow'
}`}
>
<ArrowUpOnSquareIcon className="h-6 w-6 mb-2" />
{idUploaded ? 'ID Uploaded' : 'Upload ID Document'}
</button>
{/* Additional Info */}
<button
onClick={handleCompleteInfo}
className={`relative flex flex-col items-center justify-center rounded-lg px-4 py-5 text-center border font-medium text-sm transition-all ${
additionalInfo
? 'bg-emerald-50 border-emerald-100 text-emerald-600 cursor-default'
: 'bg-blue-600 hover:bg-blue-500 text-white border-blue-600 shadow'
}`}
>
<PencilSquareIcon className="h-6 w-6 mb-2" />
{additionalInfo ? 'Profile Completed' : 'Complete Profile'}
</button>
{/* 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-4 py-5 text-center border font-medium text-sm transition-all ${
contractSigned
? 'bg-emerald-50 border-emerald-100 text-emerald-600 cursor-default'
: canSignContract
? 'bg-blue-600 hover:bg-blue-500 text-white border-blue-600 shadow'
: 'bg-gray-300 text-gray-600 border-gray-300 cursor-not-allowed'
}`}
>
<ClipboardDocumentCheckIcon className="h-6 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>
{/* Latest News */}
<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-4">
Latest News
</h2>
<ul className="list-disc pl-5 space-y-2 text-sm text-gray-700">
<li>
<span className="font-medium text-[#8D6B1D]">New:</span> Referral system
launch invite friends and earn rewards.
</li>
<li>
Profile completion unlocks more features. Keep progressing!
</li>
</ul>
</div>
</div>
</main>
</div>
{/* Tutorial Modal */}
<TutorialModal
isOpen={isTutorialOpen}
onClose={closeTutorial}
currentStep={currentTutorialStep}
steps={tutorialSteps}
onNext={nextTutorialStep}
onPrevious={previousTutorialStep}
/>
</PageLayout>
)
}