From 6dfaedcab6c5edb85b4e5d523ae44dabeb424981 Mon Sep 17 00:00:00 2001 From: DeathKaioken Date: Wed, 14 Jan 2026 18:15:48 +0100 Subject: [PATCH] feat: mobile --- src/app/admin/page.tsx | 333 +++++++------- src/app/affiliate-links/page.tsx | 2 +- .../background/GlobalAnimatedBackground.tsx | 109 ----- src/app/components/TutorialModal.tsx | 23 +- src/app/components/background/blueblurry.tsx | 35 ++ src/app/components/{ => background}/waves.tsx | 0 src/app/components/nav/Header.tsx | 130 ++---- src/app/components/toast/toastComponent.tsx | 28 +- src/app/dashboard/page.tsx | 408 ++++++++++-------- src/app/login/page.tsx | 145 ++++--- src/app/page.tsx | 2 +- src/app/password-reset/page.tsx | 2 +- src/app/profile/page.tsx | 266 +++++------- src/app/quickaction-dashboard/page.tsx | 182 +++++--- .../company/page.tsx | 223 ++++++++-- .../personal/page.tsx | 262 +++++++++-- .../register-email-verify/page.tsx | 78 +++- .../register-sign-contract/company/page.tsx | 57 ++- .../register-sign-contract/personal/page.tsx | 57 ++- .../register-upload-id/company/page.tsx | 121 ++++-- .../register-upload-id/personal/page.tsx | 119 +++-- .../components/referralLinksListWidget.tsx | 35 +- src/app/referral-management/page.tsx | 84 +++- src/app/register/page.tsx | 81 ++-- 24 files changed, 1672 insertions(+), 1110 deletions(-) delete mode 100644 src/app/background/GlobalAnimatedBackground.tsx create mode 100644 src/app/components/background/blueblurry.tsx rename src/app/components/{ => background}/waves.tsx (100%) diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 8227eb4..31d33d7 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -1,7 +1,8 @@ 'use client' import PageLayout from '../components/PageLayout' -import Waves from '../components/waves' +import Waves from '../components/background/waves' +import BlueBlurryBackground from '../components/background/blueblurry' // NEW import { UsersIcon, ExclamationTriangleIcon, @@ -25,12 +26,25 @@ export default function AdminDashboardPage() { const router = useRouter() const { userStats, isAdmin } = useAdminUsers() const [isClient, setIsClient] = useState(false) - + const [isMobile, setIsMobile] = useState(false) + // Handle client-side mounting useEffect(() => { setIsClient(true) }, []) - + + useEffect(() => { + const mq = window.matchMedia('(max-width: 768px)') + const apply = () => setIsMobile(mq.matches) + apply() + mq.addEventListener?.('change', apply) + window.addEventListener('resize', apply, { passive: true }) + return () => { + mq.removeEventListener?.('change', apply) + window.removeEventListener('resize', apply) + } + }, []) + // Fallback for loading/no data const displayStats = userStats || { totalUsers: 0, @@ -83,96 +97,76 @@ export default function AdminDashboardPage() { ) } - return ( - -
- + const content = ( +
+
+
+ {/* Header */} +
+
+

Admin Dashboard

+

+ Manage all administrative features, user management, permissions, and global settings. +

+
+
-
-
-
- {/* Header */} -
+ {/* Warning banner */} +
+ +
+

+ Warning: Settings and actions below this point can have consequences for the entire system! +

+

+ Manage all administrative features, user management, permissions, and global settings. +

+
+
+ + {/* Stats Card */} +
+
+
Total Users
+
{displayStats.totalUsers}
+
+
+
Admins
+
{displayStats.adminUsers}
+
+
+
Active
+
{displayStats.activeUsers}
+
+
+
Pending Verification
+
{displayStats.verificationPending}
+
+
+
Personal
+
{displayStats.personalUsers}
+
+
+
Company
+
{displayStats.companyUsers}
+
+
+ + {/* Management Shortcuts Card */} +
+
+
+
+ +
-

Admin Dashboard

-

- Manage all administrative features, user management, permissions, and global settings. -

-
-
- - {/* Warning banner */} -
- -
-

- Warning: Settings and actions below this point can have consequences for the entire system! -

-

- Manage all administrative features, user management, permissions, and global settings. +

Management Shortcuts

+

+ Quick access to common admin modules.

- - {/* Stats Card */} -
-
-
Total Users
-
{displayStats.totalUsers}
-
-
-
Admins
-
{displayStats.adminUsers}
-
-
-
Active
-
{displayStats.activeUsers}
-
-
-
Pending Verification
-
{displayStats.verificationPending}
-
-
-
Personal
-
{displayStats.personalUsers}
-
-
-
Company
-
{displayStats.companyUsers}
-
-
- - {/* Management Shortcuts Card */} -
-
-
-
- -
-
-

Management Shortcuts

-

- Quick access to common admin modules. -

-
-
-
+
{/* Matrix Management */} -
+
+
+
+ + {/* Server Status & Logs */} +
+
+
+ +
+
+

+ Server Status & Logs +

+

+ System health, resource usage & recent error insights. +

+
+
+ +
+ {/* Metrics */} +
+
+ +

+ Server Status:{' '} + + {serverStats.status === 'Online' ? 'Server Online' : 'Offline'} + +

+
+
+

Uptime: {serverStats.uptime}

+

CPU Usage: {serverStats.cpu}

+

Memory Usage: {serverStats.memory} GB

+
+
+ + Autoscaled environment (mock)
- {/* Server Status & Logs */} -
-
-
- -
-
-

- Server Status & Logs -

-

- System health, resource usage & recent error insights. -

-
-
+ {/* Divider */} +
-
- {/* Metrics */} -
-
- -

- Server Status:{' '} - - {serverStats.status === 'Online' ? 'Server Online' : 'Offline'} - -

-
-
-

Uptime: {serverStats.uptime}

-

CPU Usage: {serverStats.cpu}

-

Memory Usage: {serverStats.memory} GB

-
-
- - Autoscaled environment (mock) -
-
- - {/* Divider */} -
- - {/* Logs */} -
-

- Recent Error Logs -

- {serverStats.recentErrors.length === 0 && ( -

- No recent logs. -

- )} - {/* Placeholder for future logs list */} - {/* TODO: Replace with mapped log entries */} -
- -
-
+ {/* Logs */} +
+

+ Recent Error Logs +

+ {serverStats.recentErrors.length === 0 && ( +

+ No recent logs. +

+ )} + {/* Placeholder for future logs list */} + {/* TODO: Replace with mapped log entries */} +
+
-
+
-
+ +
+ ) + + return ( + + {isMobile ? ( + {content} + ) : ( +
+ + {content} +
+ )}
) } \ No newline at end of file diff --git a/src/app/affiliate-links/page.tsx b/src/app/affiliate-links/page.tsx index 173aaf1..1860753 100644 --- a/src/app/affiliate-links/page.tsx +++ b/src/app/affiliate-links/page.tsx @@ -2,7 +2,7 @@ import { useEffect, useState, useMemo } from 'react' import PageLayout from '../components/PageLayout' -import Waves from '../components/waves' +import Waves from '../components/background/waves' type Affiliate = { id: string diff --git a/src/app/background/GlobalAnimatedBackground.tsx b/src/app/background/GlobalAnimatedBackground.tsx deleted file mode 100644 index 98fc880..0000000 --- a/src/app/background/GlobalAnimatedBackground.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import React from "react"; - -// Utility to detect mobile devices -function isMobileDevice() { - if (typeof navigator === "undefined") return false; - return /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( - navigator.userAgent - ); -} - -function GlobalAnimatedBackground() { - // Always use dashboard style for a uniform look - const bgGradient = "linear-gradient(135deg, #1e293b 0%, #334155 100%)"; - - // Detect small screens (mobile/tablet) - const isMobile = isMobileDevice(); - - if (isMobile) { - // Render only the static background gradient and overlay, no animation - return ( - - ); - } - - return ( - - ); -} - -export default GlobalAnimatedBackground; \ No newline at end of file diff --git a/src/app/components/TutorialModal.tsx b/src/app/components/TutorialModal.tsx index 419ff66..ac19205 100644 --- a/src/app/components/TutorialModal.tsx +++ b/src/app/components/TutorialModal.tsx @@ -84,7 +84,7 @@ export default function TutorialModal({
-
+
- -
+ {/* CHANGED: mobile uses max-height + scrolling */} + +
{/* Background Gradient */} - {/* Content Section - Left Half */} -
+ {/* CHANGED: content scrolls on mobile */} +
{/* Icon */}
- {/* Title */} -

+ {/* CHANGED: allow wrapping on mobile (remove nowrap) */} +

{step.title}

- {/* Description */} -

+ {/* CHANGED: no fixed height clipping on mobile */} +

{step.description}

@@ -209,8 +210,8 @@ export default function TutorialModal({ )}
- {/* Visual Section - Right Half */} -
+ {/* CHANGED: hide unused visual section on mobile */} +
{/* Profit Planet Mascot + {/* background layer (FIXED so it never gets clipped by inner layout containers) */} +
+
+
+
+
+
+ + {/* content layer */} +
{children}
+
+ ) +} diff --git a/src/app/components/waves.tsx b/src/app/components/background/waves.tsx similarity index 100% rename from src/app/components/waves.tsx rename to src/app/components/background/waves.tsx diff --git a/src/app/components/nav/Header.tsx b/src/app/components/nav/Header.tsx index de17930..5a56d83 100644 --- a/src/app/components/nav/Header.tsx +++ b/src/app/components/nav/Header.tsx @@ -9,10 +9,7 @@ import { Disclosure, DisclosureButton, DisclosurePanel, - Popover, - PopoverButton, PopoverGroup, - PopoverPanel, Transition, } from '@headlessui/react' import { @@ -99,22 +96,17 @@ export default function Header({ setGlobalLoggingOut }: HeaderProps) { console.error('Logout failed:', err) setGlobalLoggingOut?.(false) } - }; + } // Helper to get user initials for profile icon const getUserInitials = () => { - if (!user) return 'U'; + if (!user) return 'U' if (user.firstName || user.lastName) { - return ( - (user.firstName?.[0] || '') + - (user.lastName?.[0] || '') - ).toUpperCase(); + return ((user.firstName?.[0] || '') + (user.lastName?.[0] || '')).toUpperCase() } - if (user.email) { - return user.email[0].toUpperCase(); - } - return 'U'; - }; + if (user.email) return user.email[0].toUpperCase() + return 'U' + } // Initial theme (dark/light) + mark mounted + start header animation useEffect(() => { @@ -507,71 +499,18 @@ export default function Header({ setGlobalLoggingOut }: HeaderProps) { {/* Information dropdown already removed here */} -
- {/* Auth slot */} -
- {userPresent ? ( - - - { - if (!user) return 'U' - if (user.firstName || user.lastName) { - return ((user.firstName?.[0] || '') + (user.lastName?.[0] || '')).toUpperCase() - } - return user.email ? user.email[0].toUpperCase() : 'U' - })()} - className="size-8 bg-gradient-to-br from-indigo-500/40 to-indigo-600/60 text-white" - /> - - -
-
-
- {user?.firstName && user?.lastName ? `${user.firstName} ${user.lastName}` : (user?.email || 'User')} -
-
- {user?.email || 'user@example.com'} -
-
- {canSeeDashboard && ( - - )} - - {/* Logout removed from profile dropdown; still available in hamburger menu bottom */} -
-
-
- ) : mounted ? ( - - ) : ( - + {/* CHANGED: remove profile icon/popover from header; keep login (when logged out) + hamburger */} +
+ {!userPresent && mounted && ( + + )} - {/* Desktop hamburger (right side, next to login/profile) */} + {/* Desktop hamburger (right side) */}
) : user ? ( <> - {/* User info + basic nav */} + {/* CHANGED: include profile icon INSIDE hamburger menu */}
-
-
- {user?.firstName && user?.lastName ? `${user.firstName} ${user.lastName}` : (user?.email || 'User')} -
-
- {user?.email || 'user@example.com'} +
+ +
+
+ {user?.firstName && user?.lastName ? `${user.firstName} ${user.lastName}` : (user?.email || 'User')} +
+
+ {user?.email || 'user@example.com'} +
+ + {/* NEW: show Quick Action Dashboard link when user cannot access /dashboard yet */} + {!canSeeDashboard && ( + + )} + {canSeeDashboard && ( + +

{item.excerpt}

+
+ +
+
+ + ))} +
+
+ + {/* Quick Actions */} +
+

Quick Actions

+
+ {quickActions.map((action, index) => ( + + ))} +
+
+ + {/* Gold Member Status */} +
+
+ +
+

Gold Member Status

+

+ Enjoy exclusive benefits and discounts +

+
+
+ +
+
+
+ + {/* Recent Activity */} +
+

Recent Activity

+
+
+
+ +
+
+

Order completed

+

Eco-friendly water bottle

+
+ 2 days ago +
+ +
+
+ +
+
+

Added to favorites

+

Sustainable backpack

+
+ 1 week ago +
+ +
+
+ +
+
+

Joined community

+

Eco Warriors Group

+
+ 2 weeks ago +
+
+
+
+
+ + +
+ ) + + return isMobile ? ( + {content} + ) : (
- -
- -
-
-
- {/* Welcome Section */} -
-

- Welcome back, {getUserName()}! 👋 -

-

- Here's what's happening with your Profit Planet account -

-
- - {/* News Section (replaces Account setup + Stats Grid) */} -
-

Latest News & Articles

-
- {news.map(item => ( -
- {/* Image/placeholder */} -
-
-
- - {item.category} - - {new Date(item.date).toLocaleDateString()} -
-

- -

-

{item.excerpt}

-
- -
-
-
- ))} -
-
- - {/* Quick Actions */} -
-

Quick Actions

-
- {quickActions.map((action, index) => ( - - ))} -
-
- - {/* Gold Member Status */} -
-
- -
-

Gold Member Status

-

- Enjoy exclusive benefits and discounts -

-
-
- -
-
-
- - {/* Recent Activity */} -
-

Recent Activity

-
-
-
- -
-
-

Order completed

-

Eco-friendly water bottle

-
- 2 days ago -
- -
-
- -
-
-

Added to favorites

-

Sustainable backpack

-
- 1 week ago -
- -
-
- -
-
-

Joined community

-

Eco Warriors Group

-
- 2 weeks ago -
-
-
-
-
-
-
-
+ {content}
) } \ No newline at end of file diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index fafe75f..1656480 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -7,7 +7,8 @@ import PageLayout from '../components/PageLayout' import useAuthStore from '../store/authStore' import { ToastProvider } from '../components/toast/toastComponent' import PageTransitionEffect from '../components/animation/pageTransitionEffect' -import Waves from '../components/waves' +import Waves from '../components/background/waves' +import BlueBlurryBackground from '../components/background/blueblurry' // NEW import CurvedLoop from '../components/curvedLoop' export default function LoginPage() { @@ -58,78 +59,82 @@ export default function LoginPage() { ) } + const content = ( + + {/* ...existing code... */} +
+ {/* ...existing code... */} + {isMobile ? ( + // ...existing code... +
+
+ +
+
+ ) : ( + // ...existing code... +
+
+ +
+ +
+ +
+
+ )} +
+ {/* ...existing code... */} +
+ ) + return ( - {/* NEW: page-level background wrapper so Waves covers everything */} -
- - - - {/* ...existing code... */} -
- {/* REMOVED: Waves background moved to wrapper */} - - {isMobile ? ( - // ...existing code... -
-
- -
-
- ) : ( - // ...existing code... -
-
- -
- -
- -
-
- )} -
- {/* ...existing code... */} -
-
+ {isMobile ? ( + + {content} + + ) : ( +
+ + {content} +
+ )}
) diff --git a/src/app/page.tsx b/src/app/page.tsx index 3c1ac01..b3cf745 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -5,7 +5,7 @@ import { useRouter } from 'next/navigation'; import { gsap } from 'gsap'; import PageLayout from './components/PageLayout'; import Crosshair from './components/Crosshair'; -import Waves from './components/waves'; +import Waves from './components/background/waves'; import SplitText from './components/SplitText'; export default function HomePage() { diff --git a/src/app/password-reset/page.tsx b/src/app/password-reset/page.tsx index 605b660..c0446f0 100644 --- a/src/app/password-reset/page.tsx +++ b/src/app/password-reset/page.tsx @@ -3,7 +3,7 @@ import { useState, useEffect } from 'react' import { useSearchParams, useRouter } from 'next/navigation' import PageLayout from '../components/PageLayout' -import Waves from '../components/waves' +import Waves from '../components/background/waves' import { ToastProvider, useToast } from '../components/toast/toastComponent' function PasswordResetPageInner() { diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx index fe9d409..a490632 100644 --- a/src/app/profile/page.tsx +++ b/src/app/profile/page.tsx @@ -4,7 +4,7 @@ import React, { useEffect } from 'react' import { useRouter } from 'next/navigation' import useAuthStore from '../store/authStore' import PageLayout from '../components/PageLayout' -import Waves from '../components/waves' +import BlueBlurryBackground from '../components/background/blueblurry' import ProfileCompletion from './components/profileCompletion' import BasicInformation from './components/basicInformation' import MediaSection from './components/mediaSection' @@ -70,7 +70,6 @@ export default function ProfilePage() { const user = useAuthStore(state => state.user) const isAuthReady = useAuthStore(state => state.isAuthReady) const [hasHydrated, setHasHydrated] = React.useState(false) - const [isMobile, setIsMobile] = React.useState(false) const [userId, setUserId] = React.useState(undefined) // --- declare ALL hooks before any early return (Rules of Hooks) --- @@ -222,18 +221,6 @@ export default function ProfilePage() { setEditModalValues(prev => ({ ...prev, [key]: value })) } - useEffect(() => { - const mq = window.matchMedia('(max-width: 768px)') - const apply = () => setIsMobile(mq.matches) - apply() - mq.addEventListener?.('change', apply) - window.addEventListener('resize', apply, { passive: true }) - return () => { - mq.removeEventListener?.('change', apply) - window.removeEventListener('resize', apply) - } - }, []) - // --- EARLY RETURN AFTER ALL HOOKS --- if (!hasHydrated || !isAuthReady || !user) { return ( @@ -247,149 +234,130 @@ export default function ProfilePage() { } return ( -
- + + +
+
+ {/* MASTER GLASS PANEL (prevents non-translucent gaps between cards) */} +
+ {/* Page Header */} +
+

Profile Settings

+

+ Manage your account information and preferences +

+
-
- -
-
- {/* 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.
+ )} - {/* 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

- -
- - - -
-
-
-
- - {/* Bank Info, Media */} -
- {/* --- My Abo Section (above bank info) --- */} - - {/* --- Edit Bank Information Section --- */} - + {/* Basic Information */} +
+ openEditModal('bank', { ... })} + HighlightIfMissing={HighlightIfMissing} + // Add edit button handler + onEdit={() => openEditModal('basic', { + firstName: profileData.firstName, + lastName: profileData.lastName, + phone: profileData.phone, + address: profileData.address, + })} /> - {/* --- Media Section --- */} - +
+ {/* 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

+ +
+ + + +
+
-
-
- {/* Edit Modal */} - { setEditModalOpen(false); setEditModalError(null); }} - > - {/* Show error message if present */} - {editModalError && ( -
{editModalError}
- )} -
-
-
-
+ {/* Bank Info, Media */} +
+ {/* --- My Abo Section (above bank info) --- */} + + {/* --- Edit Bank Information Section --- */} + openEditModal('bank', { ... })} + /> + {/* --- Media Section --- */} + +
+
+
+ + + {/* Edit Modal */} + { setEditModalOpen(false); setEditModalError(null); }} + > + {/* Show error message if present */} + {editModalError && ( +
{editModalError}
+ )} +
+ + ) } \ No newline at end of file diff --git a/src/app/quickaction-dashboard/page.tsx b/src/app/quickaction-dashboard/page.tsx index 747457d..09222af 100644 --- a/src/app/quickaction-dashboard/page.tsx +++ b/src/app/quickaction-dashboard/page.tsx @@ -1,11 +1,12 @@ 'use client' -import { useState, useCallback, useEffect } from 'react' +import { useState, useCallback, useEffect, useRef } 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 BlueBlurryBackground from '../components/background/blueblurry' // NEW import { CheckCircleIcon, XCircleIcon, @@ -30,6 +31,8 @@ interface StatusItem { export default function QuickActionDashboardPage() { const router = useRouter() const user = useAuthStore(s => s.user) + const isAuthReady = useAuthStore(s => (s as any).isAuthReady) // NEW + const accessToken = useAuthStore(s => s.accessToken) // NEW const { userStatus, loading, error, refreshStatus } = useUserStatus() const [isClient, setIsClient] = useState(false) @@ -51,32 +54,66 @@ export default function QuickActionDashboardPage() { const additionalInfo = userStatus?.profile_completed || false const contractSigned = userStatus?.contract_signed || false - // Check if we should open tutorial (from URL parameter) - separate useEffect after status is loaded + // NEW: if everything is done, quickaction-dashboard is no longer accessible + const allDone = emailVerified && idUploaded && additionalInfo && contractSigned + // NEW: smooth redirect (prevents snappy double navigation) + const [redirectTo, setRedirectTo] = useState(null) + const redirectOnceRef = useRef(false) + const smoothReplace = useCallback((to: string) => { + if (redirectOnceRef.current) return + redirectOnceRef.current = true + setRedirectTo(to) + window.setTimeout(() => router.replace(to), 200) + }, [router]) + useEffect(() => { - if (!isClient || loading || !userStatus) return - + if (!isClient) return + if (loading || !userStatus) return + if (allDone) smoothReplace('/dashboard') // CHANGED + }, [isClient, loading, userStatus, allDone, smoothReplace]) + + // NEW: decide which tutorial step to start on + const getNextTutorialStep = useCallback(() => { + const noneCompleted = !emailVerified && !idUploaded && !additionalInfo && !contractSigned + if (noneCompleted) return 1 + if (!emailVerified) return 2 + if (!idUploaded) return 3 + if (!additionalInfo) return 4 + if (!contractSigned) return 5 + return 6 + }, [emailVerified, idUploaded, additionalInfo, contractSigned]) + + // CHANGED: single auto-open mechanism (works even if tutorial_seen exists) + useEffect(() => { + if (!isClient) return + if (loading || !userStatus) return + if (allDone) return // NEW: avoid tutorial flash during redirect + if (isTutorialOpen) return + + const uid = + (user as any)?.id ?? + (user as any)?._id ?? + (user as any)?.userId ?? + (user as any)?.email ?? + null + if (!uid) return + + const tokenSuffix = (accessToken || '').slice(-12) || 'no-token' + const sessionKey = `pp:tutorialShown:${uid}:${tokenSuffix}` + const urlParams = new URLSearchParams(window.location.search) - if (urlParams.get('tutorial') === 'true') { - // Remove the parameter from URL - const newUrl = window.location.pathname - window.history.replaceState({}, '', newUrl) - - // Open tutorial and go to next step - setTimeout(() => { - setIsTutorialOpen(true) - // Determine next step based on completion status - if (!emailVerified) { - setCurrentTutorialStep(2) - } else if (!idUploaded) { - setCurrentTutorialStep(3) - } else if (!additionalInfo) { - setCurrentTutorialStep(4) - } else { - setCurrentTutorialStep(5) - } - }, 500) + const forceOpen = urlParams.get('tutorial') === 'true' + if (forceOpen) { + window.history.replaceState({}, '', window.location.pathname) } - }, [isClient, loading, userStatus, emailVerified, idUploaded, additionalInfo]) + + const alreadyShownThisLogin = sessionStorage.getItem(sessionKey) === '1' + if (alreadyShownThisLogin && !forceOpen) return + + setCurrentTutorialStep(getNextTutorialStep()) + setIsTutorialOpen(true) + sessionStorage.setItem(sessionKey, '1') + }, [isClient, loading, userStatus, isTutorialOpen, user, accessToken, getNextTutorialStep]) const statusItems: StatusItem[] = [ { @@ -111,23 +148,27 @@ export default function QuickActionDashboardPage() { // Action handlers - navigate to proper QuickAction pages with tutorial callback const handleVerifyEmail = useCallback(() => { + if (emailVerified) return router.push('/quickaction-dashboard/register-email-verify?tutorial=true') - }, [router]) + }, [router, emailVerified]) const handleUploadId = useCallback(() => { + if (idUploaded) return const userType = user?.userType || 'personal' router.push(`/quickaction-dashboard/register-upload-id/${userType}?tutorial=true`) - }, [router, user]) + }, [router, user, idUploaded]) const handleCompleteInfo = useCallback(() => { + if (additionalInfo) return const userType = user?.userType || 'personal' router.push(`/quickaction-dashboard/register-additional-information/${userType}?tutorial=true`) - }, [router, user]) + }, [router, user, additionalInfo]) const handleSignContract = useCallback(() => { + if (contractSigned) return const userType = user?.userType || 'personal' router.push(`/quickaction-dashboard/register-sign-contract/${userType}?tutorial=true`) - }, [router, user]) + }, [router, user, contractSigned]) // Tutorial handlers const startTutorial = useCallback(() => { @@ -149,16 +190,6 @@ export default function QuickActionDashboardPage() { 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, @@ -202,20 +233,27 @@ export default function QuickActionDashboardPage() { return () => clearInterval(id) }, [isClient, emailVerified, user?.email]) + // NEW: only logged-in users can access quickaction dashboard + useEffect(() => { + if (!isClient) return + if (!isAuthReady) return + if (!user) smoothReplace('/login') + }, [isClient, isAuthReady, user, smoothReplace]) + return ( -
- {/* Animated background */} -
- {/* Soft gradient blobs */} -
-
-
- {/* Subtle radial highlight */} -
+ {/* NEW: smooth redirect overlay */} + {redirectTo && ( +
+
+
Redirecting…
+
Taking you to your dashboard
+
+ )} -
+ +
{/* Title */}

@@ -261,28 +299,32 @@ export default function QuickActionDashboardPage() {

Status Overview

-
+ + {/* CHANGED: mobile 2x2 grid */} +
{statusItems.map(item => { const CompleteIcon = item.complete ? CheckCircleIcon : XCircleIcon return (
{item.label} @@ -316,19 +358,21 @@ export default function QuickActionDashboardPage() { )}
-
+ + {/* CHANGED: mobile 2x2 grid (order already matches desired layout) */} +
{/* Email Verification */}
{/* NEW: resend feedback (only when not verified) */} @@ -344,26 +388,28 @@ export default function QuickActionDashboardPage() { {/* ID Upload */} {/* Additional Info */} @@ -372,15 +418,15 @@ export default function QuickActionDashboardPage() { {!canSignContract && !contractSigned && ( @@ -409,7 +455,7 @@ export default function QuickActionDashboardPage() {
-
+ {/* Tutorial Modal */} void + options: { value: string; label: string }[] +}) { + const [open, setOpen] = useState(false) + const [query, setQuery] = useState('') + const btnRef = useRef(null) + const [pos, setPos] = useState({ left: 16, top: 0, width: 320 }) + + const selected = useMemo(() => options.find(o => o.value === value) || null, [options, value]) + const filtered = useMemo(() => { + const q = query.trim().toLowerCase() + if (!q) return options + return options.filter(o => o.label.toLowerCase().includes(q)) + }, [options, query]) + + useEffect(() => { + if (!open) return + const update = () => { + const el = btnRef.current + if (!el) return + const r = el.getBoundingClientRect() + const padding = 16 + const width = Math.min(r.width, window.innerWidth - padding * 2) + const left = Math.max(padding, Math.min(r.left, window.innerWidth - width - padding)) + const top = r.bottom + 8 + setPos({ left, top, width }) + } + update() + window.addEventListener('resize', update) + window.addEventListener('scroll', update, true) + return () => { + window.removeEventListener('resize', update) + window.removeEventListener('scroll', update, true) + } + }, [open]) + + useEffect(() => { + if (!open) return + const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') setOpen(false) } + window.addEventListener('keydown', onKey) + return () => window.removeEventListener('keydown', onKey) + }, [open]) + + return ( +
+ + + + + {open && ( + <> +
setOpen(false)} aria-hidden /> +
+
+ setQuery(e.target.value)} + placeholder="Search…" + className="w-full rounded-lg border border-gray-200 px-3 py-2 text-sm + focus:outline-none focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" + autoFocus + /> +
+
+ {filtered.length === 0 ? ( +
No results
+ ) : ( + filtered.map(o => { + const active = o.value === value + return ( + + ) + }) + )} +
+
+ + )} +
+ ) +} + export default function CompanyAdditionalInformationPage() { const router = useRouter() + const user = useAuthStore(s => s.user) // NEW + const isAuthReady = useAuthStore(s => (s as any).isAuthReady) // NEW const { accessToken } = useAuthStore() - const { refreshStatus } = useUserStatus() + const { userStatus, loading: statusLoading, refreshStatus } = useUserStatus() const { showToast } = useToast() const [form, setForm] = useState(init) @@ -58,6 +181,38 @@ export default function CompanyAdditionalInformationPage() { const [success, setSuccess] = useState(false) const [error, setError] = useState('') + // NEW: smooth redirect + const [redirectTo, setRedirectTo] = useState(null) + const redirectOnceRef = useRef(false) + const smoothReplace = useCallback((to: string) => { + if (redirectOnceRef.current) return + redirectOnceRef.current = true + setRedirectTo(to) + window.setTimeout(() => router.replace(to), 200) + }, [router]) + + // NEW: hard block if step already done OR all steps done + useEffect(() => { + if (statusLoading || !userStatus) return + const allDone = + !!userStatus.email_verified && + !!userStatus.documents_uploaded && + !!userStatus.profile_completed && + !!userStatus.contract_signed + + if (allDone) { + smoothReplace('/dashboard') // CHANGED + } else if (userStatus.profile_completed) { + smoothReplace('/quickaction-dashboard') // CHANGED + } + }, [statusLoading, userStatus, smoothReplace]) + + // NEW: must be logged in + useEffect(() => { + if (!isAuthReady) return + if (!user || !accessToken) smoothReplace('/login') + }, [isAuthReady, user, accessToken, smoothReplace]) + const handleChange = (e: React.ChangeEvent) => { const { name, value } = e.target setForm(p => ({ ...p, [name]: value })) @@ -178,8 +333,23 @@ export default function CompanyAdditionalInformationPage() { } } + const setField = (name: keyof CompanyProfileData, value: string) => { + setForm(p => ({ ...p, [name]: value })) + setError('') + } + return ( + {/* NEW: smooth redirect overlay */} + {redirectTo && ( +
+
+
Redirecting…
+
Please wait
+
+
+ )} +
{/* Animated background (same as dashboard) */}
@@ -215,7 +385,7 @@ export default function CompanyAdditionalInformationPage() { name="companyName" value={form.companyName} onChange={handleChange} - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" required />
@@ -228,7 +398,7 @@ export default function CompanyAdditionalInformationPage() { value={form.vatNumber} onChange={handleChange} placeholder="e.g. DE123456789" - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm uppercase focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm uppercase focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" required />
@@ -240,7 +410,7 @@ export default function CompanyAdditionalInformationPage() { name="street" value={form.street} onChange={handleChange} - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" required />
@@ -252,7 +422,7 @@ export default function CompanyAdditionalInformationPage() { name="postalCode" value={form.postalCode} onChange={handleChange} - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" required />
@@ -264,28 +434,18 @@ export default function CompanyAdditionalInformationPage() { name="city" value={form.city} onChange={handleChange} - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" required />
- - + onChange={(v) => setField('country', v)} + options={COUNTRIES.map(c => ({ value: c, label: c }))} + />
@@ -307,7 +467,7 @@ export default function CompanyAdditionalInformationPage() { value={form.accountHolder} onChange={handleChange} placeholder="Company / Holder name" - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" required />
@@ -320,7 +480,7 @@ export default function CompanyAdditionalInformationPage() { value={form.iban} onChange={handleChange} placeholder="DE89 3704 0044 0532 0130 00" - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm tracking-wide uppercase focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm tracking-wide uppercase focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" required />
@@ -333,7 +493,7 @@ export default function CompanyAdditionalInformationPage() { value={form.bic} onChange={handleChange} placeholder="GENODEF1XXX" - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm uppercase focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm uppercase focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" />
@@ -356,7 +516,7 @@ export default function CompanyAdditionalInformationPage() { value={form.secondPhone} onChange={handleChange} placeholder="+49 123 456 7890" - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" />
@@ -368,7 +528,7 @@ export default function CompanyAdditionalInformationPage() { value={form.emergencyName} onChange={handleChange} placeholder="Contact name" - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" />
@@ -380,7 +540,7 @@ export default function CompanyAdditionalInformationPage() { value={form.emergencyPhone} onChange={handleChange} placeholder="+49 123 456 7890" - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" />
@@ -402,14 +562,15 @@ export default function CompanyAdditionalInformationPage() { + diff --git a/src/app/quickaction-dashboard/register-additional-information/personal/page.tsx b/src/app/quickaction-dashboard/register-additional-information/personal/page.tsx index b5e9a07..e1328d1 100644 --- a/src/app/quickaction-dashboard/register-additional-information/personal/page.tsx +++ b/src/app/quickaction-dashboard/register-additional-information/personal/page.tsx @@ -1,11 +1,12 @@ 'use client' -import { useEffect, useState } from 'react' +import { useEffect, useMemo, useRef, useState, useCallback } from 'react' import { useRouter } from 'next/navigation' import PageLayout from '../../../components/PageLayout' import useAuthStore from '../../../store/authStore' import { useUserStatus } from '../../../hooks/useUserStatus' import { useToast } from '../../../components/toast/toastComponent' +import { ChevronDownIcon } from '@heroicons/react/20/solid' interface PersonalProfileData { dob: string @@ -55,10 +56,153 @@ const initialData: PersonalProfileData = { emergencyPhone: '' } +type SelectOption = { value: string; label: string } + +function ModernSelect({ + label, + placeholder = 'Select…', + value, + onChange, + options, +}: { + label: string + placeholder?: string + value: string + onChange: (next: string) => void + options: SelectOption[] +}) { + const [open, setOpen] = useState(false) + const [query, setQuery] = useState('') + const btnRef = useRef(null) + const [pos, setPos] = useState({ left: 16, top: 0, width: 320 }) + + const selected = useMemo( + () => options.find(o => o.value === value) || null, + [options, value] + ) + + const filtered = useMemo(() => { + const q = query.trim().toLowerCase() + if (!q) return options + return options.filter(o => o.label.toLowerCase().includes(q)) + }, [options, query]) + + useEffect(() => { + if (!open) return + + const update = () => { + const el = btnRef.current + if (!el) return + const r = el.getBoundingClientRect() + + const padding = 16 + const width = Math.min(r.width, window.innerWidth - padding * 2) + const left = Math.max(padding, Math.min(r.left, window.innerWidth - width - padding)) + const top = r.bottom + 8 + + setPos({ left, top, width }) + } + + update() + window.addEventListener('resize', update) + window.addEventListener('scroll', update, true) + return () => { + window.removeEventListener('resize', update) + window.removeEventListener('scroll', update, true) + } + }, [open]) + + // close on escape + useEffect(() => { + if (!open) return + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') setOpen(false) + } + window.addEventListener('keydown', onKey) + return () => window.removeEventListener('keydown', onKey) + }, [open]) + + return ( +
+ + + + + {open && ( + <> + {/* click-away overlay */} +
setOpen(false)} aria-hidden /> + + {/* dropdown (fixed so it “pops out under” even on mobile) */} +
+
+ setQuery(e.target.value)} + placeholder="Search…" + className="w-full rounded-lg border border-gray-200 px-3 py-2 text-sm + focus:outline-none focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" + autoFocus + /> +
+ +
+ {filtered.length === 0 ? ( +
No results
+ ) : ( + filtered.map(o => { + const active = o.value === value + return ( + + ) + }) + )} +
+
+ + )} +
+ ) +} + export default function PersonalAdditionalInformationPage() { const router = useRouter() + const user = useAuthStore(s => s.user) // NEW + const isAuthReady = useAuthStore(s => (s as any).isAuthReady) // NEW const { accessToken } = useAuthStore() - const { refreshStatus } = useUserStatus() + const { userStatus, loading: statusLoading, refreshStatus } = useUserStatus() const { showToast } = useToast() const [form, setForm] = useState(initialData) @@ -270,8 +414,55 @@ export default function PersonalAdditionalInformationPage() { } } + const setField = (name: keyof PersonalProfileData, value: string) => { + setForm(p => ({ ...p, [name]: value })) + setError('') + } + + // NEW: smooth redirect + const [redirectTo, setRedirectTo] = useState(null) + const redirectOnceRef = useRef(false) + const smoothReplace = useCallback((to: string) => { + if (redirectOnceRef.current) return + redirectOnceRef.current = true + setRedirectTo(to) + window.setTimeout(() => router.replace(to), 200) + }, [router]) + + // NEW: hard block if step already done OR all steps done + useEffect(() => { + if (statusLoading || !userStatus) return + const allDone = + !!userStatus.email_verified && + !!userStatus.documents_uploaded && + !!userStatus.profile_completed && + !!userStatus.contract_signed + + if (allDone) { + smoothReplace('/dashboard') // CHANGED + } else if (userStatus.profile_completed) { + smoothReplace('/quickaction-dashboard') // CHANGED + } + }, [statusLoading, userStatus, smoothReplace]) + + // NEW: must be logged in + useEffect(() => { + if (!isAuthReady) return + if (!user || !accessToken) smoothReplace('/login') + }, [isAuthReady, user, accessToken, smoothReplace]) + return ( + {/* NEW: smooth redirect overlay */} + {redirectTo && ( +
+
+
Redirecting…
+
Please wait
+
+
+ )} +
{/* Animated background (same as dashboard) */}
@@ -310,28 +501,18 @@ export default function PersonalAdditionalInformationPage() { onChange={handleChange} min={new Date(new Date().getFullYear() - 120, 0, 1).toISOString().split('T')[0]} max={new Date(new Date().getFullYear() - 18, 11, 31).toISOString().split('T')[0]} - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" required />
- - + onChange={(v) => setField('nationality', v)} + options={NATIONALITIES.map(n => ({ value: n, label: n }))} + />
@@ -355,7 +536,7 @@ export default function PersonalAdditionalInformationPage() { value={form.postalCode} onChange={handleChange} placeholder="e.g. 12345" - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" required />
@@ -368,28 +549,18 @@ export default function PersonalAdditionalInformationPage() { value={form.city} onChange={handleChange} placeholder="e.g. Berlin" - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" required />
- - + onChange={(v) => setField('country', v)} + options={COUNTRIES.map(c => ({ value: c, label: c }))} + />
@@ -411,7 +582,7 @@ export default function PersonalAdditionalInformationPage() { value={form.accountHolder} onChange={handleChange} placeholder="Full name" - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" required />
@@ -424,7 +595,7 @@ export default function PersonalAdditionalInformationPage() { value={form.iban} onChange={handleChange} placeholder="e.g. DE89 3704 0044 0532 0130 00" - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm tracking-wide uppercase focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm tracking-wide uppercase focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" required />
@@ -448,7 +619,7 @@ export default function PersonalAdditionalInformationPage() { value={form.secondPhone} onChange={handleChange} placeholder="+43 660 1234567" - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" />
@@ -460,7 +631,7 @@ export default function PersonalAdditionalInformationPage() { value={form.emergencyName} onChange={handleChange} placeholder="Contact name" - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" />
@@ -472,7 +643,7 @@ export default function PersonalAdditionalInformationPage() { value={form.emergencyPhone} onChange={handleChange} placeholder="+43 660 1234567" - className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" />
@@ -494,14 +665,15 @@ export default function PersonalAdditionalInformationPage() { + diff --git a/src/app/quickaction-dashboard/register-email-verify/page.tsx b/src/app/quickaction-dashboard/register-email-verify/page.tsx index f69cb3e..cfcb70c 100644 --- a/src/app/quickaction-dashboard/register-email-verify/page.tsx +++ b/src/app/quickaction-dashboard/register-email-verify/page.tsx @@ -2,6 +2,7 @@ import { useState, useEffect, useCallback, useRef } from 'react' import PageLayout from '../../components/PageLayout' +import BlueBlurryBackground from '../../components/background/blueblurry' // NEW import useAuthStore from '../../store/authStore' import { useUserStatus } from '../../hooks/useUserStatus' import { useRouter } from 'next/navigation' @@ -9,8 +10,9 @@ import { useToast } from '../../components/toast/toastComponent' export default function EmailVerifyPage() { const user = useAuthStore(s => s.user) + const isAuthReady = useAuthStore(s => (s as any).isAuthReady) // NEW const token = useAuthStore(s => s.accessToken) - const { refreshStatus } = useUserStatus() + const { userStatus, loading: statusLoading, refreshStatus } = useUserStatus() // CHANGED const [code, setCode] = useState(['', '', '', '', '', '']) const [submitting, setSubmitting] = useState(false) const [error, setError] = useState('') @@ -339,20 +341,51 @@ export default function EmailVerifyPage() { return `${m}:${String(s).padStart(2, '0')}` } + // NEW: hard block if step already done OR all steps done + const [redirectTo, setRedirectTo] = useState(null) + const redirectOnceRef = useRef(false) + const smoothReplace = useCallback((to: string) => { + if (redirectOnceRef.current) return + redirectOnceRef.current = true + setRedirectTo(to) + window.setTimeout(() => router.replace(to), 200) + }, [router]) + + useEffect(() => { + if (statusLoading || !userStatus) return + const allDone = + !!userStatus.email_verified && + !!userStatus.documents_uploaded && + !!userStatus.profile_completed && + !!userStatus.contract_signed + + if (allDone) { + smoothReplace('/dashboard') // CHANGED + } else if (userStatus.email_verified) { + smoothReplace('/quickaction-dashboard') // CHANGED + } + }, [statusLoading, userStatus, smoothReplace]) + + // NEW: must be logged in + useEffect(() => { + if (!isAuthReady) return + if (!user || !token) smoothReplace('/login') + }, [isAuthReady, user, token, smoothReplace]) + return ( -
- {/* Animated background (same as dashboard) */} -
- {/* Soft gradient blobs */} -
-
-
- {/* Subtle radial highlight */} -
+ {/* NEW: smooth redirect overlay */} + {redirectTo && ( +
+
+
Redirecting…
+
Please wait
+
+ )} -
+ +

@@ -362,7 +395,7 @@ export default function EmailVerifyPage() { {initialEmailSent ? ( <> We sent a 6-digit code to{' '} - + {user?.email || 'your email'} . Enter it below. @@ -370,7 +403,7 @@ export default function EmailVerifyPage() { ) : ( <> Sending verification email to{' '} - + {user?.email || 'your email'} ... @@ -379,13 +412,11 @@ export default function EmailVerifyPage() {

- {/* Card */}
- {/* Inputs */}
{code.map((v, i) => ( handlePaste(i, e)} className={`w-12 h-14 sm:w-14 sm:h-16 text-center text-2xl font-semibold rounded-lg border transition-colors outline-none ${v - ? 'border-indigo-500 ring-2 ring-indigo-400/40 bg-white text-gray-900' + ? 'border-[#8D6B1D] ring-2 ring-[#8D6B1D]/25 bg-white text-gray-900' : 'border-gray-300 bg-white/80 text-gray-700'} - focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500`} + focus:ring-2 focus:ring-[#8D6B1D] focus:border-[#8D6B1D]`} /> ))}
@@ -423,7 +454,10 @@ export default function EmailVerifyPage() {
@@ -458,7 +492,7 @@ export default function EmailVerifyPage() {
Didn’t receive the email? Please check your junk/spam folder. Still having issues?{' '} - + Contact support . @@ -466,7 +500,7 @@ export default function EmailVerifyPage() {
-
+ ) } diff --git a/src/app/quickaction-dashboard/register-sign-contract/company/page.tsx b/src/app/quickaction-dashboard/register-sign-contract/company/page.tsx index 21c258c..d353eeb 100644 --- a/src/app/quickaction-dashboard/register-sign-contract/company/page.tsx +++ b/src/app/quickaction-dashboard/register-sign-contract/company/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState, useEffect, useRef } from 'react' +import { useState, useEffect, useRef, useCallback } from 'react' import { useRouter } from 'next/navigation' import PageLayout from '../../../components/PageLayout' import useAuthStore from '../../../store/authStore' @@ -10,8 +10,10 @@ import { useToast } from '../../../components/toast/toastComponent' export default function CompanySignContractPage() { const router = useRouter() + const user = useAuthStore(s => s.user) // NEW + const isAuthReady = useAuthStore(s => (s as any).isAuthReady) // NEW const { accessToken } = useAuthStore() - const { refreshStatus } = useUserStatus() + const { userStatus, loading: statusLoading, refreshStatus } = useUserStatus() const { showToast } = useToast() const [companyName, setCompanyName] = useState('') @@ -246,15 +248,7 @@ export default function CompanySignContractPage() { // Redirect to main dashboard after short delay setTimeout(() => { - // Check if we came from tutorial - const urlParams = new URLSearchParams(window.location.search) - const fromTutorial = urlParams.get('tutorial') === 'true' - - if (fromTutorial) { - router.push('/quickaction-dashboard?tutorial=true') - } else { - router.push('/quickaction-dashboard') - } + router.push('/dashboard') }, 2000) } catch (error: any) { @@ -271,8 +265,49 @@ export default function CompanySignContractPage() { } } + // NEW: hard block if step already done OR all steps done + const [redirectTo, setRedirectTo] = useState(null) + const redirectOnceRef = useRef(false) + const smoothReplace = useCallback((to: string) => { + if (redirectOnceRef.current) return + redirectOnceRef.current = true + setRedirectTo(to) + window.setTimeout(() => router.replace(to), 200) + }, [router]) + + useEffect(() => { + if (statusLoading || !userStatus) return + const allDone = + !!userStatus.email_verified && + !!userStatus.documents_uploaded && + !!userStatus.profile_completed && + !!userStatus.contract_signed + + if (allDone) { + smoothReplace('/dashboard') // CHANGED + } else if (userStatus.contract_signed) { + smoothReplace('/quickaction-dashboard') // CHANGED + } + }, [statusLoading, userStatus, smoothReplace]) + + // NEW: must be logged in + useEffect(() => { + if (!isAuthReady) return + if (!user || !accessToken) smoothReplace('/login') + }, [isAuthReady, user, accessToken, smoothReplace]) + return ( + {/* NEW: smooth redirect overlay */} + {redirectTo && ( +
+
+
Redirecting…
+
Please wait
+
+
+ )} +
diff --git a/src/app/quickaction-dashboard/register-sign-contract/personal/page.tsx b/src/app/quickaction-dashboard/register-sign-contract/personal/page.tsx index 3332029..3158420 100644 --- a/src/app/quickaction-dashboard/register-sign-contract/personal/page.tsx +++ b/src/app/quickaction-dashboard/register-sign-contract/personal/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState, useEffect, useRef } from 'react' +import { useState, useEffect, useRef, useCallback } from 'react' import { useRouter } from 'next/navigation' import PageLayout from '../../../components/PageLayout' import useAuthStore from '../../../store/authStore' @@ -10,8 +10,10 @@ import { useToast } from '../../../components/toast/toastComponent' export default function PersonalSignContractPage() { const router = useRouter() + const user = useAuthStore(s => s.user) // NEW + const isAuthReady = useAuthStore(s => (s as any).isAuthReady) // NEW const { accessToken } = useAuthStore() - const { refreshStatus } = useUserStatus() + const { userStatus, loading: statusLoading, refreshStatus } = useUserStatus() // CHANGED const { showToast } = useToast() const [date, setDate] = useState('') @@ -160,6 +162,37 @@ export default function PersonalSignContractPage() { ]).finally(() => setPreviewLoading(false)) }, [accessToken]) + // NEW: hard block if step already done OR all steps done + const [redirectTo, setRedirectTo] = useState(null) + const redirectOnceRef = useRef(false) + const smoothReplace = useCallback((to: string) => { + if (redirectOnceRef.current) return + redirectOnceRef.current = true + setRedirectTo(to) + window.setTimeout(() => router.replace(to), 200) + }, [router]) + + // NEW: must be logged in + useEffect(() => { + if (!isAuthReady) return + if (!user || !accessToken) smoothReplace('/login') + }, [isAuthReady, user, accessToken, smoothReplace]) + + useEffect(() => { + if (statusLoading || !userStatus) return + const allDone = + !!userStatus.email_verified && + !!userStatus.documents_uploaded && + !!userStatus.profile_completed && + !!userStatus.contract_signed + + if (allDone) { + smoothReplace('/dashboard') // CHANGED + } else if (userStatus.contract_signed) { + smoothReplace('/quickaction-dashboard') // CHANGED + } + }, [statusLoading, userStatus, smoothReplace]) + const valid = () => { const contractChecked = agreeContract const dataChecked = agreeData @@ -242,15 +275,7 @@ export default function PersonalSignContractPage() { // Redirect to main dashboard after short delay setTimeout(() => { - // Check if we came from tutorial - const urlParams = new URLSearchParams(window.location.search) - const fromTutorial = urlParams.get('tutorial') === 'true' - - if (fromTutorial) { - router.push('/quickaction-dashboard?tutorial=true') - } else { - router.push('/quickaction-dashboard') - } + router.push('/dashboard') }, 2000) } catch (error: any) { @@ -269,6 +294,16 @@ export default function PersonalSignContractPage() { return ( + {/* NEW: smooth redirect overlay */} + {redirectTo && ( +
+
+
Redirecting…
+
Please wait
+
+
+ )} +
{/* Animated background (same as dashboard) */}
diff --git a/src/app/quickaction-dashboard/register-upload-id/company/page.tsx b/src/app/quickaction-dashboard/register-upload-id/company/page.tsx index 07d4b14..c1ff352 100644 --- a/src/app/quickaction-dashboard/register-upload-id/company/page.tsx +++ b/src/app/quickaction-dashboard/register-upload-id/company/page.tsx @@ -1,11 +1,13 @@ 'use client' import PageLayout from '../../../components/PageLayout' +import BlueBlurryBackground from '../../../components/background/blueblurry' import { DocumentArrowUpIcon, XMarkIcon } from '@heroicons/react/24/outline' import { useCompanyUploadId } from './hooks/useCompanyUploadId' import useAuthStore from '../../../store/authStore' -import { useEffect, useState } from 'react' +import { useEffect, useState, useCallback, useRef } from 'react' import { useRouter } from 'next/navigation' +import { useUserStatus } from '../../../hooks/useUserStatus' const DOC_TYPES = ['Personalausweis', 'Reisepass', 'Führerschein', 'Aufenthaltstitel'] @@ -26,48 +28,68 @@ export default function CompanyIdUploadPage() { } = useCompanyUploadId() const user = useAuthStore(s => s.user) + const isAuthReady = useAuthStore(s => (s as any).isAuthReady) // NEW const router = useRouter() - const [blocked, setBlocked] = useState(false) + const { userStatus, loading: statusLoading } = useUserStatus() + + // NEW: smooth redirect + const [redirectTo, setRedirectTo] = useState(null) + const redirectOnceRef = useRef(false) + const smoothReplace = useCallback((to: string) => { + if (redirectOnceRef.current) return + redirectOnceRef.current = true + setRedirectTo(to) + window.setTimeout(() => router.replace(to), 200) + }, [router]) - // Guard: only 'company' users allowed on this page useEffect(() => { const ut = (user as any)?.userType || (user as any)?.role - console.log('🧭 UploadID Guard [company]: userType =', ut) - if (ut && ut !== 'company') { - console.warn('🚫 UploadID Guard [company]: access denied for userType:', ut, '-> redirecting to personal upload') - setBlocked(true) - router.replace('/quickaction-dashboard/register-upload-id/personal') - } else if (ut === 'company') { - console.log('✅ UploadID Guard [company]: access granted') - } - }, [user, router]) + if (ut && ut !== 'company') smoothReplace('/quickaction-dashboard/register-upload-id/personal') // CHANGED + }, [user, smoothReplace]) - if (blocked) { + useEffect(() => { + if (statusLoading || !userStatus) return + const allDone = + !!userStatus.email_verified && + !!userStatus.documents_uploaded && + !!userStatus.profile_completed && + !!userStatus.contract_signed + + if (allDone) { + smoothReplace('/dashboard') // CHANGED + } else if (userStatus.documents_uploaded) { + smoothReplace('/quickaction-dashboard') // CHANGED + } + }, [statusLoading, userStatus, smoothReplace]) + + // NEW: must be logged in + useEffect(() => { + if (!isAuthReady) return + if (!user) smoothReplace('/login') + }, [isAuthReady, user, smoothReplace]) + + if (redirectTo) { return ( -
-
- Redirecting to the correct upload page… +
+
+
Redirecting…
+
Please wait
) } + const goBackToDashboard = () => { + // CHANGED: do NOT preserve ?tutorial=true, otherwise dashboard force-opens the tutorial again + router.push('/quickaction-dashboard') + } + return ( -
- {/* Animated background (same as dashboard) */} -
- {/* Soft gradient blobs */} -
-
-
- {/* Subtle radial highlight */} -
-
- -
+ +
setIdNumber(e.target.value)} - className={`${inputBase} ${idNumber ? 'text-gray-900' : 'text-gray-700'}`} + className={`${inputBase} ${idNumber ? 'text-gray-900' : 'text-gray-700'} focus:ring-[#8D6B1D] focus:border-transparent`} placeholder="Enter contact person's ID number" required /> @@ -105,7 +127,7 @@ export default function CompanyIdUploadPage() { ) : ( <> - -

+ +

Click to upload front side

@@ -203,7 +227,7 @@ export default function CompanyIdUploadPage() { {...dropHandlers} onDrop={e => onDrop(e, 'extra')} onClick={() => openPicker('extra')} - className="group relative flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50/60 px-4 py-6 sm:py-10 text-center hover:border-indigo-400 hover:bg-indigo-50/40 cursor-pointer transition" + className="group relative flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50/60 px-4 py-6 sm:py-10 text-center hover:border-[#8D6B1D] hover:bg-[#8D6B1D]/5 cursor-pointer transition" > ) : ( <> - -

+ +

Click to upload back side

@@ -249,11 +273,11 @@ export default function CompanyIdUploadPage() {

{/* Info */} -
-

+

+

Please ensure your ID documents:

-
    +
    • Are clearly visible and readable
    • Show all four corners
    • Are not expired
    • @@ -273,11 +297,20 @@ export default function CompanyIdUploadPage() {
)} -
+ {/* CHANGED: add "Back to Dashboard" next to submit */} +
+ + @@ -285,7 +318,7 @@ export default function CompanyIdUploadPage() {
-
+ ) } diff --git a/src/app/quickaction-dashboard/register-upload-id/personal/page.tsx b/src/app/quickaction-dashboard/register-upload-id/personal/page.tsx index 370bd3a..1ccc8ef 100644 --- a/src/app/quickaction-dashboard/register-upload-id/personal/page.tsx +++ b/src/app/quickaction-dashboard/register-upload-id/personal/page.tsx @@ -2,10 +2,12 @@ import { usePersonalUploadId } from './hooks/usePersonalUploadId' import PageLayout from '../../../components/PageLayout' -import { useEffect, useState } from 'react' +import BlueBlurryBackground from '../../../components/background/blueblurry' +import { useEffect, useState, useCallback, useRef } from 'react' import { useRouter } from 'next/navigation' import useAuthStore from '../../../store/authStore' import { DocumentArrowUpIcon, XMarkIcon } from '@heroicons/react/24/outline' +import { useUserStatus } from '../../../hooks/useUserStatus' // Add back ID types for the dropdown const ID_TYPES = [ @@ -16,29 +18,54 @@ const ID_TYPES = [ ] export default function PersonalIdUploadPage() { - // NEW: guard company users from accessing personal page const user = useAuthStore(s => s.user) + const isAuthReady = useAuthStore(s => (s as any).isAuthReady) // NEW const router = useRouter() - const [blocked, setBlocked] = useState(false) + const { userStatus, loading: statusLoading } = useUserStatus() + + // NEW: smooth redirect + const [redirectTo, setRedirectTo] = useState(null) + const redirectOnceRef = useRef(false) + const smoothReplace = useCallback((to: string) => { + if (redirectOnceRef.current) return + redirectOnceRef.current = true + setRedirectTo(to) + window.setTimeout(() => router.replace(to), 200) + }, [router]) useEffect(() => { const ut = (user as any)?.userType || (user as any)?.role - console.log('🧭 UploadID Guard [personal]: userType =', ut) - if (ut && ut !== 'personal') { - console.warn('🚫 UploadID Guard [personal]: access denied for userType:', ut, '-> redirecting to company upload') - setBlocked(true) - router.replace('/quickaction-dashboard/register-upload-id/company') - } else if (ut === 'personal') { - console.log('✅ UploadID Guard [personal]: access granted') - } - }, [user, router]) + if (ut && ut !== 'personal') smoothReplace('/quickaction-dashboard/register-upload-id/company') // CHANGED + }, [user, smoothReplace]) - if (blocked) { + useEffect(() => { + if (statusLoading || !userStatus) return + const allDone = + !!userStatus.email_verified && + !!userStatus.documents_uploaded && + !!userStatus.profile_completed && + !!userStatus.contract_signed + + if (allDone) { + smoothReplace('/dashboard') // CHANGED + } else if (userStatus.documents_uploaded) { + smoothReplace('/quickaction-dashboard') // CHANGED + } + }, [statusLoading, userStatus, smoothReplace]) + + // NEW: must be logged in + useEffect(() => { + if (!isAuthReady) return + if (!user) smoothReplace('/login') + }, [isAuthReady, user, smoothReplace]) + + if (redirectTo) { return ( -
-
- Redirecting to the correct upload page… +
+
+
Redirecting…
+
Please wait
@@ -58,20 +85,15 @@ export default function PersonalIdUploadPage() { inputBase, } = usePersonalUploadId() + const goBackToDashboard = () => { + // CHANGED: do NOT preserve ?tutorial=true, otherwise dashboard force-opens the tutorial again + router.push('/quickaction-dashboard') + } + return ( -
- {/* Animated background (same as dashboard) */} -
- {/* Soft gradient blobs */} -
-
-
- {/* Subtle radial highlight */} -
-
- -
+ +
setIdNumber(e.target.value)} placeholder="Enter your ID number" - className={`${inputBase} ${idNumber ? 'text-gray-900' : 'text-gray-700'}`} + className={`${inputBase} ${idNumber ? 'text-gray-900' : 'text-gray-700'} focus:ring-[#8D6B1D] focus:border-transparent`} required />

@@ -109,7 +131,7 @@ export default function PersonalIdUploadPage() { ) : ( <> - -

+ +

Click to upload front side

@@ -214,7 +236,7 @@ export default function PersonalIdUploadPage() { {...dropEvents} onDrop={e => onDrop(e, 'back')} onClick={() => openPicker('back')} - className="group relative flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50/60 px-4 py-6 sm:py-10 text-center hover:border-indigo-400 hover:bg-indigo-50/40 cursor-pointer transition" + className="group relative flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-gray-50/60 px-4 py-6 sm:py-10 text-center hover:border-[#8D6B1D] hover:bg-[#8D6B1D]/5 cursor-pointer transition" > ) : ( <> - -

+ +

Click to upload back side

@@ -260,11 +282,11 @@ export default function PersonalIdUploadPage() {

{/* Info Box, errors, success, submit */} -
-

+

+

Please ensure your ID documents:

-
    +
    • Are clearly visible and readable
    • Show all four corners
    • Are not expired
    • @@ -284,11 +306,20 @@ export default function PersonalIdUploadPage() {
)} -
+ {/* CHANGED: add "Back to Dashboard" next to submit */} +
+ + @@ -296,7 +327,7 @@ export default function PersonalIdUploadPage() {
-
+ ) } diff --git a/src/app/referral-management/components/referralLinksListWidget.tsx b/src/app/referral-management/components/referralLinksListWidget.tsx index 230a201..16a4d98 100644 --- a/src/app/referral-management/components/referralLinksListWidget.tsx +++ b/src/app/referral-management/components/referralLinksListWidget.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react' import { ClipboardDocumentIcon } from '@heroicons/react/24/outline' +import { useToast } from '../../components/toast/toastComponent' interface ReferralLink { id?: string | number @@ -39,6 +40,7 @@ function shortLink(href?: string) { } export default function ReferralLinksListWidget({ links, onDeactivate }: Props) { + const { showToast } = useToast() // Local floating tooltip (fixed) so table doesn't scroll to show it const [tooltip, setTooltip] = useState<{ visible: boolean; text: string; x: number; y: number }>({ visible: false, @@ -54,6 +56,24 @@ export default function ReferralLinksListWidget({ links, onDeactivate }: Props) } const hideTooltip = () => setTooltip(t => ({ ...t, visible: false })) + const copyToClipboard = async (text: string) => { + try { + await navigator.clipboard.writeText(text) + showToast({ + variant: 'success', + title: 'Copied', + message: 'Link copied to Zwischenablage.', + duration: 2500, + }) + } catch { + showToast({ + variant: 'error', + title: 'Copy failed', + message: 'Could not copy link to clipboard.', + }) + } + } + return ( <>
@@ -147,15 +167,16 @@ export default function ReferralLinksListWidget({ links, onDeactivate }: Props) {/* Desktop/Tablet copy button */} {/* Mobile: only copy button */} diff --git a/src/app/referral-management/page.tsx b/src/app/referral-management/page.tsx index 6b55929..e5c9621 100644 --- a/src/app/referral-management/page.tsx +++ b/src/app/referral-management/page.tsx @@ -12,8 +12,10 @@ import RegisteredUserList from './components/registeredUserList' import { useRouter } from 'next/navigation' import useAuthStore from '../store/authStore' import { useRegisteredUsers } from './hooks/registeredUsers' +import { ToastProvider, useToast } from '../components/toast/toastComponent' // NEW -export default function ReferralManagementPage() { +function ReferralManagementPageInner() { + const { showToast } = useToast() // NEW const router = useRouter() const user = useAuthStore(s => s.user) const isAuthReady = useAuthStore(s => s.isAuthReady) @@ -48,11 +50,30 @@ export default function ReferralManagementPage() { try { const tokenId = selectedLink?.tokenId ?? selectedLink?.id if (tokenId == null) return + const res = await deactivateReferralLink(tokenId) - console.log('✅ Deactivate result:', res) + + if (res?.ok) { + showToast({ + variant: 'success', + title: 'Link deactivated', + message: 'The referral link has been deactivated successfully.', + }) + } else { + showToast({ + variant: 'error', + title: 'Deactivate failed', + message: (res as any)?.body?.message || 'Could not deactivate the referral link.', + }) + } + await loadData() - } catch (e) { - // optional: toast error + } catch (e: any) { + showToast({ + variant: 'error', + title: 'Deactivate failed', + message: 'Network error while deactivating the referral link.', + }) } finally { setDeactivatePending(false) setDeactivateOpen(false) @@ -71,7 +92,6 @@ export default function ReferralManagementPage() { const run = async () => { if (!isAuthReady) return if (!user) { - console.log('🔐 referral-management: no user, redirect to /login') router.replace('/login') return } @@ -79,11 +99,15 @@ export default function ReferralManagementPage() { // Resolve user id const uid = (user as any)?.id ?? (user as any)?._id ?? (user as any)?.userId if (!uid) { - console.warn('⚠️ referral-management: user id missing, denying access') if (!cancelled) { setHasReferralPerm(false) setIsPermChecked(true) } + showToast({ + variant: 'error', + title: 'Access check failed', + message: 'User id is missing. Redirecting…', + }) router.replace('/dashboard') return } @@ -112,9 +136,7 @@ export default function ReferralManagementPage() { ...(tokenToUse ? { Authorization: `Bearer ${tokenToUse}` } : {}) } }) - console.log('📡 referral-management: permissions status:', res.status) const body = await res.json().catch(() => null) - console.log('📦 referral-management: permissions body:', body) const permsSrc = body?.data?.permissions ?? body?.permissions ?? body let can = false @@ -133,22 +155,30 @@ export default function ReferralManagementPage() { } if (!can) { - console.log('⛔ referral-management: missing permission, redirect to /dashboard') + showToast({ + variant: 'warning', + title: 'Access denied', + message: 'You do not have permission to access Referral Management.', + }) router.replace('/dashboard') } } catch (e) { - console.error('❌ referral-management: fetch permissions error:', e) if (!cancelled) { setHasReferralPerm(false) setIsPermChecked(true) } + showToast({ + variant: 'error', + title: 'Permission check failed', + message: 'Could not verify permissions. Redirecting…', + }) router.replace('/dashboard') } } run() return () => { cancelled = true } - }, [isAuthReady, user, accessToken, refreshAuthToken, router]) + }, [isAuthReady, user, accessToken, refreshAuthToken, router, showToast]) // CHANGED: add showToast // Helper: normalize list payload shapes const normalizeList = (raw: any): any[] => { @@ -187,8 +217,22 @@ export default function ReferralManagementPage() { // Helper: fetch stats + list const loadData = async () => { const [statsRes, listRes] = await Promise.all([fetchReferralStats(), fetchReferralList()]) - console.log('✅ Referral stats fetched:', statsRes) - console.log('✅ Referral list fetched:', listRes) + + if (!statsRes.ok) { + showToast({ + variant: 'error', + title: 'Load failed', + message: 'Could not load referral statistics.', + }) + } + if (!listRes.ok) { + showToast({ + variant: 'error', + title: 'Load failed', + message: 'Could not load referral links.', + }) + } + if (statsRes.ok && statsRes.body) { const b: any = statsRes.body?.data || statsRes.body?.stats || statsRes.body setStats({ @@ -259,8 +303,10 @@ export default function ReferralManagementPage() { loading={usersLoading} /> - {/* Generator */} - + {/* Generator (wrapped for desktop hover animation) */} +
+ +
{/* Referral links list (refactored) */} @@ -278,4 +324,12 @@ export default function ReferralManagementPage() { /> ) +} + +export default function ReferralManagementPage() { + return ( + + + + ) } \ No newline at end of file diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx index 300fcf7..399aae0 100644 --- a/src/app/register/page.tsx +++ b/src/app/register/page.tsx @@ -8,7 +8,8 @@ import PageLayout from '../components/PageLayout' import SessionDetectedModal from './components/SessionDetectedModal' import InvalidRefLinkModal from './components/invalidRefLinkModal' import { ToastProvider, useToast } from '../components/toast/toastComponent' -import Waves from '../components/waves' +import Waves from '../components/background/waves' +import BlueBlurryBackground from '../components/background/blueblurry' // NEW // NEW: inner component that actually uses useToast and all the logic function RegisterPageInner() { @@ -161,26 +162,36 @@ function RegisterPageInner() { opacity: 'var(--pp-page-shift-opacity, 1)', } + const BackgroundShell = ({ children }: { children: React.ReactNode }) => { + return isMobile ? ( + {children} + ) : ( +
+ + {children} +
+ ) + } + // --- Render branches (unchanged except classNames) --- if (!isRefChecked) { return ( -
- +
-
+ ) } @@ -211,21 +222,7 @@ function RegisterPageInner() { if (invalidRef) { return ( -
- +
-
+ ) } @@ -261,21 +258,7 @@ function RegisterPageInner() { // normal register return ( -
- +
-
+ ) }