Skip to content

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

Register Form Component 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

PropTypeDefaultDescription
titlestring'Create Account'Main title for signup form
subtitlestringundefinedOptional subtitle text

Events

EventPayloadDescription
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

PropertyTypeDescription
activeTab'signup' | 'login'Currently active tab
formDataobjectForm input data
isLoadingbooleanLoading state
errorsobjectForm 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>

Dependencies

  • Supabase - Authentication service
  • Vue 3 - Composition API
  • Tailwind CSS - Styling framework
  • Nuxt Icons - Loading and form icons

Built with love by mhdevfr