Spaces:
Running
Running
| import React, { useState } from 'react'; | |
| import { User, Building2, CreditCard, Shield, Sliders, Lock, Settings as SettingsIcon, LogOut, Download, AlertTriangle } from 'lucide-react'; | |
| import { useUser, UserProfile, SignOutButton, useClerk } from '@clerk/clerk-react'; | |
| import { deleteUserAccount, getAccountExport, getSubscriptionStatus, updateAccountSettings, lookupCompany } from '../api/client'; | |
| import toast from 'react-hot-toast'; | |
| import { useQuery, useMutation } from '@tanstack/react-query'; | |
| import PricingModal from '../components/dashboard/PricingModal'; | |
| const Settings: React.FC = () => { | |
| const [activeTab, setActiveTab] = useState('profile'); | |
| const { signOut } = useClerk(); | |
| const [showDeleteModal, setShowDeleteModal] = useState(false); | |
| const [deleteInput, setDeleteInput] = useState(''); | |
| const [isDeleting, setIsDeleting] = useState(false); | |
| const [deleteSuccess, setDeleteSuccess] = useState(false); | |
| const [isExporting, setIsExporting] = useState(false); | |
| // States for Pricing Modal and Companies | |
| const [showPricing, setShowPricing] = useState(false); | |
| const [companies, setCompanies] = useState(() => { | |
| const saved = localStorage.getItem('grantforge_companies'); | |
| if (saved) { | |
| try { | |
| return JSON.parse(saved); | |
| } catch (e) { | |
| console.error("Błąd parsowania firm z LocalStorage", e); | |
| } | |
| } | |
| return [ | |
| { nip: '1234567890', name: 'GrantForge sp. z o.o.', city: 'Warszawa', status: 'Zatwierdzone' } | |
| ]; | |
| }); | |
| const [showCompanyModal, setShowCompanyModal] = useState(false); | |
| const [newNip, setNewNip] = useState(''); | |
| const [isLookingUp, setIsLookingUp] = useState(false); | |
| const [fetchedCompany, setFetchedCompany] = useState<any>(null); | |
| const { data: subData, isLoading: subLoading, refetch: refetchSub } = useQuery({ | |
| queryKey: ['subscription-status'], | |
| queryFn: getSubscriptionStatus | |
| }); | |
| const updateSettingsMutation = useMutation({ | |
| mutationFn: updateAccountSettings, | |
| onSuccess: () => { | |
| toast.success("Ustawienia zostały zapisane."); | |
| refetchSub(); | |
| }, | |
| onError: () => { | |
| toast.error("Wystąpił błąd podczas zapisywania ustawień."); | |
| } | |
| }); | |
| const handleAddCompany = async () => { | |
| if (!newNip || newNip.length < 10) { | |
| toast.error("Wprowadź prawidłowy NIP (min. 10 cyfr)."); | |
| return; | |
| } | |
| setIsLookingUp(true); | |
| try { | |
| const data = await lookupCompany(newNip); | |
| setFetchedCompany({ | |
| nip: data.nip, | |
| name: data.name, | |
| city: data.voivodeship, | |
| address: `PKD: ${data.pkd ? data.pkd.join(', ') : 'Brak'}`, | |
| legalForm: `Przychody: ${data.revenue || 'Brak danych'} PLN` | |
| }); | |
| toast.success("Pobrano dane z bazy GUS."); | |
| } catch (err: any) { | |
| toast.error(err.response?.data?.detail || "Nie udało się pobrać danych z GUS."); | |
| } finally { | |
| setIsLookingUp(false); | |
| } | |
| }; | |
| const handleSaveFetchedCompany = () => { | |
| if (!fetchedCompany) return; | |
| const newCompany = { | |
| nip: fetchedCompany.nip, | |
| name: fetchedCompany.name, | |
| city: fetchedCompany.city, | |
| status: 'Zatwierdzone' | |
| }; | |
| const updatedCompanies = [...companies, newCompany]; | |
| setCompanies(updatedCompanies); | |
| localStorage.setItem('grantforge_companies', JSON.stringify(updatedCompanies)); | |
| setShowCompanyModal(false); | |
| setNewNip(''); | |
| setFetchedCompany(null); | |
| toast.success("Podmiot dodany pomyślnie."); | |
| }; | |
| const handleRemoveCompany = (nipToRemove: string) => { | |
| const updatedCompanies = companies.filter((c: any) => c.nip !== nipToRemove); | |
| setCompanies(updatedCompanies); | |
| localStorage.setItem('grantforge_companies', JSON.stringify(updatedCompanies)); | |
| toast.success("Podmiot został usunięty."); | |
| }; | |
| const handleExportData = async () => { | |
| setIsExporting(true); | |
| try { | |
| const data = await getAccountExport(); | |
| const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); | |
| const url = window.URL.createObjectURL(blob); | |
| const link = document.createElement('a'); | |
| link.href = url; | |
| link.setAttribute('download', `dotacje_ai_export_${new Date().toISOString().split('T')[0]}.json`); | |
| document.body.appendChild(link); | |
| link.click(); | |
| link.remove(); | |
| toast.success("Dane zostały wyeksportowane."); | |
| } catch (error) { | |
| toast.error("Wystąpił błąd podczas eksportu."); | |
| } finally { | |
| setIsExporting(false); | |
| } | |
| }; | |
| const handleDeleteAccount = async () => { | |
| if (deleteInput !== 'USUŃ') return; | |
| setIsDeleting(true); | |
| try { | |
| await deleteUserAccount(); | |
| setDeleteSuccess(true); | |
| setShowDeleteModal(false); | |
| toast.success( | |
| "Twoje dane w Dotacje AI zostały trwale usunięte. Za chwilę zostaniesz wylogowany.", | |
| { duration: 8000 } | |
| ); | |
| // Sign out after a short delay so they can read the toast or internal message | |
| setTimeout(() => { | |
| signOut(); | |
| }, 6000); | |
| } catch (error) { | |
| toast.error("Wystąpił błąd podczas usuwania danych."); | |
| } finally { | |
| setIsDeleting(false); | |
| } | |
| }; | |
| const tabs = [ | |
| { id: 'profile', label: 'Profil i Konto', icon: <User size={18} /> }, | |
| { id: 'companies', label: 'Moje Firmy', icon: <Building2 size={18} /> }, | |
| { id: 'billing', label: 'Subskrypcja', icon: <CreditCard size={18} /> }, | |
| { id: 'security', label: 'Bezpieczeństwo', icon: <Shield size={18} /> }, | |
| { id: 'preferences', label: 'Preferencje', icon: <Sliders size={18} /> }, | |
| { id: 'privacy', label: 'Dane i Prywatność', icon: <Lock size={18} /> }, | |
| ]; | |
| const renderContent = () => { | |
| switch (activeTab) { | |
| case 'profile': | |
| return ( | |
| <div style={{ animation: 'fadeIn 0.3s ease' }}> | |
| <h2 style={{ fontSize: '1.4rem', borderBottom: '1px solid rgba(255,255,255,0.1)', paddingBottom: '1rem', marginBottom: '1.5rem', color: '#fff' }}>Twój Profil</h2> | |
| <div style={{ background: 'var(--bg-elevated)', border: '1px solid rgba(255,255,255,0.05)', borderRadius: '12px', padding: '1rem', overflow: 'hidden' }}> | |
| {/* We use Clerk's pre-built profile component embedded here */} | |
| <UserProfile | |
| appearance={{ | |
| elements: { | |
| rootBox: { width: '100%', maxWidth: '100%' }, | |
| card: { boxShadow: 'none', background: 'transparent', padding: 0 } | |
| } | |
| }} | |
| /> | |
| </div> | |
| </div> | |
| ); | |
| case 'companies': | |
| return ( | |
| <div style={{ animation: 'fadeIn 0.3s ease' }}> | |
| <h2 style={{ fontSize: '1.4rem', borderBottom: '1px solid rgba(255,255,255,0.1)', paddingBottom: '1rem', marginBottom: '1.5rem', color: '#fff' }}>Zarządzanie podmiotami (Moje Firmy)</h2> | |
| {companies.map((c, i) => ( | |
| <div key={i} className="glass-card" style={{ marginBottom: '1rem' }}> | |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> | |
| <div> | |
| <h4 style={{ margin: '0 0 0.3rem 0', fontSize: '1.1rem' }}>{c.name}</h4> | |
| <p style={{ margin: 0, color: 'var(--text-muted)', fontSize: '0.9rem' }}>NIP: {c.nip} • {c.city} • {c.status}</p> | |
| </div> | |
| <button className="btn btn-secondary" style={{ color: '#ef4444', borderColor: 'rgba(239, 68, 68, 0.3)' }} onClick={() => handleRemoveCompany(c.nip)}>Usuń</button> | |
| </div> | |
| </div> | |
| ))} | |
| <button | |
| className="btn btn-primary" | |
| style={{ marginTop: '1rem', display: 'flex', alignItems: 'center', gap: '0.5rem' }} | |
| onClick={() => setShowCompanyModal(true)} | |
| > | |
| + Dodaj nową firmę z GUS | |
| </button> | |
| </div> | |
| ); | |
| case 'billing': | |
| return ( | |
| <div style={{ animation: 'fadeIn 0.3s ease' }}> | |
| <h2 style={{ fontSize: '1.4rem', borderBottom: '1px solid rgba(255,255,255,0.1)', paddingBottom: '1rem', marginBottom: '1.5rem', color: '#fff' }}>Subskrypcja i Rozliczenia</h2> | |
| <div style={{ background: 'linear-gradient(135deg, rgba(16,185,129,0.1), rgba(59,130,246,0.1))', border: '1px solid rgba(16,185,129,0.3)', padding: '2rem', borderRadius: '12px', marginBottom: '2rem' }}> | |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> | |
| <div> | |
| <div style={{ color: 'var(--accent-green)', fontWeight: 800, textTransform: 'uppercase', marginBottom: '0.5rem', letterSpacing: '2px' }}>Aktywny Plan</div> | |
| <h3 style={{ fontSize: '1.8rem', margin: 0, color: '#fff' }}> | |
| {subLoading ? '...' : (subData?.tier || 'FREE').toUpperCase()} | |
| </h3> | |
| <p style={{ color: 'var(--text-muted)', margin: '0.5rem 0 0 0' }}>Dostęp do wszystkich sekcji i nielimitowane analizy RAG.</p> | |
| </div> | |
| <button className="btn btn-primary" style={{ background: 'var(--accent-blue)', color: '#fff' }} onClick={() => setShowPricing(true)}>Zmień Plan</button> | |
| </div> | |
| </div> | |
| <div className="glass-card"> | |
| <h4 style={{ margin: '0 0 1rem 0' }}>Historia Płatności</h4> | |
| <p style={{ color: 'var(--text-muted)', fontSize: '0.9rem' }}>Nie posiadasz jeszcze historii faktur dla tego konta.</p> | |
| </div> | |
| </div> | |
| ); | |
| case 'security': | |
| return ( | |
| <div style={{ animation: 'fadeIn 0.3s ease' }}> | |
| <h2 style={{ fontSize: '1.4rem', borderBottom: '1px solid rgba(255,255,255,0.1)', paddingBottom: '1rem', marginBottom: '1.5rem', color: '#fff' }}>Bezpieczeństwo</h2> | |
| <div className="glass-card" style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}> | |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', paddingBottom: '1rem', borderBottom: '1px solid rgba(255,255,255,0.05)' }}> | |
| <div> | |
| <h4 style={{ margin: '0 0 0.3rem 0' }}>Weryfikacja dwuetapowa (2FA)</h4> | |
| <p style={{ margin: 0, color: 'var(--text-muted)', fontSize: '0.9rem' }}>Zarządzaj zabezpieczeniami w panelu Profilu (Clerk).</p> | |
| </div> | |
| <button className="btn btn-secondary" onClick={() => setActiveTab('profile')}>Skonfiguruj w Profilu</button> | |
| </div> | |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', paddingBottom: '1rem', borderBottom: '1px solid rgba(255,255,255,0.05)' }}> | |
| <div> | |
| <h4 style={{ margin: '0 0 0.3rem 0' }}>Aktywne Sesje</h4> | |
| <p style={{ margin: 0, color: 'var(--text-muted)', fontSize: '0.9rem' }}>Windows 11 / Chrome • Aktualna sesja</p> | |
| </div> | |
| <button className="btn hover-bg" style={{ color: 'var(--text-primary)', border: '1px solid var(--border-strong)', background: 'var(--bg-surface)' }}>Wyloguj inne urządzenia</button> | |
| </div> | |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> | |
| <div> | |
| <h4 style={{ margin: '0 0 0.3rem 0' }}>Konto i dostęp</h4> | |
| </div> | |
| <SignOutButton> | |
| <button className="btn hover-bg" style={{ background: '#ef4444', border: 'none', color: '#fff', display: 'flex', alignItems: 'center', gap: '0.5rem', fontWeight: 600, padding: '0.5rem 1rem', borderRadius: '6px' }}> | |
| <LogOut size={16} /> Wyloguj się | |
| </button> | |
| </SignOutButton> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| case 'preferences': | |
| const aiDisclaimer = subData?.settings?.ai_disclaimer_enabled ?? true; | |
| const gdprConsent = subData?.settings?.gdpr_consent_accepted ?? false; | |
| return ( | |
| <div style={{ animation: 'fadeIn 0.3s ease' }}> | |
| <h2 style={{ fontSize: '1.4rem', borderBottom: '1px solid rgba(255,255,255,0.1)', paddingBottom: '1rem', marginBottom: '1.5rem', color: '#fff' }}>Preferencje Aplikacji</h2> | |
| <div className="glass-card" style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}> | |
| <div> | |
| <label style={{ display: 'block', fontSize: '0.9rem', color: 'var(--text-secondary)', marginBottom: '0.5rem' }}>Język interfejsu</label> | |
| <select className="form-input" style={{ width: '100%', maxWidth: '300px' }}> | |
| <option>Polski</option> | |
| <option>English</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label style={{ display: 'block', fontSize: '0.9rem', color: 'var(--text-secondary)', marginBottom: '0.5rem' }}>Powiadomienia E-mail</label> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: '0.8rem', marginBottom: '0.5rem' }}> | |
| <input type="checkbox" id="n1" defaultChecked /> <label htmlFor="n1" style={{ color: '#fff' }}>Powiadomienia o nowych naborach ze statusem "Wkrótce"</label> | |
| </div> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: '0.8rem' }}> | |
| <input type="checkbox" id="n2" defaultChecked /> <label htmlFor="n2" style={{ color: '#fff' }}>Cotygodniowe podsumowanie wniosków</label> | |
| </div> | |
| </div> | |
| <div> | |
| <label style={{ display: 'block', fontSize: '0.9rem', color: 'var(--text-secondary)', marginBottom: '0.5rem' }}>Prywatność i ostrzeżenia</label> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: '0.8rem', marginBottom: '0.5rem' }}> | |
| <input | |
| type="checkbox" | |
| id="p1" | |
| checked={gdprConsent} | |
| onChange={(e) => updateSettingsMutation.mutate({ gdpr_consent_accepted: e.target.checked })} | |
| disabled={updateSettingsMutation.isPending} | |
| /> | |
| <label htmlFor="p1" style={{ color: '#fff' }}>Zgoda na przetwarzanie danych profilowych (RODO)</label> | |
| </div> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: '0.8rem' }}> | |
| <input | |
| type="checkbox" | |
| id="p2" | |
| checked={aiDisclaimer} | |
| onChange={(e) => updateSettingsMutation.mutate({ ai_disclaimer_enabled: e.target.checked })} | |
| disabled={updateSettingsMutation.isPending} | |
| /> | |
| <label htmlFor="p2" style={{ color: '#fff' }}>Wyświetlaj ostrzeżenia AI (Disclaimer)</label> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| case 'privacy': | |
| return ( | |
| <div style={{ animation: 'fadeIn 0.3s ease' }}> | |
| <h2 style={{ fontSize: '1.4rem', borderBottom: '1px solid rgba(255,255,255,0.1)', paddingBottom: '1rem', marginBottom: '1.5rem', color: '#fff' }}>Dane i Prywatność</h2> | |
| <div className="glass-card" style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}> | |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}> | |
| <div> | |
| <h4 style={{ margin: '0 0 0.3rem 0', display: 'flex', alignItems: 'center', gap: '0.5rem' }}><Download size={18} color="var(--accent-blue)" /> Pobierz moje dane</h4> | |
| <p style={{ margin: 0, color: 'var(--text-muted)', fontSize: '0.9rem', maxWidth: '400px' }}> | |
| Weksportuj paczkę ze wszystkimi Twoimi wnioskami i projektami w czytelnym formacie maszynowym (JSON). | |
| </p> | |
| </div> | |
| <button className="btn btn-secondary" onClick={handleExportData} disabled={isExporting}> | |
| {isExporting ? 'Przygotowywanie...' : 'Zażądaj archiwum JSON'} | |
| </button> | |
| </div> | |
| <hr style={{ border: 'none', borderTop: '1px solid rgba(255,255,255,0.05)', margin: '0' }} /> | |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}> | |
| <div> | |
| <h4 style={{ margin: '0 0 0.3rem 0', color: 'var(--accent-red)', display: 'flex', alignItems: 'center', gap: '0.5rem' }}><AlertTriangle size={18} /> Usuń konto</h4> | |
| <p style={{ margin: 0, color: 'var(--text-muted)', fontSize: '0.9rem', maxWidth: '400px' }}> | |
| Usunięcie konta jest nieodwracalne i obejmuje wszystkie Twoje projekty oraz dane w systemie. | |
| </p> | |
| </div> | |
| <button className="btn" onClick={() => setShowDeleteModal(true)} style={{ background: 'rgba(239, 68, 68, 0.1)', border: '1px solid var(--accent-red)', color: 'var(--accent-red)' }}>Usuń moje konto i wszystkie dane</button> | |
| </div> | |
| </div> | |
| {deleteSuccess && ( | |
| <div style={{ marginTop: '2rem', padding: '1.5rem', background: 'rgba(34, 197, 94, 0.1)', border: '1px solid #22c55e', borderRadius: '12px' }}> | |
| <h3 style={{ color: '#22c55e', marginTop: 0 }}>Twoje dane w Dotacje AI zostały trwale usunięte.</h3> | |
| <p style={{ color: 'var(--text-primary)', marginBottom: 0 }}> | |
| Jeśli chcesz całkowicie usunąć konto logowania, przejdź do swojego profilu w Clerk → Manage Account → Delete Account. | |
| </p> | |
| </div> | |
| )} | |
| {showDeleteModal && ( | |
| <div style={{ | |
| position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, | |
| background: 'rgba(0,0,0,0.8)', zIndex: 9999, | |
| display: 'flex', alignItems: 'center', justifyContent: 'center' | |
| }}> | |
| <div style={{ | |
| background: 'var(--bg-elevated)', padding: '2rem', | |
| borderRadius: '12px', width: '100%', maxWidth: '400px', | |
| border: '1px solid rgba(255,255,255,0.1)' | |
| }}> | |
| <h3 style={{ marginTop: 0, color: 'var(--accent-red)', display: 'flex', alignItems: 'center', gap: '0.5rem' }}> | |
| <AlertTriangle /> Potwierdź usunięcie | |
| </h3> | |
| <p style={{ color: 'var(--text-secondary)', fontSize: '0.9rem', lineHeight: 1.5 }}> | |
| Ta operacja jest niemożliwa do cofnięcia. Kasujemy wszystkie projekty, logi i dane o zużyciu. | |
| <br/><br/> | |
| Aby potwierdzić, wpisz słowo <strong>USUŃ</strong> poniżej: | |
| </p> | |
| <input | |
| type="text" | |
| className="form-input" | |
| value={deleteInput} | |
| onChange={(e) => setDeleteInput(e.target.value)} | |
| placeholder="Wpisz USUŃ" | |
| style={{ width: '100%', marginBottom: '1.5rem', marginTop: '0.5rem', boxSizing: 'border-box' }} | |
| /> | |
| <div style={{ display: 'flex', gap: '1rem', justifyContent: 'flex-end' }}> | |
| <button className="btn btn-secondary" onClick={() => { setShowDeleteModal(false); setDeleteInput(''); }} disabled={isDeleting}>Anuluj</button> | |
| <button | |
| className="btn" | |
| disabled={deleteInput !== 'USUŃ' || isDeleting} | |
| onClick={handleDeleteAccount} | |
| style={{ background: 'var(--accent-red)', color: 'white', opacity: deleteInput !== 'USUŃ' ? 0.5 : 1 }} | |
| > | |
| {isDeleting ? 'Usuwanie...' : 'Skasuj bezpowrotnie'} | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| default: | |
| return null; | |
| } | |
| }; | |
| return ( | |
| <div style={{ padding: '2rem', maxWidth: '1200px', width: '100%', margin: '0 auto', display: 'flex', gap: '3rem', minHeight: 'calc(100vh - 4rem)', alignItems: 'flex-start' }}> | |
| {/* Lewy nawigator */} | |
| <div style={{ width: '280px', flexShrink: 0 }}> | |
| <h1 style={{ fontSize: '1.8rem', fontWeight: 800, margin: '0 0 2rem 0', display: 'flex', alignItems: 'center', gap: '0.8rem' }}> | |
| <SettingsIcon color="var(--accent-blue)" /> Ustawienia | |
| </h1> | |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}> | |
| {tabs.map((tab) => ( | |
| <button | |
| key={tab.id} | |
| onClick={() => setActiveTab(tab.id)} | |
| style={{ | |
| display: 'flex', alignItems: 'center', gap: '0.8rem', | |
| padding: '1rem', borderRadius: '8px', border: 'none', | |
| background: activeTab === tab.id ? 'var(--bg-elevated)' : 'transparent', | |
| color: activeTab === tab.id ? 'var(--text-primary)' : 'var(--text-secondary)', | |
| fontWeight: activeTab === tab.id ? 600 : 500, | |
| cursor: 'pointer', | |
| transition: 'all 0.2s', | |
| textAlign: 'left' | |
| }} | |
| className="hover-bg" | |
| > | |
| <span style={{ color: activeTab === tab.id ? 'var(--accent-blue)' : 'inherit' }}>{tab.icon}</span> | |
| {tab.label} | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| {/* Prawy panel zwartości */} | |
| <div style={{ flex: 1, paddingRight: '2rem' }}> | |
| {renderContent()} | |
| </div> | |
| {showPricing && <PricingModal onClose={() => setShowPricing(false)} />} | |
| {showCompanyModal && ( | |
| <div style={{ | |
| position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, | |
| background: 'rgba(0,0,0,0.8)', zIndex: 9999, | |
| display: 'flex', alignItems: 'center', justifyContent: 'center' | |
| }}> | |
| <div style={{ | |
| background: 'var(--bg-elevated)', padding: '2rem', | |
| borderRadius: '12px', width: '100%', maxWidth: '500px', | |
| border: '1px solid rgba(255,255,255,0.1)' | |
| }}> | |
| <h3 style={{ marginTop: 0, color: 'var(--text-primary)' }}>Dodaj firmę z GUS</h3> | |
| {!fetchedCompany ? ( | |
| <> | |
| <p style={{ color: 'var(--text-secondary)', fontSize: '0.9rem', marginBottom: '1.5rem' }}> | |
| Podaj NIP podmiotu. System pobierze dane z Głównego Urzędu Statystycznego i zweryfikuje powiązania KRS. | |
| </p> | |
| <input | |
| type="text" | |
| className="form-input" | |
| value={newNip} | |
| onChange={(e) => setNewNip(e.target.value)} | |
| placeholder="Wpisz 10-cyfrowy NIP" | |
| style={{ width: '100%', marginBottom: '1.5rem', boxSizing: 'border-box' }} | |
| /> | |
| <div style={{ display: 'flex', gap: '1rem', justifyContent: 'flex-end' }}> | |
| <button className="btn btn-secondary" onClick={() => { setShowCompanyModal(false); setNewNip(''); }} disabled={isLookingUp}>Anuluj</button> | |
| <button | |
| className="btn btn-primary" | |
| disabled={!newNip || newNip.length < 10 || isLookingUp} | |
| onClick={handleAddCompany} | |
| > | |
| {isLookingUp ? 'Pobieranie danych...' : 'Pobierz dane'} | |
| </button> | |
| </div> | |
| </> | |
| ) : ( | |
| <> | |
| <div style={{ background: 'rgba(255,255,255,0.03)', padding: '1.5rem', borderRadius: '8px', marginBottom: '1.5rem' }}> | |
| <h4 style={{ margin: '0 0 1rem 0', color: 'var(--accent-green)' }}>Znaleziono podmiot</h4> | |
| <div style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: '0.8rem', fontSize: '0.9rem' }}> | |
| <div style={{ color: 'var(--text-muted)' }}>Nazwa:</div> | |
| <div style={{ fontWeight: 600 }}>{fetchedCompany.name}</div> | |
| <div style={{ color: 'var(--text-muted)' }}>NIP:</div> | |
| <div>{fetchedCompany.nip}</div> | |
| <div style={{ color: 'var(--text-muted)' }}>Adres:</div> | |
| <div>{fetchedCompany.address}</div> | |
| <div style={{ color: 'var(--text-muted)' }}>Miejscowość:</div> | |
| <div>{fetchedCompany.city}</div> | |
| <div style={{ color: 'var(--text-muted)' }}>Forma prawna:</div> | |
| <div>{fetchedCompany.legalForm}</div> | |
| </div> | |
| <div style={{ marginTop: '1rem', padding: '0.8rem', background: 'rgba(34, 197, 94, 0.1)', border: '1px solid rgba(34, 197, 94, 0.3)', borderRadius: '6px', fontSize: '0.85rem', color: 'var(--accent-green)' }}> | |
| ✓ Zweryfikowano w bazie REGON | |
| <br/>✓ Brak powiązań kapitałowych wykluczających MŚP | |
| </div> | |
| </div> | |
| <div style={{ display: 'flex', gap: '1rem', justifyContent: 'flex-end' }}> | |
| <button className="btn btn-secondary" onClick={() => setFetchedCompany(null)}>Wróć</button> | |
| <button className="btn btn-primary" onClick={handleSaveFetchedCompany}>Dodaj do konta</button> | |
| </div> | |
| </> | |
| )} | |
| </div> | |
| </div> | |
| )} | |
| <style>{` | |
| @keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } } | |
| .hover-bg:hover { background: rgba(255,255,255,0.03) !important; color: #fff !important; } | |
| .form-input { background: var(--bg-deep); border: 1px solid rgba(255,255,255,0.1); color: #fff; padding: 0.8rem 1rem; border-radius: 8px; outline: none; transition: 0.2s; } | |
| .form-input:focus { border-color: var(--accent-blue); box-shadow: 0 0 0 2px rgba(59,130,246,0.2); } | |
| `}</style> | |
| </div> | |
| ); | |
| }; | |
| export default Settings; | |