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
805 lines (675 loc) • 20.6 kB
Markdown
# Memberstack DOM - UI Components
## AI Assistant Instructions
When implementing Memberstack UI components:
- Use `openModal()` for pre-built authentication flows
- Use `hideModal()` to programmatically close modals
- Show loading states with `_showLoader()` and `_hideLoader()` (internal methods)
- Display messages with `_showMessage()` (internal method)
- Customize modal appearance through Memberstack dashboard settings
- Include translations parameter for multi-language support
## Overview
Memberstack DOM includes pre-built UI components for common authentication and member management flows. These components are styled according to your Memberstack app settings and provide a complete user experience without custom development.
## Modal Components
### openModal()
Open pre-built Memberstack modals for various user flows.
**Method Signature:**
```typescript
memberstack.openModal(type: ModalType, options?: {
translations?: MemberstackTranslations;
[key: string]: any;
}): Promise<any>
```
**Modal Types:**
| Type | Description | When to Use |
|------|-------------|-------------|
| `'LOGIN'` | Email/password and social login | When user needs to authenticate |
| `'SIGNUP'` | Account creation with email/password | For new user registration |
| `'FORGOT_PASSWORD'` | Password reset request | When user forgets password |
| `'RESET_PASSWORD'` | Set new password with token | Password reset confirmation page |
| `'PROFILE'` | Member profile management | Logged-in users managing account |
**Examples:**
Basic Modal Usage:
```javascript
// Login modal
document.getElementById('login-btn').addEventListener('click', () => {
memberstack.openModal('LOGIN');
});
// Signup modal
document.getElementById('signup-btn').addEventListener('click', () => {
memberstack.openModal('SIGNUP');
});
// Profile modal (requires authenticated member)
document.getElementById('profile-btn').addEventListener('click', () => {
memberstack.openModal('PROFILE');
});
// Forgot password modal
document.getElementById('forgot-password-link').addEventListener('click', (e) => {
e.preventDefault();
memberstack.openModal('FORGOT_PASSWORD');
});
```
Modal with Promise Handling:
```javascript
async function showLoginModal() {
try {
const result = await memberstack.openModal('LOGIN');
console.log('Login modal completed:', result);
// Modal resolved - user logged in successfully
// The onAuthChange callback will handle UI updates
} catch (error) {
console.log('Login modal cancelled or failed:', error);
// User closed modal or authentication failed
}
}
// Usage with async/await
document.getElementById('login-btn').addEventListener('click', showLoginModal);
```
Modal Flow Chain:
```javascript
function setupAuthFlow() {
// Login button opens login modal
document.getElementById('login-btn').addEventListener('click', async () => {
try {
await memberstack.openModal('LOGIN');
// Success handled by onAuthChange
} catch (error) {
console.log('Login cancelled');
}
});
// Signup button opens signup modal
document.getElementById('signup-btn').addEventListener('click', async () => {
try {
await memberstack.openModal('SIGNUP');
// Success handled by onAuthChange
} catch (error) {
console.log('Signup cancelled');
}
});
// Forgot password link in login modal opens forgot password modal
// This is handled automatically by the pre-built modals
}
```
### hideModal()
Programmatically close any open Memberstack modal.
**Method Signature:**
```typescript
memberstack.hideModal(): void
```
**Examples:**
Close Modal Programmatically:
```javascript
// Close modal after external event
function closeModalOnEscape(event) {
if (event.key === 'Escape') {
memberstack.hideModal();
}
}
document.addEventListener('keydown', closeModalOnEscape);
// Close modal after successful operation
async function performExternalLogin() {
// Some external authentication logic
const success = await externalAuthService.login();
if (success) {
// Close any open Memberstack modals
memberstack.hideModal();
}
}
// Auto-close modal after timeout (not recommended for auth flows)
function autoCloseModal(timeoutMs = 30000) {
setTimeout(() => {
memberstack.hideModal();
}, timeoutMs);
}
```
Modal State Management:
```javascript
class ModalManager {
constructor() {
this.modalStack = [];
this.isModalOpen = false;
this.setupModalHandling();
}
setupModalHandling() {
// Track modal state changes
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
const modalElements = document.querySelectorAll('[data-ms-modal]');
this.isModalOpen = modalElements.length > 0;
if (this.isModalOpen) {
document.body.classList.add('modal-open');
} else {
document.body.classList.remove('modal-open');
}
}
});
});
observer.observe(document.body, { childList: true, subtree: true });
}
async openModalWithTracking(type, options = {}) {
this.modalStack.push({ type, timestamp: Date.now() });
try {
const result = await memberstack.openModal(type, options);
this.modalStack.pop();
return result;
} catch (error) {
this.modalStack.pop();
throw error;
}
}
closeAllModals() {
memberstack.hideModal();
this.modalStack = [];
}
getCurrentModal() {
return this.modalStack[this.modalStack.length - 1] || null;
}
}
const modalManager = new ModalManager();
```
## Modal Customization
### Translations
Customize modal text for different languages or branding.
**Translation Interface:**
```typescript
interface MemberstackTranslations {
login?: {
title?: string;
emailPlaceholder?: string;
passwordPlaceholder?: string;
submitButton?: string;
forgotPasswordLink?: string;
signupLink?: string;
};
signup?: {
title?: string;
emailPlaceholder?: string;
passwordPlaceholder?: string;
submitButton?: string;
loginLink?: string;
};
// ... more modal translations
}
```
**Examples:**
Custom Text Translations:
```javascript
const customTranslations = {
login: {
title: 'Welcome Back!',
emailPlaceholder: 'Enter your email address',
passwordPlaceholder: 'Enter your password',
submitButton: 'Sign In',
forgotPasswordLink: 'Forgot your password?',
signupLink: "Don't have an account? Sign up"
},
signup: {
title: 'Create Your Account',
emailPlaceholder: 'Your email address',
passwordPlaceholder: 'Create a password',
submitButton: 'Create Account',
loginLink: 'Already have an account? Sign in'
},
profile: {
title: 'Account Settings',
saveButton: 'Save Changes',
cancelButton: 'Cancel'
}
};
// Use translations with modals
document.getElementById('login-btn').addEventListener('click', () => {
memberstack.openModal('LOGIN', {
translations: customTranslations
});
});
document.getElementById('signup-btn').addEventListener('click', () => {
memberstack.openModal('SIGNUP', {
translations: customTranslations
});
});
```
Multi-Language Support:
```javascript
const translations = {
en: {
login: {
title: 'Login',
emailPlaceholder: 'Email address',
passwordPlaceholder: 'Password',
submitButton: 'Sign In'
}
},
es: {
login: {
title: 'Iniciar Sesión',
emailPlaceholder: 'Dirección de correo',
passwordPlaceholder: 'Contraseña',
submitButton: 'Acceder'
}
},
fr: {
login: {
title: 'Connexion',
emailPlaceholder: 'Adresse e-mail',
passwordPlaceholder: 'Mot de passe',
submitButton: 'Se connecter'
}
}
};
function getLanguage() {
return navigator.language.split('-')[0] || 'en';
}
function openLocalizedModal(type) {
const language = getLanguage();
const modalTranslations = translations[language] || translations.en;
memberstack.openModal(type, {
translations: modalTranslations
});
}
// Usage
document.getElementById('login-btn').addEventListener('click', () => {
openLocalizedModal('LOGIN');
});
```
### Modal Styling
While modal styling is primarily controlled through the Memberstack dashboard, you can add custom CSS to complement the design.
**CSS Customization:**
```css
/* Target Memberstack modal containers */
[data-ms-modal] {
/* Custom modal container styles */
z-index: 10000;
}
[data-ms-modal] .modal-content {
/* Custom content area styles */
border-radius: 12px;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.25);
}
[data-ms-modal] .modal-header {
/* Custom header styles */
border-bottom: 1px solid #e5e5e5;
}
[data-ms-modal] .modal-footer {
/* Custom footer styles */
border-top: 1px solid #e5e5e5;
}
/* Custom button styles */
[data-ms-modal] .btn-primary {
background: linear-gradient(45deg, #007ace, #0056b3);
border: none;
}
[data-ms-modal] .btn-primary:hover {
background: linear-gradient(45deg, #0056b3, #004085);
}
/* Dark theme support */
(prefers-color-scheme: dark) {
[data-ms-modal] {
--ms-modal-bg: #1a1a1a;
--ms-text-color: #ffffff;
--ms-border-color: #333333;
}
}
```
## Internal UI Utilities
### Loading States
Display loading indicators during operations (internal methods).
**Method Signatures:**
```typescript
memberstack._showLoader(element?: HTMLElement): void
memberstack._hideLoader(element?: HTMLElement): void
```
**Examples:**
Global Loading Indicator:
```javascript
// Show global loading overlay
memberstack._showLoader();
// Perform operation
try {
await memberstack.loginMemberEmailPassword({ email, password });
} finally {
// Hide global loading overlay
memberstack._hideLoader();
}
```
Element-Specific Loading:
```javascript
const button = document.getElementById('login-btn');
// Show loading state on specific element
button.textContent = 'Signing In...';
button.disabled = true;
memberstack._showLoader(button);
try {
await memberstack.loginMemberEmailPassword({ email, password });
} finally {
// Hide element loading state
memberstack._hideLoader(button);
button.textContent = 'Sign In';
button.disabled = false;
}
```
### Message Display
Show success or error messages to users (internal method).
**Method Signature:**
```typescript
memberstack._showMessage(message: string, isError: boolean): void
```
**Examples:**
Success Messages:
```javascript
// Show success message
memberstack._showMessage('Profile updated successfully!', false);
// Show error message
memberstack._showMessage('Login failed. Please try again.', true);
```
Custom Message Handling:
```javascript
function showCustomMessage(message, type = 'info') {
// Use Memberstack's built-in messaging
memberstack._showMessage(message, type === 'error');
// Or implement custom message display
const messageEl = document.getElementById('custom-messages');
if (messageEl) {
messageEl.innerHTML = `
<div class="message message-${type}">
${message}
<button onclick="this.parentElement.remove()">×</button>
</div>
`;
}
}
// Usage
showCustomMessage('Welcome to our platform!', 'success');
showCustomMessage('Please fix the errors below', 'error');
showCustomMessage('Your session will expire soon', 'warning');
```
## Complete UI Integration Example
```javascript
class MemberstackUI {
constructor() {
this.memberstack = window.$memberstackDom;
this.currentModal = null;
this.setupEventHandlers();
this.setupAuthListener();
this.setupCustomStyling();
}
setupEventHandlers() {
// Authentication buttons
document.addEventListener('click', (e) => {
const target = e.target;
// Login buttons
if (target.matches('[data-ms-action="login"]')) {
e.preventDefault();
this.showLoginModal();
}
// Signup buttons
if (target.matches('[data-ms-action="signup"]')) {
e.preventDefault();
this.showSignupModal();
}
// Profile buttons
if (target.matches('[data-ms-action="profile"]')) {
e.preventDefault();
this.showProfileModal();
}
// Logout buttons
if (target.matches('[data-ms-action="logout"]')) {
e.preventDefault();
this.handleLogout();
}
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
// Alt + L for login
if (e.altKey && e.key === 'l') {
e.preventDefault();
this.showLoginModal();
}
// Alt + S for signup
if (e.altKey && e.key === 's') {
e.preventDefault();
this.showSignupModal();
}
// Escape to close modals
if (e.key === 'Escape') {
this.memberstack.hideModal();
}
});
}
setupAuthListener() {
this.memberstack.onAuthChange(({ member }) => {
this.updateUIState(member);
if (member) {
// User logged in - close any auth modals
this.memberstack.hideModal();
this.showWelcomeMessage(member);
}
});
}
setupCustomStyling() {
// Add custom CSS for modal enhancements
const style = document.createElement('style');
style.textContent = `
[data-ms-modal] {
backdrop-filter: blur(4px);
}
[data-ms-modal] .modal-content {
animation: modalSlideIn 0.3s ease-out;
}
modalSlideIn {
from {
opacity: 0;
transform: translateY(-50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.ms-loading-btn {
position: relative;
color: transparent !important;
}
.ms-loading-btn::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 20px;
height: 20px;
border: 2px solid #ffffff;
border-top-color: transparent;
border-radius: 50%;
animation: spin 1s linear infinite;
}
spin {
to {
transform: translate(-50%, -50%) rotate(360deg);
}
}
`;
document.head.appendChild(style);
}
async showLoginModal() {
try {
this.currentModal = 'LOGIN';
const result = await this.memberstack.openModal('LOGIN', {
translations: this.getTranslations('login')
});
console.log('Login modal completed:', result);
} catch (error) {
console.log('Login modal cancelled:', error);
} finally {
this.currentModal = null;
}
}
async showSignupModal() {
try {
this.currentModal = 'SIGNUP';
const result = await this.memberstack.openModal('SIGNUP', {
translations: this.getTranslations('signup')
});
console.log('Signup modal completed:', result);
} catch (error) {
console.log('Signup modal cancelled:', error);
} finally {
this.currentModal = null;
}
}
async showProfileModal() {
try {
// Check if user is authenticated
const member = await this.memberstack.getCurrentMember();
if (!member.data) {
this.showLoginModal();
return;
}
this.currentModal = 'PROFILE';
const result = await this.memberstack.openModal('PROFILE', {
translations: this.getTranslations('profile')
});
console.log('Profile modal completed:', result);
} catch (error) {
console.log('Profile modal cancelled:', error);
} finally {
this.currentModal = null;
}
}
async handleLogout() {
const confirmed = confirm('Are you sure you want to log out?');
if (!confirmed) return;
try {
this.memberstack._showLoader();
await this.memberstack.logout();
this.memberstack._showMessage('You have been logged out successfully', false);
} catch (error) {
console.error('Logout failed:', error);
this.memberstack._showMessage('Logout failed. Please try again.', true);
} finally {
this.memberstack._hideLoader();
}
}
updateUIState(member) {
// Update authentication-dependent UI
const authElements = document.querySelectorAll('[data-auth]');
authElements.forEach(element => {
const authState = element.dataset.auth;
if (authState === 'logged-in') {
element.style.display = member ? 'block' : 'none';
} else if (authState === 'logged-out') {
element.style.display = member ? 'none' : 'block';
}
});
// Update member-specific content
if (member) {
document.querySelectorAll('[data-member-field]').forEach(element => {
const field = element.dataset.memberField;
const value = this.getNestedValue(member, field);
if (value !== undefined) {
element.textContent = value;
}
});
}
}
showWelcomeMessage(member) {
const welcomeText = member.customFields?.firstName
? `Welcome back, ${member.customFields.firstName}!`
: 'Welcome back!';
this.memberstack._showMessage(welcomeText, false);
}
getTranslations(modalType) {
// Get translations based on user's language preference
const language = navigator.language.split('-')[0] || 'en';
const translations = {
en: {
login: {
title: 'Sign In to Your Account',
submitButton: 'Sign In',
forgotPasswordLink: 'Forgot password?'
},
signup: {
title: 'Create Your Account',
submitButton: 'Create Account'
},
profile: {
title: 'Account Settings',
saveButton: 'Save Changes'
}
}
// Add more languages as needed
};
return translations[language]?.[modalType] || {};
}
getNestedValue(obj, path) {
return path.split('.').reduce((current, key) => current?.[key], obj);
}
}
// Initialize UI system
document.addEventListener('DOMContentLoaded', () => {
new MemberstackUI();
});
```
**HTML Usage:**
```html
<!-- Authentication buttons with data attributes -->
<button data-ms-action="login">Sign In</button>
<button data-ms-action="signup">Sign Up</button>
<button data-ms-action="profile" data-auth="logged-in">My Account</button>
<button data-ms-action="logout" data-auth="logged-in">Sign Out</button>
<!-- Member-specific content -->
<span data-auth="logged-in">
Welcome, <span data-member-field="customFields.firstName">User</span>!
</span>
<!-- Plan-specific content -->
<div data-auth="logged-in" data-requires-plan="pro">
<h3>Pro Features</h3>
<p>Access to advanced features</p>
</div>
```
## Best Practices
### Modal UX Guidelines
1. **Progressive Disclosure**: Start with login, offer signup as alternative
```javascript
// Good: Clear primary action
<button data-ms-action="login" class="btn-primary">Sign In</button>
<a href="#" data-ms-action="signup" class="link">New? Create account</a>
// Avoid: Competing equal-weight options
<button data-ms-action="login">Sign In</button>
<button data-ms-action="signup">Sign Up</button>
```
2. **Context-Aware Modals**: Show appropriate modal based on user state
```javascript
function showContextualAuth() {
const intent = new URLSearchParams(window.location.search).get('intent');
if (intent === 'signup') {
memberstack.openModal('SIGNUP');
} else if (intent === 'reset') {
memberstack.openModal('FORGOT_PASSWORD');
} else {
memberstack.openModal('LOGIN');
}
}
```
3. **Error Recovery**: Provide clear paths forward on modal errors
```javascript
async function showLoginWithRecovery() {
try {
await memberstack.openModal('LOGIN');
} catch (error) {
if (error.type === 'CANCELLED') {
// User cancelled - don't show error
return;
}
// Show recovery options
const retry = confirm('Authentication failed. Try again?');
if (retry) {
showLoginWithRecovery();
}
}
}
```
## Next Steps
- **[02-authentication.md](02-authentication.md)** - Programmatic authentication methods
- **[06-member-journey.md](06-member-journey.md)** - Complete user journey flows
- **[09-error-handling.md](09-error-handling.md)** - Handling modal and UI errors
- **[10-examples.md](10-examples.md)** - Complete UI implementation examples