fix(auth): open sign-in above menus and close the burger first
This commit is contained in:
parent
7abeb2d030
commit
639afd8550
5 changed files with 94 additions and 34 deletions
|
|
@ -22,6 +22,8 @@ interface LanguageToggleProps {
|
|||
inline?: boolean;
|
||||
/** Additional class names */
|
||||
className?: string;
|
||||
/** Called after a locale is chosen (e.g. to close a sheet sub-view). */
|
||||
onSelect?: () => void;
|
||||
/** Variant style */
|
||||
variant?: 'default' | 'outline' | 'ghost';
|
||||
}
|
||||
|
|
@ -52,7 +54,7 @@ function Flag({ flag, className }: { flag: string; className?: string }) {
|
|||
/**
|
||||
* Language toggle dropdown for switching between the available locales
|
||||
*/
|
||||
export function LanguageToggle({ compact = false, inline = false, className, variant = 'ghost' }: LanguageToggleProps) {
|
||||
export function LanguageToggle({ compact = false, inline = false, className, variant = 'ghost', onSelect }: LanguageToggleProps) {
|
||||
const { language, setLanguage, t } = useTranslation();
|
||||
const [query, setQuery] = useState('');
|
||||
|
||||
|
|
@ -89,7 +91,7 @@ export function LanguageToggle({ compact = false, inline = false, className, var
|
|||
<button
|
||||
key={lang.code}
|
||||
type="button"
|
||||
onClick={() => setLanguage(lang.code)}
|
||||
onClick={() => { setLanguage(lang.code); onSelect?.(); }}
|
||||
className={cn(
|
||||
'flex items-center gap-3 rounded-xl px-4 py-3 text-left transition-all duration-200',
|
||||
language === lang.code
|
||||
|
|
@ -156,7 +158,7 @@ export function LanguageToggle({ compact = false, inline = false, className, var
|
|||
{filteredLanguages.map((lang) => (
|
||||
<DropdownMenuItem
|
||||
key={lang.code}
|
||||
onClick={() => setLanguage(lang.code)}
|
||||
onClick={() => { setLanguage(lang.code); onSelect?.(); }}
|
||||
className={cn(
|
||||
'flex min-h-11 items-center gap-3 cursor-pointer',
|
||||
language === lang.code && 'bg-primary/10 text-primary'
|
||||
|
|
|
|||
|
|
@ -520,6 +520,9 @@ export function Layout({ children, noPadding }: LayoutProps) {
|
|||
const isFullBleed = noPadding || location.pathname === '/' || location.pathname === '/map';
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const [mobileLanguageView, setMobileLanguageView] = useState(false);
|
||||
// One auth dialog for the header + burger menu. Owning it here (not inside
|
||||
// LoginArea) keeps it above the sheet and outside the sheet's unmount tree.
|
||||
const [authDialog, setAuthDialog] = useState<'login' | 'signup' | null>(null);
|
||||
const [showStatusDialog, setShowStatusDialog] = useState(false);
|
||||
const [showCustomizeNav, setShowCustomizeNav] = useState(false);
|
||||
const [showAddAccount, setShowAddAccount] = useState(false);
|
||||
|
|
@ -537,15 +540,28 @@ export function Layout({ children, noPadding }: LayoutProps) {
|
|||
try { sessionStorage.setItem(WELCOME_LOGIN_SNOOZED, '1'); } catch { /* ignore */ }
|
||||
setShowWelcomeLogin(false);
|
||||
};
|
||||
const requestAuth = (kind: 'login' | 'signup') => {
|
||||
// Close every overlay that owns a lower z-index before opening the auth
|
||||
// dialog. The burger sheet is z-2100; a dialog rendered inside it is z-50
|
||||
// and becomes invisible/unclickable.
|
||||
try { sessionStorage.setItem(WELCOME_LOGIN_SNOOZED, '1'); } catch { /* ignore */ }
|
||||
setMobileMenuOpen(false);
|
||||
setMobileLanguageView(false);
|
||||
setShowWelcomeLogin(false);
|
||||
setShowWelcomeSignup(false);
|
||||
setAuthDialog(kind);
|
||||
};
|
||||
const requestLogin = () => requestAuth('login');
|
||||
const requestSignup = () => requestAuth('signup');
|
||||
useEffect(() => {
|
||||
if (user || isNativePlatform()) return;
|
||||
if (user || authDialog || isNativePlatform()) return;
|
||||
try {
|
||||
if (sessionStorage.getItem(WELCOME_LOGIN_SNOOZED) === '1') return;
|
||||
} catch { return; }
|
||||
if (typeof window === 'undefined' || !window.matchMedia?.('(min-width: 1024px)').matches) return;
|
||||
const timer = window.setTimeout(() => setShowWelcomeLogin(true), 900);
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [user]);
|
||||
}, [user, authDialog]);
|
||||
const { t, language } = useTranslation();
|
||||
const currentLanguage = LANGUAGE_OPTIONS.find((item) => item.code === language) ?? LANGUAGE_OPTIONS[0];
|
||||
const { currentUser: currentAccount, removeLogin } = useLoggedInAccounts();
|
||||
|
|
@ -786,7 +802,7 @@ export function Layout({ children, noPadding }: LayoutProps) {
|
|||
<Settings className="h-5 w-5" />
|
||||
</Button>
|
||||
</Link>
|
||||
<LoginArea className="max-w-60" />
|
||||
<LoginArea className="max-w-60" onLoginRequest={requestLogin} onSignupRequest={requestSignup} />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
|
@ -827,7 +843,7 @@ export function Layout({ children, noPadding }: LayoutProps) {
|
|||
/>
|
||||
)}
|
||||
<div className={cn(user && 'hidden')}>
|
||||
<LoginArea compact className="flex flex-row gap-1" />
|
||||
<LoginArea compact className="flex flex-row gap-1" onLoginRequest={requestLogin} onSignupRequest={requestSignup} />
|
||||
</div>
|
||||
|
||||
<Sheet
|
||||
|
|
@ -857,7 +873,7 @@ export function Layout({ children, noPadding }: LayoutProps) {
|
|||
<h2 className="text-xl font-black uppercase tracking-wide">
|
||||
{t('settings.selectLanguage')}
|
||||
</h2>
|
||||
<LanguageToggle inline className="mt-3 w-full" variant="ghost" />
|
||||
<LanguageToggle inline className="mt-3 w-full" variant="ghost" onSelect={() => setMobileLanguageView(false)} />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -1102,7 +1118,7 @@ export function Layout({ children, noPadding }: LayoutProps) {
|
|||
<p className="text-sm text-muted-foreground mb-4">
|
||||
{t('chromeUi.loggedOutPrompt')}
|
||||
</p>
|
||||
<LoginArea className="w-full justify-center" />
|
||||
<LoginArea className="w-full justify-center" onLoginRequest={requestLogin} onSignupRequest={requestSignup} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -1256,6 +1272,24 @@ export function Layout({ children, noPadding }: LayoutProps) {
|
|||
/>
|
||||
</Suspense>
|
||||
|
||||
{/* Header/burger auth — owned here so closing the sheet cannot unmount it
|
||||
and it always sits above the sheet + welcome overlay. */}
|
||||
<Suspense fallback={null}>
|
||||
<LazyLoginDialog
|
||||
isOpen={authDialog === 'login'}
|
||||
onClose={() => setAuthDialog(null)}
|
||||
onLogin={() => setAuthDialog(null)}
|
||||
onSignup={() => setAuthDialog('signup')}
|
||||
/>
|
||||
</Suspense>
|
||||
<Suspense fallback={null}>
|
||||
<LazySignupDialog
|
||||
isOpen={authDialog === 'signup'}
|
||||
onClose={() => setAuthDialog(null)}
|
||||
onUseAmber={() => setAuthDialog('login')}
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
{/* Newsletter: floating pill + Monday admin reminder */}
|
||||
<NewsletterOverlay />
|
||||
|
||||
|
|
|
|||
|
|
@ -15,14 +15,31 @@ export interface LoginAreaProps {
|
|||
className?: string;
|
||||
/** Compact mode for mobile headers - smaller buttons without text */
|
||||
compact?: boolean;
|
||||
/**
|
||||
* Open auth in a caller-owned dialog instead of this component's local one.
|
||||
* The mobile burger sheet uses this: its local dialog would portal *behind*
|
||||
* the sheet (z-50 vs z-2100) and leave the menu looking stuck.
|
||||
*/
|
||||
onLoginRequest?: () => void;
|
||||
onSignupRequest?: () => void;
|
||||
}
|
||||
|
||||
export function LoginArea({ className, compact }: LoginAreaProps) {
|
||||
export function LoginArea({ className, compact, onLoginRequest, onSignupRequest }: LoginAreaProps) {
|
||||
const { currentUser } = useLoggedInAccounts();
|
||||
const { t } = useTranslation();
|
||||
const [loginDialogOpen, setLoginDialogOpen] = useState(false);
|
||||
const [signupDialogOpen, setSignupDialogOpen] = useState(false);
|
||||
|
||||
const globalAuth = !!onLoginRequest && !!onSignupRequest;
|
||||
const openLogin = () => {
|
||||
if (onLoginRequest) onLoginRequest();
|
||||
else setLoginDialogOpen(true);
|
||||
};
|
||||
const openSignup = () => {
|
||||
if (onSignupRequest) onSignupRequest();
|
||||
else setSignupDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleLogin = () => {
|
||||
setLoginDialogOpen(false);
|
||||
setSignupDialogOpen(false);
|
||||
|
|
@ -31,12 +48,12 @@ export function LoginArea({ className, compact }: LoginAreaProps) {
|
|||
return (
|
||||
<div className={cn("inline-flex items-center justify-center", className)}>
|
||||
{currentUser ? (
|
||||
<AccountSwitcher onAddAccountClick={() => setLoginDialogOpen(true)} />
|
||||
<AccountSwitcher onAddAccountClick={openLogin} />
|
||||
) : compact ? (
|
||||
// Compact mode for mobile headers - icon only
|
||||
<div className="flex gap-1 justify-center">
|
||||
<Button
|
||||
onClick={() => setSignupDialogOpen(true)}
|
||||
onClick={openSignup}
|
||||
size="icon"
|
||||
className='h-9 w-9 rounded-full bg-[#ccff00] text-black hover:bg-[#ccff00]/90'
|
||||
title={t('settingsUi.loginArea.signUp')}
|
||||
|
|
@ -44,7 +61,7 @@ export function LoginArea({ className, compact }: LoginAreaProps) {
|
|||
<UserPlus className='w-4 h-4' />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setLoginDialogOpen(true)}
|
||||
onClick={openLogin}
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-9 w-9 rounded-full border border-gray-600 hover:border-[#ff0040]"
|
||||
|
|
@ -57,14 +74,14 @@ export function LoginArea({ className, compact }: LoginAreaProps) {
|
|||
// Default full-size buttons
|
||||
<div className="flex gap-3 justify-center w-full">
|
||||
<Button
|
||||
onClick={() => setSignupDialogOpen(true)}
|
||||
onClick={openSignup}
|
||||
className='flex items-center gap-2 px-5 py-2.5 rounded-full bg-[#ccff00] text-black hover:bg-[#ccff00]/90 font-bold transition-all flex-1 sm:flex-none'
|
||||
>
|
||||
<UserPlus className='w-4 h-4' />
|
||||
<span>{t('settingsUi.loginArea.signUp')}</span>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setLoginDialogOpen(true)}
|
||||
onClick={openLogin}
|
||||
variant="outline"
|
||||
className="flex items-center gap-2 px-5 py-2.5 rounded-full font-medium transition-all border-2 border-gray-600 hover:border-[#ff0040] flex-1 sm:flex-none"
|
||||
>
|
||||
|
|
@ -74,24 +91,31 @@ export function LoginArea({ className, compact }: LoginAreaProps) {
|
|||
</div>
|
||||
)}
|
||||
|
||||
<LoginDialog
|
||||
isOpen={loginDialogOpen}
|
||||
onClose={() => setLoginDialogOpen(false)}
|
||||
onLogin={handleLogin}
|
||||
onSignup={() => setSignupDialogOpen(true)}
|
||||
/>
|
||||
{!globalAuth && (
|
||||
<>
|
||||
<LoginDialog
|
||||
isOpen={loginDialogOpen}
|
||||
onClose={() => setLoginDialogOpen(false)}
|
||||
onLogin={handleLogin}
|
||||
onSignup={() => {
|
||||
setLoginDialogOpen(false);
|
||||
setSignupDialogOpen(true);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* One signup entry point. The old "Recovery Phrase vs Secret Key" method
|
||||
chooser was dead code (never opened) and pure jargon for a newcomer —
|
||||
SignupDialog owns the whole flow now. */}
|
||||
<SignupDialog
|
||||
isOpen={signupDialogOpen}
|
||||
onClose={() => setSignupDialogOpen(false)}
|
||||
onUseAmber={() => {
|
||||
setSignupDialogOpen(false);
|
||||
setLoginDialogOpen(true);
|
||||
}}
|
||||
/>
|
||||
{/* One signup entry point. The old "Recovery Phrase vs Secret Key"
|
||||
method chooser was dead code (never opened) and pure jargon for a
|
||||
newcomer — SignupDialog owns the whole flow now. */}
|
||||
<SignupDialog
|
||||
isOpen={signupDialogOpen}
|
||||
onClose={() => setSignupDialogOpen(false)}
|
||||
onUseAmber={() => {
|
||||
setSignupDialogOpen(false);
|
||||
setLoginDialogOpen(true);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -321,7 +321,7 @@ export const LoginDialog: React.FC<LoginDialogProps> = ({ isOpen, onClose, onLog
|
|||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className={cn("flex w-[calc(100%-1rem)] flex-col sm:max-w-md max-h-[calc(100dvh-2rem)] p-0 overflow-hidden rounded-2xl bg-black border-2 border-[#ff0040]/50")}>
|
||||
<DialogContent className={cn("z-[2200] flex w-[calc(100%-1rem)] flex-col sm:max-w-md max-h-[calc(100dvh-2rem)] p-0 overflow-hidden rounded-2xl bg-black border-2 border-[#ff0040]/50")}>
|
||||
<DialogHeader className={cn('px-5 pt-5 pb-3')}>
|
||||
<DialogTitle className="text-center font-black uppercase tracking-wide text-lg">
|
||||
{t('settingsUi.loginDialog.welcomeTitle')}
|
||||
|
|
|
|||
|
|
@ -375,7 +375,7 @@ const SignupDialog: React.FC<SignupDialogProps> = ({ isOpen, onClose, onComplete
|
|||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent
|
||||
className={cn("w-[calc(100%-1rem)] sm:max-w-md max-h-[90vh] max-h-[90dvh] p-0 overflow-hidden rounded-2xl flex flex-col")}
|
||||
className={cn("z-[2200] w-[calc(100%-1rem)] sm:max-w-md max-h-[90vh] max-h-[90dvh] p-0 overflow-hidden rounded-2xl flex flex-col")}
|
||||
>
|
||||
<DialogHeader className={cn('px-4 sm:px-6 pt-4 sm:pt-6 pb-1 relative flex-shrink-0')}>
|
||||
<DialogTitle className={cn('font-semibold text-center text-base sm:text-lg')}>
|
||||
|
|
|
|||
Loading…
Reference in a new issue