SPA
Simple Page Application vamos a realizar una aplicación de navegación o de tipo SPA por eso anteriormente se instalo la depedencia react router vamos a volver a reescribir el archivo App.jsx y escribimos el siguiente código.
import './App.css';
import { Routes, Route } from "react-router-dom"
import Home from "./Home"
import Hemeroteca from "./Hemeroteca"
import Admin from "./Admin"
import Menu from "./Menu"
import Registrar from "./Registrar"
import Dashboard from "./Dashboard"
import PrivateRoute from "./PrivateRoute"
import Comunicadores from './Comunicadores';
import Noticias from './Noticias';
import UploadEvidencia from './UploadEvidencia';
function App() {
return ( < >
<
Routes >
<
Route path = "/"
element = { < Menu / > } >
<
Route index element = { < Home / > }
/>
<
Route path = "Hemeroteca"
element = { < Hemeroteca / > }
/> <
Route path = "Admin"
element = { < Admin / > }
/>
<
/Route>
<Route
path='Dashboard'
element={
<PrivateRoute>
<Dashboard/>
</PrivateRoute>
}
>
</Route>
<Route path="Registrar" element=
{
<PrivateRoute>
<Registrar />
</PrivateRoute>
} / >
<Route path="Comunicadores" element=
{
<PrivateRoute>
<Comunicadores />
</PrivateRoute>
} / >
<Route path="Noticias" element=
{
<PrivateRoute>
<Noticias />
</PrivateRoute>
} / >
<Route path="Upload" element=
{
<PrivateRoute>
<UploadEvidencia />
</PrivateRoute>
} / >
<
/
Routes >
<
/>
);
}
export default App;
Creamos el archivo Menu.jsx
import './App.css';
import React from 'react'
import { BreadCrumb } from 'primereact/breadcrumb';
import { Outlet,useNavigate } from "react-router-dom";
import "primereact/resources/themes/lara-light-indigo/theme.css";
import "primereact/resources/primereact.min.css";
import "primeicons/primeicons.css";
function Menu() {
const navigate = useNavigate()
const items = [
{ label: 'Home', command: () => { navigate('/') } },
{ label: 'Hemeroteca', command: () => { navigate('/Hemeroteca') } },
{ label: 'Admin', command: () => { navigate('/Admin') } }
];
return (
<>
<div className = "Admin" >
<BreadCrumb model = { items }/>
</div>
<Outlet/>
</>
);
}
export default Menu;
Instalamos axios para consumir el api rest realizada con spring boot y apache cassandra
npm install --save axios
Creamos un archivo Home.jsx y escribimos lo siguiente:
import './App.css';
import { InputText } from 'primereact/inputtext';
import { useRef, useState ,useEffect} from 'react';
import { Fieldset } from 'primereact/fieldset';
import { Button } from 'primereact/button';
import { Toast } from 'primereact/toast';
import { Badge } from 'primereact/badge';
import axios from 'axios';
function Home() {
//const [text, setText] = useState('');
//const toastRef = useRef();
const [isloading, setIsloading] = useState(true);
const [noticiasData,setNoticiasData] = useState([]);
useEffect(() => {
axios
.get("http://localhost:8888/api/noticias")
.then((res) => {
setNoticiasData(res.data);
setIsloading(false)
});
},[]);
return ( <
div className = "Home" >
<
header className = "App-header" >
<Badge value = "Divulgador de México" > </Badge> <br/ >
{isloading ? (
<p>No hay registros</p>
) : (
<section className="cards-wrapper">
{noticiasData.map((data) => (
<Fieldset legend={data.nk.tituloNoticia} key="index">
<Badge value={data.tipoNoticia+" ("+data.nk.fechaPublicacion+")"}> </Badge>
<p>{data.descripcion}</p>
<img alt={data.evidencia.ek.idArchivo} src={"https://drive.google.com/thumbnail?id="+data.evidencia.ek.idArchivo} />
<br/> <Badge value={data.comunicador}> </Badge>
</Fieldset>
))}
</section>
)}
</header >
</div>
);
}
export default Home;
Para la hemeroteca tenemos que realizar una validación que nos acepte todas las fechas anteriores al día actual tenemos que instalar la librería Moment.js para poder restar un día a la fecha actual y realizar la validación instalamos con el siguiente comando
npm install moment --save
Creamos el archivo Hemeroteca.jsx
import './App.css';
import React,{ useState, useEffect }from 'react'
import { Badge } from 'primereact/badge';
import { Calendar } from 'primereact/calendar';
import { addLocale } from 'primereact/api';
import { Fieldset } from 'primereact/fieldset';
import { Image } from 'primereact/image';
import axios from 'axios';
import moment from"moment";
function Hemeroteca() {
const [isloading, setIsloading] = useState(true);
const [diaAnterior,setDiaAnterior]=useState(moment(new Date()).subtract(1,"days"));
const [searchInput, setSearchInput] = useState(null);
const [noticiasData,setNoticiasData] = useState([]);
const [diaPosterior,setDiaPosterior]=useState(moment(new Date()));
useEffect(() => {
const fecha=moment(searchInput).format('yyyy-MM-DD HH:mm:ss');
axios
.get("http://localhost:8888/api/noticiash/"+fecha)
.then((res) => {
setNoticiasData(res.data);
setIsloading(false)
});
},[searchInput]);
addLocale('es', {
firstDayOfWeek: 1,
dayNames: ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'],
dayNamesShort: ['dom', 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb'],
dayNamesMin: ['D', 'L', 'M', 'X', 'J', 'V', 'S'],
monthNames: ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'],
monthNamesShort: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'],
today: 'Hoy',
clear: 'Limpiar'
})
return (
<><
header className="App-header">
<div className="card">
<Badge value="Bienvenido a la Hemeroteca Digital"> </Badge>
<br /><br />
<Calendar id="search" maxDate={diaAnterior.toDate()} value={searchInput}
placeholder='Seleccionar Día' locale="es" onChange={(e) => {
setSearchInput(e.target.value);
} } dateFormat="mm-dd-yy" ></Calendar>
{isloading ? (
<p>loading...</p>
) : (
<section className="cards-wrapper">
{noticiasData.map((data) => (
<Fieldset legend={data.nk.tituloNoticia} key="index">
<Badge value={data.tipoNoticia+" ("+data.nk.fechaPublicacion+")"}> </Badge>
<p>{data.descripcion}</p>
<img alt={data.evidencia.ek.idArchivo} src={"https://drive.google.com/thumbnail?id="+data.evidencia.ek.idArchivo} />
<br/> <Badge value={data.comunicador}> </Badge>
</Fieldset>
))}
</section>
)}
</div>
</header></>
);
}
export default Hemeroteca;
Instalamos react-final-form para formulario de login
npm install --save final-form react-final-form
Creamos el archivo Admin.jsx
import './App.css';
import React,{ useState } from 'react';
import { Form, Field } from 'react-final-form';
import { Button } from 'primereact/button';
import { Dialog } from 'primereact/dialog';
import { InputText } from 'primereact/inputtext';
import { classNames } from 'primereact/utils';
import { Password } from 'primereact/password';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
function Admin() {
const [showMessage, setShowMessage] = useState(false);
const [showMessage2, setShowMessage2] = useState(false);
const [formData, setFormData] = useState({});
const navigate = useNavigate();
const validate = (data) => {
let errors = {};
if (!data.email) {
errors.email = 'Email es requerido.';
}
else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(data.email)) {
errors.email = 'email inválido. E.j. example@email.com';
}
if (!data.password) {
errors.password = 'Password es requerido.';
}
return errors;
};
const onSubmit = (data, form) => {
setFormData(data);
try{
axios.get('http://localhost:8888/api/usuario/'+data.email)
.then((res)=>{
if(res.data.password === data.password) {
//setShowMessage(true);
navigate('/dashboard', {
replace: true,
state: {
logged: true,
nombre: res.data.nombre
},
});
}else{
setShowMessage2(true);
}
} , fail => {
console.error(fail); // Error!
setShowMessage2(true);
});
}
catch (err) {
setShowMessage2(true);
}
form.restart();
}
const isFormFieldValid = (meta) => !!(meta.touched && meta.error);
const getFormErrorMessage = (meta) => {
return isFormFieldValid(meta) && <small className="p-error">{meta.error}</small>;
};
const dialogFooter = <div className="flex justify-content-center"><Button label="OK" className="p-button-text" autoFocus onClick={() => setShowMessage(false) } /></div>;
const dialogFooter2 = <div className="flex justify-content-center"><Button label="OK" className="p-button-text" autoFocus onClick={() => setShowMessage2(false) } /></div>;
return (
<
header className = "App-header" >
<
h1 > Bienvenido favor de identificarse </h1>
<div className="form-demo">
<Dialog visible={showMessage} onHide={() => setShowMessage(false)} position="top" footer={dialogFooter} showHeader={false} breakpoints={{ '960px': '80vw' }} style={{ width: '30vw' }}>
<div className="flex align-items-center flex-column pt-6 px-3">
<i className="pi pi-check-circle" style={{ fontSize: '5rem', color: 'var(--green-500)' }}></i>
<h5>Bienvenido</h5>
<p style={{ lineHeight: 1.5, textIndent: '1rem' }}>
Has sido registrado correctamnete Bienvenido <b>{formData.email}</b>.
</p>
</div>
</Dialog>
<Dialog visible={showMessage2} onHide={() => setShowMessage2(false)} position="top" footer={dialogFooter2} showHeader={false} breakpoints={{ '960px': '80vw' }} style={{ width: '30vw' }}>
<div className="flex align-items-center flex-column pt-6 px-3">
<i className="pi pi-check-circle" style={{ fontSize: '5rem', color: 'var(--green-500)' }}></i>
<h5>Lo sentimos</h5>
<p style={{ lineHeight: 1.5, textIndent: '1rem' }}>
Tu password o usuario es incorrecto.
</p>
</div>
</Dialog>
<div className="flex justify-content-center">
<div className="card">
<h5 className="text-center">Iniciar sesión</h5>
<Form onSubmit={onSubmit} initialValues={{ email: '', password: '' }} validate={validate} render={({ handleSubmit }) => (
<form onSubmit={handleSubmit} className="p-fluid">
<Field name="email" render={({ input, meta }) => (
<div className="field">
<span className="p-float-label p-input-icon-right">
<i className="pi pi-envelope" />
<InputText id="email" {...input} className={classNames({ 'p-invalid': isFormFieldValid(meta) })} />
<label htmlFor="email" className={classNames({ 'p-error': isFormFieldValid(meta) })}>Email*</label>
</span>
{getFormErrorMessage(meta)}
</div>
)} />
<Field name="password" render={({ input, meta }) => (
<div className="field">
<span className="p-float-label">
<Password id="password" {...input} toggleMask className={classNames({ 'p-invalid': isFormFieldValid(meta) })} feedback={false} />
<label htmlFor="password" className={classNames({ 'p-error': isFormFieldValid(meta) })}>Password*</label>
</span>
{getFormErrorMessage(meta)}
</div>
)} />
<Button type="submit" label="Submit" className="mt-2" />
</form>
)} />
</div>
</div>
</div>
</header>
);
}
export default Admin;
Creamos el archivo PrivateRoute.jsx Este para validar el dashboard.
import { Navigate, useLocation } from 'react-router-dom';
function PrivateRoute ({ children }) {
const { state } = useLocation();
return state?.logged ? children : <Navigate to='/Admin' />;
};
export default PrivateRoute;
Creamos el archivo Menu2.jsx
import './App.css';
import React from 'react'
import { BreadCrumb } from 'primereact/breadcrumb';
import { Outlet,useNavigate } from "react-router-dom";
import { useLocation } from 'react-router-dom';
import "primereact/resources/themes/lara-light-indigo/theme.css";
import "primereact/resources/primereact.min.css";
import "primeicons/primeicons.css";
function Menu2() {
const { state } = useLocation();
const navigate = useNavigate()
const items2= [
{ label: 'Home', command: () => { navigate('/',{replace:true,}) } },
{ label: 'Hemeroteca', command: () => { navigate('/Hemeroteca',{replace:true,}) } },
{ label: 'Registrar Comunicador', command: () => { navigate('/Registrar',{
replace: true,
state: {
nombre:state?.nombre,
apellidoPaterno:state?.apellidoPaterno,
logged: true
}})}},
{ label: 'Ver Comunicadores', command: () => { navigate('/Comunicadores',{
replace: true,
state: {
nombre:state?.nombre,
apellidoPaterno:state?.apellidoPaterno,
logged: true
}})}},
{ label:'Agregar Noticia', command: () => { navigate('/Noticias',{
replace: true,
state: {
nombre:state?.nombre,
apellidoPaterno:state?.apellidoPaterno,
logged: true
}})}},
{ label: 'Salir', command: () => { navigate('/Admin',{replace:true,}) } }
];
return (
<>
<
div className = "Admin" >
<
BreadCrumb model = { items2 }
/>
<
/div>
<Outlet/>
</>
);
}
export default Menu2;
Creamos el archivo Dashboard.jsx
import './App.css';
import React from 'react'
import { useLocation } from 'react-router-dom';
import { Outlet} from "react-router-dom";
import Menu2 from "./Menu2"
function Dashboard() {
const { state } = useLocation();
return (
<>
<Menu2/>
<h1> Bienvenido { state?.nombre} < /h1>
<Outlet/>
</>
);
}
export default Dashboard;
Creamos el archivo Registrar.jsx
import './App.css';
import React,{ useState } from 'react'
import { Outlet } from "react-router-dom";
import { Form, Field } from 'react-final-form';
import { InputText } from 'primereact/inputtext';
import { Button } from 'primereact/button';
import { Password } from 'primereact/password';
import { Dialog } from 'primereact/dialog';
import { Divider } from 'primereact/divider';
import { classNames } from 'primereact/utils';
import axios from 'axios';
import Menu2 from "./Menu2"
function Registrar() {
const [showMessage, setShowMessage] = useState(false);
const [formData, setFormData] = useState({});
const validate = (data) => {
let errors = {};
if (!data.nombre) {
errors.name = 'El nombre es requerido.';
}
if (!data.apellidoPaterno) {
errors.apellidoPaterno = 'El apellido paterno es requerido';
}
if (!data.email) {
errors.email = 'El email es requerido.';
}
else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(data.email)) {
errors.email = 'Email inválido. ejemplo ejemplo@email.com';
}
if (!data.password) {
errors.password = 'El password es requerido.';
}
return errors;
};
const onSubmit = (data, form) => {
axios.post('http://localhost:8888/api/usuario',data)
.then((response) => {
setFormData(response.data);
setShowMessage(true);
});
form.restart();
};
const isFormFieldValid = (meta) => !!(meta.touched && meta.error);
const getFormErrorMessage = (meta) => {
return isFormFieldValid(meta) && <small className="p-error">{meta.error}</small>;
};
const dialogFooter = <div className="flex justify-content-center"><Button label="OK" className="p-button-text" autoFocus onClick={() => setShowMessage(false) } /></div>;
const passwordHeader = <h6>Generar password</h6>;
const passwordFooter = (
<React.Fragment>
<Divider />
<p className="mt-2">Sugerencias</p>
<ul className="pl-2 ml-2 mt-0" style={{ lineHeight: '1.5' }}>
<li>Una letra minúscula</li>
<li>Una letra mayúscula</li>
<li>dígitos</li>
<li>Minímo 8 carácteres</li>
</ul>
</React.Fragment>
);
return (
<>
<
header className = "App-header" >
<Menu2/>
<div className="form-demo">
<Dialog visible={showMessage} onHide={() => setShowMessage(false)} position="top" footer={dialogFooter} showHeader={false} breakpoints={{ '960px': '80vw' }} style={{ width: '30vw' }}>
<div className="flex align-items-center flex-column pt-6 px-3">
<i className="pi pi-check-circle" style={{ fontSize: '5rem', color: 'var(--green-500)' }}></i>
<h5>Registrado correctamente</h5>
<p style={{ lineHeight: 1.5, textIndent: '1rem' }}>
Tu cuenta ha sido registrada <b>{formData.nombre}</b>
</p>
</div>
</Dialog>
<div className="flex justify-content-center">
<div className="card">
<h5 className="text-center">Registrar Comunicador</h5>
<Form onSubmit={onSubmit} initialValues={{ nombre: '',apellidoPaterno:'',apellidoMaterno:'', email: '', password: '' }} validate={validate} render={({ handleSubmit }) => (
<form onSubmit={handleSubmit} className="p-fluid">
<Field name="nombre" render={({ input, meta }) => (
<div className="field">
<span className="p-float-label">
<InputText id="nombre" {...input} autoFocus className={classNames({ 'p-invalid': isFormFieldValid(meta) })} />
<label htmlFor="nombre" className={classNames({ 'p-error': isFormFieldValid(meta) })}>Nombre*</label>
</span>
{getFormErrorMessage(meta)}
</div>
)} />
<Field name="apellidoPaterno" render={({ input, meta }) => (
<div className="field">
<span className="p-float-label">
<InputText id="apellidoPaterno" {...input} autoFocus className={classNames({ 'p-invalid': isFormFieldValid(meta) })} />
<label htmlFor="apellidoPaterno" className={classNames({ 'p-error': isFormFieldValid(meta) })}>Apellido Paterno*</label>
</span>
{getFormErrorMessage(meta)}
</div>
)} />
<Field name="apellidoMaterno" render={({ input, meta }) => (
<div className="field">
<span className="p-float-label">
<InputText id="apellidoMaterno" {...input} autoFocus className={classNames({ 'p-invalid': isFormFieldValid(meta) })} />
<label htmlFor="apellidoMaterno" className={classNames({ 'p-error': isFormFieldValid(meta) })}>Apellido Materno</label>
</span>
</div>
)} />
<Field name="email" render={({ input, meta }) => (
<div className="field">
<span className="p-float-label p-input-icon-right">
<i className="pi pi-envelope" />
<InputText id="email" {...input} className={classNames({ 'p-invalid': isFormFieldValid(meta) })} />
<label htmlFor="email" className={classNames({ 'p-error': isFormFieldValid(meta) })}>Email*</label>
</span>
{getFormErrorMessage(meta)}
</div>
)} />
<Field name="password" render={({ input, meta }) => (
<div className="field">
<span className="p-float-label">
<Password id="password" {...input} toggleMask className={classNames({ 'p-invalid': isFormFieldValid(meta) })} header={passwordHeader} footer={passwordFooter} />
<label htmlFor="password" className={classNames({ 'p-error': isFormFieldValid(meta) })}>Password*</label>
</span>
{getFormErrorMessage(meta)}
</div>
)} />
<Button type="submit" label="Registrar" className="mt-2" />
</form>
)} />
</div>
</div>
</div>
</header>
<Outlet/>
</>
);
}
export default Registrar;