diff --git a/src/app/profile/components/bankInformation.tsx b/src/app/profile/components/bankInformation.tsx new file mode 100644 index 0000000..af97c81 --- /dev/null +++ b/src/app/profile/components/bankInformation.tsx @@ -0,0 +1,90 @@ +import React from 'react' + +export default function BankInformation({ + profileData, + editingBank, + bankDraft, + setEditingBank, + setBankDraft, + setBankInfo, + onEdit, +}: { + profileData: any, + editingBank: boolean, + bankDraft: { accountHolder: string, iban: string }, + setEditingBank: (v: boolean) => void, + setBankDraft: (v: { accountHolder: string, iban: string }) => void, + setBankInfo: (v: { accountHolder: string, iban: string }) => void, + onEdit?: () => void +}) { + return ( +
+
+

Bank Information

+ {!editingBank && ( + + )} +
+
{ + e.preventDefault() + setBankInfo(bankDraft) + setEditingBank(false) + }} + > +
+ + setBankDraft({ ...bankDraft, accountHolder: e.target.value })} + disabled={!editingBank} + placeholder={profileData.accountHolder ? '' : 'Not provided'} + /> + {!editingBank && !profileData.accountHolder && ( +
Not provided
+ )} +
+
+ + setBankDraft({ ...bankDraft, iban: e.target.value })} + disabled={!editingBank} + placeholder={profileData.iban ? '' : 'Not provided'} + /> + {!editingBank && !profileData.iban && ( +
Not provided
+ )} +
+ {editingBank && ( +
+ + +
+ )} +
+
+ ) +} diff --git a/src/app/profile/components/basicInformation.tsx b/src/app/profile/components/basicInformation.tsx new file mode 100644 index 0000000..e406d02 --- /dev/null +++ b/src/app/profile/components/basicInformation.tsx @@ -0,0 +1,83 @@ +import React from 'react' +import { UserCircleIcon, EnvelopeIcon, PhoneIcon, MapPinIcon, PencilIcon, CheckCircleIcon } from '@heroicons/react/24/outline' + +export default function BasicInformation({ profileData, HighlightIfMissing, onEdit }: { + profileData: any, + HighlightIfMissing: React.FC<{ value: any, children: React.ReactNode }> + onEdit?: () => void +}) { + return ( +
+
+

Basic Information

+ +
+
+
+
+ +
+ + + {profileData.firstName} + +
+
+
+ +
+ + + {profileData.lastName} + +
+
+
+
+ +
+ + + {profileData.email} + + +
+
+
+ +
+ + + {profileData.phone} + +
+
+
+ +
+ + + {profileData.address} + +
+
+
+
+ ) +} diff --git a/src/app/profile/components/editModal.tsx b/src/app/profile/components/editModal.tsx new file mode 100644 index 0000000..05e430a --- /dev/null +++ b/src/app/profile/components/editModal.tsx @@ -0,0 +1,100 @@ +import React, { useEffect, useState } from 'react' + +export default function EditModal({ + open, + type, + fields, + values, + onChange, + onSave, + onCancel, + children, +}: { + open: boolean, + type: 'basic' | 'bank', + fields: { key: string, label: string, type?: string }[], + values: Record, + onChange: (key: string, value: string) => void, + onSave: () => void, + onCancel: () => void, + children?: React.ReactNode +}) { + // Prevent background scroll when modal is open + useEffect(() => { + if (open) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = ''; + } + return () => { + document.body.style.overflow = ''; + }; + }, [open]); + + // Animation state + const [show, setShow] = useState(open); + + useEffect(() => { + if (open) { + setShow(true); + } else { + // Delay unmount for animation + const timeout = setTimeout(() => setShow(false), 200); + return () => clearTimeout(timeout); + } + }, [open]); + + if (!show) return null; + return ( +
+
+

+ Edit {type === 'basic' ? 'Basic Information' : 'Bank Information'} +

+ {children} +
{ + e.preventDefault(); + onSave(); + }} + className="space-y-4" + > + {fields.map(field => ( +
+ + onChange(field.key, e.target.value)} + /> +
+ ))} +
+ + +
+
+
+
+ ); +} diff --git a/src/app/profile/components/mediaSection.tsx b/src/app/profile/components/mediaSection.tsx new file mode 100644 index 0000000..b53d7f2 --- /dev/null +++ b/src/app/profile/components/mediaSection.tsx @@ -0,0 +1,39 @@ +import React from 'react' + +export default function MediaSection({ documents }: { documents: any[] }) { + const hasDocuments = Array.isArray(documents) && documents.length > 0; + return ( +
+

Media & Documents

+
+ {hasDocuments ? ( + + + + + + + + + + + {documents.map(doc => ( + + + + + + + ))} + +
NameTypeUploaded
{doc.name}{doc.type}{doc.uploaded} + + +
+ ) : ( +
No media or documents found.
+ )} +
+
+ ) +} diff --git a/src/app/profile/components/profileCompletion.tsx b/src/app/profile/components/profileCompletion.tsx new file mode 100644 index 0000000..3642739 --- /dev/null +++ b/src/app/profile/components/profileCompletion.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +export default function ProfileCompletion({ profileComplete }: { profileComplete: number }) { + return ( +
+
+

Profile Completion

+ + {profileComplete}% + +
+
+
+
+

+ Complete your profile to unlock all features +

+
+ ); +} diff --git a/src/app/profile/hooks/editProfile.ts b/src/app/profile/hooks/editProfile.ts new file mode 100644 index 0000000..f1649bf --- /dev/null +++ b/src/app/profile/hooks/editProfile.ts @@ -0,0 +1,101 @@ +import useAuthStore from '../../store/authStore' + +const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001'; + +function logEditProfile(message: string, ...args: any[]) { + // Simple logger for editProfile actions + console.log(`[editProfile] ${message}`, ...args); +} + +// Helper to get userType from sessionStorage (always parses JSON string) +function getUserType(): string | undefined { + if (typeof window !== 'undefined') { + try { + const userRaw = sessionStorage.getItem('user'); + logEditProfile('sessionStorage user raw:', userRaw); + if (userRaw) { + const userObj = JSON.parse(userRaw); + logEditProfile('parsed user object:', userObj); + logEditProfile('userType:', userObj.userType); + return userObj.userType; + } + } catch (err) { + logEditProfile('Error parsing user from sessionStorage:', err); + } + } else { + logEditProfile('window is undefined, SSR context'); + } + return undefined; +} + +export async function editProfileBasic(fields: Partial<{ firstName: string, lastName: string, email: string, phone: string, address: string }>) { + const token = useAuthStore.getState().accessToken; + const userType = getUserType(); + logEditProfile('editProfileBasic called', { fields, token, userType }); + if (userType !== 'personal') { + logEditProfile('User type is not personal or undefined:', userType); + return { error: 'Personal user required', status: 400, data: null }; + } + // FIX: Add user_type to the request body as required by backend! + const payload = { ...fields, user_type: userType }; + logEditProfile('Sending PATCH /api/profile/personal/basic', { payload }); + try { + const res = await fetch(`${BASE_URL}/api/profile/personal/basic`, { + method: 'PATCH', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + body: JSON.stringify(payload), + }); + logEditProfile('Request sent. Awaiting response...'); + const data = await res.json(); + logEditProfile('Response received:', { status: res.status, data }); + if (!res.ok) { + logEditProfile('Request failed:', { status: res.status, error: data?.message }); + return { error: data?.message || 'Update failed', status: res.status, data }; + } + logEditProfile('Request succeeded:', data); + return { success: true, data }; + } catch (e) { + logEditProfile('Network error:', e); + return { error: 'Network error', status: 0, data: null }; + } +} + +export async function editProfileBank(fields: Partial<{ accountHolder: string, iban: string }>) { + const token = useAuthStore.getState().accessToken; + const userType = getUserType(); + logEditProfile('editProfileBank called', { fields, token, userType }); + if (userType !== 'personal') { + logEditProfile('User type is not personal or undefined:', userType); + return { error: 'Personal user required', status: 400, data: null }; + } + // FIX: Add user_type to the request body as required by backend! + const payload = { ...fields, iban: fields.iban?.replace(/\s+/g, '').toUpperCase(), user_type: userType }; + logEditProfile('Sending PATCH /api/profile/personal/bank', { payload }); + try { + const res = await fetch(`${BASE_URL}/api/profile/personal/bank`, { + method: 'PATCH', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + body: JSON.stringify(payload), + }); + logEditProfile('Request sent. Awaiting response...'); + const data = await res.json(); + logEditProfile('Response received:', { status: res.status, data }); + if (!res.ok) { + logEditProfile('Request failed:', { status: res.status, error: data?.message }); + return { error: data?.message || 'Update failed', status: res.status, data }; + } + logEditProfile('Request succeeded:', data); + return { success: true, data }; + } catch (e) { + logEditProfile('Network error:', e); + return { error: 'Network error', status: 0, data: null }; + } +} diff --git a/src/app/profile/hooks/getMedia.ts b/src/app/profile/hooks/getMedia.ts new file mode 100644 index 0000000..a96e282 --- /dev/null +++ b/src/app/profile/hooks/getMedia.ts @@ -0,0 +1,32 @@ +import { useEffect, useState } from 'react' +import useAuthStore from '../../store/authStore' + +const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001'; + +export function useMedia(userId: string | number | undefined, refreshKey: number = 0) { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const token = useAuthStore.getState().accessToken + + useEffect(() => { + if (!userId) return + setLoading(true) + fetch(`${BASE_URL}/api/users/${userId}/documents`, { + credentials: 'include', + headers: token ? { Authorization: `Bearer ${token}` } : {}, + }) + .then(res => { + if (!res.ok) throw new Error('Failed to fetch user documents') + return res.json() + }) + .then(data => { + console.log('[useMedia] Response:', data) + setData(data) + }) + .catch(setError) + .finally(() => setLoading(false)) + }, [userId, token, refreshKey]) + + return { data, loading, error } +} diff --git a/src/app/profile/hooks/getProfileCompletion.ts b/src/app/profile/hooks/getProfileCompletion.ts new file mode 100644 index 0000000..62ad615 --- /dev/null +++ b/src/app/profile/hooks/getProfileCompletion.ts @@ -0,0 +1,30 @@ +import useAuthStore from "../../store/authStore"; + +const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001'; + +// Fetches the user's profile completion progress from the API, with auth token. +// Returns either percent or a progress object. +export async function getProfileCompletion(): Promise { + const token = useAuthStore.getState().accessToken; + try { + const res = await fetch(`${BASE_URL}/api/user/status-progress`, { + credentials: 'include', + headers: token ? { Authorization: `Bearer ${token}` } : {}, + }); + const data = await res.json(); + console.log('[getProfileCompletion] /api/user/status-progress response:', data); + if (data?.progress?.progressPercent !== undefined) { + return { + progressPercent: data.progress.progressPercent, + completedSteps: data.progress.completedSteps ?? [], + steps: data.progress.steps ?? [], + }; + } + if (typeof data.progress === 'number') return data.progress; + if (typeof data.completion === 'number') return data.completion; + return null; + } catch (e) { + console.warn('[getProfileCompletion] Failed to fetch:', e); + return null; + } +} diff --git a/src/app/profile/hooks/getProfileData.ts b/src/app/profile/hooks/getProfileData.ts new file mode 100644 index 0000000..3e91490 --- /dev/null +++ b/src/app/profile/hooks/getProfileData.ts @@ -0,0 +1,43 @@ +import { useEffect, useState } from 'react' +import useAuthStore from '../../store/authStore' + +const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001'; + +export function useProfileData(userId: string | number | undefined, refreshKey: number = 0) { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const token = useAuthStore.getState().accessToken + + // Log all sessionStorage contents for inspection + useEffect(() => { + const sessionData: Record = {}; + for (let i = 0; i < sessionStorage.length; i++) { + const key = sessionStorage.key(i); + if (key) sessionData[key] = sessionStorage.getItem(key); + } + console.log('Session Storage Contents:', sessionData); + }, []); + + useEffect(() => { + if (!userId) return + setLoading(true) + console.log('[useProfileData] Fetching profile for userId:', userId); + fetch(`${BASE_URL}/api/users/${userId}/full`, { + credentials: 'include', + headers: token ? { Authorization: `Bearer ${token}` } : {}, + }) + .then(res => { + if (!res.ok) throw new Error('Failed to fetch user data') + return res.json() + }) + .then(data => { + console.log('[useProfileData] Response:', data); // <-- Log full response + setData(data) + }) + .catch(setError) + .finally(() => setLoading(false)) + }, [userId, token, refreshKey]) + + return { data, loading, error } +} diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx index 1f04f6a..93f4d62 100644 --- a/src/app/profile/page.tsx +++ b/src/app/profile/page.tsx @@ -5,6 +5,11 @@ import { useRouter } from 'next/navigation' import useAuthStore from '../store/authStore' import Header from '../components/nav/Header' import Footer from '../components/Footer' +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 { UserCircleIcon, EnvelopeIcon, @@ -13,11 +18,66 @@ import { PencilIcon, CheckCircleIcon } from '@heroicons/react/24/outline' +import { getProfileCompletion } from './hooks/getProfileCompletion'; +import { useProfileData } from './hooks/getProfileData'; +import { useMedia } from './hooks/getMedia'; +import { editProfileBasic, editProfileBank } from './hooks/editProfile'; + +// 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: '', +}; export default function ProfilePage() { const router = useRouter() const user = useAuthStore(state => state.user) - + const [userId, setUserId] = React.useState(undefined); + + // Update userId when user changes + useEffect(() => { + if (user?.id) setUserId(user.id); + }, [user]); + + // Add refresh key and UI states for smooth refresh + const [refreshKey, setRefreshKey] = React.useState(0); + const [showRefreshing, setShowRefreshing] = React.useState(false); + const [completionLoading, setCompletionLoading] = React.useState(false); + + // Fetch profile data on page load/navigation, now with refreshKey + const { data: profileDataApi, loading: profileLoading, error: profileError } = useProfileData(userId, refreshKey); + + // Fetch media/documents for user, now with refreshKey + const { data: mediaData, loading: mediaLoading, error: mediaError } = useMedia(userId, refreshKey); + // Redirect if not logged in useEffect(() => { if (!user) { @@ -37,39 +97,166 @@ export default function ProfilePage() { ) } - // Mock user data for display - const profileData = { - 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: 95 - } + // Progress bar state + const [progressPercent, setProgressPercent] = React.useState(0); + const [completedSteps, setCompletedSteps] = React.useState([]); + const [allSteps, setAllSteps] = React.useState([]); + + useEffect(() => { + if (!user) { + router.push('/login'); + return; + } + async function fetchCompletion() { + setCompletionLoading(true); + const progress = await getProfileCompletion(); + // progress can be percent or object + if (progress && typeof progress === 'object') { + setProgressPercent(progress.progressPercent ?? 0); + setCompletedSteps(progress.completedSteps ?? []); + setAllSteps(progress.steps?.map((s: any) => s.name || s.title || '') ?? []); + } else if (typeof progress === 'number') { + setProgressPercent(progress); + } + setCompletionLoading(false); + } + fetchCompletion(); + }, [user, router, refreshKey]); + + // Use API profile data if available, fallback to mock + 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: '', // Always empty string if not provided + iban: '', + }; + } + 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 ?? '', // Only use account_holder_name + iban: apiUser.iban ?? '', + }; + }, [profileDataApi, user, progressPercent]); // Dummy data for new sections - const documents = [ - { id: 1, name: 'Passport.pdf', type: 'PDF', uploaded: '2024-05-01' }, - { id: 2, name: 'Invoice_2024.xlsx', type: 'Excel', uploaded: '2024-06-10' }, - { id: 3, name: 'ProfilePhoto.jpg', type: 'Image', uploaded: '2024-04-15' }, - ] + const documents = Array.isArray(mediaData?.documents) ? mediaData.documents : []; + // Adjusted bankInfo state to only have accountHolder and iban, always strings const [bankInfo, setBankInfo] = React.useState({ - accountHolder: 'Admin User', - iban: 'DE89 3704 0044 0532 0130 00', - bic: 'COBADEFFXXX', - bankName: 'Commerzbank', - }) - const [editingBank, setEditingBank] = React.useState(false) + accountHolder: '', + iban: '', + }); + const [editingBank, setEditingBank] = React.useState(false); const [bankDraft, setBankDraft] = React.useState(bankInfo) - const matrices = [ - { id: 1, name: 'Starter Matrix', level: '1', status: 'Active', joined: '2024-03-01' }, - { id: 2, name: 'Gold Matrix', level: '2', status: 'Pending', joined: '2024-05-15' }, - { id: 3, name: 'Platinum Matrix', level: '3', status: 'Active', joined: '2024-06-01' }, - ] + // Modal state + const [editModalOpen, setEditModalOpen] = React.useState(false); + const [editModalType, setEditModalType] = React.useState<'basic' | 'bank'>('basic'); + const [editModalValues, setEditModalValues] = React.useState>({}); + + // Modal error state + const [editModalError, setEditModalError] = React.useState(null); + + // Modal field definitions + const basicFields = [ + { key: 'firstName', label: 'First Name' }, + { key: 'lastName', label: 'Last Name' }, + { key: 'email', label: 'Email Address', type: 'email' }, + { key: 'phone', label: 'Phone Number' }, + { key: 'address', label: 'Address' }, + ]; + const bankFields = [ + { key: 'accountHolder', label: 'Account Holder' }, + { key: 'iban', label: 'IBAN' }, + ]; + + // Modal open handlers + function openEditModal(type: 'basic' | 'bank', values: Record) { + setEditModalType(type); + setEditModalValues(values); + setEditModalOpen(true); + } + + // Modal save handler (calls API) + async function handleEditModalSave() { + setEditModalError(null); + if (editModalType === 'basic') { + const payload: Partial = {}; + (['firstName', 'lastName', 'email', '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); + // Start smooth refresh with overlay spinner + setShowRefreshing(true); + setRefreshKey(k => k + 1); + } else if (res.status === 409) { + setEditModalError('Email already in use.'); + } else if (res.status === 401) { + router.push('/login'); + } else { + setEditModalError(res.error || 'Failed to update profile.'); + } + } else { + const payload: Partial = {}; + (['accountHolder', 'iban'] as const).forEach(key => { + if (editModalValues[key] !== getProfileField(profileData, key)) { + payload[key] = editModalValues[key]?.trim(); + } + }); + const res = await editProfileBank(payload); + if (res.success) { + setBankInfo({ + accountHolder: res.data?.profile?.account_holder_name ?? '', + iban: res.data?.user?.iban ?? '', + }); + setEditModalOpen(false); + // Start smooth refresh with overlay spinner + setShowRefreshing(true); + setRefreshKey(k => k + 1); + } else if (res.status === 400 && res.error?.toLowerCase().includes('iban')) { + setEditModalError('Invalid IBAN.'); + } else if (res.status === 401) { + router.push('/login'); + } else { + setEditModalError(res.error || 'Failed to update bank info.'); + } + } + } + + // Modal change handler + function handleEditModalChange(key: string, value: string) { + setEditModalValues(prev => ({ ...prev, [key]: value })); + } + + // Hide overlay when all data re-fetches complete + useEffect(() => { + if (showRefreshing && !profileLoading && !mediaLoading && !completionLoading) { + const t = setTimeout(() => setShowRefreshing(false), 200); // small delay for smoothness + return () => clearTimeout(t); + } + }, [showRefreshing, profileLoading, mediaLoading, completionLoading]); return (
@@ -84,90 +271,28 @@ export default function ProfilePage() {

- {/* 1. Profile Completion */} -
-
-

Profile Completion

- {profileData.profileComplete}% -
-
-
-
-

- Complete your profile to unlock all features -

-
+ {/* Profile Completion Progress Bar */} + - {/* 2. Basic Info + Sidebar */} + {/* Basic Info + Sidebar */}
{/* Basic Information */}
-
-
-

Basic Information

- -
- -
-
-
- -
- - {profileData.firstName} -
-
-
- -
- - {profileData.lastName} -
-
-
- -
- -
- - {profileData.email} - -
-
- -
- -
- - {profileData.phone} -
-
- -
- -
- - {profileData.address} -
-
-
-
+ openEditModal('basic', { + firstName: profileData.firstName, + lastName: profileData.lastName, + email: profileData.email, + phone: profileData.phone, + address: profileData.address, + })} + />
{/* Sidebar: Account Status + Quick Actions */}
@@ -216,164 +341,27 @@ export default function ProfilePage() {
- {/* 3. Media, Bank Info, Matrix Overview */} + {/* Bank Info, Media */}
- {/* --- Media Section --- */} -
-

Media & Documents

-
- - - - - - - - - - - {documents.map(doc => ( - - - - - - - ))} - -
NameTypeUploaded
{doc.name}{doc.type}{doc.uploaded} - - -
-
-
{/* --- Edit Bank Information Section --- */} -
-
-

Bank Information

- {!editingBank && ( - - )} -
-
{ - e.preventDefault() - setBankInfo(bankDraft) - setEditingBank(false) - }} - > -
- - setBankDraft({ ...bankDraft, accountHolder: e.target.value })} - disabled={!editingBank} - /> -
-
- - setBankDraft({ ...bankDraft, iban: e.target.value })} - disabled={!editingBank} - /> -
-
- - setBankDraft({ ...bankDraft, bic: e.target.value })} - disabled={!editingBank} - /> -
-
- - setBankDraft({ ...bankDraft, bankName: e.target.value })} - disabled={!editingBank} - /> -
- {editingBank && ( -
- - -
- )} -
-
- {/* --- Matrix Overview Section --- */} -
-

Matrix Overview

-
- - - - - - - - - - - - {matrices.map(matrix => ( - - - - - - - - ))} - -
Matrix NameLevelStatusJoined
{matrix.name}{matrix.level} - - {matrix.status} - - {matrix.joined} - -
-
-
+ openEditModal('bank', { + accountHolder: profileData.accountHolder, + iban: profileData.iban, + })} + /> + {/* --- Media Section --- */} +
- {/* 4. Account Settings */} + {/* Account Settings */}

Account Settings

@@ -413,6 +401,32 @@ export default function ProfilePage() {
) } \ No newline at end of file