From c25e5caf832d481f302dfc4c98269b9c530e1a07 Mon Sep 17 00:00:00 2001 From: Ido Shamun <1993245+idoshamun@users.noreply.github.com> Date: Tue, 25 Aug 2026 23:40:24 +0300 Subject: [PATCH] feat(shared): link to a user's world from the profile tooltip Adds an icon-only world button next to Follow in the user card the profile tooltip renders, so the world is reachable from anywhere a user is hovered rather than only from their profile page. Shown for every user, including the viewer themselves (who has no Follow button). The card has no cheap way to know whether a world has anything in it, and the world route already handles the empty and private cases. --- .../cards/entity/UserEntityCard.spec.tsx | 67 +++++++++++++++++++ .../cards/entity/UserEntityCard.tsx | 34 +++++++--- 2 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 packages/shared/src/components/cards/entity/UserEntityCard.spec.tsx diff --git a/packages/shared/src/components/cards/entity/UserEntityCard.spec.tsx b/packages/shared/src/components/cards/entity/UserEntityCard.spec.tsx new file mode 100644 index 00000000000..945ba544361 --- /dev/null +++ b/packages/shared/src/components/cards/entity/UserEntityCard.spec.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import UserEntityCard from './UserEntityCard'; +import AuthContext from '../../../contexts/AuthContext'; +import type { LoggedUser, UserShortProfile } from '../../../lib/user'; +import { webappUrl } from '../../../lib/constants'; + +jest.mock( + '../../../hooks/contentPreference/useContentPreferenceStatusQuery', + () => ({ + useContentPreferenceStatusQuery: () => ({ data: undefined }), + }), +); + +jest.mock('../../../hooks/useShowFollowAction', () => ({ + __esModule: true, + default: () => ({ isLoading: false, showActionBtn: true }), +})); + +const user: UserShortProfile = { + id: 'u1', + username: 'johndoe', + name: 'John Doe', + image: 'https://daily.dev/john.jpg', + permalink: 'https://daily.dev/johndoe', + createdAt: '2024-01-01T00:00:00.000Z', + bio: 'hello', + reputation: 10, +} as UserShortProfile; + +const renderComponent = (loggedUserId?: string) => + render( + + + + + , + ); + +describe('UserEntityCard', () => { + it('links to the user world', () => { + renderComponent('other'); + + const link = screen.getByLabelText("Visit @johndoe's world"); + expect(link).toHaveAttribute('href', `${webappUrl}world/johndoe`); + }); + + it('keeps the world link for the logged in user, who has no follow button', () => { + renderComponent('u1'); + + expect(screen.getByLabelText("Visit @johndoe's world")).toBeInTheDocument(); + expect(screen.queryByText('Follow')).not.toBeInTheDocument(); + }); +}); diff --git a/packages/shared/src/components/cards/entity/UserEntityCard.tsx b/packages/shared/src/components/cards/entity/UserEntityCard.tsx index 2dcb7739748..c4ae5c110fc 100644 --- a/packages/shared/src/components/cards/entity/UserEntityCard.tsx +++ b/packages/shared/src/components/cards/entity/UserEntityCard.tsx @@ -9,7 +9,7 @@ import { TypographyTag, TypographyType, } from '../../typography/Typography'; -import { DevPlusIcon } from '../../icons'; +import { DevPlusIcon, WorldIcon } from '../../icons'; import { VerifiedCompanyUserBadge } from '../../VerifiedCompanyUserBadge'; import { ProfileImageSize } from '../../ProfilePicture'; import { ReputationUserBadge } from '../../ReputationUserBadge'; @@ -19,9 +19,10 @@ import { FollowButton } from '../../contentPreference/FollowButton'; import { ContentPreferenceType } from '../../../graphql/contentPreference'; import { useContentPreferenceStatusQuery } from '../../../hooks/contentPreference/useContentPreferenceStatusQuery'; import AuthContext from '../../../contexts/AuthContext'; -import { ButtonVariant } from '../../buttons/Button'; +import { Button, ButtonSize, ButtonVariant } from '../../buttons/Button'; import EntityDescription from './EntityDescription'; import useShowFollowAction from '../../../hooks/useShowFollowAction'; +import { webappUrl } from '../../../lib/constants'; type Props = { user?: UserShortProfile; @@ -48,6 +49,7 @@ const UserEntityCard = ({ user, className }: Props) => { const { username, bio, name, image, isPlus, createdAt, id, permalink } = user; const isSameUser = loggedUser?.id === id; const showActionBtns = !isLoading && !isSameUser; + const worldHref = `${webappUrl}world/${username}`; return ( { }} entityName={username} actionButtons={ - showActionBtns && ( - +