GrantForge Bot
Deploy to Hugging Face
afd56bc
import React, { useState, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { motion, AnimatePresence } from 'framer-motion';
import { X, Search, Sparkles, Building, ChevronRight, Loader2, CheckCircle, Cpu, Tractor, HardHat, Info } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '@clerk/clerk-react';
import { createProject, lookupCompany, matchProgram } from '../../api/client';
import toast from 'react-hot-toast';
interface Props {
onClose: () => void;
}
const WizardModal: React.FC<Props> = ({ onClose }) => {
const navigate = useNavigate();
const { userId } = useAuth();
const [step, setStep] = useState(1);
const [nip, setNip] = useState('');
const [desc, setDesc] = useState('');
const [isSearching, setIsSearching] = useState(false);
const [companyFound, setCompanyFound] = useState(false);
const [analyzing, setAnalyzing] = useState(false);
const [analysisLogs, setAnalysisLogs] = useState<string[]>([]);
const [selectedProgram, setSelectedProgram] = useState<number | null>(1);
const [expandedProgram, setExpandedProgram] = useState<number | null>(null);
const [companyDetails, setCompanyDetails] = useState<{name: string, status: string, nip?: string} | null>(null);
const [recommendedPrograms, setRecommendedPrograms] = useState<any[]>([]);
const [manualEntry, setManualEntry] = useState(false);
const [clarifyingQuestions, setClarifyingQuestions] = useState<string[]>([]);
const [clarificationAnswers, setClarificationAnswers] = useState<string[]>([]);
const [clarificationPanelOpen, setClarificationPanelOpen] = useState(false);
const handleSearchCompany = async () => {
const cleanedNip = nip.replace(/\D/g, '');
if (cleanedNip.length !== 10) {
toast.error("NIP musi składać się dokładnie z 10 cyfr.");
return;
}
setIsSearching(true);
try {
const data = await lookupCompany(cleanedNip);
setCompanyDetails({ name: data.name, status: data.status, nip: cleanedNip });
setCompanyFound(true);
setManualEntry(false);
} catch(e: any) {
const errMsg = e.response?.data?.detail || "Nie znaleziono w bazie GUS. Możesz kontynuować, wpisując nazwę ręcznie.";
toast.error(errMsg);
setCompanyFound(false);
setManualEntry(true);
setCompanyDetails({ name: '', status: 'Dane wprowadzone ręcznie', nip: nip });
} finally {
setIsSearching(false);
}
};
const handleStartAnalysis = async (additionalContext?: string) => {
if (!desc && !additionalContext) return;
setStep(3);
setAnalyzing(true);
setAnalysisLogs(prev => [...prev, additionalContext ? 'Aktualizacja analizy z nowymi danymi...' : 'Inicjalizowanie silnika RAG...']);
// Złącz główny opis i dodatkowy kontekst z pytań jeśli są
const finalDesc = additionalContext ? `${desc}\n\n[DOPRECYZOWANIE]:\n${additionalContext}` : desc;
if (additionalContext) setDesc(finalDesc);
try {
const data = await matchProgram(finalDesc, nip);
setRecommendedPrograms(data.programs || []);
if (data.programs && data.programs.length > 0) setSelectedProgram(data.programs[0].id);
setClarifyingQuestions(data.clarifying_questions || []);
setClarificationAnswers(new Array((data.clarifying_questions || []).length).fill(''));
setClarificationPanelOpen((data.clarifying_questions || []).length > 0);
setAnalysisLogs(prev => [...prev, 'Zakończono generowanie raportu dopasowania.']);
} catch(e) {
toast.error("Wystąpił problem ze zgłaszaniem do modelu AI");
} finally {
setAnalyzing(false);
}
};
const renderStepContent = () => {
switch(step) {
case 1:
return (
<motion.div
key="step1"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 20 }}
style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem', width: '100%' }}
>
<div>
<h3 style={{ fontSize: '1.2rem', marginBottom: '0.4rem', color: 'var(--text-primary)' }}>Podłącz profil firmy</h3>
<p style={{ color: 'var(--text-secondary)', fontSize: '0.9rem' }}>System automatycznie pobierze dane rejestrowe z bazy GUS/KRS, by dopasować odpowiednie programy.</p>
</div>
<div style={{ display: 'flex', gap: '1rem', width: '100%' }}>
<div style={{ flex: 1, position: 'relative' }}>
<div style={{ position: 'absolute', left: '1rem', top: '50%', transform: 'translateY(-50%)', color: 'var(--accent-blue)' }}>
<Building size={20} />
</div>
<input
type="text"
className="wizard-input"
placeholder="Wpisz NIP firmy..."
value={nip}
onChange={e => setNip(e.target.value)}
style={{ width: '100%', padding: '1rem 1rem 1rem 3rem', background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(59, 130, 246, 0.4)', borderRadius: '12px', color: '#fff', fontSize: '1.1rem', outline: 'none' }}
onKeyDown={e => e.key === 'Enter' && handleSearchCompany()}
/>
</div>
<button className="btn btn-secondary" onClick={handleSearchCompany} disabled={isSearching || nip.replace(/\D/g, '').length !== 10} style={{ minWidth: '140px' }}>
{isSearching ? <Loader2 size={20} className="spin" /> : <><Search size={18}/> Szukaj w GUS</>}
</button>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<button
className="btn"
onClick={() => {
setNip('5213641211'); // Przykładowy realny NIP Testowy
setDesc('Jesteśmy firmą produkcyjną produkującą części do maszyn rolniczych. Planujemy zakup nowoczesnych robotów spawalniczych i obrabiarek CNC dla automatyzacji najcięższych procesów. Budżet projektu to 4.5 mln PLN. Wdrażamy innowację na skalę krajową.');
}}
style={{ fontSize: '0.85rem', color: 'var(--accent-blue)', background: 'rgba(59,130,246,0.1)', border: '1px dashed rgba(59,130,246,0.4)', padding: '0.5rem 1rem' }}
>
<Sparkles size={14} style={{ marginRight: '6px' }} />
Uzupełnij Zgłoszeniem Demo (Testowe)
</button>
{!manualEntry && (
<span style={{ fontSize: '0.85rem', color: 'var(--text-muted)', cursor: 'pointer', textDecoration: 'underline' }} onClick={() => { setManualEntry(true); setCompanyDetails({ name: '', status: 'Dane wprowadzone ręcznie', nip: nip }); }}>
Wprowadź dane ręcznie
</span>
)}
</div>
{companyFound && !manualEntry && companyDetails && (
<motion.div initial={{ opacity: 0, height: 0 }} animate={{ opacity: 1, height: 'auto' }} style={{ background: 'rgba(16, 185, 129, 0.1)', borderLeft: '4px solid var(--accent-green)', padding: '1rem', borderRadius: '8px', marginTop: '1rem' }}>
<div style={{ fontWeight: 'bold', display: 'flex', alignItems: 'center', gap: '0.5rem', color: 'var(--accent-green)' }}><CheckCircle size={18}/> Znaleziono firmę</div>
<div style={{ marginTop: '0.5rem', fontSize: '0.9rem', color: 'var(--text-primary)' }}>NIP: {nip.replace(/\D/g, '')}</div>
<div style={{ fontSize: '0.9rem', color: 'var(--text-primary)' }}>{companyDetails.name} • {companyDetails.status}</div>
</motion.div>
)}
{manualEntry && (
<motion.div initial={{ opacity: 0, height: 0 }} animate={{ opacity: 1, height: 'auto' }} style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', marginTop: '1rem' }}>
<label style={{ fontSize: '0.9rem', color: 'var(--text-secondary)' }}>Pełna nazwa firmy (podmiotu):</label>
<input
type="text"
value={companyDetails?.name || ''}
onChange={e => setCompanyDetails({ name: e.target.value, status: 'Dane wpisane ręcznie', nip: nip })}
placeholder="Wpisz pełną nazwę przedsiębiorstwa..."
style={{ width: '100%', padding: '1rem', background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(245, 158, 11, 0.4)', borderRadius: '12px', color: '#fff', fontSize: '1.1rem', outline: 'none' }}
/>
</motion.div>
)}
<button
className="btn btn-primary"
style={{ marginTop: '1rem', alignSelf: 'flex-end' }}
onClick={() => setStep(2)}
disabled={(!companyFound && !manualEntry) || (manualEntry && (!companyDetails?.name || companyDetails.name.trim() === ''))}
>
Przejdź dalej <ChevronRight size={18} />
</button>
</motion.div>
);
case 2:
return (
<motion.div
key="step2"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 20 }}
style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem', width: '100%' }}
>
<div>
<h3 style={{ fontSize: '1.2rem', marginBottom: '0.4rem', color: 'var(--text-primary)' }}>Opisz planowaną inwestycję</h3>
<p style={{ color: 'var(--text-secondary)', fontSize: '0.9rem' }}>Napisz swoimi słowami, co zamierzasz zrobić. Sztuczna Inteligencja przejdzie przez tekst wyciągając kryteria kluczowe dla dotacji.</p>
</div>
<div style={{ position: 'relative', width: '100%' }}>
<Sparkles size={20} color="var(--accent-green)" style={{ position: 'absolute', top: '1.2rem', left: '1.2rem' }} />
<textarea
value={desc}
onChange={e => setDesc(e.target.value)}
className="wizard-textarea"
placeholder="Np. Planujemy zakup nowej linii produkcyjnej z robotami spawalniczymi oraz wdrożenie platformy chmurowej AI do przewidywania awarii maszyn. Szacowany budżet inwestycji to ok. 6 mln PLN..."
style={{ width: '100%', height: '220px', padding: '1.2rem 1.2rem 1.2rem 3.2rem', background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(16, 185, 129, 0.4)', borderRadius: '12px', color: '#fff', fontSize: '1.05rem', lineHeight: '1.5', outline: 'none', resize: 'none' }}
/>
</div>
<button className="btn btn-primary" style={{ alignSelf: 'flex-end', background: 'linear-gradient(90deg, var(--accent-green), var(--accent-blue))', padding: '1rem 2rem', fontSize: '1.1rem', boxShadow: '0 0 20px rgba(16, 185, 129, 0.3)' }} onClick={() => handleStartAnalysis()} disabled={!desc}>
<Sparkles size={20} /> Rozpocznij Analizę AI
</button>
</motion.div>
);
case 3: {
const activeExpandedProg = expandedProgram ? recommendedPrograms.find((p: any) => p.id === expandedProgram) : null;
return (
<motion.div
key="step3"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '2rem', width: '100%', minHeight: '350px' }}
>
{analyzing ? (
<>
<div style={{ position: 'relative', width: '100px', height: '100px' }}>
<motion.div animate={{ rotate: 360 }} transition={{ repeat: Infinity, duration: 4, ease: 'linear' }} style={{ position: 'absolute', inset: 0, borderRadius: '50%', border: '4px solid transparent', borderTopColor: 'var(--accent-green)', borderRightColor: 'var(--accent-blue)' }}></motion.div>
<motion.div animate={{ rotate: -360 }} transition={{ repeat: Infinity, duration: 3, ease: 'linear' }} style={{ position: 'absolute', inset: '10px', borderRadius: '50%', border: '4px solid transparent', borderTopColor: 'var(--accent-blue)', borderLeftColor: 'var(--accent-green)' }}></motion.div>
<div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Sparkles size={32} color="var(--accent-green)" />
</div>
</div>
<div style={{ textAlign: 'center', width: '100%' }}>
<h3 style={{ fontSize: '1.5rem', marginBottom: '1rem', color: 'var(--text-primary)' }}>Silnik RAG przetwarza wniosek...</h3>
<div style={{ height: '80px', display: 'flex', flexDirection: 'column', gap: '0.4rem', alignItems: 'center', color: 'var(--text-secondary)', fontSize: '0.9rem' }}>
<AnimatePresence mode="popLayout">
{analysisLogs.slice(-2).map((log, i) => (
<motion.div key={log} initial={{ opacity: 0, y: 10 }} animate={{ opacity: i === 1 ? 1 : 0.5, y: 0 }} exit={{ opacity: 0 }}>
{log}
</motion.div>
))}
</AnimatePresence>
</div>
</div>
</>
) : (
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} style={{ display: 'flex', flexDirection: 'column', width: '100%', gap: '1rem', marginTop: '-1rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.5rem' }}>
<h2 style={{ fontSize: '1.4rem', color: 'var(--text-primary)' }}>Wybierz docelowy program dotacyjny</h2>
<div style={{ padding: '0.3rem 0.8rem', background: 'rgba(16,185,129,0.1)', color: 'var(--accent-green)', borderRadius: '20px', fontSize: '0.8rem', fontWeight: 'bold' }}>{recommendedPrograms.length} szans zidentyfikowanych</div>
</div>
{clarifyingQuestions.length > 0 && (
<div style={{ marginBottom: '1rem', background: 'rgba(56, 189, 248, 0.05)', borderRadius: '16px', border: '1px solid rgba(56, 189, 248, 0.2)', overflow: 'hidden' }}>
<div
style={{ padding: '1rem', display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'pointer', background: 'rgba(56, 189, 248, 0.1)' }}
onClick={() => setClarificationPanelOpen(!clarificationPanelOpen)}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<Sparkles size={18} color="var(--accent-blue)" />
<strong style={{ color: 'var(--accent-blue)', fontSize: '0.95rem' }}>Doprecyzuj swój projekt, by uzyskać lepsze dopasowanie</strong>
</div>
<ChevronRight size={18} color="var(--accent-blue)" style={{ transform: clarificationPanelOpen ? 'rotate(90deg)' : 'none', transition: '0.3s' }} />
</div>
<AnimatePresence>
{clarificationPanelOpen && (
<motion.div initial={{ height: 0, opacity: 0 }} animate={{ height: 'auto', opacity: 1 }} exit={{ height: 0, opacity: 0 }}>
<div style={{ padding: '1rem' }}>
<p style={{ color: 'var(--text-secondary)', fontSize: '0.9rem', marginBottom: '1rem' }}>AI zauważyło, że brakuje kilku ważnych informacji. Odpowiedz na poniższe pytania, a system poszuka lepszych dotacji.</p>
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.8rem' }}>
{clarifyingQuestions.map((q, idx) => (
<div key={idx} style={{ display: 'flex', flexDirection: 'column', gap: '0.4rem' }}>
<span style={{ color: 'var(--text-primary)', fontSize: '0.9rem' }}>{q}</span>
<input
type="text"
value={clarificationAnswers[idx] || ''}
onChange={e => {
const newAnswers = [...clarificationAnswers];
newAnswers[idx] = e.target.value;
setClarificationAnswers(newAnswers);
}}
className="wizard-input"
placeholder="Twoja odpowiedź..."
style={{ width: '100%', padding: '0.7rem', background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(56, 189, 248, 0.3)', borderRadius: '8px', color: '#fff', fontSize: '0.95rem', outline: 'none' }}
/>
</div>
))}
</div>
<button
className="btn btn-primary"
style={{ marginTop: '1rem', width: '100%', background: 'var(--accent-blue)', color: '#000', fontWeight: 'bold' }}
onClick={() => {
const additionalContext = clarifyingQuestions.map((q, i) => clarificationAnswers[i] ? `Pytanie: ${q}\nOdpowiedź: ${clarificationAnswers[i]}` : '').filter(Boolean).join("\n\n");
if (additionalContext) handleStartAnalysis(additionalContext);
else toast.error('Podaj przynajmniej jedną odpowiedź.');
}}
>
Zaktualizuj dopasowanie
</button>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
)}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(230px, 1fr))', gap: '1rem', maxHeight: '380px', overflowY: 'auto', paddingRight: '0.5rem' }}>
{recommendedPrograms.map((prog) => (
<div key={prog.id}
className="hover-lift"
style={{
borderRadius: '16px',
border: selectedProgram === prog.id ? '2px solid var(--accent-green)' : '1px solid rgba(255,255,255,0.05)',
background: selectedProgram === prog.id ? 'rgba(16, 185, 129, 0.05)' : 'rgba(255,255,255,0.02)',
transition: 'all 0.2s',
boxShadow: selectedProgram === prog.id ? '0 0 20px rgba(16,185,129,0.1)' : 'none',
cursor: 'pointer',
display: 'flex', flexDirection: 'column'
}}
onClick={() => setSelectedProgram(prog.id)}
>
<div style={{ padding: '1.2rem', display: 'flex', flexDirection: 'column', gap: '1rem', flex: 1 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<div style={{ padding: '0.7rem', borderRadius: '12px', background: 'rgba(255,255,255,0.08)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
{prog.type === 'SMART' && <Cpu size={24} color="var(--accent-blue)" />}
{prog.type === 'ARIMR' && <Tractor size={24} color="var(--accent-green)" />}
{prog.type === 'ZUS_BHP' && <HardHat size={24} color="var(--accent-orange)" />}
</div>
<div style={{ width: '22px', height: '22px', borderRadius: '50%', border: '2px solid', borderColor: selectedProgram === prog.id ? 'var(--accent-green)' : 'rgba(255,255,255,0.2)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
{selectedProgram === prog.id && <div style={{ width: '12px', height: '12px', borderRadius: '50%', background: 'var(--accent-green)' }}></div>}
</div>
</div>
<div>
<div style={{ fontWeight: 800, fontSize: '1.1rem', color: 'var(--text-primary)', marginBottom: '0.4rem' }}>{prog.name}</div>
<div style={{ fontSize: '0.85rem', color: 'var(--text-secondary)' }}>
{prog.shortDesc}
</div>
</div>
<div style={{ marginTop: 'auto', display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', borderTop: '1px solid rgba(255,255,255,0.05)', paddingTop: '0.8rem' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.4rem' }}>
<div style={{ fontSize: '0.75rem', fontWeight: 'bold', display: 'inline-block', padding: '0.2rem 0.6rem', borderRadius: '4px', background: prog.match > 80 ? 'rgba(16,185,129,0.2)' : prog.match > 60 ? 'rgba(245,158,11,0.2)' : 'rgba(239,68,68,0.2)', color: prog.match > 80 ? 'var(--accent-green)' : prog.match > 60 ? 'var(--accent-orange)' : 'var(--accent-red)', width: 'fit-content' }}>
MATCH: {prog.match}%
</div>
<div style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>Maks: <span style={{ color: 'var(--text-primary)', fontWeight: 'bold' }}>{prog.amount}</span></div>
</div>
<button
onClick={(e) => { e.stopPropagation(); setExpandedProgram(expandedProgram === prog.id ? null : prog.id); }}
className="btn hover-lift"
style={{ background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', padding: '0.4rem', color: 'var(--text-secondary)', borderRadius: '8px' }}
title="Więcej informacji"
>
<Info size={16} />
</button>
</div>
</div>
</div>
))}
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '1rem' }}>
<button
className="btn btn-primary"
onClick={async () => {
onClose();
const dynName = desc ? (desc.length > 100 ? `${desc.slice(0, 100)}...` : desc) : `Nowy Projekt - ${new Date().toLocaleDateString()}`;
const recommendedProgram = recommendedPrograms.find(p => p.id === selectedProgram);
const programName = recommendedProgram?.name || 'Nieznany Program';
const programType = recommendedProgram?.type || 'SMART';
const grantAmount = recommendedProgram?.amount || 'Nie określono';
try {
const newProject = await createProject({
title: dynName,
description: desc,
program_type: programType,
program_name: programName,
external_context: {
company_data: companyDetails,
resources: [],
grant_amount: grantAmount
}
});
import('react-hot-toast').then(rt => rt.toast.success("Projekt został wygenerowany pomyślnie."));
navigate(`/projects/${newProject.id}`, { state: { projectName: dynName } });
} catch (err) {
console.error(err);
import('react-hot-toast').then(rt => rt.toast.error("Błąd podczas tworzenia projektu."));
}
}}
disabled={!selectedProgram}
style={{
padding: '1rem 2rem',
fontWeight: 'bold',
background: selectedProgram ? 'linear-gradient(90deg, var(--accent-green), var(--accent-blue))' : 'rgba(255,255,255,0.05)',
color: selectedProgram ? '#000' : 'rgba(255,255,255,0.4)',
boxShadow: selectedProgram ? '0 0 20px rgba(16,185,129,0.3)' : 'none'
}}
>
{selectedProgram ? 'Przejdź do Generatora Wniosku' : 'Wybierz program aby kontynuować'}
</button>
</div>
{/* MODAL DETALI (OVERLAY) */}
<AnimatePresence>
{activeExpandedProg && (
<motion.div
initial={{ opacity: 0, scale: 0.96 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.96 }}
style={{
position: 'absolute',
inset: 0,
background: 'rgba(11, 14, 20, 0.95)',
backdropFilter: 'blur(10px)',
borderRadius: '16px',
padding: '2.5rem',
display: 'flex',
flexDirection: 'column',
zIndex: 20
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '1.5rem' }}>
<h2 style={{ fontSize: '1.8rem', color: 'var(--text-primary)', margin: 0 }}>{activeExpandedProg.name}</h2>
</div>
<div style={{ display: 'flex', gap: '1rem', marginBottom: '2rem' }}>
<span style={{ background: 'rgba(16,185,129,0.1)', color: 'var(--accent-green)', padding: '0.4rem 1rem', borderRadius: '6px', fontSize: '0.9rem', fontWeight: 'bold' }}>
Szansa wg AI: {activeExpandedProg.chance}
</span>
<span style={{ background: 'rgba(255,255,255,0.05)', color: 'var(--text-secondary)', padding: '0.4rem 1rem', borderRadius: '6px', fontSize: '0.9rem' }}>
Kwota wsparcia: {activeExpandedProg.amount}
</span>
</div>
<div style={{ flex: 1, overflowY: 'auto', paddingRight: '1rem' }}>
<div style={{ fontSize: '1.15rem', color: 'var(--text-primary)', marginBottom: '1.5rem', lineHeight: '1.6' }}>
{activeExpandedProg.shortDesc}
</div>
{activeExpandedProg.explanation && (
<div style={{ marginBottom: '1.5rem', display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<div style={{ background: 'rgba(16, 185, 129, 0.05)', padding: '1.2rem', borderRadius: '12px', border: '1px solid rgba(16, 185, 129, 0.2)' }}>
<strong style={{ color: 'var(--accent-green)', display: 'block', marginBottom: '0.6rem', fontSize: '1.05rem' }}>Dlaczego ten program jest dla Ciebie najlepszy?</strong>
<span style={{ color: 'var(--text-primary)', fontSize: '0.95rem', lineHeight: '1.5' }}>{activeExpandedProg.explanation.reason}</span>
</div>
{activeExpandedProg.explanation.criteria && activeExpandedProg.explanation.criteria.length > 0 && (
<div style={{ background: 'rgba(255,255,255,0.03)', padding: '1.2rem', borderRadius: '12px', border: '1px solid rgba(255,255,255,0.05)' }}>
<strong style={{ color: 'var(--text-primary)', display: 'block', marginBottom: '0.6rem', fontSize: '1.05rem' }}>Główne kryteria, które spełniasz:</strong>
<ul style={{ margin: 0, paddingLeft: '1.2rem', color: 'var(--text-secondary)', fontSize: '0.95rem', lineHeight: '1.6' }}>
{activeExpandedProg.explanation.criteria.map((crit: string, idx: number) => (
<li key={idx} style={{ marginBottom: '0.3rem' }}>{crit}</li>
))}
</ul>
</div>
)}
{activeExpandedProg.explanation.risks && (
<div style={{ background: 'rgba(239, 68, 68, 0.05)', padding: '1.2rem', borderRadius: '12px', border: '1px solid rgba(239, 68, 68, 0.2)' }}>
<strong style={{ color: 'var(--accent-red)', display: 'block', marginBottom: '0.6rem', fontSize: '1.05rem' }}>Potencjalne wyzwania do zaadresowania:</strong>
<span style={{ color: 'var(--text-secondary)', fontSize: '0.95rem', lineHeight: '1.5' }}>{activeExpandedProg.explanation.risks}</span>
</div>
)}
</div>
)}
<div style={{ fontSize: '1rem', color: 'var(--text-secondary)', lineHeight: '1.7', background: 'rgba(56, 189, 248, 0.05)', padding: '1.5rem', borderRadius: '16px', border: '1px solid rgba(56, 189, 248, 0.1)' }}>
<strong style={{ color: 'var(--accent-blue)', display: 'block', marginBottom: '0.8rem', fontSize: '1.1rem' }}><Sparkles size={18} style={{ display: 'inline', position: 'relative', top: '3px', marginRight: '6px' }} /> Raport zgodności:</strong>
{activeExpandedProg.fullDesc}
</div>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '1rem', marginTop: '1.5rem', borderTop: '1px solid rgba(255,255,255,0.05)', paddingTop: '1.5rem' }}>
<button className="btn" onClick={() => setExpandedProgram(null)} style={{ padding: '0.8rem 1.5rem', background: 'transparent', color: 'var(--text-secondary)', border: '1px solid rgba(255,255,255,0.1)' }}>
Wróć do listy
</button>
<button
className="btn btn-primary"
onClick={() => { setSelectedProgram(activeExpandedProg.id); setExpandedProgram(null); }}
style={{ padding: '0.8rem 1.5rem', background: 'var(--accent-green)', color: '#000', fontWeight: 'bold' }}
>
Wybieram i kontynuuję
</button>
</div>
</motion.div>
)}
</AnimatePresence>
</motion.div>
)}
</motion.div>
);
}
}
};
return createPortal(
<AnimatePresence>
<motion.div
className="pricing-backdrop"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.85)', backdropFilter: 'blur(5px)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 9999 }}
>
<motion.div
className="glass-card"
style={{ width: '800px', maxWidth: '95%', background: '#0B0E14', padding: '0', overflow: 'hidden' }}
initial={{ scale: 0.95, y: 20 }}
animate={{ scale: 1, y: 0 }}
exit={{ scale: 0.95, y: 20 }}
>
{/* Header */}
<div style={{ padding: '2rem', borderBottom: '1px solid rgba(255,255,255,0.05)', display: 'flex', justifyContent: 'space-between', alignItems: 'center', background: 'rgba(255,255,255,0.02)' }}>
<div>
<h2 style={{ display: 'flex', alignItems: 'center', gap: '0.6rem', fontSize: '1.4rem' }}>
<Sparkles color="var(--accent-blue)" /> Nowy Projekt Dotacyjny
</h2>
<div style={{ display: 'flex', gap: '0.5rem', marginTop: '0.8rem' }}>
{[1, 2, 3].map(i => (
<div key={i} style={{ height: '4px', width: '40px', background: i <= step ? 'var(--accent-green)' : 'rgba(255,255,255,0.1)', borderRadius: '2px', transition: '0.3s all' }}></div>
))}
<span style={{ fontSize: '0.8rem', color: 'var(--text-muted)', marginLeft: '0.5rem' }}>Krok {step} / 3</span>
</div>
</div>
{step < 3 && <X size={24} style={{ cursor: 'pointer', color: 'var(--text-muted)', alignSelf: 'flex-start' }} onClick={onClose} />}
</div>
{/* Body */}
<div style={{ padding: '2.5rem 2rem', minHeight: '380px', display: 'flex' }}>
<AnimatePresence mode="wait">
{renderStepContent()}
</AnimatePresence>
</div>
</motion.div>
</motion.div>
</AnimatePresence>,
document.body
);
};
export default WizardModal;