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'; const STORAGE_KEY = 'gpx-storyteller-state-v6'; // Incremented version for structure change const AUTH_KEY = 'promptstory-auth-token'; // --- PASSWORD CONFIGURATION --- // 1. Try fetching from Vite standard (import.meta.env) // @ts-ignore const VITE_ENV_PASS = import.meta.env && import.meta.env.VITE_APP_PASSWORD ? import.meta.env.VITE_APP_PASSWORD : ''; // 2. Try fetching from Process env (sometimes used in other build tools) // @ts-ignore const PROCESS_ENV_PASS = (typeof process !== 'undefined' && process.env && process.env.VITE_APP_PASSWORD) ? process.env.VITE_APP_PASSWORD : ''; // 3. Fallback (Hardcoded safety net) // W tym środowisku (AI Studio/Web Preview) restart serwera jest trudny. // Ustawiamy hasło "na sztywno" jako zapas, żeby działało od razu. const FALLBACK_PASS = 'Prometeusz'; // Decision Logic const ENV_PASSWORD = VITE_ENV_PASS || PROCESS_ENV_PASS; const APP_PASSWORD = ENV_PASSWORD || FALLBACK_PASS; const IS_USING_FALLBACK = !ENV_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: '' } }; // --- LOGIN SCREEN COMPONENT --- const LoginScreen: React.FC<{ onLogin: (success: boolean) => void }> = ({ onLogin }) => { const [input, setInput] = useState(''); const [error, setError] = useState(false); const [showDebug, setShowDebug] = useState(false); // DEBUGGING: Log to console on mount useEffect(() => { console.group("--- DEBUG PASSWORD SYSTEM ---"); console.log("1. Detected VITE_ENV:", VITE_ENV_PASS ? "****" : "(empty)"); console.log("2. Detected PROCESS_ENV:", PROCESS_ENV_PASS ? "****" : "(empty)"); console.log("3. Final Password Source:", IS_USING_FALLBACK ? "FALLBACK (Hardcoded)" : ".ENV FILE"); console.log("4. Active Password Length:", APP_PASSWORD.length); console.groupEnd(); }, []); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); // Trim input to avoid accidental spaces from copy-paste if (input.trim() === APP_PASSWORD) { onLogin(true); } else { console.log(`Failed Login. Input: "${input}" vs Expected: "${APP_PASSWORD}"`); setError(true); setInput(''); } }; return (
{/* Debug Toggle (Hidden in corner) */}

Dostęp Chroniony

Wprowadź hasło, aby uzyskać dostęp do kreatora PromptStory.

{ 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 && (
Nieprawidłowe hasło
)}
{/* DEBUG PANEL */} {showDebug && (

DEBUG STATUS:

SOURCE: {IS_USING_FALLBACK ? "FALLBACK (Code)" : ".ENV FILE"} ACTIVE PASS: {APP_PASSWORD.substring(0, 4)}... (len: {APP_PASSWORD.length})
{IS_USING_FALLBACK && (

System używa hasła awaryjnego zdefiniowanego w kodzie, ponieważ nie może odświeżyć pliku .env bez restartu.

Twoje hasło powinno teraz działać.

)}
)}

PromptStory v1.0 • Private Instance

); }; const App: React.FC = () => { // Auth State const [isAuthenticated, setIsAuthenticated] = useState(false); const [isAuthChecking, setIsAuthChecking] = useState(true); // App State 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); // Image loading states const [logoError, setLogoError] = useState(false); const [avatarError, setAvatarError] = useState(false); // 1. Check Auth on Mount 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(); }; // 2. Load Data Persistence Logic useEffect(() => { const saved = localStorage.getItem(STORAGE_KEY); if (saved) { try { const parsed = JSON.parse(saved); // Reset files because File objects cannot be serialized to local storage parsed.files = []; // Ensure new fields exist if loading from old state if (!parsed.stats) parsed.stats = { distance: '', duration: '', elevation: '' }; if (!parsed.waypoints) parsed.waypoints = []; // Ensure tripData exists if (!parsed.tripData) { parsed.tripData = { ...INITIAL_STATE.tripData }; } // Defaults for new fields if migrating 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) { // Exclude files from persistence to avoid quota issues and serialization errors const stateToSave = { ...data, files: [] }; localStorage.setItem(STORAGE_KEY, JSON.stringify(stateToSave)); } }, [data, isLoaded]); // UPDATED: Now supports functional updates to prevent stale state issues 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); try { const content = await generateStoryContent( data, process.env.API_KEY || '' ); setGeneratedContent(content); updateData({ step: Step.RESULT }); } catch (error: any) { console.error("Generowanie nie powiodło się:", error); setErrorMessage("Błąd generowania. Sprawdź poprawność klucza API w pliku .env oraz połączenie z siecią. " + (error.message || '')); } finally { setIsGenerating(false); } }; const handleRegenerate = async (slideCount: number, feedback: string) => { setIsGenerating(true); setErrorMessage(null); try { const content = await generateStoryContent( data, process.env.API_KEY || '', { slideCount, feedback } ); setGeneratedContent(content); } catch (error: any) { console.error("Regenerowanie nie powiodło się:", error); setErrorMessage("Błąd regenerowania: " + (error.message || '')); } finally { setIsGenerating(false); } }; const resetApp = () => { setData({ ...INITIAL_STATE }); setGeneratedContent(null); localStorage.removeItem(STORAGE_KEY); // Force reload to clear any hung states window.location.reload(); }; // --- RENDER LOGIC --- if (isAuthChecking || !isLoaded) return null; // Loading state if (!isAuthenticated) { return ; } // Step Labels for Progress Bar (Order Changed) const stepsLabels = ['Kontekst', 'Typ', 'Platforma', 'Vibe & Cel', 'Szczegóły']; return (
{/* Header */}
{!logoError ? ( setLogoError(true)} alt="PromptStory Logo" className="w-10 h-10 object-contain" /> ) : (
)}

PromptStory

{/* Progress Bar */} {data.step !== Step.RESULT && (
{stepsLabels.map((label, idx) => ( = idx ? 'text-[#EA4420]' : 'text-gray-300'}`}> 0{idx + 1}. {label} ))}
)} {/* Dynamic Content Container */}
{/* STEP 1: CONTEXT */} {data.step === Step.CONTEXT && } {/* STEP 2: EVENT TYPE (Moved UP) */} {data.step === Step.EVENT_TYPE && } {/* STEP 3: PLATFORM (Moved DOWN) */} {data.step === Step.PLATFORM && } {/* STEP 4: TONE & GOAL (NEW) */} {data.step === Step.TONE_GOAL && } {/* STEP 5: DETAILS */} {data.step === Step.DETAILS && ( )} {/* STEP 6: RESULT */} {data.step === Step.RESULT && generatedContent && ( )} {/* Error Message */} {errorMessage && (
{errorMessage}
)}
{/* Navigation Controls (Back Only) */} {data.step > Step.CONTEXT && data.step < Step.RESULT && (
)}
{/* Footer / Author Info */}
); }; export default App;