Add empty message for form actions (#12414)

<img width="503" alt="Capture d’écran 2025-06-02 à 15 55 36"
src="https://github.com/user-attachments/assets/9b3f60ae-7a13-45f8-aa87-ba32211e832f"
/>
This commit is contained in:
Thomas Trompette
2025-06-02 18:04:39 +02:00
committed by GitHub
parent e71aef5a3a
commit a508f4a4fb
6 changed files with 132 additions and 19 deletions

View File

@ -5,17 +5,18 @@ export const LINE_HEIGHT = 24;
const StyledFormFieldInputRowContainer = styled.div<{
multiline?: boolean;
maxHeight?: number;
}>`
display: flex;
flex-direction: row;
position: relative;
${({ multiline }) =>
${({ multiline, maxHeight }) =>
multiline
? css`
line-height: ${LINE_HEIGHT}px;
min-height: ${3 * LINE_HEIGHT}px;
max-height: ${5 * LINE_HEIGHT}px;
max-height: ${maxHeight ?? 5 * LINE_HEIGHT}px;
`
: css`
height: 32px;

View File

@ -8,6 +8,7 @@ import { WorkflowFormAction } from '@/workflow/types/Workflow';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
import { WorkflowEditActionFormFieldSettings } from '@/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowEditActionFormFieldSettings';
import { WorkflowFormEmptyMessage } from '@/workflow/workflow-steps/workflow-actions/form-action/components/WorkflowFormEmptyMessage';
import { WorkflowFormActionField } from '@/workflow/workflow-steps/workflow-actions/form-action/types/WorkflowFormActionField';
import { getDefaultFormFieldSettings } from '@/workflow/workflow-steps/workflow-actions/form-action/utils/getDefaultFormFieldSettings';
import { useActionHeaderTypeOrThrow } from '@/workflow/workflow-steps/workflow-actions/hooks/useActionHeaderTypeOrThrow';
@ -234,6 +235,9 @@ export const WorkflowEditActionFormBuilder = ({
disabled={actionOptions.readonly}
/>
<StyledWorkflowStepBody>
{formData.length === 0 && (
<WorkflowFormEmptyMessage data-testid="empty-form-message" />
)}
<DraggableList
onDragEnd={handleDragEnd}
draggableItems={

View File

@ -0,0 +1,66 @@
import { FormFieldInputContainer } from '@/object-record/record-field/form-types/components/FormFieldInputContainer';
import { FormFieldInputInnerContainer } from '@/object-record/record-field/form-types/components/FormFieldInputInnerContainer';
import { FormFieldInputRowContainer } from '@/object-record/record-field/form-types/components/FormFieldInputRowContainer';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
const StyledMessageContainer = styled.div`
padding-bottom: ${({ theme }) => theme.spacing(4)};
padding-inline: ${({ theme }) => theme.spacing(7)};
padding-top: ${({ theme }) => theme.spacing(2)};
`;
const StyledMessageContentContainer = styled.div`
flex-direction: column;
color: ${({ theme }) => theme.font.color.secondary};
display: flex;
gap: ${({ theme }) => theme.spacing(4)};
width: 100%;
padding: ${({ theme }) => theme.spacing(4)};
line-height: normal;
`;
const StyledMessageTitle = styled.div`
color: ${({ theme }) => theme.font.color.primary};
font-weight: ${({ theme }) => theme.font.weight.medium};
line-height: 13px;
`;
const StyledMessageDescription = styled.div`
color: ${({ theme }) => theme.font.color.secondary};
font-weight: ${({ theme }) => theme.font.weight.regular};
`;
const StyledFieldContainer = styled.div`
align-items: center;
background: transparent;
border: none;
display: flex;
font-family: inherit;
width: 100%;
`;
export const WorkflowFormEmptyMessage = () => {
const { t } = useLingui();
return (
<StyledMessageContainer>
<FormFieldInputContainer>
<FormFieldInputRowContainer multiline maxHeight={124}>
<FormFieldInputInnerContainer hasRightElement={false}>
<StyledFieldContainer>
<StyledMessageContentContainer>
<StyledMessageTitle data-testid="empty-form-message-title">
{t`Add inputs to your form`}
</StyledMessageTitle>
<StyledMessageDescription data-testid="empty-form-message-description">
{t`Click on "Add Field" below to add the first input to your form. The form will pop up on the user's screen when the workflow is launched from a manual trigger. For other types of triggers, it will be displayed in the Workflow run record page.`}
</StyledMessageDescription>
</StyledMessageContentContainer>
</StyledFieldContainer>
</FormFieldInputInnerContainer>
</FormFieldInputRowContainer>
</FormFieldInputContainer>
</StyledMessageContainer>
);
};

View File

@ -170,3 +170,29 @@ export const DisabledWithEmptyValues: Story = {
expect(addFieldButton).not.toBeInTheDocument();
},
};
export const EmptyForm: Story = {
args: {
actionOptions: {
onActionUpdate: fn(),
},
action: {
...DEFAULT_ACTION,
settings: {
...DEFAULT_ACTION.settings,
input: [],
},
},
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const messageContainer = await canvas.findByTestId(
'empty-form-message-title',
);
expect(messageContainer).toBeVisible();
const addFieldButton = await canvas.findByText('Add Field');
expect(addFieldButton).toBeVisible();
},
};

View File

@ -0,0 +1,32 @@
import type { Meta, StoryObj } from '@storybook/react';
import { expect, within } from '@storybook/test';
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
import { WorkflowFormEmptyMessage } from '../WorkflowFormEmptyMessage';
const meta: Meta<typeof WorkflowFormEmptyMessage> = {
title: 'Modules/Workflow/Actions/Form/WorkflowFormEmptyMessage',
component: WorkflowFormEmptyMessage,
parameters: {
layout: 'centered',
},
decorators: [I18nFrontDecorator],
};
export default meta;
type Story = StoryObj<typeof WorkflowFormEmptyMessage>;
export const Default: Story = {
args: {},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const messageTitleContainer = await canvas.findByTestId(
'empty-form-message-title',
);
const messageDescriptionContainer = await canvas.findByTestId(
'empty-form-message-description',
);
expect(messageTitleContainer).toBeVisible();
expect(messageDescriptionContainer).toBeVisible();
},
};

View File

@ -1,7 +1,6 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { FieldMetadataType } from 'twenty-shared/types';
import { isDefined, isValidUuid } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { v4 } from 'uuid';
@ -541,22 +540,7 @@ export class WorkflowVersionStepWorkspaceService {
valid: false,
settings: {
...BASE_STEP_DEFINITION,
input: [
{
id: v4(),
name: 'company',
label: 'Company',
placeholder: 'Select a company',
type: FieldMetadataType.TEXT,
},
{
id: v4(),
name: 'number',
label: 'Number',
placeholder: '1000',
type: FieldMetadataType.NUMBER,
},
],
input: [],
},
};
}