laravel-nuxt/nuxt/app/components/auth/AuthLoginForm.vue

174 lines
4.0 KiB
Vue

<script setup lang="ts">
import type { IAccountLoginResponse, IAccountProvider, IAccountProviderData } from '~/types/account'
import type { ButtonProps } from '#ui/components/Button.vue'
const config = useRuntimeConfig()
const router = useRouter()
const auth = useAuthStore()
const form = useTemplateRef('form')
const state = reactive({
email: '',
password: '',
remember: false,
})
const { refresh: onSubmit, status: loginStatus } = useFetch<IAccountLoginResponse>('login', {
method: 'POST',
body: state,
immediate: false,
watch: false,
async onResponse({ response }) {
if (response?.status === 422) {
form?.value?.setErrors(response._data?.errors)
}
else if (response._data?.ok) {
auth.token = response._data.token
await auth.fetchUser()
await router.push('/')
}
},
})
const providers = ref<{ [key: string]: IAccountProvider }>(
Object.fromEntries(
Object.entries(config.public.providers).map(([key, provider]) => [
key,
{
...provider,
color: provider.color as ButtonProps['color'],
},
]),
),
)
async function handleMessage(event: { data: IAccountProviderData }): Promise<void> {
const provider = event.data.provider as string
if (Object.keys(providers.value).includes(provider) && event.data.token) {
if (providers?.value[provider]?.loading) {
providers.value[provider].loading = false
}
auth.token = event.data.token
await auth.fetchUser()
await router.push('/')
}
else if (event.data.message) {
useToast().add({
icon: 'i-heroicons-exclamation-circle-solid',
color: 'error',
title: event.data.message,
})
}
}
function loginVia(provider: string): void {
providers.value[provider]!.loading = true
const width = 640
const height = 660
const left = window.screen.width / 2 - width / 2
const top = window.screen.height / 2 - height / 2
const popup = window.open(
`${config.public.apiBase}${config.public.apiPrefix}/login/${provider}/redirect`,
'Sign In',
`toolbar=no, location=no, directories=no, status=no, menubar=no, scollbars=no, resizable=no, copyhistory=no, width=${width},height=${height},top=${top},left=${left}`,
)
const interval = setInterval(() => {
if (!popup || popup.closed) {
clearInterval(interval)
providers.value[provider]!.loading = false
}
}, 500)
}
onMounted(() => window.addEventListener('message', handleMessage))
onBeforeUnmount(() => window.removeEventListener('message', handleMessage))
</script>
<template>
<UForm
ref="form"
:state="state"
class="space-y-4"
@submit="onSubmit"
>
<UFormField
label="Email"
name="email"
>
<UInput
v-model="state.email"
class="w-full"
/>
</UFormField>
<UFormField
label="Password"
name="password"
>
<UInput
v-model="state.password"
type="password"
class="w-full"
/>
</UFormField>
<div class="flex items-center justify-between">
<UCheckbox
id="remember-me"
v-model="state.remember"
label="Remember me"
name="remember-me"
/>
<div class="text-sm leading-6">
<NuxtLink
to="/forgot-password"
class="text-primary hover:text-primary-300 font-semibold"
>
Forgot
password?
</NuxtLink>
</div>
</div>
<UButton
block
size="md"
type="submit"
:loading="loginStatus === 'pending'"
icon="i-heroicons-arrow-right-on-rectangle"
>
Login
</UButton>
</UForm>
<USeparator
v-if="Object.keys(providers).length > 0"
color="neutral"
label="Login with"
class="my-4"
/>
<div class="flex gap-4">
<UButton
v-for="(provider, key) in providers"
:key="key"
:loading="provider.loading"
:icon="provider.icon"
:color="provider.color"
:label="provider.name"
size="lg"
class="w-full flex items-center justify-center"
@click="loginVia(key as string)"
/>
</div>
</template>
<style scoped>
</style>