Added /profile to see your profile
This commit is contained in:
@@ -303,6 +303,12 @@ html, body {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx-tweet-avatar--lg {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mx-compose-body { flex: 1; }
|
.mx-compose-body { flex: 1; }
|
||||||
|
|
||||||
.mx-compose-textarea, .mx-edit-textarea {
|
.mx-compose-textarea, .mx-edit-textarea {
|
||||||
@@ -964,4 +970,6 @@ html, body {
|
|||||||
}
|
}
|
||||||
.mx-header { padding: 0.75rem 1rem; }
|
.mx-header { padding: 0.75rem 1rem; }
|
||||||
.mx-detail { padding: 0.875rem 1rem; }
|
.mx-detail { padding: 0.875rem 1rem; }
|
||||||
|
/* 5-item nav: slightly smaller labels so nothing wraps */
|
||||||
|
.mx-mobile-nav-item { font-size: 0.6rem; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { userId: currentUserId } = useContext(AuthCtx);
|
||||||
const { follow, unfollow, isPending } = useFollowUser(userId);
|
const { follow, unfollow, isPending } = useFollowUser(userId);
|
||||||
const { data: user, isLoading, isError } = useQuery({
|
const { data: user, isLoading, isError } = useQuery({
|
||||||
@@ -1228,30 +1228,38 @@ function UserDetail({ userId }: { userId: string }) {
|
|||||||
if (isLoading) return <Spinner />;
|
if (isLoading) return <Spinner />;
|
||||||
if (isError || !user) return <ErrorBanner message="Could not load user" />;
|
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;
|
const amIFollowing = user.amIFollowing ?? false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-detail">
|
<div className="mx-detail">
|
||||||
<div className="mx-detail-header">
|
{!isStandalone && (
|
||||||
<a href="/users" className="mx-back-btn">
|
<div className="mx-detail-header">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
<a href="/users" className="mx-back-btn">
|
||||||
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
||||||
</svg>
|
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
|
||||||
Back
|
</svg>
|
||||||
</a>
|
Back
|
||||||
</div>
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="mx-detail-body">
|
<div className="mx-detail-body">
|
||||||
<div className="mx-detail-author">
|
<div className="mx-detail-author">
|
||||||
<div className="mx-tweet-avatar">
|
<div className="mx-tweet-avatar mx-tweet-avatar--lg">
|
||||||
<span>M</span>
|
<span>{user.email?.[0]?.toUpperCase() ?? "M"}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1 }}>
|
<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>
|
<span className="mx-tweet-handle">{user.email}</span>
|
||||||
{canFollow && (
|
{canFollow && (
|
||||||
<FollowButton amIFollowing={amIFollowing} isPending={isPending} onToggle={amIFollowing ? unfollow : follow} />
|
<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>
|
||||||
<div style={{ fontSize: "0.85rem", color: "var(--mx-muted)", marginTop: "6px", display: "flex", gap: "16px" }}>
|
<div style={{ fontSize: "0.85rem", color: "var(--mx-muted)", marginTop: "6px", display: "flex", gap: "16px" }}>
|
||||||
<span><strong>{user.followerCount ?? 0}</strong> followers</span>
|
<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 ─────────────────────────────────────────────────────────
|
// ── Mobile bottom nav ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function MobileNav({
|
function MobileNav({
|
||||||
@@ -1276,6 +1303,7 @@ function MobileNav({
|
|||||||
const onFeedPage = page === "feed" || page === "tweet";
|
const onFeedPage = page === "feed" || page === "tweet";
|
||||||
const onFollowingPage = page === "following";
|
const onFollowingPage = page === "following";
|
||||||
const onUsersPage = page === "users" || page === "user-detail";
|
const onUsersPage = page === "users" || page === "user-detail";
|
||||||
|
const onProfilePage = page === "profile";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="mx-mobile-nav">
|
<nav className="mx-mobile-nav">
|
||||||
@@ -1328,6 +1356,16 @@ function MobileNav({
|
|||||||
</svg>
|
</svg>
|
||||||
<span>Users</span>
|
<span>Users</span>
|
||||||
</a>
|
</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>
|
</nav>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1379,6 +1417,7 @@ function App() {
|
|||||||
const onFeedPage = page === "feed" || page === "tweet";
|
const onFeedPage = page === "feed" || page === "tweet";
|
||||||
const onFollowingPage = page === "following";
|
const onFollowingPage = page === "following";
|
||||||
const onUsersPage = page === "users" || page === "user-detail";
|
const onUsersPage = page === "users" || page === "user-detail";
|
||||||
|
const onProfilePage = page === "profile";
|
||||||
|
|
||||||
function renderMain() {
|
function renderMain() {
|
||||||
switch (page) {
|
switch (page) {
|
||||||
@@ -1419,6 +1458,15 @@ function App() {
|
|||||||
<UserDetail userId={profileUserId!} />
|
<UserDetail userId={profileUserId!} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
case "profile":
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<header className="mx-header">
|
||||||
|
<h1 className="mx-header-title">My Profile</h1>
|
||||||
|
</header>
|
||||||
|
<MyProfile />
|
||||||
|
</>
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -1475,6 +1523,12 @@ function App() {
|
|||||||
</svg>
|
</svg>
|
||||||
Users
|
Users
|
||||||
</a>
|
</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>
|
</nav>
|
||||||
<div className="mx-sidebar-footer">
|
<div className="mx-sidebar-footer">
|
||||||
{email ? (
|
{email ? (
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ defmodule MixerWeb.PageController do
|
|||||||
render_spa(conn, %{page: "following", tweet_id: nil, user_id: nil})
|
render_spa(conn, %{page: "following", tweet_id: nil, user_id: nil})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def profile(conn, _params) do
|
||||||
|
render_spa(conn, %{page: "profile", tweet_id: nil, user_id: nil})
|
||||||
|
end
|
||||||
|
|
||||||
def users_index(conn, _params) do
|
def users_index(conn, _params) do
|
||||||
render_spa(conn, %{page: "users", tweet_id: nil, user_id: nil})
|
render_spa(conn, %{page: "users", tweet_id: nil, user_id: nil})
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ defmodule MixerWeb.Router do
|
|||||||
get "/feed", PageController, :index
|
get "/feed", PageController, :index
|
||||||
get "/feed/:tweet_id", PageController, :show
|
get "/feed/:tweet_id", PageController, :show
|
||||||
get "/following", PageController, :following
|
get "/following", PageController, :following
|
||||||
|
get "/profile", PageController, :profile
|
||||||
get "/users", PageController, :users_index
|
get "/users", PageController, :users_index
|
||||||
get "/users/:user_id", PageController, :user_show
|
get "/users/:user_id", PageController, :user_show
|
||||||
post "/rpc/run", AshTypescriptRpcController, :run
|
post "/rpc/run", AshTypescriptRpcController, :run
|
||||||
|
|||||||
Reference in New Issue
Block a user