Skip to content
Snippets Groups Projects
Commit 47434eed authored by Bernard Roland (Student Com20)'s avatar Bernard Roland (Student Com20)
Browse files

Added some error handling

parent 6addde3f
No related branches found
No related tags found
No related merge requests found
Showing
with 175 additions and 100 deletions
......@@ -2,8 +2,9 @@
import { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import LoginRoute from 'components/helpers/LoginRoute';
import AppWrapper from 'pages/AppWrapper';
import LoginRoute from 'components/helpers/LoginRoute';
import ProtectedRoute from 'components/helpers/ProtectedRoute';
const Home = lazy(() => import('pages/Home'));
const Login = lazy(() => import('pages/Login'));
......@@ -18,7 +19,7 @@ export default function App() {
<LoginRoute path="/login" component={Login} />
<LoginRoute path="/register" component={Register} />
<Route path="/introduction" component={Introduction} />
<Route path={['/tasks', '/projects', '/stats', '/teams', '/settings']} component={AppWrapper} />
<ProtectedRoute path={['/tasks', '/projects', '/stats', '/teams', '/settings']} component={AppWrapper} />
<Route path="/" component={Home} />
</Switch>
</Suspense>
......
......@@ -36,7 +36,7 @@ export function getLoggedInUser() {
}
extendAccessToken();
setInterval(extendAccessToken, 1000 * 60 * 30);
setInterval(extendAccessToken, 1000 * 60 * 5);
async function extendAccessToken() {
if (isLoggedIn()) {
......
......@@ -4,8 +4,9 @@ import { useEffect, useState } from 'react';
import { reload } from 'index';
import { addTeamMember, Team, TeamRole } from 'adapters/team';
import UsernameForm from 'components/forms/MemberForm/UsernameForm';
import Callout from 'components/ui/Callout';
import RoleForm from 'components/forms/RoleForm';
import UsernameForm from 'components/forms/MemberForm/UsernameForm';
import './member-form.scss';
......@@ -16,6 +17,7 @@ interface Props {
}
export default function MemberForm({ roles, team, setRoles }: Props) {
const [error, setError] = useState('');
const [role, setRole] = useState<string>();
const [user, setUser] = useState('');
......@@ -23,12 +25,14 @@ export default function MemberForm({ roles, team, setRoles }: Props) {
if (role && user) {
addTeamMember(team.id, { user: user, role: role }).then(() => {
reload()
});
})
.catch(() => setError("Failed to add team member"));
}
}, [role, user, team])
return (
<div className="member-form">
{error && <Callout message={error} />}
{
!user
? <UsernameForm setResult={setUser} />
......
......@@ -5,9 +5,10 @@ import { Project, ProjectColors } from 'adapters/project';
import { getTeam, getTeams, Team } from 'adapters/team';
import { Status } from 'adapters/common';
import Callout from 'components/ui/Callout';
import Button from 'components/ui/Button';
import Callout from 'components/ui/Callout';
import TextInput from 'components/ui/TextInput';
import ErrorScreen from 'components/ui/ErrorScreen';
import CheckboxGroup from 'components/ui/CheckboxGroup';
import '../form.scss';
......@@ -64,32 +65,27 @@ export default function ProjectForm({ project, onSubmit }: Props) {
const [color, setColor] = useState(project?.color);
const [deadline, setDeadline] = useState(project?.deadline?.toISOString());
const [error, setError] = useState('');
const [loadError, setLoadError] = useState(false);
const [teams, setTeams] = useState(project?.teams ?? []);
const [allTeams, setAllTeams] = useState<Team[]>([]);
useEffect(() => {
teams.forEach((userTeam) => {
getTeam(userTeam).then((team) => {
setAllTeams(state => {
if (!state.find((t) => t.id === team.id)) {
return [...state, team];
}
return [...state];
});
});
});
getTeams().then((allTeamsItems) => {
allTeamsItems.forEach(allTeamsItem => {
setAllTeams(state => {
if (!state.find((team) => team.id === allTeamsItem.id)) {
return [...state, allTeamsItem];
}
return [...state];
});
})
});
}, [teams])
Promise.all([
Promise.all(teams.map(team => getTeam(team))),
getTeams(),
]).then(([projectTeams, userTeams]) => {
const teamIds = new Set<string>();
const teams = [];
for (const team of [...projectTeams, ...userTeams]) {
if (!teamIds.has(team.id)) {
teams.push(team);
teamIds.add(team.id);
}
}
setAllTeams(teams);
})
.catch(() => setLoadError(true))
}, [teams, loadError])
const colors = Object.values(ProjectColors);
const allStatus = Object.values(Status);
......@@ -177,7 +173,10 @@ export default function ProjectForm({ project, onSubmit }: Props) {
<div className="teams">
<h2>Teams</h2>
<p>Which ones of your teams are working on this project</p>
<CheckboxGroup choices={allTeams} chosen={teams} setChosen={setTeams} />
{ loadError
? <ErrorScreen />
: <CheckboxGroup choices={allTeams} chosen={teams} setChosen={setTeams} />
}
</div>
<div className="button-container">
<Button type="submit" className="expanded">
......
......@@ -29,7 +29,6 @@ function validateEmail(email?: string): string | null {
return 'Please enter a valid email or leave this field blank.'
}
} else {
console.log(email);
return null;
}
}
......
......@@ -17,8 +17,6 @@ export default function LoginRoute(props: RouteProps) {
}
})
return (
<Route {...props} />
);
return <Route {...props} />;
}
......@@ -3,35 +3,44 @@ import { useEffect, useState } from 'react';
import { Route, RouteProps, useHistory } from 'react-router-dom';
import { isLoggedIn } from 'adapters/auth';
import { getTeams, Team } from 'adapters/team';
import { getTeams } from 'adapters/team';
import LoadingScreen from 'components/ui/LoadingScreen';
import ErrorScreen from 'components/ui/ErrorScreen';
export default function ProtectedRoute(props: RouteProps) {
const [loaded, setLoaded] = useState(false);
const [error, setError] = useState(false);
const history = useHistory();
const [team, setTeam] = useState<Team[]>();
if (!isLoggedIn()) {
history.push('/login');
}
useEffect(() => {
getTeams().then((teams) => {
setTeam(teams);
}).catch(() => {
});
}, [])
if (team && isLoggedIn()) {
if (team.length === 0) {
history.push('/introduction');
} else {
return <Route {...props} />
if (!isLoggedIn()) {
history.push('/login');
}
});
useEffect(() => {
getTeams().then(teams => {
if (teams.length === 0) {
history.push('/introduction');
} else {
setLoaded(true);
}
}).catch(() => setError(true));
}, [history, error])
if (error) {
return <ErrorScreen
onReload={() => {
setError(false);
setLoaded(false);
}}
onGoHome={() => history.push('/')}
/>;
} else if (loaded) {
return <Route {...props} />;
} else {
return <LoadingScreen />;
}
return (
<LoadingScreen />
);
}
......@@ -8,6 +8,7 @@ import Avatar from 'components/ui/Avatar';
import CommentComponent from 'components/ui/Comment';
import './comment-list.scss';
import ErrorScreen from 'components/ui/ErrorScreen';
interface Props {
comments: Comment[]
......@@ -15,12 +16,15 @@ interface Props {
}
export default function CommentList({ comments, taskId }: Props) {
const [error, setError] = useState(false);
const [user, setUser] = useState<User>();
const [comment, setComment] = useState<string>('');
const [allComments, setComments] = useState<Comment[]>([]);
useEffect(() => {
getCurrentUser().then(setUser);
getCurrentUser()
.then(setUser)
.catch(() => setError(true));
setComments(comments);
}, [comments]);
......@@ -28,38 +32,49 @@ export default function CommentList({ comments, taskId }: Props) {
e.preventDefault();
if (comment.length > 0) {
createComment({ task: taskId, text: comment }).then(id => {
setComment('');
getComment(id).then(comment => {
setComments(state => [comment, ...state, ])
setComments(state => [comment, ...state])
})
});
setComment('');
.catch(() => setError(true))
})
.catch(() => setError(true))
}
}, [comment, taskId]);
const onError = useCallback(() => {
setError(true)
}, []);
return (
<div className="comment-list">
{
user && (
<div className="add-comment comment-container">
<div className="head">
<Avatar user={user} />
<div className="user-info">
<div className="name">
{user.realname ?? user.username}
</div>
</div>
<div className="add-comment comment-container">
<div className="head">
<Avatar user={user} />
<div className="user-info">
<div className="name">
{user?.realname ?? user?.username}
</div>
<form onSubmit={handleSubmit}>
<textarea value={comment} placeholder="Write a comment..." onChange={(e) => setComment(e.target.value)}></textarea>
<button type="submit" disabled={comment.length <= 0}>Send</button>
</form>
</div>
</div>
<form onSubmit={handleSubmit}>
<textarea value={comment} placeholder="Write a comment..." onChange={(e) => setComment(e.target.value)}></textarea>
<button type="submit" disabled={comment.length <= 0 || error}>Send</button>
</form>
</div>
{error
? <ErrorScreen onReload={() => setError(false)} />
: (
allComments.map(comment => (
<CommentComponent
key={comment.id}
comment={comment}
onError={onError}
/>
))
)
}
{allComments.map(comment => (
<CommentComponent key={comment.id} comment={comment} />
))}
</div>
)
);
}
......@@ -19,8 +19,11 @@ export default function Header({ children }: Props) {
<div className={'page-wrapper' + (showSidebar ? ' moved' : '')} onClick={() => showSidebar && setShowSidebar(false)}>
<Page>
<header className="site-header">
<div className="hamburger-container">
<div className="hamburger" onClick={() => !showSidebar && setShowSidebar(true)}>
<div
className="hamburger-container"
onClick={() => !showSidebar && setShowSidebar(true)}
>
<div className="hamburger">
<div className="line"></div>
</div>
</div>
......
......@@ -9,17 +9,21 @@ import Avatar from 'components/ui/Avatar';
import LongText from 'components/ui/LongText';
import './comment.scss';
import LoadingScreen from '../LoadingScreen';
export interface CommentProps {
comment: IComment;
onError?: () => any;
}
export default function Comment({ comment }: CommentProps) {
export default function Comment({ comment, onError }: CommentProps) {
const [user, setUser] = useState<User>();
useEffect(() => {
getUser(comment.user).then((user) => setUser(user));
}, [comment]);
getUser(comment.user)
.then((user) => setUser(user))
.catch(() => onError?.());
}, [comment, onError]);
if (user) {
return (
......@@ -45,7 +49,7 @@ export default function Comment({ comment }: CommentProps) {
</div>
)
} else {
return <>Loading</>
return <LoadingScreen />
}
}
.error-screen {
width: 100%;
height: 100%;
min-height: 100px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
button {
margin: 6px;
}
}
import './error-screen.scss';
interface Props {
onReload?: () => any;
onGoHome?: () => any;
}
export default function ErrorScreen({ onReload, onGoHome }: Props) {
return (
<div className="error-screen">
<p>Failed to load data.</p>
<div>
{ onReload &&
<button onClick={onReload}>Try to reload</button>
}
{ onReload && onGoHome && (<span> or </span>) }
{ onGoHome &&
<button onClick={onGoHome}>Go to home</button>
}
</div>
</div>
)
}
import { Suspense, lazy } from 'react';
import { Switch } from 'react-router-dom';
import { Switch, Route } from 'react-router-dom';
import Header from 'components/navigation/Header';
import ProtectedRoute from 'components/helpers/ProtectedRoute';
const Tasks = lazy(() => import('pages/Tasks'));
const TaskDetail = lazy(() => import('pages/Tasks/TaskDetail'));
......@@ -25,20 +24,20 @@ export default function AppWrapper() {
<Header>
<Suspense fallback={false}>
<Switch>
<ProtectedRoute path="/tasks/:taskId/start" component={TaskStart} />
<ProtectedRoute path="/tasks/:taskId/edit" component={TaskEdit} />
<ProtectedRoute path="/tasks/:taskId" component={TaskDetail} />
<ProtectedRoute path="/tasks" exact component={Tasks} />
<ProtectedRoute path="/projects/create" component={ProjectCreate} />
<ProtectedRoute path="/projects/:projectId/tasks/create" component={TaskCreate} />
<ProtectedRoute path="/projects/:projectId/edit" component={ProjectEdit} />
<ProtectedRoute path="/projects/:projectId" component={ProjectDetail} />
<ProtectedRoute path="/projects" component={Projects} />
<ProtectedRoute path="/stats" component={Stats} />
<ProtectedRoute path="/settings" component={Settings} />
<ProtectedRoute path="/teams/create" exact component={TeamsCreate} />
<ProtectedRoute path="/teams/:teamId/edit" exact component={TeamsEdit} />
<ProtectedRoute path={['/teams/:teamId', '/teams']} component={Teams} />
<Route path="/tasks/:taskId/start" component={TaskStart} />
<Route path="/tasks/:taskId/edit" component={TaskEdit} />
<Route path="/tasks/:taskId" component={TaskDetail} />
<Route path="/tasks" exact component={Tasks} />
<Route path="/projects/create" component={ProjectCreate} />
<Route path="/projects/:projectId/tasks/create" component={TaskCreate} />
<Route path="/projects/:projectId/edit" component={ProjectEdit} />
<Route path="/projects/:projectId" component={ProjectDetail} />
<Route path="/projects" component={Projects} />
<Route path="/stats" component={Stats} />
<Route path="/settings" component={Settings} />
<Route path="/teams/create" exact component={TeamsCreate} />
<Route path="/teams/:teamId/edit" exact component={TeamsEdit} />
<Route path={['/teams/:teamId', '/teams']} component={Teams} />
</Switch>
</Suspense>
</Header>
......
......@@ -22,7 +22,9 @@ export default function Login() {
} else {
setError('The username or password are wrong.');
}
} catch(e) {}
} catch(e) {
setError('Failed to log in. Try again later.');
}
}, [history]);
return (
......
......@@ -21,7 +21,9 @@ export default function Register() {
} else {
setError('There was an error with your registration. Please try again!');
}
} catch (e) { }
} catch (e) {
setError('Failed to register. Try again later.');
}
}, [history]);
return (
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment