firebase connected GET POST

This commit is contained in:
Tomasz Kasprowicz 2020-12-24 17:39:00 +01:00
parent 800dda5e4c
commit e4e218a910
12 changed files with 514 additions and 50 deletions

1
.eslintcache Normal file

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,7 @@ import SideDrawer from "./components/SideDrawer";
import { Switch, Route } from "react-router-dom"; import { Switch, Route } from "react-router-dom";
import Login from "./components/Login"; import Login from "./components/Login";
import CreateTask from "./components/CreateTask"; import CreateTask from "./components/CreateTask";
import TodoList from "./components/TodoList";
const StyledApp = styled.div` const StyledApp = styled.div`
height: 100vh; height: 100vh;
@ -29,6 +30,27 @@ function App() {
<Route exact path="/login"> <Route exact path="/login">
<Login /> <Login />
</Route> </Route>
<Route exact path="/all">
<TodoList />
</Route>
<Route exact path="/finished">
<TodoList />
</Route>
<Route exact path="/current">
<TodoList />
</Route>
<Route exact path="/business">
<TodoList />
</Route>
<Route exact path="/personal">
<TodoList />
</Route>
<Route exact path="/important">
<TodoList />
</Route>
<Route exact path="/Canceled">
<TodoList />
</Route>
</Switch> </Switch>
</StyledApp> </StyledApp>
); );

View File

@ -17,6 +17,12 @@ const StyledButton = styled.button`
&:hover { &:hover {
background-color: rgba(18, 18, 18, 0.6); background-color: rgba(18, 18, 18, 0.6);
} }
&:focus, &:active {
border-color: inherit;
-webkit-box-shadow: none;
box-shadow: none;
outline:none;
}
`; `;
const Button: React.FC<Props> = (props) => { const Button: React.FC<Props> = (props) => {

View File

@ -1,6 +1,9 @@
import React from "react"; import React, { ReactEventHandler, useState } from "react";
import styled from "styled-components"; import styled from "styled-components";
import "../../node_modules/bootstrap/dist/css/bootstrap.min.css"; import "../../node_modules/bootstrap/dist/css/bootstrap.min.css";
import { connect } from "react-redux";
import * as actions from "../store/actions/actions";
import { withRouter } from "react-router-dom";
const StyledTasks = styled.div` const StyledTasks = styled.div`
width: 100%; width: 100%;
@ -16,13 +19,49 @@ const StyledTasks = styled.div`
color: blue !important; color: blue !important;
cursor: pointer; cursor: pointer;
} }
textarea{ textarea {
resize:none; resize: none;
} }
} }
`; `;
const CreateTask = () => { const CreateTask = (props: any) => {
const [taskName, setTaskName] = useState<string>("");
const [taskDesc, setTaskDesc] = useState<string>("");
const [taskType, setTaskType] = useState<string>("bussiness");
const [isImportant, setIsImportant] = useState<boolean>(false);
const [date, setDate] = useState<string>("");
const addTaskHandler = () => {
const isValid = formValidator();
if (isValid) {
props.isAuth
? props.addToDo(
taskName,
taskDesc,
taskType,
isImportant,
date,
props.idToken,
props.userId
)
: props.history.push("/login");
}
};
const radioButtonsHandler = (type: string) => {
setTaskType(type);
};
const importantButtonsHandler = (e: any) => {
if (e.target.value === "true") setIsImportant(true);
};
const formValidator = () => {
if (taskName.length > 1 && taskDesc.length > 1 && date.length > 1)
return true;
};
return ( return (
<StyledTasks> <StyledTasks>
<form> <form>
@ -30,7 +69,13 @@ const CreateTask = () => {
<div className="form-group"> <div className="form-group">
<label>Task Name</label> <label>Task Name</label>
<input type="text" className="form-control" placeholder="Task Name" /> <input
type="text"
className="form-control"
placeholder="Task Name"
value={taskName}
onChange={(e) => setTaskName(e.target.value)}
/>
</div> </div>
<div className="form-group"> <div className="form-group">
@ -39,6 +84,8 @@ const CreateTask = () => {
className="form-control" className="form-control"
id="exampleFormControlTextarea1" id="exampleFormControlTextarea1"
rows={4} rows={4}
value={taskDesc}
onChange={(e) => setTaskDesc(e.target.value)}
></textarea> ></textarea>
</div> </div>
@ -51,6 +98,7 @@ const CreateTask = () => {
value="bussiness" value="bussiness"
checked checked
readOnly readOnly
onClick={() => radioButtonsHandler("bussiness")}
/> />
<label className="form-check-label" htmlFor="bussiness"> <label className="form-check-label" htmlFor="bussiness">
Bussiness Bussiness
@ -64,6 +112,7 @@ const CreateTask = () => {
id="personal" id="personal"
value="personal" value="personal"
readOnly readOnly
onClick={() => radioButtonsHandler("personal")}
/> />
<label className="form-check-label" htmlFor="personal"> <label className="form-check-label" htmlFor="personal">
Personal Personal
@ -73,20 +122,30 @@ const CreateTask = () => {
<input <input
className="form-check-input" className="form-check-input"
type="checkbox" type="checkbox"
value="" value="true"
id="important" id="important"
onChange={(e) => importantButtonsHandler(e)}
/> />
<label className="form-check-label" htmlFor="important"> <label className="form-check-label" htmlFor="important">
Important Important
</label> </label>
</div> </div>
<hr/> <hr />
<div className="form-group"> <div className="form-group">
<label>End Date</label> <label>End Date</label>
<input type="date" className="form-control" placeholder="Date" /> <input
type="date"
className="form-control"
placeholder="Date"
onChange={(e) => setDate(e.target.value)}
/>
</div> </div>
<button type="submit" className="btn btn-primary btn-block"> <button
onClick={addTaskHandler}
type="button"
className="btn btn-primary btn-block"
>
Create Task Create Task
</button> </button>
</form> </form>
@ -94,4 +153,35 @@ const CreateTask = () => {
); );
}; };
export default CreateTask; interface RootState {
isAuth: boolean;
idToken: string;
userId: string
}
const mapStateToProps = (state: RootState) => {
return {
isAuth: state.isAuth,
idToken: state.idToken,
userId: state.userId,
};
};
const mapDispatchToProps = (dispatch: any) => {
return {
addToDo: (
name: string,
desc: string,
type: string,
important: boolean,
date: string,
token: string,
userId: string,
) => dispatch(actions.addToDo(name, desc, type, important, date, token, userId)),
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(withRouter(CreateTask));

View File

@ -2,6 +2,8 @@ import React from "react";
import styled from "styled-components"; import styled from "styled-components";
import { NavLink } from "react-router-dom"; import { NavLink } from "react-router-dom";
import Button from "./Button"; import Button from "./Button";
import * as actions from "../store/actions/actions";
import { connect } from "react-redux";
const StyledHeader = styled.div` const StyledHeader = styled.div`
height: 8%; height: 8%;
@ -12,6 +14,7 @@ const StyledHeader = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
outline:none;
.menu-icon { .menu-icon {
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;
@ -32,9 +35,16 @@ const StyledHeader = styled.div`
interface Props { interface Props {
sideDrawer: () => void; sideDrawer: () => void;
isOpen: boolean; isOpen: boolean;
isAuth: boolean;
logout: () => void;
} }
const Header: React.FC<Props> = (props) => { const Header: React.FC<Props> = (props: Props) => {
const logoutHandler = ()=>{
props.isAuth && props.logout();
}
return ( return (
<StyledHeader> <StyledHeader>
<Button onClick={props.sideDrawer}> <Button onClick={props.sideDrawer}>
@ -51,10 +61,27 @@ const Header: React.FC<Props> = (props) => {
</span> </span>
<NavLink to={process.env.PUBLIC_URL + "/login"}> <NavLink to={process.env.PUBLIC_URL + "/login"}>
<Button>Login</Button> <Button onClick={logoutHandler}>{props.isAuth?'Logout': 'Login'}</Button>
</NavLink> </NavLink>
</StyledHeader> </StyledHeader>
); );
}; };
export default Header; interface RootState {
isAuth: boolean;
}
const mapStateToProps = (state: RootState) => {
return {
isAuth: state.isAuth,
};
};
const mapDispatchToProps = (dispatch: any) => {
return {
auth: (email: string, password: string, isSignupMode: boolean) => dispatch(actions.auth(email, password, isSignupMode)),
logout: () => dispatch(actions.authLogout())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Header);

View File

@ -1,6 +1,9 @@
import React, { useState } from "react"; import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import styled from "styled-components"; import styled from "styled-components";
import "../../node_modules/bootstrap/dist/css/bootstrap.min.css"; import "../../node_modules/bootstrap/dist/css/bootstrap.min.css";
import * as actions from "../store/actions/actions";
import { Redirect } from "react-router-dom";
const StyledForm = styled.div` const StyledForm = styled.div`
width: 100%; width: 100%;
@ -12,26 +15,24 @@ const StyledForm = styled.div`
width: 100%; width: 100%;
max-width: 40rem; max-width: 40rem;
padding: 2rem; padding: 2rem;
a { .login-button {
color: blue !important; color: blue !important;
cursor: pointer; cursor: pointer;
border: none;
} }
} }
`; `;
const Login = () => { const Login = (props: any) => {
const [signUp, setSignUp] = useState<boolean>(false); const [signUp, setSignUp] = useState<boolean>(false);
const [email, setEmail] = useState<string>(""); const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>(""); const [password, setPassword] = useState<string>("");
const authorizationHandler = (e:React.FormEvent) => { const authorizationHandler = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (signUp) { signUp
console.log('rejestracja', email, password); ? props.auth(email, password, true)
: props.auth(email, password, false);
} else {
console.log('log', email, password);
}
}; };
return ( return (
@ -73,14 +74,38 @@ const Login = () => {
<p className="forgot-password text-right"> <p className="forgot-password text-right">
Already registered{" "} Already registered{" "}
{signUp ? ( {signUp ? (
<a onClick={() => setSignUp(false)}>sign in?</a> <button className="login-button" onClick={() => setSignUp(false)}>
sign in?
</button>
) : ( ) : (
<a onClick={() => setSignUp(true)}>sign up?</a> <button className="login-button" onClick={() => setSignUp(true)}>
sign up?
</button>
)} )}
</p> </p>
</form> </form>
{props.isAuth && <Redirect to={props.redirectTo} />}
</StyledForm> </StyledForm>
); );
}; };
export default Login; interface RootState {
isAuth: boolean;
redirectTo: string;
}
const mapStateToProps = (state: RootState) => {
return {
isAuth: state.isAuth,
redirectTo: state.redirectTo,
};
};
const mapDispatchToProps = (dispatch: any) => {
return {
auth: (email: string, password: string, isSignupMode: boolean) =>
dispatch(actions.auth(email, password, isSignupMode)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Login);

View File

@ -1,11 +1,12 @@
import React from "react"; import React from "react";
import styled from "styled-components"; import styled from "styled-components";
import { NavLink } from "react-router-dom";
const StyledSidedrawer = styled.div` const StyledSidedrawer = styled.div`
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
z-index:8; z-index: 8;
overflow-y: hidden; overflow-y: hidden;
position: absolute; position: absolute;
top: 0; top: 0;
@ -29,14 +30,21 @@ const StyledSidedrawer = styled.div`
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
padding-right:1rem; padding-right: 1rem;
i { i {
padding: 0 1rem; padding: 0 1rem;
font-size: 1rem; font-size: 1rem;
} }
span { span {
padding: 0 1rem; padding: 0 1rem;
display:contents; display: contents;
}
a {
text-decoration: none;
color: #fff;
}
.active {
color: #4153af;
} }
} }
} }
@ -50,28 +58,46 @@ const SideDrawer = () => {
<StyledSidedrawer> <StyledSidedrawer>
<ul> <ul>
<li> <li>
<NavLink to={process.env.PUBLIC_URL + "/all"}>
<i className="fas fa-box-open"></i>
<span>All</span>
</NavLink>
</li>
<li>
<NavLink to={process.env.PUBLIC_URL + "/finished"}>
<i className="fas fa-hourglass-end"></i> <i className="fas fa-hourglass-end"></i>
<span>Finished</span> <span>Finished</span>
</NavLink>
</li> </li>
<li> <li>
<NavLink to={process.env.PUBLIC_URL + "/current"}>
<i className="fas fa-stopwatch"></i> <i className="fas fa-stopwatch"></i>
<span>On going</span> <span>On going</span>
</NavLink>
</li> </li>
<li> <li>
<NavLink to={process.env.PUBLIC_URL + "/business"}>
<i className="fas fa-briefcase"></i> <i className="fas fa-briefcase"></i>
<span>Bussiness</span> <span>Bussiness</span>
</NavLink>
</li> </li>
<li> <li>
<NavLink to={process.env.PUBLIC_URL + "/personal"}>
<i className="fas fa-lock"></i> <i className="fas fa-lock"></i>
<span>Personal</span> <span>Personal</span>
</NavLink>
</li> </li>
<li> <li>
<NavLink to={process.env.PUBLIC_URL + "/important"}>
<i className="fas fa-flag"></i> <i className="fas fa-flag"></i>
<span>Important</span> <span>Important</span>
</NavLink>
</li> </li>
<li> <li>
<NavLink to={process.env.PUBLIC_URL + "/canceled"}>
<i className="fas fa-ban"></i> <i className="fas fa-ban"></i>
<span>Canceled</span> <span>Canceled</span>
</NavLink>
</li> </li>
</ul> </ul>
</StyledSidedrawer> </StyledSidedrawer>

View File

@ -0,0 +1,78 @@
import React, { useEffect, useState } from "react";
import * as actions from "../store/actions/actions";
import "../../node_modules/bootstrap/dist/css/bootstrap.min.css";
import { connect } from "react-redux";
import styled from "styled-components";
interface TODOtype {
date: string;
taskName: string;
taskDesc: string;
isImportant: string;
taskType: string;
hash: string;
}
const TodosStyled = styled.div`
display: flex;
flex-direction:column;
height:92%;
overflow: scroll;
`;
const TodoList = (props: any) => {
useEffect(() => {
props.isAuth && props.loadToDos(props.userId, props.idToken);
}, []);
let cards = [];
if (props.isLoading === false) {
cards = props.todos.map((todo: TODOtype) => {
return (
<div
className="card text-white bg-info mb-3"
style={{ width: "100%" }}
key={todo.hash}
>
<div className="card-header">{todo.date}</div>
<div className="card-body">
<h5 className="card-title">{todo.taskName}</h5>
<p className="card-text">{todo.taskDesc}</p>
<hr />
<p className="card-text">{todo.isImportant && "important"}</p>
<hr />
<p className="card-text">{todo.taskType}</p>
</div>
</div>
);
});
}
return <TodosStyled>{cards}</TodosStyled>;
};
interface RootState {
isAuth: boolean;
todos: [];
userId: boolean;
idToken: string;
isLoading: boolean;
}
const mapStateToProps = (state: RootState) => {
return {
todos: state.todos,
userId: state.userId,
idToken: state.idToken,
isAuth: state.isAuth,
isLoading: state.isLoading,
};
};
const mapDispatchToProps = (dispatch: any) => {
return {
loadToDos: (userId: string, idToken: string) =>
dispatch(actions.loadToDos(userId, idToken)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);

View File

@ -1,3 +1,10 @@
export const ADD_TODO = 'ADD_TODO'; export const ADD_TODO = 'ADD_TODO';
export const DELETE_TODO = 'DELETE_TODO'; export const DELETE_TODO = 'DELETE_TODO';
export const LOAD_TODOS = 'LOAD_TODOS'; export const LOAD_TODOS = 'LOAD_TODOS';
export const AUTH_SUCCESS = 'AUTH_SUCCESS';
export const AUTH_FAIL = 'AUTH_FAIL';
export const AUTH_LOGOUT = 'AUTH_LOGOUT';
export const ADD_SUCCESS = 'ADD_SUCCESS';
export const ADD_FAIL = 'ADD_FAIL';
export const LOAD_FAIL = 'LOAD_FAIL';
export const LOAD_SUCCESS = 'LOAD_SUCCESS';

View File

@ -1,5 +1,135 @@
export const loadToDos = ()=>{ import * as actionType from "../actions/actionTypes";
return dispatch =>{
type ILoadToDos = {
type: string;
todos: Array<object>;
};
export const loadToDos = (userId: string, token: string) => {
return (dispatch: any) => {
let url = `https://to-do-studia.firebaseio.com/todos.json?auth=${token}&orderBy="userId"&equalTo="${userId}"`;
fetch(url)
.then((res) => res.json())
.then((data) => {
if (data.error) {
throw new Error(data.error.message);
} }
} let todos = [];
for (let key in data) {
todos.push({
hash: key,
taskName: data[key].name,
taskDesc: data[key].desc,
taskType: data[key].type,
isImportant: data[key].important,
date: data[key].date,
userId: data[key].userId,
});
}
dispatch({
type: actionType.LOAD_SUCCESS,
todos: todos,
});
})
.catch((err) => {
dispatch({
type: actionType.LOAD_FAIL,
error: err,
});
});
};
};
export const auth = (
email: string,
password: string,
isSignupMode: boolean
) => {
return (dispatch: any) => {
const data = {
email: email,
password: password,
returnSecureToken: true,
};
let url = `https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=AIzaSyBq4PiZ3S-qQnXK3LBw_8CvfBMSkQU6obY`;
if (!isSignupMode) {
url = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=AIzaSyBq4PiZ3S-qQnXK3LBw_8CvfBMSkQU6obY`;
}
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
})
.then((res) => res.json())
.then((data) => {
if (data.error) {
throw new Error(data.error.message);
}
dispatch({
type: actionType.AUTH_SUCCESS,
idToken: data.idToken,
userId: data.localId,
});
})
.catch((error) => {
dispatch({ type: actionType.AUTH_FAIL, error: error.message });
});
};
};
export const authLogout = () => {
return {
type: actionType.AUTH_LOGOUT,
};
};
export const addToDo = (
name: string,
desc: string,
type: string,
important: boolean,
date: string,
token: string,
userId: string
) => {
return (dispatch: any) => {
const data = {
name: name,
desc: desc,
type: type,
important: important,
date: date,
userId: userId,
};
let url = `https://to-do-studia.firebaseio.com/todos.json?auth=${token}`;
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
})
.then((res) => res.json())
.then((data) => {
if (data.error) {
throw new Error(data.error.message);
}
dispatch({
type: actionType.ADD_SUCCESS,
});
})
.catch((err) => {
dispatch({
type: actionType.ADD_FAIL,
error: err,
});
});
};
};

View File

@ -0,0 +1 @@
export {loadToDos, addToDo, auth, authLogout} from './actions';

View File

@ -1,21 +1,72 @@
import * as actionType from "../actions/actionTypes"; import * as actionType from "../actions/actionTypes";
import produce from "immer" import produce from "immer";
const initialState = { const initialState = {
isLoading: true, isLoading: true,
todos: [], todos: [],
isAuth: false, isAuth: false,
apiError: null,
error: null,
userId: null,
idToken: null,
redirectTo: "",
}; };
interface ActionType{ interface ActionType {
type:string type: any;
userId: any;
idToken: any;
error: any;
todos: [];
} }
const reducer = (state = initialState, action:ActionType) => { const reducer = (state = initialState, action: ActionType) => {
switch (action.type) { switch (action.type) {
case actionType.LOAD_TODOS: { case actionType.LOAD_TODOS: {
return state; return state;
} }
case actionType.AUTH_SUCCESS:
return {
...state,
isAuth: true,
error: null,
userId: action.userId,
idToken: action.idToken,
redirectTo: "/",
};
case actionType.AUTH_FAIL:
return {
...state,
error: action.error,
};
case actionType.ADD_SUCCESS:
return {
...state,
};
case actionType.ADD_FAIL:
return {
...state,
error: action.error,
};
case actionType.AUTH_LOGOUT:
return {
...state,
isAuth: false,
userId: null,
idToken: null,
rateData: null,
};
case actionType.LOAD_SUCCESS:
return {
...state,
todos: action.todos,
isLoading: false,
};
case actionType.LOAD_FAIL:
return {
...state,
error: action.error,
};
default: default:
return state; return state;
} }