feat: link sfids
This commit is contained in:
parent
c89ff7e61e
commit
5069965ba3
11 changed files with 371 additions and 11 deletions
|
|
@ -45,11 +45,12 @@ export const useAuth = () => {
|
|||
window.history.replaceState({}, document.title, window.location.pathname)
|
||||
await verifySession()
|
||||
startLiveSessionPolling()
|
||||
return true
|
||||
|
||||
return { success: true, isFirstLogin: data.isFirstLogin }
|
||||
} catch (error) {
|
||||
console.error('Code exchange failed:', error)
|
||||
clearSession()
|
||||
return false
|
||||
return { success: false, isFirstLogin: false }
|
||||
} finally {
|
||||
isInitialized.value = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,10 +14,14 @@ onMounted(async () => {
|
|||
|
||||
if (route.query.code) {
|
||||
isLoggingIn.value = true
|
||||
const success = await exchangeCodeForToken(route.query.code)
|
||||
const result = await exchangeCodeForToken(route.query.code)
|
||||
|
||||
if (success) {
|
||||
await navigateTo('/')
|
||||
if (result?.success) {
|
||||
if (result.isFirstLogin) {
|
||||
await navigateTo('/welcome')
|
||||
} else {
|
||||
await navigateTo('/')
|
||||
}
|
||||
} else {
|
||||
errorMessage.value = 'Access Denied'
|
||||
isLoggingIn.value = false
|
||||
|
|
@ -31,7 +35,6 @@ const handleLogin = () => {
|
|||
errorMessage.value = ''
|
||||
|
||||
const redirectUri = `${window.location.origin}/oauth`
|
||||
|
||||
const authUrl = new URL(
|
||||
`${config.public.keycloakUrl}/realms/${config.public.keycloakRealm}/protocol/openid-connect/auth`
|
||||
)
|
||||
|
|
|
|||
145
app/pages/welcome/index.vue
Normal file
145
app/pages/welcome/index.vue
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const { userToken } = useAuth()
|
||||
|
||||
const sfidInput = ref('')
|
||||
const step = ref(1)
|
||||
const foundUser = ref(null)
|
||||
|
||||
const isSearching = ref(false)
|
||||
const isSubmitting = ref(false)
|
||||
const errorMessage = ref('')
|
||||
|
||||
const handleLookup = async () => {
|
||||
if (!sfidInput.value.trim()) return
|
||||
|
||||
isSearching.value = true
|
||||
errorMessage.value = ''
|
||||
|
||||
try {
|
||||
const res = await $fetch('/api/user/lookup-nnas', {
|
||||
method: 'POST',
|
||||
body: { sfid: sfidInput.value }
|
||||
})
|
||||
|
||||
foundUser.value = res
|
||||
step.value = 2
|
||||
} catch (err) {
|
||||
errorMessage.value = err.data?.statusMessage || err.data?.message || 'Failed to locate user account.'
|
||||
} finally {
|
||||
isSearching.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (!foundUser.value) return
|
||||
|
||||
isSubmitting.value = true
|
||||
errorMessage.value = ''
|
||||
|
||||
try {
|
||||
await $fetch('/api/user/complete-welcome', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
token: userToken.value,
|
||||
sfid: foundUser.value.username
|
||||
}
|
||||
})
|
||||
await navigateTo('/')
|
||||
} catch (err) {
|
||||
errorMessage.value = err.data?.statusMessage || err.data?.message || 'Failed to complete setup.'
|
||||
} finally {
|
||||
isSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
step.value = 1
|
||||
foundUser.value = null
|
||||
errorMessage.value = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-wrapper">
|
||||
<section class="settings-panel" style="max-width: 480px; margin: 0 auto;">
|
||||
<div class="panel-header archivo-black">
|
||||
WELCOME
|
||||
</div>
|
||||
|
||||
<div v-if="step === 1" class="panel-body" style="padding: 2rem; text-align: center;">
|
||||
<img src="/welcome5-1.png" alt="Welcome Image"
|
||||
style="max-width: 160px; margin-bottom: 1rem; object-fit: contain;" />
|
||||
|
||||
<h2 style="font-size: 1.25rem; font-weight: 600; margin-bottom: 0.5rem;">
|
||||
First Time Setup
|
||||
</h2>
|
||||
|
||||
<p style="color: #8b949e; font-size: 0.9rem; margin-bottom: 1.5rem; line-height: 1.4;">
|
||||
Please enter your SFID to link your account. This wont be used for login.
|
||||
</p>
|
||||
|
||||
<form @submit.prevent="handleLookup">
|
||||
<div style="margin-bottom: 1.25rem; text-align: left;">
|
||||
<label for="sfid" style="display: block; font-size: 0.85rem; color: #c9d1d9; margin-bottom: 0.5rem;">
|
||||
SFID <span style="color: #f85149;">*</span>
|
||||
</label>
|
||||
<input id="sfid" v-model="sfidInput" type="text" placeholder="e.g. redbinderischud" required
|
||||
style="width: 100%; padding: 0.6rem 0.8rem; background: #0d1117; border: 1px solid #30363d; border-radius: 6px; color: #c9d1d9; font-size: 0.95rem; outline: none;" />
|
||||
</div>
|
||||
|
||||
<p v-if="errorMessage" style="color: #f85149; font-size: 0.85rem; margin-bottom: 1rem;">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
|
||||
<button type="submit" class="btn-login-action" style="width: 100%;" :disabled="isSearching">
|
||||
<span v-if="isSearching" class="spinner sm btn-spinner"></span>
|
||||
<span style="vertical-align: middle;">
|
||||
{{ isSearching ? 'Looking up...' : 'Find Account →' }}
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div v-else class="panel-body" style="padding: 2rem; text-align: center;">
|
||||
<h2 style="font-size: 1.25rem; font-weight: 600; margin-bottom: 1.5rem;">
|
||||
Is this you?
|
||||
</h2>
|
||||
|
||||
<div
|
||||
style="background: #0d1117; border: 1px solid #30363d; border-radius: 8px; padding: 1.5rem; margin-bottom: 1.5rem;">
|
||||
<img :src="`https://mii.spfn.net/${foundUser.pid}/main.png`" :alt="foundUser.username"
|
||||
style="width: 96px; height: 96px; border-radius: 50%; background: #161b22; object-fit: contain; margin-bottom: 0.75rem;"
|
||||
@error="(e) => e.target.src = '/img_unknown_MiiIcon.png'" />
|
||||
<div style="font-size: 1.1rem; font-weight: 600; color: #f0f6fc;">
|
||||
{{ foundUser.username }}
|
||||
</div>
|
||||
<div style="font-size: 0.8rem; color: #8b949e; margin-top: 0.25rem;">
|
||||
PID: {{ foundUser.pid }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="errorMessage" style="color: #f85149; font-size: 0.85rem; margin-bottom: 1rem;">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
|
||||
<div style="display: flex; gap: 0.75rem;">
|
||||
<button type="button"
|
||||
style="flex: 1; padding: 0.6rem; background: #21262d; border: 1px solid #30363d; border-radius: 6px; color: #c9d1d9; font-weight: 500; cursor: pointer;"
|
||||
:disabled="isSubmitting" @click="handleReset">
|
||||
Not Me
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn-login-action" style="flex: 1;" :disabled="isSubmitting"
|
||||
@click="handleConfirm">
|
||||
<span v-if="isSubmitting" class="spinner sm btn-spinner"></span>
|
||||
<span style="vertical-align: middle;">
|
||||
{{ isSubmitting ? 'Linking...' : 'Yes, That\'s Me!' }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
Loading…
Reference in a new issue