Bladeren bron

Login state preliminar y pagina de login

wilitp 4 jaren geleden
bovenliggende
commit
4080e82ff4

+ 6 - 46
app/package-lock.json

@@ -4127,15 +4127,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/bindings": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
-      "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
-      "optional": true,
-      "dependencies": {
-        "file-uri-to-path": "1.0.0"
-      }
-    },
     "node_modules/bluebird": {
       "version": "3.7.2",
       "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@@ -7725,12 +7716,6 @@
         "url": "https://opencollective.com/webpack"
       }
     },
-    "node_modules/file-uri-to-path": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
-      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
-      "optional": true
-    },
     "node_modules/filesize": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz",
@@ -7913,9 +7898,9 @@
       }
     },
     "node_modules/follow-redirects": {
-      "version": "1.13.2",
-      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz",
-      "integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA==",
+      "version": "1.14.4",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz",
+      "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==",
       "funding": [
         {
           "type": "individual",
@@ -11642,12 +11627,6 @@
       "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
       "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE="
     },
-    "node_modules/nan": {
-      "version": "2.15.0",
-      "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
-      "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
-      "optional": true
-    },
     "node_modules/nanoid": {
       "version": "3.1.20",
       "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz",
@@ -23213,14 +23192,6 @@
       "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
       "optional": true
     },
-    "bindings": {
-      "version": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
-      "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
-      "optional": true,
-      "requires": {
-        "file-uri-to-path": "1.0.0"
-      }
-    },
     "bluebird": {
       "version": "3.7.2",
       "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@@ -26078,12 +26049,6 @@
         }
       }
     },
-    "file-uri-to-path": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
-      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
-      "optional": true
-    },
     "filesize": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz",
@@ -26238,9 +26203,9 @@
       }
     },
     "follow-redirects": {
-      "version": "1.13.2",
-      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz",
-      "integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA=="
+      "version": "1.14.4",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz",
+      "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g=="
     },
     "for-in": {
       "version": "1.0.2",
@@ -29126,11 +29091,6 @@
       "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
       "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE="
     },
-    "nan": {
-      "version": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
-      "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
-      "optional": true
-    },
     "nanoid": {
       "version": "3.1.20",
       "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz",

+ 9 - 8
app/src/App.tsx

@@ -1,20 +1,21 @@
 import React from "react";
 import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
-import Layout from "./components/layout";
 import Home from "./components/pages/Home";
+import Login from "./components/pages/Login";
 import AuthProvider from "./context/auth/AuthProvider";
 
 function App() {
   return (
     <Router>
       <AuthProvider>
-        <Layout>
-          <Switch>
-            <Route exact path="/">
-              <Home />
-            </Route>
-          </Switch>
-        </Layout>
+        <Switch>
+          <Route exact path="/">
+            <Home />
+          </Route>
+          <Route exact path="/login">
+            <Login />
+          </Route>
+        </Switch>
       </AuthProvider>
     </Router>
   );

+ 30 - 20
app/src/components/pages/Home.tsx

@@ -1,19 +1,28 @@
-import React, { ChangeEvent, FC } from "react";
+import React, { useContext, ChangeEvent, FC } from "react";
 import Select from "../UI/forms/select";
 import DegreeDay from "../data/DegreeDay";
 import TemperaturePerSeason from "../data/TemperaturePerSeason";
 import TemperaturePerSector from "../data/TemperaturePerSector";
 import Precipitation from "../data/Precipitation";
 import CalendarInput from "../UI/forms/calendarInput";
+import { UserStateContext } from "../../context/auth/AuthProvider";
+import { Redirect } from "react-router-dom";
+import Layout from "../layout";
 
 const Home: FC = () => {
   let fincaList: string[] | number[] = ["a", "b", "c", "d"];
   let campaignList: string[] | number[] = ["2018", "2019", "2020", "2021"];
 
+  const userState = useContext(UserStateContext);
+
+  if (!userState.loggedIn) {
+    return <Redirect to="/login" />;
+  }
+
   return (
     <>
-      <section className="row p-lg-4 p-md-3 p-2">
-
+      <Layout>
+        <section className="row p-lg-4 p-md-3 p-2">
           <div className="col-12 col-lg-4 mb-2 col-xl-3 mb-xl-0">
             <Select
               list={fincaList}
@@ -56,24 +65,25 @@ const Home: FC = () => {
               Aplicar
             </button>
           </div>
-      </section>
+        </section>
 
-      {/* <section className="row">
-        <div className="col-xl-6">
-          <TemperaturePerSeason />
-          <DegreeDay
-            title={"Vista flores"}
-            periodString={"1ro Octubre - 31 Marzo"}
-          />
-          <Precipitation
-            title={"Maipú"}
-            periodString={"1ro Octubre - 31 Marzo"}
-          />
-        </div>
-        <div className="col-xl-6">
-          <TemperaturePerSector />
-        </div>
-      </section> */}
+        {/* <section className="row"> */}
+        {/*   <div className="col-xl-6"> */}
+        {/*     <TemperaturePerSeason /> */}
+        {/*     <DegreeDay */}
+        {/*       title={"Vista flores"} */}
+        {/*       periodString={"1ro Octubre - 31 Marzo"} */}
+        {/*     /> */}
+        {/*     <Precipitation */}
+        {/*       title={"Maipú"} */}
+        {/*       periodString={"1ro Octubre - 31 Marzo"} */}
+        {/*     /> */}
+        {/*   </div> */}
+        {/*   <div className="col-xl-6"> */}
+        {/*     <TemperaturePerSector /> */}
+        {/*   </div> */}
+        {/* </section> */}
+      </Layout>
     </>
   );
 };

+ 5 - 0
app/src/components/pages/Login.module.css

@@ -0,0 +1,5 @@
+.loginForm {
+  width: 100%;
+  max-width: 400px;
+  padding: 0 5px;
+}

+ 70 - 0
app/src/components/pages/Login.tsx

@@ -0,0 +1,70 @@
+import React, {
+  ChangeEvent,
+  useContext,
+  FormEventHandler,
+  useState,
+} from "react";
+import {
+  UserDispatchContext,
+  UserStateContext,
+} from "../../context/auth/AuthProvider";
+import { Redirect } from "react-router-dom";
+import * as classes from "./Login.module.css";
+import { loginRequest } from "../../context/auth/actions";
+
+const Login = () => {
+  const [formState, setFormState] = useState<any>({});
+  const dispatch = useContext(UserDispatchContext);
+  const userState = useContext(UserStateContext);
+
+  const handleFormChange = (e: ChangeEvent) => {
+    const el = e.target as HTMLInputElement;
+    const name = el.getAttribute("name") as string;
+    const value = el.value;
+    setFormState({
+      ...formState,
+      [name]: value,
+    });
+  };
+
+  const handleFormSubmit: FormEventHandler = (e) => {
+    e.preventDefault();
+    dispatch(loginRequest(formState.username, formState.password));
+  };
+
+  if (userState.loggedIn) {
+    return <Redirect to="/" />;
+  }
+
+  return (
+    <main className="d-flex justify-content-center align-items-center min-vh-100">
+      <form
+        onSubmit={handleFormSubmit}
+        className={`text-center d-flex flex-column gap-2  ${classes.loginForm}`}
+      >
+        <h3>Ingresar</h3>
+        <input
+          onChange={handleFormChange}
+          id="username"
+          className="form-control"
+          type="text"
+          name="username"
+          placeholder="Usuario"
+        />
+        <input
+          onChange={handleFormChange}
+          id="password"
+          className="form-control"
+          type="password"
+          name="password"
+          placeholder="Contraseña"
+        />
+        <button className="btn btn-primary" type="submit">
+          Continuar
+        </button>
+      </form>
+    </main>
+  );
+};
+
+export default Login;

+ 38 - 0
app/src/context/auth/AuthProvider.tsx

@@ -0,0 +1,38 @@
+import React, { createContext, useReducer, Dispatch, FC } from "react";
+import reducer, { State } from "./reducer";
+import { Action } from "./actionTypes";
+
+const UserProvider: FC = ({ children }) => {
+  const [state, dispatch] = useReducer(reducer, {});
+
+  return (
+    <UserStateContext.Provider value={state}>
+      <UserDispatchContext.Provider value={asyncDispatchWrap(dispatch)}>
+        {children}
+      </UserDispatchContext.Provider>
+    </UserStateContext.Provider>
+  );
+};
+
+type AsyncDispatch = (
+  action: Action | ((...args: any) => Promise<any>)
+) => void;
+
+function asyncDispatchWrap(dispatch: Dispatch<Action>) {
+  const asyncDispatch: AsyncDispatch = (action) => {
+    if (action instanceof Function) {
+      action(dispatch);
+      return;
+    }
+    dispatch(action);
+  };
+
+  return asyncDispatch;
+}
+
+export const UserStateContext = createContext<State>({});
+export const UserDispatchContext = createContext<
+  (action: Action | ((...args: any) => Promise<any>)) => void
+>(asyncDispatchWrap((dispatch) => {}));
+
+export default UserProvider;

+ 19 - 0
app/src/context/auth/actionTypes.ts

@@ -0,0 +1,19 @@
+// Idealmente estas constantes NO deberían usarse.
+// Deberíamos preferir usar los tipos de abajo.
+export const LOGIN_REQUEST = "LOGIN_REQUEST";
+export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
+export const LOGIN_FAILURE = "LOGIN_FAILURE";
+export const LOGOUT = "LOGOUT";
+
+export type ActionType =
+  | "LOGIN_REQUEST"
+  | "LOGIN_SUCCESS"
+  | "LOGIN_FAILURE"
+  | "LOGOUT";
+
+export type Action = {
+  type: ActionType;
+  username?: string;
+  userToken?: string;
+  error?: number;
+};

+ 31 - 0
app/src/context/auth/actions.ts

@@ -0,0 +1,31 @@
+import { Dispatch } from "react";
+import { Action } from "./actionTypes";
+
+export const loginRequest = (username: string, password: string) => async (
+  dispatch: Dispatch<Action>
+) => {
+  // Request
+  const res = await fetch(`${process.env.BACKEND_BASE_URL}/login`, {});
+
+  // TODO: get user token
+  const userToken = "";
+
+  // Handle request
+  if (res.ok) {
+    dispatch(loginSuccess(username, userToken));
+    return;
+  }
+
+  const code = res.status;
+  dispatch(loginFailure(code));
+};
+
+export const loginSuccess = (username: string, userToken: string): Action => ({
+  type: "LOGIN_SUCCESS",
+  username,
+  userToken,
+});
+export const loginFailure = (code: number): Action => ({
+  type: "LOGIN_FAILURE",
+  error: code,
+});

+ 46 - 0
app/src/context/auth/reducer.ts

@@ -0,0 +1,46 @@
+import { Reducer } from "react";
+import { Action } from "./actionTypes";
+
+export const defaultState = {
+  loggingIn: false,
+  loggedIn: false,
+  userToken: null,
+  username: null,
+};
+
+export type State = {
+  loggingIn?: Boolean;
+  loggedIn?: Boolean;
+  userToken?: string;
+  username?: string;
+  error?: number;
+};
+
+const reducer: Reducer<State, Action> = (state, action) => {
+  switch (action.type) {
+    case "LOGIN_REQUEST":
+      return {
+        ...state,
+        loggingIn: true,
+        loggedIn: false,
+        username: action.username,
+      };
+    case "LOGIN_SUCCESS":
+      return {
+        loggedIn: true,
+        loggingIn: false,
+        username: action.username,
+        userToken: action.userToken,
+      };
+    case "LOGIN_FAILURE":
+      return {
+        error: action.error,
+      };
+    case "LOGOUT":
+      return {};
+    default:
+      return state;
+  }
+};
+
+export default reducer;

+ 3 - 8
app/yarn.lock

@@ -5002,11 +5002,6 @@
     "loader-utils" "^2.0.0"
     "schema-utils" "^3.0.0"
 
-"file-uri-to-path@1.0.0":
-  "integrity" "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
-  "resolved" "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz"
-  "version" "1.0.0"
-
 "filesize@6.1.0":
   "integrity" "sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg=="
   "resolved" "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz"
@@ -5116,9 +5111,9 @@
     "readable-stream" "^2.3.6"
 
 "follow-redirects@^1.0.0":
-  "integrity" "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA=="
-  "resolved" "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz"
-  "version" "1.13.2"
+  "integrity" "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g=="
+  "resolved" "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz"
+  "version" "1.14.4"
 
 "for-in@^1.0.2":
   "integrity" "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA="