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'; const STORAGE_KEY = 'gpx-storyteller-state-v6'; const AUTH_KEY = 'promptstory-auth-token'; // --- PASSWORD CONFIGURATION --- // STRICT MODE: No fallbacks. The environment variable must be set in Coolify/Vercel/Netlify. 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: '' } }; // --- LOGIN SCREEN COMPONENT --- const LoginScreen: React.FC<{ onLogin: (success: boolean) => void }> = ({ onLogin }) => { const [input, setInput] = useState(''); const [error, setError] = useState(false); // Check if password is misconfigured (empty) 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 (

Błąd Konfiguracji

Aplikacja nie wykryła hasła w zmiennych środowiskowych.

Brakuje zmiennej: VITE_APP_PASSWORD

Jeśli używasz Coolify/Vercel:

1. Dodaj zmienną w panelu.

2. Przebuduj projekt (Re-deploy).

); } return (

Dostęp Chroniony

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

{ 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
)}

PromptStory v1.2 • Secure Production Build

); }; 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); parsed.files = []; // Reset 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); // STRICT MODE: Use VITE_API_KEY const apiKey = getEnvVar('VITE_API_KEY'); if (!apiKey) { setErrorMessage("BŁĄD KRYTYCZNY: Brak klucza API (VITE_API_KEY). Skonfiguruj zmienne w panelu Coolify."); setIsGenerating(false); return; } try { const content = await generateStoryContent(data, apiKey); setGeneratedContent(content); updateData({ step: Step.RESULT }); } catch (error: any) { console.error("Generowanie nie powiodło się:", error); setErrorMessage("Błąd generowania: " + (error.message || 'Sprawdź konsolę')); } 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) { 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); window.location.reload(); }; if (isAuthChecking || !isLoaded) return null; if (!isAuthenticated) { return ; } const stepsLabels = ['Kontekst', 'Typ', 'Platforma', 'Vibe & Cel', 'Szczegóły']; return (
{!logoError ? ( setLogoError(true)} alt="PromptStory Logo" className="w-10 h-10 object-contain" /> ) : (
)}

PromptStory

{data.step !== Step.RESULT && (
{stepsLabels.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 && (
)}
); }; export default App;