'use client' import React, { useEffect } from 'react' import { useRouter } from 'next/navigation' import useAuthStore from '../store/authStore' import PageLayout from '../components/PageLayout' import BlueBlurryBackground from '../components/background/blueblurry' import ProfileCompletion from './components/profileCompletion' import BasicInformation from './components/basicInformation' import MediaSection from './components/mediaSection' import BankInformation from './components/bankInformation' import EditModal from './components/editModal' import { getProfileCompletion } from './hooks/getProfileCompletion' import { useProfileData } from './hooks/getProfileData' import { useMedia } from './hooks/getMedia' import { editProfileBasic } from './hooks/editProfile' import { authFetch } from '../utils/authFetch' // Helper to display missing fields in subtle gray italic (no yellow highlight) function HighlightIfMissing({ value, children }: { value: any, children: React.ReactNode }) { if (value === null || value === undefined || value === '') { return ( Not provided ); } return <>{children}; } // Helper for safe access to profileData fields function getProfileField( obj: typeof defaultProfileData, key: T ) { return obj[key]; } // Default profile data for typing const defaultProfileData = { firstName: '', lastName: '', email: '', phone: '', address: '', joinDate: '', memberStatus: '', profileComplete: 0, accountHolder: '', iban: '', contactPersonName: '', userType: '', }; // Define fields for EditModal const basicFields = [ { key: 'firstName', label: 'First Name', type: 'text' }, { key: 'lastName', label: 'Last Name', type: 'text' }, { key: 'phone', label: 'Phone', type: 'text' }, { key: 'address', label: 'Address', type: 'text' }, ]; const bankFields = [ { key: 'accountHolder', label: 'Account Holder', type: 'text' }, { key: 'iban', label: 'IBAN', type: 'text' }, ]; const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001' export default function ProfilePage() { const router = useRouter() const user = useAuthStore(state => state.user) const isAuthReady = useAuthStore(state => state.isAuthReady) const [hasHydrated, setHasHydrated] = React.useState(false) const [userId, setUserId] = React.useState(undefined) // --- declare ALL hooks before any early return (Rules of Hooks) --- const [refreshKey, setRefreshKey] = React.useState(0) const [showRefreshing, setShowRefreshing] = React.useState(false) const [completionLoading, setCompletionLoading] = React.useState(false) // Progress bar state (MOVED ABOVE EARLY RETURN) const [progressPercent, setProgressPercent] = React.useState(0) const [completedSteps, setCompletedSteps] = React.useState([]) const [allSteps, setAllSteps] = React.useState([]) // Bank/edit state (keep, but bank editing disabled) const [bankInfo, setBankInfo] = React.useState({ accountHolder: '', iban: '' }) const [editingBank, setEditingBank] = React.useState(false) const [bankDraft, setBankDraft] = React.useState(bankInfo) const [editModalOpen, setEditModalOpen] = React.useState(false) const [editModalType, setEditModalType] = React.useState<'basic' | 'bank'>('basic') const [editModalValues, setEditModalValues] = React.useState>({}) const [editModalError, setEditModalError] = React.useState(null) const [downloadLoading, setDownloadLoading] = React.useState(false) const [downloadError, setDownloadError] = React.useState(null) useEffect(() => { setHasHydrated(true) }, []) useEffect(() => { if (user?.id) setUserId(user.id) }, [user]) // Fetch hooks can run with undefined userId; they should handle it internally const { data: profileDataApi, loading: profileLoading } = useProfileData(userId, refreshKey) const { data: mediaData, loading: mediaLoading } = useMedia(userId, refreshKey) // Redirect only after hydration + auth ready useEffect(() => { if (!hasHydrated || !isAuthReady) return if (!user) router.replace('/login') }, [hasHydrated, isAuthReady, user, router]) // Completion fetch (gated inside effect) useEffect(() => { if (!hasHydrated || !isAuthReady || !user) return let cancelled = false ;(async () => { setCompletionLoading(true) const progress = await getProfileCompletion() if (cancelled) return if (progress && typeof progress === 'object') { const pct = progress.progressPercent ?? 0 const isAdminVerified = Boolean(profileDataApi?.userStatus?.is_admin_verified) setProgressPercent(isAdminVerified ? pct : Math.min(pct, 95)) setCompletedSteps(progress.completedSteps ?? []) setAllSteps(progress.steps?.map((s: any) => s.name || s.title || '') ?? []) } else if (typeof progress === 'number') { setProgressPercent(progress) } setCompletionLoading(false) })() return () => { cancelled = true } }, [hasHydrated, isAuthReady, user, refreshKey, profileDataApi?.userStatus?.is_admin_verified]) useEffect(() => { const verified = Boolean(profileDataApi?.userStatus?.is_admin_verified) if (verified) setProgressPercent(prev => (prev < 100 ? 100 : prev)) }, [profileDataApi?.userStatus?.is_admin_verified]) const profileData = React.useMemo(() => { if (!profileDataApi) { return { firstName: 'Admin', lastName: 'User', email: user?.email || 'office@profit-planet.com', phone: '+49 123 456 789', address: 'Musterstraße 123, 12345 Berlin', joinDate: 'Oktober 2024', memberStatus: 'Gold Member', profileComplete: progressPercent, accountHolder: '', iban: '', contactPersonName: '', userType: user?.userType || '', } } const { user: apiUser = {}, profile: apiProfile = {}, userStatus = {} } = profileDataApi return { firstName: apiUser.firstName ?? apiProfile.first_name ?? '', lastName: apiUser.lastName ?? apiProfile.last_name ?? '', email: apiUser.email ?? '', phone: apiUser.phone ?? apiProfile.phone ?? '', address: apiProfile.address ?? '', joinDate: apiUser.createdAt ? new Date(apiUser.createdAt).toLocaleDateString('de-DE', { year: 'numeric', month: 'long' }) : '', memberStatus: userStatus.status ?? '', profileComplete: progressPercent, accountHolder: apiProfile.account_holder_name ?? '', iban: apiUser.iban ?? '', contactPersonName: apiProfile.contact_person_name ?? '', userType: apiUser.userType ?? '', } }, [profileDataApi, user, progressPercent]) const documents = Array.isArray(mediaData?.documents) ? mediaData.documents : [] useEffect(() => { if (showRefreshing && !profileLoading && !mediaLoading && !completionLoading) { const t = setTimeout(() => setShowRefreshing(false), 200) return () => clearTimeout(t) } }, [showRefreshing, profileLoading, mediaLoading, completionLoading]) function openEditModal(type: 'basic' | 'bank', values: Record) { setEditModalType(type) setEditModalValues(values) setEditModalOpen(true) } async function handleEditModalSave() { setEditModalError(null) if (editModalType === 'basic') { const payload: Partial = {} ;(['firstName', 'lastName', 'phone', 'address'] as const).forEach(key => { if (editModalValues[key] !== getProfileField(profileData, key)) { payload[key] = editModalValues[key]?.trim() } }) const res = await editProfileBasic(payload) if (res.success) { setEditModalOpen(false) setShowRefreshing(true) setRefreshKey(k => k + 1) } else if (res.status === 401) { router.push('/login') } else { setEditModalError(res.error || 'Failed to update profile.') } } else { setEditModalError('Bank information editing is disabled for now.') } } function handleEditModalChange(key: string, value: string) { setEditModalValues(prev => ({ ...prev, [key]: value })) } async function handleDownloadAccountData() { if (!userId) return setDownloadError(null) setDownloadLoading(true) try { const fullRes = await authFetch(`${BASE_URL}/api/users/${userId}/full`, { method: 'GET' }) if (fullRes.status === 401) { router.push('/login') return } if (!fullRes.ok) { throw new Error('Failed to fetch account data') } const fullData = await fullRes.json() const statusOnly = fullData?.userStatus?.status ?? null const cleanedUser = fullData?.user && typeof fullData.user === 'object' ? (() => { const { id, user_id, ...rest } = fullData.user as Record return rest })() : fullData?.user const cleanedProfile = fullData?.profile && typeof fullData.profile === 'object' ? (() => { const { id, user_id, ...rest } = fullData.profile as Record return rest })() : fullData?.profile const exportPayload = { exportedAt: new Date().toISOString(), userType: user?.userType || null, account: { success: fullData?.success ?? true, user: cleanedUser, profile: cleanedProfile, userStatus: statusOnly } } const blob = new Blob([JSON.stringify(exportPayload, null, 2)], { type: 'application/json' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `profit-planet-account-data-${userId}-${new Date().toISOString().slice(0, 10)}.json` document.body.appendChild(a) a.click() a.remove() URL.revokeObjectURL(url) } catch (error: any) { setDownloadError(error?.message || 'Failed to download account data.') } finally { setDownloadLoading(false) } } // --- EARLY RETURN AFTER ALL HOOKS --- if (!hasHydrated || !isAuthReady || !user) { return (

Loading...

) } return (
{/* MASTER GLASS PANEL (prevents non-translucent gaps between cards) */}
{/* Page Header */}

Profile Settings

Manage your account information and preferences

{/* Pending admin verification notice (above progress) */} {profileDataApi?.userStatus && profileDataApi.userStatus.is_admin_verified === 0 && (
Your account is fully submitted. Our team will verify your account shortly.
)} {/* Basic Info + Sidebar */}
{/* Basic Information */}
openEditModal('basic', { firstName: profileData.firstName, lastName: profileData.lastName, phone: profileData.phone, address: profileData.address, })} />
{/* Sidebar: Account Status + Quick Actions */}
{/* Account Status (make translucent) */}

Account Status

Member Since {profileData.joinDate}
Status {profileData.memberStatus}
Profile Verified
{/* Quick Actions (make translucent) */}

Quick Actions

{downloadError && (

{downloadError}

)}
{/* Bank Info, Media */}

My Subscriptions

View your active subscriptions, included items and subscription details on a dedicated page.

{/* --- Edit Bank Information Section --- */} openEditModal('bank', { ... })} /> {/* --- Media Section --- */}
{/* Edit Modal */} { setEditModalOpen(false); setEditModalError(null); }} > {/* Show error message if present */} {editModalError && (
{editModalError}
)}
) }