diff --git a/src/beacon/app/api/auth/[...nextauth]/route.ts b/src/beacon/app/api/auth/[...nextauth]/route.ts index 0a4c217bc5..1b06e23c44 100644 --- a/src/beacon/app/api/auth/[...nextauth]/route.ts +++ b/src/beacon/app/api/auth/[...nextauth]/route.ts @@ -1,6 +1,8 @@ import NextAuth from 'next-auth'; import { authOptions } from '@/lib/auth'; -const handler = NextAuth(authOptions); +const handler = (req: any, res: any) => { + return NextAuth(req, res, authOptions); +}; export { handler as GET, handler as POST }; diff --git a/src/beacon/components/device-model-3d-inner.tsx b/src/beacon/components/device-model-3d-inner.tsx index 29c1b0016e..fd30f2ba52 100644 --- a/src/beacon/components/device-model-3d-inner.tsx +++ b/src/beacon/components/device-model-3d-inner.tsx @@ -149,7 +149,7 @@ export default function DeviceModel3DInner({ onModelLoaded, onModelFailed }: Rea - + diff --git a/src/beacon/components/device-model-3d.tsx b/src/beacon/components/device-model-3d.tsx index e07d26322d..ee9f9fc43a 100644 --- a/src/beacon/components/device-model-3d.tsx +++ b/src/beacon/components/device-model-3d.tsx @@ -168,7 +168,7 @@ export default function DeviceModel3D({ onModelLoaded, onModelFailed }: Readonly {/* 3D Model */} - + {/* Interactive controls */} diff --git a/src/beacon/lib/auth.ts b/src/beacon/lib/auth.ts index 978254b02e..e9cce31d75 100644 --- a/src/beacon/lib/auth.ts +++ b/src/beacon/lib/auth.ts @@ -5,7 +5,34 @@ import { normalizeOAuthAccessToken, fetchEnhancedUserProfile, } from './oauth-session'; -import { buildPlatformApiUrl } from './config'; +import config, { buildPlatformApiUrl } from './config'; + +// Fallback defaults and environment-specific self-correction for NEXTAUTH_URL +const normalizeNextAuthUrl = () => { + const currentEnv = config.environment; + const rawUrl = process.env.NEXTAUTH_URL; + + // Check if NEXTAUTH_URL is missing or incorrectly set to localhost in staging/production + const isLocalhostUrl = !rawUrl || rawUrl.includes('localhost') || rawUrl.includes('127.0.0.1'); + + if (currentEnv === 'staging') { + if (isLocalhostUrl) { + process.env.NEXTAUTH_URL = 'https://staging-beacon.airqo.net'; + } + } else if (currentEnv === 'production') { + if (isLocalhostUrl) { + process.env.NEXTAUTH_URL = 'https://beacon.airqo.net'; + } + } else { + // Development / Localhost + if (!rawUrl) { + process.env.NEXTAUTH_URL = 'http://localhost:3000'; + } + } +}; + +normalizeNextAuthUrl(); + const isProduction = process.env.NODE_ENV === 'production'; @@ -53,7 +80,7 @@ const cookieOptions = { }; export const authOptions: NextAuthOptions = { - secret: process.env.NEXTAUTH_SECRET, + secret: process.env.NEXTAUTH_SECRET || process.env.AUTH_SECRET, useSecureCookies: isProduction, providers: [ CredentialsProvider({ diff --git a/src/beacon/lib/config.js b/src/beacon/lib/config.js index 8e17f2cd7a..58ae3217f1 100755 --- a/src/beacon/lib/config.js +++ b/src/beacon/lib/config.js @@ -14,6 +14,48 @@ const isLocalhost = !isServer && const isDevelopment = process.env.NODE_ENV === 'development'; const isProduction = process.env.NODE_ENV === 'production'; +/** + * Get the current environment context dynamically + */ +const getEnvironment = () => { + if (!isServer) { + const hostname = globalThis.location.hostname.toLowerCase(); + if ( + hostname === 'localhost' || + hostname === '127.0.0.1' || + hostname.startsWith('192.168.') || + hostname.startsWith('10.') || + hostname.startsWith('172.') + ) { + return 'development'; + } + if (hostname.includes('stage') || hostname.includes('staging') || hostname.includes('beacon-stage')) { + return 'staging'; + } + return 'production'; + } + + // Server-side detection + const nextAuthUrl = process.env.NEXTAUTH_URL || ''; + if (nextAuthUrl.includes('stage') || nextAuthUrl.includes('staging') || nextAuthUrl.includes('beacon-stage')) { + return 'staging'; + } + if (nextAuthUrl.includes('localhost') || nextAuthUrl.includes('127.0.0.1')) { + const serviceName = (process.env.SERVICE_NAME || '').toLowerCase(); + const podHostName = (process.env.HOSTNAME || '').toLowerCase(); + if (serviceName.includes('stage') || podHostName.includes('stage')) { + return 'staging'; + } + return 'development'; + } + + if (process.env.NODE_ENV === 'development') { + return 'development'; + } + + return 'production'; +}; + /** * Strip trailing slashes from URLs */ @@ -63,91 +105,82 @@ export const enforceHttpsForRemote = (url) => { /** * Get the appropriate API URL based on environment - * Priority: - * 1. NEXT_PUBLIC_LOCAL_API_URL (for local development with local backend) - * 2. NEXT_PUBLIC_AIRQO_STAGING_API_BASE_URL (for staging) - * 3. NEXT_PUBLIC_AIRQO_API_BASE_URL (for production) - * 4. AIRQO_STAGING_API_BASE_URL (server-side staging) - * 5. AIRQO_API_BASE_URL (server-side production) */ const getBeaconApiUrl = () => { - // For local development, use local API - if (isLocalhost || isDevelopment) { + const env = getEnvironment(); + + if (env === 'development') { const localUrl = process.env.NEXT_PUBLIC_LOCAL_API_URL || process.env.BEACON_API_URL; if (localUrl) return enforceHttpsForRemote(localUrl); + return 'http://localhost:8000'; } - - // For client-side, prefer NEXT_PUBLIC_ variables - if (!isServer) { - const publicBeaconUrl = process.env.NEXT_PUBLIC_BEACON_API_URL; - const stagingUrl = process.env.NEXT_PUBLIC_AIRQO_STAGING_API_BASE_URL; - const prodUrl = process.env.NEXT_PUBLIC_AIRQO_API_BASE_URL; - if (publicBeaconUrl) return enforceHttpsForRemote(publicBeaconUrl); + + if (env === 'staging') { + const stagingUrl = process.env.NEXT_PUBLIC_AIRQO_STAGING_API_BASE_URL || + process.env.AIRQO_STAGING_API_BASE_URL; if (stagingUrl) return enforceHttpsForRemote(stagingUrl); - if (prodUrl) return enforceHttpsForRemote(prodUrl); + return 'https://staging-platform.airqo.net'; } - - // For server-side, can use non-public variables - const beaconUrl = process.env.BEACON_API_URL; - const serverStagingUrl = process.env.AIRQO_STAGING_API_BASE_URL; - const serverProdUrl = process.env.AIRQO_API_BASE_URL; - if (beaconUrl) return enforceHttpsForRemote(beaconUrl); - if (serverStagingUrl) return enforceHttpsForRemote(serverStagingUrl); - if (serverProdUrl) return enforceHttpsForRemote(serverProdUrl); - - // Default fallback (should be avoided in production) - console.warn('No API URL configured, using default localhost:8000'); - return 'http://localhost:8000'; + + // Production + const prodUrl = process.env.NEXT_PUBLIC_AIRQO_API_BASE_URL || + process.env.AIRQO_API_BASE_URL; + if (prodUrl) return enforceHttpsForRemote(prodUrl); + + return 'https://platform.airqo.net'; }; /** * Get the AirQo Platform API URL (for auth, users, etc.) */ const getAirQoPlatformApiUrl = () => { - // Prefer staging for development - if (isDevelopment) { + const env = getEnvironment(); + + if (env === 'development' || env === 'staging') { const stagingUrl = process.env.NEXT_PUBLIC_AIRQO_STAGING_API_BASE_URL || process.env.AIRQO_STAGING_API_BASE_URL; - if (stagingUrl) return stripTrailingSlash(stagingUrl); + if (stagingUrl) return enforceHttpsForRemote(stagingUrl); + return 'https://staging-platform.airqo.net'; } // Production const prodUrl = process.env.NEXT_PUBLIC_AIRQO_API_BASE_URL || process.env.AIRQO_API_BASE_URL; - if (prodUrl) return stripTrailingSlash(prodUrl); - - // Fallback to staging - const stagingUrl = process.env.NEXT_PUBLIC_AIRQO_STAGING_API_BASE_URL || - process.env.AIRQO_STAGING_API_BASE_URL; - if (stagingUrl) return stripTrailingSlash(stagingUrl); + if (prodUrl) return enforceHttpsForRemote(prodUrl); - return 'https://staging-platform.airqo.net'; + return 'https://platform.airqo.net'; }; /** - * API Configuration + * API Configuration with dynamic properties */ export const config = { // Environment flags - isServer, - isLocalhost, - isDevelopment, - isProduction, + get isServer() { return typeof window === 'undefined'; }, + get isLocalhost() { + return typeof window !== 'undefined' && + (window.location.hostname === 'localhost' || + window.location.hostname === '127.0.0.1' || + window.location.hostname.startsWith('192.168.')); + }, + get isDevelopment() { return process.env.NODE_ENV === 'development'; }, + get isProduction() { return process.env.NODE_ENV === 'production'; }, + get environment() { return getEnvironment(); }, // API URLs - apiUrl: getBeaconApiUrl(), - beaconApiUrl: getBeaconApiUrl(), - airqoPlatformUrl: getAirQoPlatformApiUrl(), + get apiUrl() { return getBeaconApiUrl(); }, + get beaconApiUrl() { return getBeaconApiUrl(); }, + get airqoPlatformUrl() { return getAirQoPlatformApiUrl(); }, // API version apiVersion: process.env.AIRQO_API_VERSION || 'v2', // API prefixes apiPrefix: '/api/v1', - beaconApiPrefix: isLocalhost ? '/api/v1' : '/api/v1/beacon', + get beaconApiPrefix() { return this.isLocalhost ? '/api/v1' : '/api/v1/beacon'; }, // Auth settings - requiresAuth: !isLocalhost && isProduction, + get requiresAuth() { return !this.isLocalhost && this.isProduction; }, // Timeouts defaultTimeout: 30000, diff --git a/src/beacon/public/hdri/potsdamer_platz_1k.hdr b/src/beacon/public/hdri/potsdamer_platz_1k.hdr new file mode 100644 index 0000000000..e3121c15c2 Binary files /dev/null and b/src/beacon/public/hdri/potsdamer_platz_1k.hdr differ