|
|
|
|
@@ -1208,7 +1208,7 @@ function UserList() {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function UserDetail({ userId }: { userId: string }) {
|
|
|
|
|
function UserDetail({ userId, isStandalone = false }: { userId: string; isStandalone?: boolean }) {
|
|
|
|
|
const { userId: currentUserId } = useContext(AuthCtx);
|
|
|
|
|
const { follow, unfollow, isPending } = useFollowUser(userId);
|
|
|
|
|
const { data: user, isLoading, isError } = useQuery({
|
|
|
|
|
@@ -1228,11 +1228,13 @@ function UserDetail({ userId }: { userId: string }) {
|
|
|
|
|
if (isLoading) return <Spinner />;
|
|
|
|
|
if (isError || !user) return <ErrorBanner message="Could not load user" />;
|
|
|
|
|
|
|
|
|
|
const canFollow = !!currentUserId && currentUserId !== userId;
|
|
|
|
|
const isOwnProfile = currentUserId === userId;
|
|
|
|
|
const canFollow = !!currentUserId && !isOwnProfile;
|
|
|
|
|
const amIFollowing = user.amIFollowing ?? false;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="mx-detail">
|
|
|
|
|
{!isStandalone && (
|
|
|
|
|
<div className="mx-detail-header">
|
|
|
|
|
<a href="/users" className="mx-back-btn">
|
|
|
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
|
|
|
|
@@ -1241,17 +1243,23 @@ function UserDetail({ userId }: { userId: string }) {
|
|
|
|
|
Back
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<div className="mx-detail-body">
|
|
|
|
|
<div className="mx-detail-author">
|
|
|
|
|
<div className="mx-tweet-avatar">
|
|
|
|
|
<span>M</span>
|
|
|
|
|
<div className="mx-tweet-avatar mx-tweet-avatar--lg">
|
|
|
|
|
<span>{user.email?.[0]?.toUpperCase() ?? "M"}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div style={{ flex: 1 }}>
|
|
|
|
|
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
|
|
|
|
|
<div style={{ display: "flex", alignItems: "center", gap: "12px", flexWrap: "wrap" }}>
|
|
|
|
|
<span className="mx-tweet-handle">{user.email}</span>
|
|
|
|
|
{canFollow && (
|
|
|
|
|
<FollowButton amIFollowing={amIFollowing} isPending={isPending} onToggle={amIFollowing ? unfollow : follow} />
|
|
|
|
|
)}
|
|
|
|
|
{isOwnProfile && isStandalone && (
|
|
|
|
|
<a href="/sign-out" className="mx-btn-cancel" style={{ textDecoration: "none", fontSize: "0.8rem" }}>
|
|
|
|
|
Sign out
|
|
|
|
|
</a>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<div style={{ fontSize: "0.85rem", color: "var(--mx-muted)", marginTop: "6px", display: "flex", gap: "16px" }}>
|
|
|
|
|
<span><strong>{user.followerCount ?? 0}</strong> followers</span>
|
|
|
|
|
@@ -1264,6 +1272,25 @@ function UserDetail({ userId }: { userId: string }) {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function MyProfile() {
|
|
|
|
|
const { userId } = useContext(AuthCtx);
|
|
|
|
|
|
|
|
|
|
if (!userId) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="mx-empty">
|
|
|
|
|
<div className="mx-empty-icon">◎</div>
|
|
|
|
|
<p className="mx-empty-title">Your profile</p>
|
|
|
|
|
<p className="mx-empty-sub">
|
|
|
|
|
<a href="/sign-in" style={{ color: "var(--mx-accent)", textDecoration: "none" }}>Sign in</a>
|
|
|
|
|
{" "}to view your profile.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return <UserDetail userId={userId} isStandalone />;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Mobile bottom nav ─────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
function MobileNav({
|
|
|
|
|
@@ -1276,6 +1303,7 @@ function MobileNav({
|
|
|
|
|
const onFeedPage = page === "feed" || page === "tweet";
|
|
|
|
|
const onFollowingPage = page === "following";
|
|
|
|
|
const onUsersPage = page === "users" || page === "user-detail";
|
|
|
|
|
const onProfilePage = page === "profile";
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<nav className="mx-mobile-nav">
|
|
|
|
|
@@ -1328,6 +1356,16 @@ function MobileNav({
|
|
|
|
|
</svg>
|
|
|
|
|
<span>Users</span>
|
|
|
|
|
</a>
|
|
|
|
|
|
|
|
|
|
<a
|
|
|
|
|
href="/profile"
|
|
|
|
|
className={`mx-mobile-nav-item${onProfilePage ? " mx-mobile-nav-item--active" : ""}`}
|
|
|
|
|
>
|
|
|
|
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor">
|
|
|
|
|
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" />
|
|
|
|
|
</svg>
|
|
|
|
|
<span>Profile</span>
|
|
|
|
|
</a>
|
|
|
|
|
</nav>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
@@ -1379,6 +1417,7 @@ function App() {
|
|
|
|
|
const onFeedPage = page === "feed" || page === "tweet";
|
|
|
|
|
const onFollowingPage = page === "following";
|
|
|
|
|
const onUsersPage = page === "users" || page === "user-detail";
|
|
|
|
|
const onProfilePage = page === "profile";
|
|
|
|
|
|
|
|
|
|
function renderMain() {
|
|
|
|
|
switch (page) {
|
|
|
|
|
@@ -1419,6 +1458,15 @@ function App() {
|
|
|
|
|
<UserDetail userId={profileUserId!} />
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
case "profile":
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<header className="mx-header">
|
|
|
|
|
<h1 className="mx-header-title">My Profile</h1>
|
|
|
|
|
</header>
|
|
|
|
|
<MyProfile />
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
default:
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
@@ -1475,6 +1523,12 @@ function App() {
|
|
|
|
|
</svg>
|
|
|
|
|
Users
|
|
|
|
|
</a>
|
|
|
|
|
<a className={`mx-nav-item${onProfilePage ? " mx-nav-active" : ""}`} href="/profile">
|
|
|
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
|
|
|
|
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" />
|
|
|
|
|
</svg>
|
|
|
|
|
Profile
|
|
|
|
|
</a>
|
|
|
|
|
</nav>
|
|
|
|
|
<div className="mx-sidebar-footer">
|
|
|
|
|
{email ? (
|
|
|
|
|
|