diff --git a/apps/ssr/client/pages/(main)/share/users/[id]/index.tsx b/apps/ssr/client/pages/(main)/share/users/[id]/index.tsx index ceb46caa7..d725b9061 100644 --- a/apps/ssr/client/pages/(main)/share/users/[id]/index.tsx +++ b/apps/ssr/client/pages/(main)/share/users/[id]/index.tsx @@ -2,6 +2,8 @@ import { MainContainer } from "@client/components/layout/main" import { FeedIcon } from "@client/components/ui/feed-icon" import { openInFollowApp } from "@client/lib/helper" import { UrlBuilder } from "@client/lib/url-builder" +import { useListsByUserId } from "@client/query/list" +import type { SubscriptionResult, User } from "@client/query/users" import { useUserQuery, useUserSubscriptionsQuery } from "@client/query/users" import { FollowIcon } from "@follow/components/icons/follow.jsx" import { Avatar, AvatarFallback, AvatarImage } from "@follow/components/ui/avatar/index.jsx" @@ -13,14 +15,17 @@ import { Fragment, memo, useState } from "react" import { useParams } from "react-router" interface FeedCardProps { - subscription: any + subscription: SubscriptionResult[number] feedId: string view: number } const FeedCard = memo(({ subscription, feedId, view }) => { + if (!("feeds" in subscription)) { + return null + } return ( -
+
{/* Feed Content */} (({ subscription, feedId, view }) => {
- {/* Follow Button */} -
+ {/* Follow Button - positioned absolutely to avoid layout shift */} +
+
+ + ))} +
+
+ ) +} + +const Subscriptions = ({ userId }: { userId: string }) => { + const subscriptions = useUserSubscriptionsQuery(userId) + const [expandedCategories, setExpandedCategories] = useState>({}) const toggleCategory = (category: string) => { @@ -87,128 +252,67 @@ export const Component = () => { [category]: !prev[category], })) } - - const totalFeeds = Object.values(subscriptions.data || {}).reduce( - (total, category) => total + category.length, - 0, - ) - return ( - - {user.isLoading ? ( - +
+ {subscriptions.isLoading ? ( +
+ +
) : ( - - {/* Hero Section */} -
-
- {/* Avatar */} -
- - - - {user.data?.name?.slice(0, 2)} - - -
+
+ {Object.keys(subscriptions.data || {}).map((category) => { + const isExpanded = expandedCategories[category] ?? true - {/* User Info */} -
-

- {user.data?.name} -

- {user.data?.handle && ( -

@{user.data.handle}

- )} + return ( +
+ {/* Category Header */} + - {/* Stats */} -
-
-
-
- {totalFeeds} -
-
Subscriptions
-
-
-
- {Object.keys(subscriptions.data || {}).length} -
-
Categories
-
+ {/* Feeds Grid with collapse animation */} +
+
+ {subscriptions.data?.[category]!.map( + (subscription) => + "feeds" in subscription && ( + + ), + )}
-
-
- - {/* Subscriptions Section */} -
- {subscriptions.isLoading ? ( -
- -
- ) : ( -
- {Object.keys(subscriptions.data || {}).map((category) => { - const isExpanded = expandedCategories[category] ?? true - - return ( -
- {/* Category Header */} - - - {/* Feeds Grid with collapse animation */} -
-
- {subscriptions.data?.[category]!.map( - (subscription) => - "feeds" in subscription && ( - - ), - )} -
-
-
- ) - })} -
- )} -
- + ) + })} +
)} - +
) } diff --git a/apps/ssr/client/pages/(main)/share/users/[id]/metadata.ts b/apps/ssr/client/pages/(main)/share/users/[id]/metadata.ts index 79ab620b4..9cf523bb6 100644 --- a/apps/ssr/client/pages/(main)/share/users/[id]/metadata.ts +++ b/apps/ssr/client/pages/(main)/share/users/[id]/metadata.ts @@ -23,16 +23,21 @@ export default defineMetadata(async ({ params, apiClient, origin }): Promise callNotFound(e.message)) const realUserId = profileRes.data.id - const [subscriptionsRes] = await Promise.allSettled([ + const [subscriptionsRes, listsRes] = await Promise.allSettled([ profileRes.data.id ? apiClient.subscriptions.$get({ query: { userId: realUserId }, }) : Promise.reject(), + profileRes.data.id + ? apiClient.lists.list.$get({ + query: { userId: realUserId }, + }) + : Promise.reject(), ]) const isSubscriptionsResolved = subscriptionsRes.status === "fulfilled" - + const isListsResolved = listsRes.status === "fulfilled" const { name } = profileRes.data const subscriptions = isSubscriptionsResolved ? subscriptionsRes.value.data : [] @@ -67,5 +72,11 @@ export default defineMetadata(async ({ params, apiClient, origin }): Promise !!v) as MetaTag[] }) diff --git a/apps/ssr/client/query/list.ts b/apps/ssr/client/query/list.ts index ee970089d..f7f86dce3 100644 --- a/apps/ssr/client/query/list.ts +++ b/apps/ssr/client/query/list.ts @@ -15,3 +15,18 @@ export const useList = ({ id }: { id?: string }) => ReturnType >, }) + +const fetchListsByUserId = async (userId: string) => { + const res = await apiClient.lists.list.$get({ query: { userId } }) + return res.data +} + +export const useListsByUserId = (userId: string) => + useQuery({ + queryKey: ["lists", userId], + queryFn: () => fetchListsByUserId(userId), + enabled: !!userId, + initialData: getHydrateData(`lists.list.$get,query:userId=${userId}`) as any as Awaited< + ReturnType + >, + }) diff --git a/apps/ssr/client/query/users.ts b/apps/ssr/client/query/users.ts index 79de81ef1..6e3746354 100644 --- a/apps/ssr/client/query/users.ts +++ b/apps/ssr/client/query/users.ts @@ -6,8 +6,10 @@ import type { ExtractBizResponse } from "@follow/models" import { capitalizeFirstLetter, isBizId, parseUrl } from "@follow/utils/utils" import { useQuery } from "@tanstack/react-query" -const groupSubscriptions = (subscriptions: SubscriptionResult) => { - const groupFolder = {} as Record +const groupSubscriptions = ( + subscriptions: SubscriptionResult, +): Record => { + const groupFolder = {} as Record for (const subscription of subscriptions.filter((s) => !s.isPrivate) || []) { if (!subscription.category && "feeds" in subscription) { const { siteUrl } = subscription.feeds @@ -25,19 +27,25 @@ const groupSubscriptions = (subscriptions: SubscriptionResult) => { return groupFolder } -type SubscriptionResult = ExtractBizResponse["data"] +export type SubscriptionResult = ExtractBizResponse["data"] +const fetchUserSubscriptions = async (userId: string | undefined) => { + const res = await apiClient.subscriptions.$get({ + query: { userId }, + }) + return res.data +} + export const useUserSubscriptionsQuery = (userId: string | undefined) => { return useQuery({ queryKey: ["subscriptions", "group", userId], queryFn: async () => { - const res = await apiClient.subscriptions.$get({ - query: { userId }, - }) - return res.data + return fetchUserSubscriptions(userId) }, select: groupSubscriptions, enabled: !!userId, - initialData: getHydrateData(`subscriptions.$get,query:userId=${userId}`), + initialData: getHydrateData(`subscriptions.$get,query:userId=${userId}`) as any as Awaited< + ReturnType + >, }) } @@ -57,12 +65,13 @@ export const fetchUser = async (handleOrId: string | undefined) => { return res.data } +export type User = Awaited> export const useUserQuery = (handleOrId: string | undefined) => { return useQuery({ queryKey: ["profiles", handleOrId], queryFn: () => fetchUser(handleOrId), enabled: !!handleOrId, - initialData: getHydrateData(`profiles.$get,query:id=${handleOrId}`), + initialData: getHydrateData(`profiles.$get,query:id=${handleOrId}`) as any as User, }) } export interface AuthProvider { diff --git a/packages/internal/shared/src/hono.ts b/packages/internal/shared/src/hono.ts index 3a3dcf257..b673e3f2a 100644 --- a/packages/internal/shared/src/hono.ts +++ b/packages/internal/shared/src/hono.ts @@ -19910,7 +19910,11 @@ declare const _routes: hono_hono_base.HonoBase