Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: feed settings survey #3174

Merged
merged 22 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion packages/shared/src/components/Feed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,14 @@ import ShareOptionsMenu from './ShareOptionsMenu';
import { SharedFeedPage } from './utilities';
import { FeedContainer, FeedContainerProps } from './feeds/FeedContainer';
import { ActiveFeedContext } from '../contexts';
import { useBoot, useFeedLayout, useFeedVotePost } from '../hooks';
import {
useBoot,
useConditionalFeature,
useFeedLayout,
useFeedVotePost,
useViewSize,
ViewSize,
} from '../hooks';
import {
AllFeedPages,
OtherFeedPage,
Expand All @@ -51,6 +58,7 @@ import { useFeature } from './GrowthBookProvider';
import { feature } from '../lib/featureManagement';
import { acquisitionKey } from './cards/AcquisitionFormCard';
import { MarketingCtaVariant } from './cards/MarketingCta/common';
import { useAlertsContext } from '../contexts/AlertContext';

export interface FeedProps<T>
extends Pick<
Expand Down Expand Up @@ -148,6 +156,16 @@ export default function Feed<T>({
insaneMode: listMode,
loadedSettings,
} = useContext(SettingsContext);
const { isFetched, alerts } = useAlertsContext();
const { value: shouldShowSurvey } = useConditionalFeature({
feature: feature.feedSettingsFeedback,
shouldEvaluate:
!!user &&
isFetched &&
alerts.shouldShowFeedFeedback &&
feedName === SharedFeedPage.MyFeed,
});
const isLaptop = useViewSize(ViewSize.Laptop);
const insaneMode = !forceCardMode && listMode;
const numCards = currentSettings.numCards[spaciness ?? 'eco'];
const isSquadFeed = feedName === OtherFeedPage.Squad;
Expand Down Expand Up @@ -181,6 +199,7 @@ export default function Feed<T>({
variables,
options,
showPublicSquadsEligibility,
shouldShowSurvey: shouldShowSurvey && isLaptop,
settings: {
disableAds,
adPostLength: isSquadFeed ? 2 : undefined,
Expand Down Expand Up @@ -408,6 +427,7 @@ export default function Feed<T>({
actionButtons={actionButtons}
isHorizontal={isHorizontal}
feedContainerRef={feedContainerRef}
shouldShowSurvey={shouldShowSurvey && !isLaptop}
nensidosari marked this conversation as resolved.
Show resolved Hide resolved
>
{items.map((_, index) => (
<FeedItemComponent
Expand Down
14 changes: 14 additions & 0 deletions packages/shared/src/components/FeedItemComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import { MarketingCtaCard, MarketingCtaList } from './cards';
import { MarketingCtaCardV1 } from './cards/v1/MarketingCtaCard';
import { FeedItemType } from './cards/common';
import { PublicSquadEligibilityCard } from './squads/PublicSquadEligibilityCard';
import { FeedSurveyCard } from './cards/survey';
import { FeedSettingsButton } from './feeds/FeedSettingsButton';

const CommentPopup = dynamic(
() => import(/* webpackChunkName: "commentPopup" */ './cards/CommentPopup'),
Expand Down Expand Up @@ -281,6 +283,18 @@ export default function FeedItemComponent({
showImage={!insaneMode}
/>
);
case FeedItemType.FeedSurvey:
return (
<FeedSurveyCard
title="Rate your feed quality"
max={5}
lowScore={{
value: 4,
message: 'Improve your feed by adjusting your settings.',
cta: <FeedSettingsButton className="mt-4" />,
}}
/>
);
case FeedItemType.UserAcquisition:
return <AcquisitionFormCard key="user-acquisition-card" />;
case FeedItemType.PublicSquadEligibility:
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/components/cards/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,5 @@ export enum FeedItemType {
UserAcquisition = 'userAcquisition',
PublicSquadEligibility = 'publicSquadEligibility',
MarketingCta = 'marketingCta',
FeedSurvey = 'feedSurvey',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React, { ReactElement, useState } from 'react';
import { RatingStars } from '../../utilities/RatingStars';
import { Button, ButtonSize, ButtonVariant } from '../../buttons/Button';
import { useFeedSurvey } from '../../../hooks/feed/useFeedSurvey';
import { FeedSurveyProps } from './common';

export function FeedSurveyBanner({
max,
title,
lowScore,
postFeedbackMessage = 'Thank you for your feedback!',
}: FeedSurveyProps): ReactElement {
const [score, setScore] = useState(0);
const { submitted, onSubmit, onHide } = useFeedSurvey({ score });

return (
<div
className="relative mb-4 flex flex-col px-4 py-6 tablet:px-6"
style={{
background:
'linear-gradient(90deg, rgba(255, 233, 35, 0.08) 0%, rgba(252, 64, 121, 0.08) 50%, rgba(113, 71, 237, 0.08) 100%)',
rebelchris marked this conversation as resolved.
Show resolved Hide resolved
}}
>
<div
className="absolute left-0 right-0 top-0 h-px"
style={{
background:
'linear-gradient(90deg, rgba(255, 233, 35, 1) 0%, rgba(252, 64, 121, 1) 50%, rgba(113, 71, 237, 1) 100%)',
}}
/>
<div className="flex flex-row items-start tablet:items-center">
<div className="flex flex-1 flex-col items-start justify-between tablet:flex-row tablet:items-center">
<h3 className="font-bold typo-title3">{title}</h3>
<RatingStars max={max} onStarClick={setScore} />
</div>
<Button
variant={score > 0 ? ButtonVariant.Primary : ButtonVariant.Float}
size={ButtonSize.Small}
onClick={score > 0 ? onSubmit : onHide}
className="ml-2 w-24"
disabled={submitted}
>
{score > 0 ? 'Submit' : 'Hide'}
</Button>
</div>
{submitted && (
<div className="mt-6 flex flex-col tablet:flex-row">
<p className="shrink text-text-secondary typo-body">
<strong className="mr-1">{postFeedbackMessage}</strong>
{score <= lowScore.value ? lowScore.message : null}
</p>
{score <= lowScore.value && lowScore.cta}
</div>
)}
</div>
);
}
72 changes: 72 additions & 0 deletions packages/shared/src/components/cards/survey/FeedSurveyCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React, { ReactElement, useState } from 'react';
import classNames from 'classnames';
import { Card as CardV1 } from '../v1/Card';
import { Card } from '../Card';
import { useFeedLayout } from '../../../hooks';
import { Button, ButtonSize, ButtonVariant } from '../../buttons/Button';
import { RatingStars } from '../../utilities/RatingStars';
import { useFeedSurvey } from '../../../hooks/feed/useFeedSurvey';
import { FeedSurveyProps } from './common';

export function FeedSurveyCard({
nensidosari marked this conversation as resolved.
Show resolved Hide resolved
max,
title,
lowScore,
postFeedbackMessage = 'Thank you for your feedback!',
}: FeedSurveyProps): ReactElement {
const [score, setScore] = useState(0);
const { submitted, onSubmit, onHide } = useFeedSurvey({ score });
const { shouldUseListFeedLayout } = useFeedLayout();
const CardComponent = shouldUseListFeedLayout ? CardV1 : Card;

return (
<div
className="relative rounded-16 p-px"
style={{
backgroundImage:
'linear-gradient(180deg, rgba(255, 233, 35, 1) 0%, rgba(252, 83, 141, 1) 50%, rgba(113, 71, 237, 1) 100%)',
nensidosari marked this conversation as resolved.
Show resolved Hide resolved
}}
>
<div className="absolute inset-px rounded-16 bg-background-default" />
<CardComponent
className={classNames(
'relative items-center gap-4 overflow-hidden',
shouldUseListFeedLayout ? '!py-10' : 'pt-20',
)}
omBratteng marked this conversation as resolved.
Show resolved Hide resolved
style={{
background:
'linear-gradient(180deg, rgba(255, 233, 35, 0.08) 0%, rgba(252, 83, 141, 0.08) 50%, rgba(113, 71, 237, 0.08) 100%)',
}}
>
<h3 className="font-bold typo-title3">{title}</h3>
<RatingStars max={max} isDisabled={submitted} onStarClick={setScore} />
{submitted && (
<p className="mt-2 text-center text-text-secondary typo-body">
<span className="font-bold">{postFeedbackMessage}</span>
{score <= lowScore.value && <p>{lowScore.message}</p>}
{score <= lowScore.value && lowScore.cta}
</p>
)}
{score > 0 && !submitted && (
<Button
variant={ButtonVariant.Primary}
size={ButtonSize.Small}
onClick={onSubmit}
>
Submit
</Button>
)}
{score === 0 && (
<Button
variant={ButtonVariant.Float}
size={ButtonSize.Small}
className="mb-2 mt-auto"
onClick={onHide}
>
Hide
</Button>
)}
</CardComponent>
</div>
);
}
12 changes: 12 additions & 0 deletions packages/shared/src/components/cards/survey/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ReactNode } from 'react';

export interface FeedSurveyProps {
title: string;
postFeedbackMessage?: string;
max: number;
lowScore: {
value: number;
message: string;
cta: ReactNode;
};
}
2 changes: 2 additions & 0 deletions packages/shared/src/components/cards/survey/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './FeedSurveyBanner';
export * from './FeedSurveyCard';
15 changes: 15 additions & 0 deletions packages/shared/src/components/feeds/FeedContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import ConditionalWrapper from '../ConditionalWrapper';
import { useActiveFeedNameContext } from '../../contexts';
import { feature } from '../../lib/featureManagement';
import { SharedFeedPage } from '../utilities';
import { FeedSurveyBanner } from '../cards/survey';
import { FeedSettingsButton } from './FeedSettingsButton';

export interface FeedContainerProps {
children: ReactNode;
Expand All @@ -37,6 +39,7 @@ export interface FeedContainerProps {
shortcuts?: ReactNode;
actionButtons?: ReactNode;
isHorizontal?: boolean;
shouldShowSurvey?: boolean;
feedContainerRef?: React.Ref<HTMLDivElement>;
}

Expand Down Expand Up @@ -145,6 +148,7 @@ export const FeedContainer = ({
shortcuts,
actionButtons,
isHorizontal,
shouldShowSurvey,
feedContainerRef,
}: FeedContainerProps): ReactElement => {
const { value: isShortcutsV1 } = useConditionalFeature({
Expand Down Expand Up @@ -269,6 +273,17 @@ export const FeedContainer = ({
</div>
)}
>
{shouldShowSurvey && (
<FeedSurveyBanner
title="Rate your feed quality"
max={5}
lowScore={{
value: 4,
message: 'Improve your feed by adjusting your settings.',
cta: <FeedSettingsButton className="mt-4 w-fit" />,
}}
Comment on lines +279 to +284
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these props actually? We always pass them exactly the same?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized we may have another survey that could use the same component.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, just felt a bit overkill seeing we use it in 2 spots

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, kinda having that dilemma too. I can maybe store these common props somewhere to make them consistent.

/>
)}
<div
className={classNames(
'grid',
Expand Down
42 changes: 42 additions & 0 deletions packages/shared/src/components/feeds/FeedSettingsButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React, { ReactElement } from 'react';
import {
Button,
ButtonProps,
ButtonSize,
ButtonVariant,
} from '../buttons/Button';
import { useAnalyticsContext } from '../../contexts/AnalyticsContext';
import { AnalyticsEvent } from '../../lib/analytics';
import { LazyModal } from '../modals/common/types';
import { useLazyModal } from '../../hooks/useLazyModal';
import { FilterIcon } from '../icons';

export function FeedSettingsButton({
onClick,
children = 'Feed settings',
...props
}: ButtonProps<'button'>): ReactElement {
const { trackEvent } = useAnalyticsContext();
const { openModal } = useLazyModal();
const onButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
trackEvent({ event_name: AnalyticsEvent.ManageTags });

if (onClick) {
onClick(event);
} else {
openModal({ type: LazyModal.FeedFilters, persistOnRouteChange: true });
}
};

return (
<Button
variant={ButtonVariant.Primary}
size={ButtonSize.Small}
icon={<FilterIcon />}
{...props}
onClick={onButtonClick}
>
{children}
</Button>
);
}
15 changes: 5 additions & 10 deletions packages/shared/src/components/filters/MyFeedHeading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { setShouldRefreshFeed } from '../../lib/refreshFeed';
import { SharedFeedPage } from '../utilities';
import { getFeedName } from '../../lib/feed';
import { useFeedName } from '../../hooks/feed/useFeedName';
import { FeedSettingsButton } from '../feeds/FeedSettingsButton';

export const filterAlertMessage = 'Edit your personal feed preferences here';

Expand All @@ -36,12 +37,6 @@ function MyFeedHeading({
const isLaptop = useViewSize(ViewSize.Laptop);
const feedName = getFeedName(router.pathname);
const { isCustomFeed } = useFeedName({ feedName });

const onClick = () => {
trackEvent({ event_name: AnalyticsEvent.ManageTags });
onOpenFeedFilters();
};

const onRefresh = async () => {
trackEvent({ event_name: AnalyticsEvent.RefreshFeed });
setShouldRefreshFeed(true);
Expand Down Expand Up @@ -80,18 +75,18 @@ function MyFeedHeading({
{isLaptop ? 'Refresh feed' : null}
</Button>
)}
<Button
<FeedSettingsButton
onClick={onOpenFeedFilters}
className="mr-auto"
size={ButtonSize.Medium}
variant={isLaptop ? ButtonVariant.Float : ButtonVariant.Tertiary}
className="mr-auto"
onClick={onClick}
icon={<FilterIcon />}
iconPosition={
shouldUseListFeedLayout ? ButtonIconPosition.Right : undefined
}
>
{!isMobile ? feedFiltersLabel : null}
</Button>
</FeedSettingsButton>
</>
);
}
Expand Down
Loading