From 374c1d0632dbcc5035be44c00d7c98a067e59d0e Mon Sep 17 00:00:00 2001 From: Innei Date: Sun, 28 Jul 2024 12:20:30 +0800 Subject: [PATCH] fix: user profile modal Signed-off-by: Innei --- src/renderer/src/atoms/user.ts | 12 +- src/renderer/src/components/user-button.tsx | 9 +- .../src/modules/discover/feed-form.tsx | 4 +- .../modules/entry-content/read-history.tsx | 20 +- src/renderer/src/modules/profile/hooks.ts | 63 +++++++ .../modules/profile/user-profile-modal.tsx | 178 ++++++++++++++++++ .../(with-layout)/profile/[id]/index.tsx | 168 +++++++---------- 7 files changed, 342 insertions(+), 112 deletions(-) create mode 100644 src/renderer/src/modules/profile/hooks.ts create mode 100644 src/renderer/src/modules/profile/user-profile-modal.tsx diff --git a/src/renderer/src/atoms/user.ts b/src/renderer/src/atoms/user.ts index e4df2a80a..584a9ad36 100644 --- a/src/renderer/src/atoms/user.ts +++ b/src/renderer/src/atoms/user.ts @@ -6,5 +6,13 @@ export const [, , useMe, useSetMe, getMe, setMe] = createAtomHooks( atom>(null), ) -export const [, , useLoginModalShow, useSetLoginModalShow, getLoginModalShow, setLoginModalShow] = - createAtomHooks(atom(false)) +export { useMe as useWhoAmI } + +export const [ + , + , + useLoginModalShow, + useSetLoginModalShow, + getLoginModalShow, + setLoginModalShow, +] = createAtomHooks(atom(false)) diff --git a/src/renderer/src/components/user-button.tsx b/src/renderer/src/components/user-button.tsx index b166f353e..83e51ed4f 100644 --- a/src/renderer/src/components/user-button.tsx +++ b/src/renderer/src/components/user-button.tsx @@ -10,9 +10,9 @@ import { defineQuery } from "@renderer/lib/defineQuery" import { nextFrame } from "@renderer/lib/dom" import { cn } from "@renderer/lib/utils" import { LoginModalContent } from "@renderer/modules/auth/LoginModalContent" +import { usePresentUserProfileModal } from "@renderer/modules/profile/hooks" import { useSettingModal } from "@renderer/modules/settings/modal/hooks" import { useSession } from "@renderer/queries/auth" -import { WEB_URL } from "@shared/constants" import type { FC } from "react" import { memo } from "react" import { Link } from "react-router-dom" @@ -68,9 +68,11 @@ export const ProfileButton: FC = memo((props) => { const { user } = session || {} const signOut = useSignOut() const settingModalPresent = useSettingModal() + const presentUserProfile = usePresentUserProfileModal() if (status !== "authenticated") { return } + return ( @@ -96,10 +98,7 @@ export const ProfileButton: FC = memo((props) => { { - window.open( - `${WEB_URL}/profile/${user?.handle || user?.id}`, - "_blank", - ) + presentUserProfile(user?.id) }} > diff --git a/src/renderer/src/modules/discover/feed-form.tsx b/src/renderer/src/modules/discover/feed-form.tsx index 631bdb5c0..f3dcfe5a9 100644 --- a/src/renderer/src/modules/discover/feed-form.tsx +++ b/src/renderer/src/modules/discover/feed-form.tsx @@ -72,11 +72,11 @@ export const FeedForm: Component<{ }, }) - const { setClickOutSideToDismiss } = useCurrentModal() + const { setClickOutSideToDismiss } = useCurrentModal() || {} useEffect(() => { if (form.formState.isDirty) { - setClickOutSideToDismiss(false) + setClickOutSideToDismiss?.(false) } }, [form.formState.isDirty]) diff --git a/src/renderer/src/modules/entry-content/read-history.tsx b/src/renderer/src/modules/entry-content/read-history.tsx index 165ea3efe..37cbde495 100644 --- a/src/renderer/src/modules/entry-content/read-history.tsx +++ b/src/renderer/src/modules/entry-content/read-history.tsx @@ -13,6 +13,8 @@ import { useEntryReadHistory } from "@renderer/store/entry" import { useUserById } from "@renderer/store/user" import { Fragment } from "react" +import { usePresentUserProfileModal } from "../profile/hooks" + export const EntryReadHistory: Component<{ entryId: string }> = ({ entryId, }) => { @@ -66,6 +68,7 @@ const EntryUser: Component<{ i: number }> = ({ userId, i }) => { const user = useUserById(userId) + const presentUserProfile = usePresentUserProfileModal() if (!user) return null return ( @@ -76,14 +79,21 @@ const EntryUser: Component<{ zIndex: i, }} > - - - {user.name?.slice(0, 2)} - + Recent reader: - {" "} {user.name} diff --git a/src/renderer/src/modules/profile/hooks.ts b/src/renderer/src/modules/profile/hooks.ts new file mode 100644 index 000000000..645b33228 --- /dev/null +++ b/src/renderer/src/modules/profile/hooks.ts @@ -0,0 +1,63 @@ +import { useModalStack } from "@renderer/components/ui/modal" +import { NoopChildren } from "@renderer/components/ui/modal/stacked/utils" +import { useAuthQuery } from "@renderer/hooks/common" +import { apiClient } from "@renderer/lib/api-fetch" +import { defineQuery } from "@renderer/lib/defineQuery" +import { capitalizeFirstLetter } from "@renderer/lib/utils" +import { createElement, useCallback } from "react" +import { parse } from "tldts" + +import { UserProfileModalContent } from "./user-profile-modal" + +export const useUserSubscriptionsQuery = (userId: string | undefined) => { + const subscriptions = useAuthQuery( + defineQuery(["subscriptions", userId], async () => { + const res = await apiClient.subscriptions.$get({ + query: { userId }, + }) + const groupFolder = {} as Record + + for (const subscription of res.data || []) { + if (!subscription.category && subscription.feeds) { + const { siteUrl } = subscription.feeds + if (!siteUrl) continue + const parsed = parse(siteUrl) + parsed.domain && + (subscription.category = capitalizeFirstLetter(parsed.domain)) + } + if (subscription.category) { + if (!groupFolder[subscription.category]) { + groupFolder[subscription.category] = [] + } + groupFolder[subscription.category].push(subscription) + } + } + + return groupFolder + }), + { + enabled: !!userId, + }, + ) + return subscriptions +} + +export const usePresentUserProfileModal = () => { + const { present } = useModalStack() + + return useCallback( + (userId: string | undefined) => { + if (!userId) return + present({ + title: "User Profile", + content: () => + createElement(UserProfileModalContent, { + userId, + }), + CustomModalComponent: NoopChildren, + clickOutsideToDismiss: true, + }) + }, + [present], + ) +} diff --git a/src/renderer/src/modules/profile/user-profile-modal.tsx b/src/renderer/src/modules/profile/user-profile-modal.tsx new file mode 100644 index 000000000..cd0849d89 --- /dev/null +++ b/src/renderer/src/modules/profile/user-profile-modal.tsx @@ -0,0 +1,178 @@ +import { getSidebarActiveView } from "@renderer/atoms/sidebar" +import { m } from "@renderer/components/common/Motion" +import { FeedIcon } from "@renderer/components/feed-icon" +import { FollowIcon } from "@renderer/components/icons/follow" +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@renderer/components/ui/avatar" +import { StyledButton } from "@renderer/components/ui/button" +import { LoadingCircle } from "@renderer/components/ui/loading" +import { useCurrentModal, useModalStack } from "@renderer/components/ui/modal" +import { useAuthQuery } from "@renderer/hooks/common" +import { apiClient } from "@renderer/lib/api-fetch" +import { defineQuery } from "@renderer/lib/defineQuery" +import { nextFrame } from "@renderer/lib/dom" +import type { FeedViewType } from "@renderer/lib/enum" +import { cn } from "@renderer/lib/utils" +import { useUserSubscriptionsQuery } from "@renderer/modules/profile/hooks" +import { useAnimationControls } from "framer-motion" +import type { FC } from "react" +import { Fragment, useEffect, useState } from "react" + +import { FeedForm } from "../discover/feed-form" + +export const UserProfileModalContent: FC<{ + userId: string +}> = ({ userId }) => { + const user = useAuthQuery( + defineQuery(["profiles", userId], async () => { + const res = await apiClient.profiles.$get({ + query: { id: userId! }, + }) + return res.data + }), + ) + + const subscriptions = useUserSubscriptionsQuery(user.data?.id) + const modal = useCurrentModal() + const controller = useAnimationControls() + useEffect(() => { + nextFrame(() => controller.start("enter")) + }, [controller]) + + const { present } = useModalStack() + const winHeight = useState(() => window.innerHeight)[0] + + return ( +
+ e.stopPropagation()} + tabIndex={-1} + initial="initial" + animate={controller} + variants={{ + enter: { + y: 0, + opacity: 1, + }, + initial: { + y: "100%", + opacity: 0.9, + }, + exit: { + y: winHeight, + }, + }} + transition={{ + type: "spring", + + mass: 0.4, + tension: 100, + friction: 1, + }} + exit="exit" + className="shadow-perfect perfect-sm relative flex max-h-[80vh] flex-col items-center overflow-hidden rounded-xl border bg-theme-background p-8" + > + + {user.data && ( + +
+ + + {user.data.name?.slice(0, 2)} + +
+
+

{user.data.name}

+
+
+ {user.data.handle} +
+
+
+ +
+ )} + + {!user.data && ( + + )} +
+
+ ) +} diff --git a/src/renderer/src/pages/(external)/(with-layout)/profile/[id]/index.tsx b/src/renderer/src/pages/(external)/(with-layout)/profile/[id]/index.tsx index eb07a47f3..006207349 100644 --- a/src/renderer/src/pages/(external)/(with-layout)/profile/[id]/index.tsx +++ b/src/renderer/src/pages/(external)/(with-layout)/profile/[id]/index.tsx @@ -9,10 +9,11 @@ import { StyledButton } from "@renderer/components/ui/button" import { useAuthQuery, useTitle } from "@renderer/hooks/common" import { apiClient } from "@renderer/lib/api-fetch" import { defineQuery } from "@renderer/lib/defineQuery" -import { capitalizeFirstLetter, cn } from "@renderer/lib/utils" +import { stopPropagation } from "@renderer/lib/dom" +import { cn } from "@renderer/lib/utils" +import { useUserSubscriptionsQuery } from "@renderer/modules/profile/hooks" import { DEEPLINK_SCHEME } from "@shared/constants" import { useParams } from "react-router-dom" -import { parse } from "tldts" export function Component() { const { id } = useParams() @@ -29,104 +30,75 @@ export function Component() { }, ) - const subscriptions = useAuthQuery( - defineQuery(["subscriptions", user.data?.id], async () => { - const res = await apiClient.subscriptions.$get({ - query: { userId: user.data?.id }, - }) - const groupFolder = {} as Record - - for (const subscription of res.data || []) { - if (!subscription.category && subscription.feeds) { - const { siteUrl } = subscription.feeds - if (!siteUrl) continue - const parsed = parse(siteUrl) - parsed.domain && - (subscription.category = capitalizeFirstLetter(parsed.domain)) - } - if (subscription.category) { - if (!groupFolder[subscription.category]) { - groupFolder[subscription.category] = [] - } - groupFolder[subscription.category].push(subscription) - } - } - - return groupFolder - }), - { - enabled: !!user.data?.id, - }, - ) + const subscriptions = useUserSubscriptionsQuery(user.data?.id) useTitle(user.data?.name) - return ( - <> - {user.data && ( -
- - - {user.data.name?.slice(0, 2)} - -
-
-

{user.data.name}

-
-
{user.data.handle}
-
-
- {Object.keys(subscriptions.data || {}).map((category) => ( -
-
-

{category}

-
-
- {subscriptions.data?.[category].map((subscription) => ( - - ))} -
-
- ))} -
+ if (!user.data) return null + return ( +
+ + + {user.data.name?.slice(0, 2)} + +
+
+

{user.data.name}

- )} - +
{user.data.handle}
+
+
+ {Object.keys(subscriptions.data || {}).map((category) => ( +
+
+

{category}

+
+
+ {subscriptions.data?.[category].map((subscription) => ( + + ))} +
+
+ ))} +
+
) }