| | |
| | const supabaseUrl = 'https://your-project.supabase.co'; |
| | const supabaseKey = 'your-anon-key'; |
| | const supabase = window.supabase.createClient(supabaseUrl, supabaseKey); |
| |
|
| | |
| | let currentView = 'dashboard'; |
| | let recordingSession = { |
| | sentences: [], |
| | currentIndex: 0, |
| | recordings: [], |
| | startTime: null |
| | }; |
| |
|
| | let mediaRecorder = null; |
| | let audioChunks = []; |
| | let recordingStartTime = null; |
| |
|
| | |
| | const views = { |
| | dashboard: document.getElementById('dashboard-view'), |
| | recording: document.getElementById('recording-view'), |
| | admin: document.getElementById('admin-view') |
| | }; |
| |
|
| | |
| | document.addEventListener('DOMContentLoaded', () => { |
| | initializeEventListeners(); |
| | loadDashboardData(); |
| | checkAdminAccess(); |
| | }); |
| |
|
| | |
| | function initializeEventListeners() { |
| | |
| | document.getElementById('start-recording-btn').addEventListener('click', startRecordingSession); |
| | document.getElementById('recent-recordings-btn').addEventListener('click', toggleRecentWork); |
| | |
| | |
| | document.getElementById('record-btn').addEventListener('click', toggleRecording); |
| | document.getElementById('skip-btn').addEventListener('click', skipSentence); |
| | document.getElementById('next-btn').addEventListener('click', nextSentence); |
| | |
| | |
| | } |
| |
|
| | |
| | function showView(viewName) { |
| | Object.keys(views).forEach(key => { |
| | views[key].classList.add('hidden'); |
| | }); |
| | if (views[viewName]) { |
| | views[viewName].classList.remove('hidden'); |
| | currentView = viewName; |
| | |
| | if (viewName === 'admin') { |
| | loadAdminData(); |
| | } else if (viewName === 'dashboard') { |
| | loadDashboardData(); |
| | } |
| | } |
| | } |
| |
|
| | |
| | async function loadDashboardData() { |
| | try { |
| | |
| | const mockData = { |
| | totalEarnings: '12.50', |
| | completedCount: 125, |
| | pendingCount: 8 |
| | }; |
| | |
| | document.getElementById('total-earnings').textContent = mockData.totalEarnings; |
| | document.getElementById('completed-count').textContent = mockData.completedCount; |
| | document.getElementById('pending-count').textContent = mockData.pendingCount; |
| | |
| | loadRecentRecordings(); |
| | } catch (error) { |
| | console.error('Error loading dashboard:', error); |
| | } |
| | } |
| |
|
| | function loadRecentRecordings() { |
| | |
| | const recentRecordings = [ |
| | { id: 1, frenchText: "Bonjour, comment allez-vous?", status: 'approved', date: '2024-01-15' }, |
| | { id: 2, frenchText: "Je voudrais un café", status: 'pending', date: '2024-01-15' }, |
| | { id: 3, frenchText: "Où est la gare?", status: 'approved', date: '2024-01-14' } |
| | ]; |
| | |
| | const recentList = document.getElementById('recent-list'); |
| | recentList.innerHTML = recentRecordings.map(rec => ` |
| | <div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg border border-gray-200"> |
| | <div class="flex-1"> |
| | <p class="text-sm text-gray-800">${rec.frenchText}</p> |
| | <p class="text-xs text-gray-500">${rec.date}</p> |
| | </div> |
| | <span class="px-2 py-1 text-xs font-medium rounded-full ${ |
| | rec.status === 'approved' ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800' |
| | }"> |
| | ${rec.status} |
| | </span> |
| | </div> |
| | `).join(''); |
| | } |
| |
|
| | function toggleRecentWork() { |
| | const recentWork = document.getElementById('recent-work'); |
| | recentWork.classList.toggle('hidden'); |
| | } |
| |
|
| | |
| | async function startRecordingSession() { |
| | try { |
| | |
| | recordingSession.sentences = [ |
| | { id: 1, french_text: "Bonjour, comment allez-vous?", domain: "greetings" }, |
| | { id: 2, french_text: "Je voudrais un café, s'il vous plaît", domain: "food" }, |
| | { id: 3, french_text: "Où se trouve la bibliothèque?", domain: "directions" }, |
| | { id: 4, french_text: "Quel temps fait-il aujourd'hui?", domain: "weather" }, |
| | { id: 5, french_text: "Merci beaucoup pour votre aide", domain: "gratitude" }, |
| | { id: 6, french_text: "Je ne comprends pas", domain: "communication" }, |
| | { id: 7, french_text: "À quelle heure fermez-vous?", domain: "time" }, |
| | { id: 8, french_text: "Combien ça coûte?", domain: "shopping" }, |
| | { id: 9, french_text: "Pouvez-vous répéter, s'il vous plaît?", domain: "communication" }, |
| | { id: 10, french_text: "J'ai besoin d'un taxi", domain: "transport" } |
| | ]; |
| | |
| | recordingSession.currentIndex = 0; |
| | recordingSession.recordings = []; |
| | recordingSession.startTime = new Date(); |
| | |
| | showView('recording'); |
| | loadCurrentSentence(); |
| | } catch (error) { |
| | console.error('Error starting session:', error); |
| | alert('Error starting recording session'); |
| | } |
| | } |
| |
|
| | function loadCurrentSentence() { |
| | const sentence = recordingSession.sentences[recordingSession.currentIndex]; |
| | if (!sentence) return; |
| | |
| | document.getElementById('current-sentence').textContent = recordingSession.currentIndex + 1; |
| | document.getElementById('progress-bar').style.width = `${((recordingSession.currentIndex + 1) / 10) * 100}%`; |
| | document.getElementById('french-sentence').textContent = sentence.french_text; |
| | |
| | |
| | resetRecordingUI(); |
| | |
| | |
| | const frenchAudio = document.getElementById('french-audio'); |
| | frenchAudio.src = `https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3?t=${sentence.id}`; |
| | } |
| |
|
| | function resetRecordingUI() { |
| | const recordBtn = document.getElementById('record-btn'); |
| | const recordingIndicator = document.getElementById('recording-indicator'); |
| | const pulaarAudioSection = document.getElementById('pulaar-audio-section'); |
| | const nextBtn = document.getElementById('next-btn'); |
| | |
| | recordBtn.innerHTML = '<i data-feather="mic" class="w-12 h-12"></i>'; |
| | recordBtn.classList.remove('recording-active', 'bg-green-500', 'hover:bg-green-600'); |
| | recordBtn.classList.add('bg-red-500', 'hover:bg-red-600'); |
| | recordingIndicator.classList.add('hidden'); |
| | pulaarAudioSection.classList.add('hidden'); |
| | nextBtn.disabled = true; |
| | |
| | feather.replace(); |
| | } |
| |
|
| | |
| | async function toggleRecording() { |
| | if (mediaRecorder && mediaRecorder.state === 'recording') { |
| | stopRecording(); |
| | } else { |
| | await startRecording(); |
| | } |
| | } |
| |
|
| | async function startRecording() { |
| | try { |
| | const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); |
| | mediaRecorder = new MediaRecorder(stream); |
| | audioChunks = []; |
| | |
| | mediaRecorder.ondataavailable = (event) => { |
| | audioChunks.push(event.data); |
| | }; |
| | |
| | mediaRecorder.onstop = () => { |
| | const audioBlob = new Blob(audioChunks, { type: 'audio/wav' }); |
| | const audioUrl = URL.createObjectURL(audioBlob); |
| | |
| | |
| | const pulaarAudio = document.getElementById('pulaar-audio'); |
| | pulaarAudio.src = audioUrl; |
| | document.getElementById('pulaar-audio-section').classList.remove('hidden'); |
| | |
| | |
| | document.getElementById('next-btn').disabled = false; |
| | |
| | |
| | recordingSession.recordings[recordingSession.currentIndex] = { |
| | audioBlob: audioBlob, |
| | audioUrl: audioUrl, |
| | timestamp: new Date() |
| | }; |
| | |
| | |
| | stream.getTracks().forEach(track => track.stop()); |
| | }; |
| | |
| | mediaRecorder.start(); |
| | recordingStartTime = Date.now(); |
| | |
| | |
| | const recordBtn = document.getElementById('record-btn'); |
| | recordBtn.innerHTML = '<i data-feather="square" class="w-12 h-12"></i>'; |
| | recordBtn.classList.remove('bg-red-500', 'hover:bg-red-600'); |
| | recordBtn.classList.add('bg-green-500', 'hover:bg-green-600', 'recording-active'); |
| | |
| | document.getElementById('recording-indicator').classList.remove('hidden'); |
| | |
| | |
| | updateRecordingTimer(); |
| | |
| | feather.replace(); |
| | } catch (error) { |
| | console.error('Error starting recording:', error); |
| | alert('Could not access microphone. Please check permissions.'); |
| | } |
| | } |
| |
|
| | function stopRecording() { |
| | if (mediaRecorder && mediaRecorder.state === 'recording') { |
| | mediaRecorder.stop(); |
| | } |
| | } |
| |
|
| | function updateRecordingTimer() { |
| | if (mediaRecorder && mediaRecorder.state === 'recording') { |
| | const elapsed = Math.floor((Date.now() - recordingStartTime) / 1000); |
| | const minutes = Math.floor(elapsed / 60).toString().padStart(2, '0'); |
| | const seconds = (elapsed % 60).toString().padStart(2, '0'); |
| | document.getElementById('recording-timer').textContent = `${minutes}:${seconds}`; |
| | |
| | requestAnimationFrame(updateRecordingTimer); |
| | } |
| | } |
| |
|
| | function skipSentence() { |
| | recordingSession.recordings[recordingSession.currentIndex] = null; |
| | nextSentence(); |
| | } |
| |
|
| | function nextSentence() { |
| | recordingSession.currentIndex++; |
| | |
| | if (recordingSession.currentIndex >= recordingSession.sentences.length) { |
| | |
| | finishRecordingSession(); |
| | } else { |
| | loadCurrentSentence(); |
| | } |
| | } |
| |
|
| | async function finishRecordingSession() { |
| | |
| | const completedRecordings = recordingSession.recordings.filter(r => r !== null).length; |
| | const earnings = completedRecordings * 0.10; |
| | |
| | |
| | alert(`Session Complete!\n\nCompleted recordings: ${completedRecordings}\nEstimated earnings: €${earnings.toFixed(2)}\n\nYour recordings are now pending review.`); |
| | |
| | |
| | showView('dashboard'); |
| | |
| | |
| | loadDashboardData(); |
| | } |
| |
|
| | |
| | function checkAdminAccess() { |
| | |
| | const isAdmin = window.location.hash === '#admin'; |
| | if (isAdmin) { |
| | |
| | } |
| | } |
| |
|
| | async function loadAdminData() { |
| | try { |
| | |
| | const mockSubmissions = [ |
| | { |
| | id: 1, |
| | userName: 'User 1', |
| | frenchText: "Bonjour, comment allez-vous?", |
| | frenchAudioUrl: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3', |
| | pulaarAudioUrl: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3', |
| | status: 'pending', |
| | createdAt: '2024-01-15T10:30:00Z' |
| | }, |
| | { |
| | id: 2, |
| | userName: 'User 2', |
| | frenchText: "Je voudrais un café", |
| | frenchAudioUrl: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3', |
| | pulaarAudioUrl: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-4.mp3', |
| | status: 'pending', |
| | createdAt: '2024-01-15T09:45:00Z' |
| | } |
| | ]; |
| | |
| | |
| | document.getElementById('admin-total-submissions').textContent = mockSubmissions.length; |
| | document.getElementById('admin-pending').textContent = mockSubmissions.filter(s => s.status === 'pending').length; |
| | document.getElementById('admin-approved').textContent = 0; |
| | document.getElementById('admin-rejected').textContent = 0; |
| | |
| | |
| | renderSubmissions(mockSubmissions); |
| | } catch (error) { |
| | console.error('Error loading admin data:', error); |
| | } |
| | } |
| |
|
| | function renderSubmissions(submissions) { |
| | const submissionsList = document.getElementById('submissions-list'); |
| | |
| | submissionsList.innerHTML = submissions.map(sub => ` |
| | <div class="bg-gray-50 rounded-lg p-4 border border-gray-200"> |
| | <div class="mb-3"> |
| | <div class="flex justify-between items-start mb-2"> |
| | <div> |
| | <p class="font-medium text-gray-800">${sub.userName}</p> |
| | <p class="text-sm text-gray-600">${new Date(sub.createdAt).toLocaleString()}</p> |
| | </div> |
| | <span class="px-2 py-1 text-xs font-medium rounded-full bg-yellow-100 text-yellow-800"> |
| | ${sub.status} |
| | </span> |
| | </div> |
| | <p class="text-gray-700">${sub.frenchText}</p> |
| | </div> |
| | |
| | <div class="grid md:grid-cols-2 gap-4 mb-4"> |
| | <div> |
| | <p class="text-sm font-medium text-gray-600 mb-1">French Audio:</p> |
| | <audio controls class="w-full"> |
| | <source src="${sub.frenchAudioUrl}" type="audio/mpeg"> |
| | </audio> |
| | </div> |
| | <div> |
| | <p class="text-sm font-medium text-gray-600 mb-1">Pulaar Audio:</p> |
| | <audio controls class="w-full"> |
| | <source src="${sub.pulaarAudioUrl}" type="audio/mpeg"> |
| | </audio> |
| | </div> |
| | </div> |
| | |
| | <div class="flex gap-3"> |
| | <button onclick="updateSubmissionStatus(${sub.id}, 'approved')" class="flex-1 bg-green-600 hover:bg-green-700 text-white font-medium py-2 px-4 rounded-lg transition duration-200"> |
| | <i data-feather="check" class="w-4 h-4 inline mr-1"></i> |
| | Approve |
| | </button> |
| | <button onclick="updateSubmissionStatus(${sub.id}, 'rejected')" class="flex-1 bg-red-600 hover:bg-red-700 text-white font-medium py-2 px-4 rounded-lg transition duration-200"> |
| | <i data-feather="x" class="w-4 h-4 inline mr-1"></i> |
| | Reject |
| | </button> |
| | </div> |
| | </div> |
| | `).join(''); |
| | |
| | feather.replace(); |
| | } |
| |
|
| | async function updateSubmissionStatus(submissionId, status) { |
| | try { |
| | |
| | console.log(`Updating submission ${submissionId} to ${status}`); |
| | |
| | |
| | loadAdminData(); |
| | |
| | |
| | alert(`Submission ${status} successfully!`); |
| | } catch (error) { |
| | console.error('Error updating submission:', error); |
| | alert('Error updating submission status'); |
| | } |
| | } |
| |
|
| | |
| | window.updateSubmissionStatus = updateSubmissionStatus; |
| | window.showView = showView; |