diff --git a/packages/shared/src/components/Feed.tsx b/packages/shared/src/components/Feed.tsx index 8be4db5e450..bc01405f3fb 100644 --- a/packages/shared/src/components/Feed.tsx +++ b/packages/shared/src/components/Feed.tsx @@ -64,6 +64,7 @@ import { FeedCardContext } from '../features/posts/FeedCardContext'; import { briefCardFeedFeature, briefFeedEntrypointPage, + dailyShowFeedCardFeature, featureFeedAdTemplate, } from '../lib/featureManagement'; import type { AwardProps } from '../graphql/njord'; @@ -73,6 +74,8 @@ import { BriefBannerFeed } from './cards/brief/BriefBanner/BriefBannerFeed'; import { ActionType } from '../graphql/actions'; import { TopHero } from './banners/HeroBottomBanner'; import { useReadingReminderFeedHero } from '../hooks/notifications/useReadingReminderFeedHero'; +import { YoutubeLiveFeedCard } from './cards/common/YoutubeLiveFeedCard'; +import { syntaxLiveMockPost } from '../lib/syntaxLiveMockPost'; const FeedErrorScreen = dynamic( () => import(/* webpackChunkName: "feedErrorScreen" */ './FeedErrorScreen'), @@ -210,6 +213,17 @@ export default function Feed({ const isSquadFeed = feedName === OtherFeedPage.Squad; const trackedFeedFinish = useRef(false); const isMyFeed = feedName === SharedFeedPage.MyFeed; + const isDailyShowFeedCardEligible = + feedName === SharedFeedPage.MyFeed || + feedName === SharedFeedPage.Popular || + feedName === SharedFeedPage.Upvoted; + const { value: dailyShowFeedCardEnabled } = useConditionalFeature({ + feature: dailyShowFeedCardFeature, + shouldEvaluate: isDailyShowFeedCardEligible, + }); + const showDailyShowFeedCard = + isDailyShowFeedCardEligible && dailyShowFeedCardEnabled; + const dailyShowFeedCardIndex = 3; const showAcquisitionForm = isMyFeed && (routerQuery?.[acquisitionKey] as string)?.toLocaleLowerCase() === 'true' && @@ -655,6 +669,9 @@ export default function Feed({ }} /> )} + {showDailyShowFeedCard && index === dailyShowFeedCardIndex && ( + + )} {shouldShowInFeedHero && index === adjustedHeroInsertIndex && (
) => void; +}; + +const previewVideoSrc = `${webappUrl}assets/videos/syntax-live-preview.webm`; +const previewPosterSrc = `${webappUrl}assets/videos/syntax-live-preview-poster.jpg`; + +/** + * Card-shaped feed slot for the daily.dev show with a looping muted webm + * preview as background and bottom-aligned squad/title metadata. + */ +export function YoutubeLiveFeedCard({ + post, + style, + className, + onClick, +}: YoutubeLiveFeedCardProps): ReactElement { + const href = post.commentsPermalink; + const { shouldUseListFeedLayout } = useFeedLayout(); + + const handleClick = (event: MouseEvent) => { + if (!onClick) { + return; + } + event.preventDefault(); + onClick(event); + }; + + return ( + + +
+
+ + + +
+
+ + {post.title} + +
+ {post.source && ( + + )} + {post.source?.name && ( + + {post.source.name} + + )} +
+
+
+ + ); +} diff --git a/packages/shared/src/lib/featureManagement.ts b/packages/shared/src/lib/featureManagement.ts index 5ef53dfa2a6..bf4494c8173 100644 --- a/packages/shared/src/lib/featureManagement.ts +++ b/packages/shared/src/lib/featureManagement.ts @@ -135,6 +135,11 @@ export const boostSettingsFeature = new Feature('boost_settings', { export const adImprovementsV3Feature = new Feature('ad_improvements_v3', false); +export const dailyShowFeedCardFeature = new Feature( + 'daily_show_feed_card', + isDevelopment, +); + export const featureYearInReview = new Feature('year_in_review_2025', false); export const featureProfileCompletionIndicator = new Feature( diff --git a/packages/shared/src/lib/syntaxLiveMockPost.ts b/packages/shared/src/lib/syntaxLiveMockPost.ts new file mode 100644 index 00000000000..968e84bc5c3 --- /dev/null +++ b/packages/shared/src/lib/syntaxLiveMockPost.ts @@ -0,0 +1,51 @@ +import type { Post } from '../graphql/posts'; +import { PostType, UserVote } from '../graphql/posts'; +import { SourceType } from '../graphql/sources'; +import { SYNTAX_LIVE_VIDEO_ID } from './youtubeLive'; + +/** The daily.dev Show squad source for livestream experiment mocks. */ +export const syntaxSquadSource = { + handle: 'dailydevshow', + name: 'The daily.dev Show', + permalink: 'https://app.daily.dev/squads/dailydevshow', + id: '9836af88-07c6-41e0-a18d-f434aa64895c', + image: + 'https://media.daily.dev/image/upload/s--4GpvCvpD--/f_auto/v1777382360/squads/9836af88-07c6-41e0-a18d-f434aa64895c', + type: SourceType.Squad, + active: true, + public: true, + membersCount: 1000, +}; + +/** YouTube video post pointing at the Syntax.fm test live `videoId` (feed + modal QA). */ +export const syntaxLiveMockPost: Post = { + id: 'syntax-live-mock', + title: 'The roundup you actually need. S1E1: Goodbye Tim Apple.', + type: PostType.VideoYouTube, + videoId: SYNTAX_LIVE_VIDEO_ID, + createdAt: new Date().toISOString(), + image: + 'https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/youtube-placeholder', + commentsPermalink: 'https://daily.dev/posts/syntax-live-mock', + permalink: `https://www.youtube.com/watch?v=${SYNTAX_LIVE_VIDEO_ID}`, + source: syntaxSquadSource, + author: { + id: 'joao-graca', + name: 'Joao Graca', + username: 'joaograca', + permalink: 'https://app.daily.dev/joaograca', + image: 'https://i.pravatar.cc/64?img=12', + }, + tags: ['javascript'], + readTime: 0, + numComments: 0, + numUpvotes: 0, + bookmarked: false, + read: false, + upvoted: false, + commented: false, + userState: { + vote: UserVote.None, + flags: { feedbackDismiss: false }, + }, +} as Post; diff --git a/packages/shared/src/lib/youtubeLive.ts b/packages/shared/src/lib/youtubeLive.ts new file mode 100644 index 00000000000..58f53a52a3d --- /dev/null +++ b/packages/shared/src/lib/youtubeLive.ts @@ -0,0 +1,2 @@ +/** Canonical daily.dev Show test stream for the live card variation. */ +export const SYNTAX_LIVE_VIDEO_ID = 'XKO67n3xfzM'; diff --git a/packages/webapp/public/assets/videos/syntax-live-preview-poster.jpg b/packages/webapp/public/assets/videos/syntax-live-preview-poster.jpg new file mode 100644 index 00000000000..ae8e2f8baba Binary files /dev/null and b/packages/webapp/public/assets/videos/syntax-live-preview-poster.jpg differ diff --git a/packages/webapp/public/assets/videos/syntax-live-preview.webm b/packages/webapp/public/assets/videos/syntax-live-preview.webm new file mode 100644 index 00000000000..c60c62f7a31 Binary files /dev/null and b/packages/webapp/public/assets/videos/syntax-live-preview.webm differ