File size: 7,224 Bytes
afd56bc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import React, { useState } from 'react';
import { Outlet } from 'react-router-dom';
import Sidebar from './Sidebar';
import PricingModal from './PricingModal';
import { motion, AnimatePresence } from 'framer-motion';
import { useQuery } from '@tanstack/react-query';
import { getSubscriptionStatus, getProject } from '../../api/client';
import { AlertTriangle, ArrowRight, ShieldAlert, PanelLeftClose, PanelLeftOpen } from 'lucide-react';
import { useLocation } from 'react-router-dom';
import { OnboardingWelcomeModal } from './OnboardingWelcomeModal';



const Layout: React.FC = () => {
  const [showPricing, setShowPricing] = useState(false);
  const [, setWelcomeComplete] = useState(false);
  const [isSidebarOpen, setIsSidebarOpen] = useState(true);

  const location = useLocation();
  const projectId = location.pathname.split('/projects/')[1]?.split('/')[0];

  const { data: sub } = useQuery({
    queryKey: ['subscription'],
    queryFn: getSubscriptionStatus,
    refetchInterval: 60000,
    retry: false
  });

  useQuery({
    queryKey: ['project', projectId],
    queryFn: () => getProject(projectId as string),
    enabled: !!projectId,
    refetchInterval: 10000
  });

  // Uzywamy bezpiecznego dostepu do limitow, lub fallback
  const activeSub = sub && typeof sub === 'object' && sub.limits ? sub : {
      tier: 'free',
      wizard_iterations_today: 18,
      tokens_used_month: 24500,
      limits: { max_wizard_iterations: 25, max_tokens_monthly: 50000 }
  };

  const iterPercent = (activeSub.wizard_iterations_today / activeSub.limits.max_wizard_iterations) * 100;
  const showLimitBanner = activeSub.tier !== 'business' && iterPercent > 80;
  const isCriticalLimit = iterPercent > 90;

  return (
    <div className="dashboard-layout">
      {/* Kolumna 1: Lewy Panel Nawigacyjny */}
      <aside 
          className="dashboard-sidebar-left"
          style={{
              width: isSidebarOpen ? '320px' : '0px',
              padding: 0,
              overflow: 'hidden',
              transition: 'width 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
          }}
      >
          <div style={{ width: '320px', height: '100%', overflowY: 'auto' }}>
             <Sidebar />
          </div>
      </aside>

      {/* Kolumna 2: G艂贸wna Tre艣膰 + Outlet */}
      <main className="dashboard-main" style={{ display: 'flex', flexDirection: 'column', position: 'relative' }}>
        <button
            onClick={() => setIsSidebarOpen(!isSidebarOpen)}
            className="btn btn-secondary hover-bg"
            style={{
                position: 'fixed',
                bottom: '1.5rem',
                left: isSidebarOpen ? '335px' : '1rem',
                zIndex: 9999,
                padding: '0.6rem',
                borderRadius: '50%',
                background: 'rgba(255,255,255,0.1)',
                backdropFilter: 'blur(10px)',
                transition: 'left 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
                border: '1px solid rgba(255,255,255,0.1)',
                boxShadow: '0 4px 15px rgba(0,0,0,0.5)'
            }}
            title={isSidebarOpen ? "Ukryj g艂贸wne menu" : "Poka偶 g艂贸wne menu"}
        >
            {isSidebarOpen ? <PanelLeftClose size={20} /> : <PanelLeftOpen size={20} />}
        </button>

        <AnimatePresence>
            {showLimitBanner && (
               <motion.div 
                  initial={{ height: 0, opacity: 0 }}
                  animate={
                      isCriticalLimit 
                        ? { 
                             height: 'auto', 
                             opacity: 1, 
                             boxShadow: ['0 4px 30px rgba(239,68,68,0.2)', '0 4px 50px rgba(239,68,68,0.5)', '0 4px 30px rgba(239,68,68,0.2)'] 
                          } 
                        : { height: 'auto', opacity: 1 }
                  }
                  transition={isCriticalLimit ? { boxShadow: { repeat: Infinity, duration: 2, ease: 'easeInOut' } } : {}}
                  exit={{ height: 0, opacity: 0 }}
                  style={{ 
                      background: isCriticalLimit ? 'linear-gradient(90deg, rgba(239, 68, 68, 0.25), rgba(220, 38, 38, 0.15))' : 'linear-gradient(90deg, rgba(245, 158, 11, 0.1), rgba(239, 68, 68, 0.1))', 
                      borderBottom: isCriticalLimit ? '2px solid rgba(239, 68, 68, 0.6)' : '1px solid rgba(245, 158, 11, 0.3)',
                      overflow: 'hidden'
                  }}
               >
                  <div style={{ padding: '0.8rem 2rem', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                     <div style={{ display: 'flex', alignItems: 'center', gap: '0.8rem', color: isCriticalLimit ? '#EF4444' : '#F59E0B', fontWeight: 600 }}>
                         {isCriticalLimit ? <ShieldAlert size={20} /> : <AlertTriangle size={18} />}
                         <span>
                             {isCriticalLimit 
                                ? `KRYTYCZNY LIMIT: Pozosta艂o tylko ${Math.max(0, activeSub.limits.max_wizard_iterations - activeSub.wizard_iterations_today)} iteracji! Wykup wy偶szy plan by nie straci膰 dost臋pu do AI.` 
                                : `Uwaga: Wykorzysta艂e艣 ${iterPercent.toFixed(0)}% dziennych iteracji Kreatora. Tw贸j plan zaraz zablokuje dzia艂anie AI.`
                             }
                        </span>
                     </div>
                     <motion.button 
                         animate={isCriticalLimit ? { scale: [1, 1.05, 1], boxShadow: ['0 0 0px transparent', '0 0 15px rgba(239,68,68,0.5)', '0 0 0px transparent'] } : {}}
                         transition={isCriticalLimit ? { repeat: Infinity, duration: 1.5, ease: 'easeInOut' } : {}}
                         whileHover={{ scale: 1.05 }}
                         className="btn" 
                         style={{ 
                             background: isCriticalLimit ? 'rgba(239, 68, 68, 0.25)' : 'rgba(245, 158, 11, 0.2)', 
                             color: isCriticalLimit ? '#EF4444' : '#F59E0B', 
                             fontSize: '0.85rem', padding: '0.4rem 1rem', display: 'flex', alignItems: 'center', gap: '0.5rem', fontWeight: 800, 
                             border: isCriticalLimit ? '1px solid rgba(239, 68, 68, 0.6)' : '1px solid rgba(245, 158, 11, 0.4)' 
                         }}
                         onClick={() => setShowPricing(true)}
                     >
                         {isCriticalLimit ? 'Upgrade Natychmiastowy' : 'Odblokuj Limity'} <ArrowRight size={14} />
                     </motion.button>
                  </div>
               </motion.div>
            )}
        </AnimatePresence>

        <motion.div 
            initial={{ opacity: 0, y: 15 }} 
            animate={{ opacity: 1, y: 0 }} 
            transition={{ duration: 0.5 }}
            style={{ display: 'flex', flexDirection: 'column', flex: 1, overflowY: 'auto', overflowX: 'hidden' }}
        >
            <Outlet />
        </motion.div>
      </main>



      {showPricing && <PricingModal onClose={() => setShowPricing(false)} />}
      <OnboardingWelcomeModal onComplete={() => setWelcomeComplete(true)} />
    </div>
  );
};

export default Layout;