Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ typing-extensions = "*"
flask-jwt-extended = "==4.6.0"
wtforms = "==3.1.2"
sqlalchemy = "*"
bcrypt = "*"

[requires]
python_version = "3.13"
Expand Down
60 changes: 59 additions & 1 deletion Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 13 additions & 18 deletions src/front/components/Navbar.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import { Link } from "react-router-dom";
import React from 'react';
import { Link } from 'react-router-dom';

export const Navbar = () => {

return (
<nav className="navbar navbar-light bg-light">
<div className="container">
<Link to="/">
<span className="navbar-brand mb-0 h1">React Boilerplate</span>
</Link>
<div className="ml-auto">
<Link to="/demo">
<button className="btn btn-primary">Check the Context in action</button>
</Link>
</div>
</div>
</nav>
);
};
export const Navbar = () => (
<nav className="navbar navbar-light bg-light">
<div className="container">
<Link className="navbar-brand" to="/">AgriMarket</Link>
<div className="d-flex ms-auto">
<Link className="btn btn-outline-success me-2" to="/login">Acceder</Link>
<Link className="btn btn-success" to="/register">Registrarse</Link>
</div>
</div>
</nav>
);
1 change: 1 addition & 0 deletions src/front/main.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css' // Global styles for your application
Expand Down
219 changes: 170 additions & 49 deletions src/front/pages/Home.jsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,173 @@
import React, { useEffect } from "react"
import rigoImageUrl from "../assets/img/rigo-baby.jpg";
import useGlobalReducer from "../hooks/useGlobalReducer.jsx";
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import useGlobalReducer from '../hooks/useGlobalReducer';

export const Home = () => {
const { store } = useGlobalReducer();
const [offers, setOffers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

const { store, dispatch } = useGlobalReducer()

const loadMessage = async () => {
try {
const backendUrl = import.meta.env.VITE_BACKEND_URL

if (!backendUrl) throw new Error("VITE_BACKEND_URL is not defined in .env file")

const response = await fetch(backendUrl + "/api/hello")
const data = await response.json()

if (response.ok) dispatch({ type: "set_hello", payload: data.message })

return data

} catch (error) {
if (error.message) throw new Error(
`Could not fetch the message from the backend.
Please check if the backend is running and the backend port is public.`
);
}

}

useEffect(() => {
loadMessage()
}, [])

return (
<div className="text-center mt-5">
<h1 className="display-4">Hello Rigo!!</h1>
<p className="lead">
<img src={rigoImageUrl} className="img-fluid rounded-circle mb-3" alt="Rigo Baby" />
</p>
<div className="alert alert-info">
{store.message ? (
<span>{store.message}</span>
) : (
<span className="text-danger">
Loading message from the backend (make sure your python 🐍 backend is running)...
</span>
)}
</div>
</div>
);
};
// Form state
const [form, setForm] = useState({ name: "", seller: "", price: "", unit: "", img: "" });
const [submitting, setSubmitting] = useState(false);
const [submitError, setSubmitError] = useState(null);

useEffect(() => {
const fetchOffers = async () => {
try {
const backendUrl = import.meta.env.VITE_BACKEND_URL;
if (!backendUrl) throw new Error("VITE_BACKEND_URL is not defined");

const res = await fetch(`${backendUrl}/api/offers`);
if (!res.ok) throw new Error(`Error: ${res.statusText}`);
const data = await res.json();
setOffers(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchOffers();
}, []);

// Handle input change
const handleChange = e => {
const { name, value } = e.target;
setForm(prev => ({ ...prev, [name]: value }));
};

// Submit new offer
const handleSubmit = async e => {
e.preventDefault();
setSubmitting(true);
setSubmitError(null);
try {
const backendUrl = import.meta.env.VITE_BACKEND_URL;
const res = await fetch(`${backendUrl}/api/offers`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(form)
});
if (!res.ok) throw new Error(await res.text());
const newOffer = await res.json();
setOffers(prev => [newOffer, ...prev]);
setForm({ name: "", seller: "", price: "", unit: "", img: "" });
} catch (err) {
setSubmitError(err.message);
} finally {
setSubmitting(false);
}
};

return (
<div className="container my-5">
<div className="row gx-4">
<div className="col-md-8">
<div className="bg-white p-4 rounded shadow-sm mb-4">
{/* Conditionally show form if user is logged in */}
{store.user ? (
<>
<h2 className="text-success mb-3">Crear Nueva Oferta</h2>
{submitError && <div className="alert alert-danger">{submitError}</div>}
<form onSubmit={handleSubmit} className="mb-4">
<div className="row g-2">
<div className="col-md-6">
<input
name="name"
value={form.name}
onChange={handleChange}
placeholder="Nombre"
className="form-control"
required
/>
</div>
<div className="col-md-6">
<input
name="seller"
value={form.seller}
onChange={handleChange}
placeholder="Agricultor"
className="form-control"
required
/>
</div>
</div>
<div className="row g-2 mt-2">
<div className="col-md-4">
<input
name="price"
type="number"
step="0.01"
value={form.price}
onChange={handleChange}
placeholder="Precio"
className="form-control"
required
/>
</div>
<div className="col-md-4">
<input
name="unit"
value={form.unit}
onChange={handleChange}
placeholder="Unidad"
className="form-control"
required
/>
</div>
<div className="col-md-4">
<input
name="img"
value={form.img}
onChange={handleChange}
placeholder="URL Imagen"
className="form-control"
/>
</div>
</div>
<button type="submit" className="btn btn-primary mt-3" disabled={submitting}>
{submitting ? 'Enviando...' : 'Crear Oferta'}
</button>
</form>
</>
) : (
<div className="alert alert-info">
<Link to="/login">Inicia sesión</Link> para crear nuevas ofertas.
</div>
)}

{/* Listing */}
<h2 className="text-success mb-3">Ofertas</h2>
{loading && <div className="spinner-border text-success" role="status"><span className="visually-hidden">Cargando...</span></div>}
{error && <div className="alert alert-danger">{error}</div>}
{!loading && !error && (
<div className="overflow-auto" style={{ maxHeight: '400px', overflowY: 'auto', overflowX: 'hidden' }}>
<div className="d-flex flex-column gap-3">
{offers.map(o => (
<div key={o.id} className="card">
<img src={o.img || 'https://via.placeholder.com/300x150.png?text=Producto'} className="card-img-top" alt={o.name} style={{ height: 150, objectFit: 'cover' }} />
<div className="card-body">
<h5 className="card-title mb-1">{o.name}</h5>
<p className="card-text mb-1">Agricultor: {o.seller}</p>
<p className="fw-bold text-success mb-0">€{o.price} / {o.unit}</p>
</div>
</div>
))}
</div>
</div>
)}
<div className="text-center mt-2">
<Link to="/login" className="btn btn-warning">Mostrar más</Link>
</div>
</div>
</div>
<div className="col-md-4 d-flex flex-column justify-content-center align-items-center">
<Link to="/login" className="btn btn-success btn-lg w-75 mb-3">Comprar</Link>
<Link to="/login" className="btn btn-outline-success btn-lg w-75">Vender</Link>
</div>
</div>
</div>
);
};