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;
+}