Newer
Older
smart-home-server / webclient / src / features / auth / pages / LoginPage.vue
<template>
  <div class="login-page">
    <div class="login-card">
      <div class="login-brand">
        <i class="ph ph-fill ph-house-line brand-icon" />
        <h1 class="brand-title">Smart Home Server</h1>
        <p class="brand-subtitle">SHServ Web Client</p>
      </div>

      <div class="login-divider" />

      <p class="login-hint">
        You need to sign in to access the smart home dashboard.
      </p>

      <GnButton
        variant="primary"
        size="lg"
        class="login-btn"
        @click="handleLogin"
      >
        <template #icon>
          <i class="ph ph-sign-in" />
        </template>
        Sign in with gnexus-auth
      </GnButton>

      <p v-if="authStore.isLoading" class="login-loading text-muted">
        <i class="ph ph-spinner ph-spin" /> Checking session…
      </p>
    </div>
  </div>
</template>

<script setup>
import { onMounted } from "vue";
import { useRouter, useRoute } from "vue-router";
import { GnButton } from "gnexus-ui-kit/vue";
import { useAuthStore } from "../../../stores/auth.js";
import { setAccessToken } from "../../../api/auth.js";

const router = useRouter();
const route = useRoute();
const authStore = useAuthStore();

onMounted(async () => {
  // OAuth callback passes access_token in query string after successful auth
  const tokenFromUrl = route.query.access_token;
  if (tokenFromUrl && typeof tokenFromUrl === "string") {
    setAccessToken(tokenFromUrl);
    await authStore.init();
    if (authStore.isAuthenticated) {
      // Remove token from URL for cleanliness
      router.replace({ name: "areas-favorites" });
      return;
    }
  }

  if (authStore.isAuthenticated) {
    router.replace({ name: "areas-favorites" });
  }
});

function handleLogin() {
  const returnTo = window.location.hash || "#/";
  window.location.href = `/auth/login?return_to=${encodeURIComponent(returnTo)}`;
}
</script>

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

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

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

.brand-icon {
  font-size: 48px;
  color: var(--color-primary);
}

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

.brand-subtitle {
  margin: 0;
  font-size: 0.875rem;
  color: var(--color-text-secondary);
}

.login-divider {
  width: 48px;
  height: 2px;
  background: var(--color-border);
  border-radius: 1px;
}

.login-hint {
  margin: 0;
  font-size: 0.875rem;
  color: var(--color-text-secondary);
  line-height: 1.5;
}

.login-btn {
  width: 100%;
}

.login-loading {
  margin: 0;
  font-size: 0.8125rem;
  display: flex;
  align-items: center;
  gap: 0.375rem;
}
</style>