/* SoftSins Dashboard JavaScript */ class Dashboard { constructor() { this.currentUser = null; this.userProfile = null; this.userPreferences = null; this.userAppearancePreferences = null; this.userStories = []; this.availableStories = []; this.currentSection = 'stories'; this.expandedStory = null; this.init(); } async init() { // Wait for Supabase to be ready if (!window.supabase) { setTimeout(() => this.init(), 100); return; } await this.checkAuth(); this.bindEvents(); this.checkHashNavigation(); } checkHashNavigation() { // Check if URL has a hash to navigate to a specific section const hash = window.location.hash.substring(1); if (hash && ['stories', 'preferences', 'settings'].includes(hash)) { this.switchSection(hash); } } async checkAuth() { try { const { data: { session } } = await supabase.auth.getSession(); if (!session || !session.user) { this.showAccessDenied(); return; } this.currentUser = session.user; await this.loadUserData(); this.showDashboard(); } catch (error) { console.error('Auth check error:', error); this.showAccessDenied(); } } async loadUserData() { try { // Load user profile await this.loadUserProfile(); // Load user preferences await this.loadUserPreferences(); // Load appearance preferences for name await this.loadAppearancePreferences(); // Load user orders (purchased stories) await this.loadUserStories(); // Load available stories from audio mapper await this.loadAvailableStories(); // Update UI with data this.updateUI(); } catch (error) { console.error('Error loading user data:', error); } } async loadUserProfile() { const { data, error } = await supabase .from('profiles') .select('*') .eq('id', this.currentUser.id) .single(); if (error && error.code !== 'PGRST116') { console.error('Error loading profile:', error); return; } if (!data) { // Create profile if it doesn't exist await this.createUserProfile(); } else { this.userProfile = data; } } async createUserProfile() { const profileData = { id: this.currentUser.id, username: this.currentUser.email.split('@')[0], display_name: this.currentUser.email.split('@')[0], created_at: new Date().toISOString(), updated_at: new Date().toISOString() }; const { data, error } = await supabase .from('profiles') .insert([profileData]) .select() .single(); if (error) { console.error('Error creating profile:', error); return; } this.userProfile = data; } async loadUserPreferences() { const { data, error } = await supabase .from('user_preferences') .select('*') .eq('user_id', this.currentUser.id) .order('created_at', { ascending: false }) .limit(1) .single(); if (error && error.code !== 'PGRST116') { console.error('Error loading preferences:', error); return; } this.userPreferences = data; } async loadAppearancePreferences() { const { data, error } = await supabase .from('appearance_preferences') .select('*') .eq('user_id', this.currentUser.id) .order('created_at', { ascending: false }) .limit(1); if (error && error.code !== 'PGRST116') { console.error('Error loading appearance preferences:', error); return; } if (data && data.length > 0) { this.userAppearancePreferences = data[0]; } } async loadUserStories() { // Load user stories first const { data: stories, error: storiesError } = await supabase .from('user_stories') .select('*') .eq('user_id', this.currentUser.id) .order('created_at', { ascending: false }); if (storiesError) { console.error('Error loading user stories:', storiesError); return; } if (!stories || stories.length === 0) { this.userStories = []; return; } // Get all audio_file_ids for checking orders const audioFileIds = stories.map(story => story.audio_file_id).filter(Boolean); if (audioFileIds.length === 0) { this.userStories = stories.map(story => ({ ...story, is_purchased: false, purchase_info: null })); return; } // Load orders for these audio templates const { data: orders, error: ordersError } = await supabase .from('orders') .select('audio_template, status, full_audio_url, full_audio_status, personalized_title') .eq('user_id', this.currentUser.id) .in('audio_template', audioFileIds); if (ordersError) { console.error('Error loading orders:', ordersError); // Continue without purchase status this.userStories = stories.map(story => ({ ...story, is_purchased: false, purchase_info: null })); return; } // Map stories with purchase status this.userStories = stories.map(story => { const completedOrder = orders?.find(order => order.audio_template === story.audio_file_id && order.status === 'completed' ); const pendingOrder = orders?.find(order => order.audio_template === story.audio_file_id && order.status === 'pending' ); return { ...story, is_purchased: !!completedOrder, is_processing: !!pendingOrder, purchase_info: completedOrder || pendingOrder || null }; }); console.log('📚 Loaded user stories:', this.userStories); } async loadAvailableStories() { console.log('🎵 Loading available stories from user_stories...'); // First, filter stories that have preview generated (or fallback to true for backward compatibility) const availableStories = this.userStories.filter(story => { // Show story if preview_generated is true OR if it's undefined (backward compatibility) return story.preview_generated !== false; }); // Deduplicate stories by story_name - keep only the most recent one const storyMap = new Map(); availableStories.forEach(story => { const storyName = story.story_name || 'WEIHNACHTEN'; const existingStory = storyMap.get(storyName); // Keep the most recent story (by created_at or updated_at) if (!existingStory || new Date(story.created_at || story.updated_at) > new Date(existingStory.created_at || existingStory.updated_at)) { storyMap.set(storyName, story); } }); // Convert map back to array and format for UI this.availableStories = Array.from(storyMap.values()).map(story => { // Konstruiere vollständige S3 URLs für beide Audio-Typen const s3BaseUrl = 'https://audiobook-outputs-024239493753.s3.eu-north-1.amazonaws.com/audiobooks'; return { id: story.id, title: story.title || 'Unbenannte Geschichte', description: story.description || 'Personalisierte Geschichte', // Separate URLs für Preview und Full Audio previewUrl: story.preview_filename ? `${s3BaseUrl}/${story.preview_filename}` : null, fullUrl: story.audio_filename ? `${s3BaseUrl}/${story.audio_filename}` : null, // Backward compatibility filename: story.preview_filename || story.audio_filename, audioFileId: story.audio_file_id, // Purchase status from database query is_purchased: story.is_purchased || false, is_processing: story.is_processing || false, purchase_info: story.purchase_info || null, // Legacy field for backward compatibility owned: story.is_purchased || false, generated: story.preview_generated !== false, previewGenerated: story.preview_generated, fullGenerated: story.full_generated, firstName: story.quiz_preferences?.firstName || story.first_name, storyName: story.story_name || 'WEIHNACHTEN', quizPreferences: story.quiz_preferences }; }); console.log('✅ Available stories loaded (deduplicated):', this.availableStories); console.log(`📊 Original stories: ${this.userStories.length}, After deduplication: ${this.availableStories.length}`); } generateTagsFromStory(story) { const tags = []; const prefs = story.quizPreferences; if (!prefs) return [{ icon: '🎄', label: story.storyName || 'WEIHNACHTEN' }]; // Add story name tag tags.push({ icon: '🎄', label: story.storyName || 'WEIHNACHTEN' }); // Add appearance tags if available if (prefs.eyeColor) { const eyeIcons = { braun: '👀', blau: '💙', gruen: '💚', grau: '🩶', bunt: '🌈', andere: '👁️' }; tags.push({ icon: eyeIcons[prefs.eyeColor] || '👁️', label: prefs.eyeColor }); } if (prefs.hairColor) { const hairIcons = { blond: '👱', braun: '🤎', schwarz: '⚫', rot: '🔥', grau: '🩶', bunt: '🌈', andere: '💇' }; tags.push({ icon: hairIcons[prefs.hairColor] || '💇', label: prefs.hairColor }); } // Add story tags if available if (prefs.setting) { const settingIcons = { home: '🏠', office: '🏢', nature: '🌲', hotel: '🏨', car: '🚗', fantasy: '🏰' }; tags.push({ icon: settingIcons[prefs.setting] || '📍', label: prefs.setting }); } if (prefs.intensity) { const intensityIcons = { sensual: '🌹', passionate: '🔥', bold: '⚡' }; tags.push({ icon: intensityIcons[prefs.intensity] || '💫', label: prefs.intensity }); } return tags; } generatePersonalizedTitle(preferences) { const { firstName, setting } = preferences; const settingNames = { home: 'Zuhause', office: 'Im Büro', nature: 'In der Natur', hotel: 'Im Hotel', car: 'Unterwegs', fantasy: 'Fantasy' }; const settingTitle = settingNames[setting] || setting; if (firstName && firstName.trim()) { return `${firstName}s Geschichte: ${settingTitle}`; } else { return `Deine Geschichte: ${settingTitle}`; } } generatePersonalizedDescription(preferences) { const { setting, dynamic, intensity } = preferences; const settingDescriptions = { home: 'in vertrauter Umgebung', office: 'voller verbotener Spannung', nature: 'wild und ungezähmt', hotel: 'luxuriös und entspannt', car: 'spontan und aufregend', fantasy: 'in einer magischen Welt' }; const dynamicDescriptions = { gentle_dom: 'beschützender Dominanz', equal: 'gleichberechtigtem Spiel', submissive: 'sanfter Führung' }; const intensityDescriptions = { passionate: 'leidenschaftlich', sensual: 'sinnlich', romantic: 'romantisch' }; return `Eine ${intensityDescriptions[intensity] || 'sinnliche'} Geschichte ${settingDescriptions[setting] || ''} mit ${dynamicDescriptions[dynamic] || 'liebevoller Dominanz'}.`; } generateTitleFromTemplate(template) { const parts = template.split('-'); const [setting, dynamic, relationship, intensity] = parts; const settingNames = { home: 'Zuhause', office: 'Im Büro', nature: 'In der Natur', hotel: 'Im Hotel', car: 'Unterwegs', fantasy: 'Fantasy' }; const intensityNames = { sensual: 'Sinnliche', passionate: 'Leidenschaftliche', bold: 'Mutige' }; return `${intensityNames[intensity] || 'Sinnliche'} Geschichte: ${settingNames[setting] || setting}`; } generateDescriptionFromTemplate(template) { const parts = template.split('-'); const [setting, dynamic, relationship, intensity] = parts; return `Eine ${intensity === 'sensual' ? 'sinnliche' : intensity === 'passionate' ? 'leidenschaftliche' : 'mutige'} Geschichte mit ${dynamic === 'gentle_dom' ? 'sanfter Dominanz' : 'gleichberechtigtem Spiel'} zwischen ${relationship === 'partners' ? 'Partnern' : 'Charakteren'}.`; } generateTagsFromTemplate(template) { const parts = template.split('-'); const [setting, dynamic, relationship, intensity] = parts; return [ { label: this.getDisplayName('setting', setting), icon: '🏠' }, { label: this.getDisplayName('dynamic', dynamic), icon: '⚡' }, { label: this.getDisplayName('relationship', relationship), icon: '💕' }, { label: this.getDisplayName('intensity', intensity), icon: '🔥' } ]; } updateUI() { // Update header this.updateHeader(); // Update stories this.updateStoriesSection(); // Update preferences this.updatePreferencesSection(); } updateHeader() { // Priority order: appearance preferences name, story preferences name, profile name, then email let displayName = this.currentUser.email.split('@')[0]; // fallback // Check if user has a name from story preferences if (this.userPreferences?.first_name) { displayName = this.userPreferences.first_name; } // Check if user has a name from appearance preferences (higher priority) if (this.userAppearancePreferences?.first_name) { displayName = this.userAppearancePreferences.first_name; } // Override with profile display name only if it's a real name (not email) if (this.userProfile?.display_name && this.userProfile.display_name !== this.currentUser.email.split('@')[0] && this.userProfile.display_name !== this.currentUser.email) { displayName = this.userProfile.display_name; } document.getElementById('userDisplayName').textContent = displayName; const initials = this.getInitials(displayName); document.getElementById('userInitials').textContent = initials; } showLoadingMessage() { const storiesList = document.getElementById('storiesList'); storiesList.innerHTML = `

Geschichten werden geladen...

Einen Moment bitte, während wir deine SoftSins laden.

`; } updateStoriesSection() { const storiesList = document.getElementById('storiesList'); if (this.availableStories.length === 0) { storiesList.innerHTML = `

Noch keine SoftSins

Erstelle deine erste personalisierte Geschichte!

Jetzt erstellen
`; return; } // Check if user has preferences and we're showing only their story const hasUserPreferences = this.userPreferences && this.userPreferences.setting; const showingOnlyUserStory = hasUserPreferences && this.availableStories.length === 1 && this.availableStories[0].generated; const storiesHtml = this.availableStories.map((story, index) => { const isOwned = story.is_purchased; const isProcessing = story.is_processing; const isGenerating = isOwned && !story.purchase_info?.full_audio_url; // Start polling for purchased stories without full audio URL if (isGenerating) { console.log(`🔄 Auto-starting polling for generating story: ${story.audioFileId}`); setTimeout(() => { startAudioPolling(story.audioFileId, story.title); }, 1000); // Small delay to ensure DOM is ready } const lockIcon = ` `; const loadingSpinner = `
`; const expandIcon = ` `; // Generate tags from quiz preferences const tags = this.generateTagsFromStory(story); const tagsHtml = tags.map(tag => `${tag.icon} ${tag.label}` ).join(''); return `

${story.title}

${isProcessing ? `
${loadingSpinner}
` : !isOwned ? `
${lockIcon}
` : '' }
${expandIcon}

${story.description}

${isOwned ? isGenerating ? `` : `` : isProcessing ? `` : `` } Dauer: ~12-15 Min
`; }).join(''); // Add browse more stories option if showing only user's story const browseMoreHtml = showingOnlyUserStory ? `

Weitere SoftSins entdecken

Möchtest du weitere verfügbare Geschichten durchstöbern?

` : ''; storiesList.innerHTML = storiesHtml + browseMoreHtml; } updatePreferencesSection() { // This function is now handled by dashboard-preferences.js // Keeping empty to avoid conflicts console.log('🔄 updatePreferencesSection: handled by dashboard-preferences.js'); return; if (prefs.setting) { prefsHtml += `
🏠 Schauplatz: ${this.getDisplayName('setting', prefs.setting)}
`; } if (prefs.dynamic) { prefsHtml += `
⚡ Dynamik: ${this.getDisplayName('dynamic', prefs.dynamic)}
`; } if (prefs.relationship) { prefsHtml += `
💕 Beziehung: ${this.getDisplayName('relationship', prefs.relationship)}
`; } if (prefs.intensity) { prefsHtml += `
🔥 Intensität: ${this.getDisplayName('intensity', prefs.intensity)}
`; } if (prefs.first_name) { prefsHtml += `
👤 Name: ${prefs.first_name}
`; } preferencesDiv.innerHTML = prefsHtml || '

Keine Präferenzen verfügbar.

'; } toggleStoryDetails(storyId) { const detailsElement = document.getElementById(`details-${storyId}`); const headerElement = document.querySelector(`[data-story-id="${storyId}"] .story-header`); // Close any other expanded stories if (this.expandedStory && this.expandedStory !== storyId) { const prevDetails = document.getElementById(`details-${this.expandedStory}`); const prevHeader = document.querySelector(`[data-story-id="${this.expandedStory}"] .story-header`); if (prevDetails) { prevDetails.classList.remove('expanded'); prevHeader.classList.remove('expanded'); } } // Toggle current story const isExpanding = !detailsElement.classList.contains('expanded'); if (isExpanding) { detailsElement.classList.add('expanded'); headerElement.classList.add('expanded'); this.expandedStory = storyId; } else { detailsElement.classList.remove('expanded'); headerElement.classList.remove('expanded'); this.expandedStory = null; } } bindEvents() { // Dashboard navigation document.querySelectorAll('.dash-nav-btn').forEach(btn => { btn.addEventListener('click', () => { const section = btn.dataset.section; this.switchSection(section); }); }); } switchSection(section) { // Update navigation document.querySelectorAll('.dash-nav-btn').forEach(btn => { btn.classList.remove('active'); }); document.querySelector(`[data-section="${section}"]`).classList.add('active'); // Update content document.querySelectorAll('.dashboard-section').forEach(sec => { sec.classList.remove('active'); }); document.getElementById(`${section}-section`).classList.add('active'); this.currentSection = section; } showDashboard() { document.getElementById('loadingScreen').style.display = 'none'; document.getElementById('accessDenied').style.display = 'none'; document.getElementById('dashboardContent').style.display = 'block'; // Check for hash navigation (e.g., #preferences) this.handleHashNavigation(); } handleHashNavigation() { const hash = window.location.hash.substring(1); // Remove # from hash if (hash) { console.log('🔗 Hash navigation detected:', hash); // Switch to the section specified in hash if (hash === 'preferences' || hash === 'stories' || hash === 'settings') { this.switchSection(hash); } // Clear the hash after navigation window.history.replaceState(null, null, ' '); } } showAccessDenied() { document.getElementById('loadingScreen').style.display = 'none'; document.getElementById('dashboardContent').style.display = 'none'; document.getElementById('accessDenied').style.display = 'flex'; } getInitials(name) { return name .split(' ') .map(part => part.charAt(0).toUpperCase()) .join('') .substring(0, 2); } getTimeAgo(date) { const now = new Date(); const diffMs = now - date; const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); if (diffDays === 0) return 'Heute'; if (diffDays === 1) return 'Gestern'; if (diffDays < 7) return `Vor ${diffDays} Tagen`; if (diffDays < 30) return `Vor ${Math.floor(diffDays / 7)} Wochen`; return `Vor ${Math.floor(diffDays / 30)} Monaten`; } getDisplayName(type, value) { const displayNames = { setting: { home: 'Zuhause', office: 'Büro', nature: 'Natur', hotel: 'Hotel', car: 'Auto', fantasy: 'Fantasy' }, dynamic: { gentle_dom: 'Sanft dominierend', equal: 'Gleichwertig', submissive: 'Hingebungsvoll' }, relationship: { partners: 'Partner', friends_to_lovers: 'Freunde → Liebende', strangers: 'Fremde', authority: 'Autoritätsperson' }, intensity: { sensual: 'Sinnlich', passionate: 'Leidenschaftlich', bold: 'Mutig' } }; return displayNames[type]?.[value] || value; } editPreferences() { // Redirect to customizer to edit preferences window.location.href = 'customizer.html'; } async changePassword() { const newPassword = prompt('Neues Passwort eingeben (mindestens 6 Zeichen):'); if (!newPassword || newPassword.length < 6) { alert('Passwort muss mindestens 6 Zeichen lang sein.'); return; } try { const { error } = await supabase.auth.updateUser({ password: newPassword }); if (error) throw error; this.showSuccessMessage('Passwort erfolgreich geändert!'); } catch (error) { console.error('Error changing password:', error); alert('Fehler beim Ändern des Passworts: ' + error.message); } } async changeEmail() { const newEmail = prompt('Neue E-Mail-Adresse eingeben:'); if (!newEmail || !this.isValidEmail(newEmail)) { alert('Bitte gib eine gültige E-Mail-Adresse ein.'); return; } try { const { error } = await supabase.auth.updateUser({ email: newEmail }); if (error) throw error; this.showSuccessMessage('E-Mail-Adresse wird geändert. Bitte überprüfe dein E-Mail-Postfach zur Bestätigung.'); } catch (error) { console.error('Error changing email:', error); alert('Fehler beim Ändern der E-Mail: ' + error.message); } } async deleteAccount() { if (!confirm('Möchtest du dein Konto wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.')) { return; } const confirmation = prompt('Gib "DELETE" ein, um das Löschen zu bestätigen:'); if (confirmation !== 'DELETE') { alert('Löschung abgebrochen.'); return; } try { // 🔒 SECURE: Use SecureAPIClient for account deletion console.log('🔒 Starting secure account deletion...'); const result = await window.secureAPI.deleteUserAccount(); if (result.success) { alert('Dein Konto wurde erfolgreich gelöscht.'); window.location.href = 'index.html'; } else { throw new Error('Account deletion failed'); } } catch (error) { console.error('🔒 Secure account deletion failed:', error); if (error.message.includes('Rate limit')) { alert('Bitte warte einen Moment bevor du es erneut versuchst.'); } else if (error.message.includes('cancelled')) { // User cancelled - do nothing return; } else { alert('Fehler beim Löschen des Kontos. Bitte kontaktiere den Support.'); } } } isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } showSuccessMessage(message) { // Create and show success toast const toast = document.createElement('div'); toast.style.cssText = ` position: fixed; top: 20px; right: 20px; background: #9AE6B4; color: #276749; padding: 12px 16px; border-radius: 8px; font-weight: 500; z-index: 10000; box-shadow: 0 4px 12px rgba(0,0,0,0.1); `; toast.textContent = message; document.body.appendChild(toast); setTimeout(() => { if (document.body.contains(toast)) { document.body.removeChild(toast); } }, 4000); } } // Global functions for onclick handlers function switchSection(section) { if (window.dashboard) { window.dashboard.switchSection(section); } } function openLoginModal() { if (window.authModal) { window.authModal.open('login'); } } // Global functions for story interaction async function playFullStory(audioFilename, storyTitle) { console.log('🎵 playFullStory called with:', { audioFilename, storyTitle }); try { // 1. Check if user is authenticated const { data: { user }, error: authError } = await window.supabase.auth.getUser(); if (authError || !user) { console.log('🔒 User not authenticated for full story playback'); alert('Bitte melde dich an, um Geschichten vollständig anzuhören.'); if (window.authModal) { window.authModal.open('login'); } else { window.location.href = 'index.html#login'; } return; } // 2. Extract template from filename to check ownership let template = ''; if (audioFilename) { // Remove .mp3 extension if present const baseName = audioFilename.replace('.mp3', ''); template = baseName; } console.log('🔍 Checking ownership for template:', template); // 3. Check if user owns this story const { data: existingOrder, error: checkError } = await window.supabase .from('orders') .select('id, audio_template, status, full_audio_url, personalized_title') .eq('user_id', user.id) .eq('audio_template', template); if (checkError) { console.error('❌ Error checking ownership:', checkError); console.warn('⚠️ Could not verify ownership, allowing playback'); } else if (!existingOrder || existingOrder.length === 0) { // User doesn't own this story - redirect to preview instead console.log('❌ User does not own this story, showing preview instead'); alert('Du besitzt diese Geschichte noch nicht. Du kannst eine 30-Sekunden-Vorschau anhören.'); // Call preview function instead playPreview(audioFilename, storyTitle); return; } else { console.log('✅ User owns this story:', existingOrder[0]); // Check if order is successful/completed const order = existingOrder[0]; if (order.status && order.status !== 'completed' && order.status !== 'paid') { console.log('⚠️ Order status:', order.status); alert('Deine Bestellung für diese Geschichte ist noch nicht abgeschlossen. Bitte prüfe deine E-Mails oder kontaktiere den Support.'); return; } } console.log('✅ User authorized for full story playback'); // 4. Get full audio URL from the order and store for player const order = existingOrder[0]; const fullAudioUrl = order.full_audio_url; if (!fullAudioUrl) { console.log('⏳ Audio not ready yet, starting polling...'); startAudioPolling(audioFilename, storyTitle); return; } console.log('✅ Using full audio URL:', fullAudioUrl); sessionStorage.setItem('userAudioFile', fullAudioUrl); sessionStorage.setItem('storyTitle', order.personalized_title || storyTitle); sessionStorage.setItem('isFullStory', 'true'); // Mark as full story console.log('💾 Stored in sessionStorage:', { userAudioFile: sessionStorage.getItem('userAudioFile'), storyTitle: sessionStorage.getItem('storyTitle'), isFullStory: sessionStorage.getItem('isFullStory') }); console.log('🔄 Navigating to player.html...'); // 5. Navigate to player window.location.href = 'player.html'; } catch (error) { console.error('❌ Error in playFullStory:', error); alert('Ein Fehler ist aufgetreten: ' + error.message); } } function playPreview(audioFilename, storyTitle) { console.log('🎵 playPreview called with:', { audioFilename, storyTitle }); try { // Store preview info in sessionStorage (using same keys as player expects) // Extract just the filename from the full URL for the player const justFilename = audioFilename.split('/').pop(); sessionStorage.setItem('userAudioFile', justFilename); sessionStorage.setItem('storyTitle', storyTitle); sessionStorage.setItem('isFullStory', 'false'); // Mark as preview only console.log('💾 Stored in sessionStorage for preview:', { userAudioFile: sessionStorage.getItem('userAudioFile'), storyTitle: sessionStorage.getItem('storyTitle'), isFullStory: sessionStorage.getItem('isFullStory') }); console.log('🔄 Attempting to navigate to player.html...'); // Add a small delay and try navigation setTimeout(() => { console.log('🚀 Executing navigation...'); // Try multiple navigation methods try { window.location.href = 'player_aws.html'; console.log('✅ Navigation via window.location.href initiated'); } catch (navError) { console.warn('🔄 window.location failed, trying assign...', navError); try { window.location.assign('player_aws.html'); console.log('✅ Navigation via assign initiated'); } catch (assignError) { console.warn('🔄 assign failed, trying replace...', assignError); try { window.location.replace('player_aws.html'); console.log('✅ Navigation via replace initiated'); } catch (replaceError) { console.error('❌ All navigation methods failed, trying window.open...', replaceError); // Failsafe: open in new tab/window window.open('player_aws.html', '_self'); } } } }, 100); } catch (error) { console.error('❌ Error in playPreview:', error); alert('Fehler beim Öffnen des Players: ' + error.message); } } async function purchaseStory(audioFileId, storyId) { console.log('🛒 Purchase story requested for audioFileId:', audioFileId, 'storyId:', storyId); try { // 1. Check if user is authenticated const { data: { user }, error: authError } = await window.supabase.auth.getUser(); if (authError || !user) { console.log('🔒 User not authenticated, opening login modal'); // Show login modal if (window.authModal) { window.authModal.open('login'); } else { window.location.href = 'index.html#login'; } return; } console.log('✅ User authenticated, preparing purchase for:', user.email); // 2. Check if user already owns this audio by audioFileId const { data: existingOrder, error: checkError } = await window.supabase .from('orders') .select('id, status') .eq('user_id', user.id) .eq('audio_template', audioFileId); if (checkError) { console.error('❌ Error checking existing ownership:', checkError); } if (existingOrder && existingOrder.length > 0) { const completedOrder = existingOrder.find(order => order.status === 'completed'); if (completedOrder) { console.log('✅ User already owns this story'); alert('Du besitzt diese Geschichte bereits! Öffne dein Dashboard um sie anzuhören.'); return; } } // 3. Set audioFileId in session storage for Stripe checkout sessionStorage.setItem('audioFileId', audioFileId); // 4. Trigger Stripe checkout using existing StripeCheckoutManager if (window.stripeCheckout) { console.log('🚀 Triggering Stripe checkout...'); await window.stripeCheckout.handlePurchaseClick(); } else { console.error('❌ Stripe checkout manager not available'); alert('Fehler: Zahlungssystem nicht verfügbar. Bitte lade die Seite neu.'); } } catch (error) { console.error('❌ Error in purchaseStory:', error); alert('Ein Fehler ist aufgetreten: ' + error.message); } } function toggleStoryDetails(template) { if (window.dashboard) { window.dashboard.toggleStoryDetails(template); } } function editPreferences() { if (window.dashboard) { window.dashboard.editPreferences(); } } function changePassword() { if (window.dashboard) { window.dashboard.changePassword(); } } function changeEmail() { if (window.dashboard) { window.dashboard.changeEmail(); } } function deleteAccount() { if (window.dashboard) { window.dashboard.deleteAccount(); } } function showAllStories() { // Simple coming soon modal showComingSoonModal(); } function toggleStoryDetails(storyId) { if (window.dashboard) { window.dashboard.toggleStoryDetails(storyId); } } async function playFullStory(audioFileId, title) { console.log('🎵 Playing full story for audioFileId:', audioFileId, title); try { // 1. Check if user is authenticated const { data: { user }, error: authError } = await window.supabase.auth.getUser(); if (authError || !user) { console.log('🔒 User not authenticated'); if (window.authModal) { window.authModal.open('login'); } else { window.location.href = 'index.html#login'; } return; } // 2. Query orders table to get full audio URL console.log('🔍 Querying orders for audioFileId:', audioFileId); const { data: order, error: orderError } = await window.supabase .from('orders') .select('full_audio_url, full_audio_status, audio_template, personalized_title') .eq('audio_template', audioFileId) .eq('user_id', user.id) .eq('status', 'completed') .single(); console.log('🔍 Order query result:', { order, orderError, audioFileId, userId: user.id }); if (orderError) { console.error('❌ Order query failed:', orderError); // Debug: Try to find ANY order for this user const { data: allOrders } = await window.supabase .from('orders') .select('audio_template, full_audio_url, status') .eq('user_id', user.id); console.log('🔍 DEBUG - All orders for user:', allOrders); alert('Diese Geschichte wurde noch nicht gekauft oder ist nicht verfügbar.'); return; } if (!order || !order.full_audio_url) { console.error('❌ No full audio URL found for order:', order); alert('Die Vollversion wird gerade erstellt. Bitte versuche es in wenigen Minuten erneut.'); return; } console.log('✅ Full audio URL found:', order.full_audio_url); // 3. Open audio player with full audio URL const playerUrl = 'player_aws.html'; // Store COMPLETE full audio URL in sessionStorage for direct loading sessionStorage.setItem('userAudioFile', order.full_audio_url); sessionStorage.setItem('audioTitle', title || order.personalized_title || 'Vollversion'); sessionStorage.setItem('isFullVersion', 'true'); console.log('💾 Stored full audio data:', { url: order.full_audio_url, title: title || order.personalized_title, isFullVersion: true }); // Open player in new window const newWindow = window.open(playerUrl, '_blank', 'width=800,height=600,resizable=yes,scrollbars=yes'); if (!newWindow) { console.error('❌ Popup blocked'); alert('Popup wurde blockiert. Bitte erlaube Popups für diese Website.'); } else { console.log('✅ Player opened successfully'); } } catch (error) { console.error('❌ Error in playFullStory:', error); alert('Fehler beim Öffnen der Vollversion: ' + error.message); } } // playPreview function is defined above (line 825) function showComingSoonModal() { const modal = document.createElement('div'); modal.className = 'modal'; modal.style.cssText = ` display: flex; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 1000; align-items: center; justify-content: center; `; modal.innerHTML = ` `; // Close modal when clicking outside modal.addEventListener('click', (e) => { if (e.target === modal) { modal.remove(); } }); document.body.appendChild(modal); } // Player functions for audio playback let currentDashboardBlobUrl = null; // playPreview is defined above as popup modal version async function playFullStoryDashboard(audioUrl) { console.log('🎭 Playing full story with blob loading:', audioUrl) await loadAndPlayAudio(audioUrl, 'full') } async function loadAndPlayAudio(audioUrl, type) { try { // Create or get existing audio element let audio = document.getElementById('dashboardAudio') if (!audio) { audio = document.createElement('audio') audio.id = 'dashboardAudio' audio.controls = true audio.style.width = '100%' audio.style.marginTop = '10px' audio.style.maxWidth = '500px' audio.style.borderRadius = '8px' // Insert after stories list const storiesSection = document.getElementById('stories-section') if (storiesSection) { storiesSection.appendChild(audio) } else { document.body.appendChild(audio) } } // Clean up previous blob URL if (currentDashboardBlobUrl) { URL.revokeObjectURL(currentDashboardBlobUrl) currentDashboardBlobUrl = null } // Show loading state audio.style.opacity = '0.5' console.log(`🔄 Fetching ${type} audio as blob...`) // Fetch audio as blob (CORS-compatible) const response = await fetch(audioUrl) if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`) } const audioBlob = await response.blob() console.log(`✅ Audio blob loaded: ${audioBlob.size} bytes`) // Create secure blob URL currentDashboardBlobUrl = URL.createObjectURL(audioBlob) // Set source and load audio.src = currentDashboardBlobUrl audio.style.opacity = '1' audio.load() // Auto-play const playPromise = audio.play() if (playPromise !== undefined) { await playPromise console.log(`🎵 ${type} audio started playing`) } } catch (error) { console.error(`❌ Audio loading failed for ${type}:`, error) // Show detailed error let errorMsg = `Audio konnte nicht geladen werden.\n\n` errorMsg += `Typ: ${type}\n` errorMsg += `URL: ${audioUrl}\n` errorMsg += `Fehler: ${error.message}` alert(errorMsg) // Reset audio element const audio = document.getElementById('dashboardAudio') if (audio) { audio.style.opacity = '1' audio.removeAttribute('src') } } } // Global functions for story interaction async function playFullStory(audioFilename, storyTitle) { console.log('🎵 playFullStory called with:', { audioFilename, storyTitle }); try { // 1. Check if user is authenticated const { data: { user }, error: authError } = await window.supabase.auth.getUser(); if (authError || !user) { console.log('🔒 User not authenticated for full story playback'); alert('Bitte melde dich an, um Geschichten vollständig anzuhören.'); if (window.authModal) { window.authModal.open('login'); } else { window.location.href = 'index.html#login'; } return; } // 2. Extract template from filename to check ownership let template = ''; if (audioFilename) { // Remove .mp3 extension if present const baseName = audioFilename.replace('.mp3', ''); template = baseName; } console.log('🔍 Checking ownership for template:', template); // 3. Check if user owns this story const { data: existingOrder, error: checkError } = await window.supabase .from('orders') .select('id, audio_template, status, full_audio_url, personalized_title') .eq('user_id', user.id) .eq('audio_template', template); if (checkError) { console.error('❌ Error checking ownership:', checkError); console.warn('⚠️ Could not verify ownership, allowing playback'); } else if (!existingOrder || existingOrder.length === 0) { // User doesn't own this story - redirect to preview instead console.log('❌ User does not own this story, showing preview instead'); alert('Du besitzt diese Geschichte noch nicht. Du kannst eine 30-Sekunden-Vorschau anhören.'); // Call preview function instead playPreview(audioFilename, storyTitle); return; } else { console.log('✅ User owns this story:', existingOrder[0]); // Check if order is successful/completed const order = existingOrder[0]; if (order.status && order.status !== 'completed' && order.status !== 'paid') { console.log('⚠️ Order status:', order.status); alert('Deine Bestellung für diese Geschichte ist noch nicht abgeschlossen. Bitte prüfe deine E-Mails oder kontaktiere den Support.'); return; } } console.log('✅ User authorized for full story playback'); // 4. Get full audio URL from the order and store for player const order = existingOrder[0]; const fullAudioUrl = order.full_audio_url; if (!fullAudioUrl) { console.log('⏳ Audio not ready yet, starting polling...'); startAudioPolling(audioFilename, storyTitle); return; } console.log('✅ Using full audio URL:', fullAudioUrl); // Extract just the filename from the full URL for the player const fullAudioFilename = fullAudioUrl.split('/').pop(); sessionStorage.setItem('userAudioFile', fullAudioFilename); sessionStorage.setItem('storyTitle', order.personalized_title || storyTitle); sessionStorage.setItem('isFullStory', 'true'); console.log('💾 Stored in sessionStorage:', { userAudioFile: sessionStorage.getItem('userAudioFile'), storyTitle: sessionStorage.getItem('storyTitle'), isFullStory: sessionStorage.getItem('isFullStory') }); console.log('🔄 Navigating to player.html...'); // 5. Navigate to player window.location.href = 'player.html'; } catch (error) { console.error('❌ Error in playFullStory:', error); alert('Ein Fehler ist aufgetreten: ' + error.message); } } // playPreview function is defined above (popup player version) // Global polling system for audio generation let pollingIntervals = {}; function updatePlayButton(audioFileId, status) { console.log(`🔄 Updating button status: ${audioFileId} -> ${status}`); // More specific button selector const buttonSelectors = [ `[onclick*="playFullStory('${audioFileId}'"]`, `[onclick*='playFullStory("${audioFileId}"']`, `[onclick*="playFullStory(\\'${audioFileId}\\'"]` ]; let buttonsFound = 0; buttonSelectors.forEach(selector => { document.querySelectorAll(selector).forEach(button => { console.log(`🎯 Found button with selector: ${selector}`); buttonsFound++; button.setAttribute('data-status', status); if (status === 'loading') { console.log(`🔄 Setting button to loading state`); button.disabled = true; // Don't change innerHTML - CSS will handle visual } else if (status === 'ready') { console.log(`✅ Setting button to ready state`); button.disabled = false; button.removeAttribute('data-status'); } else if (status === 'timeout') { console.log(`⚠️ Setting button to timeout state`); button.disabled = false; } }); }); console.log(`🔍 Total buttons found: ${buttonsFound}`); if (buttonsFound === 0) { console.warn(`❌ No buttons found for audioFileId: ${audioFileId}`); } } async function checkAudioStatus(audioFileId, title, checkCount) { console.log(`🔍 Checking audio status (${checkCount}): ${audioFileId}`); try { const { data: { user } } = await window.supabase.auth.getUser(); if (!user) return; const { data: orders } = await window.supabase .from('orders') .select('full_audio_url, personalized_title') .eq('user_id', user.id) .eq('audio_template', audioFileId) .eq('status', 'completed'); if (orders && orders.length > 0 && orders[0].full_audio_url) { console.log('✅ Audio is ready!'); clearInterval(pollingIntervals[audioFileId]); updatePlayButton(audioFileId, 'ready'); // Auto-play the ready audio playFullStory(audioFileId, orders[0].personalized_title || title); } } catch (error) { console.error('❌ Error checking audio status:', error); } } function startAudioPolling(audioFileId, title) { console.log(`🔄 Starting audio polling for: ${audioFileId}`); // Clear any existing interval if (pollingIntervals[audioFileId]) { clearInterval(pollingIntervals[audioFileId]); } updatePlayButton(audioFileId, 'loading'); let checkCount = 0; const maxChecks = 60; // 10 minutes (60 * 10s) // Initial check after 5 seconds setTimeout(() => checkAudioStatus(audioFileId, title, 1), 5000); // Then check every 10 seconds for first 3 minutes pollingIntervals[audioFileId] = setInterval(() => { checkCount++; if (checkCount >= maxChecks) { console.log('⏰ Audio polling timeout'); clearInterval(pollingIntervals[audioFileId]); updatePlayButton(audioFileId, 'timeout'); return; } // After 3 minutes (18 checks), slow down to every 30 seconds if (checkCount === 18) { clearInterval(pollingIntervals[audioFileId]); pollingIntervals[audioFileId] = setInterval(() => { checkCount++; if (checkCount >= maxChecks) { clearInterval(pollingIntervals[audioFileId]); updatePlayButton(audioFileId, 'timeout'); return; } checkAudioStatus(audioFileId, title, checkCount); }, 30000); } checkAudioStatus(audioFileId, title, checkCount); }, 10000); } // Initialize dashboard when DOM is ready document.addEventListener('DOMContentLoaded', function() { window.dashboard = new Dashboard(); });