refactor: org settings (#11682)
parent
226ac889c7
commit
ebc70feef0
|
@ -45,7 +45,7 @@ const BillingView = () => {
|
|||
return (
|
||||
<>
|
||||
<Meta title={t("billing")} description={t("manage_billing_description")} borderInShellHeader={true} />
|
||||
<div className="border-subtle space-y-6 rounded-b-xl border border-t-0 px-6 py-8 text-sm sm:space-y-8">
|
||||
<div className="border-subtle space-y-6 rounded-b-lg border border-t-0 px-6 py-8 text-sm sm:space-y-8">
|
||||
<CtaRow title={t("view_and_manage_billing_details")} description={t("view_and_edit_billing_details")}>
|
||||
<Button color="primary" href={billingHref} target="_blank" EndIcon={ExternalLink}>
|
||||
{t("billing_portal")}
|
||||
|
|
|
@ -25,7 +25,7 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
|||
return (
|
||||
<SkeletonContainer>
|
||||
<Meta title={title} description={description} borderInShellHeader={true} />
|
||||
<div className="divide-subtle border-subtle space-y-6 rounded-b-xl border border-t-0 px-6 py-4">
|
||||
<div className="divide-subtle border-subtle space-y-6 rounded-b-lg border border-t-0 px-6 py-4">
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
</div>
|
||||
|
@ -79,7 +79,7 @@ const ApiKeysView = () => {
|
|||
<div>
|
||||
{data?.length ? (
|
||||
<>
|
||||
<div className="border-subtle rounded-b-md border border-t-0">
|
||||
<div className="border-subtle rounded-b-lg border border-t-0">
|
||||
{data.map((apiKey, index) => (
|
||||
<ApiKeyListItem
|
||||
key={apiKey.id}
|
||||
|
@ -98,7 +98,7 @@ const ApiKeysView = () => {
|
|||
Icon={LinkIcon}
|
||||
headline={t("create_first_api_key")}
|
||||
description={t("create_first_api_key_description", { appName: APP_NAME })}
|
||||
className="rounded-b-md rounded-t-none border-t-0"
|
||||
className="rounded-b-lg rounded-t-none border-t-0"
|
||||
buttonRaw={<NewApiKeyButton />}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -6,8 +6,8 @@ import { BookerLayoutSelector } from "@calcom/features/settings/BookerLayoutSele
|
|||
import SectionBottomActions from "@calcom/features/settings/SectionBottomActions";
|
||||
import ThemeLabel from "@calcom/features/settings/ThemeLabel";
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { DEFAULT_LIGHT_BRAND_COLOR, DEFAULT_DARK_BRAND_COLOR } from "@calcom/lib/constants";
|
||||
import { checkWCAGContrastColor } from "@calcom/lib/getBrandColours";
|
||||
import { useHasPaidPlan } from "@calcom/lib/hooks/useHasPaidPlan";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
@ -35,7 +35,10 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
|||
return (
|
||||
<SkeletonContainer>
|
||||
<Meta title={title} description={description} borderInShellHeader={false} />
|
||||
<div className="border-subtle mt-6 space-y-6 rounded-t-xl border border-b-0 px-4 py-6 sm:px-6">
|
||||
<div className="border-subtle mt-6 flex items-center rounded-t-xl border p-6 text-sm">
|
||||
<SkeletonText className="h-8 w-1/3" />
|
||||
</div>
|
||||
<div className="border-subtle space-y-6 border-x px-4 py-6 sm:px-6">
|
||||
<div className="flex items-center justify-center">
|
||||
<SkeletonButton className="mr-6 h-32 w-48 rounded-md p-5" />
|
||||
<SkeletonButton className="mr-6 h-32 w-48 rounded-md p-5" />
|
||||
|
@ -48,7 +51,7 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
|||
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
</div>
|
||||
<div className="rounded-b-xl">
|
||||
<div className="rounded-b-lg">
|
||||
<SectionBottomActions align="end">
|
||||
<SkeletonButton className="mr-6 h-8 w-20 rounded-md p-5" />
|
||||
</SectionBottomActions>
|
||||
|
@ -57,9 +60,6 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
|||
);
|
||||
};
|
||||
|
||||
const DEFAULT_LIGHT_BRAND_COLOR = "#292929";
|
||||
const DEFAULT_DARK_BRAND_COLOR = "#fafafa";
|
||||
|
||||
const AppearanceView = ({
|
||||
user,
|
||||
hasPaidPlan,
|
||||
|
@ -137,7 +137,7 @@ const AppearanceView = ({
|
|||
return (
|
||||
<div>
|
||||
<Meta title={t("appearance")} description={t("appearance_description")} borderInShellHeader={false} />
|
||||
<div className="border-subtle mt-6 flex items-center rounded-t-xl border p-6 text-sm">
|
||||
<div className="border-subtle mt-6 flex items-center rounded-t-lg border p-6 text-sm">
|
||||
<div>
|
||||
<p className="text-default text-base font-semibold">{t("theme")}</p>
|
||||
<p className="text-default">{t("theme_applies_note")}</p>
|
||||
|
@ -149,13 +149,13 @@ const AppearanceView = ({
|
|||
mutation.mutate({
|
||||
// Radio values don't support null as values, therefore we convert an empty string
|
||||
// back to null here.
|
||||
theme: values.theme || null,
|
||||
theme: values.theme ?? null,
|
||||
});
|
||||
}}>
|
||||
<div className="border-subtle flex flex-col justify-between border-x px-6 py-8 sm:flex-row">
|
||||
<ThemeLabel
|
||||
variant="system"
|
||||
value={null}
|
||||
value={undefined}
|
||||
label={t("theme_system")}
|
||||
defaultChecked={user.theme === null}
|
||||
register={userThemeFormMethods.register}
|
||||
|
@ -226,11 +226,7 @@ const AppearanceView = ({
|
|||
});
|
||||
}
|
||||
}}
|
||||
childrenClassName="lg:ml-0"
|
||||
switchContainerClassName={classNames(
|
||||
"py-6 px-4 sm:px-6 border-subtle rounded-xl border",
|
||||
isCustomBrandColorChecked && "rounded-b-none"
|
||||
)}>
|
||||
childrenClassName="lg:ml-0">
|
||||
<div className="border-subtle flex flex-col gap-6 border-x p-6">
|
||||
<Controller
|
||||
name="brandColor"
|
||||
|
@ -241,7 +237,7 @@ const AppearanceView = ({
|
|||
<p className="text-default mb-2 block text-sm font-medium">{t("light_brand_color")}</p>
|
||||
<ColorPicker
|
||||
defaultValue={user.brandColor}
|
||||
resetDefaultValue="#292929"
|
||||
resetDefaultValue={DEFAULT_LIGHT_BRAND_COLOR}
|
||||
onChange={(value) => {
|
||||
try {
|
||||
checkWCAGContrastColor("#ffffff", value);
|
||||
|
@ -273,7 +269,7 @@ const AppearanceView = ({
|
|||
<p className="text-default mb-2 block text-sm font-medium">{t("dark_brand_color")}</p>
|
||||
<ColorPicker
|
||||
defaultValue={user.darkBrandColor}
|
||||
resetDefaultValue="#fafafa"
|
||||
resetDefaultValue={DEFAULT_DARK_BRAND_COLOR}
|
||||
onChange={(value) => {
|
||||
try {
|
||||
checkWCAGContrastColor("#101010", value);
|
||||
|
@ -328,7 +324,7 @@ const AppearanceView = ({
|
|||
setHideBrandingValue(checked);
|
||||
mutation.mutate({ hideBranding: checked });
|
||||
}}
|
||||
switchContainerClassName="border-subtle mt-6 rounded-xl border py-6 px-4 sm:px-6"
|
||||
switchContainerClassName="mt-6"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -35,7 +35,7 @@ import PageWrapper from "@components/PageWrapper";
|
|||
const SkeletonLoader = () => {
|
||||
return (
|
||||
<SkeletonContainer>
|
||||
<div className="border-subtle mt-8 space-y-6 rounded-xl border px-4 py-6 sm:px-6">
|
||||
<div className="border-subtle mt-8 space-y-6 rounded-lg border px-4 py-6 sm:px-6">
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
|
@ -109,11 +109,11 @@ const CalendarsView = () => {
|
|||
selectedDestinationCalendarOption?.externalId === query?.data?.destinationCalendar?.externalId;
|
||||
return data.connectedCalendars.length ? (
|
||||
<div>
|
||||
<div className="border-subtle mt-8 rounded-t-xl border px-4 py-6 sm:px-6">
|
||||
<div className="border-subtle mt-8 rounded-t-lg border px-4 py-6 sm:px-6">
|
||||
<h2 className="text-emphasis mb-1 text-base font-bold leading-5 tracking-wide">
|
||||
{t("add_to_calendar")}
|
||||
</h2>
|
||||
<p className="text-default text-sm">{t("add_to_calendar_description")}</p>
|
||||
<p className="text-default text-sm leading-tight">{t("add_to_calendar_description")}</p>
|
||||
</div>
|
||||
<div className="border-subtle flex w-full flex-col space-y-3 border border-x border-y-0 px-4 py-6 sm:px-6">
|
||||
<DestinationCalendarSelector
|
||||
|
@ -137,15 +137,15 @@ const CalendarsView = () => {
|
|||
</Button>
|
||||
</SectionBottomActions>
|
||||
|
||||
<div className="border-subtle mt-8 rounded-t-xl border px-4 py-6 sm:px-6">
|
||||
<div className="border-subtle mt-8 rounded-t-lg border px-4 py-6 sm:px-6">
|
||||
<h4 className="text-emphasis text-base font-semibold leading-5">
|
||||
{t("check_for_conflicts")}
|
||||
</h4>
|
||||
<p className="text-default pb-2 text-sm leading-5">{t("select_calendars")}</p>
|
||||
<p className="text-default text-sm leading-tight">{t("select_calendars")}</p>
|
||||
</div>
|
||||
|
||||
<List
|
||||
className="border-subtle flex flex-col gap-6 rounded-b-xl border border-t-0 p-6"
|
||||
className="border-subtle flex flex-col gap-6 rounded-b-lg border border-t-0 p-6"
|
||||
noBorderTreatment>
|
||||
{data.connectedCalendars.map((item) => (
|
||||
<Fragment key={item.credentialId}>
|
||||
|
@ -173,7 +173,7 @@ const CalendarsView = () => {
|
|||
/>
|
||||
)}
|
||||
{item?.error === undefined && item.calendars && (
|
||||
<ListItem className="flex-col rounded-md">
|
||||
<ListItem className="flex-col rounded-lg">
|
||||
<div className="flex w-full flex-1 items-center space-x-3 p-4 rtl:space-x-reverse">
|
||||
{
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
|
|
|
@ -16,7 +16,7 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
|||
return (
|
||||
<SkeletonContainer>
|
||||
<Meta title={title} description={description} borderInShellHeader={true} />
|
||||
<div className="divide-subtle border-subtle space-y-6 rounded-b-xl border border-t-0 px-6 py-4">
|
||||
<div className="divide-subtle border-subtle space-y-6 rounded-b-lg border border-t-0 px-6 py-4">
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
</div>
|
||||
|
@ -100,7 +100,7 @@ const ConferencingLayout = () => {
|
|||
}
|
||||
return (
|
||||
<AppList
|
||||
listClassName="rounded-xl rounded-t-none border-t-0"
|
||||
listClassName="rounded-lg rounded-t-none border-t-0"
|
||||
handleDisconnect={handleDisconnect}
|
||||
data={data}
|
||||
variant="conferencing"
|
||||
|
|
|
@ -237,7 +237,7 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
|
|||
setIsAllowDynamicBookingChecked(checked);
|
||||
mutation.mutate({ allowDynamicBooking: checked });
|
||||
}}
|
||||
switchContainerClassName="border-subtle mt-6 rounded-xl border py-6 px-4 sm:px-6"
|
||||
switchContainerClassName="mt-6"
|
||||
/>
|
||||
|
||||
<SettingsToggle
|
||||
|
@ -250,7 +250,7 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
|
|||
setIsAllowSEOIndexingChecked(checked);
|
||||
mutation.mutate({ allowSEOIndexing: checked });
|
||||
}}
|
||||
switchContainerClassName="border-subtle mt-6 rounded-xl border py-6 px-4 sm:px-6"
|
||||
switchContainerClassName="mt-6"
|
||||
/>
|
||||
|
||||
<SettingsToggle
|
||||
|
@ -263,7 +263,7 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
|
|||
setIsReceiveMonthlyDigestEmailChecked(checked);
|
||||
mutation.mutate({ receiveMonthlyDigestEmail: checked });
|
||||
}}
|
||||
switchContainerClassName="border-subtle mt-6 rounded-xl border py-6 px-4 sm:px-6"
|
||||
switchContainerClassName="mt-6"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -50,7 +50,7 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
|||
return (
|
||||
<SkeletonContainer>
|
||||
<Meta title={title} description={description} borderInShellHeader={true} />
|
||||
<div className="border-subtle space-y-6 rounded-b-xl border border-t-0 px-4 py-8">
|
||||
<div className="border-subtle space-y-6 rounded-b-lg border border-t-0 px-4 py-8">
|
||||
<div className="flex items-center">
|
||||
<SkeletonAvatar className="me-4 mt-0 h-16 w-16 px-4" />
|
||||
<SkeletonButton className="h-6 w-32 rounded-md p-5" />
|
||||
|
@ -279,7 +279,7 @@ const ProfileView = () => {
|
|||
}
|
||||
/>
|
||||
|
||||
<div className="border-subtle mt-6 rounded-xl rounded-b-none border border-b-0 p-6">
|
||||
<div className="border-subtle mt-6 rounded-lg rounded-b-none border border-b-0 p-6">
|
||||
<Label className="text-base font-semibold text-red-700">{t("danger_zone")}</Label>
|
||||
<p className="text-subtle">{t("account_deletion_cannot_be_undone")}</p>
|
||||
</div>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import TeamBillingView from "@calcom/features/ee/teams/pages/team-billing-view";
|
||||
|
||||
import type { CalPageWrapper } from "@components/PageWrapper";
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
const Page = TeamBillingView as CalPageWrapper;
|
||||
import BillingPage from "../../settings/billing/index";
|
||||
|
||||
const Page = BillingPage as CalPageWrapper;
|
||||
Page.PageWrapper = PageWrapper;
|
||||
|
||||
export default Page;
|
||||
|
|
|
@ -66,8 +66,8 @@ const ProfileImpersonationView = ({ user }: { user: RouterOutputs["viewer"]["me"
|
|||
onCheckedChange={(checked) => {
|
||||
mutation.mutate({ disableImpersonation: !checked });
|
||||
}}
|
||||
switchContainerClassName="rounded-t-none border-t-0"
|
||||
disabled={mutation.isLoading}
|
||||
switchContainerClassName="py-6 px-4 sm:px-6 border-subtle rounded-b-xl border border-t-0"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -63,7 +63,7 @@ const TwoFactorAuthView = () => {
|
|||
{user?.twoFactorEnabled ? t("enabled") : t("disabled")}
|
||||
</Badge>
|
||||
}
|
||||
switchContainerClassName="border-subtle rounded-b-xl border border-t-0 px-5 py-6 sm:px-6"
|
||||
switchContainerClassName="rounded-t-none border-t-0"
|
||||
/>
|
||||
|
||||
<EnableTwoFactorModal
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import TeamBillingView from "@calcom/features/ee/teams/pages/team-billing-view";
|
||||
|
||||
import type { CalPageWrapper } from "@components/PageWrapper";
|
||||
import PageWrapper from "@components/PageWrapper";
|
||||
|
||||
const Page = TeamBillingView as CalPageWrapper;
|
||||
import BillingPage from "../../billing";
|
||||
|
||||
const Page = BillingPage as CalPageWrapper;
|
||||
Page.PageWrapper = PageWrapper;
|
||||
|
||||
export default Page;
|
||||
|
|
|
@ -290,6 +290,7 @@
|
|||
"where": "Where",
|
||||
"add_to_calendar": "Add to calendar",
|
||||
"add_to_calendar_description":"Select where to add events when you’re booked.",
|
||||
"add_events_to":"Add events to",
|
||||
"add_another_calendar": "Add another calendar",
|
||||
"other": "Other",
|
||||
"email_sign_in_subject": "Your sign-in link for {{appName}}",
|
||||
|
@ -459,6 +460,7 @@
|
|||
"no_event_types_have_been_setup": "This user hasn't set up any event types yet.",
|
||||
"edit_logo": "Edit logo",
|
||||
"upload_a_logo": "Upload a logo",
|
||||
"upload_logo": "Upload logo",
|
||||
"remove_logo": "Remove logo",
|
||||
"enable": "Enable",
|
||||
"code": "Code",
|
||||
|
@ -571,6 +573,7 @@
|
|||
"your_team_name": "Your team name",
|
||||
"team_updated_successfully": "Team updated successfully",
|
||||
"your_team_updated_successfully": "Your team has been updated successfully.",
|
||||
"your_org_updated_successfully": "Your Org has been updated successfully.",
|
||||
"about": "About",
|
||||
"team_description": "A few sentences about your team. This will appear on your team's url page.",
|
||||
"org_description": "A few sentences about your organization. This will appear on your organization's url page.",
|
||||
|
@ -2080,6 +2083,7 @@
|
|||
"allow": "Allow",
|
||||
"view_only_edit_availability_not_onboarded":"This user has not completed onboarding. You will not be able to set their availability until they have completed onboarding.",
|
||||
"view_only_edit_availability":"You are viewing this user's availability. You can only edit your own availability.",
|
||||
"you_can_override_calendar_in_advanced_tab":"You can override this on a per-event basis in Advanced settings in each event type.",
|
||||
"edit_users_availability":"Edit user's availability: {{username}}",
|
||||
"resend_invitation": "Resend invitation",
|
||||
"invitation_resent": "The invitation was resent.",
|
||||
|
|
|
@ -126,6 +126,7 @@ const DestinationCalendarSelector = ({
|
|||
|
||||
return (
|
||||
<div className="relative" title={`${t("create_events_on")}: ${selectedOption?.label || ""}`}>
|
||||
<p className="text-sm font-medium leading-none">{t("add_events_to")}</p>
|
||||
<Select
|
||||
name="primarySelectedCalendar"
|
||||
placeholder={
|
||||
|
@ -155,7 +156,7 @@ const DestinationCalendarSelector = ({
|
|||
}}
|
||||
isSearchable={false}
|
||||
className={classNames(
|
||||
"border-default mb-2 mt-1 block w-full min-w-0 flex-1 rounded-none rounded-r-sm text-sm"
|
||||
"border-default my-2 block w-full min-w-0 flex-1 rounded-none rounded-r-sm text-sm"
|
||||
)}
|
||||
onChange={(newValue) => {
|
||||
setSelectedOption(newValue);
|
||||
|
@ -176,6 +177,7 @@ const DestinationCalendarSelector = ({
|
|||
components={{ SingleValue: SingleValueComponent, Option: OptionComponent }}
|
||||
isMulti={false}
|
||||
/>
|
||||
<p className="text-sm leading-tight">{t("you_can_override_calendar_in_advanced_tab")}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
import { useRouter } from "next/navigation";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm, useFormContext } from "react-hook-form";
|
||||
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||
import SectionBottomActions from "@calcom/features/settings/SectionBottomActions";
|
||||
import ThemeLabel from "@calcom/features/settings/ThemeLabel";
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { DEFAULT_LIGHT_BRAND_COLOR, DEFAULT_DARK_BRAND_COLOR } from "@calcom/lib/constants";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { checkWCAGContrastColor } from "@calcom/lib/getBrandColours";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import {
|
||||
Button,
|
||||
ColorPicker,
|
||||
|
@ -14,17 +23,19 @@ import {
|
|||
SkeletonButton,
|
||||
SkeletonContainer,
|
||||
SkeletonText,
|
||||
SettingsToggle,
|
||||
Alert,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import ThemeLabel from "../../../../settings/ThemeLabel";
|
||||
import { getLayout } from "../../../../settings/layouts/SettingsLayout";
|
||||
|
||||
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
|
||||
return (
|
||||
<SkeletonContainer>
|
||||
<Meta title={title} description={description} />
|
||||
<div className="mb-8 mt-6 space-y-6">
|
||||
<div className="flex items-center">
|
||||
<Meta title={title} description={description} borderInShellHeader={false} />
|
||||
<div className="border-subtle mt-6 flex items-center rounded-t-xl border p-6 text-sm">
|
||||
<SkeletonText className="h-8 w-1/3" />
|
||||
</div>
|
||||
<div className="border-subtle space-y-6 border-x px-4 py-6 sm:px-6">
|
||||
<div className="flex items-center justify-center">
|
||||
<SkeletonButton className="mr-6 h-32 w-48 rounded-md p-5" />
|
||||
<SkeletonButton className="mr-6 h-32 w-48 rounded-md p-5" />
|
||||
<SkeletonButton className="mr-6 h-32 w-48 rounded-md p-5" />
|
||||
|
@ -35,144 +46,153 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
|||
</div>
|
||||
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
|
||||
<SkeletonButton className="mr-6 h-8 w-20 rounded-md p-5" />
|
||||
</div>
|
||||
<div className="rounded-b-xl">
|
||||
<SectionBottomActions align="end">
|
||||
<SkeletonButton className="mr-6 h-8 w-20 rounded-md p-5" />
|
||||
</SectionBottomActions>
|
||||
</div>
|
||||
</SkeletonContainer>
|
||||
);
|
||||
};
|
||||
|
||||
interface OrgAppearanceValues {
|
||||
hideBranding: boolean;
|
||||
hideBookATeamMember: boolean;
|
||||
type BrandColorsFormValues = {
|
||||
brandColor: string;
|
||||
darkBrandColor: string;
|
||||
theme: string | null | undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const OrgAppearanceView = () => {
|
||||
const OrgAppearanceView = ({
|
||||
currentOrg,
|
||||
isAdminOrOwner,
|
||||
}: {
|
||||
currentOrg: RouterOutputs["viewer"]["organizations"]["listCurrent"];
|
||||
isAdminOrOwner: boolean;
|
||||
}) => {
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
const utils = trpc.useContext();
|
||||
|
||||
const themeForm = useForm<{ theme: string | null | undefined }>({
|
||||
defaultValues: {
|
||||
theme: currentOrg?.theme,
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
formState: { isSubmitting: isOrgThemeSubmitting, isDirty: isOrgThemeDirty },
|
||||
reset: resetOrgThemeReset,
|
||||
} = themeForm;
|
||||
|
||||
const [hideBrandingValue, setHideBrandingValue] = useState(currentOrg?.hideBranding ?? false);
|
||||
|
||||
const brandColorsFormMethods = useForm<BrandColorsFormValues>({
|
||||
defaultValues: {
|
||||
brandColor: currentOrg?.brandColor || DEFAULT_LIGHT_BRAND_COLOR,
|
||||
darkBrandColor: currentOrg?.darkBrandColor || DEFAULT_DARK_BRAND_COLOR,
|
||||
},
|
||||
});
|
||||
|
||||
const mutation = trpc.viewer.organizations.update.useMutation({
|
||||
onError: (err) => {
|
||||
showToast(err.message, "error");
|
||||
},
|
||||
async onSuccess() {
|
||||
async onSuccess(res) {
|
||||
await utils.viewer.teams.get.invalidate();
|
||||
await utils.viewer.organizations.listCurrent.invalidate();
|
||||
|
||||
showToast(t("your_team_updated_successfully"), "success");
|
||||
brandColorsFormMethods.reset({
|
||||
brandColor: res.data.brandColor as string,
|
||||
darkBrandColor: res.data.darkBrandColor as string,
|
||||
});
|
||||
resetOrgThemeReset({ theme: res.data.theme as string | undefined });
|
||||
},
|
||||
});
|
||||
|
||||
const { data: currentOrg, isLoading } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
|
||||
onError: () => {
|
||||
router.push("/settings");
|
||||
},
|
||||
});
|
||||
const onBrandColorsFormSubmit = (values: BrandColorsFormValues) => {
|
||||
mutation.mutate(values);
|
||||
};
|
||||
|
||||
const form = useForm<OrgAppearanceValues>({
|
||||
defaultValues: {
|
||||
theme: currentOrg?.theme,
|
||||
brandColor: currentOrg?.brandColor,
|
||||
darkBrandColor: currentOrg?.darkBrandColor,
|
||||
hideBranding: currentOrg?.hideBranding,
|
||||
},
|
||||
});
|
||||
|
||||
const isAdmin =
|
||||
currentOrg &&
|
||||
(currentOrg.user.role === MembershipRole.OWNER || currentOrg.user.role === MembershipRole.ADMIN);
|
||||
|
||||
if (isLoading) {
|
||||
return <SkeletonLoader title={t("booking_appearance")} description={t("appearance_team_description")} />;
|
||||
}
|
||||
return (
|
||||
<LicenseRequired>
|
||||
<Meta title={t("booking_appearance")} description={t("appearance_team_description")} />
|
||||
{isAdmin ? (
|
||||
<Form
|
||||
form={form}
|
||||
handleSubmit={(values) => {
|
||||
mutation.mutate({
|
||||
...values,
|
||||
theme: values.theme || null,
|
||||
});
|
||||
}}>
|
||||
<div className="mb-6 flex items-center text-sm">
|
||||
<div>
|
||||
<p className="font-semibold">{t("theme")}</p>
|
||||
<p className="text-default">{t("theme_applies_note")}</p>
|
||||
<Meta
|
||||
title={t("appearance")}
|
||||
description={t("appearance_team_description")}
|
||||
borderInShellHeader={false}
|
||||
/>
|
||||
{isAdminOrOwner ? (
|
||||
<div>
|
||||
<Form
|
||||
form={themeForm}
|
||||
handleSubmit={(value) => {
|
||||
mutation.mutate({
|
||||
theme: value.theme ?? null,
|
||||
});
|
||||
}}>
|
||||
<div className="border-subtle mt-6 flex items-center rounded-t-xl border p-6 text-sm">
|
||||
<div>
|
||||
<p className="text-default text-base font-semibold">{t("theme")}</p>
|
||||
<p className="text-default">{t("theme_applies_note")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col justify-between sm:flex-row">
|
||||
<ThemeLabel
|
||||
variant="system"
|
||||
value={null}
|
||||
label={t("theme_system")}
|
||||
defaultChecked={currentOrg.theme === null}
|
||||
register={form.register}
|
||||
/>
|
||||
<ThemeLabel
|
||||
variant="light"
|
||||
value="light"
|
||||
label={t("theme_light")}
|
||||
defaultChecked={currentOrg.theme === "light"}
|
||||
register={form.register}
|
||||
/>
|
||||
<ThemeLabel
|
||||
variant="dark"
|
||||
value="dark"
|
||||
label={t("theme_dark")}
|
||||
defaultChecked={currentOrg.theme === "dark"}
|
||||
register={form.register}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<hr className="border-subtle my-8" />
|
||||
<div className="text-default mb-6 flex items-center text-sm">
|
||||
<div>
|
||||
<p className="font-semibold">{t("custom_brand_colors")}</p>
|
||||
<p className="mt-0.5 leading-5">{t("customize_your_brand_colors")}</p>
|
||||
<div className="border-subtle flex flex-col justify-between border-x px-6 py-8 sm:flex-row">
|
||||
<ThemeLabel
|
||||
variant="system"
|
||||
value={undefined}
|
||||
label={t("theme_system")}
|
||||
defaultChecked={currentOrg.theme === null}
|
||||
register={themeForm.register}
|
||||
/>
|
||||
<ThemeLabel
|
||||
variant="light"
|
||||
value="light"
|
||||
label={t("light")}
|
||||
defaultChecked={currentOrg.theme === "light"}
|
||||
register={themeForm.register}
|
||||
/>
|
||||
<ThemeLabel
|
||||
variant="dark"
|
||||
value="dark"
|
||||
label={t("dark")}
|
||||
defaultChecked={currentOrg.theme === "dark"}
|
||||
register={themeForm.register}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<SectionBottomActions className="mb-6" align="end">
|
||||
<Button
|
||||
disabled={isOrgThemeSubmitting || !isOrgThemeDirty}
|
||||
type="submit"
|
||||
data-testid="update-org-theme-btn"
|
||||
color="primary">
|
||||
{t("update")}
|
||||
</Button>
|
||||
</SectionBottomActions>
|
||||
</Form>
|
||||
|
||||
<div className="block justify-between sm:flex">
|
||||
<Controller
|
||||
name="brandColor"
|
||||
control={form.control}
|
||||
defaultValue={currentOrg.brandColor}
|
||||
render={() => (
|
||||
<div>
|
||||
<p className="text-emphasis mb-2 block text-sm font-medium">{t("light_brand_color")}</p>
|
||||
<ColorPicker
|
||||
defaultValue={currentOrg.brandColor || "#292929"}
|
||||
resetDefaultValue="#292929"
|
||||
onChange={(value) => form.setValue("brandColor", value, { shouldDirty: true })}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Form
|
||||
form={brandColorsFormMethods}
|
||||
handleSubmit={(values) => {
|
||||
onBrandColorsFormSubmit(values);
|
||||
}}>
|
||||
<BrandColorsForm
|
||||
onSubmit={onBrandColorsFormSubmit}
|
||||
orgBrandColor={currentOrg?.brandColor}
|
||||
orgDarkBrandColor={currentOrg?.darkBrandColor}
|
||||
/>
|
||||
<Controller
|
||||
name="darkBrandColor"
|
||||
control={form.control}
|
||||
defaultValue={currentOrg.darkBrandColor}
|
||||
render={() => (
|
||||
<div className="mt-6 sm:mt-0">
|
||||
<p className="text-emphasis mb-2 block text-sm font-medium">{t("dark_brand_color")}</p>
|
||||
<ColorPicker
|
||||
defaultValue={currentOrg.darkBrandColor || "#fafafa"}
|
||||
resetDefaultValue="#fafafa"
|
||||
onChange={(value) => form.setValue("darkBrandColor", value, { shouldDirty: true })}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Button color="primary" className="mt-8" type="submit" loading={mutation.isLoading}>
|
||||
{t("update")}
|
||||
</Button>
|
||||
</Form>
|
||||
</Form>
|
||||
|
||||
<SettingsToggle
|
||||
toggleSwitchAtTheEnd={true}
|
||||
title={t("disable_cal_branding", { appName: APP_NAME })}
|
||||
disabled={mutation?.isLoading}
|
||||
description={t("removes_cal_branding", { appName: APP_NAME })}
|
||||
checked={hideBrandingValue}
|
||||
onCheckedChange={(checked) => {
|
||||
setHideBrandingValue(checked);
|
||||
mutation.mutate({ hideBranding: checked });
|
||||
}}
|
||||
switchContainerClassName="border-subtle mt-6 rounded-xl border py-6 px-4 sm:px-6"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="border-subtle rounded-md border p-5">
|
||||
<span className="text-default text-sm">{t("only_owner_change")}</span>
|
||||
|
@ -182,6 +202,148 @@ const OrgAppearanceView = () => {
|
|||
);
|
||||
};
|
||||
|
||||
OrgAppearanceView.getLayout = getLayout;
|
||||
const BrandColorsForm = ({
|
||||
onSubmit,
|
||||
orgBrandColor,
|
||||
orgDarkBrandColor,
|
||||
}: {
|
||||
onSubmit: (values: BrandColorsFormValues) => void;
|
||||
orgBrandColor: string | undefined;
|
||||
orgDarkBrandColor: string | undefined;
|
||||
}) => {
|
||||
const { t } = useLocale();
|
||||
const brandColorsFormMethods = useFormContext();
|
||||
const {
|
||||
formState: { isSubmitting: isBrandColorsFormSubmitting, isDirty: isBrandColorsFormDirty },
|
||||
handleSubmit,
|
||||
} = brandColorsFormMethods;
|
||||
|
||||
export default OrgAppearanceView;
|
||||
const [isCustomBrandColorChecked, setIsCustomBrandColorChecked] = useState(
|
||||
orgBrandColor !== DEFAULT_LIGHT_BRAND_COLOR || orgDarkBrandColor !== DEFAULT_DARK_BRAND_COLOR
|
||||
);
|
||||
const [darkModeError, setDarkModeError] = useState(false);
|
||||
const [lightModeError, setLightModeError] = useState(false);
|
||||
return (
|
||||
<div className="mt-6">
|
||||
<SettingsToggle
|
||||
toggleSwitchAtTheEnd={true}
|
||||
title={t("custom_brand_colors")}
|
||||
description={t("customize_your_brand_colors")}
|
||||
checked={isCustomBrandColorChecked}
|
||||
onCheckedChange={(checked) => {
|
||||
setIsCustomBrandColorChecked(checked);
|
||||
if (!checked) {
|
||||
onSubmit({
|
||||
brandColor: DEFAULT_LIGHT_BRAND_COLOR,
|
||||
darkBrandColor: DEFAULT_DARK_BRAND_COLOR,
|
||||
});
|
||||
}
|
||||
}}
|
||||
childrenClassName="lg:ml-0"
|
||||
switchContainerClassName={classNames(
|
||||
"py-6 px-4 sm:px-6 border-subtle rounded-xl border",
|
||||
isCustomBrandColorChecked && "rounded-b-none"
|
||||
)}>
|
||||
<div className="border-subtle flex flex-col gap-6 border-x p-6">
|
||||
<Controller
|
||||
name="brandColor"
|
||||
control={brandColorsFormMethods.control}
|
||||
defaultValue={orgBrandColor}
|
||||
render={() => (
|
||||
<div>
|
||||
<p className="text-default mb-2 block text-sm font-medium">{t("light_brand_color")}</p>
|
||||
<ColorPicker
|
||||
defaultValue={orgBrandColor || DEFAULT_LIGHT_BRAND_COLOR}
|
||||
resetDefaultValue={DEFAULT_LIGHT_BRAND_COLOR}
|
||||
onChange={(value) => {
|
||||
try {
|
||||
checkWCAGContrastColor("#ffffff", value);
|
||||
setLightModeError(false);
|
||||
brandColorsFormMethods.setValue("brandColor", value, { shouldDirty: true });
|
||||
} catch (err) {
|
||||
setLightModeError(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{lightModeError ? (
|
||||
<div className="mt-4">
|
||||
<Alert
|
||||
severity="warning"
|
||||
message="Light Theme color doesn't pass contrast check. We recommend you change this colour so your buttons will be more visible."
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="darkBrandColor"
|
||||
control={brandColorsFormMethods.control}
|
||||
defaultValue={orgDarkBrandColor}
|
||||
render={() => (
|
||||
<div className="mt-6 sm:mt-0">
|
||||
<p className="text-default mb-2 block text-sm font-medium">{t("dark_brand_color")}</p>
|
||||
<ColorPicker
|
||||
defaultValue={orgDarkBrandColor || DEFAULT_DARK_BRAND_COLOR}
|
||||
resetDefaultValue={DEFAULT_DARK_BRAND_COLOR}
|
||||
onChange={(value) => {
|
||||
try {
|
||||
checkWCAGContrastColor("#101010", value);
|
||||
setDarkModeError(false);
|
||||
brandColorsFormMethods.setValue("darkBrandColor", value, { shouldDirty: true });
|
||||
} catch (err) {
|
||||
setDarkModeError(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{darkModeError ? (
|
||||
<div className="mt-4">
|
||||
<Alert
|
||||
severity="warning"
|
||||
message="Dark Theme color doesn't pass contrast check. We recommend you change this colour so your buttons will be more visible."
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<SectionBottomActions align="end">
|
||||
<Button
|
||||
disabled={isBrandColorsFormSubmitting || !isBrandColorsFormDirty}
|
||||
color="primary"
|
||||
type="submit">
|
||||
{t("update")}
|
||||
</Button>
|
||||
</SectionBottomActions>
|
||||
</SettingsToggle>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const OrgAppearanceViewWrapper = () => {
|
||||
const router = useRouter();
|
||||
const { t } = useLocale();
|
||||
const { data: currentOrg, isLoading } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
|
||||
onError: () => {
|
||||
router.push("/settings");
|
||||
},
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return <SkeletonLoader title={t("appearance")} description={t("appearance_team_description")} />;
|
||||
}
|
||||
|
||||
if (!currentOrg) return null;
|
||||
|
||||
const isAdminOrOwner =
|
||||
currentOrg &&
|
||||
(currentOrg.user.role === MembershipRole.OWNER || currentOrg.user.role === MembershipRole.ADMIN);
|
||||
|
||||
return <OrgAppearanceView currentOrg={currentOrg} isAdminOrOwner={isAdminOrOwner} />;
|
||||
};
|
||||
|
||||
OrgAppearanceViewWrapper.getLayout = getLayout;
|
||||
|
||||
export default OrgAppearanceViewWrapper;
|
||||
|
|
|
@ -2,6 +2,7 @@ import { useRouter } from "next/navigation";
|
|||
import { Controller, useForm } from "react-hook-form";
|
||||
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||
import SectionBottomActions from "@calcom/features/settings/SectionBottomActions";
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { nameOfDay } from "@calcom/lib/weekday";
|
||||
|
@ -24,7 +25,7 @@ import {
|
|||
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
|
||||
return (
|
||||
<SkeletonContainer>
|
||||
<Meta title={title} description={description} />
|
||||
<Meta title={title} description={description} borderInShellHeader={true} />
|
||||
<div className="mb-8 mt-6 space-y-6">
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
|
@ -131,68 +132,76 @@ const GeneralView = ({ currentOrg, isAdminOrOwner, localeProp }: GeneralViewProp
|
|||
weekStart: values.weekStart.value,
|
||||
});
|
||||
}}>
|
||||
<Meta title={t("general")} description={t("organization_general_description")} />
|
||||
|
||||
<Controller
|
||||
name="timeZone"
|
||||
control={formMethods.control}
|
||||
render={({ field: { value } }) => (
|
||||
<>
|
||||
<Label className="text-emphasis mt-8">
|
||||
<>{t("timezone")}</>
|
||||
</Label>
|
||||
<TimezoneSelect
|
||||
id="timezone"
|
||||
value={value}
|
||||
onChange={(event) => {
|
||||
if (event) formMethods.setValue("timeZone", event.value, { shouldDirty: true });
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Meta
|
||||
title={t("general")}
|
||||
description={t("organization_general_description")}
|
||||
borderInShellHeader={true}
|
||||
/>
|
||||
<Controller
|
||||
name="timeFormat"
|
||||
control={formMethods.control}
|
||||
render={({ field: { value } }) => (
|
||||
<>
|
||||
<Label className="text-emphasis mt-8">
|
||||
<>{t("time_format")}</>
|
||||
</Label>
|
||||
<Select
|
||||
value={value}
|
||||
options={timeFormatOptions}
|
||||
onChange={(event) => {
|
||||
if (event) formMethods.setValue("timeFormat", { ...event }, { shouldDirty: true });
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<div className="text-gray text-default mt-2 flex items-center text-sm">
|
||||
{t("timeformat_profile_hint")}
|
||||
<div className="border-subtle border-x border-y-0 px-4 py-8 sm:px-6">
|
||||
<Controller
|
||||
name="timeZone"
|
||||
control={formMethods.control}
|
||||
render={({ field: { value } }) => (
|
||||
<>
|
||||
<Label className="text-emphasis">
|
||||
<>{t("timezone")}</>
|
||||
</Label>
|
||||
<TimezoneSelect
|
||||
id="timezone"
|
||||
value={value}
|
||||
onChange={(event) => {
|
||||
if (event) formMethods.setValue("timeZone", event.value, { shouldDirty: true });
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="timeFormat"
|
||||
control={formMethods.control}
|
||||
render={({ field: { value } }) => (
|
||||
<>
|
||||
<Label className="text-emphasis mt-6">
|
||||
<>{t("time_format")}</>
|
||||
</Label>
|
||||
<Select
|
||||
value={value}
|
||||
options={timeFormatOptions}
|
||||
onChange={(event) => {
|
||||
if (event) formMethods.setValue("timeFormat", { ...event }, { shouldDirty: true });
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<div className="text-gray text-default mt-2 flex items-center text-sm">
|
||||
{t("timeformat_profile_hint")}
|
||||
</div>
|
||||
<Controller
|
||||
name="weekStart"
|
||||
control={formMethods.control}
|
||||
render={({ field: { value } }) => (
|
||||
<>
|
||||
<Label className="text-emphasis mt-6">
|
||||
<>{t("start_of_week")}</>
|
||||
</Label>
|
||||
<Select
|
||||
value={value}
|
||||
options={weekStartOptions}
|
||||
onChange={(event) => {
|
||||
if (event) formMethods.setValue("weekStart", { ...event }, { shouldDirty: true });
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Controller
|
||||
name="weekStart"
|
||||
control={formMethods.control}
|
||||
render={({ field: { value } }) => (
|
||||
<>
|
||||
<Label className="text-emphasis mt-8">
|
||||
<>{t("start_of_week")}</>
|
||||
</Label>
|
||||
<Select
|
||||
value={value}
|
||||
options={weekStartOptions}
|
||||
onChange={(event) => {
|
||||
if (event) formMethods.setValue("weekStart", { ...event }, { shouldDirty: true });
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<Button disabled={isDisabled} color="primary" type="submit" className="mt-8">
|
||||
<>{t("update")}</>
|
||||
</Button>
|
||||
|
||||
<SectionBottomActions align="end">
|
||||
<Button disabled={isDisabled} color="primary" type="submit">
|
||||
{t("update")}
|
||||
</Button>
|
||||
</SectionBottomActions>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@ import { z } from "zod";
|
|||
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||
import { subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import SectionBottomActions from "@calcom/features/settings/SectionBottomActions";
|
||||
import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { md } from "@calcom/lib/markdownIt";
|
||||
|
@ -25,6 +26,10 @@ import {
|
|||
TextField,
|
||||
Editor,
|
||||
LinkIconButton,
|
||||
SkeletonAvatar,
|
||||
SkeletonButton,
|
||||
SkeletonContainer,
|
||||
SkeletonText,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import { getLayout } from "../../../../settings/layouts/SettingsLayout";
|
||||
|
@ -32,191 +37,112 @@ import { useOrgBranding } from "../../../organizations/context/provider";
|
|||
|
||||
const orgProfileFormSchema = z.object({
|
||||
name: z.string(),
|
||||
logo: z.string(),
|
||||
logo: z.string().nullable(),
|
||||
bio: z.string(),
|
||||
});
|
||||
|
||||
type FormValues = {
|
||||
name: string;
|
||||
logo: string | null;
|
||||
bio: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
|
||||
return (
|
||||
<SkeletonContainer>
|
||||
<Meta title={title} description={description} borderInShellHeader={true} />
|
||||
<div className="border-subtle space-y-6 rounded-b-xl border border-t-0 px-4 py-8">
|
||||
<div className="flex items-center">
|
||||
<SkeletonAvatar className="me-4 mt-0 h-16 w-16 px-4" />
|
||||
<SkeletonButton className="h-6 w-32 rounded-md p-5" />
|
||||
</div>
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
|
||||
<SkeletonButton className="mr-6 h-8 w-20 rounded-md p-5" />
|
||||
</div>
|
||||
</SkeletonContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const OrgProfileView = () => {
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
const utils = trpc.useContext();
|
||||
const [firstRender, setFirstRender] = useState(true);
|
||||
|
||||
const orgBranding = useOrgBranding();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
document.body.focus();
|
||||
}, []);
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(orgProfileFormSchema),
|
||||
});
|
||||
|
||||
const mutation = trpc.viewer.organizations.update.useMutation({
|
||||
onError: (err) => {
|
||||
showToast(err.message, "error");
|
||||
},
|
||||
async onSuccess() {
|
||||
await utils.viewer.teams.get.invalidate();
|
||||
showToast(t("your_organization_updated_sucessfully"), "success");
|
||||
},
|
||||
});
|
||||
|
||||
const { data: currentOrganisation, isLoading } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
|
||||
onError: () => {
|
||||
router.push("/settings");
|
||||
},
|
||||
onSuccess: (org) => {
|
||||
if (org) {
|
||||
form.setValue("name", org.name || "");
|
||||
form.setValue("slug", org.slug || org.metadata?.requestedSlug || "");
|
||||
form.setValue("logo", org.logo || "");
|
||||
form.setValue("bio", org.bio || "");
|
||||
if (org.slug === null && (org?.metadata as Prisma.JsonObject)?.requestedSlug) {
|
||||
form.setValue("slug", ((org?.metadata as Prisma.JsonObject)?.requestedSlug as string) || "");
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (isLoading || !orgBranding || !currentOrganisation) {
|
||||
return <SkeletonLoader title={t("profile")} description={t("profile_org_description")} />;
|
||||
}
|
||||
|
||||
const isOrgAdminOrOwner =
|
||||
currentOrganisation &&
|
||||
(currentOrganisation.user.role === MembershipRole.OWNER ||
|
||||
currentOrganisation.user.role === MembershipRole.ADMIN);
|
||||
currentOrganisation.user.role === MembershipRole.OWNER ||
|
||||
currentOrganisation.user.role === MembershipRole.ADMIN;
|
||||
|
||||
const isBioEmpty =
|
||||
!currentOrganisation ||
|
||||
!currentOrganisation.bio ||
|
||||
!currentOrganisation.bio.replace("<p><br></p>", "").length;
|
||||
|
||||
if (!orgBranding) return null;
|
||||
const defaultValues: FormValues = {
|
||||
name: currentOrganisation?.name || "",
|
||||
logo: currentOrganisation?.logo || "",
|
||||
bio: currentOrganisation?.bio || "",
|
||||
slug:
|
||||
currentOrganisation?.slug ||
|
||||
((currentOrganisation?.metadata as Prisma.JsonObject)?.requestedSlug as string) ||
|
||||
"",
|
||||
};
|
||||
|
||||
return (
|
||||
<LicenseRequired>
|
||||
<Meta title={t("profile")} description={t("profile_org_description")} />
|
||||
{!isLoading && (
|
||||
<>
|
||||
{isOrgAdminOrOwner ? (
|
||||
<Form
|
||||
form={form}
|
||||
handleSubmit={(values) => {
|
||||
if (currentOrganisation) {
|
||||
const variables = {
|
||||
logo: values.logo,
|
||||
name: values.name,
|
||||
slug: values.slug,
|
||||
bio: values.bio,
|
||||
};
|
||||
|
||||
mutation.mutate(variables);
|
||||
}
|
||||
}}>
|
||||
<div className="flex items-center">
|
||||
<Controller
|
||||
control={form.control}
|
||||
name="logo"
|
||||
render={({ field: { value } }) => (
|
||||
<>
|
||||
<Avatar
|
||||
alt=""
|
||||
imageSrc={getPlaceholderAvatar(value, currentOrganisation?.name as string)}
|
||||
size="lg"
|
||||
/>
|
||||
<div className="ms-4">
|
||||
<ImageUploader
|
||||
target="avatar"
|
||||
id="avatar-upload"
|
||||
buttonMsg={t("update")}
|
||||
handleAvatarChange={(newLogo) => {
|
||||
form.setValue("logo", newLogo);
|
||||
}}
|
||||
imageSrc={value}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<hr className="border-subtle my-8" />
|
||||
|
||||
<Controller
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field: { value } }) => (
|
||||
<div className="mt-8">
|
||||
<TextField
|
||||
name="name"
|
||||
label={t("org_name")}
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
form.setValue("name", e?.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={form.control}
|
||||
name="slug"
|
||||
render={({ field: { value } }) => (
|
||||
<div className="mt-8">
|
||||
<TextField
|
||||
name="slug"
|
||||
label={t("org_url")}
|
||||
value={value}
|
||||
disabled
|
||||
addOnSuffix={`.${subdomainSuffix()}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8">
|
||||
<Label>{t("about")}</Label>
|
||||
<Editor
|
||||
getText={() => md.render(form.getValues("bio") || "")}
|
||||
setText={(value: string) => form.setValue("bio", turndown(value))}
|
||||
excludedToolbarItems={["blockType"]}
|
||||
disableLists
|
||||
firstRender={firstRender}
|
||||
setFirstRender={setFirstRender}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-default mt-2 text-sm">{t("org_description")}</p>
|
||||
<Button color="primary" className="mt-8" type="submit" loading={mutation.isLoading}>
|
||||
{t("update")}
|
||||
</Button>
|
||||
</Form>
|
||||
) : (
|
||||
<div className="flex">
|
||||
<div className="flex-grow">
|
||||
<div>
|
||||
<Label className="text-emphasis">{t("org_name")}</Label>
|
||||
<p className="text-default text-sm">{currentOrganisation?.name}</p>
|
||||
</div>
|
||||
{currentOrganisation && !isBioEmpty && (
|
||||
<>
|
||||
<Label className="text-emphasis mt-5">{t("about")}</Label>
|
||||
<div
|
||||
className=" text-subtle break-words text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
|
||||
dangerouslySetInnerHTML={{ __html: md.render(currentOrganisation.bio || "") }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="">
|
||||
<LinkIconButton
|
||||
Icon={LinkIcon}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(orgBranding.fullDomain);
|
||||
showToast("Copied to clipboard", "success");
|
||||
}}>
|
||||
{t("copy_link_org")}
|
||||
</LinkIconButton>
|
||||
<Meta title={t("profile")} description={t("profile_org_description")} borderInShellHeader={true} />
|
||||
<>
|
||||
{isOrgAdminOrOwner ? (
|
||||
<OrgProfileForm defaultValues={defaultValues} />
|
||||
) : (
|
||||
<div className="flex">
|
||||
<div className="flex-grow">
|
||||
<div>
|
||||
<Label className="text-emphasis">{t("org_name")}</Label>
|
||||
<p className="text-default text-sm">{currentOrganisation?.name}</p>
|
||||
</div>
|
||||
{!isBioEmpty && (
|
||||
<>
|
||||
<Label className="text-emphasis mt-5">{t("about")}</Label>
|
||||
<div
|
||||
className=" text-subtle break-words text-sm [&_a]:text-blue-500 [&_a]:underline [&_a]:hover:text-blue-600"
|
||||
dangerouslySetInnerHTML={{ __html: md.render(currentOrganisation.bio || "") }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* Disable Org disbanding */}
|
||||
{/* <hr className="border-subtle my-8 border" />
|
||||
<div className="">
|
||||
<LinkIconButton
|
||||
Icon={LinkIcon}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(orgBranding.fullDomain);
|
||||
showToast("Copied to clipboard", "success");
|
||||
}}>
|
||||
{t("copy_link_org")}
|
||||
</LinkIconButton>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Disable Org disbanding */}
|
||||
{/* <hr className="border-subtle my-8 border" />
|
||||
<div className="text-default mb-3 text-base font-semibold">{t("danger_zone")}</div>
|
||||
{currentOrganisation?.user.role === "OWNER" ? (
|
||||
<Dialog>
|
||||
|
@ -234,13 +160,156 @@ const OrgProfileView = () => {
|
|||
</ConfirmationDialogContent>
|
||||
</Dialog>
|
||||
) : null} */}
|
||||
{/* LEAVE ORG should go above here ^ */}
|
||||
</>
|
||||
)}
|
||||
{/* LEAVE ORG should go above here ^ */}
|
||||
</>
|
||||
</LicenseRequired>
|
||||
);
|
||||
};
|
||||
|
||||
const OrgProfileForm = ({ defaultValues }: { defaultValues: FormValues }) => {
|
||||
const utils = trpc.useContext();
|
||||
const { t } = useLocale();
|
||||
const [firstRender, setFirstRender] = useState(true);
|
||||
|
||||
const form = useForm({
|
||||
defaultValues,
|
||||
resolver: zodResolver(orgProfileFormSchema),
|
||||
});
|
||||
|
||||
const mutation = trpc.viewer.organizations.update.useMutation({
|
||||
onError: (err) => {
|
||||
showToast(err.message, "error");
|
||||
},
|
||||
onSuccess: async (res) => {
|
||||
reset({
|
||||
logo: (res.data?.logo || "") as string,
|
||||
name: (res.data?.name || "") as string,
|
||||
bio: (res.data?.bio || "") as string,
|
||||
slug: defaultValues["slug"],
|
||||
});
|
||||
await utils.viewer.teams.get.invalidate();
|
||||
await utils.viewer.organizations.listCurrent.invalidate();
|
||||
showToast(t("your_organization_updated_sucessfully"), "success");
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
formState: { isSubmitting, isDirty },
|
||||
reset,
|
||||
} = form;
|
||||
|
||||
const isDisabled = isSubmitting || !isDirty;
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
handleSubmit={(values) => {
|
||||
const variables = {
|
||||
logo: values.logo,
|
||||
name: values.name,
|
||||
slug: values.slug,
|
||||
bio: values.bio,
|
||||
};
|
||||
|
||||
mutation.mutate(variables);
|
||||
}}>
|
||||
<div className="border-subtle border-x px-4 py-8 sm:px-6">
|
||||
<div className="flex items-center">
|
||||
<Controller
|
||||
control={form.control}
|
||||
name="logo"
|
||||
render={({ field: { value } }) => {
|
||||
const showRemoveLogoButton = !!value;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Avatar
|
||||
alt={defaultValues.name || ""}
|
||||
imageSrc={getPlaceholderAvatar(value, defaultValues.name as string)}
|
||||
size="lg"
|
||||
/>
|
||||
<div className="ms-4">
|
||||
<div className="flex gap-2">
|
||||
<ImageUploader
|
||||
target="avatar"
|
||||
id="avatar-upload"
|
||||
buttonMsg={t("upload_logo")}
|
||||
handleAvatarChange={(newLogo) => {
|
||||
form.setValue("logo", newLogo, { shouldDirty: true });
|
||||
}}
|
||||
imageSrc={value || undefined}
|
||||
triggerButtonColor={showRemoveLogoButton ? "secondary" : "primary"}
|
||||
/>
|
||||
{showRemoveLogoButton && (
|
||||
<Button
|
||||
color="secondary"
|
||||
onClick={() => {
|
||||
form.setValue("logo", null, { shouldDirty: true });
|
||||
}}>
|
||||
{t("remove")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Controller
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field: { value } }) => (
|
||||
<div className="mt-8">
|
||||
<TextField
|
||||
name="name"
|
||||
label={t("org_name")}
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
form.setValue("name", e?.target.value, { shouldDirty: true });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={form.control}
|
||||
name="slug"
|
||||
render={({ field: { value } }) => (
|
||||
<div className="mt-8">
|
||||
<TextField
|
||||
name="slug"
|
||||
label={t("org_url")}
|
||||
value={value}
|
||||
disabled
|
||||
addOnSuffix={`.${subdomainSuffix()}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8">
|
||||
<Label>{t("about")}</Label>
|
||||
<Editor
|
||||
getText={() => md.render(form.getValues("bio") || "")}
|
||||
setText={(value: string) => form.setValue("bio", turndown(value), { shouldDirty: true })}
|
||||
excludedToolbarItems={["blockType"]}
|
||||
disableLists
|
||||
firstRender={firstRender}
|
||||
setFirstRender={setFirstRender}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-default mt-2 text-sm">{t("org_description")}</p>
|
||||
</div>
|
||||
<SectionBottomActions align="end">
|
||||
<Button color="primary" type="submit" loading={mutation.isLoading} disabled={isDisabled}>
|
||||
{t("update")}
|
||||
</Button>
|
||||
</SectionBottomActions>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
OrgProfileView.getLayout = getLayout;
|
||||
|
||||
export default OrgProfileView;
|
||||
|
|
|
@ -14,7 +14,7 @@ const BillingView = () => {
|
|||
const billingHref = `/api/integrations/stripepayment/portal?returnTo=${WEBAPP_URL}${returnTo}`;
|
||||
return (
|
||||
<>
|
||||
<Meta title={t("team_billing")} description={t("team_billing_description")} />
|
||||
<Meta title={t("billing")} description={t("team_billing_description")} borderInShellHeader={true} />
|
||||
<div className="text-default flex flex-col text-sm sm:flex-row">
|
||||
<div>
|
||||
<h2 className="font-medium">{t("view_and_manage_billing_details")}</h2>
|
||||
|
|
|
@ -14,7 +14,7 @@ const SectionBottomActions = ({
|
|||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"border-subtle bg-muted flex rounded-b-xl border px-6 py-4",
|
||||
"border-subtle bg-muted flex rounded-b-lg border px-6 py-4",
|
||||
align === "end" && "justify-end",
|
||||
className
|
||||
)}>
|
||||
|
|
|
@ -382,7 +382,7 @@ const SettingsSidebarContainer = ({
|
|||
alt={team.name || "Team logo"}
|
||||
/>
|
||||
)}
|
||||
<p className="w-1/2 truncate">{team.name}</p>
|
||||
<p className="w-1/2 truncate leading-normal">{team.name}</p>
|
||||
{!team.accepted && (
|
||||
<Badge className="ms-3" variant="orange">
|
||||
Inv.
|
||||
|
@ -526,7 +526,7 @@ const SettingsSidebarContainer = ({
|
|||
alt={otherTeam.name || "Team logo"}
|
||||
/>
|
||||
)}
|
||||
<p className="w-1/2 truncate">{otherTeam.name}</p>
|
||||
<p className="w-1/2 truncate leading-normal">{otherTeam.name}</p>
|
||||
</div>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="space-y-0.5">
|
||||
|
@ -688,7 +688,7 @@ export function ShellHeader() {
|
|||
<header
|
||||
className={classNames(
|
||||
"border-subtle mx-auto block justify-between sm:flex",
|
||||
meta.borderInShellHeader && "rounded-t-xl border px-4 py-6 sm:px-6",
|
||||
meta.borderInShellHeader && "rounded-t-lg border px-4 py-6 sm:px-6",
|
||||
meta.borderInShellHeader === undefined && "mb-8 border-b pb-8"
|
||||
)}>
|
||||
<div className="flex w-full items-center">
|
||||
|
@ -703,12 +703,12 @@ export function ShellHeader() {
|
|||
{t(meta.title)}
|
||||
</h1>
|
||||
) : (
|
||||
<div className="bg-emphasis mb-1 h-5 w-24 animate-pulse rounded-md" />
|
||||
<div className="bg-emphasis mb-1 h-5 w-24 animate-pulse rounded-lg" />
|
||||
)}
|
||||
{meta.description && isLocaleReady ? (
|
||||
<p className="text-default text-sm ltr:mr-4 rtl:ml-4">{t(meta.description)}</p>
|
||||
) : (
|
||||
<div className="bg-emphasis h-5 w-32 animate-pulse rounded-md" />
|
||||
<div className="bg-emphasis h-5 w-32 animate-pulse rounded-lg" />
|
||||
)}
|
||||
</div>
|
||||
<div className="ms-auto flex-shrink-0">{meta.CTA}</div>
|
||||
|
|
|
@ -17,7 +17,7 @@ export default function WebhookTestDisclosure() {
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="border-subtle flex justify-between rounded-t-xl border p-6">
|
||||
<div className="border-subtle flex justify-between rounded-t-lg border p-6">
|
||||
<div>
|
||||
<p className="text-emphasis text-sm font-semibold leading-5">{t("webhook_test")}</p>
|
||||
<p className="text-default text-sm">{t("test_webhook")}</p>
|
||||
|
@ -31,8 +31,8 @@ export default function WebhookTestDisclosure() {
|
|||
{t("ping_test")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="border-subtle space-y-0 rounded-b-xl border border-t-0 px-6 py-8 sm:mx-0">
|
||||
<div className="border-subtle flex justify-between rounded-t-xl border p-4">
|
||||
<div className="border-subtle space-y-0 rounded-b-lg border border-t-0 px-6 py-8 sm:mx-0">
|
||||
<div className="border-subtle flex justify-between rounded-t-lg border p-4">
|
||||
<div className="flex items-center space-x-1">
|
||||
<h3 className="text-emphasis self-center text-sm font-semibold leading-4">
|
||||
{t("webhook_response")}
|
||||
|
@ -44,7 +44,7 @@ export default function WebhookTestDisclosure() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-muted border-subtle rounded-b-xl border border-t-0 p-4 font-mono text-[13px] leading-4 ">
|
||||
<div className="bg-muted border-subtle rounded-b-lg border border-t-0 p-4 font-mono text-[13px] leading-4">
|
||||
{!mutation.data && <p>{t("no_data_yet")}</p>}
|
||||
{mutation.status === "success" && (
|
||||
<div className="overflow-x-auto">{JSON.stringify(mutation.data, null, 4)}</div>
|
||||
|
|
|
@ -61,6 +61,7 @@ function Component({ webhookId }: { webhookId: string }) {
|
|||
title={t("edit_webhook")}
|
||||
description={t("add_webhook_description", { appName: APP_NAME })}
|
||||
borderInShellHeader={true}
|
||||
backButton
|
||||
/>
|
||||
<WebhookForm
|
||||
noRoutingFormTriggers={false}
|
||||
|
|
|
@ -15,7 +15,7 @@ const SkeletonLoader = ({ title, description }: { title: string; description: st
|
|||
return (
|
||||
<SkeletonContainer>
|
||||
<Meta title={title} description={description} borderInShellHeader={true} />
|
||||
<div className="divide-subtle border-subtle space-y-6 rounded-b-xl border border-t-0 px-6 py-4">
|
||||
<div className="divide-subtle border-subtle space-y-6 rounded-b-lg border border-t-0 px-6 py-4">
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
</div>
|
||||
|
|
|
@ -32,7 +32,7 @@ const SkeletonLoader = ({
|
|||
return (
|
||||
<SkeletonContainer>
|
||||
<Meta title={title} description={description} borderInShellHeader={borderInShellHeader} />
|
||||
<div className="divide-subtle border-subtle space-y-6 rounded-b-xl border border-t-0 px-6 py-4">
|
||||
<div className="divide-subtle border-subtle space-y-6 rounded-b-lg border border-t-0 px-6 py-4">
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
<SkeletonText className="h-8 w-full" />
|
||||
</div>
|
||||
|
@ -120,8 +120,8 @@ const WebhooksList = ({ webhooksByViewer }: { webhooksByViewer: WebhooksByViewer
|
|||
<div className="flex flex-col" key={group.profile.slug}>
|
||||
<div
|
||||
className={classNames(
|
||||
"border-subtle rounded-md rounded-t-none border border-t-0",
|
||||
hasTeams && "mb-8 mt-3 rounded-t-md border-t"
|
||||
"border-subtle rounded-lg rounded-t-none border border-t-0",
|
||||
hasTeams && "mb-8 mt-3 rounded-t-lg border-t"
|
||||
)}>
|
||||
{group.webhooks.map((webhook, index) => (
|
||||
<WebhookListItem
|
||||
|
@ -145,7 +145,7 @@ const WebhooksList = ({ webhooksByViewer }: { webhooksByViewer: WebhooksByViewer
|
|||
Icon={LinkIcon}
|
||||
headline={t("create_your_first_webhook")}
|
||||
description={t("create_your_first_webhook_description", { appName: APP_NAME })}
|
||||
className="rounded-b-md rounded-t-none border-t-0"
|
||||
className="rounded-b-lg rounded-t-none border-t-0"
|
||||
buttonRaw={
|
||||
<CreateButtonWithTeamsList
|
||||
subtitle={t("create_for").toUpperCase()}
|
||||
|
|
|
@ -103,3 +103,6 @@ export const CALCOM_VERSION = process.env.NEXT_PUBLIC_CALCOM_VERSION as string;
|
|||
|
||||
export const APP_CREDENTIAL_SHARING_ENABLED =
|
||||
process.env.CALCOM_WEBHOOK_SECRET && process.env.CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY;
|
||||
|
||||
export const DEFAULT_LIGHT_BRAND_COLOR = "#292929";
|
||||
export const DEFAULT_DARK_BRAND_COLOR = "#fafafa";
|
||||
|
|
|
@ -99,7 +99,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
|||
// Sync Services: Close.com
|
||||
if (prevOrganisation) closeComUpdateTeam(prevOrganisation, updatedOrganisation);
|
||||
|
||||
return { update: true, userId: ctx.user.id };
|
||||
return { update: true, userId: ctx.user.id, data };
|
||||
};
|
||||
|
||||
export default updateHandler;
|
||||
|
|
|
@ -79,7 +79,7 @@ export const Select = <
|
|||
cx("text-emphasis placeholder:text-muted flex gap-1", innerClassNames?.valueContainer),
|
||||
multiValue: () =>
|
||||
cx(
|
||||
"bg-subtle text-default rounded-md py-1.5 px-2 flex items-center text-sm leading-none",
|
||||
"bg-subtle text-default rounded-md py-1.5 px-2 flex items-center text-sm leading-tight",
|
||||
innerClassNames?.multiValue
|
||||
),
|
||||
menu: () =>
|
||||
|
|
|
@ -46,7 +46,12 @@ function SettingsToggle({
|
|||
<div className="flex w-full flex-col space-y-4 lg:flex-row lg:space-x-4 lg:space-y-0">
|
||||
<fieldset className="block w-full flex-col sm:flex">
|
||||
{toggleSwitchAtTheEnd ? (
|
||||
<div className={classNames("flex justify-between space-x-3", switchContainerClassName)}>
|
||||
<div
|
||||
className={classNames(
|
||||
"border-subtle flex justify-between space-x-3 rounded-lg border px-4 py-6 sm:px-6",
|
||||
checked && children && "rounded-b-none",
|
||||
switchContainerClassName
|
||||
)}>
|
||||
<div>
|
||||
<div className="flex items-center">
|
||||
<Label
|
||||
|
|
Loading…
Reference in New Issue