memberstack-ai-context
Version:
AI context server for Memberstack DOM documentation - provides intelligent access to Memberstack docs for Claude Code, Cursor, and other AI coding assistants
975 lines (797 loc) • 28.5 kB
Markdown
# Memberstack DOM - Member Journey & Lifecycle
## AI Assistant Instructions
When implementing member journey flows:
- Use `sendMemberVerificationEmail()` for email verification
- Use `sendMemberResetPasswordEmail()` and `resetMemberPassword()` for password reset
- Include `onAuthChange()` callback for reactive UI updates
- Handle URL parameters for tokens (verification, reset, passwordless)
- Show appropriate UI states for unverified members
- Include proper error handling for email delivery failures
## Overview
Member journey management includes email verification, password reset workflows, authentication state changes, and member lifecycle events. These flows ensure secure and user-friendly experiences throughout the member's relationship with your application.
## Email Verification
### sendMemberVerificationEmail()
Send an email verification to the currently authenticated member.
**Method Signature:**
```typescript
await memberstack.sendMemberVerificationEmail(): Promise<SendMemberVerificationEmailPayload>
```
**Response:**
```typescript
{
data: {
success: boolean;
message: string;
}
}
```
**Examples:**
Basic Email Verification:
```javascript
async function sendVerificationEmail() {
try {
const result = await memberstack.sendMemberVerificationEmail();
console.log('Verification email sent:', result.data);
return {
success: true,
message: 'Verification email sent! Please check your inbox.'
};
} catch (error) {
console.error('Failed to send verification email:', error);
return {
success: false,
message: 'Failed to send verification email. Please try again.'
};
}
}
// Verification button handler
document.getElementById('send-verification-btn').addEventListener('click', async () => {
const result = await sendVerificationEmail();
const messageEl = document.getElementById('verification-message');
messageEl.textContent = result.message;
messageEl.className = result.success ? 'message success' : 'message error';
messageEl.style.display = 'block';
if (result.success) {
// Disable button temporarily to prevent spam
const btn = document.getElementById('send-verification-btn');
btn.disabled = true;
btn.textContent = 'Email Sent';
setTimeout(() => {
btn.disabled = false;
btn.textContent = 'Resend Verification Email';
}, 60000); // Re-enable after 1 minute
}
});
```
Verification Status Checker:
```javascript
class EmailVerificationManager {
constructor() {
this.memberstack = window.$memberstackDom;
this.checkInterval = null;
this.init();
}
async init() {
await this.checkVerificationStatus();
this.setupVerificationUI();
this.startPeriodicCheck();
}
async checkVerificationStatus() {
try {
const member = await this.memberstack.getCurrentMember({ useCache: false });
if (member.data) {
this.updateVerificationUI(member.data.verified);
return member.data.verified;
}
return false;
} catch (error) {
console.error('Failed to check verification status:', error);
return false;
}
}
updateVerificationUI(isVerified) {
const verificationBanner = document.getElementById('verification-banner');
const verifiedBadge = document.getElementById('verified-badge');
const sendVerificationBtn = document.getElementById('send-verification-btn');
if (isVerified) {
verificationBanner?.classList.add('hidden');
verifiedBadge?.classList.remove('hidden');
sendVerificationBtn?.classList.add('hidden');
this.stopPeriodicCheck();
} else {
verificationBanner?.classList.remove('hidden');
verifiedBadge?.classList.add('hidden');
sendVerificationBtn?.classList.remove('hidden');
}
}
setupVerificationUI() {
// Send verification email button
document.getElementById('send-verification-btn')?.addEventListener('click',
() => this.handleSendVerification()
);
// Resend with cooldown
document.getElementById('resend-verification-btn')?.addEventListener('click',
() => this.handleResendVerification()
);
}
async handleSendVerification() {
try {
const result = await this.memberstack.sendMemberVerificationEmail();
this.showMessage('Verification email sent! Please check your inbox.', 'success');
this.startCooldown();
} catch (error) {
this.showMessage('Failed to send verification email. Please try again.', 'error');
}
}
async handleResendVerification() {
const confirmed = confirm('Send another verification email?');
if (confirmed) {
await this.handleSendVerification();
}
}
startCooldown(duration = 60000) {
const btn = document.getElementById('send-verification-btn');
if (!btn) return;
btn.disabled = true;
let secondsLeft = duration / 1000;
const countdown = setInterval(() => {
btn.textContent = `Resend in ${secondsLeft}s`;
secondsLeft--;
if (secondsLeft < 0) {
clearInterval(countdown);
btn.disabled = false;
btn.textContent = 'Resend Verification Email';
}
}, 1000);
}
startPeriodicCheck() {
// Check every 30 seconds if user has verified their email
this.checkInterval = setInterval(() => {
this.checkVerificationStatus();
}, 30000);
}
stopPeriodicCheck() {
if (this.checkInterval) {
clearInterval(this.checkInterval);
this.checkInterval = null;
}
}
showMessage(message, type) {
const messageEl = document.getElementById('verification-message');
if (messageEl) {
messageEl.textContent = message;
messageEl.className = `message ${type}`;
messageEl.style.display = 'block';
setTimeout(() => {
messageEl.style.display = 'none';
}, 5000);
}
}
}
// Initialize verification manager for logged-in users
document.addEventListener('DOMContentLoaded', () => {
memberstack.onAuthChange(({ member }) => {
if (member && !member.verified) {
new EmailVerificationManager();
}
});
});
```
## Password Reset Flow
### sendMemberResetPasswordEmail()
Send a password reset email to a specified email address.
**Method Signature:**
```typescript
await memberstack.sendMemberResetPasswordEmail({
email: string;
}): Promise<SendMemberResetPasswordEmailPayload>
```
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| email | string | ✅ | Email address to send reset link to |
**Examples:**
Password Reset Request:
```javascript
async function sendPasswordReset(email) {
try {
const result = await memberstack.sendMemberResetPasswordEmail({
email: email.trim().toLowerCase()
});
console.log('Password reset email sent:', result.data);
return {
success: true,
message: 'Password reset email sent! Please check your inbox and follow the instructions.'
};
} catch (error) {
console.error('Password reset failed:', error);
// Don't reveal if email exists for security
return {
success: true, // Always show success to prevent email enumeration
message: 'If an account with this email exists, you will receive a password reset link.'
};
}
}
// Forgot password form
document.getElementById('forgot-password-form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const email = formData.get('email');
if (!email) {
alert('Please enter your email address');
return;
}
// Show loading state
const submitBtn = e.target.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.textContent = 'Sending...';
try {
const result = await sendPasswordReset(email);
// Show success message
document.getElementById('reset-success').textContent = result.message;
document.getElementById('reset-success').style.display = 'block';
// Hide form and show success state
e.target.style.display = 'none';
} finally {
submitBtn.disabled = false;
submitBtn.textContent = originalText;
}
});
```
### resetMemberPassword()
Complete the password reset using a token from the reset email.
**Method Signature:**
```typescript
await memberstack.resetMemberPassword({
token: string;
newPassword: string;
}): Promise<ResetMemberPasswordPayload>
```
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| token | string | ✅ | Password reset token from email |
| newPassword | string | ✅ | New password for the member |
**Examples:**
Password Reset Completion:
```javascript
async function completePasswordReset(token, newPassword) {
try {
const result = await memberstack.resetMemberPassword({
token,
newPassword
});
console.log('Password reset completed:', result.data);
return {
success: true,
message: 'Password reset successfully! You can now log in with your new password.'
};
} catch (error) {
console.error('Password reset failed:', error);
const errorMessages = {
'INVALID_TOKEN': 'This password reset link is invalid or has expired. Please request a new one.',
'EXPIRED_TOKEN': 'This password reset link has expired. Please request a new one.',
'WEAK_PASSWORD': 'Password is too weak. Please choose a stronger password.',
'TOKEN_ALREADY_USED': 'This password reset link has already been used.'
};
return {
success: false,
message: errorMessages[error.code] || 'Password reset failed. Please try again.'
};
}
}
// Password reset page handler
class PasswordResetHandler {
constructor() {
this.token = this.getTokenFromURL();
this.init();
}
getTokenFromURL() {
const params = new URLSearchParams(window.location.search);
return params.get('token');
}
init() {
if (!this.token) {
this.showInvalidTokenMessage();
return;
}
this.setupForm();
}
showInvalidTokenMessage() {
document.getElementById('reset-form').style.display = 'none';
document.getElementById('invalid-token').style.display = 'block';
}
setupForm() {
const form = document.getElementById('reset-password-form');
form.addEventListener('submit', async (e) => {
e.preventDefault();
await this.handleFormSubmit(e);
});
// Password confirmation validation
const newPassword = document.getElementById('newPassword');
const confirmPassword = document.getElementById('confirmPassword');
confirmPassword.addEventListener('input', () => {
if (newPassword.value !== confirmPassword.value) {
confirmPassword.setCustomValidity('Passwords do not match');
} else {
confirmPassword.setCustomValidity('');
}
});
}
async handleFormSubmit(event) {
const formData = new FormData(event.target);
const newPassword = formData.get('newPassword');
const confirmPassword = formData.get('confirmPassword');
if (newPassword !== confirmPassword) {
alert('Passwords do not match');
return;
}
if (newPassword.length < 8) {
alert('Password must be at least 8 characters long');
return;
}
// Show loading state
const submitBtn = event.target.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.textContent = 'Resetting Password...';
try {
const result = await completePasswordReset(this.token, newPassword);
if (result.success) {
this.showSuccessMessage(result.message);
} else {
this.showErrorMessage(result.message);
}
} finally {
submitBtn.disabled = false;
submitBtn.textContent = originalText;
}
}
showSuccessMessage(message) {
document.getElementById('reset-form').style.display = 'none';
const successEl = document.getElementById('reset-success');
successEl.textContent = message;
successEl.style.display = 'block';
// Redirect to login after delay
setTimeout(() => {
window.location.href = '/login?message=password-reset-complete';
}, 3000);
}
showErrorMessage(message) {
const errorEl = document.getElementById('reset-error');
errorEl.textContent = message;
errorEl.style.display = 'block';
setTimeout(() => {
errorEl.style.display = 'none';
}, 10000);
}
}
// Initialize on password reset page
document.addEventListener('DOMContentLoaded', () => {
if (window.location.pathname.includes('/reset-password')) {
new PasswordResetHandler();
}
});
```
## Passwordless Authentication Flow
### sendMemberLoginPasswordlessEmail()
Send a passwordless login email (magic link).
**Example with Complete Flow:**
```javascript
class PasswordlessLoginManager {
constructor() {
this.memberstack = window.$memberstackDom;
this.init();
}
init() {
// Handle passwordless login request
this.setupPasswordlessForm();
// Handle passwordless login completion (from email link)
this.handlePasswordlessCallback();
}
setupPasswordlessForm() {
const form = document.getElementById('passwordless-form');
if (!form) return;
form.addEventListener('submit', async (e) => {
e.preventDefault();
await this.sendPasswordlessEmail(e);
});
}
async sendPasswordlessEmail(event) {
const formData = new FormData(event.target);
const email = formData.get('email');
if (!email) {
this.showMessage('Please enter your email address', 'error');
return;
}
const submitBtn = event.target.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.textContent = 'Sending...';
try {
const result = await this.memberstack.sendMemberLoginPasswordlessEmail({
email: email.trim().toLowerCase()
});
console.log('Passwordless email sent:', result.data);
this.showSuccessState(email);
} catch (error) {
console.error('Passwordless email failed:', error);
this.showMessage('Failed to send login link. Please try again.', 'error');
} finally {
submitBtn.disabled = false;
submitBtn.textContent = originalText;
}
}
showSuccessState(email) {
document.getElementById('passwordless-form').style.display = 'none';
const successEl = document.getElementById('passwordless-success');
successEl.innerHTML = `
<div class="success-message">
<h3>Check Your Email</h3>
<p>We've sent a login link to <strong>${email}</strong></p>
<p>Click the link in your email to sign in instantly.</p>
<div class="help-text">
<p>Didn't receive the email?</p>
<button id="resend-passwordless" class="btn-link">Send another link</button>
<button id="use-password" class="btn-link">Use password instead</button>
</div>
</div>
`;
successEl.style.display = 'block';
// Setup help actions
document.getElementById('resend-passwordless').addEventListener('click', () => {
document.getElementById('passwordless-form').style.display = 'block';
successEl.style.display = 'none';
});
document.getElementById('use-password').addEventListener('click', () => {
window.location.href = '/login';
});
}
async handlePasswordlessCallback() {
const params = new URLSearchParams(window.location.search);
const token = params.get('token');
const email = params.get('email');
if (!token || !email) return;
this.showMessage('Completing login...', 'info');
try {
const result = await this.memberstack.loginMemberPasswordless({
passwordlessToken: token,
email: email
});
console.log('Passwordless login successful:', result.data.member);
this.showMessage('Login successful! Redirecting...', 'success');
// Redirect after short delay
setTimeout(() => {
const redirect = params.get('redirect') || '/dashboard';
window.location.href = redirect;
}, 1500);
} catch (error) {
console.error('Passwordless login failed:', error);
const errorMessages = {
'INVALID_TOKEN': 'This login link is invalid or has expired.',
'EXPIRED_TOKEN': 'This login link has expired. Please request a new one.',
'TOKEN_ALREADY_USED': 'This login link has already been used.'
};
this.showMessage(
errorMessages[error.code] || 'Login failed. Please try again.',
'error'
);
// Redirect to login page after error
setTimeout(() => {
window.location.href = '/login';
}, 3000);
}
}
showMessage(message, type) {
const messageEl = document.getElementById('passwordless-message');
if (messageEl) {
messageEl.textContent = message;
messageEl.className = `message ${type}`;
messageEl.style.display = 'block';
}
}
}
// Initialize passwordless manager
document.addEventListener('DOMContentLoaded', () => {
new PasswordlessLoginManager();
});
```
## Authentication State Management
### onAuthChange()
Handle real-time authentication state changes throughout the member journey.
**Advanced State Management Example:**
```javascript
class MemberJourneyManager {
constructor() {
this.memberstack = window.$memberstackDom;
this.currentMember = null;
this.journeyState = 'anonymous';
this.setupAuthListener();
}
setupAuthListener() {
this.memberstack.onAuthChange(({ member }) => {
const previousMember = this.currentMember;
this.currentMember = member;
this.updateJourneyState(member, previousMember);
this.handleStateTransition(member, previousMember);
});
}
updateJourneyState(member, previousMember) {
if (!member) {
this.journeyState = 'anonymous';
} else if (!member.verified) {
this.journeyState = 'unverified';
} else if (!member.planConnections?.length) {
this.journeyState = 'verified_free';
} else {
this.journeyState = 'verified_paid';
}
console.log('Journey state changed to:', this.journeyState);
}
handleStateTransition(member, previousMember) {
// Handle login
if (!previousMember && member) {
this.handleMemberLogin(member);
}
// Handle logout
if (previousMember && !member) {
this.handleMemberLogout(previousMember);
}
// Handle verification status change
if (member && previousMember) {
if (!previousMember.verified && member.verified) {
this.handleEmailVerified(member);
}
}
// Update UI for current state
this.updateJourneyUI();
}
handleMemberLogin(member) {
console.log('Member logged in:', member.email);
// Show welcome message
const welcomeMessage = member.customFields?.firstName
? `Welcome back, ${member.customFields.firstName}!`
: 'Welcome back!';
this.showNotification(welcomeMessage, 'success');
// Track login event
this.trackEvent('member_login', {
member_id: member.id,
verified: member.verified,
plan_count: member.planConnections?.length || 0
});
// Handle post-login redirects
this.handlePostLoginRedirect(member);
}
handleMemberLogout(previousMember) {
console.log('Member logged out:', previousMember.email);
this.showNotification('You have been logged out', 'info');
// Track logout event
this.trackEvent('member_logout', {
member_id: previousMember.id,
session_duration: Date.now() - (previousMember.loginTime || Date.now())
});
// Redirect to home
if (window.location.pathname.startsWith('/dashboard') ||
window.location.pathname.startsWith('/account')) {
window.location.href = '/';
}
}
handleEmailVerified(member) {
console.log('Email verified for:', member.email);
this.showNotification('Email verified successfully!', 'success');
// Track verification event
this.trackEvent('email_verified', {
member_id: member.id
});
// Show onboarding or next steps
this.showPostVerificationFlow(member);
}
handlePostLoginRedirect(member) {
const urlParams = new URLSearchParams(window.location.search);
const redirect = urlParams.get('redirect');
if (redirect) {
window.location.href = decodeURIComponent(redirect);
return;
}
// Default redirects based on member state
switch (this.journeyState) {
case 'unverified':
if (!window.location.pathname.includes('/verify')) {
window.location.href = '/verify-email';
}
break;
case 'verified_free':
if (window.location.pathname === '/login') {
window.location.href = '/dashboard';
}
break;
case 'verified_paid':
if (window.location.pathname === '/login') {
window.location.href = '/dashboard';
}
break;
}
}
showPostVerificationFlow(member) {
// Show welcome modal or onboarding
if (!member.planConnections?.length) {
// Offer plan selection
setTimeout(() => {
const showPlans = confirm('Welcome! Would you like to see our subscription plans?');
if (showPlans) {
window.location.href = '/pricing';
}
}, 2000);
}
}
updateJourneyUI() {
// Update navigation
this.updateNavigation();
// Update page content based on journey state
this.updatePageContent();
// Update banners and notifications
this.updateBanners();
}
updateNavigation() {
const nav = document.getElementById('main-navigation');
if (!nav) return;
// Remove all journey-specific nav items
nav.querySelectorAll('.journey-nav').forEach(item => item.remove());
// Add navigation based on current state
switch (this.journeyState) {
case 'anonymous':
this.addNavItem(nav, 'Login', '/login', 'journey-nav');
this.addNavItem(nav, 'Sign Up', '/signup', 'journey-nav');
break;
case 'unverified':
this.addNavItem(nav, 'Verify Email', '/verify-email', 'journey-nav');
this.addNavItem(nav, 'Logout', '#logout', 'journey-nav');
break;
case 'verified_free':
this.addNavItem(nav, 'Dashboard', '/dashboard', 'journey-nav');
this.addNavItem(nav, 'Upgrade', '/pricing', 'journey-nav');
this.addNavItem(nav, 'Account', '/account', 'journey-nav');
break;
case 'verified_paid':
this.addNavItem(nav, 'Dashboard', '/dashboard', 'journey-nav');
this.addNavItem(nav, 'Account', '/account', 'journey-nav');
this.addNavItem(nav, 'Billing', '/billing', 'journey-nav');
break;
}
}
updateBanners() {
// Remove existing banners
document.querySelectorAll('.journey-banner').forEach(banner => banner.remove());
// Show relevant banners
switch (this.journeyState) {
case 'unverified':
this.showVerificationBanner();
break;
case 'verified_free':
if (this.shouldShowUpgradeBanner()) {
this.showUpgradeBanner();
}
break;
}
}
showVerificationBanner() {
const banner = document.createElement('div');
banner.className = 'journey-banner verification-banner';
banner.innerHTML = `
<div class="banner-content">
<span class="banner-icon">📧</span>
<span class="banner-text">Please verify your email address to access all features.</span>
<button id="resend-verification-banner" class="banner-btn">Resend Email</button>
<button class="banner-close" onclick="this.parentElement.parentElement.remove()">×</button>
</div>
`;
document.body.insertBefore(banner, document.body.firstChild);
// Handle resend button
document.getElementById('resend-verification-banner').addEventListener('click', async () => {
try {
await this.memberstack.sendMemberVerificationEmail();
this.showNotification('Verification email sent!', 'success');
} catch (error) {
this.showNotification('Failed to send verification email', 'error');
}
});
}
addNavItem(nav, text, href, className = '') {
const item = document.createElement('a');
item.href = href;
item.textContent = text;
item.className = className;
if (href === '#logout') {
item.addEventListener('click', (e) => {
e.preventDefault();
this.memberstack.logout();
});
}
nav.appendChild(item);
}
shouldShowUpgradeBanner() {
// Logic to determine if upgrade banner should be shown
const lastShown = localStorage.getItem('upgrade_banner_last_shown');
const oneDayAgo = Date.now() - (24 * 60 * 60 * 1000);
return !lastShown || parseInt(lastShown) < oneDayAgo;
}
showUpgradeBanner() {
const banner = document.createElement('div');
banner.className = 'journey-banner upgrade-banner';
banner.innerHTML = `
<div class="banner-content">
<span class="banner-text">Unlock premium features with a paid plan!</span>
<button onclick="window.location.href='/pricing'" class="banner-btn">View Plans</button>
<button class="banner-close" onclick="this.parentElement.parentElement.remove()">×</button>
</div>
`;
document.body.insertBefore(banner, document.body.firstChild);
// Track banner shown
localStorage.setItem('upgrade_banner_last_shown', Date.now().toString());
}
showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
document.body.appendChild(notification);
// Auto-remove after delay
setTimeout(() => {
notification.remove();
}, 5000);
}
trackEvent(eventName, properties = {}) {
// Integration with analytics service
if (typeof gtag !== 'undefined') {
gtag('event', eventName, properties);
}
console.log('Event tracked:', eventName, properties);
}
updatePageContent() {
// Update page-specific content based on journey state
const stateElements = document.querySelectorAll('[data-journey-state]');
stateElements.forEach(element => {
const requiredState = element.dataset.journeyState;
element.style.display = requiredState === this.journeyState ? 'block' : 'none';
});
}
}
// Initialize journey manager
document.addEventListener('DOMContentLoaded', () => {
new MemberJourneyManager();
});
```
**HTML for Journey States:**
```html
<!-- Content shown only for anonymous users -->
<div data-journey-state="anonymous">
<h1>Welcome! Sign up to get started.</h1>
<button data-ms-action="signup">Create Account</button>
</div>
<!-- Content for unverified users -->
<div data-journey-state="unverified">
<h1>Please verify your email</h1>
<p>Check your inbox and click the verification link.</p>
<button id="send-verification-btn">Resend Verification</button>
</div>
<!-- Content for verified free users -->
<div data-journey-state="verified_free">
<h1>Welcome to your dashboard!</h1>
<div class="upgrade-prompt">
<p>Upgrade to unlock premium features</p>
<a href="/pricing">View Plans</a>
</div>
</div>
<!-- Content for verified paid users -->
<div data-journey-state="verified_paid">
<h1>Premium Dashboard</h1>
<div class="premium-features">
<!-- Premium content here -->
</div>
</div>
```
## Next Steps
- **[02-authentication.md](02-authentication.md)** - Authentication methods and flows
- **[03-member-management.md](03-member-management.md)** - Member profile and data management
- **[05-ui-components.md](05-ui-components.md)** - Pre-built UI components for journeys
- **[09-error-handling.md](09-error-handling.md)** - Handling email and verification errors