diff --git a/apps/mobile/app.config.js b/apps/mobile/app.config.js new file mode 100644 index 00000000000..6beff4708e2 --- /dev/null +++ b/apps/mobile/app.config.js @@ -0,0 +1,51 @@ +const path = require("node:path"); +require("dotenv").config({ + path: path.resolve(__dirname, "../../.env"), + override: true, +}); + +module.exports = { + expo: { + name: "Superset", + slug: "superset", + version: "1.0.0", + orientation: "portrait", + icon: "./assets/icon.png", + userInterfaceStyle: "light", + newArchEnabled: true, + scheme: "superset", + splash: { + image: "./assets/splash-icon.png", + resizeMode: "contain", + backgroundColor: "#ffffff", + }, + ios: { + supportsTablet: true, + bundleIdentifier: "sh.superset.mobile", + infoPlist: { + ITSAppUsesNonExemptEncryption: false, + }, + }, + android: { + adaptiveIcon: { + foregroundImage: "./assets/adaptive-icon.png", + backgroundColor: "#ffffff", + }, + package: "sh.superset.mobile", + edgeToEdgeEnabled: true, + predictiveBackGestureEnabled: false, + }, + web: { + favicon: "./assets/favicon.png", + bundler: "metro", + }, + plugins: ["expo-router"], + extra: { + router: {}, + eas: { + projectId: "fa9332a8-896a-4d2a-be5b-d82469b46e5d", + }, + }, + owner: "supserset-sh", + }, +}; diff --git a/apps/mobile/app.json b/apps/mobile/app.json deleted file mode 100644 index f9a14b4cc27..00000000000 --- a/apps/mobile/app.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "expo": { - "name": "Superset", - "slug": "superset", - "version": "1.0.0", - "orientation": "portrait", - "icon": "./assets/icon.png", - "userInterfaceStyle": "light", - "newArchEnabled": true, - "scheme": "superset", - "splash": { - "image": "./assets/splash-icon.png", - "resizeMode": "contain", - "backgroundColor": "#ffffff" - }, - "ios": { - "supportsTablet": true, - "bundleIdentifier": "sh.superset.mobile", - "infoPlist": { - "ITSAppUsesNonExemptEncryption": false - } - }, - "android": { - "adaptiveIcon": { - "foregroundImage": "./assets/adaptive-icon.png", - "backgroundColor": "#ffffff" - }, - "package": "sh.superset.mobile", - "edgeToEdgeEnabled": true, - "predictiveBackGestureEnabled": false - }, - "web": { - "favicon": "./assets/favicon.png", - "bundler": "metro" - }, - "plugins": ["expo-router"], - "extra": { - "router": {}, - "eas": { - "projectId": "fa9332a8-896a-4d2a-be5b-d82469b46e5d" - } - }, - "owner": "supserset-sh" - } -} diff --git a/apps/mobile/apps/mobile/app.config.ts b/apps/mobile/apps/mobile/app.config.ts new file mode 100644 index 00000000000..17a920b9f60 --- /dev/null +++ b/apps/mobile/apps/mobile/app.config.ts @@ -0,0 +1,54 @@ +import path from "node:path"; +import { config } from "dotenv"; +import type { ConfigContext, ExpoConfig } from "expo/config"; + +// Load .env file +config({ + path: path.resolve(__dirname, "../../.env"), + override: true, +}); + +export default ({ config }: ConfigContext): ExpoConfig => ({ + ...config, + name: "Superset", + slug: "superset", + version: "1.0.0", + orientation: "portrait", + icon: "./assets/icon.png", + userInterfaceStyle: "light", + newArchEnabled: true, + scheme: "superset", + splash: { + image: "./assets/splash-icon.png", + resizeMode: "contain", + backgroundColor: "#ffffff", + }, + ios: { + supportsTablet: true, + bundleIdentifier: "sh.superset.mobile", + infoPlist: { + ITSAppUsesNonExemptEncryption: false, + }, + }, + android: { + adaptiveIcon: { + foregroundImage: "./assets/adaptive-icon.png", + backgroundColor: "#ffffff", + }, + package: "sh.superset.mobile", + edgeToEdgeEnabled: true, + predictiveBackGestureEnabled: false, + }, + web: { + favicon: "./assets/favicon.png", + bundler: "metro", + }, + plugins: ["expo-router"], + extra: { + router: {}, + eas: { + projectId: "fa9332a8-896a-4d2a-be5b-d82469b46e5d", + }, + }, + owner: "supserset-sh", +}); diff --git a/apps/mobile/babel.config.js b/apps/mobile/babel.config.js index 7e9fca3c4bb..1f69ec2de8f 100644 --- a/apps/mobile/babel.config.js +++ b/apps/mobile/babel.config.js @@ -2,5 +2,6 @@ module.exports = (api) => { api.cache(true); return { presets: ["babel-preset-expo"], + plugins: ["react-native-worklets/plugin"], }; }; diff --git a/apps/mobile/lib/auth/client.ts b/apps/mobile/lib/auth/client.ts index 15b05e51724..ab88b5d5cca 100644 --- a/apps/mobile/lib/auth/client.ts +++ b/apps/mobile/lib/auth/client.ts @@ -1,13 +1,10 @@ import { expoClient } from "@better-auth/expo/client"; import { createAuthClient } from "better-auth/react"; import * as SecureStore from "expo-secure-store"; - -import { getBaseUrl } from "../base-url"; - -const BASE_URL = getBaseUrl(); +import { env } from "../env"; export const authClient = createAuthClient({ - baseURL: BASE_URL, + baseURL: env.EXPO_PUBLIC_API_URL, plugins: [ expoClient({ scheme: "superset", diff --git a/apps/mobile/lib/base-url.ts b/apps/mobile/lib/base-url.ts deleted file mode 100644 index 59ec5c88064..00000000000 --- a/apps/mobile/lib/base-url.ts +++ /dev/null @@ -1,48 +0,0 @@ -import Constants from "expo-constants"; - -import { env } from "./env"; - -/** - * Get the base API URL for the mobile app. - * - * In development: - * - If EXPO_PUBLIC_API_URL contains localhost/127.0.0.1, automatically replaces it - * with the dev server's IP address from Expo Constants - * - Falls back to EXPO_PUBLIC_API_URL if no dev server IP is available - * - * In production: - * - Uses EXPO_PUBLIC_API_URL as-is - * - Throws an error if it contains localhost (React Native can't use localhost) - */ -export function getBaseUrl(): string { - const apiUrl = env.EXPO_PUBLIC_API_URL; - const isDev = env.NODE_ENV === "development"; - - const isLocalhost = - apiUrl.includes("localhost") || apiUrl.includes("127.0.0.1"); - - if (!isDev && isLocalhost) { - throw new Error( - "EXPO_PUBLIC_API_URL cannot use localhost or 127.0.0.1 in production. Use your production API URL instead.", - ); - } - - if (isDev && isLocalhost) { - const devServerIp = Constants.expoConfig?.hostUri?.split(":")[0]; - - if (devServerIp) { - const urlObj = new URL(apiUrl); - const replacedUrl = apiUrl.replace(urlObj.hostname, devServerIp); - console.log( - `[base-url] Auto-detected dev server IP: ${devServerIp}, using ${replacedUrl}`, - ); - return replacedUrl; - } - - console.warn( - "[base-url] Could not auto-detect dev server IP, using localhost URL as-is. This may not work on physical devices.", - ); - } - - return apiUrl; -} diff --git a/apps/mobile/lib/env.ts b/apps/mobile/lib/env.ts index 21854b7c3ba..f30047c4e3a 100644 --- a/apps/mobile/lib/env.ts +++ b/apps/mobile/lib/env.ts @@ -4,11 +4,8 @@ const envSchema = z.object({ NODE_ENV: z .enum(["development", "production", "test"]) .default("development"), - EXPO_PUBLIC_API_URL: z - .string() - .url() - .transform((url) => url.replace(/\/$/, "")), - EXPO_PUBLIC_WEB_URL: z.string().url().optional(), + EXPO_PUBLIC_API_URL: z.url(), + EXPO_PUBLIC_WEB_URL: z.url().optional(), EXPO_PUBLIC_DEEP_LINK_SCHEME: z.string().default("superset"), EXPO_PUBLIC_DEEP_LINK_DOMAIN: z.string().optional(), }); diff --git a/apps/mobile/package.json b/apps/mobile/package.json index fefb6288da5..60e3f877f36 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -21,6 +21,7 @@ "better-auth": "1.4.16", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "dotenv": "^17.2.3", "expo": "~54.0.31", "expo-constants": "^18.0.13", "expo-dev-client": "~6.0.20", @@ -29,12 +30,13 @@ "expo-router": "^6.0.21", "expo-secure-store": "~15.0.8", "expo-status-bar": "^3.0.9", + "expo-system-ui": "~6.0.9", "expo-web-browser": "~15.0.10", "lucide-react-native": "^0.562.0", "react": "19.1.0", "react-native": "0.81.5", "react-native-reanimated": "~4.1.1", - "react-native-safe-area-context": "^5.6.2", + "react-native-safe-area-context": "~5.6.0", "react-native-screens": "^4.16.0", "react-native-svg": "^15.12.1", "react-native-worklets": "~0.5.0", diff --git a/apps/mobile/uniwind-types.d.ts b/apps/mobile/uniwind-types.d.ts index e3dcb855b07..cc099419a9b 100644 --- a/apps/mobile/uniwind-types.d.ts +++ b/apps/mobile/uniwind-types.d.ts @@ -1,10 +1,10 @@ // NOTE: This file is generated by uniwind and it should not be edited manually. /// -declare module "uniwind" { - export interface UniwindConfig { - themes: readonly ["light", "dark"]; - } +declare module 'uniwind' { + export interface UniwindConfig { + themes: readonly ['light', 'dark'] + } } -export {}; +export {} diff --git a/biome.jsonc b/biome.jsonc index 5c140156989..6af2f604a14 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -6,7 +6,13 @@ "useIgnoreFile": true }, "files": { - "includes": ["**", "!**/drizzle", "!**/*.template.js", "!**/*.template.sh"] + "includes": [ + "**", + "!**/drizzle", + "!**/*.template.js", + "!**/*.template.sh", + "!apps/mobile/uniwind-types.d.ts" + ] }, "formatter": { "formatWithErrors": true diff --git a/bun.lock b/bun.lock index e4421115a7e..3c69924e80e 100644 --- a/bun.lock +++ b/bun.lock @@ -345,6 +345,7 @@ "better-auth": "1.4.16", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "dotenv": "^17.2.3", "expo": "~54.0.31", "expo-constants": "^18.0.13", "expo-dev-client": "~6.0.20", @@ -353,12 +354,13 @@ "expo-router": "^6.0.21", "expo-secure-store": "~15.0.8", "expo-status-bar": "^3.0.9", + "expo-system-ui": "~6.0.9", "expo-web-browser": "~15.0.10", "lucide-react-native": "^0.562.0", "react": "19.1.0", "react-native": "0.81.5", "react-native-reanimated": "~4.1.1", - "react-native-safe-area-context": "^5.6.2", + "react-native-safe-area-context": "~5.6.0", "react-native-screens": "^4.16.0", "react-native-svg": "^15.12.1", "react-native-worklets": "~0.5.0", @@ -2803,6 +2805,8 @@ "expo-status-bar": ["expo-status-bar@3.0.9", "", { "dependencies": { "react-native-is-edge-to-edge": "^1.2.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-xyYyVg6V1/SSOZWh4Ni3U129XHCnFHBTcUo0dhWtFDrZbNp/duw5AGsQfb2sVeU0gxWHXSY1+5F0jnKYC7WuOw=="], + "expo-system-ui": ["expo-system-ui@6.0.9", "", { "dependencies": { "@react-native/normalize-colors": "0.81.5", "debug": "^4.3.2" }, "peerDependencies": { "expo": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-eQTYGzw1V4RYiYHL9xDLYID3Wsec2aZS+ypEssmF64D38aDrqbDgz1a2MSlHLQp2jHXSs3FvojhZ9FVela1Zcg=="], + "expo-updates-interface": ["expo-updates-interface@2.0.0", "", { "peerDependencies": { "expo": "*" } }, "sha512-pTzAIufEZdVPKql6iMi5ylVSPqV1qbEopz9G6TSECQmnNde2nwq42PxdFBaUEd8IZJ/fdJLQnOT3m6+XJ5s7jg=="], "expo-web-browser": ["expo-web-browser@15.0.10", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-fvDhW4bhmXAeWFNFiInmsGCK83PAqAcQaFyp/3pE/jbdKmFKoRCWr46uZGIfN4msLK/OODhaQ/+US7GSJNDHJg=="],