diff --git a/src/App.js b/src/App.js
index 4eaf5114ae5462673c6f232501444993714787cf..47adf6b0e408d234fdb88d0b259acc58ecf7ac7f 100644
--- a/src/App.js
+++ b/src/App.js
@@ -27,6 +27,7 @@ import { RecordListView } from "./views/records/RecordListView";
 import { RecordDetailsView } from "./views/records/RecordDetailsView";
 import { GlobalAppBarContext } from "@ess-ics/ce-ui-common";
 import { applicationTitle } from "./components/common/Helper";
+import { UserPageView } from "./views/UserPage/UserPageView";
 
 // setting up the application (TAB)title
 function App() {
@@ -112,6 +113,11 @@ function App() {
                           element={<LoginView />}
                           exact
                         />
+                        <Route
+                          path="/user/:userName"
+                          element={<UserPageView />}
+                          exact
+                        />
                         <Route
                           path="*"
                           element={<NotFound />}
diff --git a/src/api/SwaggerApi.js b/src/api/SwaggerApi.js
index cf1c3b10640bc82749b894fb02974e2a8f58d5f7..6cfee3e15dea9372d5705114f2900e0eb2059d3a 100644
--- a/src/api/SwaggerApi.js
+++ b/src/api/SwaggerApi.js
@@ -563,8 +563,10 @@ export function useUser() {
     console.warn("useUser error: ", message);
   }, []);
 
-  const [userInfo, getUserInfo, resetUserInfo, userInfoLoading] =
-    useUserInfo(onError);
+  const [userInfo, getUserInfo, resetUserInfo, userInfoLoading] = useUserInfo(
+    null,
+    onError
+  );
   const [userRoles, getUserRoles, resetUserRoles, userRolesLoading] =
     useUserRoles(onError);
   const [user, setUser] = useState();
@@ -598,10 +600,13 @@ export function useUser() {
   return [user, getUser, resetUser, loading];
 }
 
-export function useUserInfo(onError) {
+export function useUserInfo(userName, onError) {
   const api = useContext(apiContext);
   const method = useCallAndUnpack(api.apis.Git.infoFromUserName, unpackUser);
-  return useAsync({ fcn: method, call: false, onError: onError });
+  const boundMethod = useCallback(method.bind(null, { user_name: userName }), [
+    userName
+  ]);
+  return useAsync({ fcn: boundMethod, call: false, onError: onError });
 }
 
 export function unpackUserRoles(roles) {
diff --git a/src/components/common/User/UserIOCList.js b/src/components/common/User/UserIOCList.js
new file mode 100644
index 0000000000000000000000000000000000000000..3c67ddb2da784b6cfae4687548c6708e14abef1e
--- /dev/null
+++ b/src/components/common/User/UserIOCList.js
@@ -0,0 +1,52 @@
+import React from "react";
+import { Card, CardHeader } from "@mui/material";
+import { useState, useEffect } from "react";
+import { useIOCSearch } from "../../../api/SwaggerApi";
+import { IOCAsyncList } from "../../IOC/IOCAsyncList";
+import { initRequestParams } from "../Helper";
+
+export function UserIocList({ userName }) {
+  const [iocs, getIocs, , loading] = useIOCSearch();
+  const [iocList, setIocList] = useState([]);
+  const rowsPerPage = [20, 50];
+
+  const [lazyParams, setLazyParams] = useState({
+    first: 0,
+    rows: 20,
+    page: 0
+  });
+
+  useEffect(() => {
+    setIocList(iocs.iocList);
+  }, [iocs, setIocList]);
+
+  useEffect(() => {
+    let requestParams = initRequestParams(lazyParams);
+
+    requestParams.owner = userName;
+
+    getIocs(requestParams);
+  }, [getIocs, lazyParams, userName]);
+
+  return (
+    <Card>
+      <CardHeader
+        title={`IOCs registered by ${userName}`}
+        titleTypographyProps={{
+          variant: "h2",
+          component: "h2"
+        }}
+      />
+      <IOCAsyncList
+        iocs={iocList}
+        setIocs={setIocList}
+        loading={loading}
+        rowType="explore"
+        lazyParams={lazyParams}
+        setLazyParams={setLazyParams}
+        totalCount={iocs.totalCount}
+        rowsPerPage={rowsPerPage}
+      />
+    </Card>
+  );
+}
diff --git a/src/components/common/User/UserOperationList.js b/src/components/common/User/UserOperationList.js
new file mode 100644
index 0000000000000000000000000000000000000000..d98be2cb5df1b0fc946b28a32e5f0d5113499da1
--- /dev/null
+++ b/src/components/common/User/UserOperationList.js
@@ -0,0 +1,53 @@
+import React from "react";
+import { Card, CardHeader } from "@mui/material";
+import { initRequestParams } from "../Helper";
+import { useEffect, useState } from "react";
+import { useOperationsSearch } from "../../../api/SwaggerApi";
+import { JobAsyncList } from "../../Job/JobAsyncList";
+
+export function UserOperationList({ userName }) {
+  const [operations, getOperations, , loading] = useOperationsSearch();
+
+  const [lazyParams, setLazyParams] = useState({
+    first: 0,
+    rows: 20,
+    page: 0
+  });
+
+  const rowsPerPage = [20, 50];
+
+  const [jobList, setJobList] = useState([]);
+
+  useEffect(() => {
+    let requestParams = initRequestParams(lazyParams);
+
+    requestParams.user = userName;
+
+    getOperations(requestParams);
+  }, [getOperations, lazyParams, userName]);
+
+  useEffect(() => {
+    setJobList(operations.operationsList);
+  }, [operations, setJobList]);
+
+  return (
+    <Card>
+      <CardHeader
+        title={`Operations by ${userName}`}
+        titleTypographyProps={{
+          variant: "h2",
+          component: "h2"
+        }}
+      />
+      <JobAsyncList
+        jobs={jobList}
+        setJobs={setJobList}
+        loading={loading}
+        totalCount={operations.totalCount}
+        lazyParams={lazyParams}
+        setLazyParams={setLazyParams}
+        rowsPerPage={rowsPerPage}
+      />
+    </Card>
+  );
+}
diff --git a/src/components/common/User/UserProfile.js b/src/components/common/User/UserProfile.js
new file mode 100644
index 0000000000000000000000000000000000000000..25b0610fc12859fb934d05de9686648d0f6ec132
--- /dev/null
+++ b/src/components/common/User/UserProfile.js
@@ -0,0 +1,35 @@
+import React from "react";
+import { Avatar, Card, CardHeader, Typography } from "@mui/material";
+import { KeyValueTable } from "@ess-ics/ce-ui-common/dist/components/common/KeyValueTable";
+
+export function UserProfile({ userInfo, errorMessage }) {
+  const userData = {
+    username: userInfo?.loginName,
+    avatar: (
+      <Avatar
+        src={userInfo?.avatar}
+        variant="rounded"
+      />
+    )
+  };
+
+  return (
+    <Card>
+      <CardHeader
+        title="User profile page"
+        titleTypographyProps={{
+          variant: "h2",
+          component: "h2"
+        }}
+      />
+      {errorMessage ? (
+        <Typography>{errorMessage}</Typography>
+      ) : (
+        <KeyValueTable
+          obj={userData}
+          variant="overline"
+        />
+      )}
+    </Card>
+  );
+}
diff --git a/src/components/common/User/index.js b/src/components/common/User/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..44989ea76755d9b0c09beb68eba9323e55729505
--- /dev/null
+++ b/src/components/common/User/index.js
@@ -0,0 +1,5 @@
+import { UserIocList } from "./UserIOCList";
+import { UserOperationList } from "./UserOperationList";
+import { UserProfile } from "./UserProfile";
+
+export { UserIocList, UserOperationList, UserProfile };
diff --git a/src/components/navigation/NavigationMenu/LoginControls.js b/src/components/navigation/NavigationMenu/LoginControls.js
index cfdd5dbcad03735f06efe2196898a70cab6148e5..f5d8dc72914a71de0e948786ca1b8fff93b20861 100644
--- a/src/components/navigation/NavigationMenu/LoginControls.js
+++ b/src/components/navigation/NavigationMenu/LoginControls.js
@@ -8,6 +8,7 @@ import React, {
 import { string } from "prop-types";
 import { Button, Avatar, Chip } from "@mui/material";
 import LockOpenIcon from "@mui/icons-material/LockOpen";
+import PersonIcon from "@mui/icons-material/Person";
 import {
   userContext,
   LoginDialog,
@@ -21,6 +22,7 @@ const propTypes = {
 
 export function ProfileMenu({ user }) {
   const { userRoles, logout } = useContext(userContext);
+  const navigate = useNavigate();
 
   const objUserRoles = userRoles
     ?.filter((role) => role.includes("DeploymentTool"))
@@ -33,6 +35,10 @@ export function ProfileMenu({ user }) {
       )
     }));
 
+  function userProfile() {
+    navigate(`user/${user.loginName}`);
+  }
+
   const profileMenuProps = {
     /*
         If editing this id, be sure to edit the check for this in
@@ -52,6 +58,11 @@ export function ProfileMenu({ user }) {
     ),
     menuItems: [
       ...objUserRoles,
+      {
+        text: "User profile page",
+        action: () => userProfile(),
+        icon: <PersonIcon fontSize="small" />
+      },
       {
         text: "Logout",
         action: () => logout(),
diff --git a/src/views/UserPage/UserPageView.js b/src/views/UserPage/UserPageView.js
new file mode 100644
index 0000000000000000000000000000000000000000..e0f8b0938b101bfc6169c86f0bd3ff9a5c259d77
--- /dev/null
+++ b/src/views/UserPage/UserPageView.js
@@ -0,0 +1,113 @@
+import React, { useContext, useState } from "react";
+import { Grid } from "@mui/material";
+import { styled } from "@mui/material/styles";
+import { RootContainer } from "../../components/common/Container/RootContainer";
+import { useParams } from "react-router-dom";
+import { useUserInfo } from "../../api/SwaggerApi";
+import { userContext } from "@ess-ics/ce-ui-common/dist/contexts/User";
+import { UserProfile } from "../../components/common/User/UserProfile";
+import { useEffect } from "react";
+import { UserIocList } from "../../components/common/User/UserIOCList";
+import { UserOperationList } from "../../components/common/User/UserOperationList";
+
+const PREFIX = "UserPage";
+
+const classes = {
+  paper: `${PREFIX}-paper`
+};
+
+const StyledRootContainer = styled(RootContainer)(({ theme }) => ({
+  [`& .${classes.paper}`]: {
+    maxWidth: "80%",
+    padding: theme.spacing(4)
+  }
+}));
+
+export function UserPageView() {
+  const { userName } = useParams();
+  const { user } = useContext(userContext);
+  const [errorMessage, setErrorMessage] = useState();
+
+  const [userInfo, getUserInfo, ,] = useUserInfo(userName, (m, s) => {
+    // log error message for userInfo
+    console.warn(m);
+    // user not found
+    if (s === 404) {
+      setErrorMessage("User not found!");
+      // do not show snackbar error
+      return false;
+    }
+
+    // user doesn't have permission to fetch userInfo
+    if (s === 401) {
+      setErrorMessage("No activity");
+      // do not show snackbar error
+      return false;
+    }
+    // show any other errors with snackbar
+  });
+
+  useEffect(() => {
+    // user logs in => clear error message, and try to re-request userInfo
+    if (user) {
+      setErrorMessage(null);
+    } else {
+      // user is not logged in => show a message
+      setErrorMessage("No activity!");
+    }
+  }, [user]);
+
+  useEffect(() => {
+    // request userInfo only if user is logged in
+    if (user) {
+      getUserInfo();
+    }
+  }, [getUserInfo, userName, user]);
+
+  // content for userActivity, e.g. operations that the user done, or IOCs that owns
+  const userActivityContent = (
+    <>
+      <Grid
+        item
+        xs={12}
+      >
+        <UserOperationList userName={userName} />
+      </Grid>
+
+      <Grid
+        item
+        xs={12}
+      >
+        <UserIocList userName={userName} />
+      </Grid>
+    </>
+  );
+
+  // show userPage for everyone, but hide userActivity for non-logged in users, and for not existing user
+  const content = (
+    <>
+      <Grid
+        item
+        xs={12}
+      >
+        <UserProfile
+          userInfo={userInfo}
+          errorMessage={errorMessage}
+        />
+      </Grid>
+
+      {user && !errorMessage && userActivityContent}
+    </>
+  );
+
+  return (
+    <StyledRootContainer>
+      <Grid
+        container
+        spacing={1}
+      >
+        {content}
+      </Grid>
+    </StyledRootContainer>
+  );
+}
diff --git a/src/views/UserPage/index.js b/src/views/UserPage/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..232a503f885e084f47d64b422b4d761f9d8497f5
--- /dev/null
+++ b/src/views/UserPage/index.js
@@ -0,0 +1,4 @@
+import { UserPageView } from "./UserPageView";
+
+export { UserPageView };
+export default UserPageView;