diff --git a/app/client/src/api/Api.tsx b/app/client/src/api/Api.tsx index 2566056cc8..bc1e208984 100644 --- a/app/client/src/api/Api.tsx +++ b/app/client/src/api/Api.tsx @@ -5,7 +5,7 @@ import { API_REQUEST_HEADERS, } from "constants/ApiConstants"; import { ActionApiResponse } from "./ActionAPI"; -import { AUTH_LOGIN_URL } from "constants/routes"; +import { AUTH_LOGIN_URL, PAGE_NOT_FOUND_URL } from "constants/routes"; import { setRouteBeforeLogin } from "utils/storage"; import history from "utils/history"; @@ -66,6 +66,17 @@ axiosInstance.interceptors.response.use( }); } } + if ( + error.resonse.status === 404 && + error.response.app_error_code === 4028 + ) { + history.push(PAGE_NOT_FOUND_URL); + return Promise.reject({ + code: 404, + message: "Page Not Found", + show: false, + }); + } if (error.response.data.responseMeta) { return Promise.resolve(error.response.data); } diff --git a/app/client/src/api/UserApi.tsx b/app/client/src/api/UserApi.tsx index 2235230173..eb7baea265 100644 --- a/app/client/src/api/UserApi.tsx +++ b/app/client/src/api/UserApi.tsx @@ -65,6 +65,7 @@ class UserApi extends Api { static switchUserOrgURL = `${UserApi.usersURL}/switchOrganization`; static addOrgURL = `${UserApi.usersURL}/addOrganization`; static logoutURL = "v1/logout"; + static currentUserURL = "v1/users/me"; static createUser( request: CreateUserRequest, @@ -76,6 +77,10 @@ class UserApi extends Api { return Api.get(UserApi.usersURL + "/" + request.id); } + static getCurrentUser(): AxiosPromise { + return Api.get(UserApi.currentUserURL); + } + static forgotPassword( request: ForgotPasswordRequest, ): AxiosPromise { diff --git a/app/client/src/assets/images/404-image.png b/app/client/src/assets/images/404-image.png new file mode 100644 index 0000000000..cba759370c Binary files /dev/null and b/app/client/src/assets/images/404-image.png differ diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index ebd86c5f7f..a3cfa4145c 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -216,6 +216,7 @@ export const ReduxActionTypes: { [key: string]: string } = { GET_ALL_APPLICATION_INIT: "GET_ALL_APPLICATION_INIT", FETCH_USER_APPLICATIONS_ORGS_SUCCESS: "FETCH_USER_APPLICATIONS_ORGS_SUCCESS", FETCH_USER_DETAILS_SUCCESS: "FETCH_USER_DETAILS_SUCCESS", + FETCH_USER_DETAILS_ERROR: "FETCH_USER_DETAILS_ERROR", FETCH_ALL_USERS_SUCCESS: "FETCH_ALL_USERS_SUCCESS", FETCH_ALL_USERS_INIT: "FETCH_ALL_USERS_INIT", FETCH_ALL_ROLES_SUCCESS: "FETCH_ALL_ROLES_SUCCESS", diff --git a/app/client/src/constants/routes.ts b/app/client/src/constants/routes.ts index 7b78575229..828b6d61d3 100644 --- a/app/client/src/constants/routes.ts +++ b/app/client/src/constants/routes.ts @@ -2,6 +2,7 @@ import { MenuIcons } from "icons/MenuIcons"; export const BASE_URL = "/"; export const ORG_URL = "/org"; +export const PAGE_NOT_FOUND_URL = "/404"; export const APPLICATIONS_URL = `/applications`; export const BUILDER_URL = "/applications/:applicationId/pages/:pageId/edit"; export const USER_AUTH_URL = "/user"; diff --git a/app/client/src/constants/userConstants.ts b/app/client/src/constants/userConstants.ts index e3930dfe7a..ea0e91914d 100644 --- a/app/client/src/constants/userConstants.ts +++ b/app/client/src/constants/userConstants.ts @@ -1,3 +1,5 @@ +export const ANONYMOUS_USERNAME = "anonymousUser"; + export type User = { id: string; email: string; diff --git a/app/client/src/index.tsx b/app/client/src/index.tsx index a5be152258..ca2ebaecf5 100755 --- a/app/client/src/index.tsx +++ b/app/client/src/index.tsx @@ -23,6 +23,7 @@ import { BASE_LOGIN_URL, BASE_SIGNUP_URL, USERS_URL, + PAGE_NOT_FOUND_URL, } from "constants/routes"; import { LayersContext, Layers } from "constants/Layers"; @@ -120,6 +121,11 @@ ReactDOM.render( routeProtected logDisable /> + diff --git a/app/client/src/pages/Applications/ApplicationCard.tsx b/app/client/src/pages/Applications/ApplicationCard.tsx index d37f429d6e..5ead4c9a29 100644 --- a/app/client/src/pages/Applications/ApplicationCard.tsx +++ b/app/client/src/pages/Applications/ApplicationCard.tsx @@ -12,7 +12,6 @@ import { theme, getBorderCSSShorthand, getColorWithOpacity, - Theme, } from "constants/DefaultTheme"; import ContextDropdown, { ContextDropdownOption, diff --git a/app/client/src/pages/Applications/index.tsx b/app/client/src/pages/Applications/index.tsx index 27b86bc1f9..8d59ba08c0 100644 --- a/app/client/src/pages/Applications/index.tsx +++ b/app/client/src/pages/Applications/index.tsx @@ -26,9 +26,7 @@ import { PERMISSION_TYPE, isPermitted } from "./permissionHelpers"; import { MenuIcons } from "icons/MenuIcons"; import { DELETING_APPLICATION } from "constants/messages"; import { AppToaster } from "components/editorComponents/ToastComponent"; -import AnalyticsUtil from "utils/AnalyticsUtil"; import FormDialogComponent from "components/editorComponents/form/FormDialogComponent"; -import OrganizationListMockResponse from "mockResponses/OrganisationListResponse"; import { User } from "constants/userConstants"; import CustomizedDropdown, { CustomizedDropdownProps, @@ -107,7 +105,6 @@ const StyledDialog = styled(Dialog)<{ setMaxWidth?: boolean }>` type ApplicationProps = { applicationList: ApplicationPayload[]; - fetchApplications: () => void; createApplication: (appName: string) => void; isCreatingApplication: boolean; isFetchingApplications: boolean; @@ -319,8 +316,6 @@ const mapStateToProps = (state: AppState) => ({ }); const mapDispatchToProps = (dispatch: any) => ({ - fetchApplications: () => - dispatch({ type: ReduxActionTypes.FETCH_APPLICATION_LIST_INIT }), getAllApplication: () => dispatch({ type: ReduxActionTypes.GET_ALL_APPLICATION_INIT }), createApplication: (appName: string) => { diff --git a/app/client/src/pages/common/PageHeader.tsx b/app/client/src/pages/common/PageHeader.tsx index 83dd75f4bb..a9dbddf436 100644 --- a/app/client/src/pages/common/PageHeader.tsx +++ b/app/client/src/pages/common/PageHeader.tsx @@ -1,15 +1,18 @@ import React from "react"; +import { useHistory, Link } from "react-router-dom"; import { connect } from "react-redux"; import { getCurrentUser } from "selectors/usersSelectors"; -import { getOrgs, getCurrentOrg } from "selectors/organizationSelectors"; import styled from "styled-components"; import StyledHeader from "components/designSystems/appsmith/StyledHeader"; import CustomizedDropdown from "./CustomizedDropdown"; import DropdownProps from "./CustomizedDropdown/HeaderDropdownData"; import { AppState } from "reducers"; -import { Org } from "constants/orgConstants"; -import { User } from "constants/userConstants"; +import { User, ANONYMOUS_USERNAME } from "constants/userConstants"; import Logo from "assets/images/appsmith_logo.png"; +import { ReduxActionTypes } from "constants/ReduxActionConstants"; +import { useEffect } from "react"; +import { AUTH_LOGIN_URL, APPLICATIONS_URL } from "constants/routes"; +import Button from "components/editorComponents/Button"; const StyledPageHeader = styled(StyledHeader)` width: 100%; @@ -28,31 +31,47 @@ const LogoContainer = styled.div` `; type PageHeaderProps = { - orgs?: Org[]; - currentOrg?: Org; user?: User; + fetchCurrentUser: () => void; }; export const PageHeader = (props: PageHeaderProps) => { - const { user } = props; + const { user, fetchCurrentUser } = props; + const history = useHistory(); + useEffect(() => { + fetchCurrentUser(); + }, [fetchCurrentUser]); + return ( - + Appsmith Logo - + - {user && } + {user && user.username !== ANONYMOUS_USERNAME ? ( + + ) : ( + - } - /> - - + <> + + + Page Unavailable +
+

Page not found

+

+ Either this page doesn't exist, or you don't have access to
+ this page. +

+
+
+ ); } } diff --git a/app/client/src/pages/organization/settings.tsx b/app/client/src/pages/organization/settings.tsx index 3f44772193..29b39bb03b 100644 --- a/app/client/src/pages/organization/settings.tsx +++ b/app/client/src/pages/organization/settings.tsx @@ -23,6 +23,7 @@ import FormDialogComponent from "components/editorComponents/form/FormDialogComp import { getCurrentUser } from "selectors/usersSelectors"; import { User } from "constants/userConstants"; import { useTable, useFlexLayout } from "react-table"; + type OrgProps = { allOrgs: Organization[]; changeOrgName: (value: string) => void; @@ -51,6 +52,8 @@ type DropdownProps = { activeItem: string; userRoles: object; username: string; + changeOrgUserRole: (orgId: string, role: string, username: string) => void; + orgId: string; }; const StyledDropDown = styled.div` @@ -79,6 +82,90 @@ const StyledMenu = styled(Menu)` } `; +const RoleNameCell = (props: any) => { + const { + roleName, + roles, + username, + isCurrentUser, + isChangingRole, + } = props.cellProps.row.original; + + if (isCurrentUser) { + return
{roleName}
; + } + + return ( + + } + position={Position.BOTTOM_LEFT} + > + + {roleName} + + {isChangingRole ? : undefined} + + + ); +}; + +const DeleteActionCell = (props: any) => { + const { username, isCurrentUser, isDeleting } = props.cellProps.row.original; + + return ( + !isCurrentUser && + (isDeleting ? ( + + ) : ( + props.deleteOrgUser(props.orgId, username)} + style={{ alignSelf: "center", cursor: "pointer" }} + /> + )) + ); +}; + +const Dropdown = (props: DropdownProps) => { + return ( + + {Object.entries(props.userRoles).map((role, index) => { + const MenuContent = ( +
+ + {role[0]} + +
{role[1]}
+
+ ); + + return ( + + props.changeOrgUserRole(props.orgId, role[0], props.username) + } + active={props.activeItem === role[0]} + text={MenuContent} + /> + ); + })} +
+ ); +}; + export const OrgSettings = (props: PageProps) => { const { match: { @@ -97,43 +184,7 @@ export const OrgSettings = (props: PageProps) => { roles: props.allRole, isCurrentUser: user.username === props.currentUser?.username, })); - const data = React.useMemo(() => userTableData, [ - props.allUsers, - props.allRole, - ]); - - const RoleNameCell = (cellProps: any) => { - const { - roleName, - roles, - username, - isCurrentUser, - isChangingRole, - } = cellProps.row.original; - - if (isCurrentUser) { - return
{roleName}
; - } - - return ( - - } - position={Position.BOTTOM_LEFT} - > - - {roleName} - - {isChangingRole ? : undefined} - - - ); - }; + const data = React.useMemo(() => userTableData, [userTableData]); const columns = React.useMemo(() => { return [ @@ -148,37 +199,19 @@ export const OrgSettings = (props: PageProps) => { { Header: "Role", accessor: "roleName", - Cell: RoleNameCell, + Cell: (cellProps: any) => { + return RoleNameCell({ cellProps, changeOrgUserRole, orgId }); + }, }, { Header: "Delete", accessor: "delete", Cell: (cellProps: any) => { - const { - username, - isCurrentUser, - isDeleting, - } = cellProps.row.original; - - return ( - !isCurrentUser && - (isDeleting ? ( - - ) : ( - deleteOrgUser(orgId, username)} - style={{ alignSelf: "center", cursor: "pointer" }} - /> - )) - ); + return DeleteActionCell({ cellProps, deleteOrgUser, orgId }); }, }, ]; - }, [props.allUsers, props.allRole]); + }, [orgId, deleteOrgUser, changeOrgUserRole]); const currentOrg = allOrgs.find(org => org.organization.id === orgId); const currentOrgName = currentOrg?.organization.name ?? ""; @@ -203,33 +236,6 @@ export const OrgSettings = (props: PageProps) => { getAllApplication(); }, [orgId, fetchUser, fetchAllRoles, getAllApplication]); - const Dropdown = (props: DropdownProps) => { - return ( - - {Object.entries(props.userRoles).map((role, index) => { - const MenuContent = ( -
- - {role[0]} - -
{role[1]}
-
- ); - - return ( - changeOrgUserRole(orgId, role[0], props.username)} - active={props.activeItem === role[0]} - text={MenuContent} - /> - ); - })} -
- ); - }; - return ( diff --git a/app/client/src/reducers/uiReducers/applicationsReducer.tsx b/app/client/src/reducers/uiReducers/applicationsReducer.tsx index c4e45eff04..1a9408bd0e 100644 --- a/app/client/src/reducers/uiReducers/applicationsReducer.tsx +++ b/app/client/src/reducers/uiReducers/applicationsReducer.tsx @@ -7,7 +7,6 @@ import { } from "constants/ReduxActionConstants"; import { Organization } from "constants/orgConstants"; import { ERROR_MESSAGE_CREATE_APPLICATION } from "constants/messages"; -import { getApplicationPayload } from "mockComponentProps/ApplicationPayloads"; const initialState: ApplicationsReduxState = { isFetchingApplications: false, diff --git a/app/client/src/reducers/uiReducers/orgReducer.ts b/app/client/src/reducers/uiReducers/orgReducer.ts index 31cc972c9d..4d8d7b4f11 100644 --- a/app/client/src/reducers/uiReducers/orgReducer.ts +++ b/app/client/src/reducers/uiReducers/orgReducer.ts @@ -104,7 +104,7 @@ const orgReducer = createReducer(initialState, { action: ReduxAction<{ username: string }>, ) => { const _orgUsers = state.orgUsers.map((user: OrgUser) => { - if (user.username == action.payload.username) { + if (user.username === action.payload.username) { return { ...user, isChangingRole: true, @@ -119,7 +119,7 @@ const orgReducer = createReducer(initialState, { action: ReduxAction<{ username: string }>, ) => { const _orgUsers = state.orgUsers.map((user: OrgUser) => { - if (user.username == action.payload.username) { + if (user.username === action.payload.username) { return { ...user, isDeleting: true, diff --git a/app/client/src/sagas/ApplicationSagas.tsx b/app/client/src/sagas/ApplicationSagas.tsx index 20b76917a1..c80e6096f4 100644 --- a/app/client/src/sagas/ApplicationSagas.tsx +++ b/app/client/src/sagas/ApplicationSagas.tsx @@ -83,10 +83,6 @@ export function* getAllApplicationSaga() { type: ReduxActionTypes.FETCH_USER_APPLICATIONS_ORGS_SUCCESS, payload: organizationApplication, }); - yield put({ - type: ReduxActionTypes.FETCH_USER_DETAILS_SUCCESS, - payload: response.data.user, - }); } } catch (error) { yield put({ diff --git a/app/client/src/sagas/userSagas.tsx b/app/client/src/sagas/userSagas.tsx index f3a02f6a2f..0fdc976a84 100644 --- a/app/client/src/sagas/userSagas.tsx +++ b/app/client/src/sagas/userSagas.tsx @@ -72,6 +72,27 @@ export function* createUserSaga( } } +export function* getCurrentUserSaga() { + try { + const response: ApiResponse = yield call(UserApi.getCurrentUser); + + const isValidResponse = yield validateResponse(response); + if (isValidResponse) { + yield put({ + type: ReduxActionTypes.FETCH_USER_DETAILS_SUCCESS, + payload: response.data, + }); + } + } catch (error) { + yield put({ + type: ReduxActionErrorTypes.FETCH_USER_DETAILS_ERROR, + payload: { + error, + }, + }); + } +} + export function* forgotPasswordSaga( action: ReduxActionWithPromise, ) { @@ -326,6 +347,7 @@ export function* logoutSaga() { export default function* userSagas() { yield all([ takeLatest(ReduxActionTypes.CREATE_USER_INIT, createUserSaga), + takeLatest(ReduxActionTypes.FETCH_USER_INIT, getCurrentUserSaga), takeLatest(ReduxActionTypes.FORGOT_PASSWORD_INIT, forgotPasswordSaga), takeLatest(ReduxActionTypes.RESET_USER_PASSWORD_INIT, resetPasswordSaga), takeLatest(