diff --git a/packages/shared/src/components/SharedByUserBanner.tsx b/packages/shared/src/components/SharedByUserBanner.tsx deleted file mode 100644 index 0a8aec8589..0000000000 --- a/packages/shared/src/components/SharedByUserBanner.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import type { ReactElement } from 'react'; -import React from 'react'; -import { useRouter } from 'next/router'; -import classNames from 'classnames'; -import { useAuthContext } from '../contexts/AuthContext'; -import { ProfileImageSize, ProfilePicture } from './ProfilePicture'; -import { - Typography, - TypographyColor, - TypographyType, -} from './typography/Typography'; -import { FollowButton } from './contentPreference/FollowButton'; -import { ContentPreferenceType } from '../graphql/contentPreference'; -import { Origin } from '../lib/log'; -import type { WithClassNameProps } from './utilities'; -import { useUserShortByIdQuery } from '../hooks/user/useUserShortByIdQuery'; -import { useContentPreferenceStatusQuery } from '../hooks/contentPreference/useContentPreferenceStatusQuery'; - -export const SharedByUserBanner = ({ - className, -}: WithClassNameProps): ReactElement => { - const { user: currentUser } = useAuthContext(); - const { query } = useRouter(); - const userid = (query?.userid as string) || null; - - const { data: contentPreference } = useContentPreferenceStatusQuery({ - id: userid, - entity: ContentPreferenceType.User, - queryOptions: { enabled: !!userid && userid !== currentUser?.id }, - }); - const { data: user } = useUserShortByIdQuery({ id: userid }); - if (!userid || userid === currentUser?.id || !user) { - return null; - } - - return ( -
- - - {user.name} Shared this post - - -
- ); -}; diff --git a/packages/shared/src/components/post/PostContent.tsx b/packages/shared/src/components/post/PostContent.tsx index 8e08127666..61e9a91930 100644 --- a/packages/shared/src/components/post/PostContent.tsx +++ b/packages/shared/src/components/post/PostContent.tsx @@ -24,7 +24,6 @@ import { cloudinaryPostImageCoverPlaceholder } from '../../lib/image'; import { withPostById } from './withPostById'; import { PostClickbaitShield } from './common/PostClickbaitShield'; import { useSmartTitle } from '../../hooks/post/useSmartTitle'; -import { SharedByUserBanner } from '../SharedByUserBanner'; import { SmartPrompt } from './smartPrompts/SmartPrompt'; import { PostTagList } from './tags/PostTagList'; @@ -147,7 +146,6 @@ export function PostContentRaw({ origin={origin} post={post} > -

- -
Promise) | (() => unknown); interface UseToastNotification { - displayToast: (message: string, params?: NotifyOptionalProps) => void; + displayToast: ( + message: string | ReactNode, + params?: NotifyOptionalProps, + ) => void; dismissToast: () => void; subject?: ToastSubject; } @@ -15,7 +19,7 @@ export enum ToastSubject { } export interface ToastNotification { - message: string; + message: string | ReactNode; timer: number; subject?: ToastSubject; onUndo?: AnyFunction; @@ -39,7 +43,7 @@ export const useToastNotification = (): UseToastNotification => { client.setQueryData(TOAST_NOTIF_KEY, data); const displayToast = ( - message: string, + message: string | ReactNode, { timer = 5000, ...props }: NotifyOptionalProps = {}, ) => setToastNotification({ message, timer, ...props }); diff --git a/packages/webapp/hooks/useSharedByToast.tsx b/packages/webapp/hooks/useSharedByToast.tsx new file mode 100644 index 0000000000..ef9c37834c --- /dev/null +++ b/packages/webapp/hooks/useSharedByToast.tsx @@ -0,0 +1,116 @@ +import React, { useEffect, useRef } from 'react'; +import { useRouter } from 'next/router'; +import Link from 'next/link'; +import { useAuthContext } from '@dailydotdev/shared/src/contexts/AuthContext'; +import { useToastNotification } from '@dailydotdev/shared/src/hooks/useToastNotification'; +import { useContentPreferenceStatusQuery } from '@dailydotdev/shared/src/hooks/contentPreference/useContentPreferenceStatusQuery'; +import { + ContentPreferenceStatus, + ContentPreferenceType, +} from '@dailydotdev/shared/src/graphql/contentPreference'; +import { useUserShortByIdQuery } from '@dailydotdev/shared/src/hooks/user/useUserShortByIdQuery'; +import { useContentPreference } from '@dailydotdev/shared/src/hooks/contentPreference/useContentPreference'; +import { ButtonSize } from '@dailydotdev/shared/src/components/buttons/common'; +import { + ProfileImageSize, + ProfilePicture, +} from '@dailydotdev/shared/src/components/ProfilePicture'; +import { Button } from '@dailydotdev/shared/src/components/buttons/Button'; +import { + Typography, + TypographyTag, + TypographyType, +} from '@dailydotdev/shared/src/components/typography/Typography'; + +const useSharedByToast = (): void => { + const hasShownToast = useRef(false); + const { user: currentUser, isAuthReady } = useAuthContext(); + const { query } = useRouter(); + const userId = (query?.userid as string) || null; + const isSameUser = !!userId && userId === currentUser?.id; + const { data: contentPreference, isPending } = + useContentPreferenceStatusQuery({ + id: userId, + entity: ContentPreferenceType.User, + }); + const { data: user } = useUserShortByIdQuery({ id: userId }); + const { follow } = useContentPreference({ showToastOnSuccess: true }); + const { displayToast, dismissToast } = useToastNotification(); + const isDataReady = isAuthReady || (currentUser && !isPending); + + useEffect(() => { + if ( + !user || + isSameUser || + !isDataReady || + hasShownToast.current || + contentPreference?.status === ContentPreferenceStatus.Blocked + ) { + return undefined; + } + + const showFollow = + !!currentUser && + contentPreference?.status !== ContentPreferenceStatus.Follow; + + const timeout = setTimeout(() => { + hasShownToast.current = true; + displayToast( +
+ dismissToast()} + className="flex items-center gap-2 " + href={`/${user.username}`} + > + + + + {user.name} + {' '} + + shared this post + + + + {showFollow && ( + + )} +
, + ); + // Set a small timeout to ensure its shown after the page is loaded and won't be cleared by updates. + }, 1000); + + return () => clearTimeout(timeout); + }, [ + user, + contentPreference?.status, + hasShownToast, + displayToast, + dismissToast, + follow, + isSameUser, + isDataReady, + currentUser, + ]); +}; + +export default useSharedByToast; diff --git a/packages/webapp/pages/posts/[id]/index.tsx b/packages/webapp/pages/posts/[id]/index.tsx index 96f10470f8..4616b5526d 100644 --- a/packages/webapp/pages/posts/[id]/index.tsx +++ b/packages/webapp/pages/posts/[id]/index.tsx @@ -41,6 +41,7 @@ import { PostSEOSchema, } from '../../../components/PostSEOSchema'; import type { DynamicSeoProps } from '../../../components/common'; +import useSharedByToast from '../../../hooks/useSharedByToast'; const Unauthorized = dynamic( () => @@ -124,6 +125,7 @@ export const PostPage = ({ id, initialData, error }: Props): ReactElement => { [PostType.Share, PostType.Welcome, PostType.Freeform].includes(post?.type), featureTheme && 'bg-transparent', ); + useSharedByToast(); useScrollTopOffset(() => globalThis.window, { onOverOffset: () => position !== 'fixed' && setPosition('fixed'),