Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50151403c4 | ||
|
|
7aa131f349 | ||
|
|
d88bdc368b | ||
|
|
255938fda0 | ||
|
|
5766d6f5f4 | ||
|
|
9d299c6d5f | ||
|
|
f49783b453 | ||
|
|
246fce3ab8 | ||
|
|
82f262f60d |
No files matched your search
@@ -33,7 +33,7 @@ const TimeOptions: FC<Props> = ({ onToggle24hClock, onSelectTimeZone, timeFormat
|
|||||||
};
|
};
|
||||||
|
|
||||||
return selectedTimeZone !== "" ? (
|
return selectedTimeZone !== "" ? (
|
||||||
<div className="max-w-80 dark:border-darkgray-300 dark:bg-darkgray-200 absolute z-10 w-full rounded-sm border border-gray-200 bg-white px-4 pt-4 pb-3 shadow-sm">
|
<div className="dark:border-darkgray-300 dark:bg-darkgray-200 rounded-sm border border-gray-200 bg-white px-4 pt-4 pb-3 shadow-sm">
|
||||||
<div className="mb-4 flex">
|
<div className="mb-4 flex">
|
||||||
<div className="text-sm font-medium text-gray-600 dark:text-white">{t("time_options")}</div>
|
<div className="text-sm font-medium text-gray-600 dark:text-white">{t("time_options")}</div>
|
||||||
<div className="ml-auto flex items-center">
|
<div className="ml-auto flex items-center">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Get router variables
|
// Get router variables
|
||||||
import { EventType } from "@prisma/client";
|
import { EventType } from "@prisma/client";
|
||||||
import { SchedulingType } from "@prisma/client";
|
import { SchedulingType } from "@prisma/client";
|
||||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
import * as Popover from "@radix-ui/react-popover";
|
||||||
import { TFunction } from "next-i18next";
|
import { TFunction } from "next-i18next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useReducer, useEffect, useMemo, useState } from "react";
|
import { useReducer, useEffect, useMemo, useState } from "react";
|
||||||
@@ -240,8 +240,8 @@ function TimezoneDropdown({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen} className="flex">
|
<Popover.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}>
|
||||||
<Collapsible.Trigger className="min-w-32 dark:text-darkgray-600 mb-2 -ml-2 px-2 text-left text-gray-600">
|
<Popover.Trigger className="min-w-32 dark:text-darkgray-600 radix-state-open:bg-gray-200 dark:radix-state-open:bg-darkgray-200 group relative mb-2 -ml-2 inline-block rounded-md px-2 py-2 text-left text-gray-600">
|
||||||
<p className="text-sm font-medium">
|
<p className="text-sm font-medium">
|
||||||
<Icon.FiGlobe className="mr-[10px] ml-[2px] -mt-[2px] inline-block h-4 w-4" />
|
<Icon.FiGlobe className="mr-[10px] ml-[2px] -mt-[2px] inline-block h-4 w-4" />
|
||||||
{timeZone}
|
{timeZone}
|
||||||
@@ -251,15 +251,20 @@ function TimezoneDropdown({
|
|||||||
<Icon.FiChevronDown className="ml-1 inline-block h-4 w-4" />
|
<Icon.FiChevronDown className="ml-1 inline-block h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</Collapsible.Trigger>
|
</Popover.Trigger>
|
||||||
<Collapsible.Content>
|
<Popover.Portal>
|
||||||
|
<Popover.Content
|
||||||
|
hideWhenDetached
|
||||||
|
align="start"
|
||||||
|
className="animate-fade-in-up absolute left-0 top-2 w-80 max-w-[calc(100vw_-_1.5rem)]">
|
||||||
<TimeOptions
|
<TimeOptions
|
||||||
onSelectTimeZone={handleSelectTimeZone}
|
onSelectTimeZone={handleSelectTimeZone}
|
||||||
onToggle24hClock={handleToggle24hClock}
|
onToggle24hClock={handleToggle24hClock}
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
/>
|
/>
|
||||||
</Collapsible.Content>
|
</Popover.Content>
|
||||||
</Collapsible.Root>
|
</Popover.Portal>
|
||||||
|
</Popover.Root>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,7 +421,7 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
|
|||||||
<h1 className="text-bookingdark dark:text-darkgray-900 mb-4 break-words text-xl font-semibold">
|
<h1 className="text-bookingdark dark:text-darkgray-900 mb-4 break-words text-xl font-semibold">
|
||||||
{eventType.title}
|
{eventType.title}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex flex-col space-y-3">
|
<div className="flex flex-col items-start space-y-3">
|
||||||
{eventType?.description && (
|
{eventType?.description && (
|
||||||
<div className="dark:text-darkgray-600 flex py-1 text-sm font-medium text-gray-600">
|
<div className="dark:text-darkgray-600 flex py-1 text-sm font-medium text-gray-600">
|
||||||
<div>
|
<div>
|
||||||
@@ -500,7 +505,7 @@ const AvailabilityPage = ({ profile, eventType }: Props) => {
|
|||||||
<div className="overflow-hidden sm:flex">
|
<div className="overflow-hidden sm:flex">
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
"sm:dark:border-darkgray-200 hidden overflow-hidden border-gray-200 p-5 sm:border-r md:flex md:flex-col " +
|
"sm:dark:border-darkgray-200 hidden border-gray-200 p-5 sm:border-r md:flex md:flex-col " +
|
||||||
(isAvailableTimesVisible ? "sm:w-1/3" : recurringEventCount ? "sm:w-2/3" : "sm:w-1/2")
|
(isAvailableTimesVisible ? "sm:w-1/3" : recurringEventCount ? "sm:w-2/3" : "sm:w-1/2")
|
||||||
}>
|
}>
|
||||||
<UserAvatars
|
<UserAvatars
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { extendEventData, nextCollectBasicSettings } from "@calcom/lib/telemetry
|
|||||||
|
|
||||||
const V2_WHITELIST = [
|
const V2_WHITELIST = [
|
||||||
"/settings/admin",
|
"/settings/admin",
|
||||||
|
"/settings/billing",
|
||||||
"/settings/developer/webhooks",
|
"/settings/developer/webhooks",
|
||||||
"/settings/developer/api-keys",
|
"/settings/developer/api-keys",
|
||||||
"/settings/my-account",
|
"/settings/my-account",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@calcom/web",
|
"name": "@calcom/web",
|
||||||
"version": "2.0.3",
|
"version": "2.0.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "ANALYZE=true next build",
|
"analyze": "ANALYZE=true next build",
|
||||||
|
|||||||
@@ -542,6 +542,7 @@ const EventTypesPage = () => {
|
|||||||
<title>Home | Cal.com</title>
|
<title>Home | Cal.com</title>
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<Shell
|
<Shell
|
||||||
heading={t("event_types_page_title") as string}
|
heading={t("event_types_page_title") as string}
|
||||||
subtitle={t("event_types_page_subtitle") as string}
|
subtitle={t("event_types_page_subtitle") as string}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const Item = ({ type, group, readOnly }: { type: EventType; group: EventTypeGrou
|
|||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link href={`/event-types/${type.id}`}>
|
<Link href={`/event-types/${type.id}?tabName=setup`}>
|
||||||
<a
|
<a
|
||||||
className="flex-grow truncate text-sm"
|
className="flex-grow truncate text-sm"
|
||||||
title={`${type.title} ${type.description ? `– ${type.description}` : ""}`}>
|
title={`${type.title} ${type.description ? `– ${type.description}` : ""}`}>
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { HelpScout, useChat } from "react-live-chat-loader";
|
||||||
|
|
||||||
|
import { classNames } from "@calcom/lib";
|
||||||
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
import { trpc } from "@calcom/trpc/react";
|
||||||
|
import { Icon } from "@calcom/ui";
|
||||||
|
import { Button } from "@calcom/ui/v2";
|
||||||
|
import Meta from "@calcom/ui/v2/core/Meta";
|
||||||
|
import { getLayout } from "@calcom/ui/v2/core/layouts/SettingsLayout";
|
||||||
|
|
||||||
|
interface CtaRowProps {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CtaRow = ({ title, description, className, children }: CtaRowProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className={classNames("flex flex-col sm:flex-row", className)}>
|
||||||
|
<div>
|
||||||
|
<h2 className="font-medium">{title}</h2>
|
||||||
|
<p>{description}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex-shrink-0 pt-3 sm:ml-auto sm:pt-0 sm:pl-3">{children}</div>
|
||||||
|
</section>
|
||||||
|
<hr className="border-neutral-200" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const BillingView = () => {
|
||||||
|
const { t } = useLocale();
|
||||||
|
const { data: user } = trpc.useQuery(["viewer.me"]);
|
||||||
|
const isPro = user?.plan === "PRO";
|
||||||
|
const [, loadChat] = useChat();
|
||||||
|
const [showChat, setShowChat] = useState(false);
|
||||||
|
|
||||||
|
const onContactSupportClick = () => {
|
||||||
|
setShowChat(true);
|
||||||
|
loadChat({ open: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Meta title={t("billing")} description={t("manage_billing_description")} />
|
||||||
|
<div className="space-y-6 text-sm sm:space-y-8">
|
||||||
|
{!isPro && (
|
||||||
|
<CtaRow title={t("billing_freeplan_title")} description={t("billing_freeplan_description")}>
|
||||||
|
<form target="_blank" method="POST" action="/api/upgrade">
|
||||||
|
<Button type="submit" EndIcon={Icon.FiExternalLink}>
|
||||||
|
{t("billing_freeplan_cta")}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</CtaRow>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<CtaRow
|
||||||
|
className={classNames(!isPro && "pointer-events-none opacity-30")}
|
||||||
|
title={t("billing_manage_details_title")}
|
||||||
|
description={t("billing_manage_details_description")}>
|
||||||
|
<Button
|
||||||
|
color={isPro ? "primary" : "secondary"}
|
||||||
|
href="/api/integrations/stripepayment/portal"
|
||||||
|
target="_blank"
|
||||||
|
EndIcon={Icon.FiExternalLink}>
|
||||||
|
{t("billing_portal")}
|
||||||
|
</Button>
|
||||||
|
</CtaRow>
|
||||||
|
|
||||||
|
<CtaRow title={t("billing_help_title")} description={t("billing_help_description")}>
|
||||||
|
<Button color="secondary" onClick={onContactSupportClick}>
|
||||||
|
{t("billing_help_cta")}
|
||||||
|
</Button>
|
||||||
|
</CtaRow>
|
||||||
|
{showChat && <HelpScout color="#292929" icon="message" horizontalPosition="right" zIndex="1" />}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
BillingView.getLayout = getLayout;
|
||||||
|
|
||||||
|
export default BillingView;
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { expect } from "@playwright/test";
|
import { expect } from "@playwright/test";
|
||||||
|
|
||||||
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { randomString } from "../lib/random";
|
import { randomString } from "../lib/random";
|
||||||
import { test } from "./lib/fixtures";
|
import { test } from "./lib/fixtures";
|
||||||
|
|
||||||
@@ -87,7 +89,7 @@ test.describe("Event Types tests", () => {
|
|||||||
);
|
);
|
||||||
const href = await firstElement.getAttribute("href");
|
const href = await firstElement.getAttribute("href");
|
||||||
if (!href) throw new Error("No href found for event type");
|
if (!href) throw new Error("No href found for event type");
|
||||||
const [eventTypeId] = href.split("/").reverse();
|
const [eventTypeId] = new URL(WEBAPP_URL + href).pathname.split("/").reverse();
|
||||||
const firstTitle = await page.locator(`[data-testid=event-type-title-${eventTypeId}]`).innerText();
|
const firstTitle = await page.locator(`[data-testid=event-type-title-${eventTypeId}]`).innerText();
|
||||||
const firstFullSlug = await page.locator(`[data-testid=event-type-slug-${eventTypeId}]`).innerText();
|
const firstFullSlug = await page.locator(`[data-testid=event-type-slug-${eventTypeId}]`).innerText();
|
||||||
const firstSlug = firstFullSlug.split("/")[2];
|
const firstSlug = firstFullSlug.split("/")[2];
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type Prisma from "@prisma/client";
|
|||||||
import { Prisma as PrismaType, UserPlan } from "@prisma/client";
|
import { Prisma as PrismaType, UserPlan } from "@prisma/client";
|
||||||
import { hash } from "bcryptjs";
|
import { hash } from "bcryptjs";
|
||||||
|
|
||||||
|
import dayjs from "@calcom/dayjs";
|
||||||
import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability";
|
import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability";
|
||||||
import { prisma } from "@calcom/prisma";
|
import { prisma } from "@calcom/prisma";
|
||||||
|
|
||||||
@@ -257,7 +258,7 @@ const createUser = async (
|
|||||||
password: await hashPassword(uname),
|
password: await hashPassword(uname),
|
||||||
emailVerified: new Date(),
|
emailVerified: new Date(),
|
||||||
completedOnboarding: opts?.completedOnboarding ?? true,
|
completedOnboarding: opts?.completedOnboarding ?? true,
|
||||||
timeZone: opts?.timeZone ?? TimeZoneEnum.UK,
|
timeZone: opts?.timeZone ?? dayjs.tz.guess(),
|
||||||
locale: opts?.locale ?? "en",
|
locale: opts?.locale ?? "en",
|
||||||
schedules:
|
schedules:
|
||||||
opts?.completedOnboarding ?? true
|
opts?.completedOnboarding ?? true
|
||||||
|
|||||||
@@ -1272,10 +1272,28 @@
|
|||||||
"format": "Format",
|
"format": "Format",
|
||||||
"uppercase_for_letters": "Use uppercase for all letters",
|
"uppercase_for_letters": "Use uppercase for all letters",
|
||||||
"replace_whitespaces_underscores": "Replace whitespaces with underscores",
|
"replace_whitespaces_underscores": "Replace whitespaces with underscores",
|
||||||
|
"manage_billing": "Manage billing",
|
||||||
|
"manage_billing_description": "Manage all things billing",
|
||||||
|
"billing_freeplan_title": "You're currently on the FREE plan",
|
||||||
|
"billing_freeplan_description": "We work better in teams. Extend your workflows with round-robin and collective events and make advanced routing forms",
|
||||||
|
"billing_freeplan_cta": "Try now",
|
||||||
|
"billing_manage_details_title": "View and manage your billing details",
|
||||||
|
"billing_manage_details_description": "View and edit your billing details, as well as cancel your subscription.",
|
||||||
|
"billing_portal": "Billing portal",
|
||||||
|
"billing_help_title": "Need anything else?",
|
||||||
|
"billing_help_description": "If you need any further help with billing, our support team are here to help.",
|
||||||
|
"billing_help_cta": "Contact support",
|
||||||
"ignore_special_characters": "Ignore special characters in your Additional Input label. Use only letters and numbers",
|
"ignore_special_characters": "Ignore special characters in your Additional Input label. Use only letters and numbers",
|
||||||
"retry": "Retry",
|
"retry": "Retry",
|
||||||
"fetching_calendars_error": "There was a problem fetching your calendars. Please <1>try again</1> or reach out to customer support.",
|
"fetching_calendars_error": "There was a problem fetching your calendars. Please <1>try again</1> or reach out to customer support.",
|
||||||
"calendar_connection_fail": "Calendar connection failed",
|
"calendar_connection_fail": "Calendar connection failed",
|
||||||
"booking_confirmation_success": "Booking confirmation succeeded",
|
"booking_confirmation_success": "Booking confirmation succeeded",
|
||||||
"booking_confirmation_fail": "Booking confirmation failed"
|
"booking_confirmation_fail": "Booking confirmation failed",
|
||||||
|
"we_wont_show_again": "We won't show this again",
|
||||||
|
"couldnt_update_timezone": "We couldn't update the timezone",
|
||||||
|
"updated_timezone_to": "Updated timezone to {{formattedCurrentTz}}",
|
||||||
|
"update_timezone": "Update timezone",
|
||||||
|
"update_timezone_question": "Update Timezone?",
|
||||||
|
"update_timezone_description": "It seems like your local timezone has changed to {{formattedCurrentTz}}. It's very important to have the correct timezone to prevent bookings at undesired times. Do you want to update it?",
|
||||||
|
"dont_update": "Don't update"
|
||||||
}
|
}
|
||||||
@@ -6,8 +6,10 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
|
|||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import { Icon } from "@calcom/ui";
|
import { Icon } from "@calcom/ui";
|
||||||
import { Button, Avatar, Badge } from "@calcom/ui/v2";
|
import Avatar from "@calcom/ui/v2/core/Avatar";
|
||||||
import { SkeletonContainer, SkeletonText } from "@calcom/ui/v2";
|
import Badge from "@calcom/ui/v2/core/Badge";
|
||||||
|
import Button from "@calcom/ui/v2/core/Button";
|
||||||
|
import { SkeletonContainer, SkeletonText } from "@calcom/ui/v2/core/skeleton";
|
||||||
|
|
||||||
const AddNewTeamMemberSkeleton = () => {
|
const AddNewTeamMemberSkeleton = () => {
|
||||||
return (
|
return (
|
||||||
@@ -58,7 +60,7 @@ const AddNewTeamMembers = (props: { teamId: number }) => {
|
|||||||
<Avatar
|
<Avatar
|
||||||
gravatarFallbackMd5="teamMember"
|
gravatarFallbackMd5="teamMember"
|
||||||
size="mdLg"
|
size="mdLg"
|
||||||
imageSrc={member?.avatar}
|
imageSrc={WEBAPP_URL + "/" + member.username + "/avatar.png"}
|
||||||
alt="owner-avatar"
|
alt="owner-avatar"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export async function getTeamWithMembers(id?: number, slug?: string) {
|
|||||||
id: true,
|
id: true,
|
||||||
plan: true,
|
plan: true,
|
||||||
bio: true,
|
bio: true,
|
||||||
avatar: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const teamSelect = Prisma.validator<Prisma.TeamSelect>()({
|
const teamSelect = Prisma.validator<Prisma.TeamSelect>()({
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
import dayjs from "@calcom/dayjs";
|
||||||
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
import { trpc } from "@calcom/trpc/react";
|
||||||
|
import { DialogContent, showToast } from "@calcom/ui/v2";
|
||||||
|
import { Dialog } from "@calcom/ui/v2/core/Dialog";
|
||||||
|
|
||||||
|
export default function TimezoneChangeDialog() {
|
||||||
|
const { t } = useLocale();
|
||||||
|
const { data: user, isLoading } = trpc.useQuery(["viewer.me"]);
|
||||||
|
const utils = trpc.useContext();
|
||||||
|
const userTz = user?.timeZone;
|
||||||
|
const currentTz = dayjs.tz.guess();
|
||||||
|
const formattedCurrentTz = currentTz?.replace("_", " ");
|
||||||
|
|
||||||
|
// update user settings
|
||||||
|
const onSuccessMutation = async () => {
|
||||||
|
showToast(t("updated_timezone_to", { formattedCurrentTz }), "success");
|
||||||
|
await utils.invalidateQueries(["viewer.me"]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onErrorMutation = () => {
|
||||||
|
showToast(t("couldnt_update_timezone"), "error");
|
||||||
|
};
|
||||||
|
|
||||||
|
// update timezone in db
|
||||||
|
const mutation = trpc.useMutation("viewer.updateProfile", {
|
||||||
|
onSuccess: onSuccessMutation,
|
||||||
|
onError: onErrorMutation,
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateTimezone() {
|
||||||
|
setOpen(false);
|
||||||
|
mutation.mutate({
|
||||||
|
timeZone: currentTz,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for difference in user timezone and current browser timezone
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
const tzDifferent =
|
||||||
|
!isLoading && dayjs.tz(undefined, currentTz).utcOffset() !== dayjs.tz(undefined, userTz).utcOffset();
|
||||||
|
const showDialog = tzDifferent && !document.cookie.includes("calcom-timezone-dialog=1");
|
||||||
|
setOpen(showDialog);
|
||||||
|
}, [currentTz, isLoading, userTz]);
|
||||||
|
|
||||||
|
// save cookie to not show again
|
||||||
|
function onCancel(maxAge: number, toast: boolean) {
|
||||||
|
setOpen(false);
|
||||||
|
document.cookie = `calcom-timezone-dialog=1;max-age=${maxAge}`;
|
||||||
|
toast && showToast(t("we_wont_show_again"), "success");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogContent
|
||||||
|
title={t("update_timezone_question")}
|
||||||
|
description={t("update_timezone_description", { formattedCurrentTz })}
|
||||||
|
type="creation"
|
||||||
|
actionText={t("update_timezone")}
|
||||||
|
actionOnClick={() => updateTimezone()}
|
||||||
|
closeText={t("dont_update")}
|
||||||
|
onInteractOutside={() => onCancel(86400, false) /* 1 day expire */}
|
||||||
|
actionOnClose={() => onCancel(7776000, true) /* 3 months expire */}>
|
||||||
|
{/* todo: save this in db and auto-update when timezone changes (be able to disable??? if yes, /settings)
|
||||||
|
<Checkbox description="Always update timezone" />
|
||||||
|
*/}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import React, { ReactNode, useState } from "react";
|
|||||||
import { Icon } from "react-feather";
|
import { Icon } from "react-feather";
|
||||||
|
|
||||||
import classNames from "@calcom/lib/classNames";
|
import classNames from "@calcom/lib/classNames";
|
||||||
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
|
||||||
import Button from "./Button";
|
import Button from "./Button";
|
||||||
|
|
||||||
@@ -79,7 +80,10 @@ type DialogContentProps = React.ComponentProps<typeof DialogPrimitive["Content"]
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps>(
|
export const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps>(
|
||||||
({ children, Icon, actionProps, ...props }, forwardedRef) => (
|
({ children, title, Icon, actionProps, ...props }, forwardedRef) => {
|
||||||
|
const { t } = useLocale();
|
||||||
|
|
||||||
|
return (
|
||||||
<DialogPrimitive.Portal>
|
<DialogPrimitive.Portal>
|
||||||
<DialogPrimitive.Overlay className="fadeIn fixed inset-0 z-40 bg-gray-500 bg-opacity-75 transition-opacity" />
|
<DialogPrimitive.Overlay className="fadeIn fixed inset-0 z-40 bg-gray-500 bg-opacity-75 transition-opacity" />
|
||||||
{/*zIndex one less than Toast */}
|
{/*zIndex one less than Toast */}
|
||||||
@@ -100,7 +104,7 @@ export const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps
|
|||||||
ref={forwardedRef}>
|
ref={forwardedRef}>
|
||||||
{props.type === "creation" && (
|
{props.type === "creation" && (
|
||||||
<div>
|
<div>
|
||||||
{props.title && <DialogHeader title={props.title} />}
|
{title && <DialogHeader title={title} />}
|
||||||
{props.description && <p className="pb-5 text-sm text-gray-500">{props.description}</p>}
|
{props.description && <p className="pb-5 text-sm text-gray-500">{props.description}</p>}
|
||||||
<div className="flex flex-col space-y-6">{children}</div>
|
<div className="flex flex-col space-y-6">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -113,7 +117,7 @@ export const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
{props.title && <DialogHeader title={props.title} />}
|
{title && <DialogHeader title={title} />}
|
||||||
{props.description && <p className="mb-6 text-sm text-gray-500">{props.description}</p>}
|
{props.description && <p className="mb-6 text-sm text-gray-500">{props.description}</p>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -124,7 +128,7 @@ export const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps
|
|||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
{/* This will require the i18n string passed in */}
|
{/* This will require the i18n string passed in */}
|
||||||
<Button color="minimal" onClick={props.actionOnClose}>
|
<Button color="minimal" onClick={props.actionOnClose}>
|
||||||
{props.closeText ?? "Close"}
|
{props.closeText ?? t("close")}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
{props.actionOnClick ? (
|
{props.actionOnClick ? (
|
||||||
@@ -145,7 +149,8 @@ export const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps
|
|||||||
)}
|
)}
|
||||||
</DialogPrimitive.Content>
|
</DialogPrimitive.Content>
|
||||||
</DialogPrimitive.Portal>
|
</DialogPrimitive.Portal>
|
||||||
)
|
);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
type DialogHeaderProps = {
|
type DialogHeaderProps = {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import dayjs from "@calcom/dayjs";
|
|||||||
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||||
import ImpersonatingBanner from "@calcom/features/ee/impersonation/components/ImpersonatingBanner";
|
import ImpersonatingBanner from "@calcom/features/ee/impersonation/components/ImpersonatingBanner";
|
||||||
import HelpMenuItem from "@calcom/features/ee/support/components/HelpMenuItem";
|
import HelpMenuItem from "@calcom/features/ee/support/components/HelpMenuItem";
|
||||||
import UserV2OptInBanner from "@calcom/features/users/components/UserV2OptInBanner";
|
|
||||||
import CustomBranding from "@calcom/lib/CustomBranding";
|
import CustomBranding from "@calcom/lib/CustomBranding";
|
||||||
import classNames from "@calcom/lib/classNames";
|
import classNames from "@calcom/lib/classNames";
|
||||||
import { JOIN_SLACK, ROADMAP, DESKTOP_APP_LINK, WEBAPP_URL } from "@calcom/lib/constants";
|
import { JOIN_SLACK, ROADMAP, DESKTOP_APP_LINK, WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
@@ -27,6 +26,7 @@ import Dropdown, {
|
|||||||
DropdownMenuPortal,
|
DropdownMenuPortal,
|
||||||
} from "@calcom/ui/Dropdown";
|
} from "@calcom/ui/Dropdown";
|
||||||
import { Icon } from "@calcom/ui/Icon";
|
import { Icon } from "@calcom/ui/Icon";
|
||||||
|
import TimezoneChangeDialog from "@calcom/ui/TimezoneChangeDialog";
|
||||||
import Button from "@calcom/ui/v2/core/Button";
|
import Button from "@calcom/ui/v2/core/Button";
|
||||||
|
|
||||||
/* TODO: Get this from endpoint */
|
/* TODO: Get this from endpoint */
|
||||||
@@ -136,6 +136,9 @@ const Layout = (props: LayoutProps) => {
|
|||||||
<Toaster position="bottom-right" />
|
<Toaster position="bottom-right" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* todo: only run this if timezone is different */}
|
||||||
|
<TimezoneChangeDialog />
|
||||||
|
|
||||||
<div className="flex h-screen overflow-hidden" data-testid="dashboard-shell">
|
<div className="flex h-screen overflow-hidden" data-testid="dashboard-shell">
|
||||||
{props.SidebarContainer || <SideBarContainer />}
|
{props.SidebarContainer || <SideBarContainer />}
|
||||||
<div className="flex w-0 flex-1 flex-col overflow-hidden">
|
<div className="flex w-0 flex-1 flex-col overflow-hidden">
|
||||||
@@ -783,7 +786,7 @@ export function ShellMain(props: LayoutProps) {
|
|||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
props.backPath ? "relative" : "fixed right-4 bottom-[75px] z-40 ",
|
props.backPath ? "relative" : "fixed right-4 bottom-[75px] z-40 ",
|
||||||
"cta mb-4 flex-shrink-0 sm:relative sm:bottom-auto sm:right-auto sm:z-0"
|
"cta mb-4 flex-shrink-0 sm:relative sm:bottom-auto sm:right-auto"
|
||||||
)}>
|
)}>
|
||||||
{props.CTA}
|
{props.CTA}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -164,6 +164,26 @@ type InputFieldProps = {
|
|||||||
labelClassName?: string;
|
labelClassName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type AddonProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
isFilled?: boolean;
|
||||||
|
className?: string;
|
||||||
|
error?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Addon = ({ isFilled, children, className, error }: AddonProps) => (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
"addon-wrapper h-9 border border-gray-300 px-3",
|
||||||
|
isFilled && "bg-gray-100",
|
||||||
|
className
|
||||||
|
)}>
|
||||||
|
<div className={classNames("flex h-full flex-col justify-center px-1 text-sm", error && "text-red-900")}>
|
||||||
|
<span className="whitespace-nowrap py-2.5">{children}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function InputField(props, ref) {
|
const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function InputField(props, ref) {
|
||||||
const id = useId();
|
const id = useId();
|
||||||
const { t: _t, isLocaleReady, i18n } = useLocale();
|
const { t: _t, isLocaleReady, i18n } = useLocale();
|
||||||
@@ -200,26 +220,12 @@ const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function InputF
|
|||||||
</Skeleton>
|
</Skeleton>
|
||||||
)}
|
)}
|
||||||
{addOnLeading || addOnSuffix ? (
|
{addOnLeading || addOnSuffix ? (
|
||||||
<div
|
<div className="relative mb-1 flex items-center rounded-md focus-within:outline-none focus-within:ring-2 focus-within:ring-neutral-800 focus-within:ring-offset-1">
|
||||||
className={classNames(
|
{addOnLeading && (
|
||||||
"relative mb-1 flex items-center rounded-md focus-within:outline-none focus-within:ring-2 focus-within:ring-neutral-800 focus-within:ring-offset-1",
|
<Addon isFilled={addOnFilled} className="rounded-l-md border-r-0">
|
||||||
addOnSuffix && "group flex-row-reverse"
|
{addOnLeading}
|
||||||
)}>
|
</Addon>
|
||||||
<div
|
)}
|
||||||
className={classNames(
|
|
||||||
"addon-wrapper h-9 border border-gray-300",
|
|
||||||
addOnFilled && "bg-gray-100",
|
|
||||||
addOnLeading && "rounded-l-md border-r-0 px-3",
|
|
||||||
addOnSuffix && "rounded-r-md border-l-0 px-3"
|
|
||||||
)}>
|
|
||||||
<div
|
|
||||||
className={classNames(
|
|
||||||
"flex h-full flex-col justify-center px-1 text-sm",
|
|
||||||
props.error && "text-red-900"
|
|
||||||
)}>
|
|
||||||
<span className="whitespace-nowrap py-2.5">{addOnLeading || addOnSuffix}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Input
|
<Input
|
||||||
id={id}
|
id={id}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
@@ -232,6 +238,11 @@ const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function InputF
|
|||||||
{...passThrough}
|
{...passThrough}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
/>
|
/>
|
||||||
|
{addOnSuffix && (
|
||||||
|
<Addon isFilled={addOnFilled} className="rounded-r-md border-l-0">
|
||||||
|
{addOnSuffix}
|
||||||
|
</Addon>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Input id={id} placeholder={placeholder} className={className} {...passThrough} ref={ref} />
|
<Input id={id} placeholder={placeholder} className={className} {...passThrough} ref={ref} />
|
||||||
|
|||||||
@@ -47,9 +47,7 @@ const tabs: VerticalTabItemProps[] = [
|
|||||||
name: "billing",
|
name: "billing",
|
||||||
href: "/settings/billing",
|
href: "/settings/billing",
|
||||||
icon: Icon.FiCreditCard,
|
icon: Icon.FiCreditCard,
|
||||||
children: [
|
children: [{ name: "manage_billing", href: "/settings/billing" }],
|
||||||
{ name: "Manage Billing", href: "/api/integrations/stripepayment/portal", isExternalLink: true },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "developer",
|
name: "developer",
|
||||||
@@ -329,7 +327,7 @@ export default function SettingsLayout({
|
|||||||
<MobileSettingsContainer onSideContainerOpen={() => setSideContainerOpen(!sideContainerOpen)} />
|
<MobileSettingsContainer onSideContainerOpen={() => setSideContainerOpen(!sideContainerOpen)} />
|
||||||
}>
|
}>
|
||||||
<div className="flex flex-1 [&>*]:flex-1">
|
<div className="flex flex-1 [&>*]:flex-1">
|
||||||
<div className="mx-auto max-w-3xl justify-center">
|
<div className="mx-auto max-w-full justify-center md:max-w-3xl">
|
||||||
<ShellHeader />
|
<ShellHeader />
|
||||||
<ErrorBoundary>{children}</ErrorBoundary>
|
<ErrorBoundary>{children}</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user