Newer
Older
navi-1 / webclient / src / components / settings / AccountPanel.vue
<template>
  <div class="account-panel">
    <!-- First block of the Account tab: the language of the interface itself. -->
    <div class="account-language">
      <div class="account-language-header">
        <h2 class="account-language-title">{{ t('settings.languageToolbar') }}</h2>
      </div>

      <p class="account-language-description">{{ languageDescription }}</p>

      <GnSelect
        class="language-select"
        :aria-label="t('settings.languageToolbar')"
        :model-value="currentLocale"
        :options="localeOptions"
        :disabled="saving"
        @update:model-value="changeLanguage"
      />
    </div>

    <ApiKeysPanel />
  </div>
</template>

<script setup>
import { computed, ref } from 'vue'
import { GnSelect, useToast } from 'gnexus-ui-kit/vue'
import { updateMe } from '@/api'
import { useAuthStore } from '@/stores/auth.js'
import { SUPPORTED_LOCALES, locale, rememberLocale, setLocale, t } from '@/i18n/index.js'
import ApiKeysPanel from './ApiKeysPanel.vue'

const authStore = useAuthStore()
const toast = useToast()

const saving = ref(false)

// Signed in, the language is a property of the account and follows it to other
// devices. Without an account there is nothing to follow: sign-in may be off
// altogether, and a visitor who is not signed in has no account either.
const signedIn = computed(() => !authStore.isNoAuthMode && authStore.user !== null)

const LANG_SLUGS = { en: 'profile.langEn', uk: 'profile.langUk', ru: 'profile.langRu' }

// Computed, not a module-level constant: the labels go through t() and have to
// be rebuilt whenever the language changes.
const localeOptions = computed(() => {
  const languages = SUPPORTED_LOCALES.map(code => ({ value: code, label: t(LANG_SLUGS[code]) }))
  return signedIn.value
    ? [{ value: '', label: t('profile.languageAuto') }, ...languages]
    : languages
})

// '' is the Auto option — a value GnSelect can hold, unlike null.
const currentLocale = computed(() => (signedIn.value ? authStore.user.locale ?? '' : locale.value))

const languageDescription = computed(() =>
  t(signedIn.value ? 'settings.languageDescription' : 'settings.languageDescriptionOpen')
)

async function changeLanguage(value) {
  if (!signedIn.value) {
    // No account to write to: the choice stays in this browser.
    rememberLocale(value)
    setLocale(value)
    toast.success({ title: t('toast.languageSaved') })
    return
  }

  saving.value = true
  try {
    // '' is Auto; the backend reads both it and 'auto' as "follow the account".
    const updated = await updateMe({ locale: value || 'auto' })
    authStore.user = updated
    setLocale(updated.locale_effective)
    toast.success({ title: t('toast.languageSaved') })
  } catch (err) {
    // The stored language is unchanged, so the picker still shows the truth.
    toast.error({ title: t('common.error'), text: err.message })
  } finally {
    saving.value = false
  }
}
</script>

<style scoped lang="scss">
@use 'kit-deps' as *;

.account-language {
  margin-bottom: 24px;
}

.account-language-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}

.account-language-title {
  font-size: 1.1rem;
  font-weight: 600;
  margin: 0;
  color: var(--gn-color-text-medium);
}

.account-language-description {
  margin: 0 0 16px;
  font-size: 0.875rem;
  color: var(--gn-color-text-medium);
}
</style>