profit-planet-frontend/src/app/quickaction-dashboard/page.tsx
seaznCode 25fff9b1c3 feat: Implement user status management with custom hook
- Added `useUserStatus` hook to manage user status fetching and state.
- Integrated user status in Quick Action Dashboard and related pages.
- Enhanced error handling and loading states for user status.
- Updated profile completion and document upload flows to refresh user status after actions.
- Created a centralized API utility for handling requests and responses.
- Refactored authentication token management to use session storage.
2025-10-11 19:47:07 +02:00

329 lines
14 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 useAuthStore from '../store/authStore'
import { useUserStatus } from '../hooks/useUserStatus'
import {
CheckCircleIcon,
XCircleIcon,
EnvelopeOpenIcon,
IdentificationIcon,
InformationCircleIcon,
DocumentCheckIcon,
ArrowUpOnSquareIcon,
PencilSquareIcon,
ClipboardDocumentCheckIcon
} 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)
useEffect(() => {
setIsClient(true)
}, [])
// 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])
const canUploadId = emailVerified
const canCompleteInfo = emailVerified && idUploaded
const canSignContract = emailVerified && idUploaded && additionalInfo
return (
<PageLayout>
<div className="relative min-h-screen w-full px-3 sm:px-4 py-10">
{/* Background Pattern */}
<svg
aria-hidden="true"
className="absolute inset-0 -z-10 h-full w-full stroke-white/10"
>
<defs>
<pattern
x="50%"
y={-1}
id="affiliate-pattern"
width={200}
height={200}
patternUnits="userSpaceOnUse"
>
<path d="M.5 200V.5H200" fill="none" stroke="rgba(255,255,255,0.05)" />
</pattern>
</defs>
<rect fill="url(#affiliate-pattern)" width="100%" height="100%" strokeWidth={0} />
</svg>
{/* Colored Blur Effect */}
<div
aria-hidden="true"
className="absolute top-0 right-0 left-1/2 -z-10 -ml-24 transform-gpu overflow-hidden blur-3xl lg:ml-24 xl:ml-48"
>
<div
style={{
clipPath:
'polygon(63.1% 29.5%, 100% 17.1%, 76.6% 3%, 48.4% 0%, 44.6% 4.7%, 54.5% 25.3%, 59.8% 49%, 55.2% 57.8%, 44.4% 57.2%, 27.8% 47.9%, 35.1% 81.5%, 0% 97.7%, 39.2% 100%, 35.2% 81.4%, 97.2% 52.8%, 63.1% 29.5%)',
}}
className="aspect-[801/1036] w-[50.0625rem] bg-gradient-to-tr from-[#ff80b5] to-[#9089fc] opacity-50"
/>
</div>
{/* Gradient base */}
<div className="absolute inset-0 -z-20 bg-gradient-to-b from-gray-900/95 via-gray-900/80 to-gray-900" />
<div className="max-w-6xl mx-auto">
{/* Welcome */}
<div className="text-center mb-8">
<h1 className="text-3xl sm:text-4xl font-bold text-white tracking-tight">
Welcome{isClient && user?.firstName ? `, ${user.firstName}` : ''}!
</h1>
<p className="text-sm sm:text-base text-blue-100 font-medium mt-2">
{isClient && user?.userType === 'company' ? 'Company Account' : 'Personal Account'}
</p>
{loading && (
<p className="text-xs text-blue-200 mt-1">Loading status...</p>
)}
{error && (
<div className="mt-4 max-w-md mx-auto 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>
{/* Outer container card mimic (like screenshot) */}
<section className="bg-[#f8f9fc] rounded-xl shadow-sm ring-1 ring-black/5">
<div className="p-4 sm:p-6">
<div className="bg-white rounded-xl shadow-sm ring-1 ring-black/5 p-5 sm:p-8 space-y-12">
{/* Status Overview */}
<div>
<h2 className="text-sm sm:text-base font-semibold text-[#112c55] 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>
{/* Divider */}
<div className="h-px w-full bg-gray-200" />
{/* Quick Actions */}
<div>
<div className="flex items-center gap-2 mb-5">
<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-[#112c55]">
Quick Actions
</h2>
</div>
<div className="grid gap-4 sm:gap-6 grid-cols-1 md:grid-cols-2 xl:grid-cols-4">
{/* Email Verification */}
<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>
{/* ID Upload */}
<button
onClick={handleUploadId}
disabled={!canUploadId || idUploaded}
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'
: canUploadId
? 'bg-blue-600 hover:bg-blue-500 text-white border-blue-600 shadow'
: 'bg-gray-200 text-gray-500 border-gray-200 cursor-not-allowed'
}`}
>
<ArrowUpOnSquareIcon className="h-6 w-6 mb-2" />
{idUploaded ? 'ID Uploaded' : 'Upload ID Document'}
</button>
{/* Additional Info */}
<button
onClick={handleCompleteInfo}
disabled={!canCompleteInfo || additionalInfo}
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'
: canCompleteInfo
? 'bg-blue-600 hover:bg-blue-500 text-white border-blue-600 shadow'
: 'bg-gray-200 text-gray-500 border-gray-200 cursor-not-allowed'
}`}
>
<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>
{/* Divider */}
<div className="h-px w-full bg-gray-200" />
{/* Latest News */}
<div>
<h2 className="text-sm sm:text-base font-semibold text-[#112c55] mb-4">
Latest News
</h2>
<ul className="list-disc pl-5 space-y-2 text-sm text-[#1b3358]">
<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>
</div>
</section>
</div>
</div>
</PageLayout>
)
}