File size: 7,958 Bytes
3c4a809
 
3a19693
3c4a809
3a19693
76089f2
 
 
 
3a19693
3c4a809
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3a19693
 
 
 
 
 
 
 
3c4a809
3a19693
76089f2
3c4a809
 
76089f2
3c4a809
76089f2
3c4a809
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3a19693
 
 
 
 
 
3c4a809
3a19693
76089f2
 
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
import { Routes, Route, Navigate, NavLink, useNavigate } from 'react-router-dom'
import { useState } from 'react'
import { AuthProvider, useAuth } from './contexts/AuthContext'
import { ProjectProvider, useProject } from './contexts/ProjectContext'
import Login from './components/Login'
import Dashboard from './components/Dashboard'
import LeadsTable from './components/LeadsTable'
import ImportWizard from './components/ImportWizard'
import ScraperConfig from './components/ScraperConfig'
import Admin from './components/Admin'
import Projects from './components/Projects'

const PROJECT_COLORS = ['#0073ea', '#00c875', '#e2445c', '#fdab3d', '#a25ddc', '#037f4c', '#ff7575', '#579bfc']

function Sidebar() {
  const { user, isAdmin, logout } = useAuth()
  const { projects, activeProject, selectProject, addProject } = useProject()
  const navigate = useNavigate()
  const [showNewProject, setShowNewProject] = useState(false)
  const [newName, setNewName] = useState('')
  const [newColor, setNewColor] = useState(PROJECT_COLORS[0])
  const [creating, setCreating] = useState(false)

  const handleLogout = () => { logout(); navigate('/login') }

  const handleCreateProject = async (e) => {
    e.preventDefault()
    if (!newName.trim()) return
    setCreating(true)
    try {
      const base = import.meta.env.DEV ? '/api' : ''
      const token = localStorage.getItem('leadgen_token')
      const res = await fetch(`${base}/projects`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
        body: JSON.stringify({ name: newName.trim(), color: newColor }),
      })
      if (res.ok) {
        const project = await res.json()
        addProject(project)
        selectProject(project)
        setNewName('')
        setShowNewProject(false)
        navigate('/leads')
      }
    } catch {}
    setCreating(false)
  }

  return (
    <aside className="sidebar">
      <div className="sidebar-logo">&#128640; LeadGen</div>

      <div className="sidebar-section-label">Workspace</div>
      <NavLink to="/" end className={({ isActive }) => 'sidebar-link' + (isActive ? ' active' : '')}>
        <span className="icon">&#9632;</span> Dashboard
      </NavLink>
      <NavLink to="/projects" className={({ isActive }) => 'sidebar-link' + (isActive ? ' active' : '')}>
        <span className="icon">&#9670;</span> Projects
      </NavLink>

      <div className="sidebar-section-label" style={{ marginTop: 8 }}>Projects</div>

      <button
        className={'sidebar-project-item' + (!activeProject ? ' active' : '')}
        onClick={() => { selectProject(null); navigate('/leads') }}
      >
        <span className="sidebar-project-dot" style={{ background: '#676879' }} />
        All leads
      </button>

      {projects.map(p => (
        <button
          key={p.id}
          className={'sidebar-project-item' + (activeProject?.id === p.id ? ' active' : '')}
          onClick={() => { selectProject(p); navigate('/leads') }}
        >
          <span className="sidebar-project-dot" style={{ background: p.color }} />
          <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.name}</span>
        </button>
      ))}

      {showNewProject ? (
        <form onSubmit={handleCreateProject} style={{ padding: '8px 12px', margin: '2px 8px' }}>
          <input
            autoFocus
            value={newName}
            onChange={e => setNewName(e.target.value)}
            placeholder="Project name"
            style={{ marginBottom: 6, fontSize: 12, padding: '5px 8px', background: 'rgba(255,255,255,0.1)', border: '1px solid rgba(255,255,255,0.2)', color: '#fff' }}
          />
          <div style={{ display: 'flex', gap: 4, marginBottom: 6, flexWrap: 'wrap' }}>
            {PROJECT_COLORS.map(c => (
              <button
                key={c} type="button"
                onClick={() => setNewColor(c)}
                style={{ width: 18, height: 18, borderRadius: '50%', background: c, padding: 0, border: newColor === c ? '2px solid #fff' : '2px solid transparent' }}
              />
            ))}
          </div>
          <div style={{ display: 'flex', gap: 4 }}>
            <button type="submit" disabled={creating} style={{ flex: 1, background: '#0073ea', color: '#fff', padding: '4px 8px', fontSize: 12, borderRadius: 4 }}>
              {creating ? '...' : 'Create'}
            </button>
            <button type="button" onClick={() => setShowNewProject(false)} style={{ background: 'rgba(255,255,255,0.1)', color: '#fff', padding: '4px 8px', fontSize: 12, borderRadius: 4 }}>
              Cancel
            </button>
          </div>
        </form>
      ) : (
        <button className="sidebar-link" style={{ fontSize: 12, color: 'rgba(255,255,255,0.4)', marginTop: 2 }} onClick={() => setShowNewProject(true)}>
          + New project
        </button>
      )}

      <div className="sidebar-section-label" style={{ marginTop: 8 }}>Tools</div>
      <NavLink to="/import" className={({ isActive }) => 'sidebar-link' + (isActive ? ' active' : '')}>
        <span className="icon">&#8659;</span> Import
      </NavLink>
      <NavLink to="/scraper" className={({ isActive }) => 'sidebar-link' + (isActive ? ' active' : '')}>
        <span className="icon">&#9740;</span> Scraper
      </NavLink>
      {isAdmin && (
        <NavLink to="/admin" className={({ isActive }) => 'sidebar-link' + (isActive ? ' active' : '')}>
          <span className="icon">&#9733;</span> Admin
        </NavLink>
      )}

      <div className="sidebar-footer">
        <div style={{ color: 'rgba(255,255,255,0.5)', fontSize: 12, padding: '0 4px', marginBottom: 6 }}>
          {user?.full_name || user?.username}
        </div>
        <button className="sidebar-link" style={{ fontSize: 12 }} onClick={handleLogout}>
          Sign out
        </button>
      </div>
    </aside>
  )
}

function Topbar() {
  const { activeProject } = useProject()
  return (
    <div className="topbar">
      <span style={{ color: 'var(--text-muted)', fontSize: 13 }}>LeadGen</span>
      {activeProject && (
        <>
          <span style={{ color: 'var(--text-light)', fontSize: 13 }}>/</span>
          <span style={{ width: 10, height: 10, borderRadius: '50%', background: activeProject.color, display: 'inline-block', flexShrink: 0 }} />
          <span className="topbar-title">{activeProject.name}</span>
        </>
      )}
    </div>
  )
}

function ProtectedRoute({ children, adminOnly = false }) {
  const { token, isAdmin } = useAuth()
  if (!token) return <Navigate to="/login" replace />
  if (adminOnly && !isAdmin) return <Navigate to="/" replace />
  return children
}

function AppShell() {
  const { token } = useAuth()

  if (!token) {
    return (
      <Routes>
        <Route path="*" element={<Login />} />
      </Routes>
    )
  }

  return (
    <ProjectProvider>
      <div className="app-shell">
        <Sidebar />
        <div className="main-content">
          <Topbar />
          <Routes>
            <Route path="/login" element={<Navigate to="/" replace />} />
            <Route path="/" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
            <Route path="/projects" element={<ProtectedRoute><Projects /></ProtectedRoute>} />
            <Route path="/leads" element={<ProtectedRoute><LeadsTable /></ProtectedRoute>} />
            <Route path="/import" element={<ProtectedRoute><ImportWizard /></ProtectedRoute>} />
            <Route path="/scraper" element={<ProtectedRoute><ScraperConfig /></ProtectedRoute>} />
            <Route path="/admin" element={<ProtectedRoute adminOnly><Admin /></ProtectedRoute>} />
            <Route path="*" element={<Navigate to="/" replace />} />
          </Routes>
        </div>
      </div>
    </ProjectProvider>
  )
}

export default function App() {
  return (
    <AuthProvider>
      <AppShell />
    </AuthProvider>
  )
}