From 8e5914de8767773b3b3c8fdf7f22b364a669a103 Mon Sep 17 00:00:00 2001 From: Chris Bongers Date: Mon, 23 Mar 2026 09:39:23 +0200 Subject: [PATCH 1/7] chore: ts strict individual files --- packages/shared/src/components/FeedItemComponent.tsx | 6 ++++-- packages/shared/src/components/LoginButton.spec.tsx | 2 +- .../src/components/ProfileMenu/ProfileSectionItem.tsx | 2 +- .../src/components/auth/AuthenticationBanner.tsx | 2 +- .../src/components/auth/CodeVerificationForm.tsx | 2 +- .../shared/src/components/auth/CustomAuthBanner.tsx | 2 +- .../shared/src/components/auth/EmailSignupForm.tsx | 2 +- packages/shared/src/components/auth/SignBackButton.tsx | 2 +- packages/shared/src/components/auth/common.tsx | 4 ++-- packages/shared/src/components/buttons/Button.spec.tsx | 2 +- .../shared/src/components/buttons/QuaternaryButton.tsx | 2 +- .../cards/Leaderboard/LeaderboardListItem.tsx | 2 +- .../cards/ad/common/getAdFaviconImageLink.spec.ts | 2 +- .../src/components/cards/article/ArticleGrid.spec.tsx | 2 +- .../cards/brief/BriefBanner/BriefBannerFeed.tsx | 2 +- .../cards/collection/CollectionGrid.spec.tsx | 2 +- packages/shared/src/components/cards/common/Card.tsx | 2 +- .../src/components/cards/common/ClickbaitShield.tsx | 2 +- .../src/components/cards/common/PostCardHeader.tsx | 2 +- .../shared/src/components/cards/common/PostSummary.tsx | 10 +++++----- .../src/components/cards/common/SharedCardCover.tsx | 2 +- .../components/cards/common/SquadPostCardHeader.tsx | 3 ++- .../src/components/cards/common/list/PostMetadata.tsx | 2 +- .../components/cards/common/list/SignalList.spec.tsx | 2 +- .../src/components/cards/common/list/SignalList.tsx | 4 ++-- .../src/components/cards/entity/SquadEntityCard.tsx | 2 +- .../shared/src/components/cards/poll/PollOptions.tsx | 2 +- .../src/components/cards/share/ShareList.spec.tsx | 2 +- .../shared/src/components/cards/squad/SquadList.tsx | 2 +- .../components/comments/CommentActionButtons.spec.tsx | 2 +- packages/shared/src/components/comments/CommentBox.tsx | 2 +- packages/shared/src/components/drawers/ListDrawer.tsx | 2 +- packages/shared/src/components/drawers/NavDrawer.tsx | 2 +- .../feeds/FeedSettings/FeedSettingsCreate.tsx | 2 +- .../components/feeds/FeedSettings/FeedSettingsEdit.tsx | 2 +- .../feeds/FeedSettings/FeedSettingsEditContext.tsx | 2 +- .../feeds/FeedSettings/components/SmartPrompts.tsx | 2 +- .../shared/src/components/feeds/FeedSettings/types.ts | 2 +- .../src/components/layout/HeaderButtons.spec.tsx | 2 +- .../marketingCta/MarketingCtaPopoverSmall.tsx | 2 +- packages/shared/src/components/marketingCta/common.tsx | 2 +- .../components/modals/AchievementCompletionModal.tsx | 2 +- .../shared/src/components/modals/FeedbackModal.tsx | 2 +- .../src/components/modals/JobOpportunityModal.tsx | 2 +- packages/shared/src/components/modals/RepostsModal.tsx | 2 +- .../components/modals/ReputationPrivilegesModal.tsx | 2 +- .../components/modals/SharedBookmarksModal.spec.tsx | 2 +- .../shared/src/components/modals/SquadTourModal.tsx | 2 +- .../shared/src/components/modals/UserListModal.tsx | 2 +- .../components/modals/bookmark/BookmarkFolderModal.tsx | 2 +- .../shared/src/components/modals/common/ModalBody.tsx | 4 ++-- .../src/components/modals/hotTakes/HotAndColdModal.tsx | 2 +- .../components/modals/plus/MobileSmartPromptsModal.tsx | 2 +- .../src/components/modals/post/ReadingHistoryModal.tsx | 2 +- .../modals/referral/GenericReferralModal.tsx | 2 +- .../components/modals/report/ReportCommentModal.tsx | 2 +- .../modals/squads/PrivilegedMembersModal.tsx | 2 +- packages/shared/src/components/onboarding/EditTag.tsx | 4 ++-- .../opportunity/InlineEditor/InlineContentEditor.tsx | 2 +- .../components/opportunity/NewOpportunityPopover.tsx | 2 +- .../OpportunityEditRecruiterModal.tsx | 2 +- .../OpportunityEditModal/OpportunityReimportModal.tsx | 2 +- .../OpportunitySteps/OpportunityStepsInfo.tsx | 3 ++- .../OpportunitySteps/OpportunityStepsQuestions.tsx | 2 +- .../SideBySideEdit/hooks/useOpportunityEditForm.tsx | 6 +++--- .../hooks/useOpportunityEditPageSetup.ts | 2 +- .../components/organization/OrganizationEditForm.tsx | 2 +- .../shared/src/components/plus/GiftUserContext.tsx | 2 +- .../shared/src/components/plus/PlusAdjustQuantity.tsx | 2 +- packages/shared/src/components/plus/PlusExtension.tsx | 2 +- packages/shared/src/components/plus/PlusListItem.tsx | 4 ++-- .../shared/src/components/plus/PlusMarketingModal.tsx | 2 +- .../shared/src/components/plus/PlusProductList.tsx | 2 +- .../shared/src/components/plus/PostUpgradeToPlus.tsx | 2 +- packages/shared/src/components/popover/GifPopover.tsx | 2 +- .../shared/src/components/post/BasePostContent.tsx | 2 +- .../shared/src/components/post/PostHeaderActions.tsx | 8 ++++---- .../shared/src/components/post/PostLoadingSkeleton.tsx | 2 +- .../src/components/post/PostUpvotesCommentsCount.tsx | 2 +- .../shared/src/components/post/RelatedPostsWidget.tsx | 2 +- .../src/components/post/analytics/PostShortInfo.tsx | 2 +- .../src/components/post/brief/BriefPostContent.tsx | 2 +- .../post/collection/CollectionSubscribeButton.tsx | 2 +- .../src/components/post/common/PostClickbaitShield.tsx | 2 +- .../src/components/post/smartPrompts/SmartPrompt.tsx | 2 +- .../shared/src/components/profile/ActivitySection.tsx | 2 +- .../src/components/profile/ControlledAvatarUpload.tsx | 2 +- .../src/components/profile/ExperienceLevelDropdown.tsx | 2 +- .../src/components/profile/ExperienceSettings.tsx | 2 +- packages/shared/src/components/profile/JoinedDate.tsx | 2 +- .../src/components/profile/ProfileButton.spec.tsx | 2 +- .../shared/src/components/profile/ProfileButton.tsx | 2 +- packages/shared/src/components/profile/ProfileLink.tsx | 2 +- packages/shared/src/components/profile/SquadsList.tsx | 2 +- packages/shared/src/components/profile/UserList.tsx | 2 +- .../components/profile/devcard/DevCardFetchWrapper.tsx | 2 +- .../components/profile/devcard/DevCardTwitterCover.tsx | 2 +- .../shared/src/components/recruiter/ConnectHeader.tsx | 2 +- .../shared/src/components/recruiter/FeedbackList.tsx | 2 +- .../shared/src/components/recruiter/MatchProfile.tsx | 2 +- .../src/components/search/SearchFilterTimeButton.tsx | 4 ++-- .../search/SearchPanel/SearchPanelDropdown.tsx | 2 +- .../search/SearchPanel/SearchPanelInputCursor.tsx | 2 +- .../search/SearchPanel/SearchPanelPostSuggestions.tsx | 2 +- .../search/SearchPanel/SearchPanelProvider.tsx | 2 +- .../search/SearchPanel/useSearchPanelAction.ts | 2 +- .../search/SearchResults/SearchResultsLayout.tsx | 2 +- .../search/SearchResults/SearchResultsTags.tsx | 2 +- .../shared/src/components/sidebar/ClickableNavItem.tsx | 2 +- packages/shared/src/components/sidebar/Sidebar.tsx | 2 +- .../shared/src/components/sidebar/SidebarItem.spec.tsx | 2 +- packages/shared/src/components/sidebar/SidebarItem.tsx | 2 +- packages/shared/src/components/sidebar/SidebarList.tsx | 2 +- packages/shared/src/components/sidebar/common.tsx | 2 +- .../components/sidebar/sections/BookmarkSection.tsx | 4 ++-- .../src/components/sidebar/sections/NetworkSection.tsx | 2 +- .../src/components/squads/SquadCommentJoinBanner.tsx | 2 +- .../shared/src/components/squads/SquadPostListItem.tsx | 2 +- packages/shared/src/components/squads/SquadTabs.tsx | 2 +- .../squads/layout/SquadDirectoryNavbarItem.tsx | 2 +- .../squads/layout/useSquadDirectoryLayout.ts | 2 +- .../src/components/squads/stack/SourceStackModal.tsx | 2 +- packages/shared/src/components/squads/utils.tsx | 2 +- .../shared/src/components/streak/popup/DayStreak.tsx | 2 +- packages/shared/src/components/tags/TagElement.tsx | 2 +- packages/shared/src/components/tooltip/Tooltip.tsx | 4 ++-- .../shared/src/components/typography/Typography.tsx | 2 +- packages/shared/src/components/widgets/Alert.tsx | 4 ++-- .../shared/src/components/widgets/SocialShareList.tsx | 2 +- .../src/components/withFeaturesBoundary.spec.tsx | 3 ++- packages/shared/src/contexts/BuyCoresContext/types.ts | 2 +- packages/shared/src/contexts/NotificationsContext.tsx | 2 +- packages/shared/src/contexts/PostReferrerContext.tsx | 2 +- .../RecruiterPaymentPaddleContext.tsx | 2 +- .../src/contexts/RecruiterPaymentContext/types.ts | 2 +- packages/shared/src/contexts/WriteCommentContext.ts | 2 +- packages/shared/src/contexts/payment/context.ts | 2 +- packages/shared/src/contexts/payment/index.tsx | 2 +- packages/shared/src/features/boost/CampaignList.tsx | 2 +- .../src/features/boost/usePostBoostEstimation.ts | 2 +- .../briefing/components/BriefPlusUpgradeCTA.tsx | 2 +- .../src/features/briefing/hooks/useGenerateBrief.ts | 2 +- .../features/onboarding/hooks/useFunnelNavigation.ts | 2 +- .../src/features/onboarding/hooks/useStepTransition.ts | 2 +- packages/shared/src/features/onboarding/lib/utils.ts | 2 +- .../src/features/onboarding/shared/PricingPlan.tsx | 4 ++-- .../src/features/onboarding/shared/PricingPlans.tsx | 2 +- .../src/features/onboarding/shared/StepHeadline.tsx | 2 +- .../onboarding/steps/FunnelBrowserExtension.tsx | 2 +- .../src/features/onboarding/steps/FunnelCheckout.tsx | 2 +- .../onboarding/steps/FunnelFact/FunnelFactCentered.tsx | 2 +- .../onboarding/steps/FunnelFact/FunnelFactDefault.tsx | 2 +- .../steps/FunnelPricing/FunnelPricing.spec.tsx | 2 +- .../src/features/opportunity/components/CVExists.tsx | 2 +- .../components/ClearEmploymentAgreementButton.tsx | 2 +- .../opportunity/components/ClearResumeButton.tsx | 2 +- .../components/UploadEmploymentAgreementButton.tsx | 2 +- .../features/organizations/components/LinksInput.tsx | 2 +- .../src/features/organizations/components/Sidebar.tsx | 4 ++-- .../features/profile/components/Activity.helpers.tsx | 2 +- .../src/features/profile/components/ProfileCompany.tsx | 2 +- .../profile/components/ProfileGithubRepository.tsx | 2 +- .../ProfileWidgets/ActiveOrRecomendedSquads.spec.tsx | 2 +- .../ActiveOrRecomendedSquadsComponents.tsx | 2 +- .../components/ProfileWidgets/BadgesAndAwards.spec.tsx | 1 - .../components/ProfileWidgets/BadgesAndAwards.tsx | 2 +- .../ProfileWidgets/ProfileCompletion.spec.tsx | 2 +- .../profile/components/ProfileWidgets/Share.tsx | 2 +- .../components/experience/DeleteExperienceButton.tsx | 2 +- .../experience/forms/UserCertificationForm.spec.tsx | 2 +- .../experience/forms/UserEducationForm.spec.tsx | 2 +- .../profile/components/gear/GearModal.spec.tsx | 2 +- .../src/features/profile/components/gear/GearModal.tsx | 2 +- .../profile/components/hotTakes/HotTakeModal.tsx | 2 +- .../profile/components/stack/UserStackModal.tsx | 2 +- .../workspacePhotos/WorkspacePhotoUploadModal.tsx | 2 +- packages/shared/src/features/profile/hooks/useGear.ts | 2 +- .../shared/src/features/profile/hooks/useGearSearch.ts | 2 +- .../shared/src/features/profile/hooks/useHotTakes.ts | 2 +- .../src/features/profile/hooks/useStackSearch.ts | 2 +- .../shared/src/features/profile/hooks/useToolSearch.ts | 2 +- .../src/features/profile/hooks/useToolTopSquads.ts | 2 +- .../shared/src/features/profile/hooks/useUserStack.ts | 2 +- .../features/profile/hooks/useUserWorkspacePhotos.ts | 2 +- .../features/recruiter/components/OnboardingView.tsx | 3 ++- .../features/shortcuts/contexts/ShortcutsProvider.tsx | 2 +- packages/shared/src/hooks/auth/useIsSpecialUser.ts | 2 +- packages/shared/src/hooks/auth/useSignBack.ts | 2 +- packages/shared/src/hooks/comments/useCommentById.ts | 2 +- ...SuggestionsContentPreferenceMutationSubscription.ts | 10 +++++----- .../useSourceContentPreferenceMutationSubscription.ts | 4 ++-- packages/shared/src/hooks/feed/useCustomDefaultFeed.ts | 2 +- packages/shared/src/hooks/input/useDebouncedUrl.ts | 2 +- packages/shared/src/hooks/input/useDiscardPost.ts | 2 +- .../hooks/integrations/useSourceIntegrationQuery.ts | 2 +- packages/shared/src/hooks/log/useDeviceId.ts | 2 +- packages/shared/src/hooks/log/useLogContextData.ts | 2 +- .../src/hooks/log/useLogOpportunityNudgeImpression.ts | 2 +- packages/shared/src/hooks/log/useLogSharedProps.ts | 2 +- .../shared/src/hooks/onboarding/useGenerateUsername.ts | 2 +- .../shared/src/hooks/profile/useProfileAchievements.ts | 2 +- .../src/hooks/profile/useShowcaseAchievements.ts | 2 +- packages/shared/src/hooks/referral/useJoinReferral.ts | 2 +- .../src/hooks/search/useSearchProviderSuggestions.ts | 10 +++++----- .../shared/src/hooks/source/usePrivateSourceJoin.ts | 2 +- packages/shared/src/hooks/source/useSourceStack.ts | 2 +- packages/shared/src/hooks/source/useSources.ts | 2 +- packages/shared/src/hooks/useAutoComplete.ts | 2 +- .../shared/src/hooks/useConditionalFeature.spec.tsx | 3 ++- packages/shared/src/hooks/useConditionalFeature.ts | 2 +- packages/shared/src/hooks/useCookieConsent.ts | 2 +- packages/shared/src/hooks/useDomPurify.ts | 2 +- packages/shared/src/hooks/useFeedLayout.ts | 2 +- packages/shared/src/hooks/useGif.ts | 2 +- packages/shared/src/hooks/useHostPermissionStatus.ts | 2 +- packages/shared/src/hooks/useIOSError.ts | 2 +- packages/shared/src/hooks/useLanguage.ts | 2 +- packages/shared/src/hooks/useLazyModal.ts | 2 +- packages/shared/src/hooks/useMedia.ts | 2 +- packages/shared/src/hooks/usePopupSelector.ts | 2 +- packages/shared/src/hooks/usePostContent.ts | 2 +- packages/shared/src/hooks/usePrevious.ts | 2 +- packages/shared/src/hooks/usePrivilegedSession.ts | 2 +- packages/shared/src/hooks/useTagAndSource.ts | 2 +- .../src/hooks/userCompany/useUserCompaniesQuery.tsx | 2 +- 225 files changed, 261 insertions(+), 255 deletions(-) diff --git a/packages/shared/src/components/FeedItemComponent.tsx b/packages/shared/src/components/FeedItemComponent.tsx index 99382f176a6..2c8e7c430a6 100644 --- a/packages/shared/src/components/FeedItemComponent.tsx +++ b/packages/shared/src/components/FeedItemComponent.tsx @@ -394,9 +394,10 @@ function FeedItemComponent({ } switch (item.type) { - case FeedItemType.Ad: + case FeedItemType.Ad: { + const AdComponent = AdTag as React.ComponentType>; 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..a83aed1b8a6 100644 --- a/packages/shared/src/components/LoginButton.spec.tsx +++ b/packages/shared/src/components/LoginButton.spec.tsx @@ -18,7 +18,7 @@ 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..454d022fef9 100644 --- a/packages/shared/src/components/ProfileMenu/ProfileSectionItem.tsx +++ b/packages/shared/src/components/ProfileMenu/ProfileSectionItem.tsx @@ -65,7 +65,7 @@ export const ProfileSectionItem = ({ ( - + {children} )} 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 02332bfb655..cb6f9a35376 100644 --- a/packages/shared/src/components/auth/SignBackButton.tsx +++ b/packages/shared/src/components/auth/SignBackButton.tsx @@ -18,7 +18,7 @@ export function SignBackButton({ provider, onClick, }: SignBackButtonProps): ReactElement { - const item = providerMap[provider.toLowerCase()]; + 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 85cb76c8644..a929f45738f 100644 --- a/packages/shared/src/components/post/brief/BriefPostContent.tsx +++ b/packages/shared/src/components/post/brief/BriefPostContent.tsx @@ -473,7 +473,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..3c541d83581 100644 --- a/packages/shared/src/components/post/common/PostClickbaitShield.tsx +++ b/packages/shared/src/components/post/common/PostClickbaitShield.tsx @@ -84,7 +84,7 @@ export const PostClickbaitShield = ({ post }: { post: Post }): ReactElement => { }); } else { router.push( - `${webappUrl}feeds/${user.id}/edit?dview=${FeedSettingsMenu.AI}`, + `${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..53ba667659e 100644 --- a/packages/shared/src/components/profile/ExperienceSettings.tsx +++ b/packages/shared/src/components/profile/ExperienceSettings.tsx @@ -20,7 +20,7 @@ export const ExperienceSettings = ({ const { user } = useAuthContext(); const { experiences, isPending } = useUserExperiencesByType( experienceType, - user?.id, + user!.id, ); if (isPending) { 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..7e9a9238440 100644 --- a/packages/shared/src/components/profile/ProfileButton.tsx +++ b/packages/shared/src/components/profile/ProfileButton.tsx @@ -263,7 +263,7 @@ export default function ProfileButton({
- +
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 > MAX_SQUADS && (
); diff --git a/packages/shared/src/components/recruiter/MatchProfile.tsx b/packages/shared/src/components/recruiter/MatchProfile.tsx index 81f9e3155fc..03bfeeecb53 100644 --- a/packages/shared/src/components/recruiter/MatchProfile.tsx +++ b/packages/shared/src/components/recruiter/MatchProfile.tsx @@ -83,7 +83,7 @@ export const MatchProfile = ({ profile }: MatchProfileProps): ReactElement => { className="text-accent-onion-default" size={IconSize.XSmall} /> - {largeNumberFormat(profile.reputation)} + {largeNumberFormat(profile.reputation ?? 0)} } /> diff --git a/packages/shared/src/components/search/SearchFilterTimeButton.tsx b/packages/shared/src/components/search/SearchFilterTimeButton.tsx index fb35de29c8e..a5280c16ed6 100644 --- a/packages/shared/src/components/search/SearchFilterTimeButton.tsx +++ b/packages/shared/src/components/search/SearchFilterTimeButton.tsx @@ -23,14 +23,14 @@ const SearchFilterTimeButton = () => { size={ButtonSize.Small} aria-label="Open time filter menu" > - {SearchTime[time]} + {SearchTime[time as keyof typeof SearchTime]} {Object.entries(SearchTime).map(([value, label]) => ( setTime(value as keyof SearchTime)} + onClick={() => setTime(value as SearchTime)} className={classNames( 'flex', time === value diff --git a/packages/shared/src/components/search/SearchPanel/SearchPanelDropdown.tsx b/packages/shared/src/components/search/SearchPanel/SearchPanelDropdown.tsx index cbe9109ca7b..96003f9c360 100644 --- a/packages/shared/src/components/search/SearchPanel/SearchPanelDropdown.tsx +++ b/packages/shared/src/components/search/SearchPanel/SearchPanelDropdown.tsx @@ -59,7 +59,7 @@ const SearchPanelDropdown = ({ query = '', anchor }: Props): ReactElement => { event.preventDefault(); - const indexModifier = keyToIndexModifier[pressedKey]; + const indexModifier = keyToIndexModifier[pressedKey as ArrowKeyEnum]; 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..df9097c0e3f 100644 --- a/packages/shared/src/components/search/SearchPanel/SearchPanelProvider.tsx +++ b/packages/shared/src/components/search/SearchPanel/SearchPanelProvider.tsx @@ -21,7 +21,7 @@ const providerToComponentMap: Record< export const SearchPanelProvider = ({ className, -}: SearchPanelProviderProps): ReactElement => { +}: SearchPanelProviderProps): ReactElement | null => { const searchPanel = useContext(SearchPanelContext); if (searchPanel.providerIcon) { diff --git a/packages/shared/src/components/search/SearchPanel/useSearchPanelAction.ts b/packages/shared/src/components/search/SearchPanel/useSearchPanelAction.ts index e9d44053cd4..9f5592154d0 100644 --- a/packages/shared/src/components/search/SearchPanel/useSearchPanelAction.ts +++ b/packages/shared/src/components/search/SearchPanel/useSearchPanelAction.ts @@ -34,7 +34,7 @@ export const useSearchPanelAction = ({ const onInactive = () => { searchPanel.setProvider({ - provider: undefined, + provider: undefined!, }); }; diff --git a/packages/shared/src/components/search/SearchResults/SearchResultsLayout.tsx b/packages/shared/src/components/search/SearchResults/SearchResultsLayout.tsx index ebd6e55f4a8..fa3a2fd9296 100644 --- a/packages/shared/src/components/search/SearchResults/SearchResultsLayout.tsx +++ b/packages/shared/src/components/search/SearchResults/SearchResultsLayout.tsx @@ -49,7 +49,7 @@ export const SearchResultsLayout = ( provider: SearchProviderEnum.Tags, limit: 10, }); - const tags = suggestedTags?.hits?.map(({ id }) => id) ?? []; + const tags = (suggestedTags?.hits?.map(({ id }) => id) ?? []).filter((id): id is string => !!id); const { isLoading: isSourcesLoading, suggestions: suggestedSources } = useSearchProviderSuggestions({ 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..e070b713b7f 100644 --- a/packages/shared/src/components/sidebar/ClickableNavItem.tsx +++ b/packages/shared/src/components/sidebar/ClickableNavItem.tsx @@ -35,7 +35,7 @@ export function ClickableNavItem({ if (!isButton && (!item.action || item.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..ee25fedd964 100644 --- a/packages/shared/src/components/sidebar/sections/BookmarkSection.tsx +++ b/packages/shared/src/components/sidebar/sections/BookmarkSection.tsx @@ -43,7 +43,7 @@ export const BookmarkSection = ({ }); }, [openModal, closeModal, createFolder]); - const menuItems: SidebarMenuItem[] = [ + const menuItems: SidebarMenuItem[] = ([ briefUIFeatureValue && { icon: (active: boolean) => ( } /> @@ -86,7 +86,7 @@ export const BookmarkSection = ({ requiresLogin: true, rightIcon, })), - ].filter(Boolean); + ].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..58f6c47d98f 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..53a7b57a41d 100644 --- a/packages/shared/src/components/squads/SquadTabs.tsx +++ b/packages/shared/src/components/squads/SquadTabs.tsx @@ -24,7 +24,7 @@ export function SquadTabs({ active, handle }: SquadTabsProps): ReactElement { const { count } = useSquadPendingPosts({ squadId: squad?.id, }); - const isModerator = verifyPermission(squad, SourcePermissions.ModeratePost); + const isModerator = verifyPermission(squad!, SourcePermissions.ModeratePost); 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..0f512aff7be 100644 --- a/packages/shared/src/components/squads/layout/SquadDirectoryNavbarItem.tsx +++ b/packages/shared/src/components/squads/layout/SquadDirectoryNavbarItem.tsx @@ -37,7 +37,7 @@ export function SquadDirectoryNavbarItem({ condition={!!path} wrapper={(component) => ( // TODO: WT-2239 - Remove legacyBehavior prop once all SquadDirectoryNavbarItem components are updated - + {component} )} diff --git a/packages/shared/src/components/squads/layout/useSquadDirectoryLayout.ts b/packages/shared/src/components/squads/layout/useSquadDirectoryLayout.ts index 43b7c2f10f1..57eba8c8482 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 {}; diff --git a/packages/shared/src/components/squads/stack/SourceStackModal.tsx b/packages/shared/src/components/squads/stack/SourceStackModal.tsx index 9858a086df5..c8d40cf09ad 100644 --- a/packages/shared/src/components/squads/stack/SourceStackModal.tsx +++ b/packages/shared/src/components/squads/stack/SourceStackModal.tsx @@ -67,7 +67,7 @@ export function SourceStackModal({ await onSubmit({ title: data.title.trim(), }); - rest.onRequestClose?.(null); + rest.onRequestClose?.(); }); 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..bc987d45705 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)} ); diff --git a/packages/shared/src/components/tags/TagElement.tsx b/packages/shared/src/components/tags/TagElement.tsx index 8686daa653e..ad2c8a1479c 100644 --- a/packages/shared/src/components/tags/TagElement.tsx +++ b/packages/shared/src/components/tags/TagElement.tsx @@ -30,7 +30,7 @@ export const TagElement = ({ 'relative', )} variant={isSelected ? ButtonVariant.Primary : ButtonVariant.Float} - color={isSelected ? ButtonColor.Cabbage : undefined} + {...(isSelected && { color: ButtonColor.Cabbage })} onClick={() => { onClick({ tag }); }} diff --git a/packages/shared/src/components/tooltip/Tooltip.tsx b/packages/shared/src/components/tooltip/Tooltip.tsx index a9ec11d6a02..75f0a5ea09c 100644 --- a/packages/shared/src/components/tooltip/Tooltip.tsx +++ b/packages/shared/src/components/tooltip/Tooltip.tsx @@ -47,9 +47,9 @@ export function Tooltip({ onOpenChange={setOpen} > e.currentTarget.blur()} + onMouseUp={(e: React.MouseEvent) => e.currentTarget.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..0baece3e264 100644 --- a/packages/shared/src/components/withFeaturesBoundary.spec.tsx +++ b/packages/shared/src/components/withFeaturesBoundary.spec.tsx @@ -1,13 +1,14 @@ 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..dd0a2d9f5bb 100644 --- a/packages/shared/src/contexts/BuyCoresContext/types.ts +++ b/packages/shared/src/contexts/BuyCoresContext/types.ts @@ -4,7 +4,7 @@ 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..ea3c8094f6b 100644 --- a/packages/shared/src/contexts/NotificationsContext.tsx +++ b/packages/shared/src/contexts/NotificationsContext.tsx @@ -9,7 +9,7 @@ export interface NotificationsContextData { } const NotificationsContext = - React.createContext(null); + 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/RecruiterPaymentPaddleContext.tsx b/packages/shared/src/contexts/RecruiterPaymentContext/RecruiterPaymentPaddleContext.tsx index 3f26ac0c061..16f657cdc27 100644 --- a/packages/shared/src/contexts/RecruiterPaymentContext/RecruiterPaymentPaddleContext.tsx +++ b/packages/shared/src/contexts/RecruiterPaymentContext/RecruiterPaymentPaddleContext.tsx @@ -36,7 +36,7 @@ export const RecruiterPaymentPaddleContextProvider = ({ const { data: prices } = useQuery( recruiterPricesQueryOptions({ - user, + user: user!, isLoggedIn, discountId: appliedDiscountId ?? undefined, }), diff --git a/packages/shared/src/contexts/RecruiterPaymentContext/types.ts b/packages/shared/src/contexts/RecruiterPaymentContext/types.ts index a37f6d8f827..b92fa95fc5f 100644 --- a/packages/shared/src/contexts/RecruiterPaymentContext/types.ts +++ b/packages/shared/src/contexts/RecruiterPaymentContext/types.ts @@ -6,7 +6,7 @@ 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..faa86631979 100644 --- a/packages/shared/src/contexts/payment/context.ts +++ b/packages/shared/src/contexts/payment/context.ts @@ -29,7 +29,7 @@ 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..0311d374f39 100644 --- a/packages/shared/src/contexts/payment/index.tsx +++ b/packages/shared/src/contexts/payment/index.tsx @@ -27,7 +27,7 @@ export const PaymentContextProvider = ({ if (checkIsExtension()) { return ( - {children} + {children} ); } 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..afa471ad528 100644 --- a/packages/shared/src/features/boost/usePostBoostEstimation.ts +++ b/packages/shared/src/features/boost/usePostBoostEstimation.ts @@ -18,7 +18,7 @@ export const usePostBoostEstimation = ({ query, }: UsePostBoostEstimationProps) => { const isOldPost = useMemo( - () => isOlderThan(oneMinute * 5, new Date(postFromProps.createdAt)), + () => isOlderThan(oneMinute * 5, new Date(postFromProps.createdAt!)), [postFromProps.createdAt], ); const [retriesExhausted, setRetriesExhausted] = useState(false); 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..409f47e71c2 100644 --- a/packages/shared/src/features/briefing/hooks/useGenerateBrief.ts +++ b/packages/shared/src/features/briefing/hooks/useGenerateBrief.ts @@ -29,7 +29,7 @@ export const useGenerateBrief = ({ onGenerated }: UseGenerateBriefingProps) => { if (balance) { updateUser({ - ...user, + ...user!, balance, }); } diff --git a/packages/shared/src/features/onboarding/hooks/useFunnelNavigation.ts b/packages/shared/src/features/onboarding/hooks/useFunnelNavigation.ts index 3d038507aea..234c080dadf 100644 --- a/packages/shared/src/features/onboarding/hooks/useFunnelNavigation.ts +++ b/packages/shared/src/features/onboarding/hooks/useFunnelNavigation.ts @@ -211,7 +211,7 @@ export const useFunnelNavigation = ({ ); const step: FunnelStep = useMemo( - () => getFunnelStepByPosition(funnel, position), + () => getFunnelStepByPosition(funnel, position)!, [funnel, position], ); const navigationStateRef = useRef({ 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..6a3969b4592 100644 --- a/packages/shared/src/features/onboarding/shared/PricingPlan.tsx +++ b/packages/shared/src/features/onboarding/shared/PricingPlan.tsx @@ -42,8 +42,8 @@ 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..dc9fc49fb4d 100644 --- a/packages/shared/src/features/onboarding/shared/PricingPlans.tsx +++ b/packages/shared/src/features/onboarding/shared/PricingPlans.tsx @@ -31,7 +31,7 @@ export function PricingPlans({ id={`${name}-${plan.id || plan.value}`} value={plan.value} checked={value === plan.value} - onChange={() => onChange(plan.value)} + onChange={() => onChange(plan.value!)} perks={perks} /> ))} diff --git a/packages/shared/src/features/onboarding/shared/StepHeadline.tsx b/packages/shared/src/features/onboarding/shared/StepHeadline.tsx index f6b3b4d9722..0c250912004 100644 --- a/packages/shared/src/features/onboarding/shared/StepHeadline.tsx +++ b/packages/shared/src/features/onboarding/shared/StepHeadline.tsx @@ -32,7 +32,7 @@ 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..09c1b916412 100644 --- a/packages/shared/src/features/onboarding/steps/FunnelCheckout.tsx +++ b/packages/shared/src/features/onboarding/steps/FunnelCheckout.tsx @@ -40,7 +40,7 @@ export const FunnelCheckout = ({ currentPriceIdRef.current = priceId; - openCheckout({ + 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..18c8cece67e 100644 --- a/packages/shared/src/features/onboarding/steps/FunnelPricing/FunnelPricing.spec.tsx +++ b/packages/shared/src/features/onboarding/steps/FunnelPricing/FunnelPricing.spec.tsx @@ -242,7 +242,7 @@ describe('FunnelPricing', () => { ); // Click on the monthly plan - fireEvent.click(monthlyPlan); + fireEvent.click(monthlyPlan!); // Then click the CTA const proceedButtons = screen.getAllByText('Checkout'); diff --git a/packages/shared/src/features/opportunity/components/CVExists.tsx b/packages/shared/src/features/opportunity/components/CVExists.tsx index 7ae6fec2922..d4cc3efaef3 100644 --- a/packages/shared/src/features/opportunity/components/CVExists.tsx +++ b/packages/shared/src/features/opportunity/components/CVExists.tsx @@ -45,7 +45,7 @@ export const CVExists = ({ className="flex items-center gap-2" > - {preferences?.cv?.fileName ?? `${user.username}.pdf`} + {preferences?.cv?.fileName ?? `${user!.username}.pdf`} diff --git a/packages/shared/src/features/opportunity/components/ClearEmploymentAgreementButton.tsx b/packages/shared/src/features/opportunity/components/ClearEmploymentAgreementButton.tsx index b5be548f979..e9d9754bbb0 100644 --- a/packages/shared/src/features/opportunity/components/ClearEmploymentAgreementButton.tsx +++ b/packages/shared/src/features/opportunity/components/ClearEmploymentAgreementButton.tsx @@ -22,7 +22,7 @@ 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({ diff --git a/packages/shared/src/features/opportunity/components/ClearResumeButton.tsx b/packages/shared/src/features/opportunity/components/ClearResumeButton.tsx index 75411fe0077..059afb74ab5 100644 --- a/packages/shared/src/features/opportunity/components/ClearResumeButton.tsx +++ b/packages/shared/src/features/opportunity/components/ClearResumeButton.tsx @@ -22,7 +22,7 @@ export const ClearResumeButton = (): 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: clearResume, isPending: isClearResumePending } = useMutation({ diff --git a/packages/shared/src/features/opportunity/components/UploadEmploymentAgreementButton.tsx b/packages/shared/src/features/opportunity/components/UploadEmploymentAgreementButton.tsx index f752454c16e..51969302af7 100644 --- a/packages/shared/src/features/opportunity/components/UploadEmploymentAgreementButton.tsx +++ b/packages/shared/src/features/opportunity/components/UploadEmploymentAgreementButton.tsx @@ -22,7 +22,7 @@ export const UploadEmploymentAgreementButton = (): ReactElement => { const { logEvent } = useLogContext(); const { displayToast } = useToastNotification(); - const updateQuery = useUpdateQuery(getCandidatePreferencesOptions(user?.id)); + const updateQuery = useUpdateQuery(getCandidatePreferencesOptions(user!.id)); const { mutate: uploadEmploymentAgreement, isPending: isUploadPending } = useMutation({ diff --git a/packages/shared/src/features/organizations/components/LinksInput.tsx b/packages/shared/src/features/organizations/components/LinksInput.tsx index 9fff7f49b38..72c5af0f6e8 100644 --- a/packages/shared/src/features/organizations/components/LinksInput.tsx +++ b/packages/shared/src/features/organizations/components/LinksInput.tsx @@ -153,7 +153,7 @@ export const LinksInput = ({ size={ButtonSize.XSmall} icon={} onClick={handleAdd} - disabled={!url.trim() || (isUnrecognized && !customLabel.trim())} + disabled={!url.trim() || (isUnrecognized && !customLabel.trim()) || undefined} > Add 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..812236c5d6e 100644 --- a/packages/shared/src/features/profile/components/ProfileCompany.tsx +++ b/packages/shared/src/features/profile/components/ProfileCompany.tsx @@ -77,7 +77,7 @@ 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..cdfde5c16ca 100644 --- a/packages/shared/src/features/profile/components/ProfileGithubRepository.tsx +++ b/packages/shared/src/features/profile/components/ProfileGithubRepository.tsx @@ -96,7 +96,7 @@ 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..47a9e25c480 100644 --- a/packages/shared/src/features/profile/components/ProfileWidgets/BadgesAndAwards.spec.tsx +++ b/packages/shared/src/features/profile/components/ProfileWidgets/BadgesAndAwards.spec.tsx @@ -35,7 +35,6 @@ 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', 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..c6bafca2e16 100644 --- a/packages/shared/src/features/profile/components/ProfileWidgets/ProfileCompletion.spec.tsx +++ b/packages/shared/src/features/profile/components/ProfileWidgets/ProfileCompletion.spec.tsx @@ -38,7 +38,7 @@ const renderWithAuth = (user: LoggedUser | null) => { return render( { /> ))} - {globalThis?.navigator?.share && ( + {!!globalThis?.navigator?.share && ( diff --git a/packages/shared/src/features/profile/components/ProfileCompany.tsx b/packages/shared/src/features/profile/components/ProfileCompany.tsx index 812236c5d6e..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 cdfde5c16ca..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/hooks/useGear.ts b/packages/shared/src/features/profile/hooks/useGear.ts index 8f2766d8c3d..409f2082804 100644 --- a/packages/shared/src/features/profile/hooks/useGear.ts +++ b/packages/shared/src/features/profile/hooks/useGear.ts @@ -22,7 +22,11 @@ export function useGear(user: PublicProfile | null) { const { logEvent } = useLogContext(); const isOwner = loggedUser?.id === user?.id; - const queryKey = generateQueryKey(RequestKey.Gear, user ?? undefined, 'profile'); + const queryKey = generateQueryKey( + RequestKey.Gear, + user ?? undefined, + 'profile', + ); const query = useQuery({ queryKey, diff --git a/packages/shared/src/features/profile/hooks/useGearSearch.ts b/packages/shared/src/features/profile/hooks/useGearSearch.ts index d5346dbd089..a4407a4ba21 100644 --- a/packages/shared/src/features/profile/hooks/useGearSearch.ts +++ b/packages/shared/src/features/profile/hooks/useGearSearch.ts @@ -7,7 +7,11 @@ export function useGearSearch(query: string) { const trimmedQuery = query.trim(); const enabled = trimmedQuery.length >= 1; - const queryKey = generateQueryKey(RequestKey.GearSearch, undefined, trimmedQuery); + const queryKey = generateQueryKey( + RequestKey.GearSearch, + undefined, + trimmedQuery, + ); const searchQuery = useQuery({ queryKey, diff --git a/packages/shared/src/features/profile/hooks/useHotTakes.ts b/packages/shared/src/features/profile/hooks/useHotTakes.ts index a23c3fc09f6..2200ff8b2f0 100644 --- a/packages/shared/src/features/profile/hooks/useHotTakes.ts +++ b/packages/shared/src/features/profile/hooks/useHotTakes.ts @@ -25,7 +25,11 @@ export const useHotTakes = (user: PublicProfile | null) => { const { isOwner } = useProfilePreview(user); const { logEvent } = useLogContext(); - const queryKey = generateQueryKey(RequestKey.UserHotTakes, user ?? undefined, 'profile'); + const queryKey = generateQueryKey( + RequestKey.UserHotTakes, + user ?? undefined, + 'profile', + ); const query = useQuery({ queryKey, diff --git a/packages/shared/src/features/profile/hooks/useUserStack.ts b/packages/shared/src/features/profile/hooks/useUserStack.ts index 8a3ce4228b3..269157f237d 100644 --- a/packages/shared/src/features/profile/hooks/useUserStack.ts +++ b/packages/shared/src/features/profile/hooks/useUserStack.ts @@ -25,7 +25,11 @@ export function useUserStack(user: PublicProfile | null) { const { isOwner } = useProfilePreview(user); const { logEvent } = useLogContext(); - const queryKey = generateQueryKey(RequestKey.UserStack, user ?? undefined, 'profile'); + const queryKey = generateQueryKey( + RequestKey.UserStack, + user ?? undefined, + 'profile', + ); const query = useQuery({ queryKey, diff --git a/packages/shared/src/hooks/source/usePrivateSourceJoin.ts b/packages/shared/src/hooks/source/usePrivateSourceJoin.ts index 02e006c3ec8..28366577afd 100644 --- a/packages/shared/src/hooks/source/usePrivateSourceJoin.ts +++ b/packages/shared/src/hooks/source/usePrivateSourceJoin.ts @@ -62,7 +62,9 @@ export const usePrivateSourceJoin = ({ router.replace( getPathnameWithQuery( - `${webappUrl}${sourceTypeToPage[sourceType as SourceType]}/${sourceId}/${squadJoinToken}`, + `${webappUrl}${ + sourceTypeToPage[sourceType as SourceType] + }/${sourceId}/${squadJoinToken}`, searchParams, ), ); diff --git a/packages/shared/src/hooks/source/useSourceStack.ts b/packages/shared/src/hooks/source/useSourceStack.ts index eee8408e6b0..2cec99f6740 100644 --- a/packages/shared/src/hooks/source/useSourceStack.ts +++ b/packages/shared/src/hooks/source/useSourceStack.ts @@ -24,7 +24,11 @@ export function useSourceStack(squad: Squad | null) { ? verifyPermission(squad, SourcePermissionsEnum.Edit) : false; - const queryKey = generateQueryKey(RequestKey.SourceStack, undefined, squad?.id); + const queryKey = generateQueryKey( + RequestKey.SourceStack, + undefined, + squad?.id, + ); const query = useQuery({ queryKey, diff --git a/packages/shared/src/hooks/useAutoComplete.ts b/packages/shared/src/hooks/useAutoComplete.ts index ab389b21094..73df136b8ae 100644 --- a/packages/shared/src/hooks/useAutoComplete.ts +++ b/packages/shared/src/hooks/useAutoComplete.ts @@ -29,7 +29,9 @@ export function useAutoComplete( setSelectedItemIndex(event.key === 'ArrowDown' ? 0 : n - 1); } } else if (event.key === 'Enter') { - submitQuery((selectedItemIndex > -1 && items[selectedItemIndex]) || undefined); + submitQuery( + (selectedItemIndex > -1 && items[selectedItemIndex]) || undefined, + ); } }, }; diff --git a/packages/shared/src/hooks/useConditionalFeature.spec.tsx b/packages/shared/src/hooks/useConditionalFeature.spec.tsx index 12dcf43be67..44afa7e84da 100644 --- a/packages/shared/src/hooks/useConditionalFeature.spec.tsx +++ b/packages/shared/src/hooks/useConditionalFeature.spec.tsx @@ -30,7 +30,9 @@ const createWrapper = ({ value = testFeature.defaultValue }) => { loadedUserFromCache squads={[]} > - + { - const [purify, setPurify] = useState(undefined as unknown as DOMPurify.DOMPurifyI); + const [purify, setPurify] = useState( + undefined as unknown as DOMPurify.DOMPurifyI, + ); useEffect(() => { setPurify(createDOMPurify(globalThis.window)); From 962106bf3fecdc4871c7620b872ee0f301b11487 Mon Sep 17 00:00:00 2001 From: Chris Bongers Date: Tue, 24 Mar 2026 08:48:45 +0200 Subject: [PATCH 3/7] fix: ts-lint --- .../src/components/buttons/Button.spec.tsx | 2 +- .../search/SearchFilterTimeButton.tsx | 2 +- .../SearchPanel/SearchPanelDropdown.tsx | 4 ++ .../squads/layout/useSquadDirectoryLayout.ts | 2 +- .../shared/src/components/tags/TagElement.tsx | 44 ++++++++++++------- .../shared/src/components/tooltip/Tooltip.tsx | 2 +- .../shared/src/contexts/payment/index.tsx | 5 ++- .../ProfileWidgets/BadgesAndAwards.spec.tsx | 32 +++++++++----- .../ProfileWidgets/ProfileCompletion.spec.tsx | 2 - .../recruiter/components/OnboardingView.tsx | 12 +++-- .../shared/src/hooks/input/useDebouncedUrl.ts | 4 +- 11 files changed, 72 insertions(+), 39 deletions(-) diff --git a/packages/shared/src/components/buttons/Button.spec.tsx b/packages/shared/src/components/buttons/Button.spec.tsx index 9d221f69aea..ed8829a9804 100644 --- a/packages/shared/src/components/buttons/Button.spec.tsx +++ b/packages/shared/src/components/buttons/Button.spec.tsx @@ -13,7 +13,7 @@ import { UpvoteIcon } from '../icons'; const renderComponent = ( props: Partial> = {}, ): RenderResult => { - return render( + ); + } + + return ( + ); }; diff --git a/packages/shared/src/components/tooltip/Tooltip.tsx b/packages/shared/src/components/tooltip/Tooltip.tsx index 75f0a5ea09c..a7936db9211 100644 --- a/packages/shared/src/components/tooltip/Tooltip.tsx +++ b/packages/shared/src/components/tooltip/Tooltip.tsx @@ -49,7 +49,7 @@ export function Tooltip({ e.currentTarget.blur()} + onMouseUp={(e: React.MouseEvent) => (e.currentTarget as HTMLElement).blur()} {...(enableMobileClick && { onClick: () => setOpen(true) })} > {children} diff --git a/packages/shared/src/contexts/payment/index.tsx b/packages/shared/src/contexts/payment/index.tsx index b08b8b1d5a6..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 = ({ 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 47a9e25c480..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,19 +35,27 @@ const defaultUser: PublicProfile = { image: 'https://daily.dev/test.png', bio: 'Test bio', createdAt: '2020-01-01T00:00:00.000Z', - 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/ProfileCompletion.spec.tsx b/packages/shared/src/features/profile/components/ProfileWidgets/ProfileCompletion.spec.tsx index c6bafca2e16..6b89eacd90f 100644 --- a/packages/shared/src/features/profile/components/ProfileWidgets/ProfileCompletion.spec.tsx +++ b/packages/shared/src/features/profile/components/ProfileWidgets/ProfileCompletion.spec.tsx @@ -42,12 +42,10 @@ const renderWithAuth = (user: LoggedUser | null) => { updateUser={jest.fn()} tokenRefreshed getRedirectUri={jest.fn()} - closeLogin={jest.fn()} loadingUser={false} loadedUserFromCache refetchBoot={jest.fn()} firstLoad={false} - isValidSession > diff --git a/packages/shared/src/features/recruiter/components/OnboardingView.tsx b/packages/shared/src/features/recruiter/components/OnboardingView.tsx index a5315db8533..279fb869ed2 100644 --- a/packages/shared/src/features/recruiter/components/OnboardingView.tsx +++ b/packages/shared/src/features/recruiter/components/OnboardingView.tsx @@ -1,4 +1,4 @@ -import type { ReactElement } from 'react'; +import type { ReactElement, ComponentType, ReactNode } from 'react'; import React from 'react'; import { RecruiterHeader } from '../../../components/recruiter/Header'; import { @@ -9,14 +9,20 @@ import type { OpportunityPreviewContextType } from '../../opportunity/context/Op import { OpportunityPreviewProvider } from '../../opportunity/context/OpportunityPreviewContext'; import { AnalyzeContent } from '../../opportunity/components/analyze/AnalyzeContent'; +const PreviewProviderWithMock = + OpportunityPreviewProvider as unknown as ComponentType<{ + children?: ReactNode; + mockData?: OpportunityPreviewContextType; + }>; + export const OnboardingView = (): ReactElement => { return ( - +
-
+ ); }; diff --git a/packages/shared/src/hooks/input/useDebouncedUrl.ts b/packages/shared/src/hooks/input/useDebouncedUrl.ts index fc5408b1811..50b79c32ded 100644 --- a/packages/shared/src/hooks/input/useDebouncedUrl.ts +++ b/packages/shared/src/hooks/input/useDebouncedUrl.ts @@ -7,8 +7,8 @@ export const useDebouncedUrl = ( onValidate: StartFn, delay = 1000, ): [StartFn, CancelEvent] => - useDebounceFn((value: string) => { - if (!isValidHttpUrl(value) || !onValidate(value)) { + useDebounceFn((value?: string) => { + if (!value || !isValidHttpUrl(value) || !onValidate(value)) { return undefined; } From aa820d53335c09c17e4e9608836807acfd4f1994 Mon Sep 17 00:00:00 2001 From: Chris Bongers Date: Tue, 24 Mar 2026 09:28:51 +0200 Subject: [PATCH 4/7] chore: fix strict typing checks --- .../src/components/FeedItemComponent.tsx | 5 +- .../ProfileMenu/ProfileSectionItem.tsx | 83 +++++++++---------- .../shared/src/components/auth/common.tsx | 2 +- .../components/buttons/QuaternaryButton.tsx | 8 +- .../cards/Leaderboard/LeaderboardListItem.tsx | 37 +++++---- .../cards/common/ClickbaitShield.tsx | 10 ++- .../cards/common/SharedCardCover.tsx | 6 +- .../components/cards/share/ShareList.spec.tsx | 2 +- .../src/components/cards/squad/SquadList.tsx | 2 +- .../squad/common/useSquadsDirectoryLogging.ts | 6 +- .../feeds/FeedSettings/FeedSettingsCreate.tsx | 4 +- .../components/layout/HeaderButtons.spec.tsx | 4 +- .../modals/AchievementCompletionModal.tsx | 8 +- .../src/components/modals/FeedbackModal.tsx | 9 +- .../src/components/modals/RepostsModal.tsx | 7 +- .../modals/SharedBookmarksModal.spec.tsx | 2 +- .../src/components/modals/SquadTourModal.tsx | 10 ++- .../modals/plus/MobileSmartPromptsModal.tsx | 2 +- .../opportunity/NewOpportunityPopover.tsx | 4 +- .../OpportunityEditRecruiterModal.tsx | 13 ++- .../OpportunityReimportModal.tsx | 11 ++- .../src/components/plus/PlusExtension.tsx | 6 +- .../components/plus/PlusMarketingModal.tsx | 6 +- .../src/components/post/BasePostContent.tsx | 8 +- .../post/common/PostClickbaitShield.tsx | 10 ++- .../profile/ExperienceLevelDropdown.tsx | 2 +- .../components/profile/ExperienceSettings.tsx | 7 +- .../src/components/profile/ProfileButton.tsx | 6 +- .../src/components/profile/SquadsList.tsx | 2 +- .../components/recruiter/ConnectHeader.tsx | 6 +- .../src/components/recruiter/FeedbackList.tsx | 7 +- .../search/SearchPanel/SearchPanelContext.ts | 4 +- .../SearchPanel/SearchPanelProvider.tsx | 4 + .../SearchPanel/useSearchPanelAction.ts | 2 +- .../components/sidebar/ClickableNavItem.tsx | 8 +- .../sidebar/sections/BookmarkSection.tsx | 6 +- .../components/squads/SquadPostListItem.tsx | 2 +- .../src/components/squads/SquadTabs.tsx | 4 +- .../layout/SquadDirectoryNavbarItem.tsx | 48 +++++------ .../squads/stack/SourceStackModal.tsx | 10 ++- .../src/components/streak/popup/DayStreak.tsx | 2 +- .../shared/src/components/tooltip/Tooltip.tsx | 4 +- .../RecruiterPaymentPaddleContext.tsx | 9 +- .../features/boost/usePostBoostEstimation.ts | 10 ++- .../briefing/hooks/useGenerateBrief.ts | 4 +- .../onboarding/hooks/useFunnelNavigation.ts | 15 +++- .../onboarding/shared/PricingPlans.tsx | 36 +++++--- .../onboarding/steps/FunnelCheckout.tsx | 6 +- .../FunnelPricing/FunnelPricing.spec.tsx | 6 +- .../opportunity/components/CVExists.tsx | 6 +- .../ClearEmploymentAgreementButton.tsx | 9 +- .../components/ClearResumeButton.tsx | 7 +- .../UploadEmploymentAgreementButton.tsx | 8 +- .../experience/DeleteExperienceButton.tsx | 2 +- .../profile/components/gear/GearModal.tsx | 10 ++- .../components/hotTakes/HotTakeModal.tsx | 10 ++- .../components/stack/UserStackModal.tsx | 10 ++- .../WorkspacePhotoUploadModal.tsx | 13 ++- .../src/hooks/comments/useCommentById.ts | 7 +- .../shared/src/hooks/log/useLogContextData.ts | 6 +- .../log/useLogOpportunityNudgeImpression.ts | 6 +- .../hooks/onboarding/useGenerateUsername.ts | 7 +- .../src/hooks/referral/useJoinReferral.ts | 6 +- .../shared/src/hooks/useConditionalFeature.ts | 3 +- packages/shared/src/hooks/useFeedLayout.ts | 4 +- packages/shared/src/hooks/useIOSError.ts | 6 +- packages/shared/src/hooks/useLanguage.ts | 6 +- packages/shared/src/hooks/usePopupSelector.ts | 9 +- 68 files changed, 411 insertions(+), 199 deletions(-) diff --git a/packages/shared/src/components/FeedItemComponent.tsx b/packages/shared/src/components/FeedItemComponent.tsx index 2c8e7c430a6..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'; @@ -395,7 +396,9 @@ function FeedItemComponent({ switch (item.type) { case FeedItemType.Ad: { - const AdComponent = AdTag as React.ComponentType>; + const AdComponent = AdTag as React.ForwardRefExoticComponent< + AdCardProps & React.RefAttributes + >; return ( { 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/common.tsx b/packages/shared/src/components/auth/common.tsx index e73fcb777da..cfb59e49373 100644 --- a/packages/shared/src/components/auth/common.tsx +++ b/packages/shared/src/components/auth/common.tsx @@ -64,7 +64,7 @@ export interface AuthFormProps { export const getFormEmail = (e: React.FormEvent): string => { const form = e.currentTarget as HTMLFormElement; const input = Array.from(form.elements).find((el) => - ['email', 'traits.email'].includes(el.getAttribute('name')!), + ['email', 'traits.email'].includes(el.getAttribute('name') ?? ''), ) as HTMLInputElement; return input?.value?.trim() ?? ''; diff --git a/packages/shared/src/components/buttons/QuaternaryButton.tsx b/packages/shared/src/components/buttons/QuaternaryButton.tsx index 7a1f4a109b3..26c171e2179 100644 --- a/packages/shared/src/components/buttons/QuaternaryButton.tsx +++ b/packages/shared/src/components/buttons/QuaternaryButton.tsx @@ -31,7 +31,13 @@ function QuaternaryButtonComponent( ref?: Ref>, ): ReactElement { const anchorRef = useRef>(null); - useImperativeHandle(ref, () => anchorRef?.current!); + useImperativeHandle(ref, () => { + if (!anchorRef.current) { + throw new Error('QuaternaryButton ref is not attached'); + } + + return anchorRef.current; + }); const [isHovered, setIsHovered] = useState(false); const onLabelClick = (event: React.MouseEvent): void => { event.preventDefault(); diff --git a/packages/shared/src/components/cards/Leaderboard/LeaderboardListItem.tsx b/packages/shared/src/components/cards/Leaderboard/LeaderboardListItem.tsx index 56b51b3cf6c..2ecb351f1f2 100644 --- a/packages/shared/src/components/cards/Leaderboard/LeaderboardListItem.tsx +++ b/packages/shared/src/components/cards/Leaderboard/LeaderboardListItem.tsx @@ -1,7 +1,6 @@ import type { ReactElement, ReactNode } from 'react'; import React from 'react'; import { largeNumberFormat } from '../../../lib'; -import ConditionalWrapper from '../../ConditionalWrapper'; import Link from '../../utilities/Link'; import { Tooltip } from '../../tooltip/Tooltip'; @@ -26,26 +25,28 @@ export function LeaderboardListItem({ const shouldShowTooltip = concatScore && typeof index === 'number' && index >= 1000; const actualNumber = index.toLocaleString(); + const content = ( + <> + + + {formattedNumber} + + + {children} + + ); return (
  • - ( - - - {child} - - - )} - > - - - {formattedNumber} - - - {children} - + {href ? ( + + + {content} + + + ) : ( + content + )}
  • ); } diff --git a/packages/shared/src/components/cards/common/ClickbaitShield.tsx b/packages/shared/src/components/cards/common/ClickbaitShield.tsx index 65742779e76..24dc30ae704 100644 --- a/packages/shared/src/components/cards/common/ClickbaitShield.tsx +++ b/packages/shared/src/components/cards/common/ClickbaitShield.tsx @@ -71,10 +71,14 @@ export const ClickbaitShield = ({ post }: { post: Post }): ReactElement => { type: LazyModal.ClickbaitShield, }); } else { + if (!user) { + throw new Error( + 'ClickbaitShield requires an authenticated user to edit feed settings', + ); + } + router.push( - `${webappUrl}feeds/${user!.id}/edit?dview=${ - FeedSettingsMenu.AI - }`, + `${webappUrl}feeds/${user.id}/edit?dview=${FeedSettingsMenu.AI}`, ); } } else if (isMobile) { diff --git a/packages/shared/src/components/cards/common/SharedCardCover.tsx b/packages/shared/src/components/cards/common/SharedCardCover.tsx index 1761736fe48..1334304c9fb 100644 --- a/packages/shared/src/components/cards/common/SharedCardCover.tsx +++ b/packages/shared/src/components/cards/common/SharedCardCover.tsx @@ -36,7 +36,11 @@ export function SharedCardCover({ renderOverlay, CardImageComponent, }: SharedCardCoverProps): ReactElement { - const { overlay } = useCardCover({ post: post!, onShare }); + if (!post) { + throw new Error('SharedCardCover requires a post'); + } + + const { overlay } = useCardCover({ post, onShare }); const imageClasses = classNames( imageProps?.className, !!overlay && 'opacity-16', diff --git a/packages/shared/src/components/cards/share/ShareList.spec.tsx b/packages/shared/src/components/cards/share/ShareList.spec.tsx index 9b4a5bed3b9..eee680dc580 100644 --- a/packages/shared/src/components/cards/share/ShareList.spec.tsx +++ b/packages/shared/src/components/cards/share/ShareList.spec.tsx @@ -32,7 +32,7 @@ const sharedTweetPost = { type: PostType.SocialTwitter, title: 'Referenced tweet body', source: { - ...sharePost.sharedPost!.source, + ...(sharePost.sharedPost?.source ?? {}), handle: 'typescript', name: 'Typescript', }, diff --git a/packages/shared/src/components/cards/squad/SquadList.tsx b/packages/shared/src/components/cards/squad/SquadList.tsx index c70808d954a..76215eab919 100644 --- a/packages/shared/src/components/cards/squad/SquadList.tsx +++ b/packages/shared/src/components/cards/squad/SquadList.tsx @@ -34,7 +34,7 @@ export const SquadList = ({ }: SquadListProps): ReactElement => { const { image, name, permalink } = squad; const campaignId = ad?.data?.source?.flags?.campaignId; - const { ref, onClickAd } = useSquadsDirectoryLogging(ad!); + const { ref, onClickAd } = useSquadsDirectoryLogging(ad); const promotedText = useScrambler('Promoted'); return ( diff --git a/packages/shared/src/components/cards/squad/common/useSquadsDirectoryLogging.ts b/packages/shared/src/components/cards/squad/common/useSquadsDirectoryLogging.ts index e9414803189..1faf284040e 100644 --- a/packages/shared/src/components/cards/squad/common/useSquadsDirectoryLogging.ts +++ b/packages/shared/src/components/cards/squad/common/useSquadsDirectoryLogging.ts @@ -6,7 +6,7 @@ import { LogEvent } from '../../../../lib/log'; import { OtherFeedPage } from '../../../../lib/query'; import { useLogContext } from '../../../../contexts/LogContext'; -export const useSquadsDirectoryLogging = (ad: Ad) => { +export const useSquadsDirectoryLogging = (ad?: Ad) => { const { ref, inView } = useInView({ triggerOnce: true, }); @@ -16,6 +16,10 @@ export const useSquadsDirectoryLogging = (ad: Ad) => { const onLogAdEvent = useCallback( (action: LogEvent.Impression | LogEvent.Click) => { + if (!ad) { + throw new Error('Missing ad for squads directory logging'); + } + logEvent(adLogEvent(action, ad, feedLogExtra(OtherFeedPage.Squad))); }, [ad, logEvent], diff --git a/packages/shared/src/components/feeds/FeedSettings/FeedSettingsCreate.tsx b/packages/shared/src/components/feeds/FeedSettings/FeedSettingsCreate.tsx index 09da7b394ea..533686ff05d 100644 --- a/packages/shared/src/components/feeds/FeedSettings/FeedSettingsCreate.tsx +++ b/packages/shared/src/components/feeds/FeedSettings/FeedSettingsCreate.tsx @@ -253,7 +253,7 @@ export const FeedSettingsCreate = (): ReactElement => { const createdFeedId = createdFeedData?.id; - if (!newFeedId) { + if (!createdFeedId) { return ( @@ -263,5 +263,5 @@ export const FeedSettingsCreate = (): ReactElement => { ); } - return ; + return ; }; diff --git a/packages/shared/src/components/layout/HeaderButtons.spec.tsx b/packages/shared/src/components/layout/HeaderButtons.spec.tsx index e0d38f21cdc..185224f3f36 100644 --- a/packages/shared/src/components/layout/HeaderButtons.spec.tsx +++ b/packages/shared/src/components/layout/HeaderButtons.spec.tsx @@ -2,7 +2,7 @@ import type { ReactElement } from 'react'; import React from 'react'; import { QueryClient } from '@tanstack/react-query'; import { render, screen } from '@testing-library/react'; -import { GrowthBook } from '@growthbook/growthbook'; +import { GrowthBook } from '@growthbook/growthbook-react'; import { TestBootProvider } from '../../../__tests__/helpers/boot'; import { HeaderButtons } from './HeaderButtons'; @@ -57,7 +57,7 @@ const renderComponent = ({ , diff --git a/packages/shared/src/components/modals/AchievementCompletionModal.tsx b/packages/shared/src/components/modals/AchievementCompletionModal.tsx index a21fd6d7a25..4c4a092daff 100644 --- a/packages/shared/src/components/modals/AchievementCompletionModal.tsx +++ b/packages/shared/src/components/modals/AchievementCompletionModal.tsx @@ -11,7 +11,7 @@ import { useProfileAchievements } from '../../hooks/profile/useProfileAchievemen import { useTrackedAchievement } from '../../hooks/profile/useTrackedAchievement'; import { ActionType } from '../../graphql/actions'; import { LogEvent, TargetType } from '../../lib/log'; -import type { ModalProps } from './common/Modal'; +import type { LazyModalCommonProps, ModalProps } from './common/Modal'; import { Modal } from './common/Modal'; import { ModalClose } from './common/ModalClose'; import { @@ -35,9 +35,11 @@ const sparkles = Array.from({ length: 60 }, (_, i) => ({ size: 3 + ((i * 7) % 5), })); -interface AchievementCompletionModalProps extends ModalProps { +interface AchievementCompletionModalProps + extends Omit { achievementId: string; onAfterClose?: () => void; + onRequestClose?: LazyModalCommonProps['onRequestClose']; } export const AchievementCompletionModal = ({ @@ -70,7 +72,7 @@ export const AchievementCompletionModal = ({ const [sparklesFalling, setSparklesFalling] = useState(false); const loggedImpression = useRef(false); const handleClose = (event?: MouseEvent | KeyboardEvent) => - onRequestClose?.(event!); + onRequestClose?.(event); const unlockedAchievement = useMemo( () => achievements?.find((item) => item.achievement.id === achievementId), diff --git a/packages/shared/src/components/modals/FeedbackModal.tsx b/packages/shared/src/components/modals/FeedbackModal.tsx index a4541b6fb7e..2d7cde0d9b8 100644 --- a/packages/shared/src/components/modals/FeedbackModal.tsx +++ b/packages/shared/src/components/modals/FeedbackModal.tsx @@ -1,7 +1,7 @@ import type { ReactElement, ChangeEvent } from 'react'; import React, { useState, useCallback, useRef, useEffect } from 'react'; import { useMutation } from '@tanstack/react-query'; -import type { ModalProps } from './common/Modal'; +import type { LazyModalCommonProps, ModalProps } from './common/Modal'; import { Modal } from './common/Modal'; import { ModalSize } from './common/types'; import { Button, ButtonVariant, ButtonSize } from '../buttons/Button'; @@ -29,6 +29,9 @@ import { import { useSettingsContext } from '../../contexts/SettingsContext'; const FEEDBACK_MAX_LENGTH = 2000; +type FeedbackModalProps = Omit & { + onRequestClose?: LazyModalCommonProps['onRequestClose']; +}; const categoryOptions: { value: FeedbackCategory; label: string }[] = [ { value: FeedbackCategory.BugReport, label: 'Bug Report' }, @@ -41,7 +44,7 @@ const categoryOptions: { value: FeedbackCategory; label: string }[] = [ const FeedbackModal = ({ onRequestClose, ...props -}: ModalProps): ReactElement => { +}: FeedbackModalProps): ReactElement => { const { displayToast } = useToastNotification(); const { themeMode } = useSettingsContext(); const fileInputRef = useRef(null); @@ -159,7 +162,7 @@ const FeedbackModal = ({ mutationFn: (input: FeedbackInput) => submitFeedback(input), onSuccess: () => { displayToast('Thank you for your feedback!'); - onRequestClose?.(); + onRequestClose?.(undefined); }, onError: () => { hasSubmitted.current = false; diff --git a/packages/shared/src/components/modals/RepostsModal.tsx b/packages/shared/src/components/modals/RepostsModal.tsx index c1587c922a2..777031ab682 100644 --- a/packages/shared/src/components/modals/RepostsModal.tsx +++ b/packages/shared/src/components/modals/RepostsModal.tsx @@ -24,10 +24,15 @@ export function RepostsModal({ const container = useRef(null); const modalRef = useRef(null); const { requestMethod } = useRequestProtocol(); + + if (!requestMethod) { + throw new Error('Request method is required in RepostsModal'); + } + const queryResult = useInfiniteQuery({ queryKey, queryFn: ({ pageParam }) => - requestMethod!( + requestMethod( query, { ...params, after: pageParam }, { requestKey: JSON.stringify(queryKey) }, diff --git a/packages/shared/src/components/modals/SharedBookmarksModal.spec.tsx b/packages/shared/src/components/modals/SharedBookmarksModal.spec.tsx index 7228e7adeb1..f761679e818 100644 --- a/packages/shared/src/components/modals/SharedBookmarksModal.spec.tsx +++ b/packages/shared/src/components/modals/SharedBookmarksModal.spec.tsx @@ -89,7 +89,7 @@ it('should enable public mode on toggle click', async () => { const checkboxes = await screen.findAllByRole('checkbox'); const checkbox = checkboxes.find((el) => // eslint-disable-next-line testing-library/no-node-access, testing-library/prefer-screen-queries - queryByText(el.parentElement!, 'Public mode'), + el.parentElement ? queryByText(el.parentElement, 'Public mode') : null, ) as HTMLInputElement; fireEvent.click(checkbox); await waitForNock(); diff --git a/packages/shared/src/components/modals/SquadTourModal.tsx b/packages/shared/src/components/modals/SquadTourModal.tsx index 34a349ef869..6e1a6dcac8b 100644 --- a/packages/shared/src/components/modals/SquadTourModal.tsx +++ b/packages/shared/src/components/modals/SquadTourModal.tsx @@ -1,16 +1,20 @@ import type { ReactElement } from 'react'; import React from 'react'; import SquadTour from '../squads/SquadTour'; -import type { ModalProps } from './common/Modal'; +import type { LazyModalCommonProps, ModalProps } from './common/Modal'; import { Modal } from './common/Modal'; import { ButtonSize, ButtonVariant } from '../buttons/Button'; import { useSquadTour } from '../../hooks/useSquadTour'; import { ModalClose } from './common/ModalClose'; +type SquadTourModalProps = Omit & { + onRequestClose?: LazyModalCommonProps['onRequestClose']; +}; + function SquadTourModal({ onRequestClose, ...props -}: ModalProps): ReactElement { +}: SquadTourModalProps): ReactElement { const { onCloseTour } = useSquadTour(); const onModalClose: typeof onRequestClose = (param) => { onCloseTour(); @@ -27,7 +31,7 @@ function SquadTourModal({ isDrawerOnMobile drawerProps={{ className: { drawer: 'pb-4', close: 'mx-4' } }} > - onModalClose(undefined!)} /> + onModalClose(undefined)} /> onClick(event, id)} > {label} diff --git a/packages/shared/src/components/opportunity/NewOpportunityPopover.tsx b/packages/shared/src/components/opportunity/NewOpportunityPopover.tsx index 56f4b3daed9..6bbd2d386ce 100644 --- a/packages/shared/src/components/opportunity/NewOpportunityPopover.tsx +++ b/packages/shared/src/components/opportunity/NewOpportunityPopover.tsx @@ -49,8 +49,8 @@ export const NewOpportunityPopover = (): ReactElement => { type={TypographyType.Subhead} color={TypographyColor.Primary} > - Hey {user!.name}, We've found a new opportunity that aligns with - your experience and what you’ve been exploring lately. + Hey {user?.name ?? 'there'}, We've found a new opportunity that + aligns with your experience and what you’ve been exploring lately. diff --git a/packages/shared/src/components/profile/SquadsList.tsx b/packages/shared/src/components/profile/SquadsList.tsx index f81c04ed9fd..ac08c0cd5a7 100644 --- a/packages/shared/src/components/profile/SquadsList.tsx +++ b/packages/shared/src/components/profile/SquadsList.tsx @@ -191,7 +191,7 @@ export function SquadsList({ {edges.map(({ node }) => ( ))} - {isWide && memberships!.edges.length > MAX_SQUADS && ( + {isWide && (memberships?.edges.length ?? 0) > MAX_SQUADS && (
    diff --git a/packages/shared/src/components/squads/SquadTabs.tsx b/packages/shared/src/components/squads/SquadTabs.tsx index 53a7b57a41d..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 0f512aff7be..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/stack/SourceStackModal.tsx b/packages/shared/src/components/squads/stack/SourceStackModal.tsx index c8d40cf09ad..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?.(); + rest.onRequestClose?.(undefined); }); const filteredSuggestions = useMemo(() => { diff --git a/packages/shared/src/components/streak/popup/DayStreak.tsx b/packages/shared/src/components/streak/popup/DayStreak.tsx index bc987d45705..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/tooltip/Tooltip.tsx b/packages/shared/src/components/tooltip/Tooltip.tsx index a7936db9211..d044dee8062 100644 --- a/packages/shared/src/components/tooltip/Tooltip.tsx +++ b/packages/shared/src/components/tooltip/Tooltip.tsx @@ -49,7 +49,9 @@ export function Tooltip({ (e.currentTarget as HTMLElement).blur()} + onMouseUp={(e: React.MouseEvent) => + (e.currentTarget as HTMLElement).blur() + } {...(enableMobileClick && { onClick: () => setOpen(true) })} > {children} diff --git a/packages/shared/src/contexts/RecruiterPaymentContext/RecruiterPaymentPaddleContext.tsx b/packages/shared/src/contexts/RecruiterPaymentContext/RecruiterPaymentPaddleContext.tsx index 16f657cdc27..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(); @@ -36,7 +43,7 @@ export const RecruiterPaymentPaddleContextProvider = ({ const { data: prices } = useQuery( recruiterPricesQueryOptions({ - user: user!, + user, isLoggedIn, discountId: appliedDiscountId ?? undefined, }), diff --git a/packages/shared/src/features/boost/usePostBoostEstimation.ts b/packages/shared/src/features/boost/usePostBoostEstimation.ts index afa471ad528..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/hooks/useGenerateBrief.ts b/packages/shared/src/features/briefing/hooks/useGenerateBrief.ts index 409f47e71c2..e595ad35c47 100644 --- a/packages/shared/src/features/briefing/hooks/useGenerateBrief.ts +++ b/packages/shared/src/features/briefing/hooks/useGenerateBrief.ts @@ -27,9 +27,9 @@ export const useGenerateBrief = ({ onGenerated }: UseGenerateBriefingProps) => { onSuccess: async ({ id, balance }) => { displayToast('Your Presidential Briefing is being generated ✅'); - if (balance) { + if (balance && user) { updateUser({ - ...user!, + ...user, balance, }); } diff --git a/packages/shared/src/features/onboarding/hooks/useFunnelNavigation.ts b/packages/shared/src/features/onboarding/hooks/useFunnelNavigation.ts index 234c080dadf..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/shared/PricingPlans.tsx b/packages/shared/src/features/onboarding/shared/PricingPlans.tsx index dc9fc49fb4d..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/steps/FunnelCheckout.tsx b/packages/shared/src/features/onboarding/steps/FunnelCheckout.tsx index 09c1b916412..4d3913759af 100644 --- a/packages/shared/src/features/onboarding/steps/FunnelCheckout.tsx +++ b/packages/shared/src/features/onboarding/steps/FunnelCheckout.tsx @@ -40,7 +40,11 @@ export const FunnelCheckout = ({ currentPriceIdRef.current = priceId; - openCheckout!({ + 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/FunnelPricing/FunnelPricing.spec.tsx b/packages/shared/src/features/onboarding/steps/FunnelPricing/FunnelPricing.spec.tsx index 18c8cece67e..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,7 +242,11 @@ describe('FunnelPricing', () => { ); // Click on the monthly plan - fireEvent.click(monthlyPlan!); + if (!monthlyPlan) { + throw new Error('Missing monthly plan radio button'); + } + + fireEvent.click(monthlyPlan); // Then click the CTA const proceedButtons = screen.getAllByText('Checkout'); diff --git a/packages/shared/src/features/opportunity/components/CVExists.tsx b/packages/shared/src/features/opportunity/components/CVExists.tsx index d4cc3efaef3..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 e9d9754bbb0..f72a4929c7c 100644 --- a/packages/shared/src/features/opportunity/components/ClearEmploymentAgreementButton.tsx +++ b/packages/shared/src/features/opportunity/components/ClearEmploymentAgreementButton.tsx @@ -22,7 +22,14 @@ export const ClearEmploymentAgreementButton = (): ReactElement => { const { logEvent } = useLogContext(); const { displayToast } = useToastNotification(); const { completeAction } = useActions(); - const opts = getCandidatePreferencesOptions(user!.id); + + if (!user) { + throw new Error( + 'ClearEmploymentAgreementButton requires an authenticated user', + ); + } + + const opts = getCandidatePreferencesOptions(user.id); const updateQuery = useUpdateQuery(opts); const { mutate: clearFile, isPending: isClearFilePending } = useMutation({ diff --git a/packages/shared/src/features/opportunity/components/ClearResumeButton.tsx b/packages/shared/src/features/opportunity/components/ClearResumeButton.tsx index 059afb74ab5..1cd577ccb6d 100644 --- a/packages/shared/src/features/opportunity/components/ClearResumeButton.tsx +++ b/packages/shared/src/features/opportunity/components/ClearResumeButton.tsx @@ -22,7 +22,12 @@ export const ClearResumeButton = (): ReactElement => { const { logEvent } = useLogContext(); const { displayToast } = useToastNotification(); const { completeAction } = useActions(); - const opts = getCandidatePreferencesOptions(user!.id); + + if (!user) { + throw new Error('ClearResumeButton requires an authenticated user'); + } + + const opts = getCandidatePreferencesOptions(user.id); const updateQuery = useUpdateQuery(opts); const { mutate: clearResume, isPending: isClearResumePending } = useMutation({ diff --git a/packages/shared/src/features/opportunity/components/UploadEmploymentAgreementButton.tsx b/packages/shared/src/features/opportunity/components/UploadEmploymentAgreementButton.tsx index 51969302af7..933c059a7c8 100644 --- a/packages/shared/src/features/opportunity/components/UploadEmploymentAgreementButton.tsx +++ b/packages/shared/src/features/opportunity/components/UploadEmploymentAgreementButton.tsx @@ -22,7 +22,13 @@ export const UploadEmploymentAgreementButton = (): ReactElement => { const { logEvent } = useLogContext(); const { displayToast } = useToastNotification(); - const updateQuery = useUpdateQuery(getCandidatePreferencesOptions(user!.id)); + if (!user) { + throw new Error( + 'UploadEmploymentAgreementButton requires an authenticated user', + ); + } + + const updateQuery = useUpdateQuery(getCandidatePreferencesOptions(user.id)); const { mutate: uploadEmploymentAgreement, isPending: isUploadPending } = useMutation({ diff --git a/packages/shared/src/features/profile/components/experience/DeleteExperienceButton.tsx b/packages/shared/src/features/profile/components/experience/DeleteExperienceButton.tsx index 39c6f2ff04d..62c8fd66566 100644 --- a/packages/shared/src/features/profile/components/experience/DeleteExperienceButton.tsx +++ b/packages/shared/src/features/profile/components/experience/DeleteExperienceButton.tsx @@ -35,7 +35,7 @@ const DeleteExperienceButton = ({ experienceType, }: DeleteExperienceButtonProps): React.ReactElement => { const { removeExperience, isPending } = useRemoveExperience({ - type: experienceType!, + type: experienceType ?? UserExperienceType.Work, }); const { showPrompt } = usePrompt(); diff --git a/packages/shared/src/features/profile/components/gear/GearModal.tsx b/packages/shared/src/features/profile/components/gear/GearModal.tsx index 3c5e532dbc0..cd562434af4 100644 --- a/packages/shared/src/features/profile/components/gear/GearModal.tsx +++ b/packages/shared/src/features/profile/components/gear/GearModal.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 '../../../../components/modals/common/Modal'; +import type { + LazyModalCommonProps, + ModalProps, +} from '../../../../components/modals/common/Modal'; import { Modal } from '../../../../components/modals/common/Modal'; import { TextField } from '../../../../components/fields/TextField'; import { Button, ButtonVariant } from '../../../../components/buttons/Button'; @@ -18,7 +21,8 @@ const gearFormSchema = z.object({ type GearFormData = z.infer; -type GearModalProps = Omit & { +type GearModalProps = Omit & { + onRequestClose?: LazyModalCommonProps['onRequestClose']; onSubmit: (input: AddGearInput) => Promise; }; @@ -57,7 +61,7 @@ export function GearModal({ onSubmit, ...rest }: GearModalProps): ReactElement { await onSubmit({ name: data.name.trim(), }); - rest.onRequestClose?.(); + rest.onRequestClose?.(undefined); }); const filteredSuggestions = useMemo(() => { diff --git a/packages/shared/src/features/profile/components/hotTakes/HotTakeModal.tsx b/packages/shared/src/features/profile/components/hotTakes/HotTakeModal.tsx index 223443d6b9d..d913078a653 100644 --- a/packages/shared/src/features/profile/components/hotTakes/HotTakeModal.tsx +++ b/packages/shared/src/features/profile/components/hotTakes/HotTakeModal.tsx @@ -4,7 +4,10 @@ import { Controller, FormProvider, useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import dynamic from 'next/dynamic'; -import type { ModalProps } from '../../../../components/modals/common/Modal'; +import type { + LazyModalCommonProps, + ModalProps, +} from '../../../../components/modals/common/Modal'; import { Modal } from '../../../../components/modals/common/Modal'; import { TextField } from '../../../../components/fields/TextField'; import { Button, ButtonVariant } from '../../../../components/buttons/Button'; @@ -31,7 +34,8 @@ const hotTakeFormSchema = z.object({ type HotTakeFormData = z.infer; -type HotTakeModalProps = Omit & { +type HotTakeModalProps = Omit & { + onRequestClose?: LazyModalCommonProps['onRequestClose']; onSubmit: (input: AddHotTakeInput) => Promise; existingItem?: HotTake; }; @@ -71,7 +75,7 @@ export function HotTakeModal({ title: data.title.trim(), subtitle: data.subtitle?.trim() || undefined, }); - rest.onRequestClose?.(); + rest.onRequestClose?.(undefined); }); return ( diff --git a/packages/shared/src/features/profile/components/stack/UserStackModal.tsx b/packages/shared/src/features/profile/components/stack/UserStackModal.tsx index d64f904b362..ac2db959584 100644 --- a/packages/shared/src/features/profile/components/stack/UserStackModal.tsx +++ b/packages/shared/src/features/profile/components/stack/UserStackModal.tsx @@ -3,7 +3,10 @@ import React, { useMemo, useState } from 'react'; import { Controller, FormProvider, useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; -import type { ModalProps } from '../../../../components/modals/common/Modal'; +import type { + LazyModalCommonProps, + ModalProps, +} from '../../../../components/modals/common/Modal'; import { Modal } from '../../../../components/modals/common/Modal'; import { TextField } from '../../../../components/fields/TextField'; import { @@ -39,7 +42,8 @@ const userStackFormSchema = z.object({ type UserStackFormData = z.infer; -type UserStackModalProps = Omit & { +type UserStackModalProps = Omit & { + onRequestClose?: LazyModalCommonProps['onRequestClose']; onSubmit: (input: AddUserStackInput) => Promise; existingItem?: UserStack; }; @@ -113,7 +117,7 @@ export function UserStackModal({ section: effectiveSection.trim(), startedAt: startedAtValue, }); - rest.onRequestClose?.(); + rest.onRequestClose?.(undefined); }); const filteredSuggestions = useMemo(() => { diff --git a/packages/shared/src/features/profile/components/workspacePhotos/WorkspacePhotoUploadModal.tsx b/packages/shared/src/features/profile/components/workspacePhotos/WorkspacePhotoUploadModal.tsx index d9183eacb1b..91b7a21f6ba 100644 --- a/packages/shared/src/features/profile/components/workspacePhotos/WorkspacePhotoUploadModal.tsx +++ b/packages/shared/src/features/profile/components/workspacePhotos/WorkspacePhotoUploadModal.tsx @@ -1,7 +1,10 @@ import type { ReactElement, ChangeEvent, DragEvent } from 'react'; import React, { useState, useRef, useCallback } from 'react'; import classNames from 'classnames'; -import type { ModalProps } from '../../../../components/modals/common/Modal'; +import type { + LazyModalCommonProps, + ModalProps, +} from '../../../../components/modals/common/Modal'; import { Modal } from '../../../../components/modals/common/Modal'; import { Button, ButtonVariant } from '../../../../components/buttons/Button'; import { ModalHeader } from '../../../../components/modals/common/ModalHeader'; @@ -20,7 +23,11 @@ import { } from '../../../../graphql/posts'; import type { AddUserWorkspacePhotoInput } from '../../../../graphql/user/userWorkspacePhoto'; -type WorkspacePhotoUploadModalProps = Omit & { +type WorkspacePhotoUploadModalProps = Omit< + ModalProps, + 'children' | 'onRequestClose' +> & { + onRequestClose?: LazyModalCommonProps['onRequestClose']; onSubmit: (input: AddUserWorkspacePhotoInput) => Promise; }; @@ -102,7 +109,7 @@ export function WorkspacePhotoUploadModal({ try { const imageUrl = await uploadContentImage(selectedFile); await onSubmit({ image: imageUrl }); - rest.onRequestClose?.(); + rest.onRequestClose?.(undefined); } catch (err) { setError('Failed to upload image. Please try again.'); } finally { diff --git a/packages/shared/src/hooks/comments/useCommentById.ts b/packages/shared/src/hooks/comments/useCommentById.ts index 188aca4861f..bd919038900 100644 --- a/packages/shared/src/hooks/comments/useCommentById.ts +++ b/packages/shared/src/hooks/comments/useCommentById.ts @@ -30,13 +30,18 @@ const useCommentById = ({ const { user } = useAuthContext(); const { requestMethod } = useRequestProtocol(); const client = useQueryClient(); + + if (!requestMethod) { + throw new Error('Request method is required in useCommentById'); + } + const { data: commentById, isError, isLoading, } = useQuery({ queryKey: generateQueryKey(RequestKey.Comment, user, id), - queryFn: () => requestMethod!(query, { id: `${id}` }), + queryFn: () => requestMethod(query, { id: `${id}` }), ...options, enabled: !!id && options.enabled, }); diff --git a/packages/shared/src/hooks/log/useLogContextData.ts b/packages/shared/src/hooks/log/useLogContextData.ts index 542e621bc9c..ba041364fdd 100644 --- a/packages/shared/src/hooks/log/useLogContextData.ts +++ b/packages/shared/src/hooks/log/useLogContextData.ts @@ -69,9 +69,13 @@ export default function useLogContextData( logEventEnd(id, now = new Date()) { const event = durationEventsQueue.current.get(id); if (event) { + if (!event.event_timestamp) { + throw new Error('Missing event timestamp for duration event'); + } + durationEventsQueue.current.delete(id); event.event_duration = - now.getTime() - event.event_timestamp!.getTime(); + now.getTime() - event.event_timestamp.getTime(); if (window.scrollY > 0 && event.event_name !== 'page inactive') { event.page_state = 'active'; } diff --git a/packages/shared/src/hooks/log/useLogOpportunityNudgeImpression.ts b/packages/shared/src/hooks/log/useLogOpportunityNudgeImpression.ts index e527dfa4fa1..33ae22ef34b 100644 --- a/packages/shared/src/hooks/log/useLogOpportunityNudgeImpression.ts +++ b/packages/shared/src/hooks/log/useLogOpportunityNudgeImpression.ts @@ -23,7 +23,11 @@ export const useLogOpportunityNudgeImpression = ( return; } - logRef.current!({ + if (!logRef.current) { + throw new Error('Log function is required to track opportunity nudges'); + } + + logRef.current({ event_name: LogEvent.ImpressionOpportunityNudge, target_id: targetId, extra: logExtraPayload, diff --git a/packages/shared/src/hooks/onboarding/useGenerateUsername.ts b/packages/shared/src/hooks/onboarding/useGenerateUsername.ts index 3634e80e13a..df259c4032e 100644 --- a/packages/shared/src/hooks/onboarding/useGenerateUsername.ts +++ b/packages/shared/src/hooks/onboarding/useGenerateUsername.ts @@ -17,13 +17,18 @@ export const useGenerateUsername = ( const [username, setUsername] = useState(''); const usernameRef = useRef(false); const { requestMethod } = useRequestProtocol(); + + if (!requestMethod) { + throw new Error('Request method is required in useGenerateUsername'); + } + const usernameQueryKey = ['generateUsername', name]; const { data, isLoading } = useQuery<{ generateUniqueUsername: string; }>({ queryKey: usernameQueryKey, queryFn: () => - requestMethod!( + requestMethod( GET_USERNAME_SUGGESTION, { name }, { requestKey: JSON.stringify(usernameQueryKey) }, diff --git a/packages/shared/src/hooks/referral/useJoinReferral.ts b/packages/shared/src/hooks/referral/useJoinReferral.ts index 038ab88c879..1b45df4de27 100644 --- a/packages/shared/src/hooks/referral/useJoinReferral.ts +++ b/packages/shared/src/hooks/referral/useJoinReferral.ts @@ -33,7 +33,11 @@ export const useJoinReferral = (): void => { id: referringUserId, }); - refetchBoot!(); + if (!refetchBoot) { + throw new Error('Missing refetchBoot after setting join referral'); + } + + refetchBoot(); } catch (error) { if ( (error as ApiErrorResult).response?.errors?.[0]?.message === diff --git a/packages/shared/src/hooks/useConditionalFeature.ts b/packages/shared/src/hooks/useConditionalFeature.ts index 8f745747225..fc8141c14a0 100644 --- a/packages/shared/src/hooks/useConditionalFeature.ts +++ b/packages/shared/src/hooks/useConditionalFeature.ts @@ -23,7 +23,8 @@ export const useConditionalFeature = ({ if (!isPending) { value = growthbook - ? growthbook.getFeatureValue(feature.id, feature.defaultValue)! + ? growthbook.getFeatureValue(feature.id, feature.defaultValue) ?? + (feature.defaultValue as WidenPrimitives) : value; } diff --git a/packages/shared/src/hooks/useFeedLayout.ts b/packages/shared/src/hooks/useFeedLayout.ts index 07da3011be0..ad3c871e9d6 100644 --- a/packages/shared/src/hooks/useFeedLayout.ts +++ b/packages/shared/src/hooks/useFeedLayout.ts @@ -129,7 +129,9 @@ export const useFeedLayout = ({ feedName as UserProfileFeedType, ); - const isFeedIncludedInListLayout = FeedLayoutMobileFeedPages.has(feedName!); + const isFeedIncludedInListLayout = FeedLayoutMobileFeedPages.has( + feedName as OtherFeedPage, + ); const shouldUseListFeedLayoutOnMobileTablet = !isLaptop && isFeedIncludedInListLayout; diff --git a/packages/shared/src/hooks/useIOSError.ts b/packages/shared/src/hooks/useIOSError.ts index dc1f75b6473..8b722c1e0e8 100644 --- a/packages/shared/src/hooks/useIOSError.ts +++ b/packages/shared/src/hooks/useIOSError.ts @@ -27,7 +27,11 @@ export const useIOSError = (): void => { ); return () => { - (globalThis as any)?.eventControllers?.[eventName]?.abort(); + ( + globalThis as typeof globalThis & { + eventControllers?: Partial>; + } + ).eventControllers?.[eventName]?.abort(); }; }, [displayToast]); }; diff --git a/packages/shared/src/hooks/useLanguage.ts b/packages/shared/src/hooks/useLanguage.ts index 3fd615542ba..01557b03788 100644 --- a/packages/shared/src/hooks/useLanguage.ts +++ b/packages/shared/src/hooks/useLanguage.ts @@ -21,8 +21,12 @@ export const useLanguage = (): UseLanguage => { const { mutate: onLanguageChange } = useMutation({ mutationFn: async (value?: string) => { + if (!user) { + throw new Error('Cannot update language without an authenticated user'); + } + await updateUser({ - ...user!, + ...user, language: value, }); diff --git a/packages/shared/src/hooks/usePopupSelector.ts b/packages/shared/src/hooks/usePopupSelector.ts index 0d927c6a8db..62ec2d254aa 100644 --- a/packages/shared/src/hooks/usePopupSelector.ts +++ b/packages/shared/src/hooks/usePopupSelector.ts @@ -3,6 +3,13 @@ import { useQueryClient } from '@tanstack/react-query'; import { generateStorageKey, StorageTopic } from '../lib/storage'; const POPUP_SELECTOR_KEY = [generateStorageKey(StorageTopic.Popup, 'selector')]; +const getDefaultParentSelector = (): HTMLElement => { + if (typeof document === 'undefined') { + throw new Error('Popup parent selector is unavailable on the server'); + } + + return document.body; +}; type AppendFn = () => HTMLElement; @@ -35,7 +42,7 @@ export const usePopupSelector = ({ return { parentSelector: useMemo( - () => (parentSelector ?? propSelector)!, + () => parentSelector ?? propSelector ?? getDefaultParentSelector, [parentSelector, propSelector], ), onAppendTooltipTo: useCallback( From 55fc7ea2592e204a852225d4fc339ad5cc5c9234 Mon Sep 17 00:00:00 2001 From: Chris Bongers Date: Tue, 24 Mar 2026 09:47:09 +0200 Subject: [PATCH 5/7] fix: restore lint build and test checks --- .../ProfileMenu/ProfileSectionItem.tsx | 6 +++++- .../ad/common/getAdFaviconImageLink.spec.ts | 17 ++++++++++++++++- .../components/cards/common/SharedCardCover.tsx | 4 ---- .../cards/common/list/SignalList.spec.tsx | 2 +- .../profile/components/gear/GearModal.spec.tsx | 4 ++-- .../hooks/bookmark/useBookmarkReminderCover.ts | 2 +- packages/shared/src/hooks/feed/useCardCover.tsx | 6 +++++- .../shared/src/hooks/post/usePostActions.ts | 16 +++++++++------- packages/shared/src/hooks/usePrevious.ts | 4 ++-- 9 files changed, 41 insertions(+), 20 deletions(-) diff --git a/packages/shared/src/components/ProfileMenu/ProfileSectionItem.tsx b/packages/shared/src/components/ProfileMenu/ProfileSectionItem.tsx index 3e3877afeb0..77688c441aa 100644 --- a/packages/shared/src/components/ProfileMenu/ProfileSectionItem.tsx +++ b/packages/shared/src/components/ProfileMenu/ProfileSectionItem.tsx @@ -99,5 +99,9 @@ export const ProfileSectionItem = ({ return content; } - return {content}; + return ( + + {content} + + ); }; diff --git a/packages/shared/src/components/cards/ad/common/getAdFaviconImageLink.spec.ts b/packages/shared/src/components/cards/ad/common/getAdFaviconImageLink.spec.ts index 6d0746b810a..3c05e20bcea 100644 --- a/packages/shared/src/components/cards/ad/common/getAdFaviconImageLink.spec.ts +++ b/packages/shared/src/components/cards/ad/common/getAdFaviconImageLink.spec.ts @@ -3,9 +3,19 @@ import { apiUrl } from '../../../../lib/config'; import { adFaviconPlaceholder } from '../../../../lib/image'; import { getAdFaviconImageLink } from './getAdFaviconImageLink'; +const originalWindowDescriptor = Object.getOwnPropertyDescriptor( + globalThis, + 'window', +); + describe('getAdFaviconImageLink', () => { afterEach(() => { - (globalThis as Record).window = undefined; + if (originalWindowDescriptor) { + Object.defineProperty(globalThis, 'window', originalWindowDescriptor); + return; + } + + Reflect.deleteProperty(globalThis, 'window'); }); it('returns companyLogo first when available', () => { @@ -29,6 +39,11 @@ describe('getAdFaviconImageLink', () => { }); it('uses a large fallback favicon size during SSR when window is unavailable', () => { + Object.defineProperty(globalThis, 'window', { + configurable: true, + value: undefined, + }); + expect( getAdFaviconImageLink({ ad: { ...ad, adDomain: 'daily.dev' }, diff --git a/packages/shared/src/components/cards/common/SharedCardCover.tsx b/packages/shared/src/components/cards/common/SharedCardCover.tsx index 1334304c9fb..d93ab342bdd 100644 --- a/packages/shared/src/components/cards/common/SharedCardCover.tsx +++ b/packages/shared/src/components/cards/common/SharedCardCover.tsx @@ -36,10 +36,6 @@ export function SharedCardCover({ renderOverlay, CardImageComponent, }: SharedCardCoverProps): ReactElement { - if (!post) { - throw new Error('SharedCardCover requires a post'); - } - const { overlay } = useCardCover({ post, onShare }); const imageClasses = classNames( imageProps?.className, diff --git a/packages/shared/src/components/cards/common/list/SignalList.spec.tsx b/packages/shared/src/components/cards/common/list/SignalList.spec.tsx index 7660c38cf53..4590d8337ec 100644 --- a/packages/shared/src/components/cards/common/list/SignalList.spec.tsx +++ b/packages/shared/src/components/cards/common/list/SignalList.spec.tsx @@ -5,7 +5,7 @@ import userEvent from '@testing-library/user-event'; import type { NextRouter } from 'next/router'; import { useRouter } from 'next/router'; import { mocked } from 'ts-jest/utils'; -import type { Post, SharedPost } from '../../../../graphql/posts'; +import type { Post } from '../../../../graphql/posts'; import { PostType } from '../../../../graphql/posts'; import { TestBootProvider } from '../../../../../__tests__/helpers/boot'; import { sharePost } from '../../../../../__tests__/fixture/post'; diff --git a/packages/shared/src/features/profile/components/gear/GearModal.spec.tsx b/packages/shared/src/features/profile/components/gear/GearModal.spec.tsx index 17152c1dd12..346c99d364e 100644 --- a/packages/shared/src/features/profile/components/gear/GearModal.spec.tsx +++ b/packages/shared/src/features/profile/components/gear/GearModal.spec.tsx @@ -50,7 +50,7 @@ describe('GearModal', () => { }); expect(onSubmit).toHaveBeenCalledTimes(1); expect(onRequestClose).toHaveBeenCalledTimes(1); - expect(onRequestClose).toHaveBeenCalledWith(null); + expect(onRequestClose).toHaveBeenCalledWith(undefined); }); it('should submit and close when clicking add gear button', async () => { @@ -75,6 +75,6 @@ describe('GearModal', () => { }); expect(onSubmit).toHaveBeenCalledTimes(1); expect(onRequestClose).toHaveBeenCalledTimes(1); - expect(onRequestClose).toHaveBeenCalledWith(null); + expect(onRequestClose).toHaveBeenCalledWith(undefined); }); }); diff --git a/packages/shared/src/hooks/bookmark/useBookmarkReminderCover.ts b/packages/shared/src/hooks/bookmark/useBookmarkReminderCover.ts index 2d44837d509..3707c8b086d 100644 --- a/packages/shared/src/hooks/bookmark/useBookmarkReminderCover.ts +++ b/packages/shared/src/hooks/bookmark/useBookmarkReminderCover.ts @@ -1,7 +1,7 @@ import { useJustBookmarked } from './useJustBookmarked'; import type { Post } from '../../graphql/posts'; -export const useBookmarkReminderCover = (post: Post): boolean => { +export const useBookmarkReminderCover = (post?: Post): boolean => { const { justBookmarked } = useJustBookmarked({ bookmarked: post?.bookmarked ?? false, }); diff --git a/packages/shared/src/hooks/feed/useCardCover.tsx b/packages/shared/src/hooks/feed/useCardCover.tsx index d9362a2b8fb..9139d7dd355 100644 --- a/packages/shared/src/hooks/feed/useCardCover.tsx +++ b/packages/shared/src/hooks/feed/useCardCover.tsx @@ -15,7 +15,7 @@ interface UseCardCover { } interface UseCardCoverProps { - post: Post; + post?: Post; onShare?: (post: Post) => void; className?: { bookmark?: { @@ -33,6 +33,10 @@ export const useCardCover = ({ const shouldShowReminder = useBookmarkReminderCover(post); const overlay = useMemo(() => { + if (!post) { + return undefined; + } + if (interaction === 'copy') { return ( diff --git a/packages/shared/src/hooks/post/usePostActions.ts b/packages/shared/src/hooks/post/usePostActions.ts index abb3586b7f3..3c9f37ff901 100644 --- a/packages/shared/src/hooks/post/usePostActions.ts +++ b/packages/shared/src/hooks/post/usePostActions.ts @@ -15,11 +15,12 @@ type UsePostActions = PostActionData & { onInteract: (interaction: PostActionData['interaction']) => void; }; -export const usePostActions = ({ post }: { post: Post }): UsePostActions => { +export const usePostActions = ({ post }: { post?: Post }): UsePostActions => { const client = useQueryClient(); + const postId = post?.id ?? 'missing-post'; const key = useMemo(() => { - return generateQueryKey(RequestKey.PostActions, { id: post?.id }); - }, [post?.id]); + return generateQueryKey(RequestKey.PostActions, { id: postId }); + }, [postId]); const queryFn = useCallback((): PostActionData => { return { @@ -36,21 +37,22 @@ export const usePostActions = ({ post }: { post: Post }): UsePostActions => { gcTime: Infinity, ...disabledRefetch, }); + const actionData = data ?? queryFn(); const onInteract = useCallback( (interaction: PostActionData['interaction']) => { client.setQueryData(key, { interaction, previousInteraction: - data?.interaction === interaction ? 'none' : data?.interaction, + actionData.interaction === interaction ? 'none' : actionData.interaction, }); }, - [client, key, data.interaction], + [client, key, actionData.interaction], ); return { - interaction: data?.interaction, - previousInteraction: data?.previousInteraction, + interaction: actionData.interaction, + previousInteraction: actionData.previousInteraction, onInteract, }; }; diff --git a/packages/shared/src/hooks/usePrevious.ts b/packages/shared/src/hooks/usePrevious.ts index 90c7b996005..5cdb9e9d9dc 100644 --- a/packages/shared/src/hooks/usePrevious.ts +++ b/packages/shared/src/hooks/usePrevious.ts @@ -1,7 +1,7 @@ import React, { useEffect } from 'react'; -export const usePrevious = (value: T): T => { - const ref = React.useRef(value); +export const usePrevious = (value: T): T | undefined => { + const ref = React.useRef(); useEffect(() => { ref.current = value; }); From 25ebd4b0fc4a78ac486ae45fb341d565480c99b5 Mon Sep 17 00:00:00 2001 From: Chris Bongers Date: Tue, 24 Mar 2026 10:16:37 +0200 Subject: [PATCH 6/7] fix: restore recruiter prerender guards --- .../components/recruiter/ConnectHeader.tsx | 16 ++--------- .../RecruiterPaymentContext.tsx | 14 ++++++++++ .../shared/src/hooks/post/usePostActions.ts | 27 ++++++++++++++----- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/packages/shared/src/components/recruiter/ConnectHeader.tsx b/packages/shared/src/components/recruiter/ConnectHeader.tsx index 045ac124add..930a67ee39c 100644 --- a/packages/shared/src/components/recruiter/ConnectHeader.tsx +++ b/packages/shared/src/components/recruiter/ConnectHeader.tsx @@ -16,8 +16,7 @@ import Link from '../utilities/Link'; import { useOpportunityContext } from '../../features/opportunity/context/OpportunityContext'; import { boostOpportunityLink, opportunityUrl } from '../../lib/constants'; import { anchorDefaultRel } from '../../lib/strings'; -import { recruiterPricesQueryOptions } from '../../features/opportunity/queries'; -import { useAuthContext } from '../../contexts/AuthContext'; +import { recruiterPricesPublicQueryOptions } from '../../features/opportunity/queries'; type ItemProps = { children: ReactNode; @@ -50,18 +49,7 @@ export const ConnectHeader = ({ activeTab, }: ConnectHeaderProps = {}): ReactElement => { const { opportunity } = useOpportunityContext(); - const { user, isLoggedIn } = useAuthContext(); - - if (!user) { - throw new Error('ConnectHeader requires an authenticated user'); - } - - const { data: prices } = useQuery( - recruiterPricesQueryOptions({ - user, - isLoggedIn, - }), - ); + const { data: prices } = useQuery(recruiterPricesPublicQueryOptions()); const opportunityId = opportunity?.id; const forReviewHref = opportunityId 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/hooks/post/usePostActions.ts b/packages/shared/src/hooks/post/usePostActions.ts index 3c9f37ff901..4a9731bee5f 100644 --- a/packages/shared/src/hooks/post/usePostActions.ts +++ b/packages/shared/src/hooks/post/usePostActions.ts @@ -15,18 +15,24 @@ type UsePostActions = PostActionData & { onInteract: (interaction: PostActionData['interaction']) => void; }; +const defaultPostActionData: PostActionData = { + interaction: 'none', + previousInteraction: 'none', +}; + export const usePostActions = ({ post }: { post?: Post }): UsePostActions => { const client = useQueryClient(); - const postId = post?.id ?? 'missing-post'; + const postId = post?.id; const key = useMemo(() => { + if (!postId) { + return [RequestKey.PostActions, 'missing-post']; + } + return generateQueryKey(RequestKey.PostActions, { id: postId }); }, [postId]); const queryFn = useCallback((): PostActionData => { - return { - interaction: 'none', - previousInteraction: 'none', - }; + return defaultPostActionData; }, []); const { data } = useQuery({ @@ -35,19 +41,26 @@ export const usePostActions = ({ post }: { post?: Post }): UsePostActions => { initialData: queryFn, staleTime: Infinity, gcTime: Infinity, + enabled: !!postId, ...disabledRefetch, }); const actionData = data ?? queryFn(); const onInteract = useCallback( (interaction: PostActionData['interaction']) => { + if (!postId) { + return; + } + client.setQueryData(key, { interaction, previousInteraction: - actionData.interaction === interaction ? 'none' : actionData.interaction, + actionData.interaction === interaction + ? 'none' + : actionData.interaction, }); }, - [client, key, actionData.interaction], + [client, key, actionData.interaction, postId], ); return { From bcc9f84f51d5bad1a6f67fabee46d80c51646951 Mon Sep 17 00:00:00 2001 From: Chris Bongers Date: Tue, 24 Mar 2026 10:25:23 +0200 Subject: [PATCH 7/7] fix: soften auth-only component guards --- .../src/components/profile/ExperienceSettings.tsx | 11 +++-------- .../src/components/profile/ProfileButton.tsx | 10 +++++----- .../components/ClearEmploymentAgreementButton.tsx | 13 ++++++------- .../opportunity/components/ClearResumeButton.tsx | 11 ++++++----- .../components/UploadEmploymentAgreementButton.tsx | 14 +++++++------- .../profile/hooks/useUserExperiencesByType.ts | 1 + 6 files changed, 28 insertions(+), 32 deletions(-) diff --git a/packages/shared/src/components/profile/ExperienceSettings.tsx b/packages/shared/src/components/profile/ExperienceSettings.tsx index 2c8dde86c98..ccf3bfd3cfb 100644 --- a/packages/shared/src/components/profile/ExperienceSettings.tsx +++ b/packages/shared/src/components/profile/ExperienceSettings.tsx @@ -17,18 +17,13 @@ export const ExperienceSettings = ({ experienceType, emptyStateMessage, }: ExperienceSettingsProps): ReactElement => { - const { user } = useAuthContext(); - - if (!user) { - throw new Error('ExperienceSettings requires an authenticated user'); - } - + 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/ProfileButton.tsx b/packages/shared/src/components/profile/ProfileButton.tsx index 72a6d9726d6..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); @@ -60,10 +60,6 @@ export default function ProfileButton({ ? animatedReputation : user?.reputation; - if (!user) { - throw new Error('ProfileButton requires an authenticated user'); - } - const preciseBalance = formatCurrency(displayedBalance, { minimumFractionDigits: 0, }); @@ -197,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/features/opportunity/components/ClearEmploymentAgreementButton.tsx b/packages/shared/src/features/opportunity/components/ClearEmploymentAgreementButton.tsx index f72a4929c7c..8ea62c3d0b4 100644 --- a/packages/shared/src/features/opportunity/components/ClearEmploymentAgreementButton.tsx +++ b/packages/shared/src/features/opportunity/components/ClearEmploymentAgreementButton.tsx @@ -23,13 +23,7 @@ export const ClearEmploymentAgreementButton = (): ReactElement => { const { displayToast } = useToastNotification(); const { completeAction } = useActions(); - if (!user) { - throw new Error( - 'ClearEmploymentAgreementButton requires an authenticated user', - ); - } - - const opts = getCandidatePreferencesOptions(user.id); + const opts = getCandidatePreferencesOptions(user?.id ?? ''); const updateQuery = useUpdateQuery(opts); const { mutate: clearFile, isPending: isClearFilePending } = useMutation({ @@ -45,6 +39,11 @@ export const ClearEmploymentAgreementButton = (): ReactElement => { ); }, }); + + if (!user) { + return <>; + } + return (