diff --git a/mobile/app/(auth)/_layout.tsx b/mobile/app/(auth)/_layout.tsx
index e69de29..87e438f 100644
--- a/mobile/app/(auth)/_layout.tsx
+++ b/mobile/app/(auth)/_layout.tsx
@@ -0,0 +1,23 @@
+import { Stack } from "expo-router";
+
+export default function AuthLayout() {
+ return (
+
+
+
+
+
+
+ );
+}
diff --git a/mobile/app/(auth)/landing.tsx b/mobile/app/(auth)/landing.tsx
index e69de29..32dfb13 100644
--- a/mobile/app/(auth)/landing.tsx
+++ b/mobile/app/(auth)/landing.tsx
@@ -0,0 +1,45 @@
+import React from "react";
+import { View, Text, TouchableOpacity, SafeAreaView } from "react-native";
+import { useRouter } from "expo-router";
+
+export default function LandingScreen() {
+ const router = useRouter();
+
+ return (
+
+
+ {/* Simple Branding Icon */}
+
+ 🌱
+
+
+
+ TerraDetect
+
+
+ Real-time soil monitoring and AI-powered crop recommendations.
+
+
+
+
+ router.push("/(auth)/login")}
+ activeOpacity={0.8}
+ className="bg-green-600 p-5 rounded-2xl items-center shadow-sm"
+ >
+ Sign In
+
+
+ router.push("/(auth)/register")}
+ activeOpacity={0.7}
+ className="bg-white border-2 border-green-600 p-5 rounded-2xl items-center"
+ >
+
+ Create Account
+
+
+
+
+ );
+}
diff --git a/mobile/app/(auth)/login.tsx b/mobile/app/(auth)/login.tsx
index e69de29..4ad84fc 100644
--- a/mobile/app/(auth)/login.tsx
+++ b/mobile/app/(auth)/login.tsx
@@ -0,0 +1,70 @@
+import React, { useState } from "react";
+import { View, Text, TextInput, TouchableOpacity, Alert } from "react-native";
+import { useRouter } from "expo-router";
+import { api } from "../../lib/api";
+import { useAuthStore } from "../../store/authStore";
+
+export default function LoginScreen() {
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [loading, setLoading] = useState(false);
+ const router = useRouter();
+ const loginToStore = useAuthStore((state) => state.login);
+
+ const handleLogin = async () => {
+ if (!email || !password)
+ return Alert.alert("Error", "Please fill in all fields");
+
+ setLoading(true);
+ try {
+ const data = await api.login({ email, password });
+
+ // Your Go backend returns { access_token, refresh_token, user: { username, device_id } }
+ await loginToStore(
+ { accessToken: data.access_token, refreshToken: data.refresh_token },
+ { username: data.user.username, deviceId: data.user.device_id },
+ );
+
+ router.replace("/(app)/dashboard");
+ } catch (err: any) {
+ Alert.alert("Login Failed", err.message);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+ TerraDetect
+
+
+
+
+
+
+
+
+ {loading ? "Authenticating..." : "Sign In"}
+
+
+
+
+ );
+}
diff --git a/mobile/app/(auth)/register-success.tsx b/mobile/app/(auth)/register-success.tsx
index e69de29..eda5444 100644
--- a/mobile/app/(auth)/register-success.tsx
+++ b/mobile/app/(auth)/register-success.tsx
@@ -0,0 +1,30 @@
+import React from "react";
+import { View, Text, TouchableOpacity } from "react-native";
+import { useRouter } from "expo-router";
+
+export default function RegisterSuccessScreen() {
+ const router = useRouter();
+
+ return (
+
+
+ ✓
+
+
+
+ You're all set!
+
+
+ Your account has been created. You can now log in to connect your
+ sensors and analyze your soil.
+
+
+ router.replace("/(auth)/login")}
+ className="bg-green-600 w-full mt-10 p-5 rounded-2xl items-center"
+ >
+ Go to Login
+
+
+ );
+}
diff --git a/mobile/app/(auth)/register.tsx b/mobile/app/(auth)/register.tsx
index e69de29..b7085a6 100644
--- a/mobile/app/(auth)/register.tsx
+++ b/mobile/app/(auth)/register.tsx
@@ -0,0 +1,111 @@
+import React, { useState } from "react";
+import {
+ View,
+ Text,
+ TextInput,
+ TouchableOpacity,
+ Alert,
+ ScrollView,
+} from "react-native";
+import { useRouter } from "expo-router";
+import { api } from "../../lib/api";
+
+export default function RegisterScreen() {
+ const [form, setForm] = useState({
+ username: "",
+ email: "",
+ password: "",
+ deviceId: "",
+ });
+ const [loading, setLoading] = useState(false);
+ const router = useRouter();
+
+ const handleRegister = async () => {
+ if (!form.username || !form.email || !form.password) {
+ return Alert.alert(
+ "Error",
+ "Username, Email, and Password are required.",
+ );
+ }
+
+ setLoading(true);
+ try {
+ // Calling your Go backend via the api.ts utility
+ await api.register({
+ username: form.username,
+ email: form.email,
+ password: form.password,
+ device_id: form.deviceId,
+ });
+
+ // Navigate to the success screen instead of a blocking alert
+ router.push("/(auth)/register-success");
+ } catch (err: any) {
+ // Fallback to error message from your Gin middleware/handlers
+ Alert.alert("Registration Failed", err.message);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+ Create Account
+
+
+ Join TerraDetect to start monitoring your soil.
+
+
+
+ setForm({ ...form, username: val })}
+ />
+ setForm({ ...form, email: val })}
+ />
+ setForm({ ...form, password: val })}
+ />
+ setForm({ ...form, deviceId: val })}
+ />
+
+
+
+ {loading ? "Creating Account..." : "Sign Up"}
+
+
+
+ router.push("/(auth)/login")}
+ className="py-4"
+ >
+
+ Already have an account?{" "}
+ Login
+
+
+
+
+ );
+}
diff --git a/mobile/app/_layout.tsx b/mobile/app/_layout.tsx
index e69de29..ed0b08b 100644
--- a/mobile/app/_layout.tsx
+++ b/mobile/app/_layout.tsx
@@ -0,0 +1,58 @@
+import { useEffect, useState } from "react";
+import { Stack, useRouter, useSegments } from "expo-router";
+import { useAuthStore } from "../store/authStore";
+import { View, ActivityIndicator } from "react-native";
+
+export default function RootLayout() {
+ const { accessToken, loadFromStorage } = useAuthStore();
+ const segments = useSegments();
+ const router = useRouter();
+ const [isReady, setIsReady] = useState(false);
+
+ // 1. Initialize Auth State
+ useEffect(() => {
+ const initialize = async () => {
+ await loadFromStorage();
+ setIsReady(true);
+ };
+ initialize();
+ }, []);
+
+ // 2. Auth Guard Logic
+ useEffect(() => {
+ if (!isReady) return;
+
+ const inAppGroup = segments[0] === "(app)";
+
+ if (!accessToken && inAppGroup) {
+ // If user is not logged in and tries to access (app) folder
+ router.replace("/(auth)/landing");
+ } else if (accessToken && !inAppGroup) {
+ // If user is logged in and tries to access (auth) folder
+ router.replace("/(app)/dashboard");
+ }
+ }, [accessToken, segments, isReady]);
+
+ // 3. Loading State (Prevents flicker during hydration)
+ if (!isReady) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ );
+}
diff --git a/mobile/babel.config.js b/mobile/babel.config.js
new file mode 100644
index 0000000..f3c649b
--- /dev/null
+++ b/mobile/babel.config.js
@@ -0,0 +1,9 @@
+module.exports = function (api) {
+ api.cache(true);
+ return {
+ presets: [
+ ["babel-preset-expo", { jsxImportSource: "nativewind" }],
+ "nativewind/babel",
+ ],
+ };
+};
diff --git a/mobile/global.css b/mobile/global.css
new file mode 100644
index 0000000..bd6213e
--- /dev/null
+++ b/mobile/global.css
@@ -0,0 +1,3 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
\ No newline at end of file
diff --git a/mobile/nativewind-env.d.ts b/mobile/nativewind-env.d.ts
new file mode 100644
index 0000000..a13e313
--- /dev/null
+++ b/mobile/nativewind-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/mobile/tailwind.config.js b/mobile/tailwind.config.js
new file mode 100644
index 0000000..6288219
--- /dev/null
+++ b/mobile/tailwind.config.js
@@ -0,0 +1,9 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: ["./app/**/*.{js,jsx,ts,tsx}", "./components/**/*.{js,jsx,ts,tsx}"],
+ presets: [require("nativewind/preset")],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+};