Skip to content

Commit 33ebee5

Browse files
authored
Merge branch 'main' into fix-facebook-redirect-fail
2 parents 4fce8d9 + be82551 commit 33ebee5

14 files changed

Lines changed: 214 additions & 47 deletions

File tree

packages/eslint-config/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ module.exports = {
128128
},
129129
],
130130
},
131-
]
131+
],
132+
'@typescript-eslint/explicit-module-boundary-types': "off",
132133
},
133134
};

packages/extension/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "extension",
3-
"version": "3.36.23",
3+
"version": "3.36.24",
44
"scripts": {
55
"dev:chrome": "cross-env NODE_ENV=development cross-env TARGET_BROWSER=chrome webpack --watch",
66
"dev:opera": "cross-env NODE_ENV=development cross-env TARGET_BROWSER=opera webpack --watch",

packages/shared/src/components/ProfileMenu/sections/AccountSection.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,19 @@ import type { ReactElement } from 'react';
44
import { ProfileSection } from '../ProfileSection';
55
import { CreditCardIcon, InviteIcon, SettingsIcon } from '../../icons';
66
import { webappUrl } from '../../../lib/constants';
7+
import { useLazyModal } from '../../../hooks/useLazyModal';
8+
import { LazyModal } from '../../modals/common/types';
79

810
export const AccountSection = (): ReactElement => {
11+
const { openModal } = useLazyModal();
12+
913
return (
1014
<ProfileSection
1115
items={[
1216
{
1317
title: 'Settings',
14-
href: `${webappUrl}account/profile`,
18+
// href: `${webappUrl}account/profile`,
19+
onClick: () => openModal({ type: LazyModal.UserSettings }),
1520
icon: <SettingsIcon />,
1621
},
1722
{

packages/shared/src/components/comments/CommentContainer.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import { VerifiedCompanyUserBadge } from '../VerifiedCompanyUserBadge';
2121
import { Separator } from '../cards/common/common';
2222
import { PlusUserBadge } from '../PlusUserBadge';
2323
import { ProfileImageSize } from '../ProfilePicture';
24-
import { Image } from '../image/Image';
2524
import { useHasAccessToCores } from '../../hooks/useCoresFeature';
25+
import { Image } from '../image/Image';
2626

2727
interface ClassName extends CommentClassName {
2828
content?: string;
@@ -124,7 +124,7 @@ export default function CommentContainer({
124124
{!!comment.author?.isPlus && (
125125
<PlusUserBadge user={comment.author} />
126126
)}
127-
<div className="flex items-center">
127+
<div className="flex min-w-0 shrink items-center">
128128
<ProfileLink href={comment.author.permalink}>
129129
<TruncateText
130130
className="text-text-tertiary typo-footnote"
@@ -133,6 +133,8 @@ export default function CommentContainer({
133133
@{comment.author.username}
134134
</TruncateText>
135135
</ProfileLink>
136+
</div>
137+
<div>
136138
<Separator className="!mx-0.5" />
137139
<CommentPublishDate comment={comment} />
138140
</div>
@@ -149,6 +151,15 @@ export default function CommentContainer({
149151
{comment.author.id === postScoutId && <UserBadge>Scout</UserBadge>}
150152
</FlexRow>
151153
</div>
154+
{hasAccessToCores && !!comment.award && (
155+
<div className="ml-2 flex size-7 items-center justify-center rounded-10 bg-surface-float">
156+
<Image
157+
src={comment.award.image}
158+
alt={comment.award.name}
159+
className="size-5 object-contain"
160+
/>
161+
</div>
162+
)}
152163
</header>
153164
<div
154165
className={classNames(
@@ -163,15 +174,6 @@ export default function CommentContainer({
163174
/>
164175
{actions}
165176
</div>
166-
{hasAccessToCores && !!comment.award && (
167-
<div className="absolute right-3 top-3 flex size-7 items-center justify-center rounded-10 bg-surface-float">
168-
<Image
169-
src={comment.award.image}
170-
alt={comment.award.name}
171-
className="size-8"
172-
/>
173-
</div>
174-
)}
175177
</article>
176178
);
177179
}

packages/shared/src/components/fields/form/FormWrapper.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export function FormWrapper({
4141
const titleElement = (
4242
<PageHeaderTitle
4343
className={classNames(
44+
'mx-4',
4445
!isHeaderTitle && 'mt-5',
4546
className?.title ?? 'typo-body',
4647
)}

packages/shared/src/components/modals/common/ModalSidebar.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,12 @@ export function ModalSidebar({
6161
className,
6262
}: ModalSidebarProps): ReactElement {
6363
return (
64-
<div className={classNames('flex w-full flex-1 flex-row', className)}>
64+
<div
65+
className={classNames(
66+
'flex w-full flex-1 flex-row overflow-y-auto',
67+
className,
68+
)}
69+
>
6570
{children}
6671
</div>
6772
);

packages/shared/src/components/profile/ProfileButton.spec.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import { QueryClient } from '@tanstack/react-query';
33
import type { RenderResult } from '@testing-library/react';
4-
import { waitFor, render, screen } from '@testing-library/react';
4+
import { waitFor, render, screen, act } from '@testing-library/react';
55
import ProfileButton from './ProfileButton';
66
import defaultUser from '../../../__tests__/fixture/loggedUser';
77
import { TestBootProvider } from '../../../__tests__/helpers/boot';
@@ -32,21 +32,27 @@ const renderComponent = (): RenderResult => {
3232
);
3333
};
3434

35-
it('account details should link to account profile page', async () => {
35+
it('should show settings option that opens modal', async () => {
3636
renderComponent();
3737

3838
const profileBtn = await screen.findByLabelText('Profile settings');
39-
profileBtn.click();
40-
41-
const accountLink = await screen.findByRole('link', { name: 'Settings' });
42-
expect(accountLink).toHaveAttribute('href', '/account/profile');
39+
await act(async () => {
40+
profileBtn.click();
41+
});
42+
43+
const settingsButton = await screen.findByRole('button', {
44+
name: 'Settings',
45+
});
46+
expect(settingsButton).toBeInTheDocument();
4347
});
4448

4549
it('should click the logout button and logout', async () => {
4650
renderComponent();
4751

4852
const profileBtn = await screen.findByLabelText('Profile settings');
49-
profileBtn.click();
53+
await act(async () => {
54+
profileBtn.click();
55+
});
5056

5157
const logoutBtn = await screen.findByText('Logout');
5258
logoutBtn.click();

packages/shared/src/features/onboarding/hooks/useFunnelNavigation.ts

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useMemo, useState, useEffect, useCallback } from 'react';
1+
import { useMemo, useState, useEffect, useCallback, useRef } from 'react';
22
import { useSearchParams, useRouter, usePathname } from 'next/navigation';
33
import { useAtom } from 'jotai/react';
44
import { UNDO } from 'jotai-history';
@@ -10,12 +10,12 @@ import {
1010
getFunnelStepByPosition,
1111
funnelPositionHistoryAtom,
1212
} from '../store/funnelStore';
13-
import type { FunnelSession } from '../types/funnelBoot';
13+
import { useToggle } from '../../../hooks/useToggle';
1414

1515
interface UseFunnelNavigationProps {
1616
funnel: FunnelJSON;
17+
initialStepId?: string | null;
1718
onNavigation: TrackOnNavigate;
18-
session: FunnelSession;
1919
}
2020

2121
type Chapters = Array<{ steps: number }>;
@@ -32,6 +32,7 @@ interface HeaderNavigation {
3232

3333
export interface UseFunnelNavigationReturn {
3434
chapters: Chapters;
35+
isReady: boolean;
3536
navigate: NavigateFunction;
3637
position: FunnelPosition;
3738
step: FunnelStep;
@@ -74,8 +75,8 @@ function updateURLWithStepId({
7475

7576
export const useFunnelNavigation = ({
7677
funnel,
78+
initialStepId,
7779
onNavigation,
78-
session,
7980
}: UseFunnelNavigationProps): UseFunnelNavigationReturn => {
8081
const router = useRouter();
8182
const searchParams = useSearchParams();
@@ -84,6 +85,9 @@ export const useFunnelNavigation = ({
8485
const [position, setPosition] = useAtom(funnelPositionAtom);
8586
const [history, dispatchHistory] = useAtom(funnelPositionHistoryAtom);
8687
const isFirstStep = !position.step && !position.chapter;
88+
const isInitialized = useRef<boolean>(false);
89+
const [isReady, setIsReady] = useToggle(false);
90+
const urlStepId = searchParams.get('stepId');
8791

8892
const chapters: Chapters = useMemo(
8993
() => funnel.chapters.map((chapter) => ({ steps: chapter.steps.length })),
@@ -92,6 +96,16 @@ export const useFunnelNavigation = ({
9296

9397
const stepMap: StepMap = useMemo(() => getStepMap(funnel), [funnel]);
9498

99+
const setPositionById = useCallback(
100+
(stepId: FunnelStep['id']) => {
101+
const newPosition = stepMap[stepId]?.position;
102+
if (newPosition) {
103+
setPosition(newPosition);
104+
}
105+
},
106+
[setPosition, stepMap],
107+
);
108+
95109
const step: FunnelStep = useMemo(
96110
() => getFunnelStepByPosition(funnel, position),
97111
[funnel, position],
@@ -111,8 +125,7 @@ export const useFunnelNavigation = ({
111125
}
112126

113127
// update the position in the store
114-
const newPosition = stepMap[to]?.position;
115-
setPosition(newPosition);
128+
setPositionById(to);
116129

117130
// track the navigation event
118131
onNavigation({ from, to, timeDuration, type });
@@ -128,7 +141,7 @@ export const useFunnelNavigation = ({
128141
pathname,
129142
router,
130143
searchParams,
131-
setPosition,
144+
setPositionById,
132145
step,
133146
stepMap,
134147
stepTimerStart,
@@ -160,22 +173,43 @@ export const useFunnelNavigation = ({
160173
[isFirstStep, step?.transitions],
161174
);
162175

163-
useEffect(
164-
() => {
165-
// Check if the URL has a stepId parameter or if there is a session
166-
const stepId = searchParams.get('stepId') ?? session.currentStep;
176+
// On load: Update the initial position in state and URL
177+
useEffect(() => {
178+
if (isInitialized.current) {
179+
return;
180+
}
167181

168-
if (!stepId) {
169-
return;
170-
}
182+
if (initialStepId) {
183+
setPositionById(initialStepId);
184+
}
171185

172-
const newPosition = stepMap[stepId]?.position;
173-
setPosition(newPosition);
174-
},
175-
// only run on mount
176-
// eslint-disable-next-line react-hooks/exhaustive-deps
177-
[funnel.id],
178-
);
186+
updateURLWithStepId({
187+
router,
188+
pathname,
189+
searchParams,
190+
stepId: initialStepId || step.id,
191+
});
192+
193+
isInitialized.current = true;
194+
setIsReady(true);
195+
}, [
196+
initialStepId,
197+
pathname,
198+
router,
199+
searchParams,
200+
setPositionById,
201+
step.id,
202+
setIsReady,
203+
]);
204+
205+
// After load: update the position when the URL's stepId changes
206+
useEffect(() => {
207+
if (!urlStepId || !isInitialized.current) {
208+
return;
209+
}
210+
211+
setPositionById(urlStepId);
212+
}, [setPositionById, urlStepId]);
179213

180214
return {
181215
back,
@@ -184,5 +218,6 @@ export const useFunnelNavigation = ({
184218
position,
185219
skip,
186220
step,
221+
isReady,
187222
};
188223
};

packages/shared/src/features/onboarding/shared/FunnelStepper.spec.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ describe('FunnelStepper component', () => {
8686
position: { chapter: 0, step: 0 },
8787
chapters: [{ steps: 1 }],
8888
step: mockStep,
89+
isReady: true,
8990
});
9091

9192
(useFunnelTracking as jest.Mock).mockReturnValue({
@@ -273,6 +274,7 @@ describe('FunnelStepper component', () => {
273274
position: { chapter: 0, step: 0 },
274275
chapters: [{ steps: 2 }],
275276
step: quizStep,
277+
isReady: true,
276278
});
277279

278280
renderComponent(mockFunnelWithMultipleSteps);

packages/shared/src/features/onboarding/shared/FunnelStepper.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ import classed from '../../../lib/classed';
3838

3939
export interface FunnelStepperProps {
4040
funnel: FunnelJSON;
41+
initialStepId?: string | null;
42+
onComplete?: () => void;
4143
session: FunnelSession;
4244
showCookieBanner?: boolean;
43-
onComplete?: () => void;
4445
}
4546

4647
const stepComponentMap = {
@@ -78,6 +79,7 @@ const HiddenStep = classed('div', 'hidden');
7879

7980
export const FunnelStepper = ({
8081
funnel,
82+
initialStepId,
8183
session,
8284
showCookieBanner,
8385
onComplete,
@@ -90,8 +92,12 @@ export const FunnelStepper = ({
9092
trackOnComplete,
9193
trackFunnelEvent,
9294
} = useFunnelTracking({ funnel, session });
93-
const { back, chapters, navigate, position, skip, step } =
94-
useFunnelNavigation({ funnel, onNavigation: trackOnNavigate, session });
95+
const { back, chapters, navigate, position, skip, step, isReady } =
96+
useFunnelNavigation({
97+
funnel,
98+
initialStepId,
99+
onNavigation: trackOnNavigate,
100+
});
95101
const { transition: sendTransition } = useStepTransition(session.id);
96102
const { showBanner, ...cookieConsentProps } = useFunnelCookies({
97103
defaultOpen: showCookieBanner,
@@ -128,6 +134,10 @@ export const FunnelStepper = ({
128134
}
129135
};
130136

137+
if (!isReady) {
138+
return null;
139+
}
140+
131141
return (
132142
<section
133143
data-testid="funnel-stepper"

0 commit comments

Comments
 (0)