171 lines
5.8 KiB
Vue
171 lines
5.8 KiB
Vue
<script setup lang="ts">
|
|
import { toast } from 'vue-sonner'
|
|
import * as z from 'zod'
|
|
|
|
useSeoMeta({
|
|
title: 'Masuk Akun',
|
|
ogTitle: 'Masuk Akun',
|
|
description: 'Masuk untuk mengakses akun Anda',
|
|
ogDescription: 'Masuk untuk mengakses akun Anda',
|
|
})
|
|
|
|
const config = useRuntimeConfig()
|
|
const router = useRouter()
|
|
const loading = ref(false)
|
|
const { signInWithGoogle, loading: googleLoading } = useGoogleAuth()
|
|
|
|
const login = ref('')
|
|
const password = ref('')
|
|
const errors = ref<Record<string, string>>({})
|
|
|
|
const schema = z.object({
|
|
login: z.string().min(1, 'Username atau email wajib diisi'),
|
|
password: z.string().min(8, 'Kata sandi minimal 8 karakter'),
|
|
})
|
|
|
|
async function onSubmit() {
|
|
errors.value = {}
|
|
|
|
const result = schema.safeParse({ login: login.value, password: password.value })
|
|
if (!result.success) {
|
|
for (const issue of result.error.issues) {
|
|
const field = issue.path[0] as string
|
|
errors.value[field] = issue.message
|
|
}
|
|
return
|
|
}
|
|
|
|
loading.value = true
|
|
try {
|
|
await $fetch(`${config.public.apiBase}/auth/login`, {
|
|
method: 'POST',
|
|
body: {
|
|
login: login.value,
|
|
password: password.value,
|
|
}
|
|
})
|
|
toast.success('Selamat datang kembali!', {
|
|
description: `Masuk sebagai ${login.value}`,
|
|
})
|
|
router.push('/')
|
|
} catch (error: any) {
|
|
const detail = error?.data?.detail
|
|
if (detail && Array.isArray(detail)) {
|
|
const fieldErrors = detail
|
|
.filter((e: any) => e.loc?.length >= 2)
|
|
.map((e: any) => ({
|
|
path: e.loc[e.loc.length - 1],
|
|
message: e.msg
|
|
}))
|
|
for (const fe of fieldErrors) {
|
|
errors.value[fe.path] = fe.message
|
|
}
|
|
} else {
|
|
toast.error('Gagal masuk', {
|
|
description: error?.data?.message || 'Ups, terjadi kesalahan. Silakan coba lagi beberapa saat.',
|
|
})
|
|
}
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
async function handleGoogleLogin() {
|
|
await signInWithGoogle(
|
|
(result) => {
|
|
toast.success('Selamat datang kembali!', {
|
|
description: `Masuk sebagai ${result.user.email}`,
|
|
})
|
|
router.push('/')
|
|
},
|
|
(errorMsg) => {
|
|
toast.error('Gagal masuk', {
|
|
description: errorMsg,
|
|
})
|
|
}
|
|
)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="flex flex-col gap-6">
|
|
<Card>
|
|
<CardHeader class="text-center">
|
|
<CardTitle class="text-xl">
|
|
Selamat datang kembali!
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Masukkan kredensial Anda untuk mengakses akun.
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<form @submit.prevent="onSubmit">
|
|
<FieldGroup>
|
|
<Field :class="{ 'text-destructive': errors.login }">
|
|
<FieldLabel for="email">
|
|
Username atau Email <span class="text-destructive">*</span>
|
|
</FieldLabel>
|
|
<Input id="email" v-model="login" type="text" placeholder="Masukkan username atau email"
|
|
:class="{ 'border-destructive': errors.login }" required />
|
|
<p v-if="errors.login" class="text-sm text-destructive mt-1">
|
|
{{ errors.login }}
|
|
</p>
|
|
</Field>
|
|
<Field :class="{ 'text-destructive': errors.password }">
|
|
<div class="flex items-center">
|
|
<FieldLabel for="password">
|
|
Kata Sandi <span class="text-destructive">*</span>
|
|
</FieldLabel>
|
|
<NuxtLink to="/auth/forgot-password" class="ml-auto text-sm underline-offset-4 hover:underline">
|
|
Lupa kata sandi?
|
|
</NuxtLink>
|
|
</div>
|
|
<Input id="password" v-model="password" type="password" placeholder="Masukkan kata sandi"
|
|
:class="{ 'border-destructive': errors.password }" required />
|
|
<p v-if="errors.password" class="text-sm text-destructive mt-1">
|
|
{{ errors.password }}
|
|
</p>
|
|
</Field>
|
|
<Field>
|
|
<Button type="submit" :disabled="loading">
|
|
<span v-if="loading" class="i-lucide-loader-2 h-4 w-4 animate-spin mr-2" />
|
|
Masuk
|
|
</Button>
|
|
<FieldDescription class="text-center">
|
|
Tidak punya akun?
|
|
<NuxtLink to="/auth/register" class="text-primary hover:underline">
|
|
Daftar
|
|
</NuxtLink>
|
|
</FieldDescription>
|
|
</Field>
|
|
<FieldSeparator class="*:data-[slot=field-separator-content]:bg-card">
|
|
Atau lanjutkan dengan
|
|
</FieldSeparator>
|
|
<Field>
|
|
<Button variant="outline" type="button" :disabled="googleLoading" @click="handleGoogleLogin">
|
|
<span v-if="googleLoading" class="i-lucide-loader-2 h-4 w-4 animate-spin mr-2" />
|
|
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="h-4 w-4 mr-2">
|
|
<path
|
|
d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
|
|
fill="currentColor" />
|
|
</svg>
|
|
Masuk dengan Google
|
|
</Button>
|
|
</Field>
|
|
</FieldGroup>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
<FieldDescription class="px-6 text-center">
|
|
Dengan masuk, Anda menyetujui
|
|
<NuxtLink to="/terms" class="text-primary hover:underline">
|
|
Ketentuan Layanan
|
|
</NuxtLink>
|
|
dan
|
|
<NuxtLink to="/privacy" class="text-primary hover:underline">
|
|
Kebijakan Privasi
|
|
</NuxtLink>.
|
|
</FieldDescription>
|
|
</div>
|
|
</template>
|