feat: redesign project sharing & initial registration
redesigned the project sharing popup to be a multi select dropdown that populates the options by using the input as a fuzzy search filter on the current users & invited users. users can now also be directly invited by email from the project share window. if invited this way, then the user will receive an email that sends them to a registration page, then a confirmation page. the initial registration was always redone so that it uses a similar system to the above in that it now will accept the first registered user if there are no other accounts (besides 'system').
This commit is contained in:
@ -1,16 +1,20 @@
|
||||
import React from 'react';
|
||||
import { Switch, Route } from 'react-router-dom';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Switch, Route, useHistory } from 'react-router-dom';
|
||||
import * as H from 'history';
|
||||
|
||||
import Dashboard from 'Dashboard';
|
||||
import Admin from 'Admin';
|
||||
import Confirm from 'Confirm';
|
||||
import Projects from 'Projects';
|
||||
import Project from 'Projects/Project';
|
||||
import Teams from 'Teams';
|
||||
import Login from 'Auth';
|
||||
import Install from 'Install';
|
||||
import Register from 'Register';
|
||||
import Profile from 'Profile';
|
||||
import styled from 'styled-components';
|
||||
import JwtDecode from 'jwt-decode';
|
||||
import { setAccessToken } from 'shared/utils/accessToken';
|
||||
import { useCurrentUser } from 'App/context';
|
||||
|
||||
const MainContent = styled.div`
|
||||
padding: 0 0 0 0;
|
||||
@ -21,6 +25,50 @@ const MainContent = styled.div`
|
||||
flex-grow: 1;
|
||||
`;
|
||||
|
||||
const AuthorizedRoutes = () => {
|
||||
const history = useHistory();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { setUser } = useCurrentUser();
|
||||
useEffect(() => {
|
||||
fetch('/auth/refresh_token', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
}).then(async x => {
|
||||
const { status } = x;
|
||||
if (status === 400) {
|
||||
history.replace('/login');
|
||||
} else {
|
||||
const response: RefreshTokenResponse = await x.json();
|
||||
const { accessToken, setup } = response;
|
||||
if (setup) {
|
||||
history.replace(`/register?confirmToken=${setup.confirmToken}`);
|
||||
} else {
|
||||
const claims: JWTToken = JwtDecode(accessToken);
|
||||
const currentUser = {
|
||||
id: claims.userId,
|
||||
roles: { org: claims.orgRole, teams: new Map<string, string>(), projects: new Map<string, string>() },
|
||||
};
|
||||
setUser(currentUser);
|
||||
setAccessToken(accessToken);
|
||||
}
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
return loading ? null : (
|
||||
<Switch>
|
||||
<MainContent>
|
||||
<Route exact path="/" component={Dashboard} />
|
||||
<Route exact path="/projects" component={Projects} />
|
||||
<Route path="/projects/:projectID" component={Project} />
|
||||
<Route path="/teams/:teamID" component={Teams} />
|
||||
<Route path="/profile" component={Profile} />
|
||||
<Route path="/admin" component={Admin} />
|
||||
</MainContent>
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
type RoutesProps = {
|
||||
history: H.History;
|
||||
};
|
||||
@ -28,15 +76,9 @@ type RoutesProps = {
|
||||
const Routes: React.FC<RoutesProps> = () => (
|
||||
<Switch>
|
||||
<Route exact path="/login" component={Login} />
|
||||
<Route exact path="/install" component={Install} />
|
||||
<MainContent>
|
||||
<Route exact path="/" component={Dashboard} />
|
||||
<Route exact path="/projects" component={Projects} />
|
||||
<Route path="/projects/:projectID" component={Project} />
|
||||
<Route path="/teams/:teamID" component={Teams} />
|
||||
<Route path="/profile" component={Profile} />
|
||||
<Route path="/admin" component={Admin} />
|
||||
</MainContent>
|
||||
<Route exact path="/register" component={Register} />
|
||||
<Route exact path="/confirm" component={Confirm} />
|
||||
<AuthorizedRoutes />
|
||||
</Switch>
|
||||
);
|
||||
|
||||
|
@ -230,10 +230,12 @@ type GlobalTopNavbarProps = {
|
||||
menuType?: Array<MenuItem>;
|
||||
onChangeRole?: (userID: string, roleCode: RoleCode) => void;
|
||||
projectMembers?: null | Array<TaskUser>;
|
||||
projectInvitedMembers?: null | Array<InvitedUser>;
|
||||
onSaveProjectName?: (projectName: string) => void;
|
||||
onInviteUser?: ($target: React.RefObject<HTMLElement>) => void;
|
||||
onSetTab?: (tab: number) => void;
|
||||
onRemoveFromBoard?: (userID: string) => void;
|
||||
onRemoveInvitedFromBoard?: (email: string) => void;
|
||||
};
|
||||
|
||||
const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
|
||||
@ -246,8 +248,10 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
|
||||
name,
|
||||
popupContent,
|
||||
projectMembers,
|
||||
projectInvitedMembers,
|
||||
onInviteUser,
|
||||
onSaveProjectName,
|
||||
onRemoveInvitedFromBoard,
|
||||
onRemoveFromBoard,
|
||||
}) => {
|
||||
const { user, setUserRoles, setUser } = useCurrentUser();
|
||||
@ -333,6 +337,34 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
|
||||
return null;
|
||||
}
|
||||
const userIsTeamOrProjectAdmin = user.isAdmin(PermissionLevel.TEAM, PermissionObjectType.TEAM, teamID);
|
||||
const onInvitedMemberProfile = ($targetRef: React.RefObject<HTMLElement>, email: string) => {
|
||||
const member = projectInvitedMembers ? projectInvitedMembers.find(u => u.email === email) : null;
|
||||
if (member) {
|
||||
showPopup(
|
||||
$targetRef,
|
||||
<MiniProfile
|
||||
onRemoveFromBoard={() => {
|
||||
if (onRemoveInvitedFromBoard) {
|
||||
onRemoveInvitedFromBoard(member.email);
|
||||
}
|
||||
}}
|
||||
invited
|
||||
user={{
|
||||
id: member.email,
|
||||
fullName: member.email,
|
||||
bio: 'Invited',
|
||||
profileIcon: {
|
||||
bgColor: '#000',
|
||||
url: null,
|
||||
initials: member.email.charAt(0),
|
||||
},
|
||||
}}
|
||||
bio=""
|
||||
/>,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const onMemberProfile = ($targetRef: React.RefObject<HTMLElement>, memberID: string) => {
|
||||
const member = projectMembers ? projectMembers.find(u => u.id === memberID) : null;
|
||||
const warning =
|
||||
@ -382,6 +414,7 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
|
||||
canEditProjectName={userIsTeamOrProjectAdmin}
|
||||
canInviteUser={userIsTeamOrProjectAdmin}
|
||||
onMemberProfile={onMemberProfile}
|
||||
onInvitedMemberProfile={onInvitedMemberProfile}
|
||||
onInviteUser={onInviteUser}
|
||||
onChangeRole={onChangeRole}
|
||||
onChangeProjectOwner={onChangeProjectOwner}
|
||||
@ -392,6 +425,7 @@ const GlobalTopNavbar: React.FC<GlobalTopNavbarProps> = ({
|
||||
history.push('/');
|
||||
}}
|
||||
projectMembers={projectMembers}
|
||||
projectInvitedMembers={projectInvitedMembers}
|
||||
onProfileClick={onProfileClick}
|
||||
onSaveName={onSaveProjectName}
|
||||
onOpenSettings={onOpenSettings}
|
||||
|
@ -52,7 +52,6 @@ type RefreshTokenResponse = {
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [user, setUser] = useState<CurrentUserRaw | null>(null);
|
||||
const setUserRoles = (roles: CurrentUserRoles) => {
|
||||
if (user) {
|
||||
@ -63,32 +62,6 @@ const App = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/auth/refresh_token', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
}).then(async x => {
|
||||
const { status } = x;
|
||||
if (status === 400) {
|
||||
history.replace('/login');
|
||||
} else {
|
||||
const response: RefreshTokenResponse = await x.json();
|
||||
const { accessToken, isInstalled } = response;
|
||||
const claims: JWTToken = jwtDecode(accessToken);
|
||||
const currentUser = {
|
||||
id: claims.userId,
|
||||
roles: { org: claims.orgRole, teams: new Map<string, string>(), projects: new Map<string, string>() },
|
||||
};
|
||||
setUser(currentUser);
|
||||
setAccessToken(accessToken);
|
||||
if (!isInstalled) {
|
||||
history.replace('/install');
|
||||
}
|
||||
}
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<UserContext.Provider value={{ user, setUser, setUserRoles }}>
|
||||
@ -97,13 +70,7 @@ const App = () => {
|
||||
<BaseStyles />
|
||||
<Router history={history}>
|
||||
<PopupProvider>
|
||||
{loading ? (
|
||||
<div>loading</div>
|
||||
) : (
|
||||
<>
|
||||
<Routes history={history} />
|
||||
</>
|
||||
)}
|
||||
<Routes history={history} />
|
||||
</PopupProvider>
|
||||
</Router>
|
||||
<StyledContainer
|
||||
|
Reference in New Issue
Block a user