mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-29 18:00:26 -06:00
fix: remove capitalize functions (#6610)
Co-authored-by: Johannes <johannes@formbricks.com>
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import { ArrowUpRightIcon, ChevronRightIcon, LogOutIcon } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { TOrganization } from "@formbricks/types/organizations";
|
||||
import { TUser } from "@formbricks/types/user";
|
||||
import FBLogo from "@/images/formbricks-wordmark.svg";
|
||||
import { cn } from "@/lib/cn";
|
||||
import { capitalizeFirstLetter } from "@/lib/utils/strings";
|
||||
import { useSignOut } from "@/modules/auth/hooks/use-sign-out";
|
||||
import { CreateOrganizationModal } from "@/modules/organization/components/CreateOrganizationModal";
|
||||
import { ProfileAvatar } from "@/modules/ui/components/avatars";
|
||||
@@ -12,13 +18,6 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/modules/ui/components/dropdown-menu";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import { ArrowUpRightIcon, ChevronRightIcon, LogOutIcon } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { TOrganization } from "@formbricks/types/organizations";
|
||||
import { TUser } from "@formbricks/types/user";
|
||||
|
||||
interface LandingSidebarProps {
|
||||
user: TUser;
|
||||
@@ -66,10 +65,8 @@ export const LandingSidebar = ({ user, organization }: LandingSidebarProps) => {
|
||||
)}>
|
||||
{user?.name ? <span>{user?.name}</span> : <span>{user?.email}</span>}
|
||||
</p>
|
||||
<p
|
||||
title={capitalizeFirstLetter(organization?.name)}
|
||||
className="truncate text-sm text-slate-500">
|
||||
{capitalizeFirstLetter(organization?.name)}
|
||||
<p title={organization?.name} className="truncate text-sm text-slate-500">
|
||||
{organization?.name}
|
||||
</p>
|
||||
</div>
|
||||
<ChevronRightIcon className={cn("h-5 w-5 shrink-0 text-slate-700 hover:text-slate-500")} />
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import { cn } from "@/lib/cn";
|
||||
import { Badge } from "@/modules/ui/components/badge";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { H4, Small } from "@/modules/ui/components/typography";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
|
||||
interface ButtonInfo {
|
||||
text: string;
|
||||
@@ -41,7 +41,7 @@ export const SettingsCard = ({
|
||||
id={title}>
|
||||
<div className="flex justify-between border-b border-slate-200 px-4 pb-4">
|
||||
<div>
|
||||
<H4 className="font-medium capitalize tracking-normal">{title}</H4>
|
||||
<H4 className="font-medium tracking-normal">{title}</H4>
|
||||
<div className="ml-2">
|
||||
{beta && <Badge size="normal" type="warning" text="Beta" />}
|
||||
{soon && (
|
||||
|
||||
@@ -1,36 +1,7 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import {
|
||||
capitalizeFirstLetter,
|
||||
isCapitalized,
|
||||
sanitizeString,
|
||||
startsWithVowel,
|
||||
truncate,
|
||||
truncateText,
|
||||
} from "./strings";
|
||||
import { isCapitalized, sanitizeString, startsWithVowel, truncate, truncateText } from "./strings";
|
||||
|
||||
describe("String Utilities", () => {
|
||||
describe("capitalizeFirstLetter", () => {
|
||||
test("capitalizes the first letter of a string", () => {
|
||||
expect(capitalizeFirstLetter("hello")).toBe("Hello");
|
||||
});
|
||||
|
||||
test("returns empty string if input is null", () => {
|
||||
expect(capitalizeFirstLetter(null)).toBe("");
|
||||
});
|
||||
|
||||
test("returns empty string if input is empty string", () => {
|
||||
expect(capitalizeFirstLetter("")).toBe("");
|
||||
});
|
||||
|
||||
test("doesn't change already capitalized string", () => {
|
||||
expect(capitalizeFirstLetter("Hello")).toBe("Hello");
|
||||
});
|
||||
|
||||
test("handles single character string", () => {
|
||||
expect(capitalizeFirstLetter("a")).toBe("A");
|
||||
});
|
||||
});
|
||||
|
||||
describe("truncate", () => {
|
||||
test("returns the string as is if length is less than the specified length", () => {
|
||||
expect(truncate("hello", 10)).toBe("hello");
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
export const capitalizeFirstLetter = (string: string | null = "") => {
|
||||
if (string === null) {
|
||||
return "";
|
||||
}
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
};
|
||||
|
||||
// write a function that takes a string and truncates it to the specified length
|
||||
export const truncate = (str: string, length: number) => {
|
||||
if (!str) return "";
|
||||
|
||||
@@ -230,7 +230,7 @@ describe("RenderResponse", () => {
|
||||
showId={false}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByTestId("ResponseBadges")).toHaveTextContent("Value");
|
||||
expect(screen.getByTestId("ResponseBadges")).toHaveTextContent("value");
|
||||
});
|
||||
|
||||
test("renders ResponseBadges for 'Consent' question (number)", () => {
|
||||
@@ -258,7 +258,7 @@ describe("RenderResponse", () => {
|
||||
showId={false}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByTestId("ResponseBadges")).toHaveTextContent("Click");
|
||||
expect(screen.getByTestId("ResponseBadges")).toHaveTextContent("click");
|
||||
});
|
||||
|
||||
test("renders ResponseBadges for 'MultipleChoiceSingle' question (string)", () => {
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
import { cn } from "@/lib/cn";
|
||||
import { getLanguageCode, getLocalizedValue } from "@/lib/i18n/utils";
|
||||
import { getChoiceIdByValue } from "@/lib/response/utils";
|
||||
import { processResponseData } from "@/lib/responses";
|
||||
import { formatDateWithOrdinal } from "@/lib/utils/datetime";
|
||||
import { capitalizeFirstLetter } from "@/lib/utils/strings";
|
||||
import { renderHyperlinkedContent } from "@/modules/analysis/utils";
|
||||
import { ArrayResponse } from "@/modules/ui/components/array-response";
|
||||
import { FileUploadResponse } from "@/modules/ui/components/file-upload-response";
|
||||
import { PictureSelectionResponse } from "@/modules/ui/components/picture-selection-response";
|
||||
import { RankingResponse } from "@/modules/ui/components/ranking-response";
|
||||
import { RatingResponse } from "@/modules/ui/components/rating-response";
|
||||
import { ResponseBadges } from "@/modules/ui/components/response-badges";
|
||||
import { CheckCheckIcon, MousePointerClickIcon, PhoneIcon } from "lucide-react";
|
||||
import React from "react";
|
||||
import { TResponseDataValue } from "@formbricks/types/responses";
|
||||
@@ -22,6 +9,18 @@ import {
|
||||
TSurveyQuestionTypeEnum,
|
||||
TSurveyRatingQuestion,
|
||||
} from "@formbricks/types/surveys/types";
|
||||
import { cn } from "@/lib/cn";
|
||||
import { getLanguageCode, getLocalizedValue } from "@/lib/i18n/utils";
|
||||
import { getChoiceIdByValue } from "@/lib/response/utils";
|
||||
import { processResponseData } from "@/lib/responses";
|
||||
import { formatDateWithOrdinal } from "@/lib/utils/datetime";
|
||||
import { renderHyperlinkedContent } from "@/modules/analysis/utils";
|
||||
import { ArrayResponse } from "@/modules/ui/components/array-response";
|
||||
import { FileUploadResponse } from "@/modules/ui/components/file-upload-response";
|
||||
import { PictureSelectionResponse } from "@/modules/ui/components/picture-selection-response";
|
||||
import { RankingResponse } from "@/modules/ui/components/ranking-response";
|
||||
import { RatingResponse } from "@/modules/ui/components/rating-response";
|
||||
import { ResponseBadges } from "@/modules/ui/components/response-badges";
|
||||
|
||||
interface RenderResponseProps {
|
||||
responseData: TResponseDataValue;
|
||||
@@ -104,9 +103,7 @@ export const RenderResponse: React.FC<RenderResponseProps> = ({
|
||||
const rowValueInSelectedLanguage = getLocalizedValue(row.label, languagCode);
|
||||
if (!responseData[rowValueInSelectedLanguage]) return null;
|
||||
return (
|
||||
<p
|
||||
key={rowValueInSelectedLanguage}
|
||||
className="ph-no-capture my-1 font-normal capitalize text-slate-700">
|
||||
<p key={rowValueInSelectedLanguage} className="ph-no-capture my-1 font-normal text-slate-700">
|
||||
{rowValueInSelectedLanguage}:{processResponseData(responseData[rowValueInSelectedLanguage])}
|
||||
</p>
|
||||
);
|
||||
@@ -126,7 +123,7 @@ export const RenderResponse: React.FC<RenderResponseProps> = ({
|
||||
if (typeof responseData === "string" || typeof responseData === "number") {
|
||||
return (
|
||||
<ResponseBadges
|
||||
items={[{ value: capitalizeFirstLetter(responseData.toString()) }]}
|
||||
items={[{ value: responseData.toString() }]}
|
||||
isExpanded={isExpanded}
|
||||
icon={<PhoneIcon className="h-4 w-4 text-slate-500" />}
|
||||
showId={showId}
|
||||
@@ -138,7 +135,7 @@ export const RenderResponse: React.FC<RenderResponseProps> = ({
|
||||
if (typeof responseData === "string" || typeof responseData === "number") {
|
||||
return (
|
||||
<ResponseBadges
|
||||
items={[{ value: capitalizeFirstLetter(responseData.toString()) }]}
|
||||
items={[{ value: responseData.toString() }]}
|
||||
isExpanded={isExpanded}
|
||||
icon={<CheckCheckIcon className="h-4 w-4 text-slate-500" />}
|
||||
showId={showId}
|
||||
@@ -150,7 +147,7 @@ export const RenderResponse: React.FC<RenderResponseProps> = ({
|
||||
if (typeof responseData === "string" || typeof responseData === "number") {
|
||||
return (
|
||||
<ResponseBadges
|
||||
items={[{ value: capitalizeFirstLetter(responseData.toString()) }]}
|
||||
items={[{ value: responseData.toString() }]}
|
||||
isExpanded={isExpanded}
|
||||
icon={<MousePointerClickIcon className="h-4 w-4 text-slate-500" />}
|
||||
showId={showId}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/cn";
|
||||
import { capitalizeFirstLetter } from "@/lib/utils/strings";
|
||||
import { Badge } from "@/modules/ui/components/badge";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { TOrganization, TOrganizationBillingPeriod } from "@formbricks/types/organizations";
|
||||
import { cn } from "@/lib/cn";
|
||||
import { Badge } from "@/modules/ui/components/badge";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { isSubscriptionCancelledAction, manageSubscriptionAction, upgradePlanAction } from "../actions";
|
||||
import { getCloudPricingData } from "../api/lib/constants";
|
||||
import { BillingSlider } from "./billing-slider";
|
||||
@@ -141,7 +140,7 @@ export const PricingTable = ({
|
||||
<div className="flex w-full">
|
||||
<h2 className="mb-3 mr-2 inline-flex w-full text-2xl font-bold text-slate-700">
|
||||
{t("environments.settings.billing.current_plan")}:{" "}
|
||||
{capitalizeFirstLetter(organization.billing.plan)}
|
||||
<span className="capitalize">{organization.billing.plan}</span>
|
||||
{cancellingOn && (
|
||||
<Badge
|
||||
className="mx-2"
|
||||
@@ -175,7 +174,7 @@ export const PricingTable = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-2 flex flex-col rounded-xl border border-slate-200 bg-white py-4 capitalize shadow-sm dark:bg-slate-800">
|
||||
<div className="mt-2 flex flex-col rounded-xl border border-slate-200 bg-white py-4 shadow-sm dark:bg-slate-800">
|
||||
<div
|
||||
className={cn(
|
||||
"relative mx-8 mb-8 flex flex-col gap-4",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getResponsesByContactId } from "@/lib/response/service";
|
||||
import { capitalizeFirstLetter } from "@/lib/utils/strings";
|
||||
import { getContactAttributes } from "@/modules/ee/contacts/lib/contact-attributes";
|
||||
import { getContact } from "@/modules/ee/contacts/lib/contacts";
|
||||
import { IdBadge } from "@/modules/ui/components/id-badge";
|
||||
@@ -59,7 +58,7 @@ export const AttributesSection = async ({ contactId }: { contactId: string }) =>
|
||||
.map(([key, attributeData]) => {
|
||||
return (
|
||||
<div key={key}>
|
||||
<dt className="text-sm font-medium text-slate-500">{capitalizeFirstLetter(key.toString())}</dt>
|
||||
<dt className="text-sm font-medium text-slate-500">{key.toString()}</dt>
|
||||
<dd className="mt-1 text-sm text-slate-900">{attributeData}</dd>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,34 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/cn";
|
||||
import { structuredClone } from "@/lib/pollyfills/structuredClone";
|
||||
import { isCapitalized } from "@/lib/utils/strings";
|
||||
import {
|
||||
convertOperatorToText,
|
||||
convertOperatorToTitle,
|
||||
toggleFilterConnector,
|
||||
updateContactAttributeKeyInFilter,
|
||||
updateDeviceTypeInFilter,
|
||||
updateFilterValue,
|
||||
updateOperatorInFilter,
|
||||
updatePersonIdentifierInFilter,
|
||||
updateSegmentIdInFilter,
|
||||
} from "@/modules/ee/contacts/segments/lib/utils";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/modules/ui/components/dropdown-menu";
|
||||
import { Input } from "@/modules/ui/components/input";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/modules/ui/components/select";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import {
|
||||
ArrowDownIcon,
|
||||
@@ -64,6 +35,35 @@ import {
|
||||
DEVICE_OPERATORS,
|
||||
PERSON_OPERATORS,
|
||||
} from "@formbricks/types/segment";
|
||||
import { cn } from "@/lib/cn";
|
||||
import { structuredClone } from "@/lib/pollyfills/structuredClone";
|
||||
import { isCapitalized } from "@/lib/utils/strings";
|
||||
import {
|
||||
convertOperatorToText,
|
||||
convertOperatorToTitle,
|
||||
toggleFilterConnector,
|
||||
updateContactAttributeKeyInFilter,
|
||||
updateDeviceTypeInFilter,
|
||||
updateFilterValue,
|
||||
updateOperatorInFilter,
|
||||
updatePersonIdentifierInFilter,
|
||||
updateSegmentIdInFilter,
|
||||
} from "@/modules/ee/contacts/segments/lib/utils";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/modules/ui/components/dropdown-menu";
|
||||
import { Input } from "@/modules/ui/components/input";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/modules/ui/components/select";
|
||||
import { AddFilterModal } from "./add-filter-modal";
|
||||
|
||||
interface TSegmentFilterProps {
|
||||
@@ -314,7 +314,7 @@ function AttributeSegmentFilter({
|
||||
}}
|
||||
value={attrKeyValue}>
|
||||
<SelectTrigger
|
||||
className="flex w-auto items-center justify-center whitespace-nowrap bg-white capitalize"
|
||||
className="flex w-auto items-center justify-center whitespace-nowrap bg-white"
|
||||
hideArrow>
|
||||
<SelectValue>
|
||||
<div className={cn("flex items-center gap-2", !isCapitalized(attrKeyValue ?? "") && "lowercase")}>
|
||||
@@ -496,7 +496,7 @@ function PersonSegmentFilter({
|
||||
}}
|
||||
value={personIdentifier}>
|
||||
<SelectTrigger
|
||||
className="flex w-auto items-center justify-center whitespace-nowrap bg-white capitalize"
|
||||
className="flex w-auto items-center justify-center whitespace-nowrap bg-white"
|
||||
hideArrow>
|
||||
<SelectValue>
|
||||
<div className="flex items-center gap-1 lowercase">
|
||||
@@ -647,7 +647,7 @@ function SegmentSegmentFilter({
|
||||
}}
|
||||
value={currentSegment?.id}>
|
||||
<SelectTrigger
|
||||
className="flex w-auto items-center justify-center whitespace-nowrap bg-white capitalize"
|
||||
className="flex w-auto items-center justify-center whitespace-nowrap bg-white"
|
||||
hideArrow>
|
||||
<div className="flex items-center gap-1">
|
||||
<Users2Icon className="h-4 w-4 text-sm" />
|
||||
|
||||
@@ -25,6 +25,15 @@ vi.mock("../actions", () => ({
|
||||
updateInviteAction: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/membership/utils", () => ({
|
||||
getAccessFlags: (role: string) => ({
|
||||
isOwner: role === "owner",
|
||||
isManager: role === "manager",
|
||||
isMember: role === "member",
|
||||
isBilling: role === "billing",
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("EditMembershipRole Component", () => {
|
||||
const mockRouter = {
|
||||
refresh: vi.fn(),
|
||||
@@ -53,15 +62,21 @@ describe("EditMembershipRole Component", () => {
|
||||
|
||||
describe("Rendering", () => {
|
||||
test("renders a dropdown when user is owner", () => {
|
||||
render(<EditMembershipRole {...defaultProps} />);
|
||||
render(<EditMembershipRole {...defaultProps} isUserManagementDisabledFromUi={false} />);
|
||||
|
||||
const button = screen.queryByRole("button-role");
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).toHaveTextContent("Member");
|
||||
expect(button).toHaveTextContent("member");
|
||||
});
|
||||
|
||||
test("renders a badge when user is not owner or manager", () => {
|
||||
render(<EditMembershipRole {...defaultProps} currentUserRole="member" />);
|
||||
render(
|
||||
<EditMembershipRole
|
||||
{...defaultProps}
|
||||
currentUserRole="member"
|
||||
isUserManagementDisabledFromUi={false}
|
||||
/>
|
||||
);
|
||||
|
||||
const badge = screen.queryByRole("badge-role");
|
||||
expect(badge).toBeInTheDocument();
|
||||
@@ -70,21 +85,42 @@ describe("EditMembershipRole Component", () => {
|
||||
});
|
||||
|
||||
test("disables the dropdown when editing own role", () => {
|
||||
render(<EditMembershipRole {...defaultProps} memberId="user-456" userId="user-456" />);
|
||||
render(
|
||||
<EditMembershipRole
|
||||
{...defaultProps}
|
||||
memberId="user-456"
|
||||
userId="user-456"
|
||||
isUserManagementDisabledFromUi={false}
|
||||
/>
|
||||
);
|
||||
|
||||
const button = screen.getByRole("button-role");
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
|
||||
test("disables the dropdown when the user is the only owner", () => {
|
||||
render(<EditMembershipRole {...defaultProps} memberRole="owner" doesOrgHaveMoreThanOneOwner={false} />);
|
||||
render(
|
||||
<EditMembershipRole
|
||||
{...defaultProps}
|
||||
memberRole="owner"
|
||||
doesOrgHaveMoreThanOneOwner={false}
|
||||
isUserManagementDisabledFromUi={false}
|
||||
/>
|
||||
);
|
||||
|
||||
const button = screen.getByRole("button-role");
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
|
||||
test("disables the dropdown when a manager tries to edit an owner", () => {
|
||||
render(<EditMembershipRole {...defaultProps} currentUserRole="manager" memberRole="owner" />);
|
||||
render(
|
||||
<EditMembershipRole
|
||||
{...defaultProps}
|
||||
currentUserRole="manager"
|
||||
memberRole="owner"
|
||||
isUserManagementDisabledFromUi={false}
|
||||
/>
|
||||
);
|
||||
|
||||
const button = screen.getByRole("button-role");
|
||||
expect(button).toBeDisabled();
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import { ChevronDownIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import type { TOrganizationRole } from "@formbricks/types/memberships";
|
||||
import { getAccessFlags } from "@/lib/membership/utils";
|
||||
import { capitalizeFirstLetter } from "@/lib/utils/strings";
|
||||
import { Badge } from "@/modules/ui/components/badge";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import {
|
||||
@@ -11,12 +16,6 @@ import {
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/modules/ui/components/dropdown-menu";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import { ChevronDownIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import type { TOrganizationRole } from "@formbricks/types/memberships";
|
||||
import { updateInviteAction, updateMembershipAction } from "../actions";
|
||||
|
||||
interface Role {
|
||||
@@ -104,7 +103,7 @@ export function EditMembershipRole({
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
role="button-role">
|
||||
<span className="ml-1">{capitalizeFirstLetter(memberRole)}</span>
|
||||
<span className="ml-1 capitalize">{memberRole}</span>
|
||||
<ChevronDownIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
@@ -128,5 +127,5 @@ export function EditMembershipRole({
|
||||
);
|
||||
}
|
||||
|
||||
return <Badge size="tiny" type="gray" role="badge-role" text={capitalizeFirstLetter(memberRole)} />;
|
||||
return <Badge size="tiny" type="gray" role="badge-role" text={memberRole} className="capitalize" />;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { convertDateTimeStringShort } from "@/lib/time";
|
||||
import { capitalizeFirstLetter } from "@/lib/utils/strings";
|
||||
import { Label } from "@/modules/ui/components/label";
|
||||
import { Webhook } from "@prisma/client";
|
||||
import { TFnType, useTranslate } from "@tolgee/react";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { convertDateTimeStringShort } from "@/lib/time";
|
||||
import { Label } from "@/modules/ui/components/label";
|
||||
|
||||
interface ActivityTabProps {
|
||||
webhook: Webhook;
|
||||
@@ -50,8 +49,8 @@ export const WebhookOverviewTab = ({ webhook, surveys }: ActivityTabProps) => {
|
||||
<Label className="text-slate-500">
|
||||
{t("environments.integrations.webhooks.created_by_third_party")}
|
||||
</Label>
|
||||
<p className="text-sm text-slate-900">
|
||||
{webhook.source === "user" ? "No" : capitalizeFirstLetter(webhook.source)}
|
||||
<p className="text-sm capitalize text-slate-900">
|
||||
{webhook.source === "user" ? "No" : webhook.source}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { timeSince } from "@/lib/time";
|
||||
import { capitalizeFirstLetter } from "@/lib/utils/strings";
|
||||
import { Badge } from "@/modules/ui/components/badge";
|
||||
import { Webhook } from "@prisma/client";
|
||||
import { TFnType, useTranslate } from "@tolgee/react";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { TUserLocale } from "@formbricks/types/user";
|
||||
import { timeSince } from "@/lib/time";
|
||||
import { Badge } from "@/modules/ui/components/badge";
|
||||
|
||||
const renderSelectedSurveysText = (webhook: Webhook, allSurveys: TSurvey[]) => {
|
||||
if (webhook.surveyIds.length === 0) {
|
||||
@@ -82,7 +81,7 @@ export const WebhookRowData = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-1 my-auto text-center text-sm text-slate-800">
|
||||
<Badge type="gray" size="tiny" text={capitalizeFirstLetter(webhook.source) || t("common.user")} />
|
||||
<Badge type="gray" size="tiny" text={webhook.source || t("common.user")} className="capitalize" />
|
||||
</div>
|
||||
<div className="col-span-4 my-auto text-center text-sm text-slate-800">
|
||||
{renderSelectedSurveysText(webhook, surveys)}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { getActiveInactiveSurveysAction } from "@/modules/projects/settings/(setup)/app-connection/actions";
|
||||
import { createActionClassAction } from "@/modules/survey/editor/actions";
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import toast from "react-hot-toast";
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { TActionClass } from "@formbricks/types/action-classes";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { getActiveInactiveSurveysAction } from "@/modules/projects/settings/(setup)/app-connection/actions";
|
||||
import { createActionClassAction } from "@/modules/survey/editor/actions";
|
||||
import { ActionActivityTab } from "./ActionActivityTab";
|
||||
|
||||
// Mock dependencies
|
||||
@@ -51,10 +51,6 @@ vi.mock("@/lib/utils/helper", () => ({
|
||||
getFormattedErrorMessage: (error: any) => `Formatted error: ${error?.message || "Unknown error"}`,
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/utils/strings", () => ({
|
||||
capitalizeFirstLetter: (str: string) => str.charAt(0).toUpperCase() + str.slice(1),
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/survey/editor/actions", () => ({
|
||||
createActionClassAction: vi.fn(),
|
||||
}));
|
||||
@@ -209,7 +205,7 @@ describe("ActionActivityTab", () => {
|
||||
expect(screen.getByText(`formatted-${mockActionClass.createdAt.toString()}`)).toBeInTheDocument(); // Created on
|
||||
expect(screen.getByText(`formatted-${mockActionClass.updatedAt.toString()}`)).toBeInTheDocument(); // Last updated
|
||||
expect(screen.getByText("NoCodeIcon")).toBeInTheDocument(); // Type icon
|
||||
expect(screen.getByText("NoCode")).toBeInTheDocument(); // Type text
|
||||
expect(screen.getByText("noCode")).toBeInTheDocument(); // Type text (now lowercase, capitalized via CSS)
|
||||
expect(screen.getByText("Development")).toBeInTheDocument(); // Environment
|
||||
expect(screen.getByText("Copy to Production")).toBeInTheDocument(); // Copy button text
|
||||
});
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { TActionClass, TActionClassInput, TActionClassInputCode } from "@formbricks/types/action-classes";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
import { convertDateTimeStringShort } from "@/lib/time";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { capitalizeFirstLetter } from "@/lib/utils/strings";
|
||||
import { getActiveInactiveSurveysAction } from "@/modules/projects/settings/(setup)/app-connection/actions";
|
||||
import { ACTION_TYPE_ICON_LOOKUP } from "@/modules/projects/settings/(setup)/app-connection/utils";
|
||||
import { createActionClassAction } from "@/modules/survey/editor/actions";
|
||||
@@ -10,11 +14,6 @@ import { Button } from "@/modules/ui/components/button";
|
||||
import { ErrorComponent } from "@/modules/ui/components/error-component";
|
||||
import { Label } from "@/modules/ui/components/label";
|
||||
import { LoadingSpinner } from "@/modules/ui/components/loading-spinner";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { TActionClass, TActionClassInput, TActionClassInputCode } from "@formbricks/types/action-classes";
|
||||
import { TEnvironment } from "@formbricks/types/environment";
|
||||
|
||||
interface ActivityTabProps {
|
||||
actionClass: TActionClass;
|
||||
@@ -152,7 +151,7 @@ export const ActionActivityTab = ({
|
||||
<Label className="block text-xs font-normal text-slate-500">Type</Label>
|
||||
<div className="mt-1 flex items-center">
|
||||
<div className="mr-1.5 h-4 w-4 text-slate-600">{ACTION_TYPE_ICON_LOOKUP[actionClass.type]}</div>
|
||||
<p className="text-sm text-slate-700">{capitalizeFirstLetter(actionClass.type)}</p>
|
||||
<p className="text-sm capitalize text-slate-700">{actionClass.type}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="">
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { Column } from "@tanstack/react-table";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import { EllipsisVerticalIcon, EyeOffIcon, SettingsIcon } from "lucide-react";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/modules/ui/components/dropdown-menu";
|
||||
import { Column } from "@tanstack/react-table";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import { EllipsisVerticalIcon, EyeOffIcon, SettingsIcon } from "lucide-react";
|
||||
|
||||
interface ColumnSettingsDropdownProps<T> {
|
||||
column: Column<T>;
|
||||
@@ -29,7 +29,6 @@ export const ColumnSettingsDropdown = <T,>({
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
className="capitalize"
|
||||
onClick={() => {
|
||||
column.toggleVisibility(false);
|
||||
}}
|
||||
@@ -39,7 +38,6 @@ export const ColumnSettingsDropdown = <T,>({
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="capitalize"
|
||||
onClick={() => setIsTableSettingsModalOpen(true)}
|
||||
icon={<SettingsIcon className="h-4 w-4" />}>
|
||||
<div className="flex items-center space-x-2">
|
||||
|
||||
@@ -64,6 +64,7 @@ describe("SelectedRowSettings", () => {
|
||||
deleteAction={deleteAction}
|
||||
downloadRowsAction={downloadRowsAction}
|
||||
type="contact"
|
||||
isQuotasAllowed={false}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText("2 common.contacts common.selected")).toBeInTheDocument();
|
||||
@@ -82,6 +83,7 @@ describe("SelectedRowSettings", () => {
|
||||
updateRowList={updateRowList}
|
||||
deleteAction={deleteAction}
|
||||
type="response"
|
||||
isQuotasAllowed={false}
|
||||
/>
|
||||
);
|
||||
expect(screen.queryByText("common.download")).toBeNull();
|
||||
@@ -95,6 +97,7 @@ describe("SelectedRowSettings", () => {
|
||||
deleteAction={deleteAction}
|
||||
downloadRowsAction={downloadRowsAction}
|
||||
type="response"
|
||||
isQuotasAllowed={false}
|
||||
/>
|
||||
);
|
||||
fireEvent.click(screen.getByText("common.download"));
|
||||
@@ -115,6 +118,7 @@ describe("SelectedRowSettings", () => {
|
||||
deleteAction={deleteAction}
|
||||
downloadRowsAction={downloadRowsAction}
|
||||
type="contact"
|
||||
isQuotasAllowed={false}
|
||||
/>
|
||||
);
|
||||
// open delete dialog
|
||||
@@ -136,6 +140,7 @@ describe("SelectedRowSettings", () => {
|
||||
deleteAction={deleteAction}
|
||||
downloadRowsAction={downloadRowsAction}
|
||||
type="response"
|
||||
isQuotasAllowed={false}
|
||||
/>
|
||||
);
|
||||
// open delete menu (trigger button)
|
||||
@@ -156,6 +161,7 @@ describe("SelectedRowSettings", () => {
|
||||
deleteAction={deleteAction}
|
||||
downloadRowsAction={downloadRowsAction}
|
||||
type="response"
|
||||
isQuotasAllowed={false}
|
||||
/>
|
||||
);
|
||||
// open delete dialog
|
||||
@@ -177,6 +183,7 @@ describe("SelectedRowSettings", () => {
|
||||
deleteAction={deleteAction}
|
||||
downloadRowsAction={downloadRowsAction}
|
||||
type="contact"
|
||||
isQuotasAllowed={false}
|
||||
/>
|
||||
);
|
||||
// open delete menu (trigger button)
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { capitalizeFirstLetter } from "@/lib/utils/strings";
|
||||
import { Table } from "@tanstack/react-table";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import { ArrowDownToLineIcon, Loader2Icon, Trash2Icon } from "lucide-react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { TResponseWithQuotas } from "@formbricks/types/responses";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { DecrementQuotasCheckbox } from "@/modules/ui/components/decrement-quotas-checkbox";
|
||||
import { DeleteDialog } from "@/modules/ui/components/delete-dialog";
|
||||
@@ -11,12 +16,6 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/modules/ui/components/dropdown-menu";
|
||||
import { cn } from "@/modules/ui/lib/utils";
|
||||
import { Table } from "@tanstack/react-table";
|
||||
import { useTranslate } from "@tolgee/react";
|
||||
import { ArrowDownToLineIcon, Loader2Icon, Trash2Icon } from "lucide-react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { TResponseWithQuotas } from "@formbricks/types/responses";
|
||||
|
||||
interface SelectedRowSettingsProps<T> {
|
||||
table: Table<T>;
|
||||
@@ -75,14 +74,16 @@ export const SelectedRowSettings = <T,>({
|
||||
|
||||
// Update the row list UI
|
||||
updateRowList(rowsToBeDeleted);
|
||||
toast.success(t("common.table_items_deleted_successfully", { type: capitalizeFirstLetter(type) }));
|
||||
const capitalizedType = type.charAt(0).toUpperCase() + type.slice(1);
|
||||
toast.success(t("common.table_items_deleted_successfully", { type: capitalizedType }));
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
toast.error(error.message);
|
||||
} else {
|
||||
const capitalizedType = type.charAt(0).toUpperCase() + type.slice(1);
|
||||
toast.error(
|
||||
t("common.an_unknown_error_occurred_while_deleting_table_items", {
|
||||
type: capitalizeFirstLetter(type),
|
||||
type: capitalizedType,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ describe("PageHeader", () => {
|
||||
test("renders page title correctly", () => {
|
||||
render(<PageHeader pageTitle="Dashboard" />);
|
||||
expect(screen.getByText("Dashboard")).toBeInTheDocument();
|
||||
expect(screen.getByText("Dashboard")).toHaveClass("text-3xl font-bold text-slate-800 capitalize");
|
||||
expect(screen.getByText("Dashboard")).toHaveClass("text-3xl font-bold text-slate-800");
|
||||
});
|
||||
|
||||
test("renders with CTA", () => {
|
||||
|
||||
@@ -10,7 +10,7 @@ export const PageHeader = ({ cta, pageTitle, children }: PageHeaderProps) => {
|
||||
return (
|
||||
<div className="border-b border-slate-200">
|
||||
<div className="flex items-center justify-between space-x-4 pb-4">
|
||||
<h1 className={cn("text-3xl font-bold capitalize text-slate-800")}>{pageTitle}</h1>
|
||||
<h1 className={cn("text-3xl font-bold text-slate-800")}>{pageTitle}</h1>
|
||||
{cta}
|
||||
</div>
|
||||
{children}
|
||||
|
||||
Reference in New Issue
Block a user