diff --git a/packages/shared/src/components/Feed.tsx b/packages/shared/src/components/Feed.tsx index f3c2c4de8e..e20c249247 100644 --- a/packages/shared/src/components/Feed.tsx +++ b/packages/shared/src/components/Feed.tsx @@ -41,6 +41,7 @@ import { useFeedVotePost, useMutationSubscription, } from '../hooks'; +import { useProfileCompletionIndicator } from '../hooks/profile/useProfileCompletionIndicator'; import type { AllFeedPages } from '../lib/query'; import { OtherFeedPage, RequestKey } from '../lib/query'; @@ -129,6 +130,13 @@ const BriefCardFeed = dynamic( ), ); +const ProfileCompletionCard = dynamic( + () => + import( + /* webpackChunkName: "profileCompletionCard" */ './cards/ProfileCompletionCard' + ), +); + const calculateRow = (index: number, numCards: number): number => Math.floor(index / numCards); const calculateColumn = (index: number, numCards: number): number => @@ -203,6 +211,8 @@ export default function Feed({ shouldEvaluate: isMyFeed && hasNoBriefAction, }); const showBriefCard = isMyFeed && briefCardFeatureValue && hasNoBriefAction; + const { showIndicator: showProfileCompletionCard } = + useProfileCompletionIndicator(); const [getProducts] = useUpdateQuery(getProductsQueryOptions()); const { value: briefBannerPage } = useConditionalFeature({ @@ -527,10 +537,11 @@ export default function Feed({ const currentPageSize = pageSize ?? currentSettings.pageSize; const showPromoBanner = !!briefBannerPage; const columnsDiffWithPage = currentPageSize % virtualizedNumCards; + const showFirstSlotCard = showProfileCompletionCard || showBriefCard; const indexWhenShowingPromoBanner = currentPageSize * Number(briefBannerPage) - // number of items at that page columnsDiffWithPage * Number(briefBannerPage) - // cards let out of rows * page number - Number(showBriefCard); // if showing the brief card, we need to subtract 1 to the index + Number(showFirstSlotCard); // if showing a first slot card, we need to subtract 1 to the index return ( @@ -539,7 +550,14 @@ export default function Feed({ <>{emptyScreen} ) : ( <> - {showBriefCard && ( + {showProfileCompletionCard && ( + + )} + {showBriefCard && !showProfileCompletionCard && ( ; +}; + +const getCompletionItems = ( + completion: ProfileCompletion, +): CompletionItem[] => { + return [ + { + label: 'Profile image', + completed: completion.hasProfileImage, + redirectPath: `${webappUrl}settings/profile`, + cta: 'Add profile image', + benefit: + 'Stand out in comments and discussions. Profiles with photos get more engagement.', + }, + { + label: 'Headline', + completed: completion.hasHeadline, + redirectPath: `${webappUrl}settings/profile?field=bio`, + cta: 'Write your headline', + benefit: + 'Tell the community who you are. A good headline helps others connect with you.', + }, + { + label: 'Experience level', + completed: completion.hasExperienceLevel, + redirectPath: `${webappUrl}settings/profile?field=experienceLevel`, + cta: 'Set experience level', + benefit: + 'Get personalized content recommendations based on where you are in your career.', + }, + { + label: 'Work experience', + completed: completion.hasWork, + redirectPath: `${webappUrl}settings/profile/experience/work`, + cta: 'Add work experience', + benefit: + 'Showcase your background and unlock opportunities from companies looking for talent like you.', + }, + { + label: 'Education', + completed: completion.hasEducation, + redirectPath: `${webappUrl}settings/profile/experience/education`, + cta: 'Add education', + benefit: + 'Complete your story. Education helps others understand your journey.', + }, + ]; +}; + +// Using softer, less saturated purple tones that align with the brand +const profileCompletionCardBorder = + '1px solid color-mix(in srgb, var(--theme-accent-cabbage-subtler), transparent 50%)'; + +const profileCompletionCardBg = + 'linear-gradient(180deg, color-mix(in srgb, var(--theme-accent-cabbage-bolder), transparent 92%) 0%, color-mix(in srgb, var(--theme-accent-cabbage-bolder), transparent 96%) 100%)'; + +const profileCompletionButtonBg = + 'color-mix(in srgb, var(--theme-accent-cabbage-default), transparent 20%)'; + +export const ProfileCompletionCard = ({ + className, +}: ProfileCompletionCardProps): ReactElement | null => { + const { user } = useAuthContext(); + const profileCompletion = user?.profileCompletion; + + const items = useMemo( + () => (profileCompletion ? getCompletionItems(profileCompletion) : []), + [profileCompletion], + ); + + const incompleteItems = useMemo( + () => items.filter((item) => !item.completed), + [items], + ); + + const firstIncompleteItem = incompleteItems[0]; + const progress = profileCompletion?.percentage ?? 0; + const isCompleted = progress === 100; + + if (!profileCompletion || isCompleted || !firstIncompleteItem) { + return null; + } + + return ( +
+
+ + + Profile completion + + + {firstIncompleteItem.benefit} + + +
+
+ ); +}; + +export default ProfileCompletionCard;