A complete, self-contained waitlist component with email collection, rate limiting, spam protection, and database management.
This package contains two separate components:
- WaitlistComponent - Complete email collection form with built-in rate limiting
- RateLimitComponent - Standalone rate limiting that can be used independently
- âś… Email Validation - Client and server-side validation
- âś… Rate Limiting - Prevents abuse with configurable time windows
- âś… Spam Protection - Honeypot field to catch bots
- âś… Database Management - SQLite databases for waitlist and rate limiting
- âś… IP Detection - Handles proxies and load balancers
- âś… Responsive Design - Mobile-friendly styling
- âś… Animation Effects - Smooth shimmer and fade animations
- âś… Self-Contained - All assets and data within component folder
$ git submodule add https://github.com/ediril/collectiq.git
$ git submodule add https://github.com/ediril/collectiq.git <custom folder>
Basic Integration:
<?php require_once 'collectiq/components/waitlist/WaitlistComponent.php'; ?>
<!DOCTYPE html>
<html>
<head>
<!-- Put this before the app CSS in case you need to override its values -->
<link rel="stylesheet" href="/collectiq/components/waitlist/assets/waitlist.css">
</head>
<body>
<?php
$waitlist = new WaitlistComponent();
echo $waitlist->renderForm();
?>
<script src="/collectiq/components/waitlist/assets/waitlist.js"></script>
</body>
</html><?php require_once 'collectiq/components/waitlist/WaitlistComponent.php'; ?>
<!DOCTYPE html>
<html>
<head>
<?php
$waitlist = new WaitlistComponent();
echo $waitlist->renderStyles(); // Auto-versioned CSS
?>
</head>
<body>
<?php echo $waitlist->renderForm(); ?>
<?php echo $waitlist->renderScripts(); // Auto-versioned JS ?>
</body>
</html><?php
require_once 'collectiq/component/WaitlistComponent.php';
$waitlist = new WaitlistComponent();
echo $waitlist->renderForm('waitlist-form', 'Enter your email...', 'Join', true);
?>Make sure /collectiq/components/waitlist/endpoint.php is accessible from your web root.
The component includes automatic cache busting to ensure browsers load updated CSS/JS files:
renderStyles()- Outputs CSS with content-based hash (e.g.,waitlist.css?v=abc12345)renderScripts()- Outputs JS with content-based hash (e.g.,waitlist.js?v=def67890)- Hash only changes when file content actually changes
- Works with Apache, Nginx, and development servers
- Better caching - Browsers cache until content actually changes
- No stale files - Users always get the latest version after updates
- Automatic detection - Works with different server configurations
- No manual versioning - Hash updates automatically
$ git submodule update --remote
$waitlist = new WaitlistComponent();
// Custom form with different text
echo $waitlist->renderForm(
'Enter your email address...', // Placeholder text
'Subscribe Now' // Button text
);$waitlist = new WaitlistComponent(
'/custom/path/waitlist.db', # Waitlist database
'/custom/path/rate_limit.db', # Rate limit database
120 # Rate limit window (seconds)
);<!-- Form 1: Newsletter -->
<div id="newsletter-section">
<?php echo $waitlist->renderForm('Newsletter signup...', 'Subscribe'); ?>
</div>
<!-- Form 2: Beta Access -->
<div id="beta-section">
<?php echo $waitlist->renderForm('Get beta access...', 'Join Beta'); ?>
</div>
<!-- All forms are automatically initialized -->Use the component as a pure API:
fetch('/collectiq/components/waitlist/endpoint.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'user@example.com' })
})
.then(response => response.json())
.then(data => console.log(data));Use the rate limiting functionality independently:
<?php
require_once 'collectiq/components/ratelimit/RateLimitComponent.php';
// Initialize with custom settings
$rateLimit = new RateLimitComponent('/path/to/rate_limit.db', 60); // 60 second window
// Check if request is allowed
if ($rateLimit->checkRateLimit()) {
// Process the request
echo "Request allowed";
} else {
// Rate limit exceeded
http_response_code(429);
echo "Too many requests";
}$rateLimit = new RateLimitComponent($dbPath, $window);
// Set rate limit window
$rateLimit->setWindow(120); // 2 minutes
// Get current window setting
$window = $rateLimit->getWindow();
// Check rate limit for current IP
$allowed = $rateLimit->checkRateLimit();
// Check rate limit for specific IP
$allowed = $rateLimit->checkRateLimit('192.168.1.100');
// Get remaining time until next request allowed
$seconds = $rateLimit->getRemainingTime();
// Create database table (automatic, but can be called manually)
$rateLimit->createDatabaseTable();- IP-based limiting - Tracks requests per IP address
- Configurable windows - Set custom time windows (seconds)
- Automatic cleanup - Removes expired entries
- Proxy support - Handles X-Forwarded-For headers
- Manual IP testing - Test rate limits for specific IPs
- Remaining time - Check how long until next request allowed
Default: 1 request per 60 seconds per IP address
The rate limit prevents spam and abuse while allowing legitimate users to retry if needed.
// Constructor: new WaitlistComponent($dbPath, $rateLimitDbPath, $windowSeconds)
// 30 seconds (more permissive)
$waitlist = new WaitlistComponent(null, null, 30);
// 2 minutes (default recommended)
$waitlist = new WaitlistComponent(null, null, 120);
// 5 minutes (stricter)
$waitlist = new WaitlistComponent(null, null, 300);
// 10 minutes (very strict)
$waitlist = new WaitlistComponent(null, null, 600);Update your component initialization:
// In index.php or wherever you create the component
// Change from:
$waitlist = new WaitlistComponent();
// To (for 2-minute rate limit):
$waitlist = new WaitlistComponent(null, null, 120);- Tracks requests per IP address
- Returns
{"success": false, "message": "too many requests"}when exceeded - Automatically cleans up old entries
- Works with proxy headers (X-Forwarded-For)
By default, databases are stored in components/*/data/. To use custom paths:
$waitlist = new WaitlistComponent(
__DIR__ . '/custom/waitlist.db',
__DIR__ . '/custom/rate_limit.db'
);{
"success": true
}// Rate limited
{
"success": false,
"message": "too many requests"
}
// Invalid email
{
"success": false,
"message": "Invalid email address"
}
// Bot detected (honeypot)
{
"success": true,
"message": "not human"
}CREATE TABLE waitlist (
email TEXT PRIMARY KEY,
ip TEXT,
ts INTEGER DEFAULT (unixepoch()),
dt TEXT GENERATED ALWAYS AS (datetime(ts, 'unixepoch')) STORED
);CREATE TABLE rate_limit (
ip TEXT PRIMARY KEY,
window_start INTEGER
);The component uses CSS classes prefixed with collectiq- to avoid conflicts with your existing styles. You can override these styles in your own CSS:
/* Override the main form container */
.collectiq-waitlist-form {
max-width: 400px;
gap: 0.5rem;
/* Your custom styles */
}
/* Override the input container */
.collectiq-input-container {
border: 2px solid #your-color;
border-radius: 10px;
/* Custom input styling */
}
/* Override the submit button */
.collectiq-submit-btn {
background: linear-gradient(45deg, #your-color1, #your-color2);
border-radius: 25px;
font-size: 1.2rem;
/* Custom button styling */
}
/* Override button hover state */
.collectiq-submit-btn:hover {
background: linear-gradient(45deg, #your-hover-color1, #your-hover-color2);
transform: translateY(-2px);
}
/* Override button active state */
.collectiq-submit-btn:active {
background: linear-gradient(45deg, #your-active-color1, #your-active-color2);
transform: translateY(0px);
}
/* Override button text */
.collectiq-submit-btn span {
font-weight: bold;
text-transform: uppercase;
}
/* Override input field */
.collectiq-input {
font-size: 1.1rem;
color: #your-text-color;
/* Custom input field styling */
}
/* Override placeholder text */
.collectiq-input::placeholder {
color: rgba(0, 0, 0, 0.6); /* Dark placeholder for light backgrounds */
}
/* Override success message - may need !important for some properties */
.collectiq-input-container.collectiq-thank-you {
background-color: #your-success-color !important;
color: #your-success-text-color !important;
/* Custom success message styling */
}.collectiq-waitlist-form- Main form container.collectiq-input-container- Input wrapper with border effects.collectiq-input- Email input field.collectiq-submit-btn- Submit button.collectiq-shimmer-container- Button shimmer effect container.collectiq-shimmer- Animated shimmer effect.collectiq-highlight- Button highlight effect.collectiq-backdrop- Button backdrop.collectiq-thank-you- Success message styling
.collectiq-waitlist-form button {
background: linear-gradient(135deg, #ff6b6b, #ee5a24);
}
.collectiq-waitlist-form button:hover .collectiq-highlight {
box-shadow: inset 0 -6px 10px rgba(255, 255, 255, 0.4);
}.collectiq-input-container {
border: none;
background: rgba(255, 255, 255, 0.1);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.collectiq-waitlist-form input {
padding: 1.2rem;
font-size: 1.1rem;
}/* Disable shimmer effect */
.collectiq-submit-btn .collectiq-shimmer-container {
display: none;
}
/* Disable backdrop effect */
.collectiq-submit-btn .collectiq-backdrop {
display: none;
}
/* Disable highlight effect on hover */
.collectiq-submit-btn:hover .collectiq-highlight {
box-shadow: none;
}
/* Disable highlight effect on active */
.collectiq-submit-btn:active .collectiq-highlight {
box-shadow: none;
}
/* Disable all button effects at once */
.collectiq-submit-btn .collectiq-shimmer-container,
.collectiq-submit-btn .collectiq-backdrop,
.collectiq-submit-btn .collectiq-highlight {
display: none;
}The default placeholder color is designed for dark backgrounds. For light backgrounds, you'll need to override it:
/* Default (for dark backgrounds) */
.collectiq-waitlist-form input::placeholder {
color: rgba(255, 255, 255, 0.4); /* Light white */
}
/* For light backgrounds */
.collectiq-waitlist-form input::placeholder {
color: rgba(0, 0, 0, 0.6); /* Dark gray */
}
/* For specific forms */
#my-form .collectiq-waitlist-form input::placeholder {
color: #666;
}Extend the WaitlistHandler class:
class CustomWaitlistHandler extends WaitlistHandler {
showSuccessMessage(form) {
// Custom success message
alert('Welcome to our waitlist!');
}
}
// Use custom handler
new CustomWaitlistHandler('my-form');- Rate Limiting - Prevents spam and abuse
- Honeypot Protection - Hidden field catches bots
- Email Validation - Server-side validation
- IP Tracking - Logs IP addresses for monitoring
- SQL Injection Protection - Prepared statements
- Proxy Support - Handles X-Forwarded-For headers
- Ensure PHP SQLite extension is installed
- Make
components/*/data/writable by web server - Database files are created automatically on first use
- Consider backing up the
data/folder regularly
- Check that
waitlist.jsis loaded - Verify endpoint URL is correct
- Check browser console for JavaScript errors
- Ensure
data/directory is writable - Check PHP SQLite extension is installed
- Verify file permissions
- Check server system time is correct
- Verify IP detection is working properly
- Consider adjusting rate limit window