mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-17 05:47:52 +00:00
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
This commit is contained in:
parent
fd50d59d3c
commit
70953ab57a
10
src/components/layout/RootLayout.tsx
Normal file
10
src/components/layout/RootLayout.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
export function RootLayout(): JSX.Element {
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<Outlet />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { Mic, Square, Loader, Play, Upload } from 'lucide-react';
|
||||
import { supabase } from '@/lib/supabase';
|
||||
import { supabase } from '../../lib/supabase';
|
||||
|
||||
interface AudioRecorderProps {
|
||||
storyId: string;
|
||||
|
||||
@ -1,40 +1,166 @@
|
||||
import { AudioRecorder } from '../../components/story/AudioRecorder';
|
||||
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';
|
||||
|
||||
export function StoryPage() {
|
||||
// ... outros códigos ...
|
||||
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 {
|
||||
// Salvar referência do áudio no banco de dados
|
||||
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: studentId,
|
||||
student_id: session.user.id,
|
||||
audio_url: audioUrl,
|
||||
status: 'pending_analysis' // será analisado pela IA posteriormente
|
||||
status: 'pending_analysis'
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
// Aqui você pode adicionar a lógica para enviar o áudio para análise da IA
|
||||
// Por exemplo, chamar uma função que envia o áudio para um endpoint de IA
|
||||
|
||||
} catch (err) {
|
||||
console.error('Erro ao salvar gravação:', err);
|
||||
setError('Erro ao salvar gravação de áudio');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* ... outros elementos da página ... */}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
<AudioRecorder
|
||||
storyId={storyId}
|
||||
studentId={studentId}
|
||||
onAudioUploaded={handleAudioUploaded}
|
||||
/>
|
||||
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>
|
||||
);
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import { createBrowserRouter } from 'react-router-dom';
|
||||
import { RootLayout } from '../components/layout/RootLayout';
|
||||
import { HomePage } from '../components/home/HomePage';
|
||||
import { LoginForm } from '../components/auth/LoginForm';
|
||||
import { SchoolRegistrationForm } from '../components/auth/SchoolRegistrationForm';
|
||||
@ -14,6 +15,7 @@ import { InviteTeacherPage } from '../pages/dashboard/teachers/InviteTeacherPage
|
||||
import { StudentsPage } from '../pages/dashboard/students/StudentsPage';
|
||||
import { AddStudentPage } from '../pages/dashboard/students/AddStudentPage';
|
||||
import { DemoPage } from '../pages/demo/DemoPage';
|
||||
import { StoryPage } from '../pages/story/StoryPage';
|
||||
import React from 'react';
|
||||
|
||||
export const router = createBrowserRouter([
|
||||
@ -27,7 +29,7 @@ export const router = createBrowserRouter([
|
||||
},
|
||||
{
|
||||
path: 'demo',
|
||||
element: <DemoPage />,
|
||||
element: <StoryPage demo={true} />
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
@ -117,6 +119,10 @@ export const router = createBrowserRouter([
|
||||
path: '/auth/callback',
|
||||
element: <AuthCallback />
|
||||
},
|
||||
{
|
||||
path: '/story/:storyId',
|
||||
element: <StoryPage />
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
@ -94,6 +94,8 @@ export interface StoryPage {
|
||||
export interface Story {
|
||||
id: string;
|
||||
student_id: string;
|
||||
class_id: string;
|
||||
school_id: string;
|
||||
title: string;
|
||||
theme: string;
|
||||
content: {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user