Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo, useState, useEffect, useCallback } from 'react';
import { useMemo, useState, useEffect, useCallback, useRef } from 'react';
import { useSearchParams, useRouter, usePathname } from 'next/navigation';
import { useAtom } from 'jotai/react';
import { UNDO } from 'jotai-history';
Expand All @@ -10,12 +10,12 @@ import {
getFunnelStepByPosition,
funnelPositionHistoryAtom,
} from '../store/funnelStore';
import type { FunnelSession } from '../types/funnelBoot';
import { useToggle } from '../../../hooks/useToggle';

interface UseFunnelNavigationProps {
funnel: FunnelJSON;
initialStepId?: string | null;
onNavigation: TrackOnNavigate;
session: FunnelSession;
}

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

export interface UseFunnelNavigationReturn {
chapters: Chapters;
isReady: boolean;
navigate: NavigateFunction;
position: FunnelPosition;
step: FunnelStep;
Expand Down Expand Up @@ -74,8 +75,8 @@ function updateURLWithStepId({

export const useFunnelNavigation = ({
funnel,
initialStepId,
onNavigation,
session,
}: UseFunnelNavigationProps): UseFunnelNavigationReturn => {
const router = useRouter();
const searchParams = useSearchParams();
Expand All @@ -84,6 +85,9 @@ export const useFunnelNavigation = ({
const [position, setPosition] = useAtom(funnelPositionAtom);
const [history, dispatchHistory] = useAtom(funnelPositionHistoryAtom);
const isFirstStep = !position.step && !position.chapter;
const isInitialized = useRef<boolean>(false);
const [isReady, setIsReady] = useToggle(false);
const urlStepId = searchParams.get('stepId');

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

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

const setPositionById = useCallback(
(stepId: FunnelStep['id']) => {
const newPosition = stepMap[stepId]?.position;
if (newPosition) {
setPosition(newPosition);
}
},
[setPosition, stepMap],
);

const step: FunnelStep = useMemo(
() => getFunnelStepByPosition(funnel, position),
[funnel, position],
Expand All @@ -111,8 +125,7 @@ export const useFunnelNavigation = ({
}

// update the position in the store
const newPosition = stepMap[to]?.position;
setPosition(newPosition);
setPositionById(to);

// track the navigation event
onNavigation({ from, to, timeDuration, type });
Expand All @@ -128,7 +141,7 @@ export const useFunnelNavigation = ({
pathname,
router,
searchParams,
setPosition,
setPositionById,
step,
stepMap,
stepTimerStart,
Expand Down Expand Up @@ -160,22 +173,43 @@ export const useFunnelNavigation = ({
[isFirstStep, step?.transitions],
);

useEffect(
() => {
// Check if the URL has a stepId parameter or if there is a session
const stepId = searchParams.get('stepId') ?? session.currentStep;
// On load: Update the initial position in state and URL
useEffect(() => {
if (isInitialized.current) {
return;
}

if (!stepId) {
return;
}
if (initialStepId) {
setPositionById(initialStepId);
}

const newPosition = stepMap[stepId]?.position;
setPosition(newPosition);
},
// only run on mount
// eslint-disable-next-line react-hooks/exhaustive-deps
[funnel.id],
);
updateURLWithStepId({
router,
pathname,
searchParams,
stepId: initialStepId || step.id,
});

isInitialized.current = true;
setIsReady(true);
}, [
initialStepId,
pathname,
router,
searchParams,
setPositionById,
step.id,
setIsReady,
]);

// After load: update the position when the URL's stepId changes
useEffect(() => {
if (!urlStepId || !isInitialized.current) {
return;
}

setPositionById(urlStepId);
}, [setPositionById, urlStepId]);

return {
back,
Expand All @@ -184,5 +218,6 @@ export const useFunnelNavigation = ({
position,
skip,
step,
isReady,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ describe('FunnelStepper component', () => {
position: { chapter: 0, step: 0 },
chapters: [{ steps: 1 }],
step: mockStep,
isReady: true,
});

(useFunnelTracking as jest.Mock).mockReturnValue({
Expand Down Expand Up @@ -273,6 +274,7 @@ describe('FunnelStepper component', () => {
position: { chapter: 0, step: 0 },
chapters: [{ steps: 2 }],
step: quizStep,
isReady: true,
});

renderComponent(mockFunnelWithMultipleSteps);
Expand Down
16 changes: 13 additions & 3 deletions packages/shared/src/features/onboarding/shared/FunnelStepper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ import classed from '../../../lib/classed';

export interface FunnelStepperProps {
funnel: FunnelJSON;
initialStepId?: string | null;
onComplete?: () => void;
session: FunnelSession;
showCookieBanner?: boolean;
onComplete?: () => void;
}

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

export const FunnelStepper = ({
funnel,
initialStepId,
session,
showCookieBanner,
onComplete,
Expand All @@ -90,8 +92,12 @@ export const FunnelStepper = ({
trackOnComplete,
trackFunnelEvent,
} = useFunnelTracking({ funnel, session });
const { back, chapters, navigate, position, skip, step } =
useFunnelNavigation({ funnel, onNavigation: trackOnNavigate, session });
const { back, chapters, navigate, position, skip, step, isReady } =
useFunnelNavigation({
funnel,
initialStepId,
onNavigation: trackOnNavigate,
});
const { transition: sendTransition } = useStepTransition(session.id);
const { showBanner, ...cookieConsentProps } = useFunnelCookies({
defaultOpen: showCookieBanner,
Expand Down Expand Up @@ -128,6 +134,10 @@ export const FunnelStepper = ({
}
};

if (!isReady) {
return null;
}

return (
<section
data-testid="funnel-stepper"
Expand Down
9 changes: 9 additions & 0 deletions packages/webapp/pages/helloworld/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Toast from '@dailydotdev/shared/src/components/notifications/Toast';

type PageProps = {
dehydratedState: DehydratedState;
initialStepId: string | null;
showCookieBanner?: boolean;
};

Expand Down Expand Up @@ -74,17 +75,24 @@ export const getServerSideProps: GetServerSideProps<PageProps> = async ({
// Check if the user already accepted cookies
const hasAcceptedCookies = allCookies.includes(GdprConsentKey.Marketing);

// Determine the initial step ID
const queryStepId = query?.stepId as string | undefined;
const initialStepId: string | null =
queryStepId ?? boot.data?.funnelState?.session?.currentStep;

// Return props including the dehydrated state
return {
props: {
dehydratedState: dehydrate(queryClient),
showCookieBanner: !hasAcceptedCookies,
initialStepId,
},
};
};

export default function HelloWorldPage({
dehydratedState,
initialStepId,
showCookieBanner,
}: PageProps): ReactElement {
const { data: funnelBoot } = useFunnelBoot();
Expand Down Expand Up @@ -112,6 +120,7 @@ export default function HelloWorldPage({
{!!funnel && !!session.id && (
<FunnelStepper
funnel={funnel}
initialStepId={initialStepId}
session={session}
showCookieBanner={showCookieBanner}
onComplete={() => router.replace('/onboarding')}
Expand Down