chore: UX Fixes (#26643)

* Copy changes

* Move search bar inline with new button

* Get rid of no more results message

* Change hidden badge to (hidden)

* Remove Cal.ai badge from sidebar

* Add dropdown to create button when there is multiple options

* Fix delete dialog

* Saved filters updates

* More string fixes

* Switch members table to use names

* Fix member spacing

* Fix routing form identifier field

* Fix routing forms stuff

* Only show SMS hint on SMS options

* Make workflow delete button minimal

* Fix padding on workflow steps

* Remove min width on workflow title

* Fix delete workflow PR

* Fix org profile buttons

* Fix org profile screen partially scrolled down

* Improve logos & banner uploads

* Personal profile fixes

* Fix settings general view stuff

* Sentence case consistency

* Fix stuff I broke

* Fix fab

* Fix hidden translation string

* Fix text fields

* Make button small for solo users too

* fix: update E2E tests to match sentence case labels in routing forms

* fix: update tests to match sentence case label changes

- insights.e2e.ts: chart titles (14 strings)
- event-types.e2e.ts: Organizer phone number location
- EditLocationDialog.test.tsx: phone number labels

* fix: address Cubic AI review feedback (confidence 9+)

- Replace hardcoded text-gray-500 with text-muted in TextField.tsx hint section
- Replace text locator with data-testid in E2E test for location select

Co-Authored-By: unknown <>

* fix: update E2E tests for sentence case label changes

- Use data-testid selectors for location options (more reliable than text)
- Update field identifiers in routing-forms tests to match new labels
- Fix Long text selector in manage-booking-questions test

* fix: replace text locator with data-testid in manage-booking-questions E2E test

Replace fragile text="Long text" locator with resilient
page.getByTestId("select-option-textarea") selector per E2E best practices.

Addresses Cubic AI review feedback (confidence 9/10).

Co-Authored-By: unknown <>

* fix: use .last() for multiple location select items

---------

Co-authored-by: Pedro Castro <pedro@cal.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Bailey Pumfleet
2026-01-13 00:56:08 -05:00
committed by GitHub
co-authored by unknown <> Pedro Castro Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent 32d97fbea4
commit 7c66f33de2
31 changed files with 3350 additions and 1887 deletions
@@ -13,7 +13,11 @@ import { ALLOWED_FORM_WORKFLOW_ACTIONS } from "@calcom/features/ee/workflows/lib
import emailReminderTemplate from "@calcom/features/ee/workflows/lib/reminders/templates/emailReminderTemplate";
import type { FormValues } from "@calcom/features/ee/workflows/lib/types";
import type { WorkflowPermissions } from "@calcom/features/workflows/repositories/WorkflowPermissionsRepository";
import { SENDER_ID, SENDER_NAME, SCANNING_WORKFLOW_STEPS } from "@calcom/lib/constants";
import {
SENDER_ID,
SENDER_NAME,
SCANNING_WORKFLOW_STEPS,
} from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat";
import { WorkflowActions } from "@calcom/prisma/enums";
@@ -23,7 +27,10 @@ import { Button } from "@calcom/ui/components/button";
import { FormCard, FormCardBody } from "@calcom/ui/components/card";
import type { MultiSelectCheckboxesOptionType as Option } from "@calcom/ui/components/form";
import { Icon } from "@calcom/ui/components/icon";
import { useHasPaidPlan, useHasActiveTeamPlan } from "@calcom/web/modules/billing/hooks/useHasPaidPlan";
import {
useHasPaidPlan,
useHasActiveTeamPlan,
} from "@calcom/web/modules/billing/hooks/useHasPaidPlan";
import { AddActionDialog } from "./AddActionDialog";
import WorkflowStepContainer from "./WorkflowStepContainer";
@@ -70,20 +77,28 @@ export default function WorkflowDetailsPage(props: Props) {
const eventTypeId = searchParams?.get("eventTypeId");
// Get base action options and transform them for form triggers
const { data: baseActionOptions } = trpc.viewer.workflows.getWorkflowActionOptions.useQuery();
const { data: baseActionOptions } =
trpc.viewer.workflows.getWorkflowActionOptions.useQuery();
const transformedActionOptions = baseActionOptions
? baseActionOptions
.filter((option) => {
const isFormWorkflowWithInvalidSteps =
isFormTrigger(form.getValues("trigger")) &&
!ALLOWED_FORM_WORKFLOW_ACTIONS.some((action) => action === option.value);
!ALLOWED_FORM_WORKFLOW_ACTIONS.some(
(action) => action === option.value
);
const isSelectAllCalAiAction = isCalAIAction(option.value) && form.watch("selectAll");
const isSelectAllCalAiAction =
isCalAIAction(option.value) && form.watch("selectAll");
const isOrgCalAiAction = isCalAIAction(option.value) && isOrg;
if (isFormWorkflowWithInvalidSteps || isSelectAllCalAiAction || isOrgCalAiAction) {
if (
isFormWorkflowWithInvalidSteps ||
isSelectAllCalAiAction ||
isOrgCalAiAction
) {
return false;
}
return true;
@@ -100,7 +115,8 @@ export default function WorkflowDetailsPage(props: Props) {
}
}
const needsTeamsUpgrade = isFormTrigger(form.getValues("trigger")) && !hasActiveTeamPlan;
const needsTeamsUpgrade =
isFormTrigger(form.getValues("trigger")) && !hasActiveTeamPlan;
return {
...option,
@@ -117,8 +133,13 @@ export default function WorkflowDetailsPage(props: Props) {
: [];
useEffect(() => {
const matchingOption = allOptions.find((option) => option.value === eventTypeId);
if (matchingOption && !selectedOptions.find((option) => option.value === eventTypeId)) {
const matchingOption = allOptions.find(
(option) => option.value === eventTypeId
);
if (
matchingOption &&
!selectedOptions.find((option) => option.value === eventTypeId)
) {
const newOptions = [...selectedOptions, matchingOption];
setSelectedOptions(newOptions);
form.setValue("activeOn", newOptions);
@@ -141,7 +162,9 @@ export default function WorkflowDetailsPage(props: Props) {
})[0].id - 1
: 0;
const timeFormat = getTimeFormatStringFromUserTimeFormat(props.user.timeFormat);
const timeFormat = getTimeFormatStringFromUserTimeFormat(
props.user.timeFormat
);
const template = isFormTrigger(form.getValues("trigger"))
? WorkflowTemplates.CUSTOM
@@ -174,7 +197,9 @@ export default function WorkflowDetailsPage(props: Props) {
template,
numberRequired: numberRequired || false,
sender: isSMSAction(action) ? sender || SENDER_ID : SENDER_ID,
senderName: !isSMSAction(action) ? senderName || SENDER_NAME : SENDER_NAME,
senderName: !isSMSAction(action)
? senderName || SENDER_NAME
: SENDER_NAME,
numberVerificationPending: false,
includeCalendarEvent: false,
verifiedAt: SCANNING_WORKFLOW_STEPS ? null : new Date(),
@@ -185,8 +210,10 @@ export default function WorkflowDetailsPage(props: Props) {
form.setValue("steps", steps);
};
const { outboundAgentQueries: agentQueriesTrpc, inboundAgentQueries: inboundAgentQueriesTrpc } =
useAgentsData(form);
const {
outboundAgentQueries: agentQueriesTrpc,
inboundAgentQueries: inboundAgentQueriesTrpc,
} = useAgentsData(form);
return (
<>
@@ -199,9 +226,12 @@ export default function WorkflowDetailsPage(props: Props) {
<div className="border-subtle text-subtle ml-1 rounded-lg border p-1">
<Icon name="zap" size="16" />
</div>
<div className="text-sm font-medium leading-none">{t("trigger")}</div>
<div className="text-sm font-medium leading-none">
{t("trigger")}
</div>
</div>
}>
}
>
<FormCardBody className="border-muted">
<WorkflowStepContainer
form={form}
@@ -228,7 +258,8 @@ export default function WorkflowDetailsPage(props: Props) {
const agentData = agentQueriesTrpc[index]?.data;
const isAgentLoading = agentQueriesTrpc[index]?.isPending;
const inboundAgentData = inboundAgentQueriesTrpc[index]?.data;
const isInboundAgentLoading = inboundAgentQueriesTrpc[index]?.isPending;
const isInboundAgentLoading =
inboundAgentQueriesTrpc[index]?.isPending;
return (
<div key={index}>
@@ -241,13 +272,15 @@ export default function WorkflowDetailsPage(props: Props) {
<div className="border-subtle text-subtle rounded-lg border p-1">
<Icon name="arrow-right" size="16" />
</div>
<div className="text-sm font-medium leading-none">{t("action")}</div>
<div className="text-sm font-medium leading-none">
{t("action")}
</div>
</div>
}
deleteField={
!permissions.readOnly
? {
color: "destructive",
color: "minimal",
check: () => true,
disabled: !permissions.canUpdate,
fn: () => {
@@ -260,11 +293,16 @@ export default function WorkflowDetailsPage(props: Props) {
} else {
const steps = form.getValues("steps");
const updatedSteps = steps
?.filter((currStep) => currStep.id !== step.id)
?.filter(
(currStep) => currStep.id !== step.id
)
.map((s) => {
const updatedStep = s;
if (step.stepNumber < updatedStep.stepNumber) {
updatedStep.stepNumber = updatedStep.stepNumber - 1;
if (
step.stepNumber < updatedStep.stepNumber
) {
updatedStep.stepNumber =
updatedStep.stepNumber - 1;
}
return updatedStep;
});
@@ -276,7 +314,8 @@ export default function WorkflowDetailsPage(props: Props) {
},
}
: null
}>
}
>
<FormCardBody className="border-muted">
<WorkflowStepContainer
form={form}
@@ -316,7 +355,8 @@ export default function WorkflowDetailsPage(props: Props) {
type="button"
onClick={() => setIsAddActionDialogOpen(true)}
color="secondary"
className="bg-default">
className="bg-default"
>
{t("add_action")}
</Button>
</>
File diff suppressed because it is too large Load Diff