story-generator/src/pages/story/StoryPage.tsx
Lucas Santana 70953ab57a feat: adiciona RootLayout e atualiza rotas da aplicação
- Cria componente RootLayout como container principal
- Atualiza router para usar RootLayout como elemento raiz
- Organiza rotas aninhadas com Outlet do React Router
- Adiciona rota para visualização de histórias individuais
2024-12-20 10:25:21 -03:00

166 lines
5.4 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { supabase } from '../../lib/supabase';
import { Story } from '../../types/database';
import { AudioRecorder } from '../../components/story/AudioRecorder';
import { Loader2 } from 'lucide-react';
interface StoryPageProps {
demo?: boolean;
}
export function StoryPage({ demo = false }: StoryPageProps): JSX.Element {
const { storyId } = useParams();
const [story, setStory] = useState<Story | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [currentPage, setCurrentPage] = useState(0);
useEffect(() => {
const fetchStory = async () => {
try {
if (!storyId && !demo) {
throw new Error('ID da história não fornecido');
}
if (demo) {
setStory({
id: 'demo',
student_id: 'demo',
class_id: 'demo',
school_id: 'demo',
title: 'Uma Aventura Educacional',
theme: 'Demo',
content: {
pages: [
{
text: 'Bem-vindo à demonstração do Histórias Mágicas! Aqui você pode ver como funciona nossa plataforma...',
image: 'https://images.unsplash.com/photo-1472162072942-cd5147eb3902?auto=format&fit=crop&q=80&w=800&h=600',
},
{
text: 'Com histórias interativas e educativas, seus alunos aprenderão de forma divertida e envolvente...',
image: 'https://images.unsplash.com/photo-1519681393784-d120267933ba?auto=format&fit=crop&q=80&w=800&h=600',
}
],
currentPage: 0
},
status: 'published',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
});
setLoading(false);
return;
}
const { data, error } = await supabase
.from('stories')
.select('*')
.eq('id', storyId)
.single();
if (error) throw error;
setStory(data);
} catch (err) {
console.error('Erro ao buscar história:', err);
setError(err instanceof Error ? err.message : 'Erro ao carregar história');
} finally {
setLoading(false);
}
};
fetchStory();
}, [storyId, demo]);
const handleAudioUploaded = async (audioUrl: string) => {
try {
if (demo) return; // Não salva gravações no modo demo
const { data: { session } } = await supabase.auth.getSession();
if (!session?.user?.id) throw new Error('Usuário não autenticado');
const { error } = await supabase
.from('story_recordings')
.insert({
story_id: storyId,
student_id: session.user.id,
audio_url: audioUrl,
status: 'pending_analysis'
});
if (error) throw error;
} catch (err) {
console.error('Erro ao salvar gravação:', err);
setError('Erro ao salvar gravação de áudio');
}
};
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center">
<Loader2 className="w-8 h-8 text-purple-600 animate-spin" />
</div>
);
}
if (error || !story) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h1 className="text-2xl font-bold text-gray-900 mb-2">Ops!</h1>
<p className="text-gray-600">{error || 'História não encontrada'}</p>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gradient-to-b from-purple-50 to-white py-12">
<div className="max-w-4xl mx-auto px-4">
<h1 className="text-3xl font-bold text-gray-900 mb-8 text-center">
{story.title}
</h1>
<div className="bg-white rounded-2xl shadow-xl overflow-hidden mb-8">
{story.content.pages[currentPage].image && (
<img
src={story.content.pages[currentPage].image}
alt={`Ilustração da página ${currentPage + 1}`}
className="w-full h-64 object-cover"
/>
)}
<div className="p-8">
<p className="text-lg text-gray-700 mb-8">
{story.content.pages[currentPage].text}
</p>
<AudioRecorder
storyId={story.id}
studentId={demo ? 'demo' : story.student_id}
classId={demo ? 'demo' : story.class_id}
schoolId={demo ? 'demo' : story.school_id}
onAudioUploaded={handleAudioUploaded}
/>
<div className="flex justify-between mt-8">
<button
onClick={() => setCurrentPage(prev => Math.max(0, prev - 1))}
disabled={currentPage === 0}
className="px-4 py-2 bg-purple-100 text-purple-700 rounded-lg disabled:opacity-50"
>
Anterior
</button>
<button
onClick={() => setCurrentPage(prev => Math.min(story.content.pages.length - 1, prev + 1))}
disabled={currentPage === story.content.pages.length - 1}
className="px-4 py-2 bg-purple-600 text-white rounded-lg disabled:opacity-50"
>
Próxima
</button>
</div>
</div>
</div>
</div>
</div>
);
}