diff --git a/src/components/LanguageToggle.tsx b/src/components/LanguageToggle.tsx index 86088b5..1de656c 100644 --- a/src/components/LanguageToggle.tsx +++ b/src/components/LanguageToggle.tsx @@ -119,7 +119,7 @@ export function LanguageToggle({ compact = false, inline = false, className, var } return ( - + { if (!open) setQuery(''); }}> - + @@ -843,7 +845,7 @@ export function Layout({ children, noPadding }: LayoutProps) { /> )}
- +
{t('chromeUi.loggedOutPrompt')}

- + )} @@ -1296,5 +1298,6 @@ export function Layout({ children, noPadding }: LayoutProps) { {/* Mirrors in-app activity to OS notifications — inert until opted in. */} + ); } diff --git a/src/components/TripItineraryCard.tsx b/src/components/TripItineraryCard.tsx index 864f650..4a01c75 100644 --- a/src/components/TripItineraryCard.tsx +++ b/src/components/TripItineraryCard.tsx @@ -17,6 +17,7 @@ import { useCreateSpotCollection } from '@/hooks/useSpotCollections'; import { useToast } from '@/hooks/useToast'; import { useTranslation } from '@/contexts/language'; import { cn } from '@/lib/utils'; +import { sanitizeUrl } from '@/lib/sanitizeUrl'; const KIND_ICON = { spot: MapPin, @@ -117,6 +118,7 @@ export function TripItineraryCard({ itinerary }: { itinerary: Itinerary }) {
{day.stops.map((stop, i) => { const Icon = KIND_ICON[stop.kind]; + const stopUrl = sanitizeUrl(stop.url); const inner = (
@@ -131,10 +133,10 @@ export function TripItineraryCard({ itinerary }: { itinerary: Itinerary }) {
); - return stop.url ? ( + return stopUrl ? ( {t('routeUi.localCrews')}
- {itinerary.crews.map((c, i) => ( - - 👥 {c.name} - - ))} + {itinerary.crews.map((c, i) => { + const crewUrl = sanitizeUrl(c.url); + const cls = 'text-[9px] px-1.5 py-0.5 rounded bg-white/5 text-gray-200 hover:bg-[#ccff00]/10 hover:text-[#ccff00] transition-colors'; + return crewUrl ? ( + + 👥 {c.name} + + ) : ( + 👥 {c.name} + ); + })}
)} diff --git a/src/components/auth/LoginArea.tsx b/src/components/auth/LoginArea.tsx index 87735d1..13ec763 100644 --- a/src/components/auth/LoginArea.tsx +++ b/src/components/auth/LoginArea.tsx @@ -10,6 +10,7 @@ import { useLoggedInAccounts } from '@/hooks/useLoggedInAccounts'; import { AccountSwitcher } from './AccountSwitcher'; import { cn } from '@/lib/utils'; import { useTranslation } from '@/contexts/language'; +import { useAuthDialog } from '@/contexts/authDialog'; export interface LoginAreaProps { className?: string; @@ -27,16 +28,19 @@ export interface LoginAreaProps { export function LoginArea({ className, compact, onLoginRequest, onSignupRequest }: LoginAreaProps) { const { currentUser } = useLoggedInAccounts(); const { t } = useTranslation(); + const authDialog = useAuthDialog(); const [loginDialogOpen, setLoginDialogOpen] = useState(false); const [signupDialogOpen, setSignupDialogOpen] = useState(false); - const globalAuth = !!onLoginRequest && !!onSignupRequest; + const globalAuth = !!authDialog || (!!onLoginRequest && !!onSignupRequest); const openLogin = () => { - if (onLoginRequest) onLoginRequest(); + if (authDialog) authDialog.openLogin(); + else if (onLoginRequest) onLoginRequest(); else setLoginDialogOpen(true); }; const openSignup = () => { - if (onSignupRequest) onSignupRequest(); + if (authDialog) authDialog.openSignup(); + else if (onSignupRequest) onSignupRequest(); else setSignupDialogOpen(true); }; @@ -57,6 +61,7 @@ export function LoginArea({ className, compact, onLoginRequest, onSignupRequest size="icon" className='h-9 w-9 rounded-full bg-[#ccff00] text-black hover:bg-[#ccff00]/90' title={t('settingsUi.loginArea.signUp')} + aria-label={t('settingsUi.loginArea.signUp')} > @@ -66,6 +71,7 @@ export function LoginArea({ className, compact, onLoginRequest, onSignupRequest size="icon" className="h-9 w-9 rounded-full border border-gray-600 hover:border-[#ff0040]" title={t('settingsUi.loginArea.logIn')} + aria-label={t('settingsUi.loginArea.logIn')} > diff --git a/src/components/auth/LoginDialog.tsx b/src/components/auth/LoginDialog.tsx index 2caa40e..2ab66b7 100644 --- a/src/components/auth/LoginDialog.tsx +++ b/src/components/auth/LoginDialog.tsx @@ -321,7 +321,7 @@ export const LoginDialog: React.FC = ({ isOpen, onClose, onLog return ( - + {t('settingsUi.loginDialog.welcomeTitle')} diff --git a/src/components/auth/SignupDialog.tsx b/src/components/auth/SignupDialog.tsx index da8644a..db581df 100644 --- a/src/components/auth/SignupDialog.tsx +++ b/src/components/auth/SignupDialog.tsx @@ -375,6 +375,7 @@ const SignupDialog: React.FC = ({ isOpen, onClose, onComplete return ( diff --git a/src/contexts/authDialog.ts b/src/contexts/authDialog.ts new file mode 100644 index 0000000..93d58bc --- /dev/null +++ b/src/contexts/authDialog.ts @@ -0,0 +1,18 @@ +import { createContext, useContext } from 'react'; + +export interface AuthDialogActions { + openLogin: () => void; + openSignup: () => void; +} + +/** + * App-wide auth dialog controls. `Layout` owns the dialogs so they survive the + * unmount of whichever `LoginArea` (or parent card/dialog) opened them — crucial + * because signup logs the user in immediately and many cards conditionally + * render their login prompt only while logged out. + */ +export const AuthDialogContext = createContext(null); + +export function useAuthDialog(): AuthDialogActions | null { + return useContext(AuthDialogContext); +}