Newer
Older
smart-home-server / webclient / src / features / auth / pages / MobileSetupPage.vue
<template>
  <div class="setup-page">
    <div class="setup-card">
      <div class="setup-brand">
        <img src="/logo-cube-square.svg" alt="Smart Home" class="brand-logo" />
        <h1 class="brand-title">Smart Home</h1>
        <p class="brand-subtitle text-muted">Mobile App Setup</p>
      </div>

      <div class="setup-divider text-muted" />

      <p class="setup-hint text-muted">
        Enter the address of your Smart Home server. Example:
        <code class="text-info">https://shserv.home</code> or
        <code class="text-info">http://192.168.1.10</code>
      </p>

      <div class="setup-form">
        <GnInput
          v-model="serverUrl"
          label="Server URL"
          placeholder="https://your-server.com"
          :error="error"
          @keyup.enter="handleSave"
        />

        <GnButton
          variant="primary"
          size="lg"
          class="setup-btn"
          :loading="testing"
          @click="handleSave"
        >
          <template #icon>
            <i class="ph ph-check" />
          </template>
          {{ testing ? "Checking connection…" : "Save and continue" }}
        </GnButton>
      </div>

      <p v-if="testError" class="setup-error text-danger">
        <i class="ph ph-warning-circle" /> {{ testError }}
      </p>
    </div>
  </div>
</template>

<script setup>
import { ref } from "vue";
import { useRouter } from "vue-router";
import { App } from "@capacitor/app";
import { GnButton, GnInput } from "gnexus-ui-kit/vue";
import { setServerUrl, isNativeApp } from "../../../api/server-config";

const router = useRouter();
const serverUrl = ref("");
const error = ref("");
const testing = ref(false);
const testError = ref("");

async function handleSave() {
  error.value = "";
  testError.value = "";

  const raw = serverUrl.value.trim();
  if (!raw) {
    error.value = "Server URL is required";
    return;
  }

  // Basic URL validation
  let url = raw;
  if (!/^https?:\/\//i.test(url)) {
    url = "http://" + url;
  }

  try {
    new URL(url);
  } catch {
    error.value = "Invalid URL format";
    return;
  }

  testing.value = true;

  try {
    // Test connectivity: try to reach the server's /about or /api/v1/about
    const testUrls = [`${url}/api/v1/about`, `${url}/about`];
    let reachable = false;

    for (const testUrl of testUrls) {
      try {
        const controller = new AbortController();
        const timeout = setTimeout(() => controller.abort(), 5000);
        const res = await fetch(testUrl, {
          method: "GET",
          signal: controller.signal,
        });
        clearTimeout(timeout);
        if (res.ok || res.status === 401) {
          // 401 is fine — means server is alive but wants auth
          reachable = true;
          break;
        }
      } catch {
        // try next
      }
    }

    if (!reachable) {
      testError.value = "Could not connect to server. Please check the address.";
      testing.value = false;
      return;
    }

    await setServerUrl(url);

    // Remote server URL requires an app restart so Capacitor loads the SPA
    // from the network instead of the bundled assets.
    alert("Server address saved. Please restart the app to continue.");
    App.exitApp();
    return;
  } catch {
    testError.value = "Connection check failed. Please try again.";
    testing.value = false;
  }
}
</script>

<style scoped>
.setup-page {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 1rem;
}

.setup-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.5rem;
  max-width: 360px;
  width: 100%;
  padding: 2rem;
  text-align: center;
}

.setup-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
}

.brand-logo {
  width: 120px;
  height: 120px;
}

.brand-title {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 700;
}

.brand-subtitle {
  margin: 0;
  font-size: 0.875rem;
}

.setup-divider {
  width: 48px;
  height: 2px;
  background: currentColor;
  border-radius: 1px;
}

.setup-hint {
  margin: 0;
  font-size: 0.875rem;
  line-height: 1.5;
}

.setup-form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  width: 100%;
}

.setup-btn {
  width: 100%;
}

.setup-error {
  margin: 0;
  font-size: 0.875rem;
  display: flex;
  align-items: center;
  gap: 0.375rem;
}
</style>