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
904 lines (756 loc) • 24.4 kB
Markdown
# Memberstack DOM - Member Management
## AI Assistant Instructions
When implementing member management:
- Use `getCurrentMember()` to check authentication before operations
- Include `useCache: true` for frequent member data access
- Handle custom fields with proper validation
- Show authentication updates (email/password) separately from profile updates
- Include member JSON operations for advanced use cases
## Overview
Member management in Memberstack DOM includes retrieving current member data, updating profiles, managing authentication credentials, and handling custom member information.
## Getting Member Information
### getCurrentMember()
Retrieve the currently authenticated member's information.
**Method Signature:**
```typescript
await memberstack.getCurrentMember({
useCache?: boolean;
}): Promise<GetCurrentMemberPayload>
```
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| useCache | boolean | ❌ | Use cached data (faster) vs fresh data from server |
**Response:**
```typescript
{
data: {
id: string;
email: string;
verified: boolean;
loginRedirectUrl: string | null;
customFields: Record<string, any>;
profileImage: string | null;
metaData: Record<string, any>;
planConnections: Array<{
id: string;
planId: string;
status: "ACTIVE" | "CANCELLED" | "PAST_DUE";
createdAt: string;
// ... additional plan connection properties
}>;
} | null; // null if no member is authenticated
}
```
**Examples:**
Check Current Member:
```javascript
async function getCurrentMember() {
try {
const result = await memberstack.getCurrentMember();
if (result.data) {
console.log('Member is logged in:', result.data.email);
return result.data;
} else {
console.log('No member logged in');
return null;
}
} catch (error) {
console.error('Failed to get current member:', error);
return null;
}
}
// Usage
const member = await getCurrentMember();
if (member) {
document.getElementById('welcome-message').textContent =
`Welcome back, ${member.customFields?.firstName || member.email}!`;
}
```
Using Cache for Performance:
```javascript
// Fast cached access (use for frequent checks)
const cachedMember = await memberstack.getCurrentMember({ useCache: true });
// Fresh data from server (use when you need up-to-date info)
const freshMember = await memberstack.getCurrentMember({ useCache: false });
// Practical usage pattern
async function getMemberWithFallback() {
// Try cached first for speed
let member = await memberstack.getCurrentMember({ useCache: true });
// If no cached data, get fresh data
if (!member.data) {
member = await memberstack.getCurrentMember({ useCache: false });
}
return member.data;
}
```
Member Profile Component:
```javascript
async function loadMemberProfile() {
const loadingEl = document.getElementById('profile-loading');
const profileEl = document.getElementById('member-profile');
try {
loadingEl.style.display = 'block';
const result = await memberstack.getCurrentMember();
if (result.data) {
displayMemberProfile(result.data);
} else {
// Redirect to login
window.location.href = '/login';
}
} catch (error) {
console.error('Failed to load profile:', error);
document.getElementById('profile-error').style.display = 'block';
} finally {
loadingEl.style.display = 'none';
}
}
function displayMemberProfile(member) {
const profileEl = document.getElementById('member-profile');
profileEl.innerHTML = `
<div class="profile-header">
<img src="${member.profileImage || '/default-avatar.png'}" alt="Profile" class="profile-image">
<div class="profile-info">
<h2>${member.customFields?.firstName || 'Member'} ${member.customFields?.lastName || ''}</h2>
<p class="email">${member.email}</p>
${!member.verified ? '<span class="unverified">Email not verified</span>' : ''}
</div>
</div>
<div class="profile-details">
<div class="field">
<label>Member ID:</label>
<span>${member.id}</span>
</div>
<div class="field">
<label>Company:</label>
<span>${member.customFields?.company || 'Not provided'}</span>
</div>
<div class="field">
<label>Phone:</label>
<span>${member.customFields?.phone || 'Not provided'}</span>
</div>
<div class="field">
<label>Active Plans:</label>
<span>${member.planConnections?.filter(pc => pc.status === 'ACTIVE').length || 0}</span>
</div>
</div>
`;
profileEl.style.display = 'block';
}
```
## Updating Member Information
### updateMember()
Update the current member's custom fields and profile information.
**Method Signature:**
```typescript
await memberstack.updateMember({
customFields?: Record<string, any>;
}): Promise<UpdateMemberPayload>
```
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| customFields | object | ❌ | Custom fields to update |
**Response:**
```typescript
{
data: {
id: string;
email: string;
customFields: Record<string, any>;
verified: boolean;
profileImage: string | null;
// ... other member properties
}
}
```
**Examples:**
Basic Profile Update:
```javascript
async function updateProfile(formData) {
try {
const result = await memberstack.updateMember({
customFields: {
firstName: formData.firstName,
lastName: formData.lastName,
company: formData.company,
phone: formData.phone,
bio: formData.bio,
preferences: {
newsletter: formData.newsletter,
notifications: formData.notifications
}
}
});
console.log('Profile updated:', result.data);
return {
success: true,
message: 'Profile updated successfully!',
member: result.data
};
} catch (error) {
console.error('Profile update failed:', error);
return {
success: false,
message: 'Failed to update profile. Please try again.'
};
}
}
// Form handler
document.getElementById('profile-form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const profileData = {
firstName: formData.get('firstName'),
lastName: formData.get('lastName'),
company: formData.get('company'),
phone: formData.get('phone'),
bio: formData.get('bio'),
newsletter: formData.get('newsletter') === 'on',
notifications: formData.get('notifications') === 'on'
};
const result = await updateProfile(profileData);
if (result.success) {
document.getElementById('success-message').textContent = result.message;
document.getElementById('success-message').style.display = 'block';
} else {
document.getElementById('error-message').textContent = result.message;
document.getElementById('error-message').style.display = 'block';
}
});
```
Incremental Field Updates:
```javascript
async function updateSingleField(fieldName, value) {
try {
// Get current member to preserve existing fields
const currentMember = await memberstack.getCurrentMember();
if (!currentMember.data) {
throw new Error('No member logged in');
}
const result = await memberstack.updateMember({
customFields: {
...currentMember.data.customFields,
[fieldName]: value,
lastUpdated: new Date().toISOString()
}
});
console.log(`Updated ${fieldName}:`, value);
return result.data;
} catch (error) {
console.error(`Failed to update ${fieldName}:`, error);
throw error;
}
}
// Usage examples
await updateSingleField('company', 'New Company Name');
await updateSingleField('preferences', { theme: 'dark', language: 'en' });
```
Complex Profile Update with Validation:
```javascript
class ProfileManager {
constructor() {
this.memberstack = window.$memberstackDom;
}
async updateProfile(profileData) {
// Validate data before sending
const validation = this.validateProfileData(profileData);
if (!validation.valid) {
throw new Error(validation.message);
}
// Get current member to merge with updates
const currentMember = await this.memberstack.getCurrentMember();
if (!currentMember.data) {
throw new Error('No member authenticated');
}
const updatedFields = {
...currentMember.data.customFields,
...profileData,
lastProfileUpdate: new Date().toISOString()
};
try {
const result = await this.memberstack.updateMember({
customFields: updatedFields
});
// Trigger UI updates
this.onProfileUpdated(result.data);
return result.data;
} catch (error) {
console.error('Profile update failed:', error);
throw new Error('Failed to update profile. Please try again.');
}
}
validateProfileData(data) {
if (data.email && !this.isValidEmail(data.email)) {
return { valid: false, message: 'Invalid email format' };
}
if (data.phone && !this.isValidPhone(data.phone)) {
return { valid: false, message: 'Invalid phone number format' };
}
if (data.firstName && data.firstName.length > 50) {
return { valid: false, message: 'First name too long' };
}
return { valid: true };
}
isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
isValidPhone(phone) {
return /^\+?[\d\s\-\(\)]+$/.test(phone);
}
onProfileUpdated(member) {
// Update UI elements
document.querySelectorAll('[data-member-field]').forEach(el => {
const field = el.dataset.memberField;
const value = this.getNestedValue(member.customFields, field);
if (value !== undefined) {
el.textContent = value;
}
});
// Dispatch custom event
window.dispatchEvent(new CustomEvent('memberProfileUpdated', {
detail: { member }
}));
}
getNestedValue(obj, path) {
return path.split('.').reduce((current, key) => current?.[key], obj);
}
}
const profileManager = new ProfileManager();
```
### updateMemberProfileImage()
Update the member's profile image.
**Method Signature:**
```typescript
await memberstack.updateMemberProfileImage({
profileImage: File;
}): Promise<UpdateMemberProfileImagePayload>
```
**Example:**
```javascript
async function updateProfileImage(imageFile) {
try {
// Validate file
if (!imageFile || !imageFile.type.startsWith('image/')) {
throw new Error('Please select a valid image file');
}
if (imageFile.size > 5 * 1024 * 1024) { // 5MB limit
throw new Error('Image file too large. Maximum size is 5MB');
}
const result = await memberstack.updateMemberProfileImage({
profileImage: imageFile
});
console.log('Profile image updated:', result.data.profileImage);
// Update UI
document.getElementById('profile-image').src = result.data.profileImage;
return result.data.profileImage;
} catch (error) {
console.error('Profile image update failed:', error);
throw error;
}
}
// File input handler
document.getElementById('profile-image-input').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (file) {
try {
document.getElementById('image-loading').style.display = 'block';
await updateProfileImage(file);
document.getElementById('image-success').style.display = 'block';
} catch (error) {
document.getElementById('image-error').textContent = error.message;
document.getElementById('image-error').style.display = 'block';
} finally {
document.getElementById('image-loading').style.display = 'none';
}
}
});
```
## Authentication Credential Updates
### updateMemberAuth()
Update member's email address and/or password. Requires current password for security.
**Method Signature:**
```typescript
await memberstack.updateMemberAuth({
email?: string;
oldPassword?: string;
newPassword?: string;
}): Promise<UpdateMemberAuthPayload>
```
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| email | string | ❌ | New email address |
| oldPassword | string | ❌ | Current password (required for any changes) |
| newPassword | string | ❌ | New password |
**Examples:**
Change Password:
```javascript
async function changePassword(oldPassword, newPassword) {
try {
const result = await memberstack.updateMemberAuth({
oldPassword,
newPassword
});
console.log('Password changed successfully');
return {
success: true,
message: 'Password updated successfully!'
};
} catch (error) {
console.error('Password change failed:', error);
const errorMessages = {
'INVALID_PASSWORD': 'Current password is incorrect',
'WEAK_NEW_PASSWORD': 'New password is too weak',
'SAME_PASSWORD': 'New password must be different from current password'
};
return {
success: false,
message: errorMessages[error.code] || 'Failed to change password'
};
}
}
// Password change form
document.getElementById('password-form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const oldPassword = formData.get('currentPassword');
const newPassword = formData.get('newPassword');
const confirmPassword = formData.get('confirmPassword');
if (newPassword !== confirmPassword) {
alert('New passwords do not match');
return;
}
const result = await changePassword(oldPassword, newPassword);
if (result.success) {
alert(result.message);
e.target.reset();
} else {
alert(result.message);
}
});
```
Change Email Address:
```javascript
async function changeEmail(newEmail, currentPassword) {
try {
const result = await memberstack.updateMemberAuth({
email: newEmail.trim().toLowerCase(),
oldPassword: currentPassword
});
console.log('Email changed successfully:', result.data.email);
return {
success: true,
message: 'Email updated successfully! Please verify your new email address.',
newEmail: result.data.email
};
} catch (error) {
console.error('Email change failed:', error);
const errorMessages = {
'INVALID_PASSWORD': 'Current password is incorrect',
'EMAIL_ALREADY_EXISTS': 'This email address is already in use',
'INVALID_EMAIL': 'Please enter a valid email address'
};
return {
success: false,
message: errorMessages[error.code] || 'Failed to change email'
};
}
}
```
## Advanced Member Data Management
### getMemberJSON()
Get member's JSON data store (key-value storage).
**Method Signature:**
```typescript
await memberstack.getMemberJSON(): Promise<GetMemberJSONPayload>
```
**Example:**
```javascript
async function getMemberData() {
try {
const result = await memberstack.getMemberJSON();
console.log('Member JSON data:', result.data);
return result.data;
} catch (error) {
console.error('Failed to get member JSON:', error);
return {};
}
}
```
### updateMemberJSON()
Update member's JSON data store.
**Method Signature:**
```typescript
await memberstack.updateMemberJSON({
json: object;
}): Promise<GetMemberJSONPayload>
```
**Example:**
```javascript
async function saveMemberData(data) {
try {
const result = await memberstack.updateMemberJSON({
json: {
preferences: {
theme: data.theme,
language: data.language,
notifications: data.notifications
},
appData: {
lastLogin: new Date().toISOString(),
loginCount: (data.loginCount || 0) + 1,
features: data.enabledFeatures
},
metadata: {
version: '1.0',
updatedAt: new Date().toISOString()
}
}
});
console.log('Member JSON updated:', result.data);
return result.data;
} catch (error) {
console.error('Failed to update member JSON:', error);
throw error;
}
}
```
### deleteMember()
Delete the current member's account permanently.
**Method Signature:**
```typescript
await memberstack.deleteMember(): Promise<DeleteMemberPayload>
```
**Example:**
```javascript
async function deleteAccount() {
const confirmed = confirm(
'Are you sure you want to delete your account? This action cannot be undone.'
);
if (!confirmed) return;
const doubleConfirm = prompt(
'Type "DELETE" to confirm account deletion:'
);
if (doubleConfirm !== 'DELETE') {
alert('Account deletion cancelled');
return;
}
try {
await memberstack.deleteMember();
alert('Your account has been successfully deleted.');
window.location.href = '/';
} catch (error) {
console.error('Account deletion failed:', error);
alert('Failed to delete account. Please contact support.');
}
}
document.getElementById('delete-account-btn').addEventListener('click', deleteAccount);
```
## Complete Member Management Example
```javascript
class MemberProfileManager {
constructor() {
this.memberstack = window.$memberstackDom;
this.currentMember = null;
this.init();
}
async init() {
try {
await this.loadMemberProfile();
this.setupEventListeners();
} catch (error) {
console.error('Failed to initialize profile manager:', error);
}
}
async loadMemberProfile() {
try {
const result = await this.memberstack.getCurrentMember();
if (result.data) {
this.currentMember = result.data;
this.displayProfile(result.data);
} else {
window.location.href = '/login';
}
} catch (error) {
console.error('Failed to load profile:', error);
this.showError('Failed to load profile data');
}
}
setupEventListeners() {
// Profile form
document.getElementById('profile-form')?.addEventListener('submit', (e) => {
e.preventDefault();
this.handleProfileUpdate(e);
});
// Password form
document.getElementById('password-form')?.addEventListener('submit', (e) => {
e.preventDefault();
this.handlePasswordChange(e);
});
// Email form
document.getElementById('email-form')?.addEventListener('submit', (e) => {
e.preventDefault();
this.handleEmailChange(e);
});
// Profile image
document.getElementById('profile-image-input')?.addEventListener('change', (e) => {
this.handleImageUpload(e);
});
}
displayProfile(member) {
// Populate form fields
const fields = {
'firstName': member.customFields?.firstName || '',
'lastName': member.customFields?.lastName || '',
'company': member.customFields?.company || '',
'phone': member.customFields?.phone || '',
'bio': member.customFields?.bio || '',
'email-display': member.email
};
Object.entries(fields).forEach(([id, value]) => {
const element = document.getElementById(id);
if (element) {
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
element.value = value;
} else {
element.textContent = value;
}
}
});
// Profile image
const profileImg = document.getElementById('profile-image');
if (profileImg) {
profileImg.src = member.profileImage || '/default-avatar.png';
}
// Verification status
const verificationStatus = document.getElementById('verification-status');
if (verificationStatus) {
verificationStatus.textContent = member.verified ? 'Verified' : 'Not Verified';
verificationStatus.className = member.verified ? 'verified' : 'unverified';
}
}
async handleProfileUpdate(event) {
const formData = new FormData(event.target);
const profileData = {
firstName: formData.get('firstName'),
lastName: formData.get('lastName'),
company: formData.get('company'),
phone: formData.get('phone'),
bio: formData.get('bio')
};
this.setFormLoading('profile-form', true);
try {
const result = await this.memberstack.updateMember({
customFields: {
...this.currentMember.customFields,
...profileData,
lastUpdated: new Date().toISOString()
}
});
this.currentMember = result.data;
this.showSuccess('Profile updated successfully!');
} catch (error) {
this.showError('Failed to update profile');
} finally {
this.setFormLoading('profile-form', false);
}
}
async handlePasswordChange(event) {
const formData = new FormData(event.target);
const oldPassword = formData.get('currentPassword');
const newPassword = formData.get('newPassword');
const confirmPassword = formData.get('confirmPassword');
if (newPassword !== confirmPassword) {
this.showError('New passwords do not match');
return;
}
this.setFormLoading('password-form', true);
try {
await this.memberstack.updateMemberAuth({
oldPassword,
newPassword
});
this.showSuccess('Password updated successfully!');
event.target.reset();
} catch (error) {
const message = error.code === 'INVALID_PASSWORD'
? 'Current password is incorrect'
: 'Failed to change password';
this.showError(message);
} finally {
this.setFormLoading('password-form', false);
}
}
async handleEmailChange(event) {
const formData = new FormData(event.target);
const newEmail = formData.get('newEmail');
const password = formData.get('password');
this.setFormLoading('email-form', true);
try {
const result = await this.memberstack.updateMemberAuth({
email: newEmail,
oldPassword: password
});
this.currentMember = result.data;
this.showSuccess('Email updated successfully! Please verify your new email.');
this.displayProfile(result.data);
event.target.reset();
} catch (error) {
const message = error.code === 'EMAIL_ALREADY_EXISTS'
? 'This email is already in use'
: 'Failed to change email';
this.showError(message);
} finally {
this.setFormLoading('email-form', false);
}
}
async handleImageUpload(event) {
const file = event.target.files[0];
if (!file) return;
try {
const imageUrl = await this.memberstack.updateMemberProfileImage({
profileImage: file
});
document.getElementById('profile-image').src = imageUrl.data.profileImage;
this.showSuccess('Profile image updated!');
} catch (error) {
this.showError('Failed to update profile image');
}
}
setFormLoading(formId, loading) {
const form = document.getElementById(formId);
const submitBtn = form?.querySelector('button[type="submit"]');
if (submitBtn) {
submitBtn.disabled = loading;
submitBtn.textContent = loading ? 'Saving...' : 'Save Changes';
}
}
showSuccess(message) {
this.showMessage(message, 'success');
}
showError(message) {
this.showMessage(message, 'error');
}
showMessage(message, type) {
const messageEl = document.getElementById('message');
if (messageEl) {
messageEl.textContent = message;
messageEl.className = `message ${type}`;
messageEl.style.display = 'block';
setTimeout(() => {
messageEl.style.display = 'none';
}, 5000);
}
}
}
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
new MemberProfileManager();
});
```
## Next Steps
- **[04-plan-management.md](04-plan-management.md)** - Managing member subscriptions and plans
- **[06-member-journey.md](06-member-journey.md)** - Email verification and member lifecycle
- **[07-advanced-features.md](07-advanced-features.md)** - Advanced member features like teams
- **[08-types-reference.md](08-types-reference.md)** - TypeScript definitions for member objects