ghostguild-org/app/pages/login.vue

377 lines
No EOL
13 KiB
Vue

<template>
<div>
<!-- Page Header -->
<PageHeader
title="Login"
subtitle="Welcome back! Sign in to access your Ghost Guild account and connect with the cooperative community."
theme="blue"
size="large"
/>
<!-- Login Form -->
<section class="py-20 bg-white dark:bg-gray-900">
<UContainer class="max-w-md">
<div class="text-center mb-12">
<h2 class="text-3xl font-bold text-blue-600 dark:text-blue-400 mb-4">
Passwordless Login
</h2>
<p class="text-gray-600 dark:text-gray-300">
Enter your email to receive a secure login link
</p>
</div>
<div class="bg-white dark:bg-gray-800 rounded-2xl p-8 shadow-xl border border-blue-200 dark:border-blue-800">
<UForm :state="loginForm" class="space-y-6" @submit="handleLogin">
<!-- Email Field -->
<UFormField label="Email Address" name="email" required>
<UInput
v-model="loginForm.email"
type="email"
size="xl"
class="w-full"
placeholder="your.email@example.com"
/>
</UFormField>
<!-- Passwordless Info -->
<div class="bg-blue-50 dark:bg-blue-900/20 p-4 rounded-lg border border-blue-200 dark:border-blue-800">
<div class="flex items-start gap-3">
<div class="space-y-1 flex-shrink-0 mt-1">
<div class="w-2 h-2 bg-blue-500 rounded-full" />
<div class="w-2 h-2 bg-blue-400 rounded-full" />
</div>
<div class="space-y-2 flex-1">
<div class="h-1 bg-blue-500 rounded-full w-full" />
<div class="h-1 bg-blue-300 rounded-full w-3/4" />
<div class="h-1 bg-blue-200 rounded-full w-1/2" />
</div>
</div>
<p class="text-blue-700 dark:text-blue-300 text-sm mt-3">
We'll send you a secure login link via email. No password needed!
</p>
</div>
<!-- Login Button -->
<div class="flex justify-center pt-4">
<UButton
type="submit"
:loading="isLoggingIn"
:disabled="!isLoginFormValid"
size="xl"
class="w-full"
>
Send Magic Link
</UButton>
</div>
</UForm>
<!-- Success/Error Messages -->
<div v-if="loginSuccess" class="mt-6 p-4 bg-green-50 dark:bg-green-900/20 rounded-lg border border-green-200 dark:border-green-800">
<p class="text-green-700 dark:text-green-300 text-center">
✅ Magic link sent! Check your email and click the link to sign in.
</p>
</div>
<div v-if="loginError" class="mt-6 p-4 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:border-red-800">
<p class="text-red-700 dark:text-red-300 text-center">
❌ {{ loginError }}
</p>
</div>
<!-- Sign Up Link -->
<div class="mt-6 text-center">
<p class="text-gray-600 dark:text-gray-400">
Don't have an account?
<NuxtLink to="/join" class="text-blue-600 dark:text-blue-400 hover:underline font-medium">
Join Ghost Guild
</NuxtLink>
</p>
</div>
</div>
</UContainer>
</section>
<!-- Forgot Password -->
<section id="forgot-password" class="py-20 bg-gray-50 dark:bg-gray-800">
<UContainer class="max-w-md">
<div class="text-center mb-12">
<h2 class="text-3xl font-bold text-blue-600 dark:text-blue-400 mb-4">
Forgot Password
</h2>
<p class="text-gray-600 dark:text-gray-300">
Enter your email to receive a password reset link
</p>
</div>
<div class="bg-white dark:bg-gray-800 rounded-2xl p-8 shadow-xl border border-blue-200 dark:border-blue-800">
<UForm :state="forgotPasswordForm" class="space-y-6" @submit="handleForgotPassword">
<!-- Email Field -->
<UFormField label="Email Address" name="email" required>
<UInput
v-model="forgotPasswordForm.email"
type="email"
size="xl"
class="w-full"
placeholder="your.email@example.com"
/>
</UFormField>
<!-- Reset Instructions -->
<div class="bg-blue-50 dark:bg-blue-900/20 p-4 rounded-lg border border-blue-200 dark:border-blue-800">
<div class="flex items-start gap-3">
<div class="space-y-1 flex-shrink-0 mt-1">
<div class="w-2 h-2 bg-blue-500 rounded-full" />
<div class="w-2 h-2 bg-blue-400 rounded-full" />
</div>
<div class="space-y-2 flex-1">
<div class="h-1 bg-blue-500 rounded-full w-full" />
<div class="h-1 bg-blue-300 rounded-full w-3/4" />
<div class="h-1 bg-blue-200 rounded-full w-1/2" />
</div>
</div>
<p class="text-blue-700 dark:text-blue-300 text-sm mt-3">
We'll send you a secure link to reset your password. Check your email inbox and spam folder.
</p>
</div>
<!-- Send Reset Link Button -->
<div class="flex justify-center pt-4">
<UButton
type="submit"
:loading="isResettingPassword"
:disabled="!forgotPasswordForm.email"
size="xl"
class="w-full"
variant="outline"
>
Send Reset Link
</UButton>
</div>
</UForm>
<!-- Success/Error Messages -->
<div v-if="resetSuccess" class="mt-6 p-4 bg-green-50 dark:bg-green-900/20 rounded-lg border border-green-200 dark:border-green-800">
<p class="text-green-700 dark:text-green-300 text-center">
✅ Password reset link sent! Check your email.
</p>
</div>
<div v-if="resetError" class="mt-6 p-4 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:border-red-800">
<p class="text-red-700 dark:text-red-300 text-center">
❌ {{ resetError }}
</p>
</div>
</div>
</UContainer>
</section>
<!-- Sign In CTA -->
<section class="py-20 bg-white dark:bg-gray-900">
<UContainer>
<div class="text-center max-w-2xl mx-auto">
<h2 class="text-3xl font-bold text-blue-600 dark:text-blue-400 mb-8">
Sign In
</h2>
<div class="space-y-4 mb-8">
<div class="h-2 bg-blue-500 rounded-full w-64 mx-auto" />
<div class="h-2 bg-blue-300 rounded-full w-48 mx-auto" />
</div>
<p class="text-lg text-gray-600 dark:text-gray-300 mb-8">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ready to access your account and connect with the community?
</p>
<UButton
@click="scrollToLoginForm"
size="xl"
color="primary"
class="px-12"
>
Login Now
</UButton>
</div>
</UContainer>
</section>
<!-- Access Your Dashboard -->
<section class="py-20 bg-blue-50 dark:bg-blue-900/20">
<UContainer>
<div class="text-center max-w-3xl mx-auto">
<h2 class="text-3xl font-bold text-blue-600 dark:text-blue-400 mb-8">
Access Your Dashboard
</h2>
<div class="space-y-3 mb-8">
<div class="h-2 bg-blue-500 rounded-full w-full max-w-lg mx-auto" />
<div class="h-2 bg-blue-400 rounded-full w-full max-w-md mx-auto" />
<div class="h-2 bg-blue-300 rounded-full w-full max-w-sm mx-auto" />
<div class="h-2 bg-blue-200 rounded-full w-full max-w-xs mx-auto" />
</div>
<div class="bg-white dark:bg-gray-800 rounded-2xl p-8 shadow-lg border border-blue-200 dark:border-blue-800 mb-8">
<p class="text-lg text-gray-600 dark:text-gray-300 mb-6">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Once you're logged in, you'll have access to:
</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 text-left">
<div class="space-y-2">
<div class="flex items-center gap-2">
<div class="w-2 h-2 bg-blue-500 rounded-full" />
<span class="text-gray-700 dark:text-gray-300">Community forums and discussions</span>
</div>
<div class="flex items-center gap-2">
<div class="w-2 h-2 bg-blue-400 rounded-full" />
<span class="text-gray-700 dark:text-gray-300">Member directory and networking</span>
</div>
<div class="flex items-center gap-2">
<div class="w-2 h-2 bg-blue-300 rounded-full" />
<span class="text-gray-700 dark:text-gray-300">Educational resources and workshops</span>
</div>
</div>
<div class="space-y-2">
<div class="flex items-center gap-2">
<div class="w-2 h-2 bg-emerald-500 rounded-full" />
<span class="text-gray-700 dark:text-gray-300">Cooperative development tools</span>
</div>
<div class="flex items-center gap-2">
<div class="w-2 h-2 bg-emerald-400 rounded-full" />
<span class="text-gray-700 dark:text-gray-300">Mentorship opportunities</span>
</div>
<div class="flex items-center gap-2">
<div class="w-2 h-2 bg-emerald-300 rounded-full" />
<span class="text-gray-700 dark:text-gray-300">Project collaboration spaces</span>
</div>
</div>
</div>
</div>
<div class="text-center">
<p class="text-gray-600 dark:text-gray-300 mb-4">
New to Ghost Guild?
</p>
<UButton
to="/join"
variant="outline"
size="lg"
class="px-8"
>
Create Your Account
</UButton>
</div>
</div>
</UContainer>
</section>
</div>
</template>
<script setup>
import { reactive, ref, computed } from 'vue'
// Login form state
const loginForm = reactive({
email: ''
})
// Forgot password form state
const forgotPasswordForm = reactive({
email: ''
})
// UI state
const isLoggingIn = ref(false)
const isResettingPassword = ref(false)
const loginSuccess = ref(false)
const loginError = ref('')
const resetSuccess = ref(false)
const resetError = ref('')
// Form validation
const isLoginFormValid = computed(() => {
return loginForm.email && loginForm.email.includes('@')
})
// Login handler
const handleLogin = async () => {
if (isLoggingIn.value) return
isLoggingIn.value = true
loginError.value = ''
loginSuccess.value = false
try {
// Call the passwordless login API
const response = await $fetch('/api/auth/login', {
method: 'POST',
body: {
email: loginForm.email
}
})
if (response.success) {
loginSuccess.value = true
loginError.value = ''
// Clear the form
loginForm.email = ''
}
} catch (err) {
console.error('Login error:', err)
// Handle different error types
if (err.statusCode === 404) {
loginError.value = 'No account found with that email address. Please check your email or create an account.'
} else if (err.statusCode === 500) {
loginError.value = 'Failed to send login email. Please try again later.'
} else {
loginError.value = err.statusMessage || 'Something went wrong. Please try again.'
}
} finally {
isLoggingIn.value = false
}
}
// Forgot password handler
const handleForgotPassword = async () => {
if (isResettingPassword.value) return
isResettingPassword.value = true
resetError.value = ''
resetSuccess.value = false
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1500))
resetSuccess.value = true
// Reset form after success
setTimeout(() => {
forgotPasswordForm.email = ''
resetSuccess.value = false
}, 5000)
} catch (err) {
console.error('Password reset error:', err)
resetError.value = 'Failed to send reset email. Please try again.'
} finally {
isResettingPassword.value = false
}
}
// Scroll functions
const scrollToLoginForm = () => {
const formSection = document.querySelector('form')
if (formSection) {
formSection.scrollIntoView({ behavior: 'smooth', block: 'center' })
}
}
const scrollToForgotPassword = () => {
const forgotSection = document.getElementById('forgot-password')
if (forgotSection) {
forgotSection.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}
</script>