diff --git a/packages/shared/src/components/FeedItemComponent.tsx b/packages/shared/src/components/FeedItemComponent.tsx index 99382f176a6..39970ce1ee7 100644 --- a/packages/shared/src/components/FeedItemComponent.tsx +++ b/packages/shared/src/components/FeedItemComponent.tsx @@ -20,6 +20,7 @@ import { FeedItemType } from './cards/common/common'; import { AdGrid } from './cards/ad/AdGrid'; import { AdList } from './cards/ad/AdList'; import { SignalAdList } from './cards/ad/SignalAdList'; +import type { AdCardProps } from './cards/ad/common/common'; import { AcquisitionFormGrid } from './cards/AcquisitionForm/AcquisitionFormGrid'; import { AcquisitionFormList } from './cards/AcquisitionForm/AcquisitionFormList'; import { FreeformGrid } from './cards/Freeform/FreeformGrid'; @@ -394,9 +395,12 @@ function FeedItemComponent({ } switch (item.type) { - case FeedItemType.Ad: + case FeedItemType.Ad: { + const AdComponent = AdTag as React.ForwardRefExoticComponent< + AdCardProps & React.RefAttributes + >; return ( - ); + } case FeedItemType.UserAcquisition: return ; case FeedItemType.MarketingCta: diff --git a/packages/shared/src/components/LoginButton.spec.tsx b/packages/shared/src/components/LoginButton.spec.tsx index 9747eb252eb..eec80ce67e7 100644 --- a/packages/shared/src/components/LoginButton.spec.tsx +++ b/packages/shared/src/components/LoginButton.spec.tsx @@ -18,7 +18,9 @@ describe('LoginButton', () => { logEvent.mockReset(); }); - const renderLayout = (user: LoggedUser = null): RenderResult => { + const renderLayout = ( + user: LoggedUser = null as unknown as LoggedUser, + ): RenderResult => { const client = new QueryClient(); return render( diff --git a/packages/shared/src/components/ProfileMenu/ProfileSectionItem.tsx b/packages/shared/src/components/ProfileMenu/ProfileSectionItem.tsx index 9337e940aee..77688c441aa 100644 --- a/packages/shared/src/components/ProfileMenu/ProfileSectionItem.tsx +++ b/packages/shared/src/components/ProfileMenu/ProfileSectionItem.tsx @@ -9,7 +9,6 @@ import { TypographyTag, TypographyType, } from '../typography/Typography'; -import ConditionalWrapper from '../ConditionalWrapper'; import { combinedClicks } from '../../lib/click'; import { ArrowIcon, OpenLinkIcon } from '../icons'; import { anchorDefaultRel } from '../../lib/strings'; @@ -55,56 +54,54 @@ export const ProfileSectionItem = ({ typography, }: ProfileSectionItemProps): ReactElement => { const isMobile = useViewSize(ViewSize.MobileL); - const tag = href ? TypographyTag.Link : TypographyTag.Button; - const showLinkIcon = href && external; const openNewTab = showLinkIcon && !href.startsWith(webappUrl); - - return ( - ( - - {children} - + const content = ( + + tag={tag} + color={typography?.color ?? TypographyColor.Tertiary} + type={typography?.type ?? TypographyType.Subhead} + className={classNames( + 'flex h-10 cursor-pointer items-center gap-2 rounded-10 px-1 tablet:h-8', + (href || onClick) && 'hover:bg-surface-float', + isActive ? 'bg-surface-active' : undefined, + className, )} + {...combinedClicks(() => onClick?.())} + {...(openNewTab && { target: '_blank', rel: anchorDefaultRel })} > - - tag={tag} - color={typography?.color ?? TypographyColor.Tertiary} - type={typography?.type ?? TypographyType.Subhead} - className={classNames( - 'flex h-10 cursor-pointer items-center gap-2 rounded-10 px-1 tablet:h-8', - (href || onClick) && 'hover:bg-surface-float', - isActive ? 'bg-surface-active' : undefined, - className, - )} - {...combinedClicks(() => onClick?.())} - {...(openNewTab && { target: '_blank', rel: anchorDefaultRel })} - > - {Icon && ( - - )} - {title} + {Icon && ( + + )} + {title} - {!isMobile && showLinkIcon && ( - - )} + {!isMobile && showLinkIcon && ( + + )} - {isMobile && !external && ( - - )} - - + {isMobile && !external && ( + + )} + + ); + + if (!href) { + return content; + } + + return ( + + {content} + ); }; diff --git a/packages/shared/src/components/auth/AuthenticationBanner.tsx b/packages/shared/src/components/auth/AuthenticationBanner.tsx index 75c33a51408..e0daedef18d 100644 --- a/packages/shared/src/components/auth/AuthenticationBanner.tsx +++ b/packages/shared/src/components/auth/AuthenticationBanner.tsx @@ -50,7 +50,7 @@ export function AuthenticationBanner({
} trigger={AuthTriggers.Onboarding} simplified defaultDisplay={AuthDisplay.OnboardingSignup} diff --git a/packages/shared/src/components/auth/CodeVerificationForm.tsx b/packages/shared/src/components/auth/CodeVerificationForm.tsx index 7206755ebca..d4ed0858c75 100644 --- a/packages/shared/src/components/auth/CodeVerificationForm.tsx +++ b/packages/shared/src/components/auth/CodeVerificationForm.tsx @@ -41,7 +41,7 @@ function CodeVerificationForm({ setEmailSent(true); }, onVerifyCodeSuccess: () => { - onSubmit(); + onSubmit?.(); }, }, ); diff --git a/packages/shared/src/components/auth/CustomAuthBanner.tsx b/packages/shared/src/components/auth/CustomAuthBanner.tsx index 95b61a3a230..addb44421b8 100644 --- a/packages/shared/src/components/auth/CustomAuthBanner.tsx +++ b/packages/shared/src/components/auth/CustomAuthBanner.tsx @@ -7,7 +7,7 @@ import { useViewSize, ViewSize } from '../../hooks'; import LoginButton from '../LoginButton'; import { authGradientBg } from '../banners'; -const CustomAuthBanner = (): ReactElement => { +const CustomAuthBanner = (): ReactElement | null => { const { shouldShowAuthBanner } = useOnboardingActions(); const { shouldShowLogin } = useAuthContext(); const isLaptop = useViewSize(ViewSize.Laptop); diff --git a/packages/shared/src/components/auth/EmailSignupForm.tsx b/packages/shared/src/components/auth/EmailSignupForm.tsx index 74ce83ef8f9..9ce1c0c65b6 100644 --- a/packages/shared/src/components/auth/EmailSignupForm.tsx +++ b/packages/shared/src/components/auth/EmailSignupForm.tsx @@ -18,7 +18,7 @@ function EmailSignupForm({ isReady, showDisclaimer = true, }: EmailSignupFormProps): ReactElement { - const [email, setEmail] = useState(null); + const [email, setEmail] = useState(null); const inputId = useId(); const isSubmitDisabled = !email || !isReady; diff --git a/packages/shared/src/components/auth/SignBackButton.tsx b/packages/shared/src/components/auth/SignBackButton.tsx index 321aa8e6710..7524b36af4c 100644 --- a/packages/shared/src/components/auth/SignBackButton.tsx +++ b/packages/shared/src/components/auth/SignBackButton.tsx @@ -18,7 +18,7 @@ export function SignBackButton({ disabled = false, onClick, }: SignBackButtonProps): ReactElement { - const item = providerMap[provider]; + const item = providerMap[provider.toLowerCase() as keyof typeof providerMap]; return ( )} diff --git a/packages/shared/src/components/post/PostLoadingSkeleton.tsx b/packages/shared/src/components/post/PostLoadingSkeleton.tsx index 1f9e2a8b498..1a1a32c783f 100644 --- a/packages/shared/src/components/post/PostLoadingSkeleton.tsx +++ b/packages/shared/src/components/post/PostLoadingSkeleton.tsx @@ -15,7 +15,7 @@ function PostLoadingSkeleton({ type, className, hasNavigation, -}: PostLoadingSkeletonProps): ReactElement { +}: PostLoadingSkeletonProps): ReactElement | null { if (!type) { return null; } diff --git a/packages/shared/src/components/post/PostUpvotesCommentsCount.tsx b/packages/shared/src/components/post/PostUpvotesCommentsCount.tsx index b28ddce0cc8..672a676d6c6 100644 --- a/packages/shared/src/components/post/PostUpvotesCommentsCount.tsx +++ b/packages/shared/src/components/post/PostUpvotesCommentsCount.tsx @@ -61,7 +61,7 @@ export function PostUpvotesCommentsCount({ )} {upvotes > 0 && ( - onUpvotesClick(upvotes)}> + onUpvotesClick?.(upvotes)}> {largeNumberFormat(upvotes)} Upvote{upvotes > 1 ? 's' : ''} )} diff --git a/packages/shared/src/components/post/RelatedPostsWidget.tsx b/packages/shared/src/components/post/RelatedPostsWidget.tsx index 40a7c56dc85..2d4b7c2e573 100644 --- a/packages/shared/src/components/post/RelatedPostsWidget.tsx +++ b/packages/shared/src/components/post/RelatedPostsWidget.tsx @@ -24,7 +24,7 @@ export const RelatedPostsWidget = ({ post, perPage = RELATED_POSTS_PER_PAGE_DEFAULT, relationType, -}: RelatedPostsWidgetProps): ReactElement => { +}: RelatedPostsWidgetProps): ReactElement | null => { const { relatedPosts, isLoading, diff --git a/packages/shared/src/components/post/analytics/PostShortInfo.tsx b/packages/shared/src/components/post/analytics/PostShortInfo.tsx index de717de803a..45f8fabde5c 100644 --- a/packages/shared/src/components/post/analytics/PostShortInfo.tsx +++ b/packages/shared/src/components/post/analytics/PostShortInfo.tsx @@ -28,7 +28,7 @@ export function PostShortInfo({ className, showImage = true, showLinkIcon = true, -}: PostShortInfoProps): ReactElement { +}: PostShortInfoProps): ReactElement | null { const postLink = useMemo(() => { if (!post) { return undefined; diff --git a/packages/shared/src/components/post/brief/BriefPostContent.tsx b/packages/shared/src/components/post/brief/BriefPostContent.tsx index d1f5b74eb8a..ad37fd2cbff 100644 --- a/packages/shared/src/components/post/brief/BriefPostContent.tsx +++ b/packages/shared/src/components/post/brief/BriefPostContent.tsx @@ -458,7 +458,7 @@ const BriefPostContentRaw = ({ className={{ button: '!max-w-40', }} - hourIndex={digestTimeIndex} + hourIndex={digestTimeIndex ?? 0} setHourIndex={(index) => { onSubscribeDigest({ preferredHour: index, diff --git a/packages/shared/src/components/post/collection/CollectionSubscribeButton.tsx b/packages/shared/src/components/post/collection/CollectionSubscribeButton.tsx index b6e3aa51d78..184a86b3b88 100644 --- a/packages/shared/src/components/post/collection/CollectionSubscribeButton.tsx +++ b/packages/shared/src/components/post/collection/CollectionSubscribeButton.tsx @@ -35,7 +35,7 @@ export const CollectionSubscribeButton = ({ referenceId: post.id, }, ] - : undefined, + : [], }); const isSubscribed = !!preferences?.some((item) => checkHasStatusPreference( diff --git a/packages/shared/src/components/post/common/PostClickbaitShield.tsx b/packages/shared/src/components/post/common/PostClickbaitShield.tsx index 36bd3c82757..ccfb2504582 100644 --- a/packages/shared/src/components/post/common/PostClickbaitShield.tsx +++ b/packages/shared/src/components/post/common/PostClickbaitShield.tsx @@ -83,6 +83,12 @@ export const PostClickbaitShield = ({ post }: { post: Post }): ReactElement => { type: LazyModal.ClickbaitShield, }); } else { + if (!user) { + throw new Error( + 'PostClickbaitShield requires an authenticated user to edit feed settings', + ); + } + router.push( `${webappUrl}feeds/${user.id}/edit?dview=${FeedSettingsMenu.AI}`, ); diff --git a/packages/shared/src/components/post/smartPrompts/SmartPrompt.tsx b/packages/shared/src/components/post/smartPrompts/SmartPrompt.tsx index 12ba653e72e..b1df9448a2e 100644 --- a/packages/shared/src/components/post/smartPrompts/SmartPrompt.tsx +++ b/packages/shared/src/components/post/smartPrompts/SmartPrompt.tsx @@ -91,7 +91,7 @@ export const SmartPrompt = ({ diff --git a/packages/shared/src/components/profile/ActivitySection.tsx b/packages/shared/src/components/profile/ActivitySection.tsx index e9218c210da..b108e8d656c 100644 --- a/packages/shared/src/components/profile/ActivitySection.tsx +++ b/packages/shared/src/components/profile/ActivitySection.tsx @@ -53,7 +53,7 @@ export default function ActivitySection({ {title} - {count >= 0 && ( + {count !== undefined && count >= 0 && ( ({count}) )} diff --git a/packages/shared/src/components/profile/ControlledAvatarUpload.tsx b/packages/shared/src/components/profile/ControlledAvatarUpload.tsx index ade7d9e03fa..99541681820 100644 --- a/packages/shared/src/components/profile/ControlledAvatarUpload.tsx +++ b/packages/shared/src/components/profile/ControlledAvatarUpload.tsx @@ -40,7 +40,7 @@ const ControlledAvatarUpload = ({ >
Profile avatar { const [open, setOpen] = React.useState(false); const [selectedIndex, setSelectedIndex] = React.useState( - Object.keys(UserExperienceLevel).indexOf(defaultValue), + Object.keys(UserExperienceLevel).indexOf(defaultValue ?? ''), ); const { diff --git a/packages/shared/src/components/profile/ExperienceSettings.tsx b/packages/shared/src/components/profile/ExperienceSettings.tsx index 11884da5e04..ccf3bfd3cfb 100644 --- a/packages/shared/src/components/profile/ExperienceSettings.tsx +++ b/packages/shared/src/components/profile/ExperienceSettings.tsx @@ -17,13 +17,13 @@ export const ExperienceSettings = ({ experienceType, emptyStateMessage, }: ExperienceSettingsProps): ReactElement => { - const { user } = useAuthContext(); + const { user, isAuthReady } = useAuthContext(); const { experiences, isPending } = useUserExperiencesByType( experienceType, - user?.id, + user?.id ?? '', ); - if (isPending) { + if (!isAuthReady || !user || isPending) { return (
diff --git a/packages/shared/src/components/profile/JoinedDate.tsx b/packages/shared/src/components/profile/JoinedDate.tsx index 195d4432fe0..9ab41d62c55 100644 --- a/packages/shared/src/components/profile/JoinedDate.tsx +++ b/packages/shared/src/components/profile/JoinedDate.tsx @@ -11,7 +11,7 @@ export default function JoinedDate({ date, dateFormat = 'MMMM y', ...props -}: JoinedDateProps): ReactElement { +}: JoinedDateProps): ReactElement | null { if (!isValid(date)) { return null; } diff --git a/packages/shared/src/components/profile/ProfileButton.spec.tsx b/packages/shared/src/components/profile/ProfileButton.spec.tsx index 9cfa770487e..35571e16823 100644 --- a/packages/shared/src/components/profile/ProfileButton.spec.tsx +++ b/packages/shared/src/components/profile/ProfileButton.spec.tsx @@ -38,7 +38,7 @@ const renderComponent = (user = defaultUser): RenderResult => { getRedirectUri: jest.fn(), closeLogin: jest.fn(), trackingId: '21', - loginState: null, + loginState: undefined, }} > diff --git a/packages/shared/src/components/profile/ProfileButton.tsx b/packages/shared/src/components/profile/ProfileButton.tsx index cda76d8e2a8..0ba3a8b38b8 100644 --- a/packages/shared/src/components/profile/ProfileButton.tsx +++ b/packages/shared/src/components/profile/ProfileButton.tsx @@ -36,7 +36,7 @@ export default function ProfileButton({ settingsIconOnly, }: ProfileButtonProps): ReactElement { const { isOpen, onUpdate, wrapHandler } = useInteractivePopup(); - const { user } = useAuthContext(); + const { user, isAuthReady } = useAuthContext(); const { streak, isLoading, isStreaksEnabled } = useReadingStreak(); const hasCoresAccess = useHasAccessToCores(); const [animatedCores, setAnimatedCores] = useState(null); @@ -193,6 +193,10 @@ export default function ProfileButton({ }; }, [playCounterImpactAnimation, user?.balance?.amount, user?.reputation]); + if (!isAuthReady || !user) { + return <>; + } + return ( <> {settingsIconOnly ? ( diff --git a/packages/shared/src/components/profile/ProfileLink.tsx b/packages/shared/src/components/profile/ProfileLink.tsx index f06c75cfa75..38bbb0492ff 100644 --- a/packages/shared/src/components/profile/ProfileLink.tsx +++ b/packages/shared/src/components/profile/ProfileLink.tsx @@ -13,7 +13,7 @@ function ProfileLinkComponent( ref?: Ref, ): ReactElement { return ( - + ( ))} - {isWide && memberships.edges.length > MAX_SQUADS && ( + {isWide && (memberships?.edges.length ?? 0) > MAX_SQUADS && ( diff --git a/packages/shared/src/components/search/SearchPanel/SearchPanelContext.ts b/packages/shared/src/components/search/SearchPanel/SearchPanelContext.ts index fc36494d360..44f22f87e0c 100644 --- a/packages/shared/src/components/search/SearchPanel/SearchPanelContext.ts +++ b/packages/shared/src/components/search/SearchPanel/SearchPanelContext.ts @@ -3,7 +3,7 @@ import { createContext } from 'react'; import { SearchProviderEnum } from '../../../graphql/search'; export type SearchPanelContextValue = { - provider: SearchProviderEnum; + provider?: SearchProviderEnum; providerText?: string; providerIcon?: ReactElement; query: string; @@ -13,7 +13,7 @@ export type SearchPanelContextValue = { text, icon, }: { - provider: SearchProviderEnum; + provider?: SearchProviderEnum; text?: string; icon?: ReactElement; }) => void; diff --git a/packages/shared/src/components/search/SearchPanel/SearchPanelDropdown.tsx b/packages/shared/src/components/search/SearchPanel/SearchPanelDropdown.tsx index cbe9109ca7b..e834f84522a 100644 --- a/packages/shared/src/components/search/SearchPanel/SearchPanelDropdown.tsx +++ b/packages/shared/src/components/search/SearchPanel/SearchPanelDropdown.tsx @@ -59,7 +59,11 @@ const SearchPanelDropdown = ({ query = '', anchor }: Props): ReactElement => { event.preventDefault(); - const indexModifier = keyToIndexModifier[pressedKey]; + const indexModifier = keyToIndexModifier[pressedKey as ArrowKeyEnum]; + + if (indexModifier === undefined) { + return; + } const nextElement = navigableElements[activeElementIndex + indexModifier]; diff --git a/packages/shared/src/components/search/SearchPanel/SearchPanelInputCursor.tsx b/packages/shared/src/components/search/SearchPanel/SearchPanelInputCursor.tsx index e1657eb6eca..e763f52909d 100644 --- a/packages/shared/src/components/search/SearchPanel/SearchPanelInputCursor.tsx +++ b/packages/shared/src/components/search/SearchPanel/SearchPanelInputCursor.tsx @@ -13,7 +13,7 @@ export type SearchPanelInputCursorProps = { export const SearchPanelInputCursor = ({ className, -}: SearchPanelInputCursorProps): ReactElement => { +}: SearchPanelInputCursorProps): ReactElement | null => { const searchPanel = useContext(SearchPanelContext); const purify = useDomPurify(); diff --git a/packages/shared/src/components/search/SearchPanel/SearchPanelPostSuggestions.tsx b/packages/shared/src/components/search/SearchPanel/SearchPanelPostSuggestions.tsx index fd8710f9668..c66801e77f7 100644 --- a/packages/shared/src/components/search/SearchPanel/SearchPanelPostSuggestions.tsx +++ b/packages/shared/src/components/search/SearchPanel/SearchPanelPostSuggestions.tsx @@ -54,7 +54,7 @@ const PanelItem = ({ export const SearchPanelPostSuggestions = ({ className, title, -}: SearchPanelPostSuggestionsProps): ReactElement => { +}: SearchPanelPostSuggestionsProps): ReactElement | null => { const router = useRouter(); const { logEvent } = useLogContext(); const searchPanel = useContext(SearchPanelContext); diff --git a/packages/shared/src/components/search/SearchPanel/SearchPanelProvider.tsx b/packages/shared/src/components/search/SearchPanel/SearchPanelProvider.tsx index f98d950b0e9..bc4770a3209 100644 --- a/packages/shared/src/components/search/SearchPanel/SearchPanelProvider.tsx +++ b/packages/shared/src/components/search/SearchPanel/SearchPanelProvider.tsx @@ -21,13 +21,17 @@ const providerToComponentMap: Record< export const SearchPanelProvider = ({ className, -}: SearchPanelProviderProps): ReactElement => { +}: SearchPanelProviderProps): ReactElement | null => { const searchPanel = useContext(SearchPanelContext); if (searchPanel.providerIcon) { return searchPanel.providerIcon; } + if (!searchPanel.provider) { + return null; + } + const Component = providerToComponentMap[searchPanel.provider]; if (!Component) { diff --git a/packages/shared/src/components/search/SearchResults/SearchResultsTags.tsx b/packages/shared/src/components/search/SearchResults/SearchResultsTags.tsx index eb75d2e3aa6..fe6fdf6e1b6 100644 --- a/packages/shared/src/components/search/SearchResults/SearchResultsTags.tsx +++ b/packages/shared/src/components/search/SearchResults/SearchResultsTags.tsx @@ -13,7 +13,7 @@ interface SearchResultsTagsProps { export const SearchResultsTags = ( props: SearchResultsTagsProps, -): ReactElement => { +): ReactElement | null => { const { items = [], isLoading, onTagClick } = props; if (!isLoading && !items.length) { diff --git a/packages/shared/src/components/sidebar/ClickableNavItem.tsx b/packages/shared/src/components/sidebar/ClickableNavItem.tsx index bb8bc8a911e..c60dd39fe98 100644 --- a/packages/shared/src/components/sidebar/ClickableNavItem.tsx +++ b/packages/shared/src/components/sidebar/ClickableNavItem.tsx @@ -34,8 +34,14 @@ export function ClickableNavItem({ ); if (!isButton && (!item.action || item.path)) { + const itemPath = item.path; + + if (!itemPath) { + throw new Error('ClickableNavItem link items require a path'); + } + return ( - + { +}: SidebarProps): ReactElement | null => { const isLaptop = useViewSize(ViewSize.Laptop); const isTablet = useViewSize(ViewSize.Tablet); const featureTheme = useFeatureTheme(); diff --git a/packages/shared/src/components/sidebar/SidebarItem.spec.tsx b/packages/shared/src/components/sidebar/SidebarItem.spec.tsx index ab4f08fcb01..45eb5199f63 100644 --- a/packages/shared/src/components/sidebar/SidebarItem.spec.tsx +++ b/packages/shared/src/components/sidebar/SidebarItem.spec.tsx @@ -17,7 +17,7 @@ const renderComponent = (shouldShowLabel: boolean) => render( showLogin({ trigger: item.title as AuthTriggersType }) - : null + : undefined } isButton={isItemsButton && !item?.isForcedLink} > diff --git a/packages/shared/src/components/sidebar/SidebarList.tsx b/packages/shared/src/components/sidebar/SidebarList.tsx index 8e5e3ababe8..7fff7d0ee80 100644 --- a/packages/shared/src/components/sidebar/SidebarList.tsx +++ b/packages/shared/src/components/sidebar/SidebarList.tsx @@ -56,7 +56,7 @@ function SidebarList({ size={ButtonSize.Small} className="flex -rotate-90 tablet:hidden" icon={} - onClick={onRequestClose} + onClick={onRequestClose ?? undefined} /> {title} diff --git a/packages/shared/src/components/sidebar/common.tsx b/packages/shared/src/components/sidebar/common.tsx index b24bfadef6a..b6a1619254c 100644 --- a/packages/shared/src/components/sidebar/common.tsx +++ b/packages/shared/src/components/sidebar/common.tsx @@ -33,7 +33,7 @@ export interface SidebarMenuItem { } interface ListIconProps { - Icon: React.ComponentType<{ className }>; + Icon: React.ComponentType<{ className?: string }>; } export interface ItemInnerProps { diff --git a/packages/shared/src/components/sidebar/sections/BookmarkSection.tsx b/packages/shared/src/components/sidebar/sections/BookmarkSection.tsx index 9cf1cc10bd6..b747aea9c0b 100644 --- a/packages/shared/src/components/sidebar/sections/BookmarkSection.tsx +++ b/packages/shared/src/components/sidebar/sections/BookmarkSection.tsx @@ -29,7 +29,9 @@ export const BookmarkSection = ({ const { createFolder } = useCreateBookmarkFolder(); const isLaptop = useViewSize(ViewSize.Laptop); - const rightIcon = !isLaptop && (() => ); + const rightIcon = !isLaptop + ? () => + : undefined; const handleAddFolder = useCallback(() => { openModal({ @@ -43,7 +45,7 @@ export const BookmarkSection = ({ }); }, [openModal, closeModal, createFolder]); - const menuItems: SidebarMenuItem[] = [ + const allMenuItems = [ briefUIFeatureValue && { icon: (active: boolean) => ( } /> @@ -86,7 +88,10 @@ export const BookmarkSection = ({ requiresLogin: true, rightIcon, })), - ].filter(Boolean); + ]; + const menuItems: SidebarMenuItem[] = allMenuItems.filter( + Boolean, + ) as SidebarMenuItem[]; return (
{ +}: SquadCommentJoinBannerProps): ReactElement | null => { const queryClient = useQueryClient(); const [isSquadMember, setIsSquadMember] = useState(!!squad?.currentMember); const isMobile = useViewSize(ViewSize.MobileL); diff --git a/packages/shared/src/components/squads/SquadPostListItem.tsx b/packages/shared/src/components/squads/SquadPostListItem.tsx index e8f36bf3bdf..db392689898 100644 --- a/packages/shared/src/components/squads/SquadPostListItem.tsx +++ b/packages/shared/src/components/squads/SquadPostListItem.tsx @@ -46,7 +46,7 @@ export const SquadPostListItem = ({ /> )}

- {post.title ?? post.sharedPost.title} + {post.title ?? post.sharedPost?.title}

diff --git a/packages/shared/src/components/squads/SquadTabs.tsx b/packages/shared/src/components/squads/SquadTabs.tsx index 1cc1b7f2437..ca8c74544d6 100644 --- a/packages/shared/src/components/squads/SquadTabs.tsx +++ b/packages/shared/src/components/squads/SquadTabs.tsx @@ -24,7 +24,9 @@ export function SquadTabs({ active, handle }: SquadTabsProps): ReactElement { const { count } = useSquadPendingPosts({ squadId: squad?.id, }); - const isModerator = verifyPermission(squad, SourcePermissions.ModeratePost); + const isModerator = squad + ? verifyPermission(squad, SourcePermissions.ModeratePost) + : false; const squadLink = `${webappUrl}squads/${handle}`; const pendingTabLabel = count ? `${SquadTab.PendingPosts} (${count})` diff --git a/packages/shared/src/components/squads/layout/SquadDirectoryNavbarItem.tsx b/packages/shared/src/components/squads/layout/SquadDirectoryNavbarItem.tsx index 65b5d81a0f4..6d2b2ae87f5 100644 --- a/packages/shared/src/components/squads/layout/SquadDirectoryNavbarItem.tsx +++ b/packages/shared/src/components/squads/layout/SquadDirectoryNavbarItem.tsx @@ -2,7 +2,6 @@ import classNames from 'classnames'; import type { ComponentProps, ReactElement } from 'react'; import React from 'react'; import Link from '../../utilities/Link'; -import ConditionalWrapper from '../../ConditionalWrapper'; import type { ButtonProps, ButtonSize } from '../../buttons/Button'; import { Button, ButtonVariant } from '../../buttons/Button'; @@ -23,6 +22,22 @@ export function SquadDirectoryNavbarItem({ onClick, elementProps = {}, }: SquadNavbarItemProps): ReactElement { + const button = ( + + ); + return (
  • - ( - // TODO: WT-2239 - Remove legacyBehavior prop once all SquadDirectoryNavbarItem components are updated - - {component} - - )} - > - - + {path ? ( + // TODO: WT-2239 - Remove legacyBehavior prop once all SquadDirectoryNavbarItem components are updated + + {button} + + ) : ( + button + )}
  • ); } diff --git a/packages/shared/src/components/squads/layout/useSquadDirectoryLayout.ts b/packages/shared/src/components/squads/layout/useSquadDirectoryLayout.ts index 43b7c2f10f1..7dcd2e383de 100644 --- a/packages/shared/src/components/squads/layout/useSquadDirectoryLayout.ts +++ b/packages/shared/src/components/squads/layout/useSquadDirectoryLayout.ts @@ -19,7 +19,7 @@ export const useSquadDirectoryLayout = (): SquadDirectoryLayoutReturn => { const { data: categories, isFetched } = useSquadCategories(); const tabs = useMemo(() => { - const path = { ...squadCategoriesPaths }; + const path: Partial> = { ...squadCategoriesPaths }; if (!isFetched) { return {}; @@ -44,7 +44,7 @@ export const useSquadDirectoryLayout = (): SquadDirectoryLayoutReturn => { return { hasSquad, squads, - categoryPaths: tabs, + categoryPaths: tabs as Record, isMobileLayout: !isLaptop, }; }; diff --git a/packages/shared/src/components/squads/stack/SourceStackModal.tsx b/packages/shared/src/components/squads/stack/SourceStackModal.tsx index 9858a086df5..e3955baffd2 100644 --- a/packages/shared/src/components/squads/stack/SourceStackModal.tsx +++ b/packages/shared/src/components/squads/stack/SourceStackModal.tsx @@ -3,7 +3,10 @@ import React, { useMemo, useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; -import type { ModalProps } from '../../modals/common/Modal'; +import type { + LazyModalCommonProps, + ModalProps, +} from '../../modals/common/Modal'; import { Modal } from '../../modals/common/Modal'; import { TextField } from '../../fields/TextField'; import { Button, ButtonVariant } from '../../buttons/Button'; @@ -23,7 +26,8 @@ const sourceStackFormSchema = z.object({ type SourceStackFormData = z.infer; -type SourceStackModalProps = Omit & { +type SourceStackModalProps = Omit & { + onRequestClose?: LazyModalCommonProps['onRequestClose']; onSubmit: (input: AddSourceStackInput) => Promise; existingItem?: SourceStack; }; @@ -67,7 +71,7 @@ export function SourceStackModal({ await onSubmit({ title: data.title.trim(), }); - rest.onRequestClose?.(null); + rest.onRequestClose?.(undefined); }); const filteredSuggestions = useMemo(() => { diff --git a/packages/shared/src/components/squads/utils.tsx b/packages/shared/src/components/squads/utils.tsx index da2f560756f..ad64953d98d 100644 --- a/packages/shared/src/components/squads/utils.tsx +++ b/packages/shared/src/components/squads/utils.tsx @@ -60,7 +60,7 @@ export const createModerationPromptProps: PromptOptions = { title: 'Got it', className: 'tablet:w-full', }, - cancelButton: null, + cancelButton: undefined, promptSize: ModalSize.XSmall, icon: , }; diff --git a/packages/shared/src/components/streak/popup/DayStreak.tsx b/packages/shared/src/components/streak/popup/DayStreak.tsx index d32030bb909..fbfb4a341fd 100644 --- a/packages/shared/src/components/streak/popup/DayStreak.tsx +++ b/packages/shared/src/components/streak/popup/DayStreak.tsx @@ -87,7 +87,7 @@ export function DayStreak({ /> )} {renderIcon()} - {dateFormatInTimezone(date, 'iiiii', user.timezone)} + {dateFormatInTimezone(date, 'iiiii', user?.timezone ?? 'UTC')}
    ); diff --git a/packages/shared/src/components/tags/TagElement.tsx b/packages/shared/src/components/tags/TagElement.tsx index 8686daa653e..a7086ca6d57 100644 --- a/packages/shared/src/components/tags/TagElement.tsx +++ b/packages/shared/src/components/tags/TagElement.tsx @@ -21,21 +21,10 @@ export const TagElement = ({ isHighlighted = false, ...attrs }: OnboardingTagProps): ReactElement => { - return ( - + ); + } + + return ( + ); }; diff --git a/packages/shared/src/components/tooltip/Tooltip.tsx b/packages/shared/src/components/tooltip/Tooltip.tsx index a9ec11d6a02..d044dee8062 100644 --- a/packages/shared/src/components/tooltip/Tooltip.tsx +++ b/packages/shared/src/components/tooltip/Tooltip.tsx @@ -47,9 +47,11 @@ export function Tooltip({ onOpenChange={setOpen} > e.currentTarget.blur()} + onMouseUp={(e: React.MouseEvent) => + (e.currentTarget as HTMLElement).blur() + } {...(enableMobileClick && { onClick: () => setOpen(true) })} > {children} diff --git a/packages/shared/src/components/typography/Typography.tsx b/packages/shared/src/components/typography/Typography.tsx index 259c7f7d867..28d8c5dbe39 100644 --- a/packages/shared/src/components/typography/Typography.tsx +++ b/packages/shared/src/components/typography/Typography.tsx @@ -94,7 +94,7 @@ function BaseTypography( className, type, { 'font-bold': bold, 'text-center': center }, - color ?? tagToColor[tag], + color ?? (tagToColor as Record)[tag], truncate && truncateTextClassNames, ); const Tag = classed(tag, classes); diff --git a/packages/shared/src/components/widgets/Alert.tsx b/packages/shared/src/components/widgets/Alert.tsx index f2988cc2055..73cdd492c15 100644 --- a/packages/shared/src/components/widgets/Alert.tsx +++ b/packages/shared/src/components/widgets/Alert.tsx @@ -68,8 +68,8 @@ function Alert({ {title} diff --git a/packages/shared/src/components/widgets/SocialShareList.tsx b/packages/shared/src/components/widgets/SocialShareList.tsx index ff1d1a8a55c..61761d6626f 100644 --- a/packages/shared/src/components/widgets/SocialShareList.tsx +++ b/packages/shared/src/components/widgets/SocialShareList.tsx @@ -114,7 +114,7 @@ export function SocialShareList({ onClick={() => openShareLink(ShareProvider.Email)} label="Email" /> - {globalThis?.navigator?.share && ( + {typeof globalThis?.navigator?.share === 'function' && ( } variant={ButtonVariant.Primary} diff --git a/packages/shared/src/components/withFeaturesBoundary.spec.tsx b/packages/shared/src/components/withFeaturesBoundary.spec.tsx index 1ad7d2de24f..2365511b69e 100644 --- a/packages/shared/src/components/withFeaturesBoundary.spec.tsx +++ b/packages/shared/src/components/withFeaturesBoundary.spec.tsx @@ -1,13 +1,16 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import type { FeaturesReadyContextValue } from './GrowthBookProvider'; import { FeaturesReadyContext } from './GrowthBookProvider'; import { withFeaturesBoundary } from './withFeaturesBoundary'; const renderComponent = (Component: React.ElementType, ready = true) => { return render( - + , diff --git a/packages/shared/src/contexts/BuyCoresContext/types.ts b/packages/shared/src/contexts/BuyCoresContext/types.ts index 1fdbf02b8c6..f07c75aef66 100644 --- a/packages/shared/src/contexts/BuyCoresContext/types.ts +++ b/packages/shared/src/contexts/BuyCoresContext/types.ts @@ -4,7 +4,9 @@ import { createContext, useContext } from 'react'; import type { OpenCheckoutFn } from '../payment/context'; import type { Origin } from '../../lib/log'; -export const BuyCoresContext = createContext(undefined); +export const BuyCoresContext = createContext( + undefined as unknown as BuyCoresContextData, +); export const useBuyCoresContext = (): BuyCoresContextData => useContext(BuyCoresContext); diff --git a/packages/shared/src/contexts/NotificationsContext.tsx b/packages/shared/src/contexts/NotificationsContext.tsx index 8edeac3958d..a7f6885c0bd 100644 --- a/packages/shared/src/contexts/NotificationsContext.tsx +++ b/packages/shared/src/contexts/NotificationsContext.tsx @@ -8,8 +8,9 @@ export interface NotificationsContextData { incrementUnreadCount: () => void; } -const NotificationsContext = - React.createContext(null); +const NotificationsContext = React.createContext( + null as unknown as NotificationsContextData, +); export default NotificationsContext; diff --git a/packages/shared/src/contexts/PostReferrerContext.tsx b/packages/shared/src/contexts/PostReferrerContext.tsx index 29651ccd42d..640a678849d 100644 --- a/packages/shared/src/contexts/PostReferrerContext.tsx +++ b/packages/shared/src/contexts/PostReferrerContext.tsx @@ -9,7 +9,7 @@ import { usePrevious } from '../hooks'; type ReferredPost = { id: Post['id']; - author?: Pick; + author?: Pick, 'id'>; origin?: Origin; }; diff --git a/packages/shared/src/contexts/RecruiterPaymentContext/RecruiterPaymentContext.tsx b/packages/shared/src/contexts/RecruiterPaymentContext/RecruiterPaymentContext.tsx index dc292dca9b4..ca94220be01 100644 --- a/packages/shared/src/contexts/RecruiterPaymentContext/RecruiterPaymentContext.tsx +++ b/packages/shared/src/contexts/RecruiterPaymentContext/RecruiterPaymentContext.tsx @@ -2,11 +2,25 @@ import type { ReactElement } from 'react'; import React from 'react'; import type { RecruiterContextProviderProps } from './types'; import { RecruiterPaymentPaddleContextProvider } from './RecruiterPaymentPaddleContext'; +import { RecruiterPaymentPublicContextProvider } from './RecruiterPaymentPublicContext'; +import { useAuthContext } from '../AuthContext'; export const RecruiterPaymentContext = ({ children, ...props }: RecruiterContextProviderProps): ReactElement => { + const { user, isLoggedIn } = useAuthContext(); + + if (!user || !isLoggedIn) { + return ( + + {children} + + ); + } + return ( {children} diff --git a/packages/shared/src/contexts/RecruiterPaymentContext/RecruiterPaymentPaddleContext.tsx b/packages/shared/src/contexts/RecruiterPaymentContext/RecruiterPaymentPaddleContext.tsx index 3f26ac0c061..bde9bfa9cb6 100644 --- a/packages/shared/src/contexts/RecruiterPaymentContext/RecruiterPaymentPaddleContext.tsx +++ b/packages/shared/src/contexts/RecruiterPaymentContext/RecruiterPaymentPaddleContext.tsx @@ -22,6 +22,13 @@ export const RecruiterPaymentPaddleContextProvider = ({ const router = useRouter(); const { user, isLoggedIn } = useAuthContext(); const { logEvent } = useLogContext(); + + if (!user) { + throw new Error( + 'RecruiterPaymentPaddleContextProvider requires an authenticated user', + ); + } + const [selectedProduct, setSelectedProduct] = useState(); const logRef = useRef(); diff --git a/packages/shared/src/contexts/RecruiterPaymentContext/types.ts b/packages/shared/src/contexts/RecruiterPaymentContext/types.ts index a37f6d8f827..cbaab8861f4 100644 --- a/packages/shared/src/contexts/RecruiterPaymentContext/types.ts +++ b/packages/shared/src/contexts/RecruiterPaymentContext/types.ts @@ -6,7 +6,9 @@ import type { Origin } from '../../lib/log'; import type { ProductPricingPreview } from '../../graphql/paddle'; export const RecruiterPaymentContext = - createContext(undefined); + createContext( + undefined as unknown as RecruiterPaymentContextData, + ); export const useRecruiterPaymentContext = (): RecruiterPaymentContextData => useContext(RecruiterPaymentContext); diff --git a/packages/shared/src/contexts/WriteCommentContext.ts b/packages/shared/src/contexts/WriteCommentContext.ts index 796ceac6ba6..c68cc88fb0c 100644 --- a/packages/shared/src/contexts/WriteCommentContext.ts +++ b/packages/shared/src/contexts/WriteCommentContext.ts @@ -6,7 +6,7 @@ interface WriteCommentContextProp { } export const WriteCommentContext = createContext({ - mutateComment: null, + mutateComment: null as unknown as UseMutateCommentResult, }); export const useWriteCommentContext = (): WriteCommentContextProp => diff --git a/packages/shared/src/contexts/payment/context.ts b/packages/shared/src/contexts/payment/context.ts index ffa13aeac25..1cfc7f90e62 100644 --- a/packages/shared/src/contexts/payment/context.ts +++ b/packages/shared/src/contexts/payment/context.ts @@ -29,7 +29,9 @@ export interface PaymentContextData { setPriceType?: Dispatch>; } -export const PaymentContext = createContext(undefined); +export const PaymentContext = createContext( + undefined as unknown as PaymentContextData, +); export const usePaymentContext = (): PaymentContextData => { const context = useContext(PaymentContext); diff --git a/packages/shared/src/contexts/payment/index.tsx b/packages/shared/src/contexts/payment/index.tsx index 2fde6aeaccc..44b4fffa28b 100644 --- a/packages/shared/src/contexts/payment/index.tsx +++ b/packages/shared/src/contexts/payment/index.tsx @@ -10,7 +10,10 @@ import type { StoreKitSubProviderProps } from './StoreKit'; import { iOSSupportsPlusPurchase } from '../../lib/ios'; const StoreKitSubProvider = dynamic(() => - import('./StoreKit').then((mod) => mod.StoreKitSubProvider), + import('./StoreKit').then( + (mod) => + mod.StoreKitSubProvider as React.ComponentType, + ), ); export const PaymentContextProvider = ({ @@ -26,9 +29,7 @@ export const PaymentContextProvider = ({ } if (checkIsExtension()) { - return ( - {children} - ); + return {children}; } return ( diff --git a/packages/shared/src/features/boost/CampaignList.tsx b/packages/shared/src/features/boost/CampaignList.tsx index e89051f3484..dad2a609944 100644 --- a/packages/shared/src/features/boost/CampaignList.tsx +++ b/packages/shared/src/features/boost/CampaignList.tsx @@ -65,7 +65,7 @@ export function CampaignList({ onClick(campaign)} + onClick={() => onClick?.(campaign)} className="px-6 py-2" /> ))} diff --git a/packages/shared/src/features/boost/usePostBoostEstimation.ts b/packages/shared/src/features/boost/usePostBoostEstimation.ts index dbae96eb330..30fff188640 100644 --- a/packages/shared/src/features/boost/usePostBoostEstimation.ts +++ b/packages/shared/src/features/boost/usePostBoostEstimation.ts @@ -17,9 +17,15 @@ export const usePostBoostEstimation = ({ post: postFromProps, query, }: UsePostBoostEstimationProps) => { + const { createdAt } = postFromProps; + + if (!createdAt) { + throw new Error('usePostBoostEstimation requires post.createdAt'); + } + const isOldPost = useMemo( - () => isOlderThan(oneMinute * 5, new Date(postFromProps.createdAt)), - [postFromProps.createdAt], + () => isOlderThan(oneMinute * 5, new Date(createdAt)), + [createdAt], ); const [retriesExhausted, setRetriesExhausted] = useState(false); const [post, setPost] = useState(postFromProps); diff --git a/packages/shared/src/features/briefing/components/BriefPlusUpgradeCTA.tsx b/packages/shared/src/features/briefing/components/BriefPlusUpgradeCTA.tsx index fbe37344842..07cf803d66d 100644 --- a/packages/shared/src/features/briefing/components/BriefPlusUpgradeCTA.tsx +++ b/packages/shared/src/features/briefing/components/BriefPlusUpgradeCTA.tsx @@ -15,7 +15,7 @@ import { useAuthContext } from '../../../contexts/AuthContext'; export const BriefPlusUpgradeCTA = ({ className, ...attrs -}: ButtonProps<'a'> | undefined): ReactElement => { +}: ButtonProps<'a'>): ReactElement => { const { isAuthReady } = useAuthContext(); const { logSubscriptionEvent, isPlus } = usePlusSubscription(); const { diff --git a/packages/shared/src/features/briefing/hooks/useGenerateBrief.ts b/packages/shared/src/features/briefing/hooks/useGenerateBrief.ts index ca75ba03dca..e595ad35c47 100644 --- a/packages/shared/src/features/briefing/hooks/useGenerateBrief.ts +++ b/packages/shared/src/features/briefing/hooks/useGenerateBrief.ts @@ -27,7 +27,7 @@ export const useGenerateBrief = ({ onGenerated }: UseGenerateBriefingProps) => { onSuccess: async ({ id, balance }) => { displayToast('Your Presidential Briefing is being generated ✅'); - if (balance) { + if (balance && user) { updateUser({ ...user, balance, diff --git a/packages/shared/src/features/onboarding/hooks/useFunnelNavigation.ts b/packages/shared/src/features/onboarding/hooks/useFunnelNavigation.ts index 3d038507aea..9b8193bdfeb 100644 --- a/packages/shared/src/features/onboarding/hooks/useFunnelNavigation.ts +++ b/packages/shared/src/features/onboarding/hooks/useFunnelNavigation.ts @@ -210,10 +210,17 @@ export const useFunnelNavigation = ({ [setPosition, stepMap], ); - const step: FunnelStep = useMemo( - () => getFunnelStepByPosition(funnel, position), - [funnel, position], - ); + const step: FunnelStep = useMemo(() => { + const currentStep = getFunnelStepByPosition(funnel, position); + + if (!currentStep) { + throw new Error( + 'Failed to resolve funnel step from the current position', + ); + } + + return currentStep; + }, [funnel, position]); const navigationStateRef = useRef({ step, stepTimerStart }); navigationStateRef.current = { step, stepTimerStart }; diff --git a/packages/shared/src/features/onboarding/hooks/useStepTransition.ts b/packages/shared/src/features/onboarding/hooks/useStepTransition.ts index 07b23494ea3..51f8f30c3e5 100644 --- a/packages/shared/src/features/onboarding/hooks/useStepTransition.ts +++ b/packages/shared/src/features/onboarding/hooks/useStepTransition.ts @@ -38,7 +38,7 @@ export function useStepTransition(sessionId: string): UseStepTransitionRet { }); return { - transition: mutateAsync, + transition: mutateAsync as UseStepTransitionRet['transition'], isPending, }; } diff --git a/packages/shared/src/features/onboarding/lib/utils.ts b/packages/shared/src/features/onboarding/lib/utils.ts index 9120f8987ec..914a01a2ffd 100644 --- a/packages/shared/src/features/onboarding/lib/utils.ts +++ b/packages/shared/src/features/onboarding/lib/utils.ts @@ -59,7 +59,7 @@ export const getCookiesAndHeadersFromRequest = ( }, ); if (!forwardedHeaders['x-forwarded-for']) { - forwardedHeaders['x-forwarded-for'] = req.socket.remoteAddress; + forwardedHeaders['x-forwarded-for'] = req.socket.remoteAddress ?? ''; } return { cookies: allCookies, forwardedHeaders }; diff --git a/packages/shared/src/features/onboarding/shared/PricingPlan.tsx b/packages/shared/src/features/onboarding/shared/PricingPlan.tsx index 4aa76cfce8a..ac7a00ac4a3 100644 --- a/packages/shared/src/features/onboarding/shared/PricingPlan.tsx +++ b/packages/shared/src/features/onboarding/shared/PricingPlan.tsx @@ -42,8 +42,9 @@ export function PricingPlan({ const isBestValue = variation === PricingPlanVariation.BEST_VALUE; const baseClassName: RadioItemProps['className'] = { wrapper: - isBestValue && - `relative p-1 pt-8 rounded-16 overflow-hidden ${styles.bestValue}`, + (isBestValue && + `relative p-1 pt-8 rounded-16 overflow-hidden ${styles.bestValue}`) || + undefined, content: classNames( styles.label, 'z-1 !items-start gap-2 overflow-hidden rounded-12 border p-3', diff --git a/packages/shared/src/features/onboarding/shared/PricingPlans.tsx b/packages/shared/src/features/onboarding/shared/PricingPlans.tsx index 4c5bde3e60b..7a3ee0f7610 100644 --- a/packages/shared/src/features/onboarding/shared/PricingPlans.tsx +++ b/packages/shared/src/features/onboarding/shared/PricingPlans.tsx @@ -23,18 +23,30 @@ export function PricingPlans({ }: PricingPlansProps): ReactElement { return (
    - {plans.map((plan) => ( - onChange(plan.value)} - perks={perks} - /> - ))} + {plans.map((plan) => + (() => { + const planValue = plan.value; + + if (!planValue) { + throw new Error( + 'PricingPlans requires each plan to define a value', + ); + } + + return ( + onChange(planValue)} + perks={perks} + /> + ); + })(), + )}
    ); } diff --git a/packages/shared/src/features/onboarding/shared/StepHeadline.tsx b/packages/shared/src/features/onboarding/shared/StepHeadline.tsx index f6b3b4d9722..f054ed0b5f8 100644 --- a/packages/shared/src/features/onboarding/shared/StepHeadline.tsx +++ b/packages/shared/src/features/onboarding/shared/StepHeadline.tsx @@ -32,7 +32,10 @@ export const StepHeadline = ({ className, }: StepHeadlineProps): ReactElement => { const titleHtml = useMemo(() => sanitizeMessage(heading), [heading]); - const descHtml = useMemo(() => sanitizeMessage(description), [description]); + const descHtml = useMemo( + () => (description ? sanitizeMessage(description) : undefined), + [description], + ); return (
    diff --git a/packages/shared/src/features/onboarding/steps/FunnelCheckout.tsx b/packages/shared/src/features/onboarding/steps/FunnelCheckout.tsx index 24bbfadd4a1..4d3913759af 100644 --- a/packages/shared/src/features/onboarding/steps/FunnelCheckout.tsx +++ b/packages/shared/src/features/onboarding/steps/FunnelCheckout.tsx @@ -40,6 +40,10 @@ export const FunnelCheckout = ({ currentPriceIdRef.current = priceId; + if (!openCheckout) { + throw new Error('FunnelCheckout requires an available checkout handler'); + } + openCheckout({ priceId, discountId: applyDiscount ? discountCode : undefined, diff --git a/packages/shared/src/features/onboarding/steps/FunnelFact/FunnelFactCentered.tsx b/packages/shared/src/features/onboarding/steps/FunnelFact/FunnelFactCentered.tsx index 20000e7981d..78933cbf5cf 100644 --- a/packages/shared/src/features/onboarding/steps/FunnelFact/FunnelFactCentered.tsx +++ b/packages/shared/src/features/onboarding/steps/FunnelFact/FunnelFactCentered.tsx @@ -33,7 +33,7 @@ export const FunnelFactCentered = (props: FunnelStepFact): ReactElement => { } - variant={badge.variant} + variant={badge.variant ?? 'primary'} /> ); diff --git a/packages/shared/src/features/onboarding/steps/FunnelFact/FunnelFactDefault.tsx b/packages/shared/src/features/onboarding/steps/FunnelFact/FunnelFactDefault.tsx index 5543c86e248..18c625b4332 100644 --- a/packages/shared/src/features/onboarding/steps/FunnelFact/FunnelFactDefault.tsx +++ b/packages/shared/src/features/onboarding/steps/FunnelFact/FunnelFactDefault.tsx @@ -57,7 +57,7 @@ export const FunnelFactDefault = (props: FunnelStepFact): ReactElement => { } - variant={badge.variant} + variant={badge.variant ?? 'primary'} /> ); diff --git a/packages/shared/src/features/onboarding/steps/FunnelPricing/FunnelPricing.spec.tsx b/packages/shared/src/features/onboarding/steps/FunnelPricing/FunnelPricing.spec.tsx index dae73e2a29e..f9f0871601e 100644 --- a/packages/shared/src/features/onboarding/steps/FunnelPricing/FunnelPricing.spec.tsx +++ b/packages/shared/src/features/onboarding/steps/FunnelPricing/FunnelPricing.spec.tsx @@ -242,6 +242,10 @@ describe('FunnelPricing', () => { ); // Click on the monthly plan + if (!monthlyPlan) { + throw new Error('Missing monthly plan radio button'); + } + fireEvent.click(monthlyPlan); // Then click the CTA diff --git a/packages/shared/src/features/opportunity/components/CVExists.tsx b/packages/shared/src/features/opportunity/components/CVExists.tsx index 7ae6fec2922..0fd678fbadf 100644 --- a/packages/shared/src/features/opportunity/components/CVExists.tsx +++ b/packages/shared/src/features/opportunity/components/CVExists.tsx @@ -18,6 +18,10 @@ export const CVExists = ({ preferences: UserCandidatePreferences; }): ReactElement => { const { user } = useAuthContext(); + const fileName = + preferences?.cv?.fileName || + (user?.username ? `${user.username}.pdf` : 'resume.pdf'); + return (
    @@ -45,7 +49,7 @@ export const CVExists = ({ className="flex items-center gap-2" > - {preferences?.cv?.fileName ?? `${user.username}.pdf`} + {fileName} diff --git a/packages/shared/src/features/opportunity/components/ClearEmploymentAgreementButton.tsx b/packages/shared/src/features/opportunity/components/ClearEmploymentAgreementButton.tsx index b5be548f979..8ea62c3d0b4 100644 --- a/packages/shared/src/features/opportunity/components/ClearEmploymentAgreementButton.tsx +++ b/packages/shared/src/features/opportunity/components/ClearEmploymentAgreementButton.tsx @@ -22,7 +22,8 @@ export const ClearEmploymentAgreementButton = (): ReactElement => { const { logEvent } = useLogContext(); const { displayToast } = useToastNotification(); const { completeAction } = useActions(); - const opts = getCandidatePreferencesOptions(user?.id); + + const opts = getCandidatePreferencesOptions(user?.id ?? ''); const updateQuery = useUpdateQuery(opts); const { mutate: clearFile, isPending: isClearFilePending } = useMutation({ @@ -38,6 +39,11 @@ export const ClearEmploymentAgreementButton = (): ReactElement => { ); }, }); + + if (!user) { + return <>; + } + return ( diff --git a/packages/shared/src/features/organizations/components/Sidebar.tsx b/packages/shared/src/features/organizations/components/Sidebar.tsx index 43a4e71fc2d..270f384b16b 100644 --- a/packages/shared/src/features/organizations/components/Sidebar.tsx +++ b/packages/shared/src/features/organizations/components/Sidebar.tsx @@ -23,7 +23,7 @@ import { InnerProfileSettingsMenu } from '../../../components/profile/ProfileSet type MenuItems = Record< string, { - title: string | null; + title: string | undefined; items: Record; } >; @@ -32,7 +32,7 @@ const defineMenuItems = (items: T): T => items; const menuItems = defineMenuItems({ main: { - title: null, + title: undefined, items: { general: { title: 'General', diff --git a/packages/shared/src/features/profile/components/Activity.helpers.tsx b/packages/shared/src/features/profile/components/Activity.helpers.tsx index d557a3e1ed9..6b0e054ff0b 100644 --- a/packages/shared/src/features/profile/components/Activity.helpers.tsx +++ b/packages/shared/src/features/profile/components/Activity.helpers.tsx @@ -70,7 +70,7 @@ export const ACTIVITY_QUERY_KEYS = { posts: (userId: string) => ['author', userId] as const, upvoted: (userId: string) => [OtherFeedPage.UserUpvoted, userId] as const, comments: (userId: string) => - generateQueryKey(RequestKey.UserComments, null, userId, 'activity'), + generateQueryKey(RequestKey.UserComments, undefined, userId, 'activity'), } as const; export const getItemCount = ( diff --git a/packages/shared/src/features/profile/components/ProfileCompany.tsx b/packages/shared/src/features/profile/components/ProfileCompany.tsx index c42583f18d6..d551344f432 100644 --- a/packages/shared/src/features/profile/components/ProfileCompany.tsx +++ b/packages/shared/src/features/profile/components/ProfileCompany.tsx @@ -77,7 +77,10 @@ const ProfileCompany = ({ } }; - const [debouncedQuery] = useDebounceFn((q) => handleSearch(q), 300); + const [debouncedQuery] = useDebounceFn( + (q) => handleSearch(q ?? ''), + 300, + ); return (
    diff --git a/packages/shared/src/features/profile/components/ProfileGithubRepository.tsx b/packages/shared/src/features/profile/components/ProfileGithubRepository.tsx index c180a832c96..2ecc6c49947 100644 --- a/packages/shared/src/features/profile/components/ProfileGithubRepository.tsx +++ b/packages/shared/src/features/profile/components/ProfileGithubRepository.tsx @@ -96,7 +96,10 @@ const ProfileGithubRepository = ({ }); }; - const [debouncedQuery] = useDebounceFn((q) => handleSearch(q), 300); + const [debouncedQuery] = useDebounceFn( + (q) => handleSearch(q ?? ''), + 300, + ); const repositoryFullName = repository?.owner ? `${repository.owner}/${repository.name}` diff --git a/packages/shared/src/features/profile/components/ProfileWidgets/ActiveOrRecomendedSquads.spec.tsx b/packages/shared/src/features/profile/components/ProfileWidgets/ActiveOrRecomendedSquads.spec.tsx index 04a24716e92..c09ef2ef74e 100644 --- a/packages/shared/src/features/profile/components/ProfileWidgets/ActiveOrRecomendedSquads.spec.tsx +++ b/packages/shared/src/features/profile/components/ProfileWidgets/ActiveOrRecomendedSquads.spec.tsx @@ -146,7 +146,7 @@ const renderComponent = ( if (recommendedSquads.length > 0) { const sourcesKey = generateQueryKey( RequestKey.Sources, - null, + undefined, undefined, // featured true, // isPublic undefined, // categoryId diff --git a/packages/shared/src/features/profile/components/ProfileWidgets/ActiveOrRecomendedSquadsComponents.tsx b/packages/shared/src/features/profile/components/ProfileWidgets/ActiveOrRecomendedSquadsComponents.tsx index 37b5d6a4020..446f4c10238 100644 --- a/packages/shared/src/features/profile/components/ProfileWidgets/ActiveOrRecomendedSquadsComponents.tsx +++ b/packages/shared/src/features/profile/components/ProfileWidgets/ActiveOrRecomendedSquadsComponents.tsx @@ -81,7 +81,7 @@ export const SquadListItem = ({ }} origin={Origin.Profile} size={ButtonSize.Small} - squad={{ ...squad, currentMember: null }} + squad={{ ...squad, currentMember: undefined }} /> )} diff --git a/packages/shared/src/features/profile/components/ProfileWidgets/BadgesAndAwards.spec.tsx b/packages/shared/src/features/profile/components/ProfileWidgets/BadgesAndAwards.spec.tsx index 1e3d509619b..c7b6a0e135e 100644 --- a/packages/shared/src/features/profile/components/ProfileWidgets/BadgesAndAwards.spec.tsx +++ b/packages/shared/src/features/profile/components/ProfileWidgets/BadgesAndAwards.spec.tsx @@ -35,20 +35,27 @@ const defaultUser: PublicProfile = { image: 'https://daily.dev/test.png', bio: 'Test bio', createdAt: '2020-01-01T00:00:00.000Z', - twitter: 'testuser', - github: 'testuser', - hashnode: 'testuser', - portfolio: 'https://test.com', permalink: 'https://daily.dev/testuser', - roadmap: 'testuser', - threads: 'testuser', - codepen: 'testuser', - reddit: 'testuser', - stackoverflow: '123456/testuser', - youtube: 'testuser', - linkedin: 'testuser', - mastodon: 'https://mastodon.social/@testuser', - bluesky: 'testuser.bsky.social', + socialLinks: [ + { platform: 'github', url: 'https://github.com/testuser' }, + { platform: 'hashnode', url: 'https://hashnode.com/testuser' }, + { platform: 'portfolio', url: 'https://test.com' }, + { platform: 'roadmap', url: 'https://roadmap.sh/testuser' }, + { platform: 'threads', url: 'https://threads.net/testuser' }, + { platform: 'codepen', url: 'https://codepen.io/testuser' }, + { platform: 'reddit', url: 'https://reddit.com/u/testuser' }, + { + platform: 'stackoverflow', + url: 'https://stackoverflow.com/users/123456/testuser', + }, + { platform: 'youtube', url: 'https://youtube.com/testuser' }, + { platform: 'linkedin', url: 'https://linkedin.com/in/testuser' }, + { platform: 'mastodon', url: 'https://mastodon.social/@testuser' }, + { + platform: 'bluesky', + url: 'https://bsky.app/profile/testuser.bsky.social', + }, + ], }; const mockTopReaders = [ diff --git a/packages/shared/src/features/profile/components/ProfileWidgets/BadgesAndAwards.tsx b/packages/shared/src/features/profile/components/ProfileWidgets/BadgesAndAwards.tsx index c6964ca50c5..6d800232861 100644 --- a/packages/shared/src/features/profile/components/ProfileWidgets/BadgesAndAwards.tsx +++ b/packages/shared/src/features/profile/components/ProfileWidgets/BadgesAndAwards.tsx @@ -29,7 +29,7 @@ export const BadgesAndAwards = ({ user, }: { user: PublicProfile; -}): ReactElement => { +}): ReactElement | null => { const { data: topReaders, isPending: isTopReaderLoading } = useTopReader({ user, limit: 5, diff --git a/packages/shared/src/features/profile/components/ProfileWidgets/ProfileCompletion.spec.tsx b/packages/shared/src/features/profile/components/ProfileWidgets/ProfileCompletion.spec.tsx index 32778fca44d..6b89eacd90f 100644 --- a/packages/shared/src/features/profile/components/ProfileWidgets/ProfileCompletion.spec.tsx +++ b/packages/shared/src/features/profile/components/ProfileWidgets/ProfileCompletion.spec.tsx @@ -38,16 +38,14 @@ const renderWithAuth = (user: LoggedUser | null) => { return render( diff --git a/packages/shared/src/features/profile/components/ProfileWidgets/Share.tsx b/packages/shared/src/features/profile/components/ProfileWidgets/Share.tsx index 41abc30d483..a67126b8efe 100644 --- a/packages/shared/src/features/profile/components/ProfileWidgets/Share.tsx +++ b/packages/shared/src/features/profile/components/ProfileWidgets/Share.tsx @@ -170,7 +170,7 @@ export const Share = ({ permalink, className }: ShareProps): ReactElement => { /> ))} - {globalThis?.navigator?.share && ( + {!!globalThis?.navigator?.share && (