RegisterForm Component
Complete registration and login form with tab navigation, validation, and Supabase integration. Features modern UI with dark mode support and comprehensive form handling.
Overview
The RegisterForm component provides a dual-purpose authentication interface with signup and login functionality in a single component. It includes form validation, error handling, loading states, and seamless integration with Supabase authentication.
Preview
Basic Usage
vue
<template>
<div class="auth-page">
<RegisterForm
title="Get Started"
subtitle="Create your account to begin"
/>
</div>
</template>
<script setup lang="ts">
import RegisterForm from '@/components/RegisterForm.vue'
</script>Features
🔄 Tab Navigation
- Seamless switching between signup and login
- Smooth tab transitions
- Visual active state indicators
📝 Form Validation
- Required field validation
- Email format validation
- Password strength requirements
- Real-time error feedback
🔐 Authentication Integration
- Supabase authentication
- Email/password signup and login
- Loading states during authentication
- Error handling and display
🎨 Modern UI Design
- Clean, professional styling
- Dark mode support
- Responsive design
- Custom input styling
Component Structure
vue
<template>
<div class="min-h-screen flex items-center justify-center p-4">
<div class="card-Clawplate w-full max-w-md p-8 space-y-6">
<!-- Tab Navigation -->
<div class="flex items-center bg-gray-900 rounded-lg p-1">
<button
v-for="tab in tabs"
@click="activeTab = tab.id"
:class="tabClasses(tab.id)"
>
{{ tab.label }}
</button>
</div>
<!-- Form Title -->
<div class="text-center">
<h1>{{ activeTab === 'signup' ? title : 'Welcome back' }}</h1>
<p>{{ subtitle }}</p>
</div>
<!-- Signup Form -->
<form v-if="activeTab === 'signup'" @submit.prevent="handleSignup">
<!-- Form fields -->
</form>
<!-- Login Form -->
<form v-else @submit.prevent="handleLogin">
<!-- Form fields -->
</form>
</div>
</div>
</template>Component API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | 'Create Account' | Main title for signup form |
subtitle | string | undefined | Optional subtitle text |
Events
| Event | Payload | Description |
|---|---|---|
signup-success | { user, session } | Emitted on successful signup |
login-success | { user, session } | Emitted on successful login |
auth-error | { error } | Emitted on authentication error |
Data Properties
| Property | Type | Description |
|---|---|---|
activeTab | 'signup' | 'login' | Currently active tab |
formData | object | Form input data |
isLoading | boolean | Loading state |
errors | object | Form validation errors |
Form Fields
Signup Form Fields
typescript
interface SignupFormData {
firstName: string
lastName: string
email: string
password: string
confirmPassword: string
acceptTerms: boolean
}Login Form Fields
typescript
interface LoginFormData {
email: string
password: string
rememberMe: boolean
}Form Validation
Validation Rules
typescript
const validateForm = () => {
const errors: any = {}
// Email validation
if (!formData.email) {
errors.email = 'Email is required'
} else if (!isValidEmail(formData.email)) {
errors.email = 'Please enter a valid email'
}
// Password validation
if (!formData.password) {
errors.password = 'Password is required'
} else if (formData.password.length < 8) {
errors.password = 'Password must be at least 8 characters'
}
// Confirm password (signup only)
if (activeTab.value === 'signup' && formData.password !== formData.confirmPassword) {
errors.confirmPassword = 'Passwords do not match'
}
return errors
}Error Display
vue
<div v-if="errors.email" class="text-red-500 text-xs mt-1">
{{ errors.email }}
</div>Authentication Logic
Signup Handler
typescript
const handleSignup = async () => {
isLoading.value = true
errors.value = {}
try {
const validationErrors = validateForm()
if (Object.keys(validationErrors).length > 0) {
errors.value = validationErrors
return
}
const { data, error } = await supabase.auth.signUp({
email: formData.email,
password: formData.password,
options: {
data: {
first_name: formData.firstName,
last_name: formData.lastName
}
}
})
if (error) {
errors.value.general = error.message
} else {
emit('signup-success', { user: data.user, session: data.session })
}
} catch (error) {
errors.value.general = 'An unexpected error occurred'
} finally {
isLoading.value = false
}
}Login Handler
typescript
const handleLogin = async () => {
isLoading.value = true
errors.value = {}
try {
const { data, error } = await supabase.auth.signInWithPassword({
email: formData.email,
password: formData.password
})
if (error) {
errors.value.general = error.message
} else {
emit('login-success', { user: data.user, session: data.session })
}
} catch (error) {
errors.value.general = 'Login failed. Please try again.'
} finally {
isLoading.value = false
}
}Styling Details
Card Styling
vue
<div class="card-Clawplate w-full max-w-md p-8 space-y-6">Custom card class with ClawPlate branding:
css
.card-Clawplate {
@apply bg-white dark:bg-gray-900 rounded-2xl shadow-xl border border-gray-200 dark:border-gray-700;
}Input Styling
vue
<input class="input-Clawplate w-full" />Custom input class:
css
.input-Clawplate {
@apply px-4 py-3 rounded-lg border border-gray-200 dark:border-gray-700
bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100
focus:ring-2 focus:ring-blue-500 focus:border-transparent
placeholder-gray-400 dark:placeholder-gray-500;
}Tab Navigation Styling
vue
<button :class="[
'flex-1 py-2 px-4 rounded-md text-sm font-medium transition-all duration-200',
activeTab === tab.id
? 'bg-blue-50 text-gray-950 border border-black'
: 'text-gray-400 hover:text-white'
]">Dark Mode Support
Theme Classes
vue
<div class="dark:bg-gray-50 dark:text-gray-900">
<!-- Light mode: default styles -->
<!-- Dark mode: dark: prefixed styles -->
</div>Input Dark Mode
vue
<input class="bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100" />Loading States
Submit Button Loading
vue
<button
type="submit"
:disabled="isLoading"
class="w-full py-3 px-4 rounded-lg font-medium transition-all"
:class="isLoading ? 'bg-gray-400 cursor-not-allowed' : 'bg-blue-600 hover:bg-blue-700'"
>
<span v-if="isLoading" class="flex items-center justify-center">
<Icon name="loading-spinner" class="animate-spin mr-2" />
{{ activeTab === 'signup' ? 'Creating Account...' : 'Signing In...' }}
</span>
<span v-else>
{{ activeTab === 'signup' ? 'Create Account' : 'Sign In' }}
</span>
</button>Customization
Custom Validation Rules
typescript
const customValidation = {
email: (value: string) => {
if (!value) return 'Email is required'
if (!value.includes('@')) return 'Please enter a valid email'
return null
},
password: (value: string) => {
if (!value) return 'Password is required'
if (value.length < 8) return 'Password must be at least 8 characters'
if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) {
return 'Password must contain uppercase, lowercase, and number'
}
return null
}
}Custom Styling Theme
vue
<!-- Purple theme -->
<div class="bg-purple-50 border-purple-200">
<button class="bg-purple-600 hover:bg-purple-700">
<!-- Green theme -->
<div class="bg-green-50 border-green-200">
<button class="bg-green-600 hover:bg-green-700">Additional Form Fields
vue
<!-- Company field for signup -->
<div v-if="activeTab === 'signup'">
<label class="block text-xs text-gray-400 mb-1">Company (Optional)</label>
<input
v-model="formData.company"
type="text"
class="input-Clawplate w-full"
placeholder="Your company"
>
</div>Integration Examples
With Router Navigation
vue
<script setup lang="ts">
const router = useRouter()
const handleSignupSuccess = ({ user, session }) => {
// Redirect to dashboard after successful signup
router.push('/dashboard')
}
const handleLoginSuccess = ({ user, session }) => {
// Redirect to appropriate page after login
const redirectTo = router.currentRoute.value.query.redirect || '/dashboard'
router.push(redirectTo)
}
</script>With Toast Notifications
vue
<script setup lang="ts">
const { toast } = useToast()
const handleAuthError = ({ error }) => {
toast.error('Authentication Error', error.message)
}
const handleSignupSuccess = () => {
toast.success('Account Created', 'Welcome to ClawPlate!')
}
</script>Accessibility
Form Labels
vue
<label for="email" class="block text-xs text-gray-400 mb-1">
Email Address
</label>
<input
id="email"
v-model="formData.email"
type="email"
aria-describedby="email-error"
aria-required="true"
/>
<div id="email-error" v-if="errors.email" class="text-red-500 text-xs mt-1">
{{ errors.email }}
</div>Keyboard Navigation
- Tab order follows logical flow
- Enter key submits forms
- Escape key can close modals
Screen Reader Support
vue
<div role="tablist" aria-label="Authentication options">
<button
role="tab"
:aria-selected="activeTab === 'signup'"
:tabindex="activeTab === 'signup' ? 0 : -1"
>
Sign Up
</button>
</div>Related Components
- ContactForm - Contact form component
- OAuthButtons - Social authentication
- Toast - Form feedback notifications
Dependencies
- Supabase - Authentication service
- Vue 3 - Composition API
- Tailwind CSS - Styling framework
- Nuxt Icons - Loading and form icons
