Skip to content

Commit

Permalink
feat: add auth flow
Browse files Browse the repository at this point in the history
  • Loading branch information
roanrobersson committed May 2, 2022
1 parent b4f174d commit 45cb8e6
Show file tree
Hide file tree
Showing 32 changed files with 575 additions and 2,146 deletions.
1,898 changes: 0 additions & 1,898 deletions package-lock.json

This file was deleted.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
"axios": "^0.27.2",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-query": "^3.38.0",
"react-redux": "^8.0.1",
"react-router-dom": "^6.3.0"
"react-router-dom": "^6.3.0",
"redux-saga": "^1.1.3"
},
"devDependencies": {
"@types/react": "^18.0.0",
Expand Down
59 changes: 59 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,58 @@ import TopBar from "@/components/TopBar";
import LeftMenu from "./components/LeftMenu";
import Home from "./components/Home";
import LeftMenuProvider from "./providers/LeftMenuProvider";
import { AUTH_STATE } from "./configs/env.js";
import AuthModal from "./components/AuthModal";
import { FETCH_TOKEN } from "./state/slices/authSlice";
import { redirectToAuth, getParamFromActualURL } from "@/lib/util.js";
import { AUTHORIZED } from "@/state/slices/authSlice.js";
import LoadingModal from "./components/LoadingModal";

const App = () => {
const [authModalIsOpen, setAuthModalIsOpen] = useState(false);
const authState = useSelector((state) => state.auth);
const commonState = useSelector((state) => state.common);
const dispatch = useDispatch();

useEffect(() => {
const token = localStorage.getItem("token");
const state = getParamFromActualURL("state");
const code = getParamFromActualURL("code");

if (token) {
dispatch(AUTHORIZED(token));
return;
}

// state != AUTH_STATE is used to check if the response has been tampered with by a third party
if (
token == null &&
(state == null || code == null || state != AUTH_STATE)
) {
redirectToAuth(true);
return;
}

if (token == null && state && code) {
dispatch(FETCH_TOKEN(code));
return;
}
}, []);

useEffect(() => {
setAuthModalIsOpen(!authState.token);
}, [authState.token]);

const handleRetryClick = () => {
redirectToAuth(false);
};

const authModalTitle = () => {
if (authState.error) return "Erro";
else if (authState.loading) return "Autenticando...";
else return "Redirecionando para a página de login...";
};

return (
<BrowserRouter>
<LeftMenuProvider>
Expand All @@ -16,7 +66,16 @@ const App = () => {
<Routes>
<Route exact path={"/"} element={<Navigate to="/app" />} />
<Route path={"/app"} element={<Home />} />
<Route path={"*"} element={<Navigate to={"/app"} />} />
</Routes>
<AuthModal
isOpen={authModalIsOpen}
title={authModalTitle()}
showRetryButton={authState.error}
onRetryClick={handleRetryClick}
/>
<LoadingModal isOpen={commonState.loadingInitialData}/>
<button onClick={() => localStorage.clear()}>Clear localStorage</button>
</BrowserRouter>
);
};
Expand Down
68 changes: 15 additions & 53 deletions src/api/request.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import axios from 'axios';
import { API_URL } from 'core/configs/env';
import { isRefreshTokenValid } from './auth';
import { getSessionData, replaceSessionData } from '../lib/storage';
import { AUTH_URL, TOKEN_URL, CLIENT_ID, CLIENT_SECRET } from 'core/configs/env.js';
import axios from "axios";
import { API_URL, CLIENT_ID, CLIENT_SECRET } from "@/configs/env.js";

const apiBasicAuthCredentials = `${CLIENT_ID}:${CLIENT_SECRET}`;

export const request = (config) => {
const headers = {
Authorization: `Basic ${window.btoa(apiBasicAuthCredentials)}`,
'Content-Type': 'application/json;charset=UTF-8',
"Content-Type": "application/json;charset=UTF-8",
};

return axios({
Expand All @@ -19,54 +16,19 @@ export const request = (config) => {
});
};

export const privateRequest = (config) => {
export const tokenRequest = (authCode) => {
const headers = {
Authorization: `Bearer ${getSessionData()?.accessToken}`,
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
};

return request({ ...config, headers });
};

export const loginRequest = (config) => {
const headers = {
Authorization: `Basic ${window.btoa(apiBasicAuthCredentials)}`,
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
};

return request({ ...config, headers });
return request({
url: "/oauth/access_token",
method: "POST",
headers,
data: new URLSearchParams({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
code: authCode,
}),
});
};

// axios.interceptors.response.use(
// (response) => {
// return response;
// },
// async (err) => {
// const originalRequest = err.response.config;

// if (err.response?.status === 401 && !isRefreshTokenValid()) {
// window.dirtLogout = true;
// return Promise.reject(err);
// }

// if (
// err.response?.status === 401 &&
// !originalRequest.__isRetry &&
// !originalRequest.__isTryRefreshToken
// ) {
// originalRequest.__isRetry = true;

// try {
// const response = await refreshAccessToken();
// replaceSessionData(response.data);
// console.warn('Access token refreshed');
// return privateRequest(originalRequest);
// } catch (_error) {
// window.dirtLogout = true;
// console.error('Access token expirated');
// return Promise.reject(_error);
// }
// }

// return Promise.reject(err);
// }
// );
Empty file removed src/api/services/projectService.js
Empty file.
Empty file removed src/api/services/taskService.js
Empty file.
12 changes: 12 additions & 0 deletions src/api/todoistApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { TodoistApi } from "@doist/todoist-api-typescript";

let _api = null;
const getApi = () => _api;

const onNewToken = ({detail}) => {
_api = new TodoistApi(detail);
};

window.addEventListener("new_token", onNewToken);

export default getApi;
Binary file added src/assets/images/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 45 additions & 0 deletions src/components/AuthModal/AuthModal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Modal, Box, Typography, Button } from "@mui/material";
import Loading from "../Loading";

const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 500,
bgcolor: "background.paper",
boxShadow: 24,
p: 4,
};

const AuthModal = ({ isOpen, title, showRetryButton, onRetryClick }) => {
return (
<Modal
open={isOpen}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<Typography
id="modal-modal-title"
variant="h6"
component="h2"
align="center"
>
{title}
</Typography>
{showRetryButton ? (
<Box sx={{ mt: 5, display: "flex", justifyContent: "center" }}>
<Button variant="outlined" onClick={onRetryClick}>
Tentar novamente
</Button>
</Box>
) : (
<Loading />
)}
</Box>
</Modal>
);
};

export default AuthModal;
3 changes: 3 additions & 0 deletions src/components/AuthModal/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import AuthModal from "./AuthModal";

export default AuthModal;
15 changes: 15 additions & 0 deletions src/components/Home/HomePage.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
import { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { FETCH_PROJECTS, SET_PROJECTS } from "@/state/slices/projectsSlice.js";

const Home = () => {
const projects = useSelector((state) => state.projects.data);
const dispatch = useDispatch();

const handleFetchProjects = () => {
dispatch(FETCH_PROJECTS());
};

return (
<>
{projects.map((project) => (
<p key={project.id}>{project.name}</p>
))}
HOME
<button onClick={handleFetchProjects}>Fetch projects</button>
</>
);
};
Expand Down
4 changes: 2 additions & 2 deletions src/components/Home/index.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import HomePage from './HomePage.jsx';
import HomePage from "./HomePage.jsx";

export default HomePage;
export default HomePage;
2 changes: 1 addition & 1 deletion src/components/LeftMenu/LeftMenu.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useState, useContext } from "react";
import { LeftMenuContext } from "@/providers/LeftMenuProvider";
import ListItemIcon from "@mui/material/ListItemIcon";
import { StyledDrawer, Main } from "./styles";
import {
Inbox as InboxIcon,
Event as EventIcon,
Expand All @@ -9,7 +10,6 @@ import {
Circle as CircleIcon,
CalendarMonth as CalendarMonthIcon,
} from "@mui/icons-material";
import { StyledDrawer, Main } from "./styles";
import {
Divider,
List,
Expand Down
18 changes: 18 additions & 0 deletions src/components/Link/Link.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Link as MuiLink } from "@mui/material";
import { Link as ReactRouterLink } from "react-router-dom";

const Link = ({ children, external = false, href, color = "inherit" }) => (
<>
{external ? (
<MuiLink href={href} color={color}>
{children}
</MuiLink>
) : (
<ReactRouterLink to={href} color={color}>
{children}
</ReactRouterLink>
)}
</>
);

export default Link;
3 changes: 3 additions & 0 deletions src/components/Link/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Link from "./Link";

export default Link;
35 changes: 35 additions & 0 deletions src/components/Loading/Loading.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Box } from "@mui/system";
import { CircularProgress } from "@mui/material";

const Loading = ({
color,
disableShrink,
noPadding = false,
thickness,
size,
sx,
value,
variant,
}) => {
return (
<Box
sx={{
display: "flex",
justifyContent: "center",
...(!noPadding && { pt: 10 }),
}}
>
<CircularProgress
color={color}
disableShrink={disableShrink}
thickness={thickness}
size={size}
sx={{ ...sx }}
value={value}
variant={variant}
/>
</Box>
);
};

export default Loading;
3 changes: 3 additions & 0 deletions src/components/Loading/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Loading from "./Loading";

export default Loading;
34 changes: 34 additions & 0 deletions src/components/LoadingModal/LoadingModal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Modal, Box } from "@mui/material";
import Loading from "../Loading";
import logoImg from "@/assets/images/logo.png";

const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 600,
height: 400,
bgcolor: "background.paper",
boxShadow: 24,
pt: 9,
};

const LoadingModal = ({ isOpen }) => {
return (
<Modal
open={isOpen}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<Box sx={{ display: "flex", justifyContent: "center", pb: 4 }}>
<img src={logoImg} />
</Box>
<Loading noPadding="true" />
</Box>
</Modal>
);
};

export default LoadingModal;
3 changes: 3 additions & 0 deletions src/components/LoadingModal/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import LoadingModal from "./LoadingModal";

export default LoadingModal;
2 changes: 1 addition & 1 deletion src/components/TopBar/TopBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ const TopBar = () => {
>
<MenuItem>
<IconButton size="large" color="inherit">
<AddIcon/>
<AddIcon />
</IconButton>
<p>Adicionar tarefa</p>
</MenuItem>
Expand Down
Loading

0 comments on commit 45cb8e6

Please sign in to comment.