diff --git a/src-react/App.tsx b/src-react/App.tsx
index e1a1153f..6e636b5e 100644
--- a/src-react/App.tsx
+++ b/src-react/App.tsx
@@ -6,6 +6,7 @@ import Header from './core/header/Header';
import Feed from './feeds/feed/Feed';
import ItemDetails from './item-details/ItemDetails';
import type { FeedName } from './shared/models';
+import User from './user/User';
import './App.scss';
const feedNames: FeedName[] = ['news', 'newest', 'show', 'ask', 'jobs'];
@@ -28,6 +29,7 @@ function App() {
/>
))}
} />
+ } />
diff --git a/src-react/user/User.scss b/src-react/user/User.scss
new file mode 100644
index 00000000..53285578
--- /dev/null
+++ b/src-react/user/User.scss
@@ -0,0 +1,89 @@
+@import "../shared/scss/media";
+@import "../shared/scss/theme_variables";
+
+.other-details pre {
+ white-space: pre-wrap;
+}
+
+.profile {
+ padding: 30px;
+}
+
+@media #{$mobile-only} {
+ .profile {
+ padding: 110px 15px 0 15px;
+ }
+ .title-block {
+ font-size: 15px;
+ text-align: center;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ margin: 0 75px;
+ }
+ .back-button {
+ position: absolute;
+ top: 52%;
+ width: 0.6rem;
+ height: 0.6rem;
+ background: transparent;
+ box-shadow: 0 0 0 lightgray;
+ transition: all 200ms ease;
+ left: 4%;
+ transform: translate3d(0, -50%, 0) rotate(-135deg);
+ }
+ .item-header {
+ padding-bottom: 10px;
+ background-color: #fff;
+ padding: 10px 0 10px 0;
+ position: fixed;
+ width: 100%;
+ left: 0;
+ top: 62px;
+ height: 20px;
+ }
+}
+
+@media #{$laptop-only} {
+ .mobile {
+ display: none;
+ }
+}
+
+.main-details {
+ .name {
+ font-weight: bold;
+ font-size: 32px;
+ letter-spacing: 2px;
+ }
+ .age {
+ font-weight: bold;
+ color: #696969;
+ padding-bottom: 0;
+ }
+ .right {
+ float: right;
+ font-weight: bold;
+ font-size: 32px;
+ letter-spacing: 2px;
+ }
+}
+
+@media #{$mobile-only} {
+ .main-details {
+ margin-top: 20px;
+ .name {
+ font-size: 18px;
+ }
+ }
+}
+
+@media #{$mobile-only} {
+ .main-details .right {
+ font-size: 18px;
+ }
+}
+
+.other-details {
+ word-wrap: break-word;
+}
diff --git a/src-react/user/User.tsx b/src-react/user/User.tsx
new file mode 100644
index 00000000..24a302c6
--- /dev/null
+++ b/src-react/user/User.tsx
@@ -0,0 +1,71 @@
+import { useEffect, useState } from 'react';
+import { useNavigate, useParams } from 'react-router-dom';
+
+import { fetchUser } from '../shared/api/hackernewsApi';
+import ErrorMessage from '../shared/components/error-message/ErrorMessage';
+import Loader from '../shared/components/loader/Loader';
+import type { User as UserModel } from '../shared/models';
+import { sanitizeHtml } from '../shared/utils/sanitizeHtml';
+import './User.scss';
+
+function User() {
+ const params = useParams<{ id?: string }>();
+ const userID = params.id ?? '';
+ const navigate = useNavigate();
+ const [user, setUser] = useState();
+ const [errorMessage, setErrorMessage] = useState('');
+
+ useEffect(() => {
+ let ignore = false;
+ setErrorMessage('');
+
+ fetchUser(userID)
+ .then((nextUser) => {
+ if (!ignore) {
+ setUser(nextUser);
+ }
+ })
+ .catch(() => {
+ if (!ignore) {
+ setErrorMessage(`Could not load user ${userID}.`);
+ }
+ });
+
+ return () => {
+ ignore = true;
+ };
+ }, [userID]);
+
+ const goBack = () => {
+ navigate(-1);
+ };
+
+ return (
+ <>
+ {!user && !errorMessage && }
+ {!user && errorMessage !== '' && }
+ {user && (
+
+
+
+
+ Profile: {user.id}
+
+
+
+
{user.id}
+
{user.karma} ★
+
Created {user.created}
+
+ {user.about && (
+
+ )}
+
+ )}
+ >
+ );
+}
+
+export default User;