olivetin/frontend/resources/vue/views/LoginView.vue

237 lines
5.7 KiB
Vue

<template>
<Section
:title="t('login.title')"
class="small"
>
<div class="login-form">
<div
v-if="!hasOAuth && !hasLocalLogin"
class="login-disabled"
>
<span>{{ t('login.disabled') }}</span>
</div>
<div
v-if="hasOAuth"
class="login-oauth2"
>
<h3>{{ t('login.oauth') }}</h3>
<div class="oauth-providers">
<button
v-for="provider in oauthProviders"
:key="provider.key"
class="oauth-button"
@click="loginWithOAuth(provider)"
>
<span
v-if="providerIcon(provider)"
class="provider-icon"
>
<iconify-icon
v-if="providerIcon(provider).kind === 'iconify'"
:icon="providerIcon(provider).id"
/>
<span v-else>{{ providerIcon(provider).text }}</span>
</span>
<span class="provider-name">{{ t('login.oauth-with', { provider: provider.title }) }}</span>
</button>
</div>
</div>
<div
v-if="hasLocalLogin"
class="login-local"
>
<form
class="local-login-form"
@submit.prevent="handleLocalLogin"
>
<div
v-if="loginError"
class="bad"
>
{{ loginError }}
</div>
<input
id="username"
v-model="username"
type="text"
name="username"
autocomplete="username"
required
:placeholder="t('login.username')"
>
<input
id="password"
v-model="password"
type="password"
name="password"
autocomplete="current-password"
:placeholder="t('login.password')"
required
>
<button
type="submit"
:disabled="loading"
class="login-button"
>
{{ loading ? t('login.submitting') : t('login.submit') }}
</button>
</form>
</div>
</div>
</Section>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { useRouter } from 'vue-router'
import Section from 'picocrank/vue/components/Section.vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const router = useRouter()
const username = ref('')
const password = ref('')
const loading = ref(false)
const loginError = ref('')
const hasOAuth = ref(false)
const hasLocalLogin = ref(false)
const oauthProviders = ref([])
const trustedProviderIconifyIds = {
github: 'simple-icons:github',
google: 'simple-icons:google'
}
function providerIcon (provider) {
const raw = (provider?.icon || '').trim()
if (!raw) {
return null
}
const iconifyTagMatch = raw.match(/<iconify-icon\b[^>]*\bicon=["']([^"']+)["'][^>]*>/i)
if (iconifyTagMatch) {
return { kind: 'iconify', id: iconifyTagMatch[1] }
}
if (/^[a-z0-9-]+:[a-z0-9-]+$/i.test(raw)) {
return { kind: 'iconify', id: raw }
}
const trustedId = trustedProviderIconifyIds[provider.key]
if (trustedId && (raw.includes('<') || raw === provider.key)) {
return { kind: 'iconify', id: trustedId }
}
if (!raw.includes('<')) {
return { kind: 'text', text: raw }
}
return trustedId ? { kind: 'iconify', id: trustedId } : null
}
function loadLoginOptions () {
// Use the init response data that was loaded in App.vue
if (window.initResponse) {
hasOAuth.value = window.initResponse.oAuth2Providers && window.initResponse.oAuth2Providers.length > 0
hasLocalLogin.value = window.initResponse.authLocalLogin
if (hasOAuth.value) {
oauthProviders.value = window.initResponse.oAuth2Providers
}
} else {
console.warn('Init response not available yet, login options will be empty')
}
}
async function handleLocalLogin () {
loading.value = true
loginError.value = ''
try {
const response = await window.client.localUserLogin({
username: username.value,
password: password.value
})
if (response.success) {
// Re-initialize to get updated user context
try {
const initResponse = await window.client.init({})
window.initResponse = initResponse
window.initError = false
window.initErrorMessage = ''
window.initCompleted = true
// Update the header with new user info
if (window.updateHeaderFromInit) {
window.updateHeaderFromInit()
}
} catch (initErr) {
console.error('Failed to reinitialize after login:', initErr)
}
// Redirect to home page on successful login
router.push('/')
} else {
loginError.value = t('login.failed')
}
} catch (err) {
console.error('Login error:', err)
loginError.value = err.message || t('login.network-error')
} finally {
loading.value = false
}
}
function loginWithOAuth (provider) {
if (!provider.key) {
console.error('OAuth provider missing key:', provider)
return
}
const providerKey = encodeURIComponent(provider.key)
window.location.href = `/oauth/login?provider=${providerKey}`
}
onMounted(() => {
loadLoginOptions()
// Also watch for when init response becomes available
watch(() => window.initResponse, () => {
loadLoginOptions()
}, { immediate: true })
})
</script>
<style scoped>
section {
margin: auto;
}
.login-view {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
form {
grid-template-columns: 1fr;
gap: 1em;
}
.provider-icon {
width: 1em;
height: 1em;
margin-right: .4em;
display: inline-flex;
vertical-align: middle;
}
</style>