import React, { useState, useEffect } from 'react'; import { WizardState, Step, GeneratedContent } from './types'; import StepContext from './components/StepContext'; import StepPlatform from './components/StepPlatform'; import StepEventType from './components/StepEventType'; import StepToneGoal from './components/StepToneGoal'; import StepDetails from './components/StepDetails'; import StepResult from './components/StepResult'; import { ChevronLeft, ExternalLink, Sparkles, User, Lock, ArrowRight, AlertCircle, Bug } from 'lucide-react'; import { generateStoryContent } from './services/geminiService'; import { getEnvVar } from './utils/envUtils'; // --- CONFIG IMPORT --- import { AUTHOR_CONFIG } from './_EDITABLE_CONFIG/author'; import { UI_TEXT } from './_EDITABLE_CONFIG/ui_text'; // --- ASSET IMPORT --- // Importujemy logo bezpośrednio, aby bundler poprawnie rozwiązał ścieżkę import logoImg from './logo.png'; const STORAGE_KEY = 'gpx-storyteller-state-v6'; const AUTH_KEY = 'promptstory-auth-token'; const APP_PASSWORD = getEnvVar('VITE_APP_PASSWORD'); const INITIAL_STATE: WizardState = { step: Step.CONTEXT, context: null, storyStyle: null, platform: null, eventType: null, tone: null, goal: null, title: 'Poznań Maraton 2025', description: 'Byłem totalnie nieprzygotowany. Ostatnie pół roku prawie nie ćwiczyłem, a w dodatku biegłem na antybiotykach z zapaleniem osemki. To była walka a nie przyjemność z biegania. To było głupie. Ale było warto', files: [], stats: { distance: '', duration: '', elevation: '', }, waypoints: [], tripData: { startPoint: { place: '', description: '' }, endPoint: { place: '', description: '' }, stops: [], travelMode: null, googleMapsKey: '' } }; const LoginScreen: React.FC<{ onLogin: (success: boolean) => void }> = ({ onLogin }) => { const [input, setInput] = useState(''); const [error, setError] = useState(false); const isConfigMissing = !APP_PASSWORD; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (input.trim() === APP_PASSWORD) { onLogin(true); } else { setError(true); setInput(''); } }; if (isConfigMissing) { return (

{UI_TEXT.login.configError}

Missing VITE_APP_PASSWORD.

); } return (

{UI_TEXT.login.title}

{UI_TEXT.login.desc}

{ setInput(e.target.value); setError(false); }} placeholder="Hasło..." className={`w-full p-4 border rounded-lg outline-none transition-all font-medium text-center tracking-widest ${ error ? 'border-red-300 bg-red-50 focus:border-red-500' : 'border-gray-200 focus:border-[#EA4420] focus:ring-1 focus:ring-[#EA4420]' }`} autoFocus /> {error && (
{UI_TEXT.login.error}
)}
); }; const App: React.FC = () => { const [isAuthenticated, setIsAuthenticated] = useState(false); const [isAuthChecking, setIsAuthChecking] = useState(true); const [data, setData] = useState(INITIAL_STATE); const [generatedContent, setGeneratedContent] = useState(null); const [isGenerating, setIsGenerating] = useState(false); const [isLoaded, setIsLoaded] = useState(false); const [errorMessage, setErrorMessage] = useState(null); // LOGO & AVATAR STATE const [logoError, setLogoError] = useState(false); const [avatarError, setAvatarError] = useState(false); useEffect(() => { const storedAuth = localStorage.getItem(AUTH_KEY); if (storedAuth === 'true') setIsAuthenticated(true); setIsAuthChecking(false); }, []); const handleLogin = (success: boolean) => { if (success) { localStorage.setItem(AUTH_KEY, 'true'); setIsAuthenticated(true); } }; const handleLogout = () => { localStorage.removeItem(AUTH_KEY); setIsAuthenticated(false); resetApp(); }; useEffect(() => { const saved = localStorage.getItem(STORAGE_KEY); if (saved) { try { const parsed = JSON.parse(saved); parsed.files = []; if (!parsed.stats) parsed.stats = { distance: '', duration: '', elevation: '' }; if (!parsed.waypoints) parsed.waypoints = []; if (!parsed.tripData) parsed.tripData = { ...INITIAL_STATE.tripData }; if (!parsed.tone) parsed.tone = null; if (!parsed.goal) parsed.goal = null; if (!parsed.storyStyle) parsed.storyStyle = null; setData(parsed); } catch (e) { console.error("Failed to load state", e); } } setIsLoaded(true); }, []); useEffect(() => { if (isLoaded) { const stateToSave = { ...data, files: [] }; localStorage.setItem(STORAGE_KEY, JSON.stringify(stateToSave)); } }, [data, isLoaded]); const updateData = (updates: Partial | ((prev: WizardState) => Partial)) => { setData(prev => { const newValues = typeof updates === 'function' ? updates(prev) : updates; return { ...prev, ...newValues }; }); }; const nextStep = () => { if (data.step < Step.RESULT) updateData({ step: data.step + 1 }); }; const prevStep = () => { if (data.step > Step.CONTEXT) updateData({ step: data.step - 1 }); }; const handleGenerate = async () => { setIsGenerating(true); setErrorMessage(null); const apiKey = getEnvVar('VITE_API_KEY'); if (!apiKey) { setErrorMessage("BŁĄD: Brak VITE_API_KEY."); setIsGenerating(false); return; } try { const content = await generateStoryContent(data, apiKey); setGeneratedContent(content); updateData({ step: Step.RESULT }); } catch (error: any) { setErrorMessage("Błąd: " + (error.message || 'Unknown')); } finally { setIsGenerating(false); } }; const handleRegenerate = async (slideCount: number, feedback: string) => { setIsGenerating(true); setErrorMessage(null); const apiKey = getEnvVar('VITE_API_KEY'); try { const content = await generateStoryContent(data, apiKey || '', { slideCount, feedback }); setGeneratedContent(content); } catch (error: any) { setErrorMessage("Błąd: " + (error.message || '')); } finally { setIsGenerating(false); } }; const resetApp = () => { setData({ ...INITIAL_STATE }); setGeneratedContent(null); localStorage.removeItem(STORAGE_KEY); window.location.reload(); }; if (isAuthChecking || !isLoaded) return null; if (!isAuthenticated) return ; return (
{/* LOGO LOGIC: Use imported logoImg */} {!logoError ? ( Logo setLogoError(true)} className="h-10 object-contain" /> ) : (

{UI_TEXT.header.appTitle}

)}
{data.step !== Step.RESULT && (
{UI_TEXT.steps.labels.map((label, idx) => ( = idx ? 'text-[#EA4420]' : 'text-gray-300'}`}> 0{idx + 1}. {label} ))}
)}
{data.step === Step.CONTEXT && } {data.step === Step.EVENT_TYPE && } {data.step === Step.PLATFORM && } {data.step === Step.TONE_GOAL && } {data.step === Step.DETAILS && } {data.step === Step.RESULT && generatedContent && } {errorMessage && (
{errorMessage}
)}
{data.step > Step.CONTEXT && data.step < Step.RESULT && (
)}
{/* FOOTER - Uses AUTHOR_CONFIG */}
); }; export default App;