Skip to main content
Glama
skyrmionz

ChatGPT Interactive Components Examples

by skyrmionz
target-auth.htmlβ€’16 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Target Sign In</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif; background: #f5f5f5; padding: 16px; transition: background 0.2s; } body.dark-mode { background: #1a1a1a; } .container { max-width: 420px; margin: 0 auto; background: white; border-radius: 12px; padding: 32px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); border: 1px solid rgba(0, 0, 0, 0.1); transition: all 0.2s; } body.dark-mode .container { background: #2d2d2d; border-color: rgba(255, 255, 255, 0.1); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); } .header { text-align: center; margin-bottom: 32px; } .target-logo { width: 64px; height: 64px; margin: 0 auto 16px; background: linear-gradient(135deg, #cc0000 0%, #ff0000 100%); border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(204, 0, 0, 0.3); position: relative; } .target-logo::before { content: ''; position: absolute; width: 44px; height: 44px; border-radius: 50%; background: white; } .target-logo::after { content: ''; position: absolute; width: 24px; height: 24px; border-radius: 50%; background: #cc0000; } h1 { font-size: 24px; font-weight: 600; color: #333; margin-bottom: 8px; transition: color 0.2s; } body.dark-mode h1 { color: #f0f0f0; } .subtitle { color: #666; font-size: 14px; transition: color 0.2s; } body.dark-mode .subtitle { color: #a0a0a0; } .form-group { margin-bottom: 16px; } label { display: block; margin-bottom: 6px; font-weight: 500; font-size: 14px; color: #333; transition: color 0.2s; } body.dark-mode label { color: #e0e0e0; } input { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; transition: all 0.2s; background: white; color: #333; } body.dark-mode input { background: #1a1a1a; border-color: #444; color: #f0f0f0; } input:focus { outline: none; border-color: #cc0000; box-shadow: 0 0 0 3px rgba(204, 0, 0, 0.1); } body.dark-mode input:focus { box-shadow: 0 0 0 3px rgba(204, 0, 0, 0.2); } .code-inputs { display: flex; gap: 8px; justify-content: center; margin: 24px 0; } .code-input { width: 48px; height: 56px; text-align: center; font-size: 24px; font-weight: 600; border: 2px solid #ddd; border-radius: 8px; background: white; color: #333; transition: all 0.2s; } body.dark-mode .code-input { background: #1a1a1a; border-color: #444; color: #f0f0f0; } .code-input:focus { border-color: #cc0000; box-shadow: 0 0 0 3px rgba(204, 0, 0, 0.1); } body.dark-mode .code-input:focus { box-shadow: 0 0 0 3px rgba(204, 0, 0, 0.2); } .btn-primary { width: 100%; padding: 12px; background: #cc0000; color: white; border: none; border-radius: 6px; font-size: 15px; font-weight: 600; cursor: pointer; transition: background 0.2s; } .btn-primary:hover { background: #b30000; } .btn-primary:active { background: #990000; } .btn-primary:disabled { background: #ccc; cursor: not-allowed; } .success-icon { width: 64px; height: 64px; margin: 0 auto 16px; background: #10b981; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-size: 32px; font-weight: bold; } .confirmation-text { text-align: center; margin-top: 16px; } .confirmation-text p { color: #333; transition: color 0.2s; } body.dark-mode .confirmation-text p { color: #f0f0f0; } .auth-text { color: #333 !important; transition: color 0.2s; } body.dark-mode .auth-text { color: white !important; } .auth-name { font-size: 18px; font-weight: 600; color: #333; transition: color 0.2s; } body.dark-mode .auth-name { color: white; } .user-email { color: #cc0000; font-weight: 600; } .loading { display: inline-block; width: 14px; height: 14px; border: 2px solid #ffffff; border-top-color: transparent; border-radius: 50%; animation: spin 0.8s linear infinite; margin-right: 8px; } @keyframes spin { to { transform: rotate(360deg); } } .hidden { display: none; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .help-text { text-align: center; font-size: 13px; color: #666; margin-top: 16px; transition: color 0.2s; } body.dark-mode .help-text { color: #a0a0a0; } </style> </head> <body> <div class="container"> <div class="header"> <div class="target-logo"></div> <h1>Sign In</h1> <p class="subtitle">Access your Target account</p> </div> <!-- Screen 0: Loading (waiting for sessionId) --> <div id="screen-loading"> <div style="text-align: center;"> <div class="loading-spinner" style=" width: 48px; height: 48px; border: 4px solid #f3f3f3; border-top: 4px solid #cc0000; border-radius: 50%; margin: 0 auto 24px; animation: spin 1s linear infinite; "></div> <p class="subtitle" id="loading-status">Loading authentication portal...</p> </div> </div> <!-- Screen 1: Login --> <div id="screen-login" class="hidden"> <form id="loginForm"> <div class="form-group"> <label for="email">Email</label> <input type="email" id="email" placeholder="you@example.com" required autocomplete="email" /> </div> <div class="form-group"> <label for="password">Password</label> <input type="password" id="password" placeholder="Enter your password" required autocomplete="current-password" /> </div> <button type="submit" class="btn-primary" id="loginBtn"> Sign In </button> </form> </div> <!-- Screen 2: Verification Code --> <div id="screen-verify" class="hidden"> <p class="subtitle" style="text-align: center; margin-bottom: 24px;"> Enter the 6-digit code sent to your phone </p> <form id="verifyForm"> <div class="code-inputs"> <input type="text" class="code-input" maxlength="1" data-index="0" /> <input type="text" class="code-input" maxlength="1" data-index="1" /> <input type="text" class="code-input" maxlength="1" data-index="2" /> <input type="text" class="code-input" maxlength="1" data-index="3" /> <input type="text" class="code-input" maxlength="1" data-index="4" /> <input type="text" class="code-input" maxlength="1" data-index="5" /> </div> <button type="submit" class="btn-primary" id="verifyBtn"> Submit Code </button> </form> </div> <!-- Screen 3: Success --> <div id="screen-success" class="hidden"> <div style="text-align: center;"> <div class="success-icon">βœ“</div> <h1 style="margin-bottom: 8px;">Welcome Back!</h1> <p class="subtitle">You're now signed in</p> <div class="confirmation-text"> <p class="auth-text" style="margin: 16px 0;"> Authenticated as<br> <span id="userName" class="auth-name">Lauren Bailey</span> </p> <p style="font-size: 14px; color: #999;"> <span class="user-email" id="userEmail"></span> </p> </div> </div> </div> <!-- Screen 4: Already Authenticated (shown on second call) --> <div id="screen-already-auth" class="hidden"> <div style="text-align: center;"> <div class="success-icon">βœ“</div> <h1 style="margin-bottom: 8px;">Authentication Verified</h1> <p class="subtitle">You're already signed in</p> <div class="confirmation-text"> <p class="auth-text" style="margin: 16px 0;"> Authenticated as<br> <span class="auth-name">Lauren Bailey</span> </p> <p style="font-size: 14px; color: #999;"> Session active </p> </div> </div> </div> </div> <script> console.log('Target Auth Widget loaded'); console.log('window.openai:', window.openai); // State variables let sessionId = null; let authenticated = false; let pollAttempts = 0; let isInitialized = false; // Apply theme immediately const theme = window.openai?.theme || 'light'; if (theme === 'dark') { document.body.classList.add('dark-mode'); } function checkForSessionId() { const toolOutput = window.openai?.toolOutput || {}; // Update loading status pollAttempts++; const statusEl = document.getElementById('loading-status'); if (statusEl) { statusEl.textContent = `Loading authentication portal...`; } console.log(`[Poll ${pollAttempts}] Checking toolOutput:`, JSON.stringify(toolOutput)); if (toolOutput.sessionId) { sessionId = toolOutput.sessionId; authenticated = toolOutput.authenticated === true; console.log('βœ“ SessionId received:', sessionId); console.log(' Authenticated:', authenticated); // Initialize the widget now that we have sessionId if (!isInitialized) { isInitialized = true; initializeWidget(); } return true; } return false; } function startPolling() { console.log('Starting continuous polling for sessionId...'); // Check immediately if (checkForSessionId()) { return; } // Then check every 200ms indefinitely until we get it const pollInterval = setInterval(() => { if (checkForSessionId()) { clearInterval(pollInterval); } }, 200); } function initializeWidget() { console.log('🎯 Initializing widget with sessionId:', sessionId); // Hide loading screen document.getElementById('screen-loading').classList.add('hidden'); // Check if already authenticated (from server) if (authenticated) { // Show "already authenticated" confirmation screen document.getElementById('screen-already-auth').classList.remove('hidden'); console.log('βœ“ Showing already-authenticated screen'); } else { // Show login screen document.getElementById('screen-login').classList.remove('hidden'); console.log('βœ“ Showing login screen'); } } let userEmail = ''; // Helper function to mark session as authenticated on server async function markSessionAuthenticated(email, name) { if (!sessionId) { console.error('Cannot mark session - no sessionId available!'); return; } try { console.log(`Marking session ${sessionId} as authenticated...`); const response = await fetch('https://chatgpt-components-0d9232341440.herokuapp.com/api/session/authenticate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: sessionId, email: email, name: name }) }); const result = await response.json(); console.log('Session marked as authenticated:', result); return result; } catch (error) { console.error('Error marking session as authenticated:', error); } } // Screen 1: Login Form document.getElementById('loginForm').addEventListener('submit', async (e) => { e.preventDefault(); const email = document.getElementById('email').value; const password = document.getElementById('password').value; const loginBtn = document.getElementById('loginBtn'); userEmail = email; // Show loading loginBtn.disabled = true; loginBtn.innerHTML = '<span class="loading"></span>Signing in...'; // Simulate API call await new Promise(resolve => setTimeout(resolve, 800)); // Move to verification screen document.getElementById('screen-login').classList.add('hidden'); document.getElementById('screen-verify').classList.remove('hidden'); // Focus first code input document.querySelector('.code-input[data-index="0"]').focus(); }); // Screen 2: Verification Code const codeInputs = document.querySelectorAll('.code-input'); // Auto-advance to next input codeInputs.forEach((input, index) => { input.addEventListener('input', (e) => { if (e.target.value.length === 1 && index < codeInputs.length - 1) { codeInputs[index + 1].focus(); } }); input.addEventListener('keydown', (e) => { if (e.key === 'Backspace' && !e.target.value && index > 0) { codeInputs[index - 1].focus(); } }); }); document.getElementById('verifyForm').addEventListener('submit', async (e) => { e.preventDefault(); const code = Array.from(codeInputs).map(input => input.value).join(''); const verifyBtn = document.getElementById('verifyBtn'); if (code.length !== 6) { alert('Please enter all 6 digits'); return; } // Show loading verifyBtn.disabled = true; verifyBtn.innerHTML = '<span class="loading"></span>Verifying...'; // Simulate API call await new Promise(resolve => setTimeout(resolve, 800)); // Move to success screen document.getElementById('screen-verify').classList.add('hidden'); document.getElementById('screen-success').classList.remove('hidden'); document.getElementById('userEmail').textContent = userEmail; // Mark session as authenticated on server await markSessionAuthenticated(userEmail, 'Lauren Bailey'); // Save widget state if (window.openai?.setWidgetState) { await window.openai.setWidgetState({ authenticated: true, email: userEmail, name: 'Lauren Bailey', sessionId: sessionId }); console.log('Widget state saved'); } // Notify ChatGPT if (window.openai?.sendFollowUpMessage) { await window.openai.sendFollowUpMessage({ prompt: `Successfully authenticated as Lauren Bailey (${userEmail}). Session ID: ${sessionId}` }); console.log('Follow-up message sent'); } }); // Start continuous polling for sessionId startPolling(); </script> </body> </html>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/skyrmionz/chatgpt-mcp-server-interactive-components'

If you have feedback or need assistance with the MCP directory API, please join our Discord server