@chimera-pe/react-saas 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,31 +0,0 @@
1
- import {Box,Alert,AlertTitle} from "@mui/material";
2
- import {useTranslate} from "react-polyglot";
3
- import PropTypes from "prop-types";
4
-
5
- const Error = ({titulo,texto,align = "center",severity = "error"}) => {
6
- const translate = useTranslate();
7
-
8
- return (
9
- <Box sx={{
10
- display: "flex",
11
- flexDirection: "column",
12
- flexGrow: 1,
13
- justifyContent: "center",
14
- alignItems: align
15
- }}>
16
- <Alert severity={severity}>
17
- <AlertTitle>{translate(titulo)}</AlertTitle>
18
- {texto && translate(texto)}
19
- </Alert>
20
- </Box>
21
- );
22
- };
23
-
24
- Error.propTypes={
25
- titulo: PropTypes.string.isRequired,
26
- texto: PropTypes.string,
27
- align: PropTypes.string,
28
- severity: PropTypes.string
29
- };
30
-
31
- export default Error;
@@ -1,48 +0,0 @@
1
- import {useEffect} from "react";
2
- import {useDispatch,useSelector} from "react-redux";
3
- import {I18n} from "react-polyglot";
4
- import {enGB,es} from "date-fns/locale"
5
- import {LocalizationProvider} from "@mui/x-date-pickers";
6
- import {AdapterDateFns} from "@mui/x-date-pickers/AdapterDateFns";
7
- import {cambiarIdioma} from "../redux";
8
- import saasMessages from "../i18n";
9
-
10
- const supportedLocales={en: enGB,es};
11
-
12
- const IdiomaInner=({messages,children}) => {
13
- const idioma=useSelector(store => store.ui.idioma);
14
-
15
- return (
16
- <I18n locale={idioma} messages={messages[idioma]}>
17
- <LocalizationProvider dateAdapter={AdapterDateFns} adapterLocale={supportedLocales[idioma]}>
18
- {children}
19
- </LocalizationProvider>
20
- </I18n>
21
- );
22
- };
23
-
24
- const Idioma=({messages,idioma,children}) => {
25
- const dispatch=useDispatch();
26
-
27
- useEffect(() => {
28
- if(idioma){
29
- dispatch(cambiarIdioma(idioma));
30
- }
31
- },[dispatch,idioma]);
32
-
33
- const m={};
34
- Object.keys(messages).forEach(key => {
35
- m[key]={
36
- ...messages[key],
37
- saas: saasMessages[key]
38
- };
39
- });
40
-
41
- return (
42
- <IdiomaInner messages={m}>
43
- {children}
44
- </IdiomaInner>
45
- );
46
- };
47
-
48
- export default Idioma;
@@ -1,64 +0,0 @@
1
- import {useEffect} from "react";
2
- import {Box} from "@mui/material";
3
- import {useDispatch,useSelector} from "react-redux";
4
- import {inicializar} from "../redux";
5
- import Cargando from "./Cargando";
6
- import Error from "./Error";
7
- import Notificacion from "./Notificacion";
8
- import Idioma from "./Idioma";
9
- import Tema from "./Tema";
10
- import MainRouter from "./MainRouter";
11
-
12
- const InicializarInner = ({devURL,children}) => {
13
- const aplicacion = useSelector(store => store.aplicacion);
14
-
15
- return (
16
- <Box sx={{
17
- display: "flex",
18
- flexDirection: "column",
19
- minHeight: "100vh",
20
- justifyContent: "flex-start",
21
- backgroundColor: "background.default"
22
- }}>
23
- {aplicacion.inicializando ?
24
- <Cargando />
25
- : (aplicacion.error || !aplicacion.inicializado) ?
26
- <Error titulo={"saas.inicializar.error.titulo"} texto={"saas.inicializar.error.mensaje"} />
27
- :
28
- <>
29
- <MainRouter devURL={devURL} requiereLogin={aplicacion.instancia.requiereLogin}>
30
- {children}
31
- </MainRouter>
32
- <Notificacion />
33
- </>
34
- }
35
- </Box>
36
- );
37
- };
38
-
39
- const Inicializar = ({
40
- aplicacion,
41
- devSaasURL,
42
- devAuthURL,
43
- messages,
44
- idioma,
45
- children
46
- }) => {
47
- const dispatch = useDispatch();
48
-
49
- useEffect(() => {
50
- dispatch(inicializar({devURL: devSaasURL,aplicacion: aplicacion}));
51
- },[dispatch,aplicacion,devSaasURL]);
52
-
53
- return (
54
- <Idioma messages={messages} idioma={idioma}>
55
- <Tema>
56
- <InicializarInner devURL={devAuthURL}>
57
- {children}
58
- </InicializarInner>
59
- </Tema>
60
- </Idioma>
61
- );
62
- };
63
-
64
- export default Inicializar;
@@ -1,202 +0,0 @@
1
- import {useEffect} from "react";
2
- import {useSelector,useDispatch} from "react-redux";
3
- import {Navigate,useLocation} from "react-router-dom";
4
- import {
5
- Box,
6
- Container,
7
- Grid,
8
- Typography,
9
- Card,
10
- CardHeader,
11
- CardContent,
12
- InputAdornment,
13
- Button,
14
- CircularProgress,
15
- AppBar,
16
- Toolbar,
17
- Link
18
- } from "@mui/material";
19
- import {Email,Lock} from "@mui/icons-material";
20
- import {Form} from "react-final-form";
21
- import {TextField} from "mui-rff";
22
- import {useTranslate} from "react-polyglot";
23
- import {requestToken} from "../redux";
24
- import {useCheckLogin,useNotificar} from "../hooks";
25
-
26
- const FormularioLogin = ({devURL}) => {
27
- const dispatch = useDispatch();
28
- const translate = useTranslate();
29
- const notificar = useNotificar();
30
- const {cargando,error} = useSelector(store => store.login);
31
- const instancia = useSelector(store => store.aplicacion.instancia);
32
-
33
- const submit = values => {
34
- dispatch(requestToken({
35
- devURL: devURL,
36
- clientCredentials: instancia.clientCredentials,
37
- data: {
38
- correo: values.correo,
39
- password: values.password
40
- }
41
- }));
42
- };
43
-
44
- const validate = values => {
45
- const errors = {correo: undefined,password: undefined};
46
-
47
- if(!values.correo) {
48
- errors.correo = translate("saas.login.validacion.correo");
49
- }
50
- if(!values.password) {
51
- errors.password = translate("saas.login.validacion.password");
52
- }
53
- return errors;
54
- };
55
-
56
- useEffect(() => {
57
- if(error) {
58
- notificar("saas.login.error","error");
59
- }
60
- },[notificar,error]);
61
-
62
- return (
63
- <Form
64
- onSubmit={submit}
65
- validate={validate}
66
- render={({handleSubmit}) => (
67
- <form onSubmit={handleSubmit}>
68
- <TextField
69
- id="correo"
70
- name="correo"
71
- label={translate("saas.login.correo")}
72
- variant="outlined"
73
- autoComplete="off"
74
- disabled={cargando}
75
- autoFocus
76
- InputProps={{
77
- startAdornment: (
78
- <InputAdornment position="start">
79
- <Email color="primary" />
80
- </InputAdornment>
81
- )
82
- }}
83
- />
84
- <TextField
85
- id="password"
86
- name="password"
87
- type="password"
88
- label={translate("saas.login.password")}
89
- variant="outlined"
90
- autoComplete="current-password"
91
- disabled={cargando}
92
- InputProps={{
93
- startAdornment: (
94
- <InputAdornment position="start">
95
- <Lock color="primary" />
96
- </InputAdornment>
97
- )
98
- }}
99
- />
100
- <Grid container>
101
- <Grid item xs={6}></Grid>
102
- <Grid item xs={6} align="right">
103
- <Button
104
- variant="contained"
105
- color="primary"
106
- type="submit"
107
- disabled={cargando}
108
- >
109
- {cargando ?
110
- <CircularProgress size={24} thickness={4} />
111
- :
112
- translate("saas.login.ingresar")
113
- }
114
- </Button>
115
- </Grid>
116
- </Grid>
117
- </form>
118
- )}
119
- />
120
- );
121
- };
122
-
123
- const Login = ({devURL}) => {
124
- const instancia = useSelector(store => store.aplicacion.instancia);
125
- const translate = useTranslate();
126
- const location = useLocation();
127
- const autenticado = useCheckLogin(devURL);
128
- const {from} = location.state || {from: {pathname: "/"}};
129
-
130
- if(!instancia.requiereLogin || autenticado) {
131
- return <Navigate to={from} />;
132
- }
133
-
134
- return (
135
- <Box sx={{
136
- position: "relative",
137
- display: "flex",
138
- flexDirection: "column",
139
- flexGrow: 1,
140
- minHeight: "100vh",
141
- backgroundColor: "primary.main",
142
- "&::before": {
143
- content: '""',
144
- backgroundImage: `url(${instancia.logo})`,
145
- backgroundSize: "contain",
146
- backgroundRepeat: "no-repeat",
147
- backgroundPosition: "right",
148
- position: "absolute",
149
- top: "20%",
150
- bottom: 0,
151
- right: 0,
152
- width: "50%",
153
- opacity: 0.02,
154
- filter: "grayscale(100%)"
155
- }
156
- }}>
157
- <Box sx={{
158
- display: "flex",
159
- justifyContent: "center",
160
- alignItems: "center",
161
- flexGrow: 1
162
- }}>
163
- <Container maxWidth="md">
164
- <Grid container spacing={3}>
165
- <Grid item xs={12} lg={5} align="center">
166
- <img src={instancia.logo} alt={instancia.nombre} style={{maxWidth: "100%"}} />
167
- <Typography variant="h3" align="center">{translate("aplicacion.nombre",{smart_count: 1})}</Typography>
168
- <Typography variant="h5" align="center">{instancia.nombre}</Typography>
169
- </Grid>
170
- <Grid item xs={12} lg={7} sx={{
171
- display: "flex",
172
- alignItems: "center",
173
- justifyContent: "center",
174
- zIndex: 5
175
- }}>
176
- <Card elevation={5}>
177
- <CardHeader title={translate("saas.login.titulo")} titleTypographyProps={{align: "center"}} />
178
- <CardContent sx={{
179
- "& .MuiTextField-root": {
180
- mb: 2
181
- },
182
- }}>
183
- <FormularioLogin devURL={devURL} />
184
- </CardContent>
185
- </Card>
186
- </Grid>
187
- </Grid>
188
- </Container>
189
- </Box>
190
- <AppBar position="static" color="primary">
191
- <Toolbar sx={{justifyContent: "center"}}>
192
- <Typography variant="caption">
193
- {translate("saas.copy")}
194
- <Link href="//chimera.com.pe" color="inherit" target="_blank" rel="noreferrer">Chimera Software</Link>
195
- </Typography>
196
- </Toolbar>
197
- </AppBar>
198
- </Box>
199
- );
200
- };
201
-
202
- export default Login;
@@ -1,35 +0,0 @@
1
- import {
2
- BrowserRouter,
3
- Routes,
4
- Route,
5
- Navigate,
6
- useLocation
7
- } from "react-router-dom";
8
- import {useCheckLogin} from "../hooks";
9
- import Login from "./Login";
10
-
11
- const RequiereAuth = ({devURL,redirectTo,children}) => {
12
- const location = useLocation();
13
- const autenticado = useCheckLogin(devURL);
14
-
15
- return autenticado ? children : <Navigate to={redirectTo} state={{from: location}} replace />;
16
- };
17
-
18
- const MainRouter = ({devURL,requiereLogin,children}) => (
19
- <BrowserRouter>
20
- <Routes>
21
- <Route path="/login" element={<Login devURL={devURL} />} />
22
- <Route
23
- path="/*"
24
- element={requiereLogin ? (
25
- <RequiereAuth devURL={devURL} redirectTo="/login">
26
- {children}
27
- </RequiereAuth>
28
- ) : children
29
- }
30
- />
31
- </Routes>
32
- </BrowserRouter>
33
- );
34
-
35
- export default MainRouter;
@@ -1,36 +0,0 @@
1
- import {useEffect,useState} from "react";
2
- import {useSelector,useDispatch} from "react-redux";
3
- import {Snackbar,Alert} from "@mui/material";
4
- import {useTranslate} from "react-polyglot";
5
- import {ocultarNotificacion} from "../redux";
6
-
7
- const Notificacion=() => {
8
- const [open,setOpen]=useState(false);
9
- const dispatch=useDispatch();
10
- const translate=useTranslate();
11
- const notificacion=useSelector(store => store.notificaciones[0]);
12
-
13
- useEffect(() => {
14
- setOpen(!!notificacion);
15
- },[notificacion]);
16
-
17
- const handleClose=() => {
18
- setOpen(false);
19
- dispatch(ocultarNotificacion());
20
- };
21
-
22
- return (
23
- <Snackbar
24
- open={open}
25
- message={notificacion && notificacion.mensaje && notificacion.tipo === "default" && translate(notificacion.mensaje)}
26
- autoHideDuration={5000}
27
- onClose={handleClose}
28
- >
29
- {notificacion && notificacion.mensaje && notificacion.tipo !== "default" &&
30
- <Alert severity={notificacion.tipo}>{translate(notificacion.mensaje)}</Alert>
31
- }
32
- </Snackbar>
33
- );
34
- };
35
-
36
- export default Notificacion;
@@ -1,30 +0,0 @@
1
- import {Provider} from "react-redux";
2
- import {store} from "../redux";
3
- import Inicializar from "./Inicializar";
4
-
5
- const SaasApp = ({
6
- customReducers,
7
- aplicacion,
8
- devSaasURL,
9
- devAuthURL,
10
- dev = false,
11
- idioma,
12
- messages,
13
- children
14
- }) => {
15
- return (
16
- <Provider store={store(customReducers)}>
17
- <Inicializar
18
- aplicacion={aplicacion}
19
- devSaasURL={dev && devSaasURL}
20
- devAuthURL={dev && devAuthURL}
21
- idioma={idioma}
22
- messages={messages}
23
- >
24
- {children}
25
- </Inicializar>
26
- </Provider>
27
- );
28
- };
29
-
30
- export default SaasApp;
@@ -1,59 +0,0 @@
1
- import {useMemo,useEffect} from "react";
2
- import {useDispatch,useSelector} from "react-redux";
3
- import {CssBaseline,useMediaQuery} from "@mui/material";
4
- import {ThemeProvider,createTheme} from "@mui/material/styles";
5
- import {grey} from "@mui/material/colors";
6
- import {cambiarTema} from "../redux";
7
-
8
- const Tema=({children}) => {
9
- const dispatch=useDispatch();
10
- const {instancia}=useSelector(store => store.aplicacion);
11
- const tema=useSelector(store => store.ui.tema);
12
- const prefersDarkMode=useMediaQuery("(prefers-color-scheme: dark)");
13
-
14
- useEffect(() => {
15
- dispatch(cambiarTema(prefersDarkMode ? "dark" : "light"));
16
- },[dispatch,prefersDarkMode]);
17
-
18
- const theme=useMemo(() => createTheme({
19
- palette: {
20
- mode: tema,
21
- primary: {
22
- main: instancia.color.primary
23
- },
24
- secondary: {
25
- main: instancia.color.secondary
26
- },
27
- error: {
28
- main: instancia.color.error
29
- },
30
- warning: {
31
- main: instancia.color.warning
32
- },
33
- info: {
34
- main: instancia.color.info
35
- },
36
- success: {
37
- main: instancia.color.success
38
- },
39
- ...(tema === "light" && {
40
- background: {
41
- default: grey["A200"],
42
- paper: grey[100]
43
- }
44
- })
45
- },
46
- shape: {
47
- borderRadius: 6
48
- }
49
- }),[tema,instancia]);
50
-
51
- return (
52
- <ThemeProvider theme={theme}>
53
- <CssBaseline />
54
- {children}
55
- </ThemeProvider>
56
- );
57
- };
58
-
59
- export default Tema;
@@ -1,9 +0,0 @@
1
- import Cargando from "./Cargando";
2
- import Error from "./Error";
3
- import SaasApp from "./SaasApp";
4
-
5
- export {
6
- Cargando,
7
- Error,
8
- SaasApp
9
- };
@@ -1,7 +0,0 @@
1
- import useCheckLogin from "./useCheckLogin";
2
- import useNotificar from "./useNotificar";
3
-
4
- export {
5
- useCheckLogin,
6
- useNotificar
7
- };
@@ -1,24 +0,0 @@
1
- import {useEffect} from "react";
2
- import {useDispatch,useSelector} from "react-redux";
3
- import {logout, refreshToken} from "../redux";
4
-
5
- const useCheckLogin = (devURL) => {
6
- const dispatch = useDispatch();
7
- const login = useSelector(store => store.login);
8
- const instancia = useSelector(store => store.aplicacion.instancia);
9
-
10
- useEffect(() => {
11
- if(login.autenticado && !!login.expiracion && new Date(login.expiracion) < new Date()){
12
- if(login.refreshToken){
13
- dispatch(refreshToken(devURL,instancia.clientCredentials,login.refreshToken));
14
- }
15
- else{
16
- dispatch(logout());
17
- }
18
- }
19
- },[devURL,instancia.clientCredentials,login,dispatch]);
20
-
21
- return login.autenticado;
22
- };
23
-
24
- export default useCheckLogin;
@@ -1,12 +0,0 @@
1
- import {useCallback} from "react";
2
- import {useDispatch} from "react-redux";
3
- import {mostrarNotificacion} from "../redux";
4
-
5
- const useNotificar = () => {
6
- const dispatch = useDispatch();
7
- return useCallback((mensaje,tipo = "default") => {
8
- dispatch(mostrarNotificacion({mensaje,tipo}));
9
- },[dispatch]);
10
- };
11
-
12
- export default useNotificar;
package/src/i18n/es.jsx DELETED
@@ -1,22 +0,0 @@
1
- const es={
2
- inicializar: {
3
- error: {
4
- titulo: "Error iniciando aplicación",
5
- mensaje: "No fue posible conectarnos con el servicio"
6
- }
7
- },
8
- login: {
9
- titulo: "Acceder al sistema",
10
- correo: "Correo electrónico",
11
- password: "Contraseña",
12
- ingresar: "Acceder",
13
- validacion: {
14
- correo: "Debe ingresar una dirección de correo",
15
- password: "Debe ingresar una contraseña"
16
- },
17
- error: "Usuario o contraseña incorrectos"
18
- },
19
- copy: "Todos los derechos reservados "
20
- };
21
-
22
- export default es;
@@ -1,7 +0,0 @@
1
- import es from "./es";
2
-
3
- const messages={
4
- es: es
5
- };
6
-
7
- export default messages;
package/src/index.js DELETED
@@ -1,12 +0,0 @@
1
- import {SaasApp} from "./components";
2
- import {useNotificar} from "./hooks";
3
- import {Cargando,Error} from "./components";
4
- import {logout} from "./redux";
5
-
6
- export {
7
- SaasApp,
8
- useNotificar,
9
- Cargando,
10
- Error,
11
- logout
12
- };
@@ -1,17 +0,0 @@
1
- import {store} from "./store";
2
- import {inicializar} from "./inicializarSlice";
3
- import {refreshToken,requestToken,logout} from "./loginSlice";
4
- import {mostrarNotificacion,ocultarNotificacion} from "./notificacionSlice";
5
- import {cambiarIdioma,cambiarTema} from "./uiSlice";
6
-
7
- export {
8
- store,
9
- inicializar,
10
- refreshToken,
11
- requestToken,
12
- logout,
13
- mostrarNotificacion,
14
- ocultarNotificacion,
15
- cambiarIdioma,
16
- cambiarTema
17
- };
@@ -1,57 +0,0 @@
1
- import {createSlice,createAsyncThunk} from "@reduxjs/toolkit";
2
- import {identidadApi} from "../api/inicializarApi";
3
-
4
- const colorDefault = {
5
- primary: "#1C6CCC",
6
- secondary: "#17A7FF",
7
- error: "#f44336",
8
- warning: "#ff9800",
9
- info: "#2196f3",
10
- success: "#4caf50"
11
- };
12
-
13
- const inicializarSlice = createSlice({
14
- name: "inicializar",
15
- initialState: {
16
- inicializando: true,
17
- inicializado: false,
18
- instancia: {
19
- color: colorDefault
20
- },
21
- error: null
22
- },
23
- extraReducers(builder) {
24
- builder
25
- .addCase(inicializar.pending,state => {
26
- state.inicializando = true;
27
- })
28
- .addCase(inicializar.fulfilled,(state,action) => {
29
- state.inicializando = false;
30
- state.inicializado = true;
31
- state.instancia = {
32
- ...action.payload,
33
- abreviatura: action.payload.nombre.match(/\b([A-Z])/g).join(""),
34
- color: {
35
- ...colorDefault,
36
- ...action.payload.color
37
- }
38
- };
39
- state.error = null;
40
- })
41
- .addCase(inicializar.rejected,(state,action) => {
42
- state.inicializando = false;
43
- state.inicializado = false;
44
- state.instancia = {
45
- color: colorDefault
46
- };
47
- state.error = action.payload;
48
- });
49
- }
50
- });
51
-
52
- export const inicializar = createAsyncThunk("inicializar",async (payload) => {
53
- const response = await identidadApi(payload.devURL,payload.aplicacion);
54
- return response.data;
55
- });
56
-
57
- export default inicializarSlice.reducer;