beautify: register #1
This commit is contained in:
parent
d5cb8e673d
commit
2bddd8360b
@ -45,6 +45,7 @@ const navLinks = [
|
|||||||
export default function Header() {
|
export default function Header() {
|
||||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
||||||
const [isDark, setIsDark] = useState(false)
|
const [isDark, setIsDark] = useState(false)
|
||||||
|
const [mounted, setMounted] = useState(false) // added
|
||||||
const user = useAuthStore((s) => s.user);
|
const user = useAuthStore((s) => s.user);
|
||||||
const logout = useAuthStore((s) => s.logout);
|
const logout = useAuthStore((s) => s.logout);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -83,6 +84,7 @@ export default function Header() {
|
|||||||
document.documentElement.classList.remove('dark')
|
document.documentElement.classList.remove('dark')
|
||||||
setIsDark(false)
|
setIsDark(false)
|
||||||
}
|
}
|
||||||
|
setMounted(true) // hydration complete
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const toggleTheme = useCallback(() => {
|
const toggleTheme = useCallback(() => {
|
||||||
@ -99,6 +101,9 @@ export default function Header() {
|
|||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const isLoggedIn = !!user
|
||||||
|
const userPresent = mounted && isLoggedIn
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className="relative isolate z-10 border-b border-white/10 shadow-lg shadow-black/30 after:pointer-events-none after:absolute after:inset-0 after:-z-10 after:bg-[radial-gradient(circle_at_20%_20%,rgba(56,124,255,0.18),transparent_55%),radial-gradient(circle_at_80%_35%,rgba(139,92,246,0.16),transparent_60%)]"
|
className="relative isolate z-10 border-b border-white/10 shadow-lg shadow-black/30 after:pointer-events-none after:absolute after:inset-0 after:-z-10 after:bg-[radial-gradient(circle_at_20%_20%,rgba(56,124,255,0.18),transparent_55%),radial-gradient(circle_at_80%_35%,rgba(139,92,246,0.16),transparent_60%)]"
|
||||||
@ -190,60 +195,67 @@ export default function Header() {
|
|||||||
{/* Removed user profile Popover from here (now on right side) */}
|
{/* Removed user profile Popover from here (now on right side) */}
|
||||||
</PopoverGroup>
|
</PopoverGroup>
|
||||||
<div className="hidden lg:flex lg:flex-1 lg:justify-end lg:items-center lg:gap-x-4">
|
<div className="hidden lg:flex lg:flex-1 lg:justify-end lg:items-center lg:gap-x-4">
|
||||||
{/* Avatar first (when logged in) - repositioned dropdown */}
|
{/* Stable auth slot to avoid SSR/CSR structural drift */}
|
||||||
{user && (
|
<div className="flex items-center">
|
||||||
<Popover className="relative">
|
{userPresent ? (
|
||||||
<PopoverButton className="flex items-center gap-x-1 text-sm font-semibold text-gray-900 dark:text-white">
|
<Popover className="relative">
|
||||||
<Avatar
|
<PopoverButton className="flex items-center gap-x-1 text-sm font-semibold text-gray-900 dark:text-white">
|
||||||
src=""
|
<Avatar
|
||||||
initials={getUserInitials()}
|
src=""
|
||||||
className="size-8 bg-gradient-to-br from-indigo-500/40 to-indigo-600/60 text-white"
|
initials={(() => {
|
||||||
/>
|
if (!user) return 'U'
|
||||||
<ChevronDownIcon aria-hidden="true" className="size-5 flex-none text-gray-500" />
|
if (user.firstName || user.lastName) {
|
||||||
</PopoverButton>
|
return ((user.firstName?.[0] || '') + (user.lastName?.[0] || '')).toUpperCase()
|
||||||
<PopoverPanel
|
}
|
||||||
transition
|
return user.email ? user.email[0].toUpperCase() : 'U'
|
||||||
className="absolute left-0 top-full mt-2 w-64 rounded-md bg-white dark:bg-gray-900 ring-1 ring-black/10 dark:ring-white/10 shadow-lg data-closed:-translate-y-1 data-closed:opacity-0 data-enter:duration-200 data-leave:duration-150"
|
})()}
|
||||||
>
|
className="size-8 bg-gradient-to-br from-indigo-500/40 to-indigo-600/60 text-white"
|
||||||
<div className="p-4">
|
/>
|
||||||
<div className="flex flex-col border-b border-gray-200 dark:border-white/10 pb-4 mb-4">
|
<ChevronDownIcon aria-hidden="true" className="size-5 flex-none text-gray-500" />
|
||||||
<div className="font-medium text-gray-900 dark:text-white">
|
</PopoverButton>
|
||||||
{user?.firstName && user?.lastName ? `${user.firstName} ${user.lastName}` : (user?.email || 'User')}
|
<PopoverPanel
|
||||||
</div>
|
transition
|
||||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
className="absolute left-0 top-full mt-2 w-64 rounded-md bg-white dark:bg-gray-900 ring-1 ring-black/10 dark:ring-white/10 shadow-lg data-closed:-translate-y-1 data-closed:opacity-0 data-enter:duration-200 data-leave:duration-150"
|
||||||
{user?.email || 'user@example.com'}
|
>
|
||||||
|
<div className="p-4">
|
||||||
|
<div className="flex flex-col border-b border-gray-200 dark:border-white/10 pb-4 mb-4">
|
||||||
|
<div className="font-medium text-gray-900 dark:text-white">
|
||||||
|
{user?.firstName && user?.lastName ? `${user.firstName} ${user.lastName}` : (user?.email || 'User')}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
{user?.email || 'user@example.com'}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => router.push('/profile')}
|
||||||
|
className="flex items-center gap-x-2 w-full text-left p-2 text-sm text-gray-800 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-white/5 rounded-md"
|
||||||
|
>
|
||||||
|
<UserCircleIcon className="size-5 text-gray-400" />
|
||||||
|
Profile
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleLogout}
|
||||||
|
className="flex items-center gap-x-2 w-full text-left p-2 text-sm text-gray-800 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-white/5 rounded-md"
|
||||||
|
>
|
||||||
|
<ArrowRightOnRectangleIcon className="size-5 text-gray-400" />
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
</PopoverPanel>
|
||||||
onClick={() => router.push('/profile')}
|
</Popover>
|
||||||
className="flex items-center gap-x-2 w-full text-left p-2 text-sm text-gray-800 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-white/5 rounded-md"
|
) : mounted ? (
|
||||||
>
|
<button
|
||||||
<UserCircleIcon className="size-5 text-gray-400" />
|
onClick={() => router.push('/login')}
|
||||||
Profile
|
className="text-sm/6 font-semibold text-gray-900 dark:text-white hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors"
|
||||||
</button>
|
>
|
||||||
<button
|
Log in <span aria-hidden="true">→</span>
|
||||||
onClick={handleLogout}
|
</button>
|
||||||
className="flex items-center gap-x-2 w-full text-left p-2 text-sm text-gray-800 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-white/5 rounded-md"
|
) : (
|
||||||
>
|
<div aria-hidden="true" className="w-20 h-8 rounded-md bg-gray-200 dark:bg-gray-700/70 animate-pulse" />
|
||||||
<ArrowRightOnRectangleIcon className="size-5 text-gray-400" />
|
)}
|
||||||
Logout
|
</div>
|
||||||
</button>
|
{/* Language & theme remain after auth slot */}
|
||||||
</div>
|
|
||||||
</PopoverPanel>
|
|
||||||
</Popover>
|
|
||||||
)}
|
|
||||||
{/* Login button (only when not logged in) */}
|
|
||||||
{!user && (
|
|
||||||
<button
|
|
||||||
onClick={() => router.push('/login')}
|
|
||||||
className="text-sm/6 font-semibold text-gray-900 dark:text-white hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors"
|
|
||||||
>
|
|
||||||
Log in <span aria-hidden="true">→</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{/* Language after avatar/login */}
|
|
||||||
<LanguageSwitcher variant={isDark ? 'dark' : 'light'} />
|
<LanguageSwitcher variant={isDark ? 'dark' : 'light'} />
|
||||||
{/* Theme toggle last */}
|
|
||||||
<button
|
<button
|
||||||
onClick={toggleTheme}
|
onClick={toggleTheme}
|
||||||
aria-label="Toggle theme"
|
aria-label="Toggle theme"
|
||||||
@ -254,7 +266,6 @@ export default function Header() {
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<Dialog open={mobileMenuOpen} onClose={setMobileMenuOpen} className="lg:hidden">
|
<Dialog open={mobileMenuOpen} onClose={setMobileMenuOpen} className="lg:hidden">
|
||||||
{/* Overlay with fade */}
|
|
||||||
<Transition
|
<Transition
|
||||||
appear
|
appear
|
||||||
show={mobileMenuOpen}
|
show={mobileMenuOpen}
|
||||||
@ -308,7 +319,11 @@ export default function Header() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-6 flow-root">
|
<div className="mt-6 flow-root">
|
||||||
<div className="-my-6 divide-y divide-gray-200 dark:divide-white/10">
|
<div className="-my-6 divide-y divide-gray-200 dark:divide-white/10">
|
||||||
{user ? (
|
{!mounted ? (
|
||||||
|
<div className="py-6 px-3">
|
||||||
|
<div className="h-28 w-full rounded-lg bg-gray-200 dark:bg-gray-700 animate-pulse" />
|
||||||
|
</div>
|
||||||
|
) : user ? (
|
||||||
<>
|
<>
|
||||||
{/* User info now FIRST under logo */}
|
{/* User info now FIRST under logo */}
|
||||||
<div className="pt-6 space-y-2">
|
<div className="pt-6 space-y-2">
|
||||||
|
|||||||
@ -8,13 +8,54 @@ interface SessionDetectedModalProps {
|
|||||||
open: boolean
|
open: boolean
|
||||||
onLogout: () => void
|
onLogout: () => void
|
||||||
onCancel: () => void
|
onCancel: () => void
|
||||||
|
inline?: boolean // added
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SessionDetectedModal({
|
export default function SessionDetectedModal({
|
||||||
open,
|
open,
|
||||||
onLogout,
|
onLogout,
|
||||||
onCancel
|
onCancel,
|
||||||
|
inline = false
|
||||||
}: SessionDetectedModalProps) {
|
}: SessionDetectedModalProps) {
|
||||||
|
if (inline) {
|
||||||
|
return (
|
||||||
|
<div className="w-full flex justify-center items-center flex-1 min-h-[50vh]">
|
||||||
|
<div className="bg-white px-6 py-6 rounded-xl shadow-xl max-w-lg w-full border border-gray-200">
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div className="flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-orange-100">
|
||||||
|
<ExclamationTriangleIcon className="h-6 w-6 text-orange-600" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-base font-semibold leading-6 text-[#0F172A]">
|
||||||
|
Aktive Sitzung erkannt
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 text-sm text-[#4A4A4A]">
|
||||||
|
Du bist bereits angemeldet. Um dich zu registrieren, musst du dich zuerst abmelden
|
||||||
|
oder du kannst zum Dashboard gehen.
|
||||||
|
</p>
|
||||||
|
<div className="mt-5 flex flex-col sm:flex-row gap-3 sm:justify-end">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onCancel}
|
||||||
|
className="inline-flex justify-center rounded-md bg-white px-4 py-2 text-sm font-medium text-[#4A4A4A] shadow-sm ring-1 ring-gray-300 hover:bg-gray-50 transition-colors"
|
||||||
|
>
|
||||||
|
Zum Dashboard
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onLogout}
|
||||||
|
className="inline-flex justify-center rounded-md bg-[#8D6B1D] px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-[#7A5E1A] transition-colors"
|
||||||
|
>
|
||||||
|
Abmelden und registrieren
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={open} as={Fragment}>
|
<Transition.Root show={open} as={Fragment}>
|
||||||
<Dialog as="div" className="relative z-50" onClose={onCancel}>
|
<Dialog as="div" className="relative z-50" onClose={onCancel}>
|
||||||
|
|||||||
@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useSearchParams, useRouter } from 'next/navigation'
|
import { useSearchParams, useRouter } from 'next/navigation'
|
||||||
import PageLayout from '../components/PageLayout'
|
|
||||||
import useAuthStore from '../store/authStore'
|
import useAuthStore from '../store/authStore'
|
||||||
|
import RegisterForm from './components/RegisterForm'
|
||||||
|
import PageLayout from '../components/PageLayout'
|
||||||
|
import SessionDetectedModal from './components/SessionDetectedModal'
|
||||||
|
|
||||||
export default function RegisterPage() {
|
export default function RegisterPage() {
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
@ -11,142 +13,69 @@ export default function RegisterPage() {
|
|||||||
const [registered, setRegistered] = useState(false)
|
const [registered, setRegistered] = useState(false)
|
||||||
const [mode, setMode] = useState<'personal' | 'company'>('personal')
|
const [mode, setMode] = useState<'personal' | 'company'>('personal')
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
// Auth state
|
// Auth state
|
||||||
const user = useAuthStore(state => state.user)
|
const user = useAuthStore(state => state.user)
|
||||||
const logout = useAuthStore(state => state.logout)
|
const logout = useAuthStore(state => state.logout)
|
||||||
|
|
||||||
// Session management
|
// Session management
|
||||||
const [showSessionModal, setShowSessionModal] = useState(false)
|
const [showSessionModal, setShowSessionModal] = useState(false)
|
||||||
const [sessionCleared, setSessionCleared] = useState(false)
|
const [sessionCleared, setSessionCleared] = useState(false)
|
||||||
|
|
||||||
// Redirect to login on successful registration
|
// Redirect to login after simulated registration
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (registered) {
|
if (registered) {
|
||||||
// Show success toast would go here
|
const t = setTimeout(() => router.push('/login'), 1200)
|
||||||
setTimeout(() => router.push('/login'), 1200)
|
return () => clearTimeout(t)
|
||||||
}
|
}
|
||||||
}, [registered, router])
|
}, [registered, router])
|
||||||
|
|
||||||
// Check for existing session
|
// Detect existing logged-in session
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user && !sessionCleared) {
|
if (user && !sessionCleared) setShowSessionModal(true)
|
||||||
setShowSessionModal(true)
|
|
||||||
}
|
|
||||||
}, [user, sessionCleared])
|
}, [user, sessionCleared])
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
await logout()
|
await logout()
|
||||||
setSessionCleared(true)
|
setSessionCleared(true)
|
||||||
setShowSessionModal(false)
|
setShowSessionModal(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
setShowSessionModal(false)
|
setShowSessionModal(false)
|
||||||
router.push('/dashboard')
|
router.push('/dashboard')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Block registration if session detected and not cleared
|
|
||||||
if (showSessionModal) {
|
|
||||||
return (
|
|
||||||
<PageLayout>
|
|
||||||
<div className="min-h-screen flex items-center justify-center">
|
|
||||||
<div className="bg-white p-8 rounded-lg shadow-lg max-w-md w-full">
|
|
||||||
<h3 className="text-lg font-semibold text-[#0F172A] mb-4">
|
|
||||||
Aktive Sitzung erkannt
|
|
||||||
</h3>
|
|
||||||
<p className="text-[#4A4A4A] mb-6">
|
|
||||||
Du bist bereits angemeldet. Um dich zu registrieren, musst du dich zuerst abmelden.
|
|
||||||
</p>
|
|
||||||
<div className="flex gap-3">
|
|
||||||
<button
|
|
||||||
onClick={handleLogout}
|
|
||||||
className="flex-1 bg-[#8D6B1D] text-white px-4 py-2 rounded-lg hover:bg-[#7A5E1A] transition-colors"
|
|
||||||
>
|
|
||||||
Abmelden und registrieren
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={handleCancel}
|
|
||||||
className="flex-1 bg-gray-100 text-[#4A4A4A] px-4 py-2 rounded-lg hover:bg-gray-200 transition-colors"
|
|
||||||
>
|
|
||||||
Zum Dashboard
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PageLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent form rendering until session is cleared
|
|
||||||
if (!sessionCleared && user) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageLayout>
|
<PageLayout>
|
||||||
<div className="min-h-screen w-full flex flex-col px-4 py-8">
|
<main className="w-full flex flex-col flex-1 px-4 pt-12 pb-16 gap-10 min-h-[70vh]">
|
||||||
<div className="flex-1 flex items-start justify-center w-full pt-8">
|
{showSessionModal ? (
|
||||||
<div className="w-full max-w-4xl mx-auto bg-white rounded-2xl shadow-2xl px-6 py-8 sm:px-12 sm:py-10">
|
<div className="flex flex-1 items-center justify-center">
|
||||||
<div className="text-center">
|
<SessionDetectedModal
|
||||||
<h2 className="text-2xl sm:text-3xl font-extrabold text-[#0F172A] mb-4">
|
inline
|
||||||
Registrierung für Profit Planet
|
open
|
||||||
</h2>
|
onLogout={handleLogout}
|
||||||
{refToken && (
|
onCancel={handleCancel}
|
||||||
<p className="text-base sm:text-sm text-[#8D6B1D] font-medium mb-8">
|
/>
|
||||||
Du wurdest eingeladen! Referral-Token: {refToken}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Mode Toggle */}
|
|
||||||
<div className="flex justify-center mb-8">
|
|
||||||
<div className="bg-gray-100 p-1 rounded-lg">
|
|
||||||
<button
|
|
||||||
className={`px-6 py-2 rounded-md font-semibold text-sm transition-all duration-200 ${
|
|
||||||
mode === 'personal'
|
|
||||||
? 'bg-[#8D6B1D] text-white shadow-sm'
|
|
||||||
: 'bg-transparent text-[#4A4A4A] hover:text-[#8D6B1D]'
|
|
||||||
}`}
|
|
||||||
onClick={() => setMode('personal')}
|
|
||||||
>
|
|
||||||
Privatperson
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={`px-6 py-2 rounded-md font-semibold text-sm transition-all duration-200 ${
|
|
||||||
mode === 'company'
|
|
||||||
? 'bg-[#8D6B1D] text-white shadow-sm'
|
|
||||||
: 'bg-transparent text-[#4A4A4A] hover:text-[#8D6B1D]'
|
|
||||||
}`}
|
|
||||||
onClick={() => setMode('company')}
|
|
||||||
>
|
|
||||||
Unternehmen
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-center">
|
|
||||||
<p className="text-[#4A4A4A] mb-4">
|
|
||||||
Registrierungsmodus: <strong>{mode === 'personal' ? 'Privatperson' : 'Unternehmen'}</strong>
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
onClick={() => setRegistered(true)}
|
|
||||||
className="bg-[#8D6B1D] text-white px-6 py-3 rounded-lg hover:bg-[#7A5E1A] transition-colors font-semibold"
|
|
||||||
>
|
|
||||||
Test: Registrierung erfolgreich simulieren
|
|
||||||
</button>
|
|
||||||
<div className="mt-8">
|
|
||||||
<p className="text-[#4A4A4A]">
|
|
||||||
Bereits registriert?{' '}
|
|
||||||
<a href="/login" className="text-[#8D6B1D] hover:text-[#7A5E1A] font-medium transition-colors">
|
|
||||||
Hier anmelden
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
</div>
|
<>
|
||||||
|
{(!user || sessionCleared) && (
|
||||||
|
<RegisterForm
|
||||||
|
mode={mode}
|
||||||
|
setMode={setMode}
|
||||||
|
refToken={refToken}
|
||||||
|
onRegistered={() => setRegistered(true)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{registered && (
|
||||||
|
<div className="mt-6 mx-auto text-center text-sm text-gray-600">
|
||||||
|
Registrierung erfolgreich – Weiterleitung...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</main>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user