Skip to content

feat(funnels): Informative Screen and StepHeadline component #4321

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 94 commits into from
Apr 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
bb7d759
feat: poc app folder
ilasw Mar 18, 2025
9f62e27
Merge branch 'main' of https://github.com/dailydotdev/apps into feat-…
ilasw Mar 18, 2025
30bc2c1
Merge branch 'main' of https://github.com/dailydotdev/apps into feat-…
ilasw Mar 21, 2025
1072de0
feat: add app folder boot fetch and jotai query
ilasw Mar 26, 2025
138e866
feat: make get current user working on both ssr and client
ilasw Mar 26, 2025
1110abc
feat: add funnel basic types
ilasw Mar 27, 2025
c337435
feat: add funnel id/version support and sub folder
ilasw Mar 27, 2025
647c00c
refactor: clean log and remove tsx extension
ilasw Mar 27, 2025
70ccbcc
fix: build error
ilasw Mar 27, 2025
11fbe24
Merge branch 'main' of https://github.com/dailydotdev/apps into feat-…
ilasw Mar 27, 2025
7b20e1f
fix: build errors and linter
ilasw Mar 27, 2025
8b10637
feat: add client hook for testing ssr hydration
ilasw Mar 31, 2025
a2bef0e
fix: client not aligned with server
ilasw Apr 1, 2025
2fb8e7c
Merge branch 'main' into feat-app-directory-helloworld
ilasw Apr 1, 2025
9402d34
feat: update auth actions
ilasw Apr 1, 2025
1a27388
fix: mock next/navigation for testing
ilasw Apr 1, 2025
aced153
Merge branch 'main' into feat-app-directory-helloworld
ilasw Apr 1, 2025
e3dd2dc
fix: mock next/navigation for testing
ilasw Apr 1, 2025
b36b0eb
refactor: moved useAppAuth hook
ilasw Apr 1, 2025
da69daf
refactor: remove fragment
ilasw Apr 1, 2025
1e43ab9
refactor: remove unnecessary 'use client' directive from useRefreshToken
ilasw Apr 1, 2025
27bbb3f
Merge remote-tracking branch 'origin/feat-app-directory-helloworld' i…
ilasw Apr 1, 2025
389405a
fix: lint prettier
ilasw Apr 1, 2025
c279170
feat: update context providers and refresh sub
ilasw Apr 1, 2025
24c5e6e
feat: add return type
ilasw Apr 1, 2025
12675f4
feat: Informative Screen + StepHeadline component
AmarTrebinjac Apr 2, 2025
cef7176
feat: add cta to button
AmarTrebinjac Apr 2, 2025
c1d4fa2
feat: add FormInputRating component
ilasw Apr 2, 2025
22258c6
feat: add FormInputRating component and update button elements
ilasw Apr 2, 2025
ca3635d
feat: update stepheadline to differentiate between quiz and other types
AmarTrebinjac Apr 2, 2025
8c94256
fix: update boot data fetching method to use fetchQuery
ilasw Apr 2, 2025
d28d6eb
refactor: remove unused dependencies and improve boot data handling
ilasw Apr 2, 2025
3a20140
fix: update useAppAuth to utilize initial data in boot data query
ilasw Apr 2, 2025
23e435b
refactor: update FunnelStepType enum values to camelCase
ilasw Apr 2, 2025
c57893e
Merge branch 'feat-app-directory-helloworld' of https://github.com/da…
ilasw Apr 2, 2025
f287961
feat: add FormInputRating story and tests
ilasw Apr 2, 2025
27bcb54
refactor: improve rating validation and default value handling in For…
ilasw Apr 2, 2025
1e5e0b7
feat: add FormInputRating component with labels
ilasw Apr 2, 2025
28e4af2
refactor: add meta to app router layout
ilasw Apr 2, 2025
8c5f268
fix: remove invalid defaultValue error test
ilasw Apr 2, 2025
78d222f
fix: make onValueChange optional
ilasw Apr 2, 2025
6fa0937
fix: gradient not occupying full screen on iOS
AmarTrebinjac Apr 2, 2025
4156870
fix: change to min-h-dvh instead of min-h-screen for iOS compat
AmarTrebinjac Apr 2, 2025
3f32738
Merge branch 'feat-app-directory-helloworld' into MI-847
AmarTrebinjac Apr 2, 2025
b62ab93
chore: remove boot var for now
AmarTrebinjac Apr 2, 2025
2715642
feat: add tests and storybook
AmarTrebinjac Apr 2, 2025
6ce13ad
feat: add FormInputCheckboxGroup component and stories
ilasw Apr 2, 2025
4a8d672
feat: implement Checkbox variant for Button and add FormInputCheckbox…
ilasw Apr 2, 2025
20991ce
feat: add typo class for checkboxes
ilasw Apr 2, 2025
70607b4
feat: rename Checkbox variant to Quiz and update related styles
ilasw Apr 3, 2025
9691fdb
feat: refactor FormInputCheckboxGroup to use FormInputCheckbox component
ilasw Apr 3, 2025
9c2e4f8
feat: update FormInputRating to use Quiz variant for buttons
ilasw Apr 3, 2025
c575ee9
feat: add autodocs tags to Button stories
ilasw Apr 3, 2025
c1557eb
feat: add role attribute to FormInputCheckboxGroup for accessibility …
ilasw Apr 3, 2025
ae3b368
feat: update FormInputRating to accept custom options and refactor re…
ilasw Apr 3, 2025
beaf613
feat: add FunnelQuiz component and update funnel types for quiz quest…
ilasw Apr 3, 2025
0241984
Merge branch 'feat-web-funnel' of https://github.com/dailydotdev/apps…
ilasw Apr 3, 2025
edc9739
Merge branch 'feat-web-funnel' of https://github.com/dailydotdev/apps…
ilasw Apr 3, 2025
b5c1134
fix: merge errors
ilasw Apr 3, 2025
bf136ce
fix: merge errors
ilasw Apr 3, 2025
e194b25
Merge branch 'feat-web-funnel' of https://github.com/dailydotdev/apps…
ilasw Apr 3, 2025
0f395fe
fix: merge errors
ilasw Apr 3, 2025
704abe6
fix: merge errors
ilasw Apr 3, 2025
3f83e07
feat: better alignment and explainer text handling
ilasw Apr 3, 2025
e2dee53
fix: lint prettier
ilasw Apr 3, 2025
38b5282
feat(funnel): add transition callback to FunnelQuiz
ilasw Apr 3, 2025
aa4d5ce
feat: refactor CTA handling in FunnelQuiz and add FunnelStepCtaWrappe…
ilasw Apr 3, 2025
0a74bae
feat: update button color classes for Quiz variant
ilasw Apr 3, 2025
ba17cde
feat: remove form components from ClientTest
ilasw Apr 3, 2025
d6d28f6
refactor: update names
ilasw Apr 3, 2025
e07a955
test: update FormInputRating tests following new logic
ilasw Apr 3, 2025
b2f921f
refactor: rename 'headline' and 'explainer' to 'heading' and 'descrip…
ilasw Apr 3, 2025
8118df4
feat: custom CTA labels for wrapper
ilasw Apr 4, 2025
db2fb1a
refactor: removed span
ilasw Apr 4, 2025
2d552b0
refactor: infer value from type
ilasw Apr 4, 2025
69d4fb8
refactor: update component titles and add tags for autodocs
ilasw Apr 4, 2025
2718058
refactor: rename InformativeScreen to FunnelInformative and updated t…
ilasw Apr 4, 2025
97604cc
refactor: rename InformativeScreen to FunnelInformative and updated t…
ilasw Apr 4, 2025
5241fc9
feat: wip
ilasw Apr 4, 2025
ab9cdbd
Merge branch 'MI-849-funnel-form-inputs' of https://github.com/dailyd…
ilasw Apr 4, 2025
7c34208
refactor: add cta wrapper and transition type
ilasw Apr 4, 2025
f1f9fbe
Merge branch 'feat-web-funnel' of https://github.com/dailydotdev/apps…
ilasw Apr 4, 2025
0262bf0
feat: update InformativeScreen story
ilasw Apr 4, 2025
b49af1a
feat: add background variants and update styles
ilasw Apr 4, 2025
fe566c7
feat: add background and StepHeadline component to FunnelQuiz
ilasw Apr 4, 2025
5cceaa5
refactor: removed gradient from tailwind.config.ts
ilasw Apr 4, 2025
1579a84
feat: revert linter spacing
ilasw Apr 4, 2025
92a1918
fix: use theme variables
ilasw Apr 4, 2025
7a0013e
fix: default bottom gradient
ilasw Apr 4, 2025
ee21dc9
fix: add dummy components in Storybook shared folder
ilasw Apr 4, 2025
c4cedd0
feat: Add containerClassname to FunnelStepCtaWrapper
AmarTrebinjac Apr 7, 2025
1a321a2
chore: remove unused color
AmarTrebinjac Apr 7, 2025
2a73d25
refactor: add lazyimage instead of image
AmarTrebinjac Apr 7, 2025
cffb2c8
Merge branch 'feat-web-funnel' into MI-847
AmarTrebinjac Apr 7, 2025
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,22 +1,25 @@
import type { ReactElement } from 'react';
import React from 'react';
import classNames from 'classnames';
import type { ButtonProps } from '../../../components/buttons/Button';
import { Button, ButtonVariant } from '../../../components/buttons/Button';

export type FunnelStepCtaWrapperProps = ButtonProps<'button'> & {
cta?: {
label?: string;
};
containerClassName?: string;
};

export function FunnelStepCtaWrapper({
children,
cta,
containerClassName,
...props
}: FunnelStepCtaWrapperProps): ReactElement {
return (
<div className="relative flex min-h-dvh flex-col gap-4">
<div className="flex-1">{children}</div>
<div className={classNames('flex-1', containerClassName)}>{children}</div>
<div className="sticky bottom-2 m-4">
<Button
type="button"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import StepHeadline, { StepHeadlineAlign } from './StepHeadline';
import { TypographyType } from '../../../components/typography/Typography';

const defaultProps = {
heading: 'Test Headline',
description: 'Test description text',
};

const renderComponent = (props = {}) => {
return render(<StepHeadline {...defaultProps} {...props} />);
};

describe('StepHeadline', () => {
it('should render headline and description text', () => {
renderComponent();
expect(screen.getByText('Test Headline')).toBeInTheDocument();
expect(screen.getByText('Test description text')).toBeInTheDocument();
});

it('should have text-center class when align is center', () => {
renderComponent({ align: StepHeadlineAlign.Center });
const container = screen.getByTestId('step-headline-container');
expect(container).toHaveClass('text-center');
});

it('should have text-left class when align is left', () => {
renderComponent({ align: StepHeadlineAlign.Left });
const container = screen.getByTestId('step-headline-container');
expect(container).toHaveClass('text-left');
});

it('should have text-right class when align is right', () => {
renderComponent({ align: StepHeadlineAlign.Right });
const container = screen.getByTestId('step-headline-container');
expect(container).toHaveClass('text-right');
});

it('should apply custom props to description text', () => {
renderComponent({
description: 'Test description text',
descriptionProps: { type: TypographyType.Subhead },
});
const description = screen.getByTestId('step-headline-description');
expect(description).toBeInTheDocument();
expect(description).toHaveClass('typo-subhead');
});

it('should not render description when not provided', () => {
renderComponent({ description: undefined });
expect(
screen.queryByTestId('step-headline-description'),
).not.toBeInTheDocument();
});
});
61 changes: 61 additions & 0 deletions packages/shared/src/features/onboarding/shared/StepHeadline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { ReactElement } from 'react';
import React from 'react';
import classNames from 'classnames';
import {
Typography,
TypographyType,
TypographyTag,
TypographyColor,
} from '../../../components/typography/Typography';
import type { TypographyProps } from '../../../components/typography/Typography';

export enum StepHeadlineAlign {
Left = 'left',
Center = 'center',
Right = 'right',
}

export type StepHeadlineProps = {
align?: StepHeadlineAlign;
description?: string;
descriptionProps?: TypographyProps<TypographyTag.P>;
heading: string;
};

const StepHeadline = ({
align = StepHeadlineAlign.Center,
description,
descriptionProps,
heading,
}: StepHeadlineProps): ReactElement => {
return (
<div
data-testid="step-headline-container"
className={classNames('flex flex-col gap-2', {
'text-left': align === StepHeadlineAlign.Left,
'text-center': align === StepHeadlineAlign.Center,
'text-right': align === StepHeadlineAlign.Right,
})}
>
<Typography
bold
color={TypographyColor.Primary}
tag={TypographyTag.H2}
type={TypographyType.Title1}
>
{heading}
</Typography>
{!!description?.length && (
<Typography
data-testid="step-headline-description"
color={TypographyColor.Primary}
type={TypographyType.Body}
{...descriptionProps}
>
{description}
</Typography>
)}
</div>
);
};
export default StepHeadline;
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import FunnelInformative from './FunnelInformative';
import { FunnelStepType } from '../types/funnel';
import type { FunnelInformativeProps } from './FunnelInformative';

const mockOnTransition = jest.fn();

const defaultProps: FunnelInformativeProps = {
id: 'test-id',
type: FunnelStepType.Fact,
transitions: [],
parameters: {
headline: 'Test Headline',
explainer: 'Test explanation text',
align: 'center',
cta: 'Continue',
},
onTransition: mockOnTransition,
};

const renderComponent = (props = {}) => {
return render(<FunnelInformative {...defaultProps} {...props} />);
};

describe('FunnelInformative', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('should render headline and explainer text', async () => {
renderComponent();
expect(await screen.findByText('Test Headline')).toBeInTheDocument();
expect(
await screen.findByText('Test explanation text'),
).toBeInTheDocument();
});

it('should call onTransition when button is clicked', async () => {
renderComponent();
const button = await screen.findByText('Continue');
fireEvent.click(button);
expect(mockOnTransition).toHaveBeenCalledTimes(1);
});

it('should use "Next" as default CTA text when not provided', async () => {
renderComponent({
parameters: {
...defaultProps.parameters,
cta: undefined,
},
});
expect(await screen.findByText('Next')).toBeInTheDocument();
});

it('should render with reverse layout when specified', async () => {
renderComponent({
parameters: {
...defaultProps.parameters,
reverse: 'true',
},
});

const content = screen.getByTestId('informative-content');
expect(content).toHaveClass('flex-col-reverse');
});

it('should render an image when visualUrl is provided', async () => {
const visualUrl = 'https://example.com/image.png';
renderComponent({
parameters: {
...defaultProps.parameters,
visualUrl,
},
});

const image = screen.getByAltText(
'Supportive illustration for the information',
);
expect(image).toBeInTheDocument();
expect(image).toHaveAttribute('src', visualUrl);
});

it('should pass left alignment to StepHeadline when specified', async () => {
renderComponent({
parameters: {
...defaultProps.parameters,
align: 'left',
},
});

const headlineContainer = screen.getByTestId('step-headline-container');
expect(headlineContainer).toHaveClass('text-left');
expect(headlineContainer).not.toHaveClass('text-center');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use client';

import React from 'react';
import type { ReactElement } from 'react';
import classNames from 'classnames';
import type { StepHeadlineAlign } from '../shared/StepHeadline';
import StepHeadline from '../shared/StepHeadline';
import type { FunnelStepFact } from '../types/funnel';
import { FunnelStepTransitionType } from '../types/funnel';
import { FunnelStepCtaWrapper } from '../shared/FunnelStepCtaWrapper';
import {
FunnelStepBackground,
FunnelBackgroundVariant,
} from '../shared/FunnelStepBackground';
import { LazyImage } from '../../../components/LazyImage';

const FunnelInformative = ({
parameters,
onTransition,
}: FunnelStepFact): ReactElement => {
const bgVariant = parameters?.reverse
? FunnelBackgroundVariant.Top
: FunnelBackgroundVariant.Bottom;
return (
<FunnelStepBackground variant={bgVariant}>
<FunnelStepCtaWrapper
containerClassName="flex"
onClick={() =>
onTransition({
type: FunnelStepTransitionType.Complete,
})
}
cta={{ label: parameters?.cta ?? 'Next' }}
>
<div
data-testid="informative-content"
className={classNames(
'flex flex-1 items-center gap-12 px-4 pt-6',
parameters?.reverse
? 'flex-col-reverse justify-end'
: 'flex-col justify-between',
)}
>
<StepHeadline
heading={parameters?.headline}
description={parameters?.explainer}
align={parameters?.align as StepHeadlineAlign}
/>
{parameters?.visualUrl && (
<LazyImage
eager
imgSrc={parameters?.visualUrl}
className="h-auto w-full object-cover"
ratio="64%"
imgAlt="Supportive illustration for the information"
/>
)}
</div>
</FunnelStepCtaWrapper>
</FunnelStepBackground>
);
};

export default FunnelInformative;
53 changes: 31 additions & 22 deletions packages/shared/src/features/onboarding/steps/FunnelQuiz.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { FormInputRating } from '../../common/components/FormInputRating';
import { FormInputCheckboxGroup } from '../../common/components/FormInputCheckboxGroup';
import ConditionalWrapper from '../../../components/ConditionalWrapper';
import { FunnelStepCtaWrapper } from '../shared/FunnelStepCtaWrapper';
import { FunnelStepBackground } from '../shared/FunnelStepBackground';
import StepHeadline from '../shared/StepHeadline';
import { Image } from '../../../components/image/Image';

const quizComponentsMap = {
[FunnelStepQuizQuestionType.Rating]: FormInputRating,
Expand All @@ -26,6 +29,7 @@ const checkIfSingleChoice = (type: FunnelStepQuizQuestionType): boolean => {
export const FunnelQuiz = ({
id,
question,
explainer,
onTransition,
}: FunnelStepQuiz): ReactElement => {
const { type, text, options, imageUrl } = question;
Expand Down Expand Up @@ -68,28 +72,33 @@ export const FunnelQuiz = ({
}, [isSingleChoice, stepValue, onTransition]);

return (
<ConditionalWrapper
condition={!isSingleChoice}
wrapper={(component) => (
<FunnelStepCtaWrapper onClick={onCtaClick}>
{component}
</FunnelStepCtaWrapper>
)}
>
{/* todo: MI-854 add Step Headline component once ready */}
<div className="flex flex-col gap-4">
<h2>{text}</h2>
{imageUrl && (
<img
alt="Question additional context"
aria-hidden
className="max-w-lg object-contain object-center"
role="presentation"
src={imageUrl}
/>
<FunnelStepBackground>
<ConditionalWrapper
condition={!isSingleChoice}
wrapper={(component) => (
<FunnelStepCtaWrapper onClick={onCtaClick}>
{component}
</FunnelStepCtaWrapper>
)}
<Component name={id} options={inputOptions} onValueChange={onChange} />
</div>
</ConditionalWrapper>
>
<div className="flex flex-col gap-4 px-4 py-6">
<StepHeadline heading={text} description={explainer} />
{imageUrl && (
<Image
alt="Question additional context"
aria-hidden
className="mx-auto max-w-lg object-contain object-center"
role="presentation"
src={imageUrl}
/>
)}
<Component
name={id}
options={inputOptions}
onValueChange={onChange}
/>
</div>
</ConditionalWrapper>
</FunnelStepBackground>
);
};
1 change: 1 addition & 0 deletions packages/shared/src/features/onboarding/types/funnel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export interface FunnelStepLandingPage extends FunnelStepCommon {

export interface FunnelStepFact extends FunnelStepCommon {
type: FunnelStepType.Fact;
onTransition: FunnelStepTransitionCallback<void>;
}

export enum FunnelStepQuizQuestionType {
Expand Down
Loading