claude fixed up the ui

This commit is contained in:
2026-04-02 21:46:15 -04:00
parent 9c131b98a6
commit abe10922eb
2 changed files with 61 additions and 25 deletions

View File

@@ -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-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-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 { .mx-tweet-text {
font-size: 1rem; font-size: 1rem;
line-height: 1.6; line-height: 1.6;

View File

@@ -896,8 +896,7 @@ function RefreshButton() {
); );
} }
function FollowButton({ targetUserId, amIFollowing }: { targetUserId: string; amIFollowing: boolean }) { function useFollowUser(targetUserId: string) {
const { userId: currentUserId } = useContext(AuthCtx);
const qc = useQueryClient(); const qc = useQueryClient();
const followMutation = useMutation({ const followMutation = useMutation({
@@ -928,35 +927,42 @@ function FollowButton({ targetUserId, amIFollowing }: { targetUserId: string; am
}, },
}); });
if (!currentUserId || currentUserId === targetUserId) return null; return {
follow: () => followMutation.mutate(),
const isPending = followMutation.isPending || unfollowMutation.isPending; unfollow: () => unfollowMutation.mutate(),
isPending: followMutation.isPending || unfollowMutation.isPending,
};
}
function FollowButton({ amIFollowing, isPending, onToggle }: { amIFollowing: boolean; isPending: boolean; onToggle: () => void }) {
return ( return (
<button <button
className={`mx-action-btn${amIFollowing ? " mx-action-btn--active" : ""}`} className={`mx-follow-btn${amIFollowing ? " mx-follow-btn--following" : ""}`}
disabled={isPending} disabled={isPending}
onClick={(e) => { onClick={(e) => { e.stopPropagation(); onToggle(); }}
e.stopPropagation();
amIFollowing ? unfollowMutation.mutate() : followMutation.mutate();
}}
> >
{isPending ? "..." : amIFollowing ? "Unfollow" : "Follow"} {isPending ? "" : amIFollowing ? "Unfollow" : "Follow"}
</button> </button>
); );
} }
function UserCard({ user }: { user: User }) { function UserCard({ user }: { user: User }) {
const { userId: currentUserId } = useContext(AuthCtx);
const [ctxMenu, setCtxMenu] = useState<{ x: number; y: number } | null>(null); 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 userUrl = `${window.location.origin}/users/${user.id}`;
const canFollow = !!currentUserId && currentUserId !== user.id;
const amIFollowing = user.amIFollowing ?? false;
const ctxItems: ContextMenuItem[] = [ const ctxItems: ContextMenuItem[] = [
{ { type: "item", label: "Share", onClick: () => navigator.clipboard.writeText(userUrl) },
type: "item", ...(canFollow ? [
label: "Share", { type: "separator" as const },
onClick: () => navigator.clipboard.writeText(userUrl), amIFollowing
}, ? { type: "item" as const, label: "Unfollow", onClick: unfollow }
: { type: "item" as const, label: "Follow", onClick: follow },
] : []),
]; ];
return ( return (
@@ -972,7 +978,6 @@ function UserCard({ user }: { user: User }) {
<div className="mx-tweet-body"> <div className="mx-tweet-body">
<div className="mx-tweet-header"> <div className="mx-tweet-header">
<span className="mx-tweet-handle">{user.email}</span> <span className="mx-tweet-handle">{user.email}</span>
<FollowButton targetUserId={user.id} amIFollowing={user.amIFollowing ?? false} />
</div> </div>
{(user.followerCount !== undefined || user.followingCount !== undefined) && ( {(user.followerCount !== undefined || user.followingCount !== undefined) && (
<div className="mx-tweet-meta" style={{ fontSize: "0.8rem", color: "var(--mx-muted)", marginTop: "4px" }}> <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>
)} )}
</div> </div>
{canFollow && (
<div style={{ display: "flex", alignItems: "center", flexShrink: 0 }}>
<FollowButton amIFollowing={amIFollowing} isPending={isPending} onToggle={amIFollowing ? unfollow : follow} />
</div>
)}
{ctxMenu && ( {ctxMenu && (
<ContextMenu <ContextMenu x={ctxMenu.x} y={ctxMenu.y} items={ctxItems} onClose={() => setCtxMenu(null)} />
x={ctxMenu.x}
y={ctxMenu.y}
items={ctxItems}
onClose={() => setCtxMenu(null)}
/>
)} )}
</article> </article>
); );
@@ -1032,6 +1037,8 @@ function UserList() {
} }
function UserDetail({ userId }: { userId: string }) { function UserDetail({ userId }: { userId: string }) {
const { userId: currentUserId } = useContext(AuthCtx);
const { follow, unfollow, isPending } = useFollowUser(userId);
const { data: user, isLoading, isError } = useQuery({ const { data: user, isLoading, isError } = useQuery({
queryKey: ["user", userId], queryKey: ["user", userId],
queryFn: async () => { queryFn: async () => {
@@ -1049,6 +1056,9 @@ 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 amIFollowing = user.amIFollowing ?? false;
return ( return (
<div className="mx-detail"> <div className="mx-detail">
<div className="mx-detail-header"> <div className="mx-detail-header">
@@ -1064,10 +1074,12 @@ function UserDetail({ userId }: { userId: string }) {
<div className="mx-tweet-avatar"> <div className="mx-tweet-avatar">
<span>M</span> <span>M</span>
</div> </div>
<div> <div style={{ flex: 1 }}>
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}> <div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
<span className="mx-tweet-handle">{user.email}</span> <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>
<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>