mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-16 21:37:51 +00:00
feat: Adiciona toggle de texto maiúsculo para apoio à alfabetização
- Implementa componente TextCaseToggle para alternância de caixa - Cria sistema de texto adaptativo com componentes AdaptiveText - Adiciona hook useUppercasePreference para gerenciar estado - Integra funcionalidade em todas as páginas principais - Persiste preferência do usuário no banco de dados
This commit is contained in:
parent
7880ce8dda
commit
e4c225ebd7
18
CHANGELOG.md
18
CHANGELOG.md
@ -54,3 +54,21 @@ e este projeto adere ao [Semantic Versioning](https://semver.org/lang/pt-BR/).
|
||||
|
||||
### Removido
|
||||
- N/A (primeira versão)
|
||||
|
||||
## [1.1.0] - 2024-05-20
|
||||
|
||||
### Adicionado
|
||||
- Novo componente `TextCaseToggle` para alternar entre maiúsculas/minúsculas
|
||||
- Componentes de texto adaptativo (`AdaptiveText`, `AdaptiveTitle`, `AdaptiveParagraph`)
|
||||
- Hook `useUppercasePreference` para gerenciar preferência do usuário
|
||||
- Suporte a texto em maiúsculas para crianças em fase de alfabetização
|
||||
|
||||
### Modificado
|
||||
- Páginas de histórias e exercícios para usar o novo sistema de texto
|
||||
- Cabeçalho das páginas principal com controle de caixa de texto
|
||||
- Componentes de exercícios para suportar transformação de texto
|
||||
|
||||
### Técnico
|
||||
- Adicionada coluna `uppercase_text_preferences` na tabela students
|
||||
- Sistema de persistência de preferências via Supabase
|
||||
- Otimizações de performance com memoização de componentes
|
||||
|
||||
@ -3,12 +3,17 @@ import { ArrowLeft, Sparkles } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { StoryGenerator } from '../../components/story/StoryGenerator';
|
||||
import { useSession } from '../../hooks/useSession';
|
||||
import { TextCaseToggle } from '../../components/ui/text-case-toggle';
|
||||
import { AdaptiveTitle, AdaptiveParagraph, AdaptiveText } from '../../components/ui/adaptive-text';
|
||||
import { useUppercasePreference } from '../../hooks/useUppercasePreference';
|
||||
|
||||
export function CreateStoryPage() {
|
||||
const navigate = useNavigate();
|
||||
const { session } = useSession();
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
|
||||
const { isUpperCase, toggleUppercase, isLoading } = useUppercasePreference(session?.user?.id);
|
||||
|
||||
if (!session) {
|
||||
return (
|
||||
<div className="text-center py-12">
|
||||
@ -39,11 +44,23 @@ export function CreateStoryPage() {
|
||||
<Sparkles className="h-6 w-6 text-purple-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">Criar Nova História</h1>
|
||||
<p className="text-gray-600">
|
||||
Vamos criar uma história personalizada baseada nos seus interesses
|
||||
</p>
|
||||
<AdaptiveTitle
|
||||
text="Criar Nova História"
|
||||
isUpperCase={isUpperCase}
|
||||
className="text-2xl font-bold text-gray-900"
|
||||
/>
|
||||
<AdaptiveParagraph
|
||||
text="Vamos criar uma história personalizada baseada nos seus interesses"
|
||||
isUpperCase={isUpperCase}
|
||||
className="text-gray-600"
|
||||
/>
|
||||
</div>
|
||||
<TextCaseToggle
|
||||
isUpperCase={isUpperCase}
|
||||
onToggle={toggleUppercase}
|
||||
isLoading={isLoading}
|
||||
className="ml-auto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
@ -59,10 +76,19 @@ export function CreateStoryPage() {
|
||||
Como funciona?
|
||||
</h3>
|
||||
<ol className="text-sm text-purple-700 space-y-2">
|
||||
<li>1. Conte-nos sobre seus interesses e preferências</li>
|
||||
<li>2. Escolha personagens e cenários para sua história</li>
|
||||
<li>3. Nossa IA criará uma história única e personalizada</li>
|
||||
<li>4. Você poderá ler e praticar com sua nova história</li>
|
||||
{[
|
||||
'Conte-nos sobre seus interesses e preferências',
|
||||
'Escolha personagens e cenários para sua história',
|
||||
'Nossa IA criará uma história única e personalizada',
|
||||
'Você poderá ler e praticar com sua nova história'
|
||||
].map((text, index) => (
|
||||
<AdaptiveText
|
||||
key={index}
|
||||
text={`${index + 1}. ${text}`}
|
||||
isUpperCase={isUpperCase}
|
||||
as="li"
|
||||
/>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -5,6 +5,10 @@ import { WordFormation } from '../../components/exercises/WordFormation';
|
||||
import { SentenceCompletion } from '../../components/exercises/SentenceCompletion';
|
||||
import { PronunciationPractice } from '../../components/exercises/PronunciationPractice';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
import { TextCaseToggle } from '../../components/ui/text-case-toggle';
|
||||
import { AdaptiveText, AdaptiveTitle, AdaptiveParagraph } from '../../components/ui/adaptive-text';
|
||||
import { useUppercasePreference } from '../../hooks/useUppercasePreference';
|
||||
import { useSession } from '../../hooks/useSession';
|
||||
|
||||
interface ExerciseWord {
|
||||
word: string;
|
||||
@ -31,6 +35,8 @@ export function ExercisePage() {
|
||||
const [exerciseData, setExerciseData] = React.useState<any>(null);
|
||||
const [exerciseWords, setExerciseWords] = React.useState<ExerciseWord[]>([]);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
const { session } = useSession();
|
||||
const { isUpperCase, toggleUppercase, isLoading } = useUppercasePreference(session?.user?.id);
|
||||
|
||||
React.useEffect(() => {
|
||||
const loadExerciseData = async () => {
|
||||
@ -136,7 +142,9 @@ export function ExercisePage() {
|
||||
case 'word-formation':
|
||||
return (
|
||||
<WordFormation
|
||||
words={exerciseWords.map(w => w.word)}
|
||||
words={exerciseWords.map(w =>
|
||||
isUpperCase ? w.word.toUpperCase() : w.word
|
||||
)}
|
||||
storyId={storyId as string}
|
||||
studentId={exerciseData.story.student_id}
|
||||
/>
|
||||
@ -170,8 +178,24 @@ export function ExercisePage() {
|
||||
className="flex items-center gap-2 text-gray-600 hover:text-gray-900 mb-4"
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5" />
|
||||
Voltar para história
|
||||
<AdaptiveText
|
||||
text="Voltar para história"
|
||||
isUpperCase={isUpperCase}
|
||||
/>
|
||||
</button>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<AdaptiveTitle
|
||||
text={`Exercício: ${type?.replace('-', ' ')}`}
|
||||
isUpperCase={isUpperCase}
|
||||
className="text-xl font-bold"
|
||||
/>
|
||||
<TextCaseToggle
|
||||
isUpperCase={isUpperCase}
|
||||
onToggle={toggleUppercase}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Exercício */}
|
||||
|
||||
@ -8,6 +8,10 @@ import { StoryMetrics } from '../../components/story/StoryMetrics';
|
||||
import { convertWebmToMp3 } from '../../utils/audioConverter';
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { ExerciseSuggestions } from '../../components/learning/ExerciseSuggestions';
|
||||
import { TextCaseToggle } from '../../components/ui/text-case-toggle';
|
||||
import { AdaptiveText, AdaptiveTitle, AdaptiveParagraph } from '../../components/ui/adaptive-text';
|
||||
import { useUppercasePreference } from '../../hooks/useUppercasePreference';
|
||||
import { useSession } from '../../hooks/useSession';
|
||||
|
||||
interface StoryRecording {
|
||||
id: string;
|
||||
@ -385,6 +389,8 @@ export function StoryPage() {
|
||||
const [loadingRecordings, setLoadingRecordings] = React.useState(true);
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const { session } = useSession();
|
||||
const { isUpperCase, toggleUppercase, isLoading } = useUppercasePreference(session?.user?.id);
|
||||
|
||||
React.useEffect(() => {
|
||||
const fetchStory = async () => {
|
||||
@ -602,10 +608,15 @@ export function StoryPage() {
|
||||
className="flex items-center gap-2 text-gray-600 hover:text-gray-900"
|
||||
>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
Voltar para histórias
|
||||
<AdaptiveText text="Voltar para histórias" isUpperCase={isUpperCase} />
|
||||
</button>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<TextCaseToggle
|
||||
isUpperCase={isUpperCase}
|
||||
onToggle={toggleUppercase}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<button
|
||||
onClick={handleShare}
|
||||
className="flex items-center gap-2 px-4 py-2 text-gray-600 hover:text-gray-900"
|
||||
@ -635,12 +646,18 @@ export function StoryPage() {
|
||||
)}
|
||||
|
||||
<div className="p-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-6">{story?.title}</h1>
|
||||
<AdaptiveTitle
|
||||
text={story?.title || ''}
|
||||
isUpperCase={isUpperCase}
|
||||
className="text-3xl font-bold text-gray-900 mb-6"
|
||||
/>
|
||||
|
||||
{/* Texto da página atual */}
|
||||
<p className="text-xl leading-relaxed text-gray-700 mb-8">
|
||||
{story?.content?.pages?.[currentPage]?.text || 'Carregando...'}
|
||||
</p>
|
||||
<AdaptiveParagraph
|
||||
text={story?.content?.pages?.[currentPage]?.text || 'Carregando...'}
|
||||
isUpperCase={isUpperCase}
|
||||
className="text-xl leading-relaxed text-gray-700 mb-8"
|
||||
/>
|
||||
|
||||
{/* Gravador de áudio */}
|
||||
<AudioRecorder
|
||||
|
||||
@ -64,7 +64,7 @@ export function StudentDashboardLayout() {
|
||||
{!isCollapsed && <span>Minhas Histórias</span>}
|
||||
</NavLink>
|
||||
|
||||
<NavLink
|
||||
{/* <NavLink
|
||||
to="/aluno/conquistas"
|
||||
onClick={handleNavigation}
|
||||
className={({ isActive }) =>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user