From ece68ed078be3f6d66b5dcd7de8ba9853d48be27 Mon Sep 17 00:00:00 2001
From: Mohamed Bassem <me@mbassem.com>
Date: Mon, 22 Dec 2025 16:36:23 +0200
Subject: feat(mobile): Convert server address editing to modal in mobile app
 (#2290)

* feat: Convert server address editing to modal in mobile app

Changed the server address editing experience from an inline button to a modal dialog. This improves UX by forcing users to explicitly save or cancel their changes rather than forgetting to click a save button.

Changes:
- Created ServerAddressModal component following the CustomHeadersModal pattern
- Updated signin page to use the modal instead of inline editing
- Enhanced settings page to allow changing server address (was previously read-only)
- Added validation and error handling within the modal
- Made the settings page server address clickable with visual feedback

This resolves the issue where users forget to click the save button after editing the server address.

* refactor: Convert server address to screen modal

Changed from React Native Modal to Expo Router screen modal presentation.
This provides a better native experience with proper navigation stack integration.

Changes:
- Created server-address.tsx as a screen route with modal presentation
- Registered the route in root _layout.tsx
- Updated signin.tsx to navigate to the screen modal instead of opening RN modal
- Reverted settings page to original (no server address editing from settings)
- Removed ServerAddressModal component (no longer needed)

Benefits:
- Native modal presentation with proper animations
- Better integration with the navigation stack
- Cleaner separation of concerns

* merge the custom headers inside the server-add screen

* fix the look of the address UI

---------

Co-authored-by: Claude <noreply@anthropic.com>
---
 apps/mobile/app/signin.tsx | 103 +++++++++------------------------------------
 1 file changed, 19 insertions(+), 84 deletions(-)

(limited to 'apps/mobile/app/signin.tsx')

diff --git a/apps/mobile/app/signin.tsx b/apps/mobile/app/signin.tsx
index 6a554f89..03cbba5a 100644
--- a/apps/mobile/app/signin.tsx
+++ b/apps/mobile/app/signin.tsx
@@ -7,7 +7,6 @@ import {
   View,
 } from "react-native";
 import { Redirect, useRouter } from "expo-router";
-import { CustomHeadersModal } from "@/components/CustomHeadersModal";
 import Logo from "@/components/Logo";
 import { TailwindResolver } from "@/components/TailwindResolver";
 import { Button } from "@/components/ui/Button";
@@ -15,7 +14,7 @@ import { Input } from "@/components/ui/Input";
 import { Text } from "@/components/ui/Text";
 import useAppSettings from "@/lib/settings";
 import { api } from "@/lib/trpc";
-import { Bug, Check, Edit3 } from "lucide-react-native";
+import { Bug, Edit3 } from "lucide-react-native";
 
 enum LoginType {
   Password,
@@ -28,12 +27,6 @@ export default function Signin() {
 
   const [error, setError] = useState<string | undefined>();
   const [loginType, setLoginType] = useState<LoginType>(LoginType.Password);
-  const [isEditingServerAddress, setIsEditingServerAddress] = useState(false);
-  const [tempServerAddress, setTempServerAddress] = useState(
-    settings.address ?? "https://cloud.karakeep.app",
-  );
-  const [isCustomHeadersModalVisible, setIsCustomHeadersModalVisible] =
-    useState(false);
 
   const emailRef = useRef<string>("");
   const passwordRef = useRef<string>("");
@@ -82,19 +75,15 @@ export default function Signin() {
     return <Redirect href="dashboard" />;
   }
 
-  const handleSaveCustomHeaders = (headers: Record<string, string>) => {
-    setSettings({ ...settings, customHeaders: headers });
-  };
-
   const onSignin = () => {
-    if (!tempServerAddress) {
+    if (!settings.address) {
       setError("Server address is required");
       return;
     }
 
     if (
-      !tempServerAddress.startsWith("http://") &&
-      !tempServerAddress.startsWith("https://")
+      !settings.address.startsWith("http://") &&
+      !settings.address.startsWith("https://")
     ) {
       setError("Server address must start with http:// or https://");
       return;
@@ -137,71 +126,23 @@ export default function Signin() {
           )}
           <View className="gap-2">
             <Text className="font-bold">Server Address</Text>
-            {!isEditingServerAddress ? (
-              <View className="flex-row items-center gap-2">
-                <View className="flex-1 rounded-md border border-border bg-card px-3 py-2">
-                  <Text>{tempServerAddress}</Text>
-                </View>
-                <Button
-                  size="icon"
-                  variant="secondary"
-                  onPress={() => {
-                    setIsEditingServerAddress(true);
-                  }}
-                >
-                  <TailwindResolver
-                    comp={(styles) => (
-                      <Edit3 size={16} color={styles?.color?.toString()} />
-                    )}
-                    className="color-foreground"
-                  />
-                </Button>
+            <View className="flex-row items-center gap-2">
+              <View className="flex-1 rounded-md border border-border bg-card px-3 py-2">
+                <Text>{settings.address ?? "https://cloud.karakeep.app"}</Text>
               </View>
-            ) : (
-              <View className="flex-row items-center gap-2">
-                <Input
-                  className="flex-1"
-                  inputClasses="bg-card"
-                  placeholder="Server Address"
-                  value={tempServerAddress}
-                  autoCapitalize="none"
-                  keyboardType="url"
-                  onChangeText={setTempServerAddress}
-                  autoFocus
+              <Button
+                size="icon"
+                variant="secondary"
+                onPress={() => router.push("/server-address")}
+              >
+                <TailwindResolver
+                  comp={(styles) => (
+                    <Edit3 size={16} color={styles?.color?.toString()} />
+                  )}
+                  className="color-foreground"
                 />
-                <Button
-                  size="icon"
-                  variant="primary"
-                  onPress={() => {
-                    if (tempServerAddress.trim()) {
-                      setSettings({
-                        ...settings,
-                        address: tempServerAddress.trim().replace(/\/$/, ""),
-                      });
-                    }
-                    setIsEditingServerAddress(false);
-                  }}
-                >
-                  <TailwindResolver
-                    comp={(styles) => (
-                      <Check size={16} color={styles?.color?.toString()} />
-                    )}
-                    className="text-white"
-                  />
-                </Button>
-              </View>
-            )}
-            <Pressable
-              onPress={() => setIsCustomHeadersModalVisible(true)}
-              className="mt-1"
-            >
-              <Text className="text-xs text-gray-500 underline">
-                Configure Custom Headers{" "}
-                {settings.customHeaders &&
-                  Object.keys(settings.customHeaders).length > 0 &&
-                  `(${Object.keys(settings.customHeaders).length})`}
-              </Text>
-            </Pressable>
+              </Button>
+            </View>
           </View>
           {loginType === LoginType.Password && (
             <>
@@ -282,12 +223,6 @@ export default function Signin() {
           </Pressable>
         </View>
       </TouchableWithoutFeedback>
-      <CustomHeadersModal
-        visible={isCustomHeadersModalVisible}
-        customHeaders={settings.customHeaders || {}}
-        onClose={() => setIsCustomHeadersModalVisible(false)}
-        onSave={handleSaveCustomHeaders}
-      />
     </KeyboardAvoidingView>
   );
 }
-- 
cgit v1.2.3-70-g09d2

