From 56d543a666697f83e06ae11c606c29914eb6ba55 Mon Sep 17 00:00:00 2001 From: Edmilson Lopes Date: Tue, 9 Dec 2025 15:36:37 -0300 Subject: [PATCH] Add database credential login and improve API handling --- server/index.js | 14 +++++ src/App.js | 110 ++++++++++++++++++++++++++++----- src/App.test.js | 6 +- src/config/apiClient.js | 68 +++++++++++++------- src/services/authService.js | 11 ++++ src/services/orderService.js | 10 ++- src/services/productService.js | 10 ++- 7 files changed, 182 insertions(+), 47 deletions(-) create mode 100644 src/services/authService.js diff --git a/server/index.js b/server/index.js index 7adac36f..9db2bdc1 100644 --- a/server/index.js +++ b/server/index.js @@ -29,6 +29,20 @@ app.get('/api/products', async (_, res) => { res.json(result.rows); }); +app.post('/api/login', (req, res) => { + const { username, password } = req.body || {}; + + const validUser = process.env.ADMIN_USER || process.env.PGUSER || 'postgres'; + const validPassword = process.env.ADMIN_PASSWORD || process.env.PGPASSWORD || 'postgres'; + + if (username === validUser && password === validPassword) { + res.json({ token: 'ok', name: 'Administrador' }); + return; + } + + res.status(401).json({ message: 'Credenciais inválidas' }); +}); + app.post('/api/products', async (req, res) => { const { name, price, category, description, active = true } = req.body; const query = diff --git a/src/App.js b/src/App.js index 089f2e72..cd94412e 100644 --- a/src/App.js +++ b/src/App.js @@ -10,10 +10,9 @@ import { FileText, LogOut } from 'lucide-react'; -import { signInWithPopup, onAuthStateChanged, signOut } from 'firebase/auth'; -import { auth, googleProvider } from './config/firebase'; import { productService } from './services/productService'; import { orderService } from './services/orderService'; +import { authService } from './services/authService'; import { MenuView } from './components/Client/MenuView'; import { CartView } from './components/Client/CartView'; import { SuccessView } from './components/Client/SuccessView'; @@ -33,13 +32,20 @@ function App() { const [adminTab, setAdminTab] = useState('dashboard'); const [cart, setCart] = useState({}); const [customer, setCustomer] = useState(initialCustomer); + const [loginForm, setLoginForm] = useState({ username: '', password: '' }); + const [loginError, setLoginError] = useState(''); useEffect(() => { - const unsubAuth = onAuthStateChanged(auth, setUser); + const savedSession = localStorage.getItem('adminSession'); + if (savedSession) { + const parsedSession = JSON.parse(savedSession); + setUser(parsedSession); + setView('admin'); + } + const unsubProd = productService.subscribe(setProducts); const unsubOrders = orderService.subscribeAll(setOrders); return () => { - unsubAuth(); unsubProd(); unsubOrders(); }; @@ -91,17 +97,25 @@ function App() { setView('success'); }; - const handleLogin = async () => { + const handleLogin = async (event) => { + event?.preventDefault(); + setLoginError(''); + try { - await signInWithPopup(auth, googleProvider); + const session = await authService.login(loginForm.username, loginForm.password); + const sessionData = { ...session, username: loginForm.username }; + localStorage.setItem('adminSession', JSON.stringify(sessionData)); + setUser(sessionData); setView('admin'); } catch (error) { - alert('Erro ao logar: ' + error.message); + setLoginError(error.message || 'Falha ao autenticar'); } }; const logout = () => { - signOut(auth); + localStorage.removeItem('adminSession'); + setUser(null); + setLoginForm({ username: '', password: '' }); setView('menu'); }; @@ -152,11 +166,11 @@ function App() {
- {user.displayName?.[0] || 'U'} + {user.name?.[0]?.toUpperCase() || user.username?.[0]?.toUpperCase() || 'U'}
-

{user.displayName}

-

{user.email}

+

{user.name || 'Administrador'}

+

{user.username || 'admin'}

- + +
+ + )} {view === 'menu' && setView('cart')} />} {view === 'cart' && ( { +test('renderiza a marca Datony no topo', () => { render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); + const heading = screen.getByText(/Datony/i); + expect(heading).toBeInTheDocument(); }); diff --git a/src/config/apiClient.js b/src/config/apiClient.js index 65c6650b..a6d62b88 100644 --- a/src/config/apiClient.js +++ b/src/config/apiClient.js @@ -1,4 +1,20 @@ -const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || 'http://localhost:4000'; +const resolveBaseUrl = () => { + if (process.env.REACT_APP_API_BASE_URL) { + return process.env.REACT_APP_API_BASE_URL; + } + + if (typeof window === 'undefined') { + return 'http://localhost:4000'; + } + + if (window.location.port && window.location.port !== '3000') { + return `${window.location.origin}`; + } + + return 'http://localhost:4000'; +}; + +const API_BASE_URL = resolveBaseUrl(); const defaultHeaders = { 'Content-Type': 'application/json' @@ -12,31 +28,35 @@ const handleResponse = async (response) => { return response.json(); }; +const request = async (path, options) => { + try { + const response = await fetch(`${API_BASE_URL}${path}`, options); + return await handleResponse(response); + } catch (error) { + console.error('Erro ao comunicar com a API', error); + throw new Error('Não foi possível conectar à API. Verifique se o servidor está ativo.'); + } +}; + export const apiClient = { - get: async (path) => handleResponse(await fetch(`${API_BASE_URL}${path}`)), + get: async (path) => request(path), post: async (path, body) => - handleResponse( - await fetch(`${API_BASE_URL}${path}`, { - method: 'POST', - headers: defaultHeaders, - body: JSON.stringify(body) - }) - ), + request(path, { + method: 'POST', + headers: defaultHeaders, + body: JSON.stringify(body) + }), put: async (path, body) => - handleResponse( - await fetch(`${API_BASE_URL}${path}`, { - method: 'PUT', - headers: defaultHeaders, - body: JSON.stringify(body) - }) - ), + request(path, { + method: 'PUT', + headers: defaultHeaders, + body: JSON.stringify(body) + }), patch: async (path, body) => - handleResponse( - await fetch(`${API_BASE_URL}${path}`, { - method: 'PATCH', - headers: defaultHeaders, - body: JSON.stringify(body) - }) - ), - delete: async (path) => handleResponse(await fetch(`${API_BASE_URL}${path}`, { method: 'DELETE' })) + request(path, { + method: 'PATCH', + headers: defaultHeaders, + body: JSON.stringify(body) + }), + delete: async (path) => request(path, { method: 'DELETE' }) }; diff --git a/src/services/authService.js b/src/services/authService.js new file mode 100644 index 00000000..cdded347 --- /dev/null +++ b/src/services/authService.js @@ -0,0 +1,11 @@ +import { apiClient } from '../config/apiClient'; + +export const authService = { + async login(username, password) { + const response = await apiClient.post('/api/login', { username, password }); + return { + token: response.token, + name: response.name || 'Administrador' + }; + } +}; diff --git a/src/services/orderService.js b/src/services/orderService.js index 6b7af9dc..52819541 100644 --- a/src/services/orderService.js +++ b/src/services/orderService.js @@ -15,9 +15,13 @@ export const orderService = { let cancelled = false; const load = async () => { - const data = await apiClient.get('/api/orders'); - if (!cancelled) { - callback(data.map(normalizeOrder)); + try { + const data = await apiClient.get('/api/orders'); + if (!cancelled) { + callback(data.map(normalizeOrder)); + } + } catch (error) { + console.error('Erro ao carregar pedidos', error); } }; diff --git a/src/services/productService.js b/src/services/productService.js index 1eda4735..b95e4900 100644 --- a/src/services/productService.js +++ b/src/services/productService.js @@ -22,9 +22,13 @@ export const productService = { let cancelled = false; const load = async () => { - const data = await apiClient.get('/api/products'); - if (!cancelled) { - callback(data.map(normalizeProduct)); + try { + const data = await apiClient.get('/api/products'); + if (!cancelled) { + callback(data.map(normalizeProduct)); + } + } catch (error) { + console.error('Erro ao carregar produtos', error); } };