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