Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions docs/email-verification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Email Verification System

This document describes the email verification system implemented for the SwapTrade waitlist signup process.

## Overview

The email verification system ensures that users provide valid email addresses and helps prevent spam signups. When users sign up for the waitlist, they receive a verification email with a unique token that they must click to activate their entry.

## Features

- **Secure Token Generation**: Uses JWT or random UUID for verification tokens
- **Token Expiration**: Tokens expire after a configurable period (default: 24 hours)
- **Email Templates**: Branded email templates matching the SwapTrade design
- **Error Handling**: Graceful handling of expired or invalid tokens
- **Resend Functionality**: Users can request new verification emails
- **Status Tracking**: User verification status is tracked in the database

## Components

### Frontend Components

#### Pages
- `/signup` - Waitlist signup form
- `/verify` - Email verification page
- `/resend-verification` - Resend verification email page

#### Components
- `VerificationStatus` - Displays verification status with appropriate icons and messages

#### Hooks
- `useEmailVerification` - Custom hook for managing verification state and API calls

#### Types
- `WaitlistUser` - User data structure
- `VerificationToken` - Token data structure
- `EmailVerificationRequest/Response` - API request/response types

### API Endpoints (Backend)

The following API endpoints need to be implemented in the backend:

#### POST `/api/waitlist/signup`
- Accepts: `{ email: string }`
- Sends verification email with unique token
- Returns: Success/error message

#### POST `/api/verify-email`
- Accepts: `{ token: string }`
- Validates token and updates user status to "verified"
- Returns: Success/error message

#### POST `/api/resend-verification`
- Accepts: `{ email: string }`
- Sends new verification email
- Returns: Success/error message

## User Flow

1. User visits `/signup` and enters their email
2. System generates verification token and sends email
3. User receives email with verification link: `/verify?token=<token>`
4. User clicks link, system validates token
5. If valid, user status becomes "verified"
6. If expired/invalid, user can request new verification email

## Email Template

The verification email should include:
- SwapTrade branding
- Clear call-to-action button
- Verification link with token
- Expiration notice
- Contact information for support

## Security Considerations

- Tokens should be cryptographically secure
- Tokens should expire after reasonable time (24 hours)
- Rate limiting on signup and resend endpoints
- Input validation and sanitization
- HTTPS required for all verification links

## Error Handling

- Invalid tokens: Display error message with option to resend
- Expired tokens: Display expiration message with resend option
- Network errors: Retry mechanism and user-friendly messages
- Duplicate signups: Handle gracefully without sending multiple emails

## Integration

### Email Service Provider
- SendGrid, Mailgun, or similar service
- Template management for branded emails
- Delivery tracking and analytics

### Database Schema
```sql
-- Users table
CREATE TABLE waitlist_users (
id UUID PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
status ENUM('pending', 'verified', 'expired') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
verified_at TIMESTAMP NULL
);

-- Verification tokens table
CREATE TABLE verification_tokens (
token VARCHAR(255) PRIMARY KEY,
email VARCHAR(255) NOT NULL,
expires_at TIMESTAMP NOT NULL,
used BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```

## Testing

- Unit tests for token generation and validation
- Integration tests for email sending
- E2E tests for complete verification flow
- Edge cases: expired tokens, invalid tokens, network failures

## Future Enhancements

- Email preference management
- Bulk verification for enterprise users
- Analytics and conversion tracking
- Multi-language support for emails
- Advanced spam prevention measures
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"@heroicons/react": "^2.2.0",
"next": "15.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
Expand Down
115 changes: 115 additions & 0 deletions src/app/resend-verification/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
'use client';

import { useState } from 'react';
import Link from 'next/link';
import { useEmailVerification } from '@/hooks/useEmailVerification';
import VerificationStatus from '@/components/verification/VerificationStatus';

export default function ResendVerificationPage() {
const [email, setEmail] = useState('');
const { isLoading, resendVerification } = useEmailVerification();
const [message, setMessage] = useState('');
const [messageType, setMessageType] = useState<'success' | 'error'>('success');

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setMessage('');

const result = await resendVerification(email);

setMessageType(result.success ? 'success' : 'error');
setMessage(result.message);

if (result.success) {
setEmail('');
}
};

return (
<div className="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<div className="text-center">
<Link href="/" className="text-2xl font-bold text-[#16a34a]">
SwapTrade
</Link>
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
Resend Verification Email
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Enter your email address to receive a new verification link
</p>
</div>
</div>

<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<form className="space-y-6" onSubmit={handleSubmit}>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
Email address
</label>
<div className="mt-1">
<input
id="email"
name="email"
type="email"
autoComplete="email"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md placeholder-gray-400 focus:outline-none focus:ring-[#16a34a] focus:border-[#16a34a] sm:text-sm"
placeholder="Enter your email"
/>
</div>
</div>

<div>
<button
type="submit"
disabled={isLoading}
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-[#16a34a] hover:bg-[#15803d] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#16a34a] disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLoading ? 'Sending...' : 'Send Verification Email'}
</button>
</div>
</form>

{message && (
<div className="mt-4">
<VerificationStatus
status={messageType === 'success' ? 'verified' : 'error'}
message={message}
/>
</div>
)}

<div className="mt-6">
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300" />
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-white text-gray-500">Remember your verification link?</span>
</div>
</div>

<div className="mt-6 text-center space-y-2">
<Link
href="/signup"
className="text-sm text-[#16a34a] hover:text-[#15803d] block"
>
Back to Signup
</Link>
<Link
href="/"
className="text-sm text-gray-600 hover:text-gray-800 block"
>
Back to Home
</Link>
</div>
</div>
</div>
</div>
</div>
);
}
Loading