Newer
Older
gn-synapse / frontend / src / views / LoginView.vue
<script setup>
// Вход через gnexus-auth. Скелет: пока токен вставляется вручную (dev).
// TODO(#32): полноценный OAuth2 Authorization Code + PKCE через JS-клиент
// gnexus-auth (packages/auth-client, docs 18-20) — redirect на authorize,
// callback с обменом кода, refresh-ротация на стороне SPA.
import { ref } from "vue";
import { useRouter } from "vue-router";
import { GnButton, GnStatusCard } from "gnexus-ui-kit/vue";
import { adminMe, setToken } from "../api.js";

const router = useRouter();
const token = ref("");
const error = ref("");
const busy = ref(false);

async function signIn() {
  error.value = "";
  busy.value = true;
  setToken(token.value.trim());
  try {
    const me = await adminMe();
    if (me.system_role === "admin" || me.system_role === "superadmin") {
      await router.push(router.currentRoute.value.query.next || "/dashboard");
    } else {
      await router.push("/denied");
    }
  } catch (ex) {
    error.value = ex.status === 403 ? "Недостаточно прав: роль admin и выше" : `Вход не удался: ${ex.message}`;
  } finally {
    busy.value = false;
  }
}
</script>

<template>
  <div class="login-page">
    <div class="login-panel">
      <h2 class="login-title">Synapse Admin</h2>
      <p class="login-note">
        Вход через gnexus-auth. Скелет: вставьте access-токен (dev-режим), финальный
        OAuth-поток — в задаче #32.
      </p>
      <input
        v-model="token"
        class="login-input"
        type="password"
        placeholder="access token"
        @keyup.enter="signIn"
      />
      <GnButton variant="primary" icon="ph-sign-in" :loading="busy" @click="signIn">
        Войти
      </GnButton>
      <GnStatusCard v-if="error" variant="error" icon="ph-warning" :text="error" />
    </div>
  </div>
</template>

<style scoped>
.login-page {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: var(--gn-space-4);
}
.login-panel {
  background: var(--gn-surface-panel-muted);
  border: var(--gn-border-width-base) var(--gn-border-style-base) var(--gn-border-color-muted);
  border-left-width: var(--gn-border-width-accent);
  padding: var(--gn-space-6);
  display: flex;
  flex-direction: column;
  gap: var(--gn-space-4);
  max-width: 420px;
  width: 100%;
}
.login-title {
  font-size: var(--gn-font-size-lg);
  font-weight: var(--gn-font-weight-bold);
  text-transform: uppercase;
}
.login-note {
  font-size: var(--gn-font-size-xs);
  color: var(--gn-color-text-medium);
}
.login-input {
  background: var(--gn-surface-base);
  color: var(--gn-color-text-light);
  border: var(--gn-border-width-base) var(--gn-border-style-base) var(--gn-border-color-muted);
  padding: var(--gn-space-2) var(--gn-space-3);
  font-family: var(--gn-font-family-base);
}
.login-input:focus-visible {
  outline: var(--gn-focus-ring);
}
</style>