// === Início de: ./src/index.css === @import "tailwindcss"; // === Fim de: ./src/index.css === // === Início de: ./src/App.css === #root { max-width: 1280px; margin: 0 auto; padding: 2rem; text-align: center; } .logo { height: 6em; padding: 1.5em; will-change: filter; transition: filter 300ms; } .logo:hover { filter: drop-shadow(0 0 2em #646cffaa); } .logo.react:hover { filter: drop-shadow(0 0 2em #61dafbaa); } @keyframes logo-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @media (prefers-reduced-motion: no-preference) { a:nth-of-type(2) .logo { animation: logo-spin infinite 20s linear; } } .card { padding: 2em; } .read-the-docs { color: #888; } // === Fim de: ./src/App.css === // === Início de: ./src/main.jsx === import React, { Suspense, lazy } from 'react'; import { createRoot } from 'react-dom/client'; import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; import AppLayout from './App'; import './index.css'; import { AuthProvider } from './contexts/AuthContext'; import { ToastProvider } from './components/ToastContext'; import { ChargersProvider } from './contexts/ChargersContext'; import ProtectedRoute from './components/ProtectedRoute'; const PageLoading = () => (
Loading...
); const ChargersPage = lazy(() => import('./pages/ChargersPage')); const ChargerDashboardPage = lazy(() => import('./pages/ChargerDashboardPage')); const HistoryPage = lazy(() => import('./pages/HistoryPage')); const SettingsPage = lazy(() => import('./pages/SettingsPage')); const LoginPage = lazy(() => import('./pages/LoginPage')); const container = document.getElementById('root'); if (!container) throw new Error('Root element not found'); const root = createRoot(container); root.render( {/* ✅ Coloque BrowserRouter primeiro */} {/* ✅ Agora dentro de BrowserRouter */} }> } /> } > } /> } /> } /> } /> } /> } />

404

Page not found

Go back home } />
); // === Fim de: ./src/main.jsx === // === Início de: ./src/App.jsx === import { Outlet } from 'react-router-dom'; import BottomNav from './components/BottomNav'; function AppLayout() { return (
); } export default AppLayout; // === Fim de: ./src/App.jsx === // === Início de: ./src/hooks/useChargers.js === // src/hooks/useChargers.js import { useEffect, useState } from "react"; import { getAuthHeader } from "@/utils/apiAuthHeader"; export function useChargers() { const [chargers, setChargers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); useEffect(() => { setLoading(true); fetch("/api/chargers", { headers: getAuthHeader(), }) .then(res => { if (!res.ok) throw new Error("Erro ao buscar carregadores"); return res.json(); }) .then(json => { setChargers(json.data || []); setError(""); }) .catch(() => setError("Falha ao carregar carregadores")) .finally(() => setLoading(false)); }, []); return { chargers, setChargers, loading, error }; } // === Fim de: ./src/hooks/useChargers.js === // === Início de: ./src/hooks/useSchedules.js === // src/hooks/useSchedules.js import { useState, useEffect } from 'react'; import { getAuthHeader } from '@/utils/apiAuthHeader'; // Agora recebe chargerId! export function useSchedules(chargerId) { const [schedules, setSchedules] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); useEffect(() => { if (!chargerId) return; // Só busca se houver carregador selecionado setLoading(true); fetch(`/api/chargers/${chargerId}/schedule`, { headers: getAuthHeader(), }) .then(res => { if (res.status === 401 || res.status === 403) { setError('Sessão expirada. Faça login novamente.'); return { data: [] }; } return res.json(); }) .then(json => { setSchedules(json.data || []); setError(''); }) .catch(() => setError('Falha ao carregar agendamentos')) .finally(() => setLoading(false)); }, [chargerId]); // <-- Atualiza quando o chargerId mudar return { schedules, setSchedules, loading, error }; } // === Fim de: ./src/hooks/useSchedules.js === // === Início de: ./src/hooks/useChargerStatus.js === // === Fim de: ./src/hooks/useChargerStatus.js === // === Início de: ./src/hooks/useApi.js === // src/hooks/useApi.js import { useState } from 'react'; import { getAuthHeader } from '@/utils/apiAuthHeader'; export default function useApi() { const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const request = async (url, options = {}) => { setLoading(true); setError(''); try { const res = await fetch(url, { ...options, headers: { ...getAuthHeader(), ...(options.headers || {}) }, }); if (res.status === 401 || res.status === 403) { setError('Sessão expirada. Faça login novamente.'); throw new Error('Sessão expirada'); } const data = await res.json(); if (!res.ok) throw new Error(data.message || 'Erro na API'); return data; } catch (err) { setError(err.message); throw err; } finally { setLoading(false); } }; return { request, loading, error }; } // === Fim de: ./src/hooks/useApi.js === // === Início de: ./src/hooks/useCharger.js === // src/hooks/useCharger.js import { useState, useEffect } from 'react'; import { getAuthHeader } from '@/utils/apiAuthHeader'; export function useCharger(chargerId) { const [chargerData, setChargerData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); useEffect(() => { if (!chargerId) return; const fetchCharger = async () => { setLoading(true); try { const res = await fetch(`/api/chargers/${chargerId}/status`, { headers: getAuthHeader(), }); if (!res.ok) throw new Error('Failed to fetch charger data'); const json = await res.json(); setChargerData(json.data); setError(''); } catch (err) { setError(err.message); } finally { setLoading(false); } }; fetchCharger(); }, [chargerId]); return { chargerData, loading, error }; } // === Fim de: ./src/hooks/useCharger.js === // === Início de: ./src/hooks/useHistory.js === // src/hooks/useHistory.js import { useState, useEffect } from 'react'; import { getAuthHeader } from '@/utils/apiAuthHeader'; // Agora recebe chargerId! export function useHistory(chargerId) { const [histories, setHistory] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); useEffect(() => { if (!chargerId) return; // Só busca se houver carregador selecionado setLoading(true); fetch(`/api/chargers/${chargerId}/history`, { headers: getAuthHeader(), }) .then(res => { if (res.status === 401 || res.status === 403) { setError('Sessão expirada. Faça login novamente.'); return { data: [] }; } return res.json(); }) .then(json => { setHistory(json.data || []); setError(''); }) .catch(() => setError('Falha ao carregar agendamentos')) .finally(() => setLoading(false)); }, [chargerId]); // <-- Atualiza quando o chargerId mudar return { histories, setHistory, loading, error }; } // === Fim de: ./src/hooks/useHistory.js === // === Início de: ./src/utils/apiAuthHeader.js === export function getAuthHeader() { if (typeof window === 'undefined') return {}; const token = window.localStorage?.getItem('token'); return token ? { Authorization: `Bearer ${token}` } : {}; } // === Fim de: ./src/utils/apiAuthHeader.js === // === Início de: ./src/utils/apiFetch.js === // utils/apiFetch.js import { getAuthHeader } from './apiAuthHeader'; export async function apiFetch(url, options = {}) { const res = await fetch(url, { ...options, headers: { ...getAuthHeader(), ...(options.headers || {}), }, }); const json = await res.json(); if (!res.ok || !json.success) { throw new Error(json.message || 'Erro na requisição'); } return json.data; } // === Fim de: ./src/utils/apiFetch.js === // === Início de: ./src/components/ProtectedRoute.jsx === import { useAuth } from '@/contexts/AuthContext'; import { Navigate, useLocation } from 'react-router-dom'; // Uso: export default function ProtectedRoute({ children }) { const { isAuthenticated } = useAuth(); const location = useLocation(); if (!isAuthenticated) { // redireciona para /login, guardando a rota atual para possível redirect pós-login return ; } return children; } // === Fim de: ./src/components/ProtectedRoute.jsx === // === Início de: ./src/components/BottomNav.jsx === import { Link, useMatch, useResolvedPath } from 'react-router-dom'; import { Zap, Calendar, Settings, BatteryCharging } from 'lucide-react'; import { motion } from 'framer-motion'; const tabs = [ { label: 'Carregador', to: '/dashboard', icon: Zap }, { label: 'Histórico', to: '/history', icon: Calendar }, { label: 'Configuração', to: '/settings', icon: Settings }, { label: 'Carregadores', to: '/', icon: BatteryCharging }, ]; export default function BottomNav() { return ( {tabs.map(({ label, to, icon: Icon }) => { const resolved = useResolvedPath(to); const match = useMatch({ path: resolved.pathname, end: true }); return ( {label} ); })} ); } // === Fim de: ./src/components/BottomNav.jsx === // === Início de: ./src/components/ToastContext.jsx === // src/components/ToastContext.jsx import { createContext, useContext, useState } from 'react'; const ToastContext = createContext(); export function ToastProvider({ children }) { const [toast, setToast] = useState(null); function showToast(message, type = 'success') { setToast({ message, type }); setTimeout(() => setToast(null), 2500); } return ( {children} {toast && (
{toast.message}
)}
); } export function useToast() { return useContext(ToastContext); } // === Fim de: ./src/components/ToastContext.jsx === // === Início de: ./src/components/ui/SectionCard.jsx === // src/components/ui/SectionCard.jsx import { motion } from 'framer-motion'; export function SectionCard({ title, icon, children }) { return (
{icon &&
{icon}
}

{title}

{children}
); } // === Fim de: ./src/components/ui/SectionCard.jsx === // === Início de: ./src/pages/ChargersPage.jsx === // src/pages/ChargersPage.jsx import React, { useState } from 'react'; import { Plus, Loader2, X, Trash2 } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { useChargersContext } from '@/contexts/ChargersContext'; import { useToast } from '@/components/ToastContext'; export default function ChargersPage() { const { chargers, selectedCharger, selectChargerById, fetchChargers, loading, error } = useChargersContext(); const [deletingId, setDeletingId] = useState(null); const [showAdd, setShowAdd] = useState(false); const toast = useToast(); const handleOpenAdd = () => setShowAdd(true); const handleCloseAdd = () => setShowAdd(false); const handleSelect = (charger) => { selectChargerById(charger.id); }; const handleDelete = async (id) => { if (!window.confirm('Are you sure you want to delete this charger? This action cannot be undone.')) return; setDeletingId(id); try { const res = await fetch(`/api/chargers/${id}`, { method: 'DELETE' }); if (!res.ok) throw new Error('Failed to delete charger'); toast('Charger deleted', 'success'); await fetchChargers(); } catch (err) { console.error(err); toast(err.message, 'error'); } finally { setDeletingId(null); } }; if (loading) { return (
); } if (error) { return

{error}

; } return (

My Chargers

{chargers.length > 0 ? ( chargers.map((charger) => ( handleSelect(charger)} >
{charger.name}
Pairing code: {charger.pairingCode}
Location: {charger.location}
Last activity: {charger.lastActivity}
)) ) : (

No chargers found

)}
{showAdd && }
); } function AddChargerModal({ onClose, onSaved }) { const [name, setName] = useState(''); const [location, setLocation] = useState(''); const [saving, setSaving] = useState(false); const toast = useToast(); const handleSubmit = async (e) => { e.preventDefault(); setSaving(true); try { const res = await fetch('/api/chargers', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, location }), }); if (!res.ok) throw new Error('Failed to add charger'); toast('Charger added', 'success'); onSaved(); onClose(); } catch (err) { console.error(err); toast(err.message, 'error'); } finally { setSaving(false); } }; return (

New Charger

setName(e.target.value)} required className="w-full border border-gray-300 rounded-lg px-3 py-2" />
setLocation(e.target.value)} required className="w-full border border-gray-300 rounded-lg px-3 py-2" />
); } // === Fim de: ./src/pages/ChargersPage.jsx === // === Início de: ./src/pages/HistoryPage.jsx === // src/pages/HistoryPage.jsx import React, { useState, useEffect } from 'react'; import { History } from 'lucide-react'; import { SectionCard } from '@/components/ui/SectionCard'; import { useHistory } from '@/hooks/useHistory'; import { useChargersContext } from '@/contexts/ChargersContext'; import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, ReferenceLine, } from 'recharts'; export default function HistoryPage() { const { selectedCharger } = useChargersContext(); const chargerId = selectedCharger?.id; const { histories, loading, error } = useHistory(chargerId); // History/demo state const [viewMode, setViewMode] = useState('Week'); const [chartData] = useState([ { day: 'Mon', kwh: 12 }, { day: 'Tue', kwh: 15 }, { day: 'Wed', kwh: 13 }, { day: 'Thu', kwh: 0 }, { day: 'Fri', kwh: 14 }, { day: 'Sat', kwh: 18 }, { day: 'Sun', kwh: 12 }, ]); const [sessions] = useState([ { date: '27 Oct', time: '19:30', kwh: 12, duration: '2h 03m', cost: '€2,40' }, { date: '25 Oct', time: '07:15', kwh: 8, duration: '1h 20m', cost: '€1,60' }, ]); const totalKwh = chartData.reduce((sum, d) => sum + d.kwh, 0).toFixed(2); const totalCost = (totalKwh * 0.2).toFixed(2); return (
{/* === Histórico === */} }>
{['Week', 'Month', 'Year'].map(mode => ( ))}
Total energy
{totalKwh} kWh
Spent €{totalCost}
`${val} kWh`} /> s + d.kwh, 0) / chartData.length} stroke="#999" strokeDasharray="3 3" />
23 – 29 Oct 2023
Recent sessions
    {sessions.map((s, i) => (
  • {s.date} · {s.time}
    {s.kwh} kWh · {s.duration}
    {s.cost}
  • ))}
); } // === Fim de: ./src/pages/HistoryPage.jsx === // === Início de: ./src/pages/ChargerDashboardPage.jsx === import React, { useEffect, useState } from 'react'; import { Play, Square, Zap, BatteryCharging, Clock, Activity, Loader2, } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { SectionCard } from '@/components/ui/SectionCard'; import { useToast } from '@/components/ToastContext'; import { useChargersContext } from '@/contexts/ChargersContext'; import { getAuthHeader } from '@/utils/apiAuthHeader'; export default function ChargerDashboardPage() { const { selectedCharger } = useChargersContext(); const chargerId = selectedCharger?.id; const [chargerData, setChargerData] = useState(null); const [ampLimit, setAmpLimit] = useState(6); const [maxAmps, setMaxAmps] = useState(32); const [error, setError] = useState(''); const [loading, setLoading] = useState(true); const [busy, setBusy] = useState(false); const toast = useToast(); useEffect(() => { if (!chargerId) { setError('Charger not found!'); setLoading(false); return; } let canceled = false; const fetchStatus = async () => { setLoading(true); setError(''); try { const res = await fetch(`/api/chargers/${chargerId}/status`, { headers: getAuthHeader(), // ✅ Com token }); const json = await res.json(); if (!res.ok || !json.success) { throw new Error(json.message || 'Failed to fetch status'); } if (!canceled) { setChargerData(json.data); setAmpLimit(json.data.ampLimit ?? 6); setMaxAmps(json.data.maxAmps ?? 32); } } catch (err) { if (!canceled) setError(err.message); } finally { if (!canceled) setLoading(false); } }; fetchStatus(); const interval = setInterval(fetchStatus, 8000); return () => { canceled = true; clearInterval(interval); }; }, [chargerId]); const handleAction = async (action) => { if (!chargerId) return; setBusy(true); try { const res = await fetch(`/api/chargers/${chargerId}/${action}`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...getAuthHeader(), // ✅ Corrigido aqui }, body: JSON.stringify({ ampLimit }), }); const json = await res.json(); if (!res.ok || !json.success) { throw new Error(json.message || 'Failed to send command'); } setChargerData(json.data); toast(action === 'start' ? 'Charging started' : 'Charging stopped', 'success'); setError(''); } catch (err) { setError(err.message); toast(err.message, 'error'); } finally { setBusy(false); } }; return (
} > {error && ( {error} )} {loading || busy ? (
{busy ? 'Sending command...' : 'Loading status...'}
) : ( chargerData && ( <>
7 ? 'border-yellow-400 opacity-60' : 'border-blue-200 opacity-30' }`} />
7 ? 'border-yellow-500' : 'border-blue-600' } flex flex-col items-center justify-center text-blue-700`} > 7 ? 'text-yellow-500' : 'text-blue-600' }`} > {chargerData.currentPower} kW Power
} label="Status" value={chargerData.status} /> } label="Time Remaining" value={chargerData.timeRemaining} /> } label="Power" value={{chargerData.currentPower} kW} /> } label="Mode" value={chargerData.mode?.toUpperCase()} />
setAmpLimit(Number(e.target.value))} className="w-full accent-blue-600" disabled={busy} />
6 A {maxAmps} A
handleAction('start')} disabled={busy} className="bg-green-600 hover:bg-green-700 text-white px-5 py-2 rounded-xl shadow font-medium flex items-center gap-2" > Start handleAction('stop')} disabled={busy} className="bg-red-500 hover:bg-red-600 text-white px-5 py-2 rounded-xl shadow font-medium flex items-center gap-2" > Stop
) )}
); } function InfoItem({ icon, label, value }) { return (
{icon} {label}
{value}
); } // === Fim de: ./src/pages/ChargerDashboardPage.jsx === // === Início de: ./src/pages/SettingsPage.jsx === // src/pages/SettingsPage.jsx import React, { useState } from 'react'; import { Settings as SettingsIcon, Power, Loader2, CalendarDays } from 'lucide-react'; import { SectionCard } from '@/components/ui/SectionCard'; import { motion } from 'framer-motion'; import { useAuth } from '@/contexts/AuthContext'; import { useSchedules } from '@/hooks/useSchedules'; import { useToast } from '@/components/ToastContext'; import { useChargersContext } from '@/contexts/ChargersContext'; export default function SettingsPage() { const { logout } = useAuth(); const toast = useToast(); // Settings state const [energyRate, setEnergyRate] = useState(0.5); const [batteryCapacity, setBatteryCapacity] = useState(60); const [chargeLimit, setChargeLimit] = useState(80); const [theme, setTheme] = useState('light'); const [loadingDiag, setLoadingDiag] = useState(false); const handleDiagnostic = () => { setLoadingDiag(true); setTimeout(() => { setLoadingDiag(false); toast('Diagnostic complete: Charger OK!', 'success'); }, 1200); }; // Agendamentos state const { selectedCharger } = useChargersContext(); const chargerId = selectedCharger?.id; const { schedules, setSchedules, loading, error } = useSchedules(chargerId); const [startTime, setStartTime] = useState(''); const [targetPercentage, setTargetPercentage] = useState(80); const handleAddSchedule = (e) => { e.preventDefault(); if (!startTime) return; setSchedules([...schedules, { startTime, targetPercentage }]); toast('Agendamento adicionado!', 'success'); setStartTime(''); setTargetPercentage(80); }; return (
{/* Agendamentos */} }>
setStartTime(e.target.value)} className="w-full border px-3 py-2 rounded-lg" required />
setTargetPercentage(parseInt(e.target.value))} className="w-full border px-3 py-2 rounded-lg" required />
{loading ?

Carregando...

: error ?

{error}

: (
    {schedules.length === 0 ? (
  • Nenhum agendamento
  • ) : ( schedules.map((item, i) => (
  • {item.startTime} — {item.targetPercentage}%
  • )) )}
)}
{/* Settings */} }>
setEnergyRate(parseFloat(e.target.value))} className="w-full border px-4 py-2 rounded-lg focus:ring-2 focus:ring-blue-500" />
setBatteryCapacity(parseInt(e.target.value))} className="w-full border px-4 py-2 rounded-lg focus:ring-2 focus:ring-blue-500" />
setChargeLimit(Math.min(100, Math.max(1, parseInt(e.target.value))))} className="w-full border px-4 py-2 rounded-lg focus:ring-2 focus:ring-blue-500" />
{loadingDiag ? : 'Run Diagnostic'}
Logout
); } // === Fim de: ./src/pages/SettingsPage.jsx === // === Início de: ./src/pages/LoginPage.jsx === import { useState, useRef } from 'react'; import { useAuth } from '@/contexts/AuthContext'; import { useToast } from '@/components/ToastContext'; import { Loader2 } from 'lucide-react'; export default function Login() { const { login } = useAuth(); const showToast = useToast(); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [loading, setLoading] = useState(false); const userInput = useRef(null); const handleSubmit = async (e) => { e.preventDefault(); setLoading(true); try { const res = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }), }); const data = await res.json(); if (!res.ok) throw new Error(data?.error || 'Erro ao fazer login'); login(data.token); // ✅ usa o token corretamente } catch (err) { showToast(err.message, 'error'); setLoading(false); userInput.current?.focus(); } }; function handleForgotPassword(e) { e.preventDefault(); showToast('Link de recuperação em breve!', 'success'); // Aqui você pode abrir modal, redirecionar, ou acionar fluxo real de recuperação } return (

EV Station Login

setUsername(e.target.value)} className="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 transition" required autoFocus />
setPassword(e.target.value)} className="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 transition" required />
); } // === Fim de: ./src/pages/LoginPage.jsx === // === Início de: ./src/contexts/AuthContext.jsx === import { createContext, useContext, useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; // Utilitário para decodificar JWT sem biblioteca externa function parseJwt(token) { try { return JSON.parse(atob(token.split('.')[1])); } catch { return null; } } const AuthContext = createContext(); export function AuthProvider({ children }) { const navigate = useNavigate(); const [token, setToken] = useState(() => localStorage.getItem('token') || ''); const [user, setUser] = useState(() => { const stored = localStorage.getItem('token'); return stored ? parseJwt(stored) : null; }); useEffect(() => { if (!token) return; const payload = parseJwt(token); if (!payload) { logout(); return; } setUser(payload); // Expiração do token const expiresAt = payload.exp * 1000; const now = Date.now(); const timeout = expiresAt - now; if (timeout <= 0) { logout(); } else { const timer = setTimeout(() => { logout(); }, timeout); return () => clearTimeout(timer); } }, [token]); const login = (newToken) => { const payload = parseJwt(newToken); if (!payload) { console.error("Token inválido"); return; } localStorage.setItem('token', newToken); setToken(newToken); setUser(payload); navigate('/'); }; const logout = () => { localStorage.removeItem('token'); setToken(''); setUser(null); navigate('/login'); }; const isAuthenticated = !!token; return ( {children} ); } export function useAuth() { const ctx = useContext(AuthContext); if (!ctx) throw new Error('useAuth must be used within AuthProvider'); return ctx; } // === Fim de: ./src/contexts/AuthContext.jsx === // === Início de: ./src/contexts/ChargersContext.jsx === import React, { createContext, useContext, useState, useEffect } from 'react'; import useApi from '@/hooks/useApi'; const ChargersContext = createContext(); export function ChargersProvider({ children }) { const { request, loading: apiLoading, error: apiError } = useApi(); const [chargers, setChargers] = useState([]); const [selectedCharger, setSelectedCharger] = useState(null); // Busca lista de carregadores e seleciona o primeiro se necessário const fetchChargers = async () => { try { const response = await request('/api/chargers'); const list = response.data || []; setChargers(list); if (!selectedCharger && list.length > 0) { setSelectedCharger(list[0]); } } catch (err) { console.error('Error fetching chargers:', err); } }; useEffect(() => { fetchChargers(); }, []); // Seleciona um carregador por ID const selectChargerById = (id) => { const charger = chargers.find(c => c.id === id) || null; setSelectedCharger(charger); }; return ( {children} ); } export function useChargersContext() { const context = useContext(ChargersContext); if (!context) throw new Error('useChargersContext must be used within a ChargersProvider'); return context; } // === Fim de: ./src/contexts/ChargersContext.jsx === // === Início de: ./vite.config.js === import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import tailwindcss from '@tailwindcss/vite'; import { VitePWA } from 'vite-plugin-pwa'; import path from 'path'; // 👈 necessário para resolver caminhos export default defineConfig({ base: '/', plugins: [ react(), tailwindcss(), VitePWA({ registerType: 'autoUpdate', includeAssets: ['icons/icon-192.png', 'icons/icon-512.png', 'favicon.ico'], manifest: { name: 'EV Station Controller', short_name: 'EVStation', start_url: '/', display: 'standalone', background_color: '#ffffff', theme_color: '#0f172a', icons: [ { src: 'icons/icon-192.png', sizes: '192x192', type: 'image/png', }, { src: 'icons/icon-512.png', sizes: '512x512', type: 'image/png', }, ], }, }), ], resolve: { alias: { '@': path.resolve(__dirname, 'src'), // 👈 alias @ → ./src }, }, server: { proxy: { '/api': 'http://localhost:4000', }, }, build: { sourcemap: false, minify: 'esbuild', target: 'esnext', outDir: 'dist', }, }); // === Fim de: ./vite.config.js === // === Início de: ./package.json === { "name": "ev-pwa", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "vite build", "lint": "eslint .", "preview": "vite preview" }, "dependencies": { "framer-motion": "^12.18.1", "lucide-react": "^0.515.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-router-dom": "^7.6.2", "recharts": "^2.15.3" }, "devDependencies": { "@eslint/js": "^9.25.0", "@tailwindcss/cli": "^4.1.10", "@tailwindcss/vite": "^4.1.10", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", "@vitejs/plugin-react": "^4.4.1", "autoprefixer": "^10.4.21", "eslint": "^9.25.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", "postcss": "^8.5.5", "tailwindcss": "^4.1.10", "vite": "^6.3.5", "vite-plugin-pwa": "^1.0.0" } } // === Fim de: ./package.json ===