feat: implement flexible editor gate system

- Replace automatic auth controls with developer-placed .insertr-gate elements
- Add OAuth-ready authentication flow with mock implementation
- Support any HTML element as gate with custom styling
- Implement proper gate restoration after authentication
- Move auth controls to bottom-right corner for better UX
- Add editor gates to demo pages (footer link and styled button)
- Maintain gates visible by default with hideGatesAfterAuth option
- Prevent duplicate authentication attempts with loading states

This enables small business owners to access editor via discrete
footer links or custom-styled elements placed anywhere by developers.
This commit is contained in:
2025-09-04 18:42:30 +02:00
parent 1d81c636cb
commit 6fef293df3
8 changed files with 454 additions and 49 deletions

View File

@@ -6,7 +6,7 @@ export class InsertrAuth {
constructor(options = {}) {
this.options = {
mockAuth: options.mockAuth !== false, // Enable mock auth by default
autoCreateControls: options.autoCreateControls !== false,
hideGatesAfterAuth: options.hideGatesAfterAuth === true, // Keep gates visible by default
...options
};
@@ -15,31 +15,191 @@ export class InsertrAuth {
isAuthenticated: false,
editMode: false,
currentUser: null,
activeEditor: null
activeEditor: null,
isInitialized: false,
isAuthenticating: false
};
this.statusIndicator = null;
}
/**
* Initialize authentication system
* Initialize gate system (called on page load)
*/
init() {
console.log('🔐 Initializing Insertr Authentication');
console.log('🔧 Insertr: Scanning for editor gates');
if (this.options.autoCreateControls) {
this.createAuthControls();
this.setupEditorGates();
}
/**
* Initialize full editing system (called after successful OAuth)
*/
initializeFullSystem() {
if (this.state.isInitialized) {
return; // Already initialized
}
console.log('🔐 Initializing Insertr Editing System');
this.createAuthControls();
this.setupAuthenticationControls();
this.createStatusIndicator();
this.updateBodyClasses();
console.log('📱 Auth controls ready - Look for buttons in top-right corner');
// Auto-enable edit mode after OAuth
this.state.editMode = true;
this.state.isInitialized = true;
// Start the editor system
if (window.Insertr && window.Insertr.startEditor) {
window.Insertr.startEditor();
}
this.updateButtonStates();
this.updateStatusIndicator();
console.log('📱 Editing system active - Controls in bottom-right corner');
console.log('✏️ Edit mode enabled - Click elements to edit');
}
/**
* Create authentication control buttons if they don't exist
* Setup editor gate click handlers for any .insertr-gate elements
*/
setupEditorGates() {
const gates = document.querySelectorAll('.insertr-gate');
if (gates.length === 0) {
console.log(' No .insertr-gate elements found - editor access disabled');
return;
}
console.log(`🚪 Found ${gates.length} editor gate(s)`);
// Add gate styles
this.addGateStyles();
gates.forEach((gate, index) => {
// Store original text for later restoration
if (!gate.hasAttribute('data-original-text')) {
gate.setAttribute('data-original-text', gate.textContent);
}
gate.addEventListener('click', (e) => {
e.preventDefault();
this.handleGateClick(gate, index);
});
// Add subtle styling to indicate it's clickable
gate.style.cursor = 'pointer';
});
}
/**
* Handle click on an editor gate element
*/
async handleGateClick(gateElement, gateIndex) {
// Prevent multiple simultaneous authentication attempts
if (this.state.isAuthenticating) {
console.log('⏳ Authentication already in progress...');
return;
}
console.log(`🚀 Editor gate activated (gate ${gateIndex + 1})`);
this.state.isAuthenticating = true;
// Store original text and show loading state
const originalText = gateElement.textContent;
gateElement.setAttribute('data-original-text', originalText);
gateElement.textContent = '⏳ Signing in...';
gateElement.style.pointerEvents = 'none';
try {
// Perform OAuth authentication
await this.performOAuthFlow();
// Initialize full editing system
this.initializeFullSystem();
// Conditionally hide gates based on options
if (this.options.hideGatesAfterAuth) {
this.hideAllGates();
} else {
this.updateGateState();
}
} catch (error) {
console.error('❌ Authentication failed:', error);
// Restore clicked gate to original state
const originalText = gateElement.getAttribute('data-original-text');
if (originalText) {
gateElement.textContent = originalText;
}
gateElement.style.pointerEvents = '';
} finally {
this.state.isAuthenticating = false;
}
}
/**
* Perform OAuth authentication flow
*/
async performOAuthFlow() {
// In development, simulate OAuth flow
if (this.options.mockAuth) {
console.log('🔐 Mock OAuth: Simulating authentication...');
// Simulate network delay
await new Promise(resolve => setTimeout(resolve, 1000));
// Set authenticated state
this.state.isAuthenticated = true;
this.state.currentUser = {
name: 'Site Owner',
email: 'owner@example.com',
role: 'admin'
};
console.log('✅ Mock OAuth: Authentication successful');
return;
}
// TODO: In production, implement real OAuth flow
// This would redirect to OAuth provider, handle callback, etc.
throw new Error('Production OAuth not implemented yet');
}
/**
* Hide all editor gates after successful authentication (optional)
*/
hideAllGates() {
document.body.classList.add('insertr-hide-gates');
console.log('🚪 Editor gates hidden (hideGatesAfterAuth enabled)');
}
/**
* Update gate state after authentication (restore normal appearance)
*/
updateGateState() {
const gates = document.querySelectorAll('.insertr-gate');
gates.forEach(gate => {
// Restore original text if it was saved
const originalText = gate.getAttribute('data-original-text');
if (originalText) {
gate.textContent = originalText;
}
// Restore interactive state
gate.style.pointerEvents = '';
gate.style.opacity = '';
});
console.log('🚪 Editor gates restored to original state');
}
/**
* Create authentication control buttons (bottom-right positioned)
*/
createAuthControls() {
// Check if controls already exist
@@ -220,6 +380,32 @@ export class InsertrAuth {
return this.state.currentUser;
}
/**
* Add minimal styles for editor gates
*/
addGateStyles() {
const styles = `
.insertr-gate {
transition: opacity 0.2s ease;
user-select: none;
}
.insertr-gate:hover {
opacity: 0.7;
}
/* Optional: Hide gates when authenticated (only if hideGatesAfterAuth option is true) */
body.insertr-hide-gates .insertr-gate {
display: none !important;
}
`;
const styleSheet = document.createElement('style');
styleSheet.type = 'text/css';
styleSheet.innerHTML = styles;
document.head.appendChild(styleSheet);
}
/**
* Add styles for authentication controls
*/
@@ -227,11 +413,12 @@ export class InsertrAuth {
const styles = `
.insertr-auth-controls {
position: fixed;
top: 20px;
bottom: 20px;
right: 20px;
z-index: 9999;
display: flex;
gap: 10px;
flex-direction: column;
gap: 8px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
@@ -281,6 +468,7 @@ export class InsertrAuth {
padding: 8px 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 200px;
}
.insertr-status-content {