Pierwszy wrzut promptstory
This commit is contained in:
212
components/StepResult.tsx
Normal file
212
components/StepResult.tsx
Normal file
@@ -0,0 +1,212 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { GeneratedContent, WizardState } from '../types';
|
||||
import { Copy, Check, Instagram, Image as ImageIcon, MessageSquare, Edit2, RefreshCw, X } from 'lucide-react';
|
||||
import TripMap from './TripMap';
|
||||
|
||||
interface StepResultProps {
|
||||
content: GeneratedContent;
|
||||
onRegenerate: (slideCount: number, feedback: string) => void;
|
||||
isRegenerating: boolean;
|
||||
// We need to access the wizard state to check for trip data
|
||||
// But standard props here only have content.
|
||||
// Ideally, StepResult should receive `data` too, but for now I'll check if I can pass it from App.tsx or infer it.
|
||||
// Wait, I can't access `data` unless I modify App.tsx to pass it to StepResult.
|
||||
// Let's assume the parent updates the props.
|
||||
// Actually, I'll modify the StepResultProps in this file, but I also need to modify App.tsx to pass 'data'.
|
||||
// However, looking at App.tsx, StepResult is rendered inside App.tsx. I can pass `data` there easily.
|
||||
// But wait, the previous code block for StepResult didn't show 'data' in props.
|
||||
// I will add `tripData` to the props.
|
||||
}
|
||||
|
||||
// Extending interface to include tripData optionally passed from parent
|
||||
// Note: I will update App.tsx to pass this prop.
|
||||
interface ExtendedStepResultProps extends StepResultProps {
|
||||
tripData?: WizardState['tripData'];
|
||||
}
|
||||
|
||||
const StepResult: React.FC<ExtendedStepResultProps> = ({ content, onRegenerate, isRegenerating, tripData }) => {
|
||||
const [copiedSection, setCopiedSection] = useState<string | null>(null);
|
||||
const [copiedSlideIndex, setCopiedSlideIndex] = useState<number | null>(null);
|
||||
|
||||
// Edit Mode State
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [slideCount, setSlideCount] = useState(content.slides.length || 12);
|
||||
const [feedback, setFeedback] = useState("");
|
||||
|
||||
const copyToClipboard = (text: string, sectionId: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
setCopiedSection(sectionId);
|
||||
setTimeout(() => setCopiedSection(null), 2000);
|
||||
};
|
||||
|
||||
const copySlideText = (text: string, index: number) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
setCopiedSlideIndex(index);
|
||||
setTimeout(() => setCopiedSlideIndex(null), 2000);
|
||||
};
|
||||
|
||||
const handleApplyChanges = () => {
|
||||
onRegenerate(slideCount, feedback);
|
||||
setIsEditing(false); // Close edit panel on submit, assumes success or loading state handles visual feedback
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-12 animate-fade-in pb-20 relative">
|
||||
|
||||
{/* Top Header with Edit Button */}
|
||||
<div className="text-center max-w-2xl mx-auto relative">
|
||||
<h2 className="text-4xl font-bold tracking-tight text-gray-900 mb-3">Twój Vibe Gotowy! 🎉</h2>
|
||||
<p className="text-gray-500 text-lg mb-6">Oto kompletna struktura Twojego posta. Skopiuj i publikuj.</p>
|
||||
|
||||
{!isEditing && !isRegenerating && (
|
||||
<button
|
||||
onClick={() => setIsEditing(true)}
|
||||
className="inline-flex items-center space-x-2 text-sm font-bold text-gray-600 bg-gray-100 hover:bg-gray-200 px-5 py-2.5 rounded-full transition-colors"
|
||||
>
|
||||
<Edit2 size={16} />
|
||||
<span>Edytuj / Popraw</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Edit Panel (Conditional) */}
|
||||
{(isEditing || isRegenerating) && (
|
||||
<div className="bg-white border-2 border-[#EA4420]/20 rounded-xl p-6 shadow-sm mb-10 animate-fade-in relative overflow-hidden">
|
||||
{isRegenerating && (
|
||||
<div className="absolute inset-0 bg-white/80 z-10 flex flex-col items-center justify-center backdrop-blur-[1px]">
|
||||
<RefreshCw size={32} className="text-[#EA4420] animate-spin mb-3" />
|
||||
<p className="font-bold text-gray-800">Nanuszę poprawki...</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-between items-start mb-6">
|
||||
<h3 className="text-xl font-bold text-gray-900 flex items-center gap-2">
|
||||
<Edit2 size={20} className="text-[#EA4420]" />
|
||||
Wprowadź poprawki
|
||||
</h3>
|
||||
{!isRegenerating && (
|
||||
<button onClick={() => setIsEditing(false)} className="text-gray-400 hover:text-gray-600">
|
||||
<X size={24} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Slider */}
|
||||
<div>
|
||||
<label className="flex justify-between text-sm font-bold text-gray-700 mb-3">
|
||||
<span>Liczba slajdów / Elementów</span>
|
||||
<span className="text-[#EA4420]">{slideCount}</span>
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
min="3"
|
||||
max="20"
|
||||
value={slideCount}
|
||||
onChange={(e) => setSlideCount(parseInt(e.target.value))}
|
||||
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-[#EA4420]"
|
||||
/>
|
||||
<div className="flex justify-between text-xs text-gray-400 mt-2 font-medium">
|
||||
<span>3 (Minimum)</span>
|
||||
<span>20 (Maksimum)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Feedback Textarea */}
|
||||
<div>
|
||||
<label className="block text-sm font-bold text-gray-700 mb-2">Co chcesz zmienić w treści?</label>
|
||||
<textarea
|
||||
value={feedback}
|
||||
onChange={(e) => setFeedback(e.target.value)}
|
||||
placeholder="np. Zmień 'ból szczęki' na 'ból głowy'. Dodaj więcej emoji w slajdzie nr 3. Zrób bardziej agresywny wstęp."
|
||||
rows={3}
|
||||
className="w-full border border-gray-200 rounded-md p-3 text-sm focus:ring-1 focus:ring-[#EA4420] focus:border-[#EA4420] outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex justify-end pt-2">
|
||||
<button
|
||||
onClick={handleApplyChanges}
|
||||
className="bg-[#EA4420] text-white px-6 py-3 rounded-md font-bold hover:bg-[#d63b1a] transition-colors flex items-center gap-2"
|
||||
>
|
||||
<RefreshCw size={18} />
|
||||
Zastosuj poprawki
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* TRIP MAP (IF APPLICABLE) */}
|
||||
{tripData && tripData.startPoint && (
|
||||
<TripMap tripData={tripData} />
|
||||
)}
|
||||
|
||||
{/* Caption Section */}
|
||||
<div className="bg-white rounded-md border border-gray-200 overflow-hidden">
|
||||
<div className="bg-gray-50 px-8 py-5 border-b border-gray-200 flex justify-between items-center">
|
||||
<div className="flex items-center space-x-3 text-gray-900">
|
||||
<MessageSquare size={20} className="text-[#EA4420]" />
|
||||
<span className="font-bold">Post Caption (Opis)</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => copyToClipboard(content.caption, 'caption')}
|
||||
className="flex items-center space-x-2 text-sm font-semibold text-[#EA4420] hover:text-[#d63b1a] transition-colors bg-white border border-gray-200 px-4 py-2 rounded-md hover:bg-gray-50"
|
||||
>
|
||||
{copiedSection === 'caption' ? <Check size={16} /> : <Copy size={16} />}
|
||||
<span>{copiedSection === 'caption' ? 'Skopiowano!' : 'Kopiuj'}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-8 text-gray-700 whitespace-pre-wrap font-sans text-base leading-relaxed">
|
||||
{content.caption}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Slides Grid */}
|
||||
<div>
|
||||
<div className="flex items-center space-x-3 text-gray-900 mb-8 px-1">
|
||||
<ImageIcon size={28} className="text-[#EA4420]" />
|
||||
<h3 className="text-2xl font-bold tracking-tight">Struktura Wizualna (Slajdy / Zdjęcia)</h3>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{content.slides.map((slide, idx) => (
|
||||
<div key={idx} className="bg-white rounded-md border border-gray-200 flex flex-col h-full hover:border-[#EA4420]/30 transition-colors group">
|
||||
<div className="px-6 py-4 border-b border-gray-100 bg-gray-50/50 flex justify-between items-center">
|
||||
<span className="text-xs font-bold text-gray-400 uppercase tracking-widest">
|
||||
Element {idx + 1}
|
||||
</span>
|
||||
</div>
|
||||
<div className="p-6 flex-1 flex flex-col space-y-6">
|
||||
<div>
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<span className="text-xs text-[#EA4420] uppercase font-bold tracking-wider">Nagłówek / Typ</span>
|
||||
<button
|
||||
onClick={() => copySlideText(slide.overlay_text, idx)}
|
||||
className="text-gray-300 hover:text-[#EA4420] transition-colors"
|
||||
title="Kopiuj tekst"
|
||||
>
|
||||
{copiedSlideIndex === idx ? <Check size={14} className="text-green-500" /> : <Copy size={14} />}
|
||||
</button>
|
||||
</div>
|
||||
<p className="font-bold text-gray-900 text-xl leading-tight">"{slide.overlay_text}"</p>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t border-gray-100 mt-auto">
|
||||
<div className="flex items-start space-x-3 text-gray-500">
|
||||
<ImageIcon size={16} className="mt-1 flex-shrink-0 text-gray-400" />
|
||||
<p className="text-sm italic leading-relaxed">{slide.image_prompt}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StepResult;
|
||||
Reference in New Issue
Block a user