Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src-react/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand All @@ -28,6 +29,7 @@ function App() {
/>
))}
<Route path="item/:id" element={<ItemDetails />} />
<Route path="user/:id" element={<User />} />
</Routes>
<Footer />
</div>
Expand Down
89 changes: 89 additions & 0 deletions src-react/user/User.scss
Original file line number Diff line number Diff line change
@@ -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;
}
71 changes: 71 additions & 0 deletions src-react/user/User.tsx
Original file line number Diff line number Diff line change
@@ -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<UserModel>();
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]);
Comment on lines +18 to +37

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Stale profile persists across navigation

On userID change the effect resets errorMessage but never clears user, so navigating from a loaded profile to one that errors keeps the previous profile on screen. This mirrors ItemDetails and the Angular original.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional — as you note, it matches the Angular original (which also only assigned user on success) and the sibling ItemDetails. Changing it would be a behavior change rather than a port, so I'm leaving it consistent across the three views; worth a follow-up if the stale-view-on-error transition is considered a bug in its own right.


const goBack = () => {
navigate(-1);
};

return (
<>
{!user && !errorMessage && <Loader />}
{!user && errorMessage !== '' && <ErrorMessage message={errorMessage} />}
{user && (
<div className="profile">
<div className="mobile item-header">
<p className="title-block">
<span className="back-button" onClick={goBack} />
Profile: {user.id}
</p>
</div>
<div className="main-details">
<span className="name">{user.id}</span>
<span className="right">{user.karma} ★</span>
<p className="age">Created {user.created}</p>
</div>
{user.about && (
<div className="other-details">
<p dangerouslySetInnerHTML={{ __html: sanitizeHtml(user.about) }} />
</div>
)}
</div>
)}
</>
);
}

export default User;