Enhance application structure: Add runtime configuration for environment variables, integrate new dependencies for Cloudinary and UI components, and refactor member management features including improved forms and member dashboard. Update styles and layout for better user experience.
This commit is contained in:
parent
6e7e27ac4e
commit
e4a0a9ab0f
61 changed files with 7902 additions and 950 deletions
377
app/pages/login.vue
Normal file
377
app/pages/login.vue
Normal file
|
|
@ -0,0 +1,377 @@
|
|||
<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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue