V2/settings shell (#3987)

* Fix breadcrumb colors

* HorizontalTabs

* Team List Item WIP

* Horizontal Tabs

* Cards

* Remove team list item WIP

* Login Page

* Add welcome back i118n

* EventType page work

* Update EventType Icons

* WIP Availability

* Horizontal Tab Work

* Add build command for in root

* Update build DIr/command

* Add Edit Button + change buttons to v2

* Availablitiy page

* Fix IPAD

* Make mobile look a little nicer

* WIP bookingshell

* Remove list items from breaking build

* Mian bulk of Booking Page.

* Few updates to components

* Fix chormatic feedback

* Fix banner

* Fix Empty Screen

* Text area + embded window fixes

* Semi fix avatar

* Troubleshoot container + Active on count

* Improve mobile

* NITS

* Fix padding on input

* Fix icons

* Starting to move event types settings to tabs

* Begin migration to single page form

* Single page tabs

* Limits Page

* Advanced tab

* Add RHF to dependancies

* Most of advanced tab

* Solved RHF mismtach

* Build fixes

* RHF conditionals fixes

* Improved legibility

* Major refactor/organisation into optional V2 UI

* Portal EditLocationModal

* Fix dialoug form

* Update imports

* Auto Animate + custom inputs WIP

* Custom Inputs

* WIP Apps

* Fixing stories imports

* Stripe app

* Remove duplicate dialog

* Remove duplicate dialog

* Fix embed URL

* Fix app toggles + number of active apps

* Fix container padding on disabledBorder prop

* Removes strict

* EventType Team page WIP

* Fix embed

* NIT

* Add Darkmode gray color

* V2 Shell WIP

* Create my account folder

* Add profile section

* Fix headings on shell V2

* Fix mobile layout with V2 shell

* V2 create event type button

* Checked Team Select

* Hidden to happen on save - not on toggle

* Team Attendee Select animation

* WIP

* Fix scheduling type and remove multi select label

* Fix overflow on teams url

* Finish profile fields

* Show toast on success

* General tab WIP

* Even Type move order handles

* Add switching of destination calendar

* List calendar and delete

* Render empty screenwhen no calendars

* Fix Embed TS errors

* Fix TS errors

* Fix Eslint errors

* Fix TS errors for UI

* Fix ESLINT error

* added SidebarCard for promo to v2 and storybook (#3906)

Co-authored-by: Julian Benegas <julianbenegas99@gmail.com>
Co-authored-by: Alan <alannnc@gmail.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>

* Tooltip Provider - Wrapper due to dep upgrade

* public event type list darkmode

* V2 Color changes to public booking

* Remove unused component

* Fix typecheck

* Transfer to SSR

* Appearance screen made

* V2 image uploader

* WIP appearance page

* Remove unnecessary data from viewer.me

* Add profile translations

* Add translations to general page

* Add calendar switch

* Add calendar switch

* Add translations to appearance page

* Clean up conferencing page

* Clean up appearance page

* Draft shell on md screen

* fixed slots availability by fixing buffertime (#3756)

Co-authored-by: Peer Richelsen <peeroke@gmail.com>

* Hidding import features from other services (#3970)

* Update PULL_REQUEST_TEMPLATE.md

* Fixes CLS when loading Avatars (#3973)

* Fixes CLS when loading Avatars

* Update packages/ui/v2/core/Avatar.tsx

Co-authored-by: Leo Giovanetti <hello@leog.me>

Co-authored-by: Leo Giovanetti <hello@leog.me>

* New Crowdin translations by Github Action (#3954)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>

* Adding continue button to connectCalendar on getting-started view  (#3971)

* Adding continue button to connectCalendar view getting-started

* Fixing extra space

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Leo Giovanetti <hello@leog.me>

* 2.0 Settings / My Account {View} (#3874)

* Fix breadcrumb colors

* HorizontalTabs

* Team List Item WIP

* Horizontal Tabs

* Cards

* Remove team list item WIP

* Login Page

* Add welcome back i118n

* EventType page work

* Update EventType Icons

* WIP Availability

* Horizontal Tab Work

* Add build command for in root

* Update build DIr/command

* Add Edit Button + change buttons to v2

* Availablitiy page

* Fix IPAD

* Make mobile look a little nicer

* WIP bookingshell

* Remove list items from breaking build

* Mian bulk of Booking Page.

* Few updates to components

* Fix chormatic feedback

* Fix banner

* Fix Empty Screen

* Text area + embded window fixes

* Semi fix avatar

* Troubleshoot container + Active on count

* Improve mobile

* NITS

* Fix padding on input

* Fix icons

* Starting to move event types settings to tabs

* Begin migration to single page form

* Single page tabs

* Limits Page

* Advanced tab

* Add RHF to dependancies

* Most of advanced tab

* Solved RHF mismtach

* Build fixes

* RHF conditionals fixes

* Improved legibility

* Major refactor/organisation into optional V2 UI

* Portal EditLocationModal

* Fix dialoug form

* Update imports

* Auto Animate + custom inputs WIP

* Custom Inputs

* WIP Apps

* Fixing stories imports

* Stripe app

* Remove duplicate dialog

* Remove duplicate dialog

* Fix embed URL

* Fix app toggles + number of active apps

* Fix container padding on disabledBorder prop

* Removes strict

* EventType Team page WIP

* Fix embed

* NIT

* Add Darkmode gray color

* V2 Shell WIP

* Create my account folder

* Add profile section

* Fix headings on shell V2

* Fix mobile layout with V2 shell

* V2 create event type button

* Checked Team Select

* Hidden to happen on save - not on toggle

* Team Attendee Select animation

* WIP

* Fix scheduling type and remove multi select label

* Fix overflow on teams url

* Finish profile fields

* Show toast on success

* General tab WIP

* Even Type move order handles

* Add switching of destination calendar

* List calendar and delete

* Render empty screenwhen no calendars

* Fix Embed TS errors

* Fix TS errors

* Fix Eslint errors

* Fix TS errors for UI

* Fix ESLINT error

* added SidebarCard for promo to v2 and storybook (#3906)

Co-authored-by: Julian Benegas <julianbenegas99@gmail.com>
Co-authored-by: Alan <alannnc@gmail.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>

* Tooltip Provider - Wrapper due to dep upgrade

* public event type list darkmode

* V2 Color changes to public booking

* Remove unused component

* Fix typecheck

* Transfer to SSR

* Appearance screen made

* V2 image uploader

* WIP appearance page

* Remove unnecessary data from viewer.me

* Add profile translations

* Add translations to general page

* Add calendar switch

* Add calendar switch

* Add translations to appearance page

* Clean up conferencing page

* Settings sidebar fixes

* Updates middleware

* Update SettingsLayout.tsx

* Settings layout improvements

* Type fix

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Julian Benegas <julianbenegas99@gmail.com>
Co-authored-by: Alan <alannnc@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* AppStore CLI: Making video app creation a breeze with major cleanup of locations code throughout (#3825)

* Fix breadcrumb colors

* HorizontalTabs

* Team List Item WIP

* Horizontal Tabs

* Cards

* Remove team list item WIP

* Login Page

* Add welcome back i118n

* EventType page work

* Update EventType Icons

* WIP Availability

* Horizontal Tab Work

* Add build command for in root

* Update build DIr/command

* Add Edit Button + change buttons to v2

* Availablitiy page

* Fix IPAD

* Make mobile look a little nicer

* WIP bookingshell

* Remove list items from breaking build

* Add Embed ModalBox for routing forms

* Mian bulk of Booking Page.

* Few updates to components

* Fix chormatic feedback

* Add duplicate form support

* Fix duplication logic

* Change to feathericons everywhere and other fixes

* Dont allow routes for fallback route

* Fix banner

* Fix Empty Screen

* Text area + embded window fixes

* Semi fix avatar

* Fix all TS issues

* Fix tests

* Troubleshoot container + Active on count

* Support routing using query params

* Improve mobile

* NITS

* Fix padding on input

* Support multiselect in router endpoint

* Fix the issue where app goes in embed mode after viewing embed once

* Fix icons

* Add router url tests

* Add Responses download and form toggling tests

* Add required validation test

* Change Icons everywhere

* App typeform app

* Improvements in cli

* Starting to move event types settings to tabs

* Begin migration to single page form

* Single page tabs

* Limits Page

* Advanced tab

* Add RHF to dependancies

* Add typeform how-to-use page

* Add typeform how-to-use page and screenshots

* Most of advanced tab

* Solved RHF mismtach

* Build fixes

* RHF conditionals fixes

* Improved legibility

* Fix TS error

* Add missing image

* Update CliApp.tsx

* Major refactor/organisation into optional V2 UI

* Portal EditLocationModal

* Fix dialoug form

* Update imports

* Auto Animate + custom inputs WIP

* Custom Inputs

* WIP Apps

* Fixing stories imports

* Stripe app

* Remove duplicate dialog

* Remove duplicate dialog

* Major locations cleanup, 10s of bug fixes and app-store improvements

* Fix missing pieces

* More fixes

* Fix embed URL

* Fix app toggles + number of active apps

* Fix container padding on disabledBorder prop

* Removes strict

* more fixes

* EventType Team page WIP

* Fix embed

* NIT

* Add Darkmode gray color

* V2 Shell WIP

* Fix headings on shell V2

* Fix mobile layout with V2 shell

* V2 create event type button

* Checked Team Select

* Hidden to happen on save - not on toggle

* Team Attendee Select animation

* Fix scheduling type and remove multi select label

* Fix overflow on teams url

* Revert console

* Revert api

* Fix Embed TS errors

* Fix TS errors

* Fix Eslint errors

* Fix TS errors for UI

* Fix ESLINT error

* Fix TS errors

* Add missing import

* Fix CLI

* Add a default placeholder

* Remove hardcoded daily:integrations

* Fix message for payment page

* Revert api and console to main

* Update README

* Fix TS errors

* Fix Lint warnings

* Fix Tests

* Fix conflict issues

* Fix conflict issues

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: zomars <zomars@me.com>

* Button to test a workflow action (#3873)

* add Test action button + UI improvements

* add test action functionality

* add confirmation dialog before sending SMS

* code clean up

* show error message if test action fails

* disable test action button in edit mode

* fixes SMS testing

* use updated values

* fix wrongly updated data in useEffect

* fix typo

* code clean up

* fix UI issue in mobile view

* small design fix

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* Improve CLI App Help Text (#3982)

* Adds deprecation notice to QueryCell (#3977)

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>

* added campfire.to app (#3984)

* fixed file size campfire (#3985)

* added campfire.to app

* fixed file size

* Draft shell on md screen

* Draft small settings bar

* Add padding to dropdown menu

* WIP

* Create mobile settings nav

* Fix conferencing render

* Only display mobile settings nav

* Clean up my account section

* Clean up

* Remove old draft shell

* Remove old draft shell

* Implement changes from #3817

* Revise sidebar

* WIP

* Side navigation drawer

* Fix type error

* Fix lint problem

* Get rid of main top padding on main shell

* Fix e2e test

* Fix type error

* Remove unused imports

* Add back button function

* Update SettingsSidebarContainer.tsx

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Julian Benegas <julianbenegas99@gmail.com>
Co-authored-by: Alan <alannnc@gmail.com>
Co-authored-by: Kszemi <mkrzemien11@gmail.com>
Co-authored-by: Leo Giovanetti <hello@leog.me>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
This commit is contained in:
Joe Au-Yeung 2022-09-06 14:23:17 -04:00 committed by GitHub
parent 62ab3056a5
commit 3a35eb413f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 380 additions and 227 deletions

View File

@ -1,10 +1,14 @@
import { SyntheticEvent, useState } from "react";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/lib/auth";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import Button from "@calcom/ui/v2/core/Button";
import { Dialog, DialogContent } from "@calcom/ui/v2/core/Dialog";
import { Form, Label } from "@calcom/ui/v2/core/form/fields";
import { PasswordField } from "@calcom/ui/v2/core/form/fields";
import { ErrorCode } from "@lib/auth";
import TwoFactor from "@components/auth/TwoFactor";
import TwoFactorAuthAPI from "./TwoFactorAuthAPI";
@ -18,20 +22,24 @@ interface DisableTwoFactorAuthModalProps {
onDisable: () => void;
}
interface DisableTwoFactorValues {
totpCode: string;
password: string;
}
const DisableTwoFactorAuthModal = ({
onDisable,
onCancel,
open,
onOpenChange,
}: DisableTwoFactorAuthModalProps) => {
const [password, setPassword] = useState("");
const [isDisabling, setIsDisabling] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const { t } = useLocale();
async function handleDisable(e: SyntheticEvent) {
e.preventDefault();
const form = useForm<DisableTwoFactorValues>();
async function handleDisable({ totpCode, password }: DisableTwoFactorValues) {
if (isDisabling) {
return;
}
@ -39,7 +47,7 @@ const DisableTwoFactorAuthModal = ({
setErrorMessage(null);
try {
const response = await TwoFactorAuthAPI.disable(password);
const response = await TwoFactorAuthAPI.disable(password, totpCode);
if (response.status === 200) {
onDisable();
return;
@ -48,6 +56,12 @@ const DisableTwoFactorAuthModal = ({
const body = await response.json();
if (body.error === ErrorCode.IncorrectPassword) {
setErrorMessage(t("incorrect_password"));
}
if (body.error === ErrorCode.SecondFactorRequired) {
setErrorMessage(t("2fa_required"));
}
if (body.error === ErrorCode.IncorrectTwoFactorCode) {
setErrorMessage(t("incorrect_2fa"));
} else {
setErrorMessage(t("something_went_wrong"));
}
@ -66,39 +80,31 @@ const DisableTwoFactorAuthModal = ({
description={t("disable_2fa_recommendation")}
type="creation"
useOwnActionButtons>
<form onSubmit={handleDisable}>
<Form form={form} handleSubmit={handleDisable}>
<div className="mb-4">
<label htmlFor="password" className="mt-4 block text-sm font-medium text-gray-700">
{t("password")}
</label>
<div className="mt-1">
<input
type="password"
name="password"
id="password"
required
value={password}
onInput={(e) => setPassword(e.currentTarget.value)}
className="block w-full rounded-sm border-gray-300 text-sm"
/>
</div>
<PasswordField
labelProps={{
className: "block text-sm font-medium text-gray-700",
}}
{...form.register("password")}
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-black focus:outline-none focus:ring-black"
/>
<Label className="mt-4"> {t("2fa_code")}</Label>
<TwoFactor center={false} />
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>}
</div>
</form>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<Button
type="submit"
className="ltr:ml-2 rtl:mr-2"
onClick={handleDisable}
disabled={password.length === 0 || isDisabling}>
{t("disable")}
</Button>
<Button color="secondary" onClick={onCancel}>
{t("cancel")}
</Button>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<Button type="submit" className="ltr:ml-2 rtl:mr-2" disabled={isDisabling}>
{t("disable")}
</Button>
<Button color="secondary" onClick={onCancel}>
{t("cancel")}
</Button>
</div>
</Form>
</DialogContent>
</Dialog>
);

View File

@ -1,10 +1,13 @@
import React, { SyntheticEvent, useState } from "react";
import React, { BaseSyntheticEvent, useState } from "react";
import { useForm } from "react-hook-form";
import { ErrorCode } from "@calcom/lib/auth";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import Button from "@calcom/ui/v2/core/Button";
import { Dialog, DialogContent } from "@calcom/ui/v2/core/Dialog";
import { Form } from "@calcom/ui/v2/core/form/fields";
import { ErrorCode } from "@lib/auth";
import TwoFactor from "@components/auth/TwoFactor";
import TwoFactorAuthAPI from "./TwoFactorAuthAPI";
@ -41,8 +44,14 @@ const WithStep = ({
return step === current ? children : null;
};
interface EnableTwoFactorValues {
totpCode: string;
}
const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: EnableTwoFactorModalProps) => {
const { t } = useLocale();
const form = useForm<EnableTwoFactorValues>();
const setupDescriptions = {
[SetupStep.ConfirmPassword]: t("2fa_confirm_current_password"),
[SetupStep.DisplayQrCode]: t("2fa_scan_image_or_use_code"),
@ -50,13 +59,12 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
};
const [step, setStep] = useState(SetupStep.ConfirmPassword);
const [password, setPassword] = useState("");
const [totpCode, setTotpCode] = useState("");
const [dataUri, setDataUri] = useState("");
const [secret, setSecret] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
async function handleSetup(e: SyntheticEvent) {
async function handleSetup(e: React.FormEvent) {
e.preventDefault();
if (isSubmitting) {
@ -90,10 +98,10 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
}
}
async function handleEnable(e: SyntheticEvent) {
e.preventDefault();
async function handleEnable({ totpCode }: EnableTwoFactorValues, e: BaseSyntheticEvent | undefined) {
e?.preventDefault();
if (isSubmitting || totpCode.length !== 6) {
if (isSubmitting) {
return;
}
@ -128,11 +136,7 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
title={t("enable_2fa")}
description={setupDescriptions[step]}
type="creation"
useOwnActionButtons
// Icon={Icon.FiAlertTriangle}>
>
{/* <TwoFactorModalHeader title={t("enable_2fa")} description={setupDescriptions[step]} /> */}
useOwnActionButtons>
<WithStep step={SetupStep.ConfirmPassword} current={step}>
<form onSubmit={handleSetup}>
<div className="mb-4">
@ -166,64 +170,42 @@ const EnableTwoFactorModal = ({ onEnable, onCancel, open, onOpenChange }: Enable
<p className="text-center font-mono text-xs">{secret}</p>
</>
</WithStep>
<WithStep step={SetupStep.EnterTotpCode} current={step}>
<form onSubmit={handleEnable}>
<Form handleSubmit={handleEnable} form={form}>
<WithStep step={SetupStep.EnterTotpCode} current={step}>
<div className="mb-4">
<label htmlFor="code" className="mt-4 block text-sm font-medium text-gray-700">
{t("code")}
</label>
<div className="mt-1">
<input
type="text"
name="code"
id="code"
required
value={totpCode}
maxLength={6}
minLength={6}
inputMode="numeric"
onInput={(e) => setTotpCode(e.currentTarget.value)}
className="block w-full rounded-sm border-gray-300 text-sm"
autoComplete="one-time-code"
/>
</div>
<TwoFactor center />
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>}
</div>
</form>
</WithStep>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<WithStep step={SetupStep.ConfirmPassword} current={step}>
<Button
type="submit"
className="ltr:ml-2 rtl:mr-2"
onClick={handleSetup}
disabled={password.length === 0 || isSubmitting}>
{t("continue")}
</Button>
</WithStep>
<WithStep step={SetupStep.DisplayQrCode} current={step}>
<Button
type="submit"
className="ltr:ml-2 rtl:mr-2"
onClick={() => setStep(SetupStep.EnterTotpCode)}>
{t("continue")}
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<WithStep step={SetupStep.ConfirmPassword} current={step}>
<Button
type="submit"
className="ltr:ml-2 rtl:mr-2"
onClick={handleSetup}
disabled={password.length === 0 || isSubmitting}>
{t("continue")}
</Button>
</WithStep>
<WithStep step={SetupStep.DisplayQrCode} current={step}>
<Button
type="submit"
className="ltr:ml-2 rtl:mr-2"
onClick={() => setStep(SetupStep.EnterTotpCode)}>
{t("continue")}
</Button>
</WithStep>
<WithStep step={SetupStep.EnterTotpCode} current={step}>
<Button type="submit" className="ltr:ml-2 rtl:mr-2" disabled={isSubmitting}>
{t("enable")}
</Button>
</WithStep>
<Button color="secondary" onClick={onCancel}>
{t("cancel")}
</Button>
</WithStep>
<WithStep step={SetupStep.EnterTotpCode} current={step}>
<Button
type="submit"
className="ltr:ml-2 rtl:mr-2"
onClick={handleEnable}
disabled={totpCode.length !== 6 || isSubmitting}>
{t("enable")}
</Button>
</WithStep>
<Button color="secondary" onClick={onCancel}>
{t("cancel")}
</Button>
</div>
</div>
</Form>
</DialogContent>
</Dialog>
);

View File

@ -19,10 +19,10 @@ const TwoFactorAuthAPI = {
});
},
async disable(password: string) {
async disable(password: string, code: string) {
return fetch("/api/auth/two-factor/totp/disable", {
method: "POST",
body: JSON.stringify({ password }),
body: JSON.stringify({ password, code }),
headers: {
"Content-Type": "application/json",
},

View File

@ -159,7 +159,7 @@ export default function Login({
</div>
</div>
{twoFactorRequired && <TwoFactor />}
{twoFactorRequired && <TwoFactor center />}
{errorMessage && <Alert severity="error" title={errorMessage} />}
<div className="pb-8">

View File

@ -27,21 +27,21 @@ const ConferencingLayout = (props: inferSSRProps<typeof getServerSideProps>) =>
// });
return (
<div className="m-4 rounded-md border-neutral-200 bg-white sm:mx-0 md:border xl:mt-0">
<div className="w-40 rounded-md border border-neutral-200 bg-white sm:mx-0 md:w-96 xl:mt-0">
<Meta title="conferencing" description="conferencing_description" />
{apps.map((app) => (
<div
key={app.title}
className="flex w-full flex-1 items-center space-x-3 border-b py-5 rtl:space-x-reverse">
className="flex flex-1 items-center space-x-3 border-b py-5 px-4 rtl:space-x-reverse">
<img className="h-10 w-10" src={app.logo} alt={app.title} />
<div className="flex-grow truncate pl-2">
<div className="truncate pl-2">
<h3 className="truncate text-sm font-medium text-neutral-900">{app.title}</h3>
<p className="truncate text-sm text-gray-500">{app.description}</p>
</div>
<Dropdown>
<DropdownMenuTrigger className="focus:ring-brand-900 mr-4 block h-[36px] w-auto justify-center rounded-md border border-gray-200 bg-transparent text-gray-700 focus:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-1">
<DropdownMenuTrigger className="focus:ring-brand-900 block h-[36px] w-auto justify-center rounded-md border border-gray-200 bg-transparent text-gray-700 focus:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-1">
<Icon.FiMoreHorizontal className="group-hover:text-gray-800" />
</DropdownMenuTrigger>
<DropdownMenuContent>

View File

@ -1,29 +1,37 @@
import crypto from "crypto";
import { GetServerSidePropsContext } from "next";
import { signOut } from "next-auth/react";
import { Trans } from "next-i18next";
import { useRef, useState } from "react";
import { useRef, useState, BaseSyntheticEvent } from "react";
import { Controller, useForm } from "react-hook-form";
import { ErrorCode, getSession } from "@calcom/lib/auth";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import prisma from "@calcom/prisma";
import { TRPCClientErrorLike } from "@calcom/trpc/client";
import { trpc } from "@calcom/trpc/react";
import { AppRouter } from "@calcom/trpc/server/routers/_app";
import { Icon } from "@calcom/ui";
import { Alert } from "@calcom/ui/Alert";
import Avatar from "@calcom/ui/v2/core/Avatar";
import { Button } from "@calcom/ui/v2/core/Button";
import { Dialog, DialogContent, DialogTrigger } from "@calcom/ui/v2/core/Dialog";
import Meta from "@calcom/ui/v2/core/Meta";
import { Form, Label, TextField } from "@calcom/ui/v2/core/form/fields";
import { Form, Label, TextField, PasswordField } from "@calcom/ui/v2/core/form/fields";
import { getLayout } from "@calcom/ui/v2/core/layouts/AdminLayout";
import showToast from "@calcom/ui/v2/core/notifications";
import { getSession } from "@lib/auth";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import TwoFactor from "@components/auth/TwoFactor";
import ImageUploader from "@components/v2/settings/ImageUploader";
interface DeleteAccountValues {
totpCode: string;
}
const ProfileView = (props: inferSSRProps<typeof getServerSideProps>) => {
const { t } = useLocale();
const utils = trpc.useContext();
const { user } = props;
// const { data: user, isLoading } = trpc.useQuery(["viewer.me"]);
@ -37,16 +45,16 @@ const ProfileView = (props: inferSSRProps<typeof getServerSideProps>) => {
});
const [deleteAccountOpen, setDeleteAccountOpen] = useState(false);
const [hasDeleteErrors, setHasDeleteErrors] = useState(false);
const [deleteErrorMessage, setDeleteErrorMessage] = useState("");
const deleteAccount = async () => {
await fetch("/api/user/me", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}).catch((e) => {
console.error(`Error Removing user: ${user?.id}, email: ${user?.email} :`, e);
});
const form = useForm<DeleteAccountValues>();
const onDeleteMeSuccessMutation = async () => {
await utils.invalidateQueries(["viewer.me"]);
showToast(t("Your account was deleted"), "success");
setHasDeleteErrors(false); // dismiss any open errors
if (process.env.NEXT_PUBLIC_WEBAPP_URL === "https://app.cal.com") {
signOut({ callbackUrl: "/auth/logout?survey=true" });
} else {
@ -54,6 +62,30 @@ const ProfileView = (props: inferSSRProps<typeof getServerSideProps>) => {
}
};
const onDeleteMeErrorMutation = (error: TRPCClientErrorLike<AppRouter>) => {
setHasDeleteErrors(true);
setDeleteErrorMessage(errorMessages[error.message]);
};
const deleteMeMutation = trpc.useMutation("viewer.deleteMe", {
onSuccess: onDeleteMeSuccessMutation,
onError: onDeleteMeErrorMutation,
async onSettled() {
await utils.invalidateQueries(["viewer.me"]);
},
});
const onConfirmButton = (e: Event | React.MouseEvent<HTMLElement, MouseEvent>) => {
e.preventDefault();
const totpCode = form.getValues("totpCode");
const password = passwordRef.current.value;
deleteMeMutation.mutate({ password, totpCode });
};
const onConfirm = ({ totpCode }: DeleteAccountValues, e: BaseSyntheticEvent | undefined) => {
e?.preventDefault();
const password = passwordRef.current.value;
deleteMeMutation.mutate({ password, totpCode });
};
const formMethods = useForm({
defaultValues: {
avatar: user.avatar || "",
@ -63,7 +95,16 @@ const ProfileView = (props: inferSSRProps<typeof getServerSideProps>) => {
},
});
const avatarRef = useRef<HTMLInputElement>(null!);
const passwordRef = useRef<HTMLInputElement>(null!);
const errorMessages: { [key: string]: string } = {
[ErrorCode.SecondFactorRequired]: t("2fa_enabled_instructions"),
[ErrorCode.IncorrectPassword]: `${t("incorrect_password")} ${t("please_try_again")}`,
[ErrorCode.UserNotFound]: t("no_account_exists"),
[ErrorCode.IncorrectTwoFactorCode]: `${t("incorrect_2fa_code")} ${t("please_try_again")}`,
[ErrorCode.InternalServerError]: `${t("something_went_wrong")} ${t("please_try_again_and_contact_us")}`,
[ErrorCode.ThirdPartyIdentityProviderEnabled]: t("account_created_with_identity_provider"),
};
return (
<>
@ -163,17 +204,31 @@ const ProfileView = (props: inferSSRProps<typeof getServerSideProps>) => {
<DialogContent
title={t("delete_account_modal_title")}
description={t("confirm_delete_account_modal")}
type="confirmation"
type="creation"
actionText={t("delete_my_account")}
Icon={Icon.FiAlertTriangle}
actionOnClick={() => deleteAccount()}>
{/* Use trans component for translation */}
<p>
<Trans i18nKey="delete_account_warning">
Anyone who you have shared your account link with will no longer be able to book using it and
any preferences you have saved will be lost
</Trans>
</p>
actionOnClick={(e) => e && onConfirmButton(e)}>
<>
<p className="mb-7">{t("delete_account_confirmation_message")}</p>
<PasswordField
data-testid="password"
name="password"
id="password"
type="password"
autoComplete="current-password"
required
label="Password"
ref={passwordRef}
/>
{user.twoFactorEnabled && (
<Form handleSubmit={onConfirm} className="pb-4" form={form}>
<TwoFactor center={false} />
</Form>
)}
{hasDeleteErrors && <Alert severity="error" title={deleteErrorMessage} />}
</>
</DialogContent>
</Dialog>
</Form>
@ -203,6 +258,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
name: true,
bio: true,
avatar: true,
twoFactorEnabled: true,
},
});

View File

@ -2,6 +2,7 @@ import { IdentityProvider } from "@prisma/client";
import { Trans } from "next-i18next";
import { Controller, useForm } from "react-hook-form";
import { identityProviderNameMap } from "@calcom/lib/auth";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Button } from "@calcom/ui/v2/core/Button";
@ -10,15 +11,13 @@ import { Form, TextField } from "@calcom/ui/v2/core/form/fields";
import { getLayout } from "@calcom/ui/v2/core/layouts/AdminLayout";
import showToast from "@calcom/ui/v2/core/notifications";
import { identityProviderNameMap } from "@lib/auth";
const PasswordView = () => {
const { t } = useLocale();
const { data: user } = trpc.useQuery(["viewer.me"]);
const mutation = trpc.useMutation("viewer.auth.changePassword", {
onSuccess: () => {
showToast(t("password_updated_successfully"), "success");
showToast(t("password_has_been_changed"), "success");
},
onError: (error) => {
showToast(`${t("error_updating_password")}, ${error.message}`, "error");

View File

@ -1119,7 +1119,23 @@
"two_factor_auth": "Two factor authentication",
"recurring_event_tab_description": "Set up a repeating schedule",
"today": "today",
"active": "active",
"appearance": "Appearance",
"appearance_subtitle": "Manage settings for your booking appearance",
"my_account": "My account",
"general": "General",
"calendars": "Calendars",
"2fa_auth": "Two factor auth",
"invoices": "Invoices",
"embeds": "Embeds",
"impersonation": "Impersonation",
"users": "Users",
"profile_description": "Manage settings for your cal profile",
"general_description": "Manage settings for your language and timezone",
"calendars_description": "Configure how your event types interact with your calendars",
"appearance_description": "Manage settings for your booking appearance",
"conferencing_description": "Manage your video conferencing apps for your meetings",
"password_description": "Manage settings for your account passwords",
"2fa_description": "Manage settings for your account passwords",
"add_variable": "Add variable",
"custom_phone_number": "Custom phone number",
"message_template": "Message template",

View File

@ -71,7 +71,7 @@ type DialogContentProps = React.ComponentProps<typeof DialogPrimitive["Content"]
Icon?: Icon;
// If this is set it allows you to overide the action buttons. Usefull if you need to use formcontext
useOwnActionButtons?: boolean;
actionOnClick?: () => void;
actionOnClick?: (e: Event | React.MouseEvent<HTMLElement, MouseEvent>) => void;
actionOnClose?: () => void;
};

View File

@ -7,7 +7,6 @@ import { Toaster } from "react-hot-toast";
import dayjs from "@calcom/dayjs";
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
import LicenseBanner from "@calcom/features/ee/common/components/LicenseBanner";
import TrialBanner from "@calcom/features/ee/common/components/TrialBanner";
import ImpersonatingBanner from "@calcom/features/ee/impersonation/components/ImpersonatingBanner";
import HelpMenuItem from "@calcom/features/ee/support/components/HelpMenuItem";
@ -120,6 +119,7 @@ export function ShellSubHeading(props: {
const Layout = (props: LayoutProps) => {
const pageTitle = typeof props.heading === "string" ? props.heading : props.title;
const router = useRouter();
return (
<>
@ -135,8 +135,8 @@ const Layout = (props: LayoutProps) => {
<Toaster position="bottom-right" />
</div>
<div className="flex h-screen overflow-hidden" data-testid="dashboard-shell">
{props.SidebarContainer || <SideBarContainer />}
<div className={classNames("flex h-screen overflow-hidden")} data-testid="dashboard-shell">
{router.route.startsWith("/v2/settings/") ? <></> : <SideBarContainer />}
<div className="flex w-0 flex-1 flex-col overflow-hidden">
<UserV2OptInBanner />
<ImpersonatingBanner />
@ -514,7 +514,9 @@ function MobileNavigationContainer() {
}
const MobileNavigation = () => {
const router = useRouter();
const isEmbed = useIsEmbed();
if (router.route.startsWith("/v2/settings/")) return null;
return (
<>
<nav
@ -680,8 +682,13 @@ export function ShellMain(props: LayoutProps) {
}
function MainContainer(props: LayoutProps) {
const router = useRouter();
return (
<main className="relative z-0 flex flex-1 flex-col overflow-y-auto bg-white focus:outline-none ">
<main
className={classNames(
"relative z-0 flex flex-1 flex-col overflow-y-auto bg-white focus:outline-none",
router.route.startsWith("/v2/settings/") ? "" : "py-2"
)}>
{/* show top navigation for md and smaller (tablet and phones) */}
<TopNavContainer />
<div className="px-4 py-2 lg:py-8 lg:px-12">
@ -697,18 +704,25 @@ function MainContainer(props: LayoutProps) {
}
function TopNavContainer() {
const router = useRouter();
const { status } = useSession();
if (status !== "authenticated") return null;
if (router.route.startsWith("/v2/settings/")) return null;
return <TopNav />;
}
function TopNav() {
const router = useRouter();
const isEmbed = useIsEmbed();
const { t } = useLocale();
return (
<nav
style={isEmbed ? { display: "none" } : {}}
className="flex items-center justify-between border-b border-gray-200 bg-gray-50 py-1.5 px-4 sm:p-4 md:hidden">
className={classNames(
"flex items-center justify-between border-b border-gray-200 bg-white p-4 md:hidden",
router.route.startsWith("/v2/settings/") && "hidden"
)}>
<Link href="/event-types">
<a>
<Logo />

View File

@ -9,7 +9,7 @@ export default function AdminLayout({
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
return (
<SettingsLayout {...rest}>
<div className="mx-auto flex max-w-4xl flex-row divide-y divide-gray-200 lg:p-12">
<div className="mx-auto flex max-w-4xl flex-row divide-y divide-gray-200 md:px-12">
<div className="flex flex-1 [&>*]:flex-1">{children}</div>
</div>
</SettingsLayout>

View File

@ -1,98 +1,48 @@
import React, { ComponentProps } from "react";
import React, { ComponentProps, useState } from "react";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "../../../Icon";
import { useMeta } from "../Meta";
import Shell from "../Shell";
import VerticalTabs, { VerticalTabItem } from "../navigation/tabs/VerticalTabs";
const tabs = [
{
name: "my_account",
href: "/settings/profile",
icon: Icon.FiUser,
children: [
{ name: "profile", href: "/settings/my-account/profile" },
{ name: "general", href: "/settings/my-account/general" },
{ name: "calendars", href: "/settings/my-account/calendars" },
{ name: "conferencing", href: "/settings/my-account/conferencing" },
{ name: "appearance", href: "/settings/my-account/appearance" },
// TODO
{ name: "referrals", href: "/settings/my-account/referrals" },
],
},
{
name: "security",
href: "/settings/security",
icon: Icon.FiKey,
children: [
//
{ name: "password", href: "/settings/security/password" },
{ name: "2fa_auth", href: "/settings/security/two-factor-auth" },
],
},
{
name: "billing",
href: "/settings/billing",
icon: Icon.FiCreditCard,
children: [
//
{ name: "invoices", href: "/settings/billing" },
],
},
{
name: "developer",
href: "/settings/developer",
icon: Icon.FiTerminal,
children: [
//
{ name: "webhooks", href: "/settings/developer" },
{ name: "api_keys", href: "/settings/developer" },
{ name: "embeds", href: "/settings/developer" },
],
},
{
name: "teams",
href: "/settings/teams",
icon: Icon.FiUsers,
},
{
name: "admin",
href: "/settings/admin",
icon: Icon.FiLock,
adminRequired: true,
children: [
//
{ name: "impersonation", href: "/settings/admin/impersonation" },
{ name: "apps", href: "/settings/admin/apps" },
{ name: "users", href: "/settings/admin/users" },
],
},
];
import MobileSettingsContainer from "../navigation/MobileSettingsContainer";
import SettingsSidebarContainer from "../navigation/SettingsSidebarContainer";
export default function SettingsLayout({
children,
...rest
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
const [sideContainerOpen, setSideContainerOpen] = useState(false);
return (
<Shell
flexChildrenContainer
{...rest}
SidebarContainer={
<VerticalTabs tabs={tabs} className="py-3 pl-3">
<VerticalTabItem
name="Settings"
href="/"
icon={Icon.FiArrowLeft}
textClassNames="text-md font-medium leading-none text-black"
className="mb-1"
/>
</VerticalTabs>
}>
<div className="flex-1 [&>*]:flex-1">
<ShellHeader />
{children}
<Shell flexChildrenContainer {...rest} SidebarContainer={<SettingsSidebarContainer />}>
<div
className={classNames(
"absolute z-40 m-0 h-screen w-screen bg-black opacity-50",
sideContainerOpen ? "" : "hidden"
)}
onClick={() => {
setSideContainerOpen(false);
}}
/>
<div className="relative md:flex">
<div className="md:hidden">
<MobileSettingsContainer onSideContainerOpen={() => setSideContainerOpen(!sideContainerOpen)} />
</div>
<div
className={classNames(
"absolute inset-y-0 z-50 m-0 h-screen w-56 transform border-gray-100 bg-gray-50 transition duration-200 ease-in-out md:relative",
sideContainerOpen ? "" : "-translate-x-full md:translate-x-0"
)}>
<SettingsSidebarContainer />
</div>
<div className="flex flex-1 [&>*]:flex-1">
<div className="color-black mt-8 justify-center px-4 sm:px-6 md:px-8 ">
<ShellHeader />
{children}
</div>
</div>
</div>
</Shell>
);
@ -104,17 +54,17 @@ function ShellHeader() {
const { meta } = useMeta();
const { t, isLocaleReady } = useLocale();
return (
<header className="block justify-between px-4 pt-8 sm:flex sm:px-6 md:px-8">
<div className="mb-8 w-full">
<header className="mx-auto block max-w-4xl justify-between sm:flex md:px-12 md:pt-8">
<div className="mb-8 w-full border-b border-gray-200 pb-8">
{meta.title && isLocaleReady ? (
<h1 className="font-cal mb-1 text-xl font-bold capitalize tracking-wide text-gray-900">
<h1 className="font-cal mb-1 text-xl font-bold capitalize tracking-wide text-black">
{t(meta.title)}
</h1>
) : (
<div className="mb-1 h-6 w-24 animate-pulse rounded-md bg-gray-200" />
)}
{meta.description && isLocaleReady ? (
<p className="text-sm text-neutral-500 ltr:mr-4 rtl:ml-4">{t(meta.description)}</p>
<p className="text-sm text-gray-600 ltr:mr-4 rtl:ml-4">{t(meta.description)}</p>
) : (
<div className="mb-1 h-6 w-32 animate-pulse rounded-md bg-gray-200" />
)}

View File

@ -0,0 +1,28 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui";
import Button from "@calcom/ui/v2/core/Button";
const MobileSettingsContainer = (props: { onSideContainerOpen: () => void }) => {
const { t } = useLocale();
return (
<>
<nav className="flex items-center justify-between border-b border-gray-100 bg-gray-50 p-4 lg:hidden">
<div className=" flex items-center space-x-3 ">
<Button
StartIcon={Icon.FiMenu}
color="minimalSecondary"
size="icon"
onClick={props.onSideContainerOpen}
/>
<a href="/" className="flex items-center space-x-2 rounded-md px-3 py-1 hover:bg-gray-200">
<Icon.FiArrowLeft className="text-gray-700" />
<p className="font-semibold text-black">{t("settings")}</p>
</a>
</div>
</nav>
</>
);
};
export default MobileSettingsContainer;

View File

@ -0,0 +1,102 @@
import { classNames } from "@calcom/lib";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "../../../Icon";
const settingsTabs = [
{
name: "my_account",
href: "/settings/my-account",
icon: Icon.FiUser,
children: [
{ name: "profile", href: "/v2/settings/my-account/profile" },
{ name: "general", href: "/v2/settings/my-account/general" },
{ name: "calendars", href: "/v2/settings/my-account/calendars" },
{ name: "conferencing", href: "/v2/settings/my-account/conferencing" },
{ name: "appearance", href: "/v2/settings/my-account/appearance" },
// TODO
// { name: "referrals", href: "/settings/my-account/referrals" },
],
},
{
name: "security",
href: "/settings/security",
icon: Icon.FiKey,
children: [
//
{ name: "password", href: "/v2/settings/security/password" },
{ name: "2fa_auth", href: "/v2/settings/security/two-factor-auth" },
],
},
{
name: "billing",
href: "/settings/billing",
icon: Icon.FiCreditCard,
children: [
//
{ name: "invoices", href: "/v2/settings/billing" },
],
},
{
name: "developer",
href: "/settings/developer",
icon: Icon.FiTerminal,
children: [
//
{ name: "webhooks", href: "/v2/settings/developer/webhooks" },
{ name: "api_keys", href: "/v2/settings/developer/api_keys" },
{ name: "embeds", href: "/v2/settings/developer/embeds" },
],
},
{
name: "teams",
href: "/settings/teams",
icon: Icon.FiUsers,
children: [],
},
{
name: "admin",
href: "/settings/admin",
icon: Icon.FiLock,
adminRequired: true,
children: [
//
{ name: "impersonation", href: "/v2/settings/admin/impersonation" },
{ name: "apps", href: "/v2/settings/admin/apps" },
{ name: "users", href: "/v2/settings/admin/users" },
],
},
];
const SettingsSidebarContainer = () => {
const { t } = useLocale();
return (
<nav className="no-scrollbar w-56 flex-col space-y-1 py-3 px-3" aria-label="Tabs">
<div className="mt-7 mb-6 ml-4 flex items-center space-x-3">
<a href={`${WEBAPP_URL}`}>
<Icon.FiArrowLeft />
</a>
<p className="font-semibold">{t("settings")}</p>
</div>
{settingsTabs.map((section, index) => (
<div key={section.name} className={classNames("ml-4", index !== 0 && "pt-3")}>
<div className="flex">
<section.icon />
<p className="ml-3 text-sm font-medium leading-5 text-gray-600">{t(section.name)}</p>
</div>
{section.children?.map((child) => (
<div key={child.name} className="ml-10 py-0.5">
<a className="text-sm font-medium text-gray-900" href={child.href}>
{t(child.name)}
</a>
</div>
))}
</div>
))}
</nav>
);
};
export default SettingsSidebarContainer;