Skip to content

Commit

Permalink
feat: feed settings survey (#3174)
Browse files Browse the repository at this point in the history
  • Loading branch information
sshanzel committed Jun 5, 2024
1 parent 6d71a53 commit 0147b25
Show file tree
Hide file tree
Showing 22 changed files with 406 additions and 22 deletions.
24 changes: 23 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,18 @@ export default function Feed<T>({
insaneMode: listMode,
loadedSettings,
} = useContext(SettingsContext);
const { isFetched, alerts } = useAlertsContext();
const shouldEvaluateSurvey =
!!user &&
isFetched &&
alerts.shouldShowFeedFeedback &&
feedName === SharedFeedPage.MyFeed;
const { value: feedSurvey } = useConditionalFeature({
feature: feature.feedSettingsFeedback,
shouldEvaluate: shouldEvaluateSurvey,
});
const shouldShowSurvey = shouldEvaluateSurvey && feedSurvey;
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 +201,7 @@ export default function Feed<T>({
variables,
options,
showPublicSquadsEligibility,
shouldShowSurvey: shouldShowSurvey && isLaptop,
settings: {
disableAds,
adPostLength: isSquadFeed ? 2 : undefined,
Expand Down Expand Up @@ -408,6 +429,7 @@ export default function Feed<T>({
actionButtons={actionButtons}
isHorizontal={isHorizontal}
feedContainerRef={feedContainerRef}
shouldShowSurvey={shouldShowSurvey && !isLaptop}
>
{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
4 changes: 2 additions & 2 deletions packages/shared/src/components/buttons/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ function ButtonComponent<TagName extends AllowedTags>(
aria-pressed={pressed}
ref={ref}
className={classNames(
`btn shadow-none focus-outline inline-flex cursor-pointer select-none
flex-row items-center border no-underline transition
`btn focus-outline inline-flex cursor-pointer select-none flex-row
items-center border no-underline shadow-none transition
duration-200 ease-in-out typo-callout`,
variant !== ButtonVariant.Option && 'justify-center font-bold',
{ iconOnly },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function MarketingCtaCard({

trackEvent({
event_name: AnalyticsEvent.Impression,
target_type: TargetType.MarketingCtaCard,
target_type: TargetType.PromotionCard,
target_id: marketingCta.campaignId,
});
isImpressionTracked.current = true;
Expand All @@ -33,7 +33,7 @@ export function MarketingCtaCard({
const onCtaClick = useCallback(() => {
trackEvent({
event_name: AnalyticsEvent.Click,
target_type: TargetType.MarketingCtaCard,
target_type: TargetType.PromotionCard,
target_id: marketingCta.campaignId,
});
clearMarketingCta(marketingCta.campaignId);
Expand All @@ -42,7 +42,7 @@ export function MarketingCtaCard({
const onCtaDismiss = useCallback(() => {
trackEvent({
event_name: AnalyticsEvent.MarketingCtaDismiss,
target_type: TargetType.MarketingCtaCard,
target_type: TargetType.PromotionCard,
target_id: marketingCta.campaignId,
});
clearMarketingCta(marketingCta.campaignId);
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',
}
50 changes: 50 additions & 0 deletions packages/shared/src/components/cards/survey/FeedSurveyBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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';
import { feedSurveyBg, feedSurveyTopBorder } from '../../../styles/custom';

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="mb-4 pt-0.5" style={{ background: feedSurveyTopBorder }}>
<div
className="flex flex-col px-4 py-6 tablet:px-6"
style={{ background: feedSurveyBg }}
>
<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>
</div>
);
}
66 changes: 66 additions & 0 deletions packages/shared/src/components/cards/survey/FeedSurveyCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
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';
import { feedSurveyBg, feedSurveyBorder } from '../../../styles/custom';

export function FeedSurveyCard({
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-0.5"
style={{ backgroundImage: feedSurveyBorder }}
>
<CardComponent
className={classNames(
'relative items-center gap-4 overflow-hidden !rounded-14 border-0 shadow-none',
shouldUseListFeedLayout ? '!py-10' : 'pt-20',
)}
style={{ background: feedSurveyBg }}
>
<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';
6 changes: 3 additions & 3 deletions packages/shared/src/components/cards/v1/MarketingCtaCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function MarketingCtaCardV1({

trackEvent({
event_name: AnalyticsEvent.Impression,
target_type: TargetType.MarketingCtaCard,
target_type: TargetType.PromotionCard,
target_id: marketingCta.campaignId,
});
isImpressionTracked.current = true;
Expand All @@ -39,7 +39,7 @@ export function MarketingCtaCardV1({
const onCtaClick = useCallback(() => {
trackEvent({
event_name: AnalyticsEvent.Click,
target_type: TargetType.MarketingCtaCard,
target_type: TargetType.PromotionCard,
target_id: marketingCta.campaignId,
});
clearMarketingCta(marketingCta.campaignId);
Expand All @@ -48,7 +48,7 @@ export function MarketingCtaCardV1({
const onCtaDismiss = useCallback(() => {
trackEvent({
event_name: AnalyticsEvent.MarketingCtaDismiss,
target_type: TargetType.MarketingCtaCard,
target_type: TargetType.PromotionCard,
target_id: marketingCta.campaignId,
});
clearMarketingCta(marketingCta.campaignId);
Expand Down
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" />,
}}
/>
)}
<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>
);
}
Loading

0 comments on commit 0147b25

Please sign in to comment.