fix(ssr): restore shared user profiles
This commit is contained in:
parent
cf224d63f5
commit
110960a6f0
|
|
@ -7,7 +7,7 @@ import { m, useAnimationControls } from "motion/react"
|
|||
import { Fragment, useEffect, useState } from "react"
|
||||
import * as React from "react"
|
||||
|
||||
const NotFoundContent = () => {
|
||||
export const NotFoundContent = () => {
|
||||
const [glitchText, setGlitchText] = useState("404")
|
||||
const [isGlitching, setIsGlitching] = useState(false)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { NotFoundContent } from "@client/components/common/404"
|
||||
import { FeedIcon } from "@client/components/ui/feed-icon"
|
||||
import { openInFollowApp } from "@client/lib/helper"
|
||||
import { UrlBuilder } from "@client/lib/url-builder"
|
||||
|
|
@ -10,6 +11,7 @@ import { LoadingCircle } from "@follow/components/ui/loading/index.jsx"
|
|||
import { useTitle } from "@follow/hooks"
|
||||
import { cn } from "@follow/utils/utils"
|
||||
import type { SubscriptionWithFeed, UserProfile } from "@follow-app/client-sdk"
|
||||
import { FollowAPIError } from "@follow-app/client-sdk"
|
||||
import * as React from "react"
|
||||
import { Fragment, memo, useState } from "react"
|
||||
import { useParams } from "react-router"
|
||||
|
|
@ -89,22 +91,43 @@ export const Component = () => {
|
|||
|
||||
useTitle(user.data?.name)
|
||||
|
||||
if (user.isLoading) {
|
||||
return <LoadingCircle size="large" className="center fixed inset-0" />
|
||||
}
|
||||
|
||||
if (!user.data) {
|
||||
if (user.error instanceof FollowAPIError && user.error.status === 404) {
|
||||
return <NotFoundContent />
|
||||
}
|
||||
|
||||
return <ProfileLoadError onRetry={() => void user.refetch()} />
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{user.isLoading ? (
|
||||
<LoadingCircle size="large" className="center fixed inset-0" />
|
||||
) : (
|
||||
<Fragment>
|
||||
<UserHero user={user.data!} />
|
||||
<Lists userId={user.data?.id} />
|
||||
{/* Subscriptions Section */}
|
||||
<Subscriptions userId={user.data?.id} />
|
||||
</Fragment>
|
||||
)}
|
||||
</>
|
||||
<Fragment>
|
||||
<UserHero user={user.data} />
|
||||
<Lists userId={user.data.id} />
|
||||
{/* Subscriptions Section */}
|
||||
<Subscriptions userId={user.data.id} />
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
const ProfileLoadError = ({ onRetry }: { onRetry: () => void }) => (
|
||||
<div className="mx-auto flex min-h-[60vh] max-w-xl flex-col items-center justify-center px-6 text-center">
|
||||
<i className="i-mgc-warning-fill mb-6 size-12 text-orange-500" />
|
||||
<h1 className="text-2xl font-semibold text-zinc-900 dark:text-zinc-100">
|
||||
Unable to load this profile
|
||||
</h1>
|
||||
<p className="mt-3 text-zinc-500 dark:text-zinc-400">
|
||||
This may be a temporary problem. Please try again.
|
||||
</p>
|
||||
<Button buttonClassName="mt-8" onClick={onRetry}>
|
||||
Try again
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
const UserHero = ({ user }: { user: UserProfile }) => {
|
||||
const subscriptions = useUserSubscriptionsQuery(user.id)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { followClient } from "@client/lib/api-fetch"
|
|||
import { getProviders } from "@client/lib/auth"
|
||||
import { getHydrateData } from "@client/lib/helper"
|
||||
import type { LoginHydrateData } from "@client/pages/(login)/login/metadata"
|
||||
import { isBizId, sortByAlphabet } from "@follow/utils/utils"
|
||||
import { sortByAlphabet } from "@follow/utils/utils"
|
||||
import type {
|
||||
InboxSubscriptionResponse,
|
||||
ListSubscriptionResponse,
|
||||
|
|
@ -10,6 +10,8 @@ import type {
|
|||
} from "@follow-app/client-sdk"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
|
||||
import { getUserProfile } from "../../src/lib/user-profile-params"
|
||||
|
||||
type GetUserSubscriptionsResponse = (
|
||||
SubscriptionWithFeed | ListSubscriptionResponse | InboxSubscriptionResponse
|
||||
)[]
|
||||
|
|
@ -69,13 +71,7 @@ export const useUserSubscriptionsQuery = (userId: string | undefined) => {
|
|||
}
|
||||
|
||||
export const fetchUser = async (handleOrId: string | undefined) => {
|
||||
const handle = isBizId(handleOrId || "")
|
||||
? handleOrId
|
||||
: `${handleOrId}`.startsWith("@")
|
||||
? `${handleOrId}`.slice(1)
|
||||
: handleOrId
|
||||
|
||||
const res = await followClient.api.profiles.getProfile({ id: handleOrId, handle })
|
||||
const res = await getUserProfile(followClient, handleOrId)
|
||||
return res.data
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
import type { FollowClient } from "@follow-app/client-sdk"
|
||||
import { describe, expect, it, vi } from "vitest"
|
||||
|
||||
import { getUserProfile, resolveUserProfileParams } from "./user-profile-params"
|
||||
|
||||
vi.mock("@follow/utils/utils", () => ({
|
||||
isBizId: (value: string | undefined) => value === "41125409313095680",
|
||||
}))
|
||||
|
||||
describe("resolveUserProfileParams", () => {
|
||||
it("uses a handle without also sending it as an id", () => {
|
||||
expect(resolveUserProfileParams("DIYgod")).toEqual({
|
||||
id: undefined,
|
||||
handle: "DIYgod",
|
||||
})
|
||||
})
|
||||
|
||||
it("removes a leading at sign from handles", () => {
|
||||
expect(resolveUserProfileParams("@DIYgod")).toEqual({
|
||||
id: undefined,
|
||||
handle: "DIYgod",
|
||||
})
|
||||
})
|
||||
|
||||
it("uses a business id without also sending it as a handle", () => {
|
||||
expect(resolveUserProfileParams("41125409313095680")).toEqual({
|
||||
id: "41125409313095680",
|
||||
handle: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it("uses the resolved parameters for the profile request", async () => {
|
||||
const getProfile = vi.fn().mockResolvedValue({ data: { id: "profile-id" } })
|
||||
const apiClient = {
|
||||
api: {
|
||||
profiles: {
|
||||
getProfile,
|
||||
},
|
||||
},
|
||||
} as unknown as FollowClient
|
||||
|
||||
await getUserProfile(apiClient, "DIYgod")
|
||||
|
||||
expect(getProfile).toHaveBeenCalledOnce()
|
||||
expect(getProfile).toHaveBeenCalledWith({
|
||||
id: undefined,
|
||||
handle: "DIYgod",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { isBizId } from "@follow/utils/utils"
|
||||
import type { FollowClient } from "@follow-app/client-sdk"
|
||||
|
||||
export const resolveUserProfileParams = (handleOrId: string | undefined) => {
|
||||
if (isBizId(handleOrId || "")) {
|
||||
return {
|
||||
id: handleOrId,
|
||||
handle: undefined,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: undefined,
|
||||
handle: handleOrId?.startsWith("@") ? handleOrId.slice(1) : handleOrId,
|
||||
}
|
||||
}
|
||||
|
||||
export const getUserProfile = (apiClient: FollowClient, handleOrId: string | undefined) =>
|
||||
apiClient.api.profiles.getProfile(resolveUserProfileParams(handleOrId))
|
||||
|
|
@ -1,17 +1,12 @@
|
|||
import { isBizId } from "@follow/utils/utils"
|
||||
import type { FollowClient } from "@follow-app/client-sdk"
|
||||
import * as React from "react"
|
||||
|
||||
import { renderToImage } from "../../lib/og/render-to-image"
|
||||
import { getUserProfile } from "../../lib/user-profile-params"
|
||||
import { getImageBase64, OGAvatar, OGCanvas } from "./__base"
|
||||
|
||||
export const renderUserOG = async (apiClient: FollowClient, id: string) => {
|
||||
const handle = isBizId(id || "") ? id : `${id}`.startsWith("@") ? `${id}`.slice(1) : id
|
||||
|
||||
const user = await apiClient.api.profiles.getProfile({
|
||||
id,
|
||||
handle,
|
||||
})
|
||||
export const renderUserOG = async (apiClient: FollowClient, handleOrId: string) => {
|
||||
const user = await getUserProfile(apiClient, handleOrId)
|
||||
|
||||
if (!user) {
|
||||
throw 404
|
||||
|
|
|
|||
|
|
@ -3,7 +3,11 @@
|
|||
"name": "folo-ssr",
|
||||
"main": "dist/worker/worker-entry.mjs",
|
||||
"compatibility_date": "2026-02-01",
|
||||
"compatibility_flags": ["nodejs_compat"],
|
||||
"compatibility_flags": [
|
||||
"nodejs_compat",
|
||||
// The SSR Worker fetches api.folo.is, another Worker Route in the same zone.
|
||||
"global_fetch_strictly_public",
|
||||
],
|
||||
"observability": {
|
||||
"logs": {
|
||||
"enabled": true,
|
||||
|
|
|
|||
Loading…
Reference in New Issue