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:
parent
62ab3056a5
commit
3a35eb413f
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
)}
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
Loading…
Reference in New Issue
Block a user