Skip to content

Commit 736d4dc

Browse files
authored
feat(desktop): improve profile layout (#4101)
* feat(desktop): improve profile layout * update * clean
1 parent 608e877 commit 736d4dc

1 file changed

Lines changed: 102 additions & 94 deletions

File tree

apps/desktop/layer/renderer/src/modules/profile/user-profile-modal/UserProfileModalContent.tsx

Lines changed: 102 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Spring } from "@follow/components/constants/spring.js"
2-
import { FollowIcon } from "@follow/components/icons/follow.jsx"
32
import { Avatar, AvatarFallback, AvatarImage } from "@follow/components/ui/avatar/index.jsx"
43
import { ActionButton, Button } from "@follow/components/ui/button/index.js"
54
import { LoadingCircle } from "@follow/components/ui/loading/index.jsx"
@@ -229,102 +228,112 @@ export const UserProfileModalContent: FC<SubscriptionModalContentProps> = ({ use
229228
type PickedUser = ReturnType<typeof pickUserData>
230229

231230
const UserInfo = ({ userInfo }: { userInfo: PickedUser }) => {
232-
const { t } = useTranslation()
233231
const whoami = useWhoami()
234232
const follow = useFollow()
233+
234+
// It's a string value when it's from the store
235+
if (typeof userInfo.socialLinks === "string") {
236+
userInfo.socialLinks = JSON.parse(userInfo.socialLinks)
237+
}
238+
235239
return (
236-
<div className="border-fill bg-material-medium border-b p-6">
237-
<div className="flex items-start gap-4">
238-
<Avatar className="size-20 shrink-0">
239-
<AvatarImage
240-
src={replaceImgUrlIfNeed(userInfo.image || undefined)}
241-
className="bg-material-ultra-thick"
242-
/>
243-
<AvatarFallback className="text-3xl uppercase">
244-
{userInfo.name?.slice(0, 2)}
245-
</AvatarFallback>
246-
</Avatar>
247-
<div className="min-w-0 flex-1">
248-
<h1 className="text-text text-2xl font-bold">{userInfo.name}</h1>
249-
<p
250-
className={cn(
251-
"text-text-secondary mt-1 text-sm",
252-
userInfo.handle ? "visible" : "hidden select-none",
240+
<div className="border-fill from-material-medium to-material-thin relative flex flex-col border-r bg-gradient-to-br p-8">
241+
<div className="flex h-full">
242+
<div className="flex flex-1 flex-col gap-4">
243+
<div className="flex items-center gap-4">
244+
<div className="relative">
245+
<Avatar className="size-16 shrink-0 shadow-lg ring-4 ring-white/10">
246+
<AvatarImage
247+
src={replaceImgUrlIfNeed(userInfo.image || undefined)}
248+
className="bg-material-ultra-thick"
249+
/>
250+
<AvatarFallback className="from-blue to-purple bg-gradient-to-br text-4xl font-bold uppercase text-white">
251+
{userInfo.name?.slice(0, 2)}
252+
</AvatarFallback>
253+
</Avatar>
254+
255+
{whoami?.id !== userInfo.id && (
256+
<Button
257+
buttonClassName="absolute -bottom-1 -right-1 size-6 rounded-full"
258+
onClick={() => {
259+
follow({
260+
url: `rsshub://follow/profile/${userInfo.id}`,
261+
isList: false,
262+
})
263+
}}
264+
size="sm"
265+
>
266+
<i className="i-mgc-add-cute-re size-4" />
267+
</Button>
268+
)}
269+
</div>
270+
<div className="flex-1">
271+
<h1 className="text-text max-w-[200px] truncate text-xl font-bold tracking-tight">
272+
{userInfo.name}
273+
</h1>
274+
<p
275+
className={cn(
276+
"text-text-secondary text-sm font-medium",
277+
userInfo.handle ? "visible" : "hidden select-none",
278+
)}
279+
>
280+
@{userInfo.handle}
281+
</p>
282+
</div>
283+
</div>
284+
285+
<div className="space-y-2">
286+
{userInfo.bio && (
287+
<p className="text-text-secondary text-sm leading-relaxed">{userInfo.bio}</p>
253288
)}
254-
>
255-
@{userInfo.handle}
256-
</p>
257-
{whoami?.id !== userInfo.id && (
258-
<Button
259-
onClick={() => {
260-
follow({
261-
url: `rsshub://follow/profile/${userInfo.id}`,
262-
isList: false,
263-
})
264-
}}
265-
buttonClassName="mt-3"
266-
size="sm"
267-
>
268-
<FollowIcon className="mr-1 size-3" />
269-
{t("feed_form.follow")}
270-
</Button>
271-
)}
289+
{userInfo.website && (
290+
<a
291+
href={userInfo.website}
292+
target="_blank"
293+
rel="noopener noreferrer"
294+
className="text-text-secondary hover:text-accent group inline-flex items-center gap-2 text-sm leading-relaxed transition-colors"
295+
>
296+
<span className="truncate">{userInfo.website.replace(/^https?:\/\//, "")}</span>
297+
<i className="i-mgc-external-link-cute-re size-3 opacity-60 transition-opacity group-hover:opacity-100" />
298+
</a>
299+
)}
300+
</div>
272301
</div>
273-
</div>
274-
</div>
275-
)
276-
}
277302

278-
const UserSocialSection = ({ user }: { user: PickedUser }) => {
279-
const { t } = useTranslation()
280-
if (!user || (!user.bio && !user.website && !user.socialLinks)) return null
281-
282-
return (
283-
<div className="border-fill bg-material-thin border-b px-6 py-4">
284-
<h3 className="text-text mb-3 text-sm font-medium uppercase tracking-wide">
285-
{t("user_profile.about")}
286-
</h3>
287-
{user.bio && <p className="text-text-secondary mb-3 text-sm leading-relaxed">{user.bio}</p>}
288-
<div className="space-y-2">
289-
{user.website && (
290-
<a
291-
href={user.website}
292-
target="_blank"
293-
rel="noopener noreferrer"
294-
className="text-accent hover:text-accent/80 inline-flex items-center gap-2 text-sm transition-colors"
295-
>
296-
<i className="i-mgc-link-cute-re text-base" />
297-
<span className="truncate">{user.website.replace(/^https?:\/\//, "")}</span>
298-
</a>
299-
)}
300-
{user.socialLinks && (
301-
<div className="flex flex-wrap items-center gap-3">
302-
{Object.entries(user.socialLinks).map(([platform, id]) => {
303-
if (!id || !(platform in socialIconClassNames)) return null
303+
{userInfo.socialLinks && Object.values(userInfo.socialLinks).filter(Boolean).length > 0 && (
304+
<div className="mt-auto space-y-3">
305+
<p className="text-text-secondary text-xs font-medium uppercase tracking-wide">
306+
Social Media
307+
</p>
308+
<div className="flex flex-wrap gap-2">
309+
{Object.entries(userInfo.socialLinks).map(([platform, id]) => {
310+
if (!id || !(platform in socialIconClassNames) || typeof id !== "string")
311+
return null
304312

305-
return (
306-
<Tooltip key={platform}>
307-
<TooltipTrigger asChild>
308-
<a
309-
href={getSocialLink(platform as keyof typeof socialIconClassNames, id)}
310-
target="_blank"
311-
rel="noopener noreferrer"
312-
className="text-text-secondary hover:text-accent group flex items-center justify-center rounded-full transition-colors"
313-
>
314-
<i
315-
className={cn(
316-
socialIconClassNames[platform as keyof typeof socialIconClassNames],
317-
"text-lg",
318-
)}
319-
/>
320-
</a>
321-
</TooltipTrigger>
322-
<TooltipContent className="text-sm">
323-
{socialCopyMap[platform as keyof typeof socialCopyMap]}
324-
</TooltipContent>
325-
</Tooltip>
326-
)
327-
})}
313+
return (
314+
<Tooltip key={platform}>
315+
<TooltipTrigger asChild>
316+
<a
317+
href={getSocialLink(platform as keyof typeof socialIconClassNames, id)}
318+
target="_blank"
319+
rel="noopener noreferrer"
320+
className="bg-material-ultra-thin/50 hover:bg-accent/10 group flex size-10 items-center justify-center rounded-lg backdrop-blur-sm transition-all duration-200 hover:scale-110"
321+
>
322+
<i
323+
className={cn(
324+
socialIconClassNames[platform as keyof typeof socialIconClassNames],
325+
"text-text-secondary group-hover:text-accent text-base transition-colors",
326+
)}
327+
/>
328+
</a>
329+
</TooltipTrigger>
330+
<TooltipContent className="text-xs font-medium">
331+
{socialCopyMap[platform as keyof typeof socialCopyMap]}
332+
</TooltipContent>
333+
</Tooltip>
334+
)
335+
})}
336+
</div>
328337
</div>
329338
)}
330339
</div>
@@ -338,7 +347,7 @@ const Subscriptions = ({ userId }: { userId: string }) => {
338347

339348
return (
340349
<>
341-
<div className="-mb-4 mt-4 flex items-center justify-between">
350+
<div className="-mb-4 flex items-center justify-between">
342351
<h2 className="text-text text-lg font-semibold">{t("user_profile.subscriptions")}</h2>
343352
<ActionButton
344353
tooltip={t("user_profile.toggle_item_style")}
@@ -394,12 +403,11 @@ const Content = ({ userInfo }: { userInfo: PickedUser }) => {
394403
<Fragment>
395404
<UserInfo userInfo={userInfo} />
396405
<ScrollArea.ScrollArea
397-
rootClassName="grow max-w-full w-full"
406+
rootClassName="grow max-w-full w-full bg-material-ultra-thin/30 "
398407
viewportClassName="[&>div]:!flex [&>div]:flex-col"
399408
>
400-
<UserSocialSection user={userInfo} />
401409
{!lists.isLoading && (
402-
<div className="@container flex flex-col px-6 py-4">
410+
<div className="@container flex flex-col space-y-4 px-8 py-6">
403411
{/* Lists Section */}
404412
{lists.data && lists.data.length > 0 && <Lists lists={lists.data} />}
405413
{/* Subscriptions Section */}

0 commit comments

Comments
 (0)