claude fixed up the ui
This commit is contained in:
@@ -466,6 +466,30 @@ html, body {
|
||||
.mx-action-delete:hover { color: var(--mx-red); background: color-mix(in oklch, var(--mx-red) 10%, transparent); }
|
||||
.mx-action-confirm { color: var(--mx-red) !important; background: color-mix(in oklch, var(--mx-red) 15%, transparent) !important; }
|
||||
|
||||
.mx-follow-btn {
|
||||
padding: 0.25rem 0.875rem;
|
||||
border-radius: 9999px;
|
||||
border: 1.5px solid var(--mx-border2);
|
||||
background: none;
|
||||
color: var(--mx-fg);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
||||
}
|
||||
.mx-follow-btn:hover:not(:disabled) { background: var(--mx-surface2); }
|
||||
.mx-follow-btn--following {
|
||||
background: var(--mx-surface);
|
||||
color: var(--mx-muted);
|
||||
}
|
||||
.mx-follow-btn--following:hover:not(:disabled) {
|
||||
background: color-mix(in oklch, var(--mx-red) 10%, transparent);
|
||||
border-color: var(--mx-red);
|
||||
color: var(--mx-red);
|
||||
}
|
||||
.mx-follow-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
.mx-tweet-text {
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
|
||||
@@ -896,8 +896,7 @@ function RefreshButton() {
|
||||
);
|
||||
}
|
||||
|
||||
function FollowButton({ targetUserId, amIFollowing }: { targetUserId: string; amIFollowing: boolean }) {
|
||||
const { userId: currentUserId } = useContext(AuthCtx);
|
||||
function useFollowUser(targetUserId: string) {
|
||||
const qc = useQueryClient();
|
||||
|
||||
const followMutation = useMutation({
|
||||
@@ -928,35 +927,42 @@ function FollowButton({ targetUserId, amIFollowing }: { targetUserId: string; am
|
||||
},
|
||||
});
|
||||
|
||||
if (!currentUserId || currentUserId === targetUserId) return null;
|
||||
|
||||
const isPending = followMutation.isPending || unfollowMutation.isPending;
|
||||
return {
|
||||
follow: () => followMutation.mutate(),
|
||||
unfollow: () => unfollowMutation.mutate(),
|
||||
isPending: followMutation.isPending || unfollowMutation.isPending,
|
||||
};
|
||||
}
|
||||
|
||||
function FollowButton({ amIFollowing, isPending, onToggle }: { amIFollowing: boolean; isPending: boolean; onToggle: () => void }) {
|
||||
return (
|
||||
<button
|
||||
className={`mx-action-btn${amIFollowing ? " mx-action-btn--active" : ""}`}
|
||||
className={`mx-follow-btn${amIFollowing ? " mx-follow-btn--following" : ""}`}
|
||||
disabled={isPending}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
amIFollowing ? unfollowMutation.mutate() : followMutation.mutate();
|
||||
}}
|
||||
onClick={(e) => { e.stopPropagation(); onToggle(); }}
|
||||
>
|
||||
{isPending ? "..." : amIFollowing ? "Unfollow" : "Follow"}
|
||||
{isPending ? "…" : amIFollowing ? "Unfollow" : "Follow"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function UserCard({ user }: { user: User }) {
|
||||
const { userId: currentUserId } = useContext(AuthCtx);
|
||||
const [ctxMenu, setCtxMenu] = useState<{ x: number; y: number } | null>(null);
|
||||
const { follow, unfollow, isPending } = useFollowUser(user.id);
|
||||
|
||||
const userUrl = `${window.location.origin}/users/${user.id}`;
|
||||
const canFollow = !!currentUserId && currentUserId !== user.id;
|
||||
const amIFollowing = user.amIFollowing ?? false;
|
||||
|
||||
const ctxItems: ContextMenuItem[] = [
|
||||
{
|
||||
type: "item",
|
||||
label: "Share",
|
||||
onClick: () => navigator.clipboard.writeText(userUrl),
|
||||
},
|
||||
{ type: "item", label: "Share", onClick: () => navigator.clipboard.writeText(userUrl) },
|
||||
...(canFollow ? [
|
||||
{ type: "separator" as const },
|
||||
amIFollowing
|
||||
? { type: "item" as const, label: "Unfollow", onClick: unfollow }
|
||||
: { type: "item" as const, label: "Follow", onClick: follow },
|
||||
] : []),
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -972,7 +978,6 @@ function UserCard({ user }: { user: User }) {
|
||||
<div className="mx-tweet-body">
|
||||
<div className="mx-tweet-header">
|
||||
<span className="mx-tweet-handle">{user.email}</span>
|
||||
<FollowButton targetUserId={user.id} amIFollowing={user.amIFollowing ?? false} />
|
||||
</div>
|
||||
{(user.followerCount !== undefined || user.followingCount !== undefined) && (
|
||||
<div className="mx-tweet-meta" style={{ fontSize: "0.8rem", color: "var(--mx-muted)", marginTop: "4px" }}>
|
||||
@@ -981,13 +986,13 @@ function UserCard({ user }: { user: User }) {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{canFollow && (
|
||||
<div style={{ display: "flex", alignItems: "center", flexShrink: 0 }}>
|
||||
<FollowButton amIFollowing={amIFollowing} isPending={isPending} onToggle={amIFollowing ? unfollow : follow} />
|
||||
</div>
|
||||
)}
|
||||
{ctxMenu && (
|
||||
<ContextMenu
|
||||
x={ctxMenu.x}
|
||||
y={ctxMenu.y}
|
||||
items={ctxItems}
|
||||
onClose={() => setCtxMenu(null)}
|
||||
/>
|
||||
<ContextMenu x={ctxMenu.x} y={ctxMenu.y} items={ctxItems} onClose={() => setCtxMenu(null)} />
|
||||
)}
|
||||
</article>
|
||||
);
|
||||
@@ -1032,6 +1037,8 @@ function UserList() {
|
||||
}
|
||||
|
||||
function UserDetail({ userId }: { userId: string }) {
|
||||
const { userId: currentUserId } = useContext(AuthCtx);
|
||||
const { follow, unfollow, isPending } = useFollowUser(userId);
|
||||
const { data: user, isLoading, isError } = useQuery({
|
||||
queryKey: ["user", userId],
|
||||
queryFn: async () => {
|
||||
@@ -1049,6 +1056,9 @@ 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 amIFollowing = user.amIFollowing ?? false;
|
||||
|
||||
return (
|
||||
<div className="mx-detail">
|
||||
<div className="mx-detail-header">
|
||||
@@ -1064,10 +1074,12 @@ function UserDetail({ userId }: { userId: string }) {
|
||||
<div className="mx-tweet-avatar">
|
||||
<span>M</span>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
|
||||
<span className="mx-tweet-handle">{user.email}</span>
|
||||
<FollowButton targetUserId={user.id} amIFollowing={user.amIFollowing ?? false} />
|
||||
{canFollow && (
|
||||
<FollowButton amIFollowing={amIFollowing} isPending={isPending} onToggle={amIFollowing ? unfollow : follow} />
|
||||
)}
|
||||
</div>
|
||||
<div style={{ fontSize: "0.85rem", color: "var(--mx-muted)", marginTop: "6px", display: "flex", gap: "16px" }}>
|
||||
<span><strong>{user.followerCount ?? 0}</strong> followers</span>
|
||||
|
||||
Reference in New Issue
Block a user