diff --git a/app/javascript/mastodon/components/account_header/buttons.tsx b/app/javascript/mastodon/components/account_header/buttons.tsx index a4db87e044d..2f33a88b6b9 100644 --- a/app/javascript/mastodon/components/account_header/buttons.tsx +++ b/app/javascript/mastodon/components/account_header/buttons.tsx @@ -3,12 +3,12 @@ import type { FC } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { useLocation } from 'react-router-dom'; - import { followAccount } from '@/mastodon/actions/accounts'; import { useAccount } from '@/mastodon/hooks/useAccount'; +import { useFollowReference } from '@/mastodon/hooks/useFollowReference'; import { getAccountHidden } from '@/mastodon/selectors/accounts'; import { useAppDispatch, useAppSelector } from '@/mastodon/store'; +import { isRedesignEnabled } from '@/mastodon/utils/environment'; import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react'; import NotificationsActiveIcon from '@/material-icons/400-24px/notifications_active-fill.svg?react'; import ShareIcon from '@/material-icons/400-24px/share.svg?react'; @@ -83,10 +83,8 @@ const AccountButtonsOther: FC< }); } }, [accountUrl]); - const { state } = useLocation<{ - reference?: string; - } | null>(); - const reference = state?.reference ?? 'profile'; + + const reference = useFollowReference('profile'); if (!account) { return null; @@ -99,8 +97,10 @@ const AccountButtonsOther: FC< <> {!isMovedAndUnfollowedAccount && ( diff --git a/app/javascript/mastodon/components/button/redesign.tsx b/app/javascript/mastodon/components/button/redesign.tsx index bc23c0037da..fb27aa28bba 100644 --- a/app/javascript/mastodon/components/button/redesign.tsx +++ b/app/javascript/mastodon/components/button/redesign.tsx @@ -107,7 +107,7 @@ const BaseButton: React.FC = ({ ); }; -type ButtonProps = BaseButtonProps & { +export type ButtonProps = BaseButtonProps & { leadingIcon?: IconProp; trailingIcon?: IconProp; }; diff --git a/app/javascript/mastodon/components/follow_button.tsx b/app/javascript/mastodon/components/follow_button.tsx index e5b822408bd..60b47f4bb51 100644 --- a/app/javascript/mastodon/components/follow_button.tsx +++ b/app/javascript/mastodon/components/follow_button.tsx @@ -18,6 +18,11 @@ import { me } from 'mastodon/initial_state'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; import { useBreakpoint } from '../features/ui/hooks/useBreakpoint'; +import { isRedesignEnabled } from '../utils/environment'; + +import { Button as RedesignButton } from './button/redesign'; +import type { ButtonProps as RedesignButtonProps } from './button/redesign'; +import type { MastodonLocationDescriptor } from './router'; const longMessages = defineMessages({ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, @@ -55,21 +60,29 @@ const shortMessages = { }), }; -export const FollowButton: React.FC<{ +interface FollowButtonOptions { accountId?: string; - compact?: boolean; labelLength?: 'auto' | 'short' | 'long'; - className?: string; withUnmute?: boolean; reference?: string; -}> = ({ +} + +interface FollowButtonReturn { + label: React.ReactNode; + onClick: (() => void) | undefined; + link: MastodonLocationDescriptor | undefined; + disabled: boolean; + following: boolean; + secondary: boolean; + hidden: boolean; +} + +export function useFollowButton({ accountId, - compact, labelLength = 'auto', - className, withUnmute = true, reference, -}) => { +}: FollowButtonOptions): FollowButtonReturn { const intl = useIntl(); const dispatch = useAppDispatch(); const { signedIn } = useIdentity(); @@ -79,7 +92,9 @@ export const FollowButton: React.FC<{ const relationship = useAppSelector((state) => accountId ? state.relationships.get(accountId) : undefined, ); - const following = relationship?.following || relationship?.requested; + const following = relationship?.following || relationship?.requested || false; + const secondary = following || relationship?.blocking || false; + const link = accountId === me ? '/profile/edit' : undefined; useEffect(() => { if (accountId && signedIn) { @@ -87,7 +102,7 @@ export const FollowButton: React.FC<{ } }, [dispatch, accountId, signedIn]); - const handleClick = useCallback(() => { + const onClick = useCallback(() => { if (!signedIn) { dispatch( openModal({ @@ -148,7 +163,7 @@ export const FollowButton: React.FC<{ ? messages.followRequest : messages.follow; - let label; + let label: React.ReactNode; let disabled = relationship?.blocked_by || account?.suspended || !!account?.moved; @@ -176,7 +191,30 @@ export const FollowButton: React.FC<{ label = intl.formatMessage(followMessage); } - if (accountId === me) { + const isMovedAndUnfollowedAccount = + (account?.moved && !relationship?.following) || false; + + return { + onClick, + link, + label, + disabled, + following, + secondary, + hidden: isMovedAndUnfollowedAccount, + }; +} + +const FollowButtonLegacy: React.FC< + FollowButtonOptions & { + compact?: boolean; + className?: string; + } +> = ({ compact, className, ...props }) => { + const { onClick, link, label, disabled, following, secondary } = + useFollowButton(props); + + if (link) { const buttonClasses = classNames(className, 'button button-secondary', { 'button--compact': compact, }); @@ -190,9 +228,9 @@ export const FollowButton: React.FC<{ return ( ); }; + +const FollowButtonRedesign: React.FC< + FollowButtonOptions & + Pick +> = ({ accountId, labelLength, withUnmute, reference, ...buttonProps }) => { + const { onClick, link, label, disabled, secondary, hidden } = useFollowButton( + { + accountId, + labelLength, + withUnmute, + reference, + }, + ); + + if (hidden) { + return null; + } + + if (link) { + return ( + + {label} + + ); + } + + return ( + + {label} + + ); +}; + +export const FollowButton: React.FC< + FollowButtonOptions & { + compact?: boolean; + className?: string; + } +> = ({ compact, ...props }) => { + if (isRedesignEnabled()) { + return ; + } + return ; +}; diff --git a/app/javascript/mastodon/hooks/useFollowReference.ts b/app/javascript/mastodon/hooks/useFollowReference.ts new file mode 100644 index 00000000000..d4e72cb47c4 --- /dev/null +++ b/app/javascript/mastodon/hooks/useFollowReference.ts @@ -0,0 +1,17 @@ +import { useLocation } from 'react-router-dom'; + +import type { LocationState } from '../components/router'; + +/** + * Gets the current `reference` state from location state if present, + * otherwise returns the passed-in fallback reference. + * + * `reference` is a string that we use to anonymously & locally track + * where a follow is coming from, helping us understand which UI features + * people actually use to discover people. + */ +export function useFollowReference(fallbackReference: string) { + const { state } = useLocation(); + + return state?.reference ?? fallbackReference; +}