Skip to content

Commit

Permalink
feat(ui-editor): enable component poc and feedback form (#14210)
Browse files Browse the repository at this point in the history
Co-authored-by: Gørild Døhl <[email protected]>
  • Loading branch information
nkylstad and Ildest authored Dec 6, 2024
1 parent 8e95988 commit 3ab9086
Show file tree
Hide file tree
Showing 19 changed files with 251 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{
"$schema": "news.schema.json",
"news": [
{
"date": "2024-11-29",
"title": "Prøv nytt design på Utforming!",
"content": "Vi tester en ny måte å legge til komponenter på Utforming-siden. Du kan velge det nye designet øverst til høyre på Utforming-siden og teste så mye du vil! Etterpå kan du gi tilbakemelding på hva du synes, ved å velge Gi tilbakemelding nederst til høyre."
},
{
"date": "2024-10-25",
"title": "Prosess har blitt voksen!",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
z-index: 99999999; /* Make sure this is on top of everything */
}

.disclaimer {
margin-top: var(--fds-spacing-2);
}

.submit {
margin-top: var(--fds-spacing-2);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const buttonTexts: ButtonTexts = {
};

const heading = 'Heading';
const description = 'Description';

describe('FeedbackForm', () => {
it('should render FeedbackForm', () => {
Expand Down Expand Up @@ -102,8 +103,10 @@ const renderFeedbackForm = ({
render(
<FeedbackFormContext.Provider value={{ answers: {}, setAnswers: setAnswers || jest.fn() }}>
<FeedbackForm
id='test'
buttonTexts={buttonTexts}
heading={heading}
description={description}
questions={questions}
position={position || 'inline'}
onSubmit={jest.fn()}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useCallback, useRef } from 'react';
import { StudioButton, StudioModal } from '@studio/components';
import { StudioButton, StudioModal, StudioParagraph } from '@studio/components';
import type { ButtonTexts, QuestionConfig, QuestionsProps } from '../types/QuestionsProps';
import { YesNoQuestion } from './Question/YesNoQuestion';
import { useFeedbackFormContext } from '../contexts/FeedbackFormContext';
Expand All @@ -9,17 +9,23 @@ import { getDefaultAnswerValueForQuestion } from '../utils/questionUtils';
import type { AnswerType } from '../types/AnswerType';

type FeedbackFormProps = {
id: string;
buttonTexts: ButtonTexts;
heading: string;
description: string;
disclaimer?: string;
questions: QuestionConfig[];
position?: 'inline' | 'fixed';
onSubmit: (answers: Record<string, AnswerType>) => void;
};

export function FeedbackForm({
id,
questions,
buttonTexts,
heading,
description,
disclaimer,
position = 'inline',
onSubmit,
}: FeedbackFormProps): React.ReactElement {
Expand All @@ -42,7 +48,10 @@ export function FeedbackForm({
};

const handleSubmit = () => {
onSubmit(answers);
onSubmit({
...answers,
feedbackFormId: id,
});
handleCloseModal();
};

Expand Down Expand Up @@ -77,9 +86,17 @@ export function FeedbackForm({
closeButtonTitle={buttonTexts.close}
ref={modalRef}
>
<StudioParagraph size='sm' spacing={true}>
{description}
</StudioParagraph>
{questions.map((question) => {
return renderQuestion(question);
})}
{disclaimer && (
<StudioParagraph size='xs' spacing={true} className={classes.disclaimer}>
{disclaimer}
</StudioParagraph>
)}
<StudioButton className={classes.submit} onClick={handleSubmit} color='success'>
{buttonTexts.submit}
</StudioButton>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import React, { type ChangeEvent } from 'react';
import type { QuestionsProps } from '../../types/QuestionsProps';
import { StudioTextfield } from '@studio/components';
import { StudioTextarea } from '@studio/components';
import { useDebounce } from '@studio/hooks';

export function TextQuestion({ id, label, value, onChange }: QuestionsProps): React.ReactElement {
const { debounce } = useDebounce({ debounceTimeInMs: 500 });
const debouncedOnChange = (newValue: string) => debounce(() => onChange(id, newValue));
return (
<StudioTextfield
<StudioTextarea
size='sm'
id={id}
label={label}
value={value}
onChange={(e: ChangeEvent<HTMLInputElement>) => debouncedOnChange(e.target.value)}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => debouncedOnChange(e.target.value)}
/>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { StudioButton, StudioParagraph } from '@studio/components';
import { StudioButton, StudioLabelAsParagraph } from '@studio/components';
import { ThumbDownFillIcon, ThumbDownIcon, ThumbUpFillIcon, ThumbUpIcon } from '@studio/icons';
import type { QuestionsProps } from '../../types/QuestionsProps';
import classes from './YesNoQuestion.module.css';
Expand All @@ -26,7 +26,7 @@ export function YesNoQuestion({ id, label, value, buttonLabels, onChange }: YesN

return (
<div>
<StudioParagraph>{label}</StudioParagraph>
<StudioLabelAsParagraph size='sm'>{label}</StudioLabelAsParagraph>
<div className={classes.buttons}>
<StudioButton
variant='tertiary'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ describe('FeedbackFormImpl', () => {
it('should render FeedbackFormImpl', () => {
const feedbackForm = new FeedbackFormImpl({
onSubmit: jest.fn(),
id: 'test',
buttonTexts: {
submit: 'Submit',
trigger: 'Give feedback',
close: 'Close',
},
heading: 'Give feedback - heading',
description: 'Description',
questions: mockQuestions,
});

Expand All @@ -25,13 +27,15 @@ describe('FeedbackFormImpl', () => {
it('should open form modal when trigger button is clicked', async () => {
const user = userEvent.setup();
const feedbackForm = new FeedbackFormImpl({
id: 'test',
onSubmit: jest.fn(),
buttonTexts: {
submit: 'Submit',
trigger: 'Give feedback',
close: 'Close',
},
heading: 'Give feedback - heading',
description: 'Description',
questions: mockQuestions,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,45 @@ import { FeedbackForm } from '../FeedbackForm/FeedbackForm';
import type { AnswerType } from '../types/AnswerType';

export class FeedbackFormImpl {
private readonly id: string;
private readonly buttonTexts: ButtonTexts;
private readonly heading: string;
private readonly description: string;
private readonly disclaimer?: string;
private readonly questions: QuestionConfig[];
private readonly position: 'inline' | 'fixed' = 'inline';
private readonly onSubmit: (answers: Record<string, any>) => void;

constructor(config: {
id: string;
buttonTexts: ButtonTexts;
heading: string;
description: string;
disclaimer?: string;
questions: QuestionConfig[];
position?: 'inline' | 'fixed';
onSubmit: (answers: Record<string, AnswerType>) => void;
}) {
this.id = config.id;
this.buttonTexts = config.buttonTexts;
this.heading = config.heading;
this.description = config.description;
this.disclaimer = config.disclaimer;
this.questions = config.questions;
this.getFeedbackForm = this.getFeedbackForm.bind(this);
this.position = config.position || 'inline';
this.onSubmit = config.onSubmit;
}

public getFeedbackForm(): React.ReactNode {
public getFeedbackForm(): React.ReactElement {
return (
<FeedbackFormContextProvider>
<FeedbackForm
id={this.id}
buttonTexts={this.buttonTexts}
heading={this.heading}
description={this.description}
disclaimer={this.disclaimer}
questions={this.questions}
position={this.position}
onSubmit={this.onSubmit}
Expand Down
3 changes: 3 additions & 0 deletions frontend/packages/shared/src/api/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export const dataModelAddXsdFromRepoPath = (org, app, filePath) => `${basePath}/
// Deployment
// See frontend/app-development/utils/urlHelper.ts Deployments

// Feedback form
export const submitFeedbackPath = (org, app) => `${basePath}/${org}/${app}/feedbackform/submit`; // Post

// FormEditor
export const ruleHandlerPath = (org, app, layoutSetName) => `${basePath}/${org}/${app}/app-development/rule-handler?${s({ layoutSetName })}`; // Get, Post
export const widgetSettingsPath = (org, app) => `${basePath}/${org}/${app}/app-development/widget-settings`; // Get
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.componentButtonInline {
height: 80px;
width: 140px;
margin: 8px;
height: 60px;
width: 120px;
margin: 4px;
padding: 6px;
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
.componentButton {
margin: 4px;
width: 140px;
height: 100px;
width: 130px;
height: 80px;
padding: 2px;
overflow: wrap;
}

.componentButtonInline {
height: 80px;
width: 140px;
margin: 8px;
height: 60px;
width: 120px;
margin: 4px;
padding: 2px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function ComponentButton({
<StudioButton
variant={selected ? 'primary' : 'secondary'}
onClick={onClick}
size='sm'
size='xs'
aria-label={tooltipContent}
className={inline ? classes.componentButtonInline : classes.componentButton}
title={tooltipContent}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { FeedbackForm } from './FeedbackForm';
import axios from 'axios';

jest.mock('axios');
var mockedAxios = axios as jest.Mocked<typeof axios>;

describe('FeedbackForm', () => {
afterAll(() => {
jest.clearAllMocks();
});

it('should render feedback form', () => {
renderFeedbackForm();
expect(screen.getByRole('button', { name: 'Gi tilbakemelding' })).toBeInTheDocument();
});

it('should open the feedback form when clicking trigger', async () => {
const user = userEvent.setup();
renderFeedbackForm();
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
await user.click(screen.getByRole('button', { name: 'Gi tilbakemelding' }));
expect(await screen.findByRole('dialog')).toBeInTheDocument();
});

it('should close the feedback form when clicking send', async () => {
const user = userEvent.setup();
mockedAxios.post.mockResolvedValueOnce({});
renderFeedbackForm();
await user.click(screen.getByRole('button', { name: 'Gi tilbakemelding' }));
expect(await screen.findByRole('dialog')).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: 'Send' }));
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
});

const renderFeedbackForm = () => {
return render(<FeedbackForm />);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { ReactElement } from 'react';
import { submitFeedbackPath } from 'app-shared/api/paths';
import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams';
import { post } from 'app-shared/utils/networking';
import { FeedbackFormImpl } from '@studio/feedback-form';
import { toast } from 'react-toastify';

/**
* This is a feedback form to gather feedback on the new design for adding components.
* It uses the FeedbackForm component from the @studio/feedback-form package.
* The form is temporary, and will be removed once the new design is fully tested, or we decide to go in a different direction.
* As such, all texts are hardcoded in Norwegian, to avoid adding unnecessary translations.
* @returns The FeedbackForm component.
*/
export function FeedbackForm(): ReactElement {
const { org, app } = useStudioEnvironmentParams();

const submitFeedback = async (answers: Record<string, string>) => {
try {
// Using regular axios post rather than a mutation hook, since we are not storing
// the feedback in the cache, nor are we updating any state.
await post(submitFeedbackPath(org, app), { answers: { ...answers } });
toast.success('Takk for tilbakemeldingen!');
} catch (error) {
console.error('Failed to submit feedback', error);
toast.error('Noe gikk galt. Prøv igjen senere.');
}
};

const feedbackForm = new FeedbackFormImpl({
id: 'add-component-poc-feedback',
onSubmit: submitFeedback,
buttonTexts: {
submit: 'Send',
trigger: 'Gi tilbakemelding',
close: 'Lukk',
},
heading: 'Gi tilbakemelding',
description:
'Hei! Så bra at du har testet det nye designet for å legge til komponenter! Vi vil gjerne høre hva du synes.',
disclaimer:
'Merk at KI-verktøy kan bli brukt til å analysere svarene. Påse at du ikke inkluderer personopplysninger i dine svar.',
position: 'fixed',
questions: [
{
id: 'bedreJaNei',
type: 'yesNo',
questionText: 'Likte du dette designet bedre?',
buttonLabels: {
yes: 'Ja',
no: 'Nei',
},
},
{
id: 'kommentar',
type: 'text',
questionText: 'Har du kommentarer eller forslag til forbedringer?',
},
],
});

return feedbackForm.getFeedbackForm();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.switchWrapper {
display: flex;
flex-direction: row;
gap: var(--fds-spacing-2);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { ToggleAddComponentPoc } from './ToggleAddComponentPoc';

describe('ToggleAddComponentPoc', () => {
it('should render the component', () => {
render(<ToggleAddComponentPoc />);
expect(screen.getByText('Prøv nytt design')).toBeInTheDocument();
});
});
Loading

0 comments on commit 3ab9086

Please sign in to comment.