import React, { useState, useEffect, useRef } from 'react'; // Import useRef
import { initializeApp } from 'firebase/app';
import { getAuth, signInWithEmailAndPassword, createUserWithEmailAndPassword, onAuthStateChanged, signOut, signInAnonymously, signInWithCustomToken } from 'firebase/auth';
import { getFirestore, doc, getDoc, setDoc, onSnapshot, collection, query } from 'firebase/firestore';
// Global variables for Firebase configuration (provided by the Canvas environment)
// DO NOT MODIFY THESE LINES
const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';
const firebaseConfig = JSON.parse(typeof __firebase_config !== 'undefined' ? __firebase_config : '{}');
const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null;
// Helper function to simulate vibration (for Japa Mala)
const vibrate = (duration) => {
if (navigator.vibrate) {
navigator.vibrate(duration);
} else {
console.log(`Vibrating for ${duration}ms (simulated)`);
}
};
// --- UI Components ---
// Shared Button Component
const Button = ({ onClick, children, className = '', colorClass = 'bg-blue-500 hover:bg-blue-600' }) => (
);
// AuthScreen Component - Modified for Creator-Only Login
const AuthScreen = ({ onAuthSuccess, auth, db }) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
// --- ATENÇÃO: PARA DEMONSTRAÇÃO APENAS ---
// Em um ambiente de produção real, NUNCA hardcode credenciais.
// Use um sistema de gerenciamento de papéis ou autenticação de administrador segura.
const CREATOR_EMAIL = "criador@exemplo.com"; // Substitua pelo seu e-mail de criador
const CREATOR_PASSWORD = "senha_segura_criador"; // Substitua pela sua senha de criador
// --- FIM DA ATENÇÃO ---
const handleLogin = async () => {
setLoading(true);
setError('');
try {
if (email === CREATOR_EMAIL && password === CREATOR_PASSWORD) {
// Attempt to log in with Firebase Auth to establish the session
await signInWithEmailAndPassword(auth, email, password);
// onAuthStateChanged (in the App component) will handle navigation
} else {
setError('Invalid creator credentials.');
}
} catch (e) {
console.error("Creator login error:", e);
if (e.code === 'auth/user-not-found' || e.code === 'auth/wrong-password' || e.code === 'auth/invalid-credential') {
setError('Invalid creator credentials.');
} else if (e.code === 'auth/invalid-email') {
setError('Invalid email format.');
} else {
setError('Authentication error. Please try again.');
}
} finally {
setLoading(false);
}
};
return (
// Removed specific background from AuthScreen to use App's global background
);
};
// Dashboard Component
const Dashboard = ({ onLogout, userId, setCurrentScreen }) => {
const [dailyPhrase, setDailyPhrase] = useState('');
// List of daily phrases
const phrases = [
"Mantra do dia: Eu honro minha trajetória.",
"Mantra do dia: Eu sou minha melhor companhia.",
"Mantra do dia: Eu inspiro paz, expiro tensão.",
"Mantra do dia: Meu passado não define meu futuro.",
"Mantra do dia: A cada caída, eu me levanto mais sábia.",
"Mantra do dia: Tudo que busco fora está dentro de mim.",
"Mantra do dia: Eu ajo mesmo com medo.",
"Mantra do dia: Nada é mais urgente que minha paz.",
"Mantra do dia: Ser imperfeita é ser real.",
"Mantra do dia: A paz começa dentro de mim.",
"Mantra do dia: Me escutar é meu primeiro passo.",
"Mantra do dia: Eu sou feita de força e esperança.",
"Mantra do dia: A quietude me fortalece.",
"Mantra do dia: Eu enfrento o medo com coragem.",
"Mantra do dia: Eu sou suficiente.",
"Mantra do dia: Tudo o que preciso já existe em mim.",
"Mantra do dia: Eu celebro minha evolução.",
"Mantra do dia: Meu esforço vale a pena.",
"Mantra do dia: A serenidade me encontra.",
"Mantra do dia: Eu sou mais forte do que imagino.",
"Mantra do dia: O impossível só dura até eu tentar.",
"Mantra do dia: Eu me reinvento quando é preciso.",
"Mantra do dia: Hoje é dia de me superar.",
"Mantra do dia: Eu transformo medo em ação.",
"Mantra do dia: Eu sou mais do que os obstáculos.",
"Mantra do dia: Eu escolho me conhecer um pouco mais a cada dia.",
"Mantra do dia: Eu solto o que não posso controlar.",
"Mantra do dia: Eu descanso sem culpa.",
"Mantra do dia: Cada respiração é um renascimento.",
"Mantra do dia: Eu deixo ir o que pesa.",
"Mantra do dia: Eu sou firme como a rocha e flexível como o vento.",
"Mantra do dia: Tudo acontece no tempo certo.",
"Mantra do dia: Eu confio na minha força interior.",
"Mantra do dia: Nada externo pode roubar minha paz.",
"Mantra do dia: Eu sou uma versão melhor de mim a cada dia.",
"Mantra do dia: Eu sou movimento, não estagnação.",
"Mantra do dia: Me amar é um ato de coragem.",
"Mantra do dia: Eu sou o lar que procuro.",
"Mantra do dia: Eu me abraço com amor e verdade.",
"Mantra do dia: Cada parte de mim merece compaixão.",
"Mantra do dia: Eu sou um canal de tranquilidade.",
"Mantra do dia: Eu ajo com fé no caminho que escolhi.",
"Mantra do dia: Eu sou mais do que os rótulos.",
"Mantra do dia: Eu posso, eu consigo, eu realizo.",
"Mantra do dia: Meus sentimentos merecem espaço.",
"Mantra do dia: Eu sou guerreira do meu próprio caminho.",
"Mantra do dia: Eu me escolho, todos os dias.",
"Mantra do dia: Eu abro espaço para o silêncio.",
"Mantra do dia: Eu valorizo minhas conquistas internas.",
"Mantra do dia: Eu escolho a calma.",
"Mantra do dia: Eu sou o que sou, sem precisar provar.",
"Mantra do dia: Eu não preciso ter todas as respostas agora.",
"Mantra do dia: Me permitir mudar é um presente.",
"Mantra do dia: Ser é mais importante que fazer.",
"Mantra do dia: Minha intuição é uma bússola segura.",
"Mantra do dia: Eu não desisto de mim.",
"Mantra do dia: Eu me perdoo por não saber antes.",
"Mantra do dia: Hoje, escolho a calma.",
"Mantra do dia: Minha mente se aquieta no presente.",
"Mantra do dia: Eu sou mais do que os “e se”.",
"Mantra do dia: Eu cultivo a harmonia interior.",
"Mantra do dia: Eu me liberto dos “e se”.",
"Mantra do dia: A coragem mora em mim.",
"Mantra do dia: Eu deixo o medo passar, sem me prender.",
"Mantra do dia: Eu abraço os riscos com confiança.",
"Mantra do dia: Eu me acolho com amor.",
"Mantra do dia: O agora é tudo que tenho.",
"Mantra do dia: Eu sou a autora da minha própria história.",
"Mantra do dia: Eu sou um mistério bonito em constante revelação.",
"Mantra do dia: Eu descanso e permito que minha alma respire.",
"Mantra do dia: Eu mereço viver com leveza.",
"Mantra do dia: Eu reconheço meus limites e escolho crescer com sabedoria.",
"Mantra do dia: A vida pode ser mais suave.",
"Mantra do dia: Eu transformo medo em movimento.",
"Mantra do dia: Eu sou luz, mesmo nos dias nublados.",
"Mantra do dia: Eu me observo sem julgamento.",
"Mantra do dia: Eu confio em mim mesma.",
"Mantra do dia: Eu sou capaz de enfrentar o desconhecido.",
"Mantra do dia: Eu sou um campo de tranquilidade.",
"Mantra do dia: Eu abraço minhas imperfeições com gentileza.",
"Mantra do dia: Hoje é um bom dia para desacelerar.",
"Mantra do dia: Eu escolho respirar em paz.",
"Mantra do dia: Eu sigo em frente, mesmo com incertezas.",
"Mantra do dia: Eu sou mais do que minhas dúvidas.",
"Mantra do dia: Eu me desligo da pressa.",
"Mantra do dia: Silenciar também é sabedoria.",
"Mantra do dia: Eu permito que minha alma descanse.",
"Mantra do dia: Eu sou o presente.",
"Mantra do dia: Eu me permito ser verdadeira comigo.",
"Mantra do dia: Pensar positivo me fortalece, mesmo quando as coisas ficam difíceis.",
];
useEffect(() => {
// Calculate the day of the year to pick a unique phrase for each day
const now = new Date();
const startOfYear = new Date(now.getFullYear(), 0, 0);
const diff = now.getTime() - startOfYear.getTime();
const oneDay = 1000 * 60 * 60 * 24;
const dayOfYear = Math.floor(diff / oneDay);
const phraseIndex = dayOfYear % phrases.length;
setDailyPhrase(phrases[phraseIndex]);
}, []); // Run once on component mount
return (
// Removed specific background from Dashboard to use App's global background
);
};
// --- Content Sections (Placeholders) ---
const SectionHeader = ({ title, onBack }) => (
);
const VideosSection = ({ onBack }) => {
const allVideos = [
{ id: '1', title: 'Video Teste 1', youtubeId: 'FsYdjlqU_zk' },
{ id: '2', title: 'Video Teste 2', youtubeId: 'oG6-MQmEgx4' },
{ id: '3', title: 'Video Teste 3', youtubeId: '4DuqSxe5wR0' },
{ id: '4', title: 'Video Teste 4', youtubeId: 'r7dYs5hgGJ8' },
];
const [selectedVideoId, setSelectedVideoId] = useState(null); // Stores the ID of the video to play
const [showVideoPlayer, setShowVideoPlayer] = useState(false); // State to toggle video player view
const handlePlayVideo = (youtubeId) => {
setSelectedVideoId(youtubeId);
setShowVideoPlayer(true);
};
const handleBackToList = () => {
setSelectedVideoId(null);
setShowVideoPlayer(false);
};
const youTubeChannelLink = "https://www.youtube.com/@ObrigadaaoUniversomusic"; // Your YouTube channel link
return (
);
};
const MusicSection = ({ onBack }) => {
const allMusicTracks = [
{
id: '1',
name: 'Canção teste',
url: 'https://obrigadaaouniverso.com/wp-content/uploads/2025/06/Audio-do-WhatsApp-de-2025-06-24-as-18.50.12_1f6835f6.mp3',
category: 'Mantra',
imageUrl: 'https://placehold.co/100x100/A7C7E7/FFFFFF?text=Mantra',
lyrics: "Essa é a letra da Canção teste. \nÉ um mantra suave e calmante. \nRepita comigo para mais paz."
},
{
id: '2',
name: 'Outra canção',
url: 'https://obrigadaaouniverso.com/wp-content/uploads/2025/06/Audio-do-WhatsApp-de-2025-06-24-as-18.50.12_f1b6180f.mp3',
category: 'Amor Próprio',
imageUrl: 'https://placehold.co/100x100/CBAACB/FFFFFF?text=Amor',
lyrics: "Letra da Outra canção. \nSobre se amar e se valorizar. \nVocê é suficiente."
},
{
id: '3',
name: 'Cantar ela',
url: 'https://obrigadaaouniverso.com/wp-content/uploads/2025/06/Audio-do-WhatsApp-de-2025-06-24-as-18.50.13_394ad6cb.mp3',
category: 'Casa',
imageUrl: 'https://placehold.co/100x100/F7C6C7/FFFFFF?text=Casa',
lyrics: "Essa é a letra de Cantar ela. \nUma canção para o lar. \nCom muito amor e aconchego."
},
];
const [filteredTracks, setFilteredTracks] = useState(allMusicTracks);
const [selectedCategory, setSelectedCategory] = useState('Todas');
const [currentTrackIndex, setCurrentTrackIndex] = useState(-1); // -1 means no track is selected/playing
const [isPlaying, setIsPlaying] = useState(false);
const [showDetailView, setShowDetailView] = useState(false); // New state to toggle detail view
const audioRef = useRef(null); // Use useRef for the Audio object
// Update filtered tracks when category changes
useEffect(() => {
if (selectedCategory === 'Todas') {
setFilteredTracks(allMusicTracks);
} else {
setFilteredTracks(allMusicTracks.filter(track => track.category === selectedCategory));
}
// Reset playback when filter changes
setIsPlaying(false);
setCurrentTrackIndex(-1);
if (audioRef.current) {
audioRef.current.pause();
audioRef.current.src = '';
}
setShowDetailView(false); // Hide detail view when filter changes
}, [selectedCategory]);
// Handle audio playback logic and cleanup
useEffect(() => {
if (audioRef.current) {
// Clean up previous event listener
audioRef.current.onended = null;
audioRef.current.pause();
audioRef.current.src = ''; // Clear source to release resources
}
if (isPlaying && currentTrackIndex !== -1 && filteredTracks.length > 0) {
const trackToPlay = filteredTracks[currentTrackIndex];
if (trackToPlay && trackToPlay.url) {
audioRef.current = new Audio(trackToPlay.url);
audioRef.current.play().catch(e => console.error("Erro ao tocar música:", e));
// Set up onended event to play next song, looping the playlist
audioRef.current.onended = () => {
// If not in loop mode, go to next song or stop if at the end of the filtered list
if (!audioRef.current.loop) { // Check audioRef.current.loop
const nextIndex = (currentTrackIndex + 1);
if (nextIndex < filteredTracks.length) {
setCurrentTrackIndex(nextIndex);
} else {
setIsPlaying(false);
setCurrentTrackIndex(-1); // Stop if at the end of the list and not looping
}
} else {
// If loop is true, the `audioRef.current.loop` property will handle continuous looping.
// We just ensure it plays the current song again.
audioRef.current.play();
}
};
}
} else {
if (audioRef.current) {
audioRef.current.pause();
}
}
// Cleanup function for useEffect
return () => {
if (audioRef.current) {
audioRef.current.onended = null;
audioRef.current.pause();
audioRef.current.src = '';
}
};
}, [isPlaying, currentTrackIndex, filteredTracks]); // Dependencies
const handlePlayMusic = () => {
// Only attempt to play if there's a track selected or we need to select the first one
if (filteredTracks.length === 0) return;
if (currentTrackIndex === -1) { // If no track is selected, start from the first one
setCurrentTrackIndex(0);
}
setIsPlaying(true);
setShowDetailView(true); // Ensure detail view is shown
};
const handlePauseMusic = () => {
if (audioRef.current) {
audioRef.current.pause();
setIsPlaying(false);
}
};
const handleRepeatToggle = () => {
if (audioRef.current) {
audioRef.current.loop = !audioRef.current.loop;
console.log('Looping:', audioRef.current.loop);
// Optionally provide user feedback that loop state changed
}
};
const handleSelectAndPlayTrack = (index) => {
setCurrentTrackIndex(index);
setIsPlaying(true);
setShowDetailView(true); // Show detail view when a track is selected
};
const handleNextTrack = () => {
if (filteredTracks.length === 0) return;
const nextIndex = (currentTrackIndex + 1) % filteredTracks.length;
setCurrentTrackIndex(nextIndex);
setIsPlaying(true); // Ensure playback continues
};
const handlePreviousTrack = () => {
if (filteredTracks.length === 0) return;
const prevIndex = (currentTrackIndex - 1 + filteredTracks.length) % filteredTracks.length;
setCurrentTrackIndex(prevIndex);
setIsPlaying(true); // Ensure playback continues
};
const currentPlayingTrack = filteredTracks[currentTrackIndex];
const uniqueCategories = Array.from(new Set(allMusicTracks.map(track => track.category)));
return (
// Removed specific background from MusicSection to use App's global background
);
};
const BooksSection = ({ onBack, userId, db }) => {
// Book content definitions
const initialBooksData = [
{
id: '1',
title: 'Os 4 Níveis da Lei da Atração', // Updated title
category: 'Lei da Atração',
coverImageUrl: 'https://obrigadaaouniverso.com/wp-content/uploads/2025/06/Obrigada-ao-universo.jpg', // Updated cover image
synopsis: 'Descubra os 4 níveis da Lei da Atração — pensamento, decreto, emoção e vibração — e aprenda a alinhar mente, palavra e energia para atrair o que deseja com consciência e poder.', // Updated synopsis
chapters: [
{
title: 'CAPÍTULO 1 – Introdução',
contentBlocks: [
{ type: 'section-title', content: 'Os 4 Níveis da Lei da Atração' },
{ type: 'text', content: 'Antes de aprender como usar a Lei da Atração, é importante entender que ela se manifesta em quatro níveis complementares.\n\nVocê está prestes a conhecer cada um deles — e ao alinhar todos, sua capacidade de atrair o que deseja será muito mais poderosa.\nEste modelo dos quatro níveis é uma forma prática de organizar os ensinamentos populares sobre a Lei da Atração, inspirados principalmente nas ideias divulgadas por Rhonda Byrne (autora de “O Segredo”) e nos ensinamentos de Abraham-Hicks.' },
],
},
{
title: 'CAPÍTULO 2 – Pensamento',
contentBlocks: [
{ type: 'section-title', content: '1º Nível – O Poder do Pensar Positivo' },
{ type: 'text', content: 'Esse é o nível mais conhecido: pensar positivo.\nMas atenção: só pensar positivo não é suficiente. O pensamento é o início, mas não o caminho completo.\nPensamentos são como sementes. Se você pensa constantemente em problemas, escassez ou medo, estará plantando exatamente isso.\n\nPor outro lado, pensar com frequência em coisas boas — conquistas, alegrias, sonhos — fortalece o seu campo de atração.\nA chave está na repetição consciente.\nPensamentos negativos vão surgir, é natural. Mas você pode escolher o que vai nutrir.\n\nSempre que perceber um pensamento limitante, redirecione:\nPense em algo que deseja atrair.\nVisualize lugares, momentos felizes.\nLembre-se do que te faz sentir gratidão.\nPensamento direcionado é o primeiro passo da manifestação.',
},
],
},
{
title: 'CAPÍTULO 3 – Decreto',
contentBlocks: [
{ type: 'section-title', content: '2º Nível – O Que Você Fala, Você Atrai' },
{ type: 'text', content: 'Tudo o que você fala, você fortalece.\nQuando você declara algo em voz alta, está reforçando o que pensa — e enviando uma mensagem mais intensa ao universo.\nExemplo:\n\nAo dizer “Hoje eu termino esse trabalho”, seu cérebro registra isso como um compromisso.\n\nO mesmo vale para frases negativas como “Eu sou um fracasso”. Ao repeti-las, você se sabota.\nFale com consciência.\n\nSubstitua frases negativas por versões fortalecedoras:\nEm vez de “Eu não consigo”, diga “Estou aprendendo”.\nEm vez de “Isso é impossível”, diga “Ainda não sei como, mas vai dar certo”.\nPrática de decreto:\nEscolha um desejo e diga em voz alta:\n\n“Eu posso…”\n“Eu mereço…”\n“Eu consigo…”\n“Eu sou…” (abençoada, saudável, próspera, amada…)\nInclua também uma frase positiva sobre alguém que você ama. Espalhar energia positiva cria um ciclo de retorno.',
},
],
},
{
title: 'CAPÍTULO 4 – Emoção',
contentBlocks: [
{ type: 'section-title', content: '3º Nível – Sentir é o Segredo' },
{ type: 'text', content: 'A emoção é a ponte entre o que você pensa e o que você atrai.\nPensar e decretar são importantes, mas sem sentimento, não há energia suficiente para manifestar.\nAo declarar seus desejos, sinta como se já fossem reais.\n\nImagine a alegria de viver aquilo, sinta no corpo, sorria, feche os olhos, vibre.\nNão se trata de fingir, mas de ativar em você o sentimento verdadeiro da conquista.\nSentir é o que magnetiza o seu desejo.\n\nQuanto mais forte for sua emoção, mais clara será sua vibração.',
},
],
},
{
title: 'CAPÍTULO 5 – Vibração',
contentBlocks: [
{ type: 'section-title', content: '4º Nível – Sua Frequência Atrai o Que Você Vibra' },
{ type: 'text', content: 'A vibração é a frequência energética que você emite.\n\nÉ o frio na barriga, o arrepio, a expectativa boa. É quando seu corpo e mente acreditam que algo incrível está por vir.\nMuita gente vibra na ansiedade, no medo ou na dúvida. Mas você pode escolher vibrar na confiança, na leveza e na certeza.\n\nPara elevar sua vibração:\nVisualize-se vivendo aquilo que deseja.\nSinta a emoção como se já tivesse acontecido.\nPergunte-se:\nO que vou fazer quando isso se realizar?\nComo vou comemorar?\nQuem estará comigo?\nEsse tipo de vibração acelera os caminhos e te coloca em sintonia com o que você busca.',
},
],
},
{
title: 'CAPÍTULO 6 – Conclusão',
contentBlocks: [
{ type: 'section-title', content: 'Atração Consciente: O Poder Está em Você' },
{ type: 'text', content: 'A Lei da Atração funciona melhor quando você alinha:\n\nPensamento – A intenção clara.\nDecreto – A palavra que afirma.\nEmoção – O sentimento verdadeiro.\nVibração – A energia que você transmite.\nVocê é um campo magnético de possibilidades.\n\nAgora que conhece os quatro níveis, pratique com consciência, constância e coração aberto.\nA mudança começa dentro.\n\nVocê atrai aquilo que acredita, sente e vive.\nVocê é a chave.', },
],
},
],
},
{
id: '2',
title: 'Caderno da Gratidão',
category: 'Bônus',
coverImageUrl: 'https://obrigadaaouniverso.com/wp-content/uploads/2025/06/capa-2.png',
synopsis: 'Um guia prático com exercícios diários para cultivar a gratidão e transformar sua perspectiva de vida. Ideal para reflexão e bem-estar, este caderno ajuda a focar nas coisas boas e a desenvolver uma mentalidade mais positiva e resiliente. Perfeito para iniciar ou aprofundar sua prática de gratidão.',
chapters: [
{ type: 'chapter-title', content: 'Introdução ao Caderno da Gratidão' },
{ type: 'text', content: 'Este é um espaço para registrar diariamente suas gratidões e observar a transformação em sua vida.' },
{ type: 'section-title', content: 'Exercício 1: Três Coisas Boas' },
{ type: 'text', content: 'Todos os dias, antes de dormir, anote três coisas pelas quais você é grato(a).' }
]
},
{
id: '3',
title: 'Afirmações de Amor',
category: 'Afirmações',
coverImageUrl: 'https://obrigadaaouniverso.com/wp-content/uploads/2025/06/capa-1.png',
synopsis: 'Coleção de afirmações poderosas focadas no amor próprio e nos relacionamentos, para fortalecer sua autoestima e atrair mais amor. Com frases cuidadosamente elaboradas, este livro é um recurso diário para nutrir a compaixão por si mesmo e pelos outros, promovendo harmonia e conexões profundas.',
chapters: [
{ type: 'chapter-title', content: 'Afirmações de Amor Próprio' },
{ type: 'text', content: 'Eu sou digno(a) de amor e respeito. Eu me aceito e me amo incondicionalmente.' },
{ type: 'section-title', content: 'Afirmações para Relacionamentos' },
{ type: 'text', content: 'Eu atraio relacionamentos saudáveis e amorosos para a minha vida.' }
]
},
];
const [books, setBooks] = useState(initialBooksData);
const [selectedBook, setSelectedBook] = useState(null);
const [showDetailView, setShowDetailView] = useState(false);
const [userFavorites, setUserFavorites] = useState({}); // { bookId: true/false }
const [selectedCategory, setSelectedCategory] = useState('Todas'); // New state for category filter
// Filtered books based on selected category and favorites
const filteredBooks = React.useMemo(() => {
let currentBooks = books;
if (selectedCategory === 'Favoritos') {
currentBooks = currentBooks.filter(book => userFavorites[book.id]);
} else if (selectedCategory !== 'Todas') {
currentBooks = currentBooks.filter(book => book.category === selectedCategory);
}
return currentBooks;
}, [books, selectedCategory, userFavorites]);
// Generate unique categories for the dropdown
const uniqueCategories = React.useMemo(() => {
const categories = Array.from(new Set(initialBooksData.map(book => book.category)));
return ['Todas', 'Favoritos', ...categories];
}, [initialBooksData]);
// Fetch user's favorite books from Firestore
useEffect(() => {
if (db && userId) {
const userBooksRef = collection(db, `artifacts/${appId}/users/${userId}/books`);
const unsubscribe = onSnapshot(userBooksRef, (snapshot) => {
const favorites = {};
snapshot.docs.forEach(doc => {
favorites[doc.id] = doc.data().isFavorite;
});
setUserFavorites(favorites);
}, (error) => {
// Corrected console.error syntax
console.error(`Erro ao carregar favoritos: ${error}`);
});
return () => unsubscribe();
}
}, [db, userId, appId]);
// Update a book's favorite status in Firestore
const handleToggleFavorite = async (bookId, currentStatus) => {
if (!db || !userId) return;
const bookDocRef = doc(db, `artifacts/${appId}/users/${userId}/books/${bookId}`);
try {
await setDoc(bookDocRef, { isFavorite: !currentStatus }, { merge: true });
// UI will update via onSnapshot listener
} catch (error) {
console.error("Erro ao atualizar favorito:", error);
}
};
const handleSelectBook = (book) => {
setSelectedBook(book);
setShowDetailView(true);
};
const handleBackToShelf = () => {
setSelectedBook(null);
setShowDetailView(false);
};
const [readingBook, setReadingBook] = useState(null); // State for the book currently being read
const handleReadBook = (book) => {
setReadingBook(book);
};
if (readingBook) {
return setReadingBook(null)} userId={userId} db={db} />;
}
return (
);
};
// New BookReaderView Component
const BookReaderView = ({ book, onBack, userId, db }) => {
const contentRef = useRef(null);
const [currentChapterIndex, setCurrentChapterIndex] = useState(0);
const [readerFontSize, setReaderFontSize] = useState('text-base'); // Default font size
const fontSizes = ['text-sm', 'text-base', 'text-lg', 'text-xl'];
const speechUtteranceRef = useRef(null); // Ref for SpeechSynthesisUtterance
// Load reading progress from Firestore
useEffect(() => {
if (db && userId && book.id) {
const readingProgressDocRef = doc(db, `artifacts/${appId}/users/${userId}/readingProgress/${book.id}`);
const unsubscribe = onSnapshot(readingProgressDocRef, (docSnap) => {
if (docSnap.exists()) {
const data = docSnap.data();
setCurrentChapterIndex(data.chapterIndex || 0);
setReaderFontSize(data.fontSize || 'text-base');
// Ensure contentRef.current is available before setting scroll position
if (contentRef.current) {
contentRef.current.scrollTop = data.scrollPosition || 0;
}
}
}, (error) => {
console.error(`Erro ao carregar progresso de leitura: ${error}`); // Corrected console.error syntax
});
return () => unsubscribe();
}
}, [db, userId, book.id, appId]); // Added appId to dependencies
// Save reading progress to Firestore (debounced for performance)
useEffect(() => {
const handler = setTimeout(() => {
saveReadingProgress();
}, 1000); // Save every 1 second after scroll/chapter change
return () => {
clearTimeout(handler);
};
}, [currentChapterIndex, readerFontSize, contentRef.current?.scrollTop]); // Depend on relevant states
const saveReadingProgress = async () => {
if (!db || !userId || !book.id || !contentRef.current) return;
const readingProgressDocRef = doc(db, `artifacts/${appId}/users/${userId}/readingProgress/${book.id}`);
try {
await setDoc(readingProgressDocRef, {
chapterIndex: currentChapterIndex,
fontSize: readerFontSize,
scrollPosition: contentRef.current.scrollTop,
lastRead: new Date(),
}, { merge: true });
// console.log("Progresso de leitura salvo!");
} catch (error) {
console.error("Erro ao salvar progresso de leitura:", error);
}
};
const handleFontSizeChange = (direction) => {
const currentIndex = fontSizes.indexOf(readerFontSize);
let newIndex = currentIndex;
if (direction === 'increase' && currentIndex < fontSizes.length - 1) {
newIndex++;
} else if (direction === 'decrease' && currentIndex > 0) {
newIndex--;
}
setReaderFontSize(fontSizes[newIndex]);
};
const handleNextChapter = () => {
if (currentChapterIndex < book.chapters.length - 1) {
setCurrentChapterIndex(prevIndex => prevIndex + 1);
if (contentRef.current) {
contentRef.current.scrollTop = 0; // Scroll to top of new chapter
}
stopAudiobook(); // Stop any ongoing narration
}
};
const handlePreviousChapter = () => {
if (currentChapterIndex > 0) {
setCurrentChapterIndex(prevIndex => prevIndex - 1);
if (contentRef.current) {
contentRef.current.scrollTop = 0; // Scroll to top of new chapter
}
stopAudiobook(); // Stop any ongoing narration
}
};
// Audiobook functionality
const startAudiobook = () => {
if ('speechSynthesis' in window) {
// If there's an utterance and it's paused, resume it.
if (speechUtteranceRef.current && window.speechSynthesis.paused) {
window.speechSynthesis.resume();
return;
}
// If not paused or no utterance, start new narration
const textToSpeak = book.chapters[currentChapterIndex].contentBlocks
.filter(block => block.type === 'text' || block.type === 'section-title' || block.type === 'chapter-title')
.map(block => block.content)
.join('\n\n');
if (!textToSpeak) {
alert('Não há texto para ler neste capítulo.');
return;
}
const utterance = new SpeechSynthesisUtterance(textToSpeak);
// Optionally set language, voice, pitch, rate
utterance.lang = 'pt-BR'; // Set language to Portuguese (Brazil)
// You can find available voices using window.speechSynthesis.getVoices()
// utterance.voice = window.speechSynthesis.getVoices().find(voice => voice.lang === 'pt-BR');
utterance.rate = 1.0; // Speed of speech
utterance.pitch = 1.0; // Pitch of speech
utterance.onend = () => {
console.log('Leitura finalizada.');
speechUtteranceRef.current = null; // Clear ref when finished
};
utterance.onerror = (event) => {
console.error('Erro na síntese de fala:', event.error);
alert('Erro ao tentar ler o livro em voz alta. Seu navegador pode não suportar ou há um problema com a voz.');
};
window.speechSynthesis.cancel(); // Stop any previous speech
window.speechSynthesis.speak(utterance);
speechUtteranceRef.current = utterance; // Store reference to current utterance
} else {
alert('Seu navegador não suporta a leitura de texto em voz alta (Speech Synthesis).');
}
};
const pauseAudiobook = () => {
if ('speechSynthesis' in window && speechUtteranceRef.current) {
window.speechSynthesis.pause();
}
};
const stopAudiobook = () => {
if ('speechSynthesis' in window) {
window.speechSynthesis.cancel();
speechUtteranceRef.current = null; // Clear ref to ensure new start on next play
}
};
const currentChapter = book.chapters[currentChapterIndex];
return (
);
};
// JapaMalaSection Component - Reconstruído do Zero (corrigindo problemas de escopo/definição)
const JapaMalaSection = ({ onBack, userId, db }) => {
const [count, setCount] = useState(0);
const [targetCount, setTargetCount] = useState(108); // Default target count
const [inputTarget, setInputTarget] = useState('108'); // For the input field
const [selectedMandala, setSelectedMandala] = useState('mandala1'); // Default to the first mandala
const [music, setMusic] = useState('none');
const [currentAudio, setCurrentAudio] = useState(null); // State to hold the current Audio object
const lastClickTime = useRef(0); // To track last click time for double tap
// Background options with both icon and main mandala URLs
const mandalaOptions = {
mandala1: {
icon: 'url(https://obrigadaaouniverso.com/wp-content/uploads/2025/06/icone-mandala-01-100.jpg) center/cover no-repeat',
main: 'url(https://obrigadaaouniverso.com/wp-content/uploads/2025/06/mandala-01.png) center/contain no-repeat'
},
mandala2: {
icon: 'url(https://obrigadaaouniverso.com/wp-content/uploads/2025/06/icone-mandala-02-100.jpg) center/cover no-repeat',
main: 'url(https://obrigadaaouniverso.com/wp-content/uploads/2025/06/mandala-02.png) center/contain no-repeat'
},
mandala3: {
icon: 'url(https://obrigadaaouniverso.com/wp-content/uploads/2025/06/icone-mandala-03-100.jpg) center/cover no-repeat',
main: 'url(https://obrigadaaouniverso.com/wp-content/uploads/2025/06/mandala-03.png) center/contain no-repeat'
},
mandala4: {
icon: 'url(https://obrigadaaouniverso.com/wp-content/uploads/2025/06/icone-mandala-04-100.jpg) center/cover no-repeat',
main: 'url(https://obrigadaaouniverso.com/wp-content/uploads/2025/06/mandala-04.png) center/contain no-repeat'
},
};
// Music options
const musicOptions = {
none: { name: 'Nenhuma', url: '' }, // Ensure 'none' has an empty URL
'cancao-teste': {
name: 'Canção teste',
url: 'https://obrigadaaouniverso.com/wp-content/uploads/2025/06/Audio-do-WhatsApp-de-2025-06-24-as-18.50.12_1f6835f6.mp3'
},
'outra-cancao': {
name: 'Outra canção',
url: 'https://obrigadaaouniverso.com/wp-content/uploads/2025/06/Audio-do-WhatsApp-de-2025-06-24-as-18.50.12_f1b6180f.mp3'
},
'cantar-ela': {
name: 'Cantar ela',
url: 'https://obrigadaaouniverso.com/wp-content/uploads/2025/06/Audio-do-WhatsApp-de-2025-06-24-as-18.50.13_394ad6cb.mp3'
},
};
// Effect to load user's Japa Mala settings from Firestore
useEffect(() => {
if (db && userId) {
const userDocRef = doc(db, `artifacts/${appId}/users/${userId}/settings/japaMala`);
const unsubscribe = onSnapshot(userDocRef, (docSnap) => {
if (docSnap.exists()) {
const data = docSnap.data();
setTargetCount(data.targetCount || 108);
setInputTarget(String(data.targetCount || 108));
setSelectedMandala(data.selectedMandala || 'mandala1');
setMusic(data.music || 'none');
setCount(data.currentCount || 0);
}
}, (error) => {
console.error(`Erro ao carregar configurações do Japa Mala: ${error}`);
});
return () => unsubscribe();
}
}, [db, userId, appId]);
// Effect to save Japa Mala settings to Firestore (debounced)
useEffect(() => {
const handler = setTimeout(() => {
if (db && userId) {
const userDocRef = doc(db, `artifacts/${appId}/users/${userId}/settings/japaMala`);
setDoc(userDocRef, {
targetCount: targetCount,
selectedMandala: selectedMandala,
music: music,
currentCount: count,
lastUpdated: new Date(),
}, { merge: true }).catch(error => {
console.error("Erro ao salvar configurações do Japa Mala:", error);
});
}
}, 1000); // Save every 1 second after a state change
return () => {
clearTimeout(handler);
};
}, [count, targetCount, selectedMandala, music, db, userId, appId]); // Dependencies for saving
// Effect to manage current audio playback
useEffect(() => {
if (currentAudio) {
currentAudio.pause();
currentAudio.src = '';
currentAudio.onended = null; // Clear event listener
}
const selectedMusicUrl = musicOptions[music]?.url;
if (selectedMusicUrl) {
const newAudio = new Audio(selectedMusicUrl);
newAudio.loop = false; // Default to no loop, controlled by button
newAudio.volume = 0.5; // Default volume
setCurrentAudio(newAudio);
// Listen for when audio ends to potentially play next
newAudio.onended = () => {
// Here, if you wanted the Japa Mala music to loop indefinitely, you'd set newAudio.loop = true earlier
// For now, it just ends. If you want a playlist in JapaMala, we'd need more complex state.
};
} else {
setCurrentAudio(null);
}
return () => {
if (currentAudio) {
currentAudio.pause();
currentAudio.src = '';
currentAudio.onended = null;
}
};
}, [music]); // Only re-run when 'music' selection changes
const handlePlayMusic = () => {
if (currentAudio) {
currentAudio.play().catch(e => console.error("Erro ao tocar música:", e));
}
};
const handlePauseMusic = () => {
if (currentAudio) {
currentAudio.pause();
}
};
const handleStopMusic = () => {
if (currentAudio) {
currentAudio.pause();
currentAudio.currentTime = 0; // Reset to start
}
};
const handleRepeatToggle = () => {
if (currentAudio) {
currentAudio.loop = !currentAudio.loop;
console.log('Looping Japa Mala Music:', currentAudio.loop);
}
};
const handleIncrement = () => {
const now = Date.now();
const DOUBLE_TAP_THRESHOLD = 300; // milliseconds
// If count is at or above target, and it's a double tap, then reset
if (count >= targetCount && (now - lastClickTime.current < DOUBLE_TAP_THRESHOLD)) {
setCount(0);
vibrate(500); // Strong vibrate for reset
lastClickTime.current = 0; // Reset for next sequence
return; // Exit, as reset handled
}
// If count is less than target, increment normally
if (count < targetCount) {
const newCount = count + 1;
setCount(newCount);
vibrate(50); // Small vibration on each tap
if (newCount === targetCount) {
vibrate(500); // Stronger vibration at target
}
}
// If count is >= targetCount and it's not a double tap, do nothing (don't increment further)
lastClickTime.current = now; // Update last click time for double tap detection
};
const handleReset = () => {
setCount(0);
vibrate(100); // Small vibration on reset
};
const handleTargetChange = (e) => {
setInputTarget(e.target.value);
const value = parseInt(e.target.value, 10);
if (!isNaN(value) && value >= 0 && value <= 200) {
setTargetCount(value);
}
};
return (
);
};
// Main App Component
const App = () => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [userId, setUserId] = useState(null);
const [currentScreen, setCurrentScreen] = useState('auth'); // 'auth', 'dashboard', 'videos', 'music', 'books', 'japa-mala'
const [db, setDb] = useState(null);
const [auth, setAuth] = useState(null);
// Initialize Firebase and set up authentication listener
useEffect(() => {
try {
const app = initializeApp(firebaseConfig);
const firestoreDb = getFirestore(app);
const firebaseAuth = getAuth(app);
setDb(firestoreDb);
setAuth(firebaseAuth);
const unsubscribeAuth = onAuthStateChanged(firebaseAuth, async (user) => {
if (user) {
setIsAuthenticated(true);
setUserId(user.uid);
// If auth success, navigate to dashboard unless already in a content screen
if (currentScreen === 'auth' || currentScreen === 'loading') {
setCurrentScreen('dashboard');
}
} else {
setIsAuthenticated(false);
setUserId(null);
setCurrentScreen('auth');
// Sign in anonymously if initialAuthToken is provided, or just keep at auth screen
if (initialAuthToken) {
try {
await signInWithCustomToken(firebaseAuth, initialAuthToken);
} catch (error) {
console.error("Error trying to log in with initial token:", error);
// Fallback to anonymous or just stay on auth screen
// We keep signInAnonymously here to allow Firestore rules to work
await signInAnonymously(firebaseAuth);
}
} else {
// Ensure there's always an anonymous user for Firestore access rules
await signInAnonymously(firebaseAuth);
}
}
});
return () => unsubscribeAuth();
} catch (error) {
console.error("Error initializing Firebase:", error);
// Potentially show an error message to the user
}
}, [currentScreen, initialAuthToken]); // Added initialAuthToken to dependencies
const handleAuthSuccess = (uid) => {
setIsAuthenticated(true);
setUserId(uid);
setCurrentScreen('dashboard');
};
const handleLogout = async () => {
if (auth) {
try {
await signOut(auth);
// onAuthStateChanged will handle setting isAuthenticated to false and navigating to 'auth'
} catch (error) {
console.error("Error logging out:", error);
}
}
};
// Render the appropriate screen based on isAuthenticated and currentScreen state
let content;
if (!isAuthenticated) {
content = ;
} else {
switch (currentScreen) {
case 'dashboard':
content = ;
break;
case 'videos':
content = setCurrentScreen('dashboard')} />;
break;
case 'music':
content = setCurrentScreen('dashboard')} />;
break;
case 'books':
content = setCurrentScreen('dashboard')} userId={userId} db={db} />; // Pass userId and db
break;
case 'japa-mala':
// JapaMalaSection is now defined above App, so it's in scope.
content = setCurrentScreen('dashboard')} userId={userId} db={db} />;
break;
default:
content = ;
}
}
return (
Creator Login
{error &&{error}
}
setEmail(e.target.value)}
className="w-full p-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-[#A7C7E7] focus:border-transparent outline-none shadow-sm"
/>
setPassword(e.target.value)}
className="w-full p-3 border border-gray-200 rounded-xl focus:ring-2 focus:ring-[#A7C7E7] focus:border-transparent outline-none shadow-sm"
/>
{/* [Image of Logo Obrigada ao Universo] */}
{ e.target.onerror = null; e.target.src = "https://placehold.co/180x80/cccccc/ffffff?text=Logo"; }}
/>
{/* Título e Frase Diária */}

!Mantra do dia
{/* Título roxo claro */}{dailyPhrase}
ID do Usuário (Criador): {userId}
{title}
{/* Placeholder for alignment */}Aqui você poderá acessar os links dos seus vídeos exclusivos do YouTube.
{showVideoPlayer && selectedVideoId ? (
// Detailed Video Player View
Ir para o canal no YouTube
) : (
// Video List View
{allVideos.find(video => video.youtubeId === selectedVideoId)?.title || "Vídeo"}
-
{allVideos.map((video) => (
- handlePlayVideo(video.youtubeId)}
className="flex items-center p-3 bg-gray-100 rounded-lg shadow-sm hover:shadow-md transition-all duration-200 cursor-pointer"
>
{ e.target.onerror = null; e.target.src = "https://placehold.co/80x45/cccccc/ffffff?text=Vídeo"; }} /> {video.title}
))}
Sua playlist de músicas exclusivas estará disponível aqui.
{showDetailView && currentPlayingTrack ? (
// Detailed Music View
{ e.target.onerror = null; e.target.src = "https://placehold.co/200x200/A7C7E7/FFFFFF?text=Capa+da+Música"; }}
/>
) : (
// Music List View
<>
{currentPlayingTrack.name}
{currentPlayingTrack.category}
{/* Music Controls */} {/* Added items-center */}
{/* Previous Button */}
{/* Pause Button */}
{/* Play Button */}
{/* Next Button */}
{/* Repeat Toggle (optional to move, keeping it separate for now) */}
Letra da Música:
{currentPlayingTrack.lyrics}
Filtrar por Categoria:
Minhas Músicas:
{filteredTracks.length === 0 ? (Nenhuma música encontrada nesta categoria.
) : (-
{filteredTracks.map((track, index) => (
- handleSelectAndPlayTrack(index)} // Click anywhere on the item to play and show detail
className={`flex items-center p-3 rounded-lg shadow-sm justify-between cursor-pointer transition-all duration-200
${currentTrackIndex === index && isPlaying ? 'bg-[#CBAACB] bg-opacity-30 border border-[#CBAACB]' : 'bg-gray-100'}`}
>
{ e.target.onerror = null; e.target.src = "https://placehold.co/40x40/A7C7E7/FFFFFF?text=🎶"; }} />
{track.name}
{track.category}
))}
Conferir em Outras Plataformas:
Leia seus livros digitais favoritos diretamente no aplicativo.
{showDetailView && selectedBook ? (
// Detailed Book View
{ e.target.onerror = null; e.target.src = "https://placehold.co/200x260/A0E7E5/000?text=Capa+Livro"; }}
/>
) : (
// Book Shelf View
<>
{selectedBook.title}
Categoria: {selectedBook.category}
{/* Favorite Star */}Sinopse:
{selectedBook.synopsis}
Filtrar por Categoria:
{filteredBooks.length === 0 ? (Nenhum livro encontrado nesta categoria.
) : ( {/* Two columns */}
{filteredBooks.map((book) => (
handleSelectBook(book)}
onError={(e) => { e.target.onerror = null; e.target.src = "https://placehold.co/100x150/A0E7E5/000?text=Capa"; }}
/>
))}
)}
>
)}
{book.title}
{book.category}
{/* Header with back button and title */}
{/* Top Controls: Font Size and Mark Page */}
{/* Audiobook Controls: Play, Pause, Stop (Icons) */}
{/* Chapter Navigation */}
{/* Book Content Area */}
{book.title}
Capítulo {currentChapterIndex + 1} de {book.chapters.length}
{currentChapter && (
{/* Render the chapter title */}
{block.type === 'section-title' && (
))}
)}
{currentChapter.title}
{/* Render content blocks within the chapter */} {currentChapter.contentBlocks.map((block, blockIndex) => ({block.content}
)} {block.type === 'text' && ({block.content}
)}
{/* Header with back button and title */}
{/* Central Japa Mala Number - larger clickable area */}
{/* More delicate title with light font */}
{/* Target Count Option - at the top */}
Digital Japa Mala
{/* Placeholder for alignment */}
{count}
{/* Reset Count Button */}
{/* Background and Music Options - at the bottom */}
Escolha o Fundo:
{/* Mandala selection buttons - removed background and shadow-inner */}
{Object.keys(mandalaOptions).map((key) => {
const option = mandalaOptions[key];
let previewStyle = {
background: option.icon, // Use the icon URL for the preview circle
backgroundSize: 'cover', // Ensure icons cover the circle
backgroundPosition: 'center',
};
return (
setSelectedMandala(key)} // Set selectedMandala
className={`w-12 h-12 rounded-full cursor-pointer border-2
${selectedMandala === key ? 'border-[#A7C7E7] ring-2 ring-[#CBAACB]' : 'border-gray-200'}
transition-all duration-200 flex items-center justify-center text-xs text-white overflow-hidden`}
style={previewStyle}
>
{/* No text here, relying on visual preview */}
);
})}
Escolha a Música:
{/* Music Selection Dropdown */} {/* Music Control Buttons - Reordered and updated icons */}
{/* Pause Button */}
{/* Play Button */}
{/* Repeat Button */}
{content}
{/* Tailwind CSS Script - Always include this in HTML/React apps */}
{/* Configure Tailwind to use JIT mode for faster compilation in development */}
{/* Using dangerouslySetInnerHTML for the script content to avoid JSX parsing issues */}
);
};
export default App;