Skip to content
2 changes: 1 addition & 1 deletion k8s/beacon/k8s-aks/airqo-beacon/values-stage.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
replicaCount: 2
image:
repository: airqoacr.azurecr.io/airqo-stage-beacon
tag: stage-28ed4fb6-1783946212
tag: stage-5417f56a-1783974410
pullPolicy: IfNotPresent
imagePullSecrets: []
nameOverride: ''
Expand Down
25 changes: 24 additions & 1 deletion src/beacon/components/providers/auth-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import authService from '@/services/api-service';
function AuthEffect({ children }: { children: ReactNode }) {
const { data: session, status } = useSession();
const [handoffProcessed, setHandoffProcessed] = useState(false);
const [isHandlingHandoff, setIsHandlingHandoff] = useState(() => {
if (typeof window !== 'undefined') {
return window.location.hash.includes('token=');
}
return false;
});

useEffect(() => {
if (status === 'authenticated' && session) {
Expand All @@ -29,11 +35,15 @@ function AuthEffect({ children }: { children: ReactNode }) {
role: (rawUser as any).privilege || 'user',
});
}

if (isHandlingHandoff) {
setIsHandlingHandoff(false);
}
} else if (status === 'unauthenticated') {
authService.setToken(null);
authService.setUserData(null);
}
}, [session, status]);
}, [session, status, isHandlingHandoff]);

useEffect(() => {
if (handoffProcessed) return;
Expand All @@ -50,9 +60,22 @@ function AuthEffect({ children }: { children: ReactNode }) {
redirect: true,
callbackUrl: callbackUrl,
});
} else {
setIsHandlingHandoff(false);
}
}, [handoffProcessed]);

if (isHandlingHandoff || (status === 'unauthenticated' && typeof window !== 'undefined' && window.location.hash.includes('token='))) {
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-50">
<div className="w-16 h-16 border-4 border-blue-200 rounded-full mb-4 relative">
<div className="absolute top-0 left-0 w-16 h-16 border-4 border-blue-600 rounded-full border-t-transparent animate-spin"></div>
</div>
<div className="text-gray-900 text-xl font-semibold mb-2">Completing sign in...</div>
</div>
);
}

return <>{children}</>;
}

Expand Down
6 changes: 4 additions & 2 deletions src/beacon/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ export default withAuth(
typeof token?.airqoExp === 'number' && Date.now() / 1000 > token.airqoExp;
const isAuthenticated = !!token && !isAirqoTokenExpired;
const isPublicRoute = PUBLIC_ROUTES.some(route => pathname.startsWith(route))
const isOAuthCallback = request.nextUrl.searchParams.has('success') || request.nextUrl.searchParams.has('token')

// Redirect unauthenticated users to login
if (!isAuthenticated && !isPublicRoute) {
// Redirect unauthenticated users to login, unless it's an OAuth callback
// (We need to let the client handle OAuth callbacks to preserve the URL hash fragment)
if (!isAuthenticated && !isPublicRoute && !isOAuthCallback) {
const loginUrl = new URL('/login', request.url)
loginUrl.searchParams.set('from', pathname)
return NextResponse.redirect(loginUrl)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import SelfiesScreen from '@/features/clean-air-forum-2026/screens/SelfiesScreen';
import { generateMetadata } from '@/lib/metadata';

export const metadata = generateMetadata({
title: 'Selfies | Faces of Clean Air',
description:
'View selfies shared by attendees of the Africa Clean Air Forum showcasing air quality readings from their locations.',
keywords:
'Faces of Clean Air selfies, Africa Clean Air Forum photos, air quality advocates, PM2.5 readings Africa, clean air community',
url: '/africa-clean-air-forum-2026/selfies',
});

export default function SelfiesRoute() {
return <SelfiesScreen />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ export default function QrCodeButton({ src = QR_IMAGE_SRC }: { src?: string }) {
aria-label="Open QR code in full view"
>
<div className="flex flex-col items-center gap-1">
<div className="overflow-hidden rounded-lg border-2 border-white/30 bg-white/10 p-1 shadow-lg backdrop-blur-sm transition-transform hover:scale-105 sm:border-white/40 sm:p-1.5 lg:p-2">
<div className="overflow-hidden rounded-lg border border-white/25 bg-white/10 p-1 shadow-lg backdrop-blur-sm transition-transform hover:scale-105 sm:border-white/30 sm:p-1.5">
<Image
src={src}
alt="Clean Air Forum QR Code"
width={60}
height={60}
className="h-[50px] w-[50px] object-contain sm:h-[65px] sm:w-[65px] lg:h-[80px] lg:w-[80px]"
className="h-[44px] w-[44px] object-contain sm:h-[52px] sm:w-[52px] lg:h-[60px] lg:w-[60px]"
/>
</div>
<span className="text-[8px] font-medium text-white/80 sm:text-[10px] lg:text-xs">
<span className="text-[7px] font-medium text-white/70 sm:text-[9px]">
Scan to participate
</span>
</div>
Expand Down
Loading
Loading