From 0d9d182b564517641ad8b354ae21828b5b74db5e Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Date: Thu, 15 Sep 2022 05:05:26 -0400 Subject: [PATCH] Settings Improvements - tRPC, Skeletons & Screen Width Issues (#4484) * Add skeleton and trpc to profile page * Refactor general page * Calendars add skeleton loader * Add translations to calendar empty screen * Improve conferencing pages * Add skeleton loader and TRPC to appearance page * Fixes children being cut off at screen types * PR feedback fixes Co-authored-by: Hariom Balhara Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../v2/settings/my-account/appearance.tsx | 287 ++++++++---------- .../v2/settings/my-account/calendars.tsx | 24 +- .../v2/settings/my-account/conferencing.tsx | 194 ++++++------ .../pages/v2/settings/my-account/general.tsx | 74 ++--- .../pages/v2/settings/my-account/profile.tsx | 80 ++--- apps/web/public/static/locales/en/common.json | 5 +- packages/trpc/server/routers/viewer.tsx | 4 + .../ui/v2/core/layouts/SettingsLayout.tsx | 4 +- 8 files changed, 312 insertions(+), 360 deletions(-) diff --git a/apps/web/pages/v2/settings/my-account/appearance.tsx b/apps/web/pages/v2/settings/my-account/appearance.tsx index 3b036d1b36..3b2b52ecde 100644 --- a/apps/web/pages/v2/settings/my-account/appearance.tsx +++ b/apps/web/pages/v2/settings/my-account/appearance.tsx @@ -1,8 +1,6 @@ -import { GetServerSidePropsContext } from "next"; import { Controller, useForm } from "react-hook-form"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import prisma from "@calcom/prisma"; import { trpc } from "@calcom/trpc/react"; import Badge from "@calcom/ui/v2/core/Badge"; import { Button } from "@calcom/ui/v2/core/Button"; @@ -12,14 +10,34 @@ import ColorPicker from "@calcom/ui/v2/core/colorpicker"; import { Form } from "@calcom/ui/v2/core/form/fields"; import { getLayout } from "@calcom/ui/v2/core/layouts/SettingsLayout"; import showToast from "@calcom/ui/v2/core/notifications"; +import { SkeletonContainer, SkeletonText, SkeletonButton } from "@calcom/ui/v2/core/skeleton"; -import { getSession } from "@lib/auth"; -import { inferSSRProps } from "@lib/types/inferSSRProps"; +const SkeletonLoader = () => { + return ( + +
+
+ + + +
+
+ + +
-const AppearanceView = (props: inferSSRProps) => { + + + +
+
+ ); +}; + +const AppearanceView = () => { const { t } = useLocale(); - const { user } = props; + const { data: user, isLoading } = trpc.useQuery(["viewer.me"]); const mutation = trpc.useMutation("viewer.updateProfile", { onSuccess: () => { showToast(t("settings_updated_successfully"), "success"); @@ -31,169 +49,132 @@ const AppearanceView = (props: inferSSRProps) => { const formMethods = useForm(); - return ( -
{ - mutation.mutate({ - ...values, - // Radio values don't support null as values, therefore we convert an empty string - // back to null here. - theme: values.theme || null, - }); - }}> - -
-
-

{t("theme")}

-

{t("theme_applies_note")}

-
-
-
- - - -
+ if (isLoading) return ; -
-
-
-

{t("custom_brand_colors")}

-

{t("customize_your_brand_colors")}

+ if (user) + return ( + { + mutation.mutate({ + ...values, + // Radio values don't support null as values, therefore we convert an empty string + // back to null here. + theme: values.theme || null, + }); + }}> + +
+
+

{t("theme")}

+

{t("theme_applies_note")}

+
+
+
+ + +
-
-
- ( -
-

{t("light_brand_color")}

- formMethods.setValue("brandColor", value)} - /> -
- )} - /> - ( -
-

{t("dark_brand_color")}

- formMethods.setValue("darkBrandColor", value)} - /> -
- )} - /> -
- {/* TODO future PR to preview brandColors */} - {/* */} -
- ( - <> -
-
-
-

{t("disable_cal_branding")}

{" "} - {t("pro")} +
+ ( + <> +
+
+
+

{t("disable_cal_branding")}

{" "} + {t("pro")} +
+

{t("removes_cal_branding")}

+
+
+ formMethods.setValue("hideBranding", checked)} + checked={value} + />
-

{t("removes_cal_branding")}

-
- formMethods.setValue("hideBranding", checked)} - checked={value} - /> -
-
- - )} - /> - - - ); + + )} + /> + + + ); }; AppearanceView.getLayout = getLayout; export default AppearanceView; - -export const getServerSideProps = async (context: GetServerSidePropsContext) => { - const session = await getSession(context); - - if (!session?.user?.id) { - return { redirect: { permanent: false, destination: "/auth/login" } }; - } - - const user = await prisma.user.findUnique({ - where: { - id: session.user.id, - }, - select: { - username: true, - timeZone: true, - timeFormat: true, - weekStart: true, - brandColor: true, - darkBrandColor: true, - hideBranding: true, - theme: true, - eventTypes: { - select: { - title: true, - }, - }, - }, - }); - - if (!user) { - throw new Error("User seems logged in but cannot be found in the db"); - } - - return { - props: { - user, - }, - }; -}; - interface ThemeLabelProps { variant: "light" | "dark" | "system"; value?: "light" | "dark" | null; diff --git a/apps/web/pages/v2/settings/my-account/calendars.tsx b/apps/web/pages/v2/settings/my-account/calendars.tsx index 3ee3739ba2..b4bf5a2863 100644 --- a/apps/web/pages/v2/settings/my-account/calendars.tsx +++ b/apps/web/pages/v2/settings/my-account/calendars.tsx @@ -11,6 +11,7 @@ import Badge from "@calcom/ui/v2/core/Badge"; import EmptyScreen from "@calcom/ui/v2/core/EmptyScreen"; import Meta from "@calcom/ui/v2/core/Meta"; import { getLayout } from "@calcom/ui/v2/core/layouts/SettingsLayout"; +import { SkeletonContainer, SkeletonText, SkeletonButton } from "@calcom/ui/v2/core/skeleton"; import { List, ListItem, ListItemText, ListItemTitle } from "@calcom/ui/v2/modules/List"; import DestinationCalendarSelector from "@calcom/ui/v2/modules/event-types/DestinationCalendarSelector"; import DisconnectIntegration from "@calcom/ui/v2/modules/integrations/DisconnectIntegration"; @@ -19,6 +20,21 @@ import { QueryCell } from "@lib/QueryCell"; import { CalendarSwitch } from "@components/v2/settings/CalendarSwitch"; +const SkeletonLoader = () => { + return ( + +
+ + + + + + +
+
+ ); +}; + const CalendarsView = () => { const { t } = useLocale(); const router = useRouter(); @@ -37,6 +53,7 @@ const CalendarsView = () => { } success={({ data }) => { return data.connectedCalendars.length ? (
@@ -65,7 +82,6 @@ const CalendarsView = () => { />
-

{t("check_for_conflicts")}

@@ -130,9 +146,9 @@ const CalendarsView = () => { ) : ( router.push(`${WEBAPP_URL}/apps/categories/calendar`)} /> ); diff --git a/apps/web/pages/v2/settings/my-account/conferencing.tsx b/apps/web/pages/v2/settings/my-account/conferencing.tsx index 9081aa9b2b..15f27f4eb7 100644 --- a/apps/web/pages/v2/settings/my-account/conferencing.tsx +++ b/apps/web/pages/v2/settings/my-account/conferencing.tsx @@ -1,92 +1,110 @@ -import { GetServerSidePropsContext } from "next"; +import { useState } from "react"; -import getApps from "@calcom/app-store/utils"; -import { getSession } from "@calcom/lib/auth"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import prisma from "@calcom/prisma"; +import { trpc } from "@calcom/trpc/react"; import { Icon } from "@calcom/ui"; import { Dropdown, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Button } from "@calcom/ui/v2"; +import { Dialog, DialogContent } from "@calcom/ui/v2/core/Dialog"; import Meta from "@calcom/ui/v2/core/Meta"; import { getLayout } from "@calcom/ui/v2/core/layouts/SettingsLayout"; +import showToast from "@calcom/ui/v2/core/notifications"; +import { SkeletonContainer, SkeletonText } from "@calcom/ui/v2/core/skeleton"; import { List, ListItem, ListItemText, ListItemTitle } from "@calcom/ui/v2/modules/List"; -import DisconnectIntegration from "@calcom/ui/v2/modules/integrations/DisconnectIntegration"; -import { inferSSRProps } from "@lib/types/inferSSRProps"; +const SkeletonLoader = () => { + return ( + +
+ + +
+
+ ); +}; -const ConferencingLayout = (props: inferSSRProps) => { +const ConferencingLayout = () => { const { t } = useLocale(); + const utils = trpc.useContext(); - const { apps } = props; + const { data: apps, isLoading } = trpc.useQuery( + ["viewer.integrations", { variant: "conferencing", onlyInstalled: true }], + { + suspense: true, + } + ); + const deleteAppMutation = trpc.useMutation("viewer.deleteCredential", { + onSuccess: () => { + showToast("Integration deleted successfully", "success"); + utils.invalidateQueries(["viewer.integrations", { variant: "conferencing", onlyInstalled: true }]); + setDeleteAppModal(false); + }, + onError: () => { + showToast("Error deleting app", "error"); + setDeleteAppModal(false); + }, + }); - // Error reason: getaddrinfo EAI_AGAIN http - // const query = trpc.useQuery(["viewer.integrations", { variant: "conferencing", onlyInstalled: true }], { - // suspense: true, - // }); + const [deleteAppModal, setDeleteAppModal] = useState(false); + const [deleteCredentialId, setDeleteCredentialId] = useState(0); + + if (isLoading) return ; return (
- {apps.map((app) => ( - -
- { - // eslint-disable-next-line @next/next/no-img-element - app.logo && {app.title} - } -
- -

{app.title}

-
- {app.description} -
-
- - -
- {/*
- {app.title} - -
-

{app.title}

-

{app.description}

+ {apps?.items && + apps.items + .map((app) => ({ ...app, title: app.title || app.name })) + .map((app) => ( + +
+ { + // eslint-disable-next-line @next/next/no-img-element + app.logo && {app.title} + } +
+ +

{app.title}

+
+ {app.description} +
+
+ + + + + + +
-
- -
- - - - - - - - - - -
*/} -
- - ))} + + ))} + + + deleteAppMutation.mutate({ id: deleteCredentialId })} + /> +
); }; @@ -94,39 +112,3 @@ const ConferencingLayout = (props: inferSSRProps) => ConferencingLayout.getLayout = getLayout; export default ConferencingLayout; - -export const getServerSideProps = async (context: GetServerSidePropsContext) => { - const session = await getSession(context); - - if (!session?.user?.id) { - return { redirect: { permanent: false, destination: "/auth/login" } }; - } - - const videoCredentials = await prisma.credential.findMany({ - where: { - userId: session.user.id, - app: { - categories: { - has: "video", - }, - }, - }, - }); - - const apps = getApps(videoCredentials) - .filter((app) => { - return app.variant === "conferencing" && app.credentials.length; - }) - .map((app) => { - return { - slug: app.slug, - title: app.title || app.name, - logo: app.logo, - description: app.description, - credentialId: app.credentials[0].id, - isGlobal: app.isGlobal || false, - }; - }); - - return { props: { apps } }; -}; diff --git a/apps/web/pages/v2/settings/my-account/general.tsx b/apps/web/pages/v2/settings/my-account/general.tsx index 5b49512e82..ee87d07cd1 100644 --- a/apps/web/pages/v2/settings/my-account/general.tsx +++ b/apps/web/pages/v2/settings/my-account/general.tsx @@ -1,11 +1,8 @@ -import { GetServerSidePropsContext } from "next"; -import { TFunction } from "next-i18next"; import { useRouter } from "next/router"; import { useMemo } from "react"; import { useForm, Controller } from "react-hook-form"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import prisma from "@calcom/prisma"; import { trpc } from "@calcom/trpc/react"; import { Button } from "@calcom/ui/v2/core/Button"; import Meta from "@calcom/ui/v2/core/Meta"; @@ -14,35 +11,47 @@ import Select from "@calcom/ui/v2/core/form/Select"; import { Form, Label } from "@calcom/ui/v2/core/form/fields"; import { getLayout } from "@calcom/ui/v2/core/layouts/SettingsLayout"; import showToast from "@calcom/ui/v2/core/notifications"; +import { SkeletonContainer, SkeletonText, SkeletonButton } from "@calcom/ui/v2/core/skeleton"; import { withQuery } from "@lib/QueryCell"; -import { getSession } from "@lib/auth"; import { nameOfDay } from "@lib/core/i18n/weekday"; -import { inferSSRProps } from "@lib/types/inferSSRProps"; + +const SkeletonLoader = () => { + return ( + +
+ + + + + + +
+
+ ); +}; interface GeneralViewProps { localeProp: string; - t: TFunction; - user: { - timeZone: string; - timeFormat: number | null; - weekStart: string; - }; } const WithQuery = withQuery(["viewer.public.i18n"], { context: { skipBatch: true } }); -const GeneralQueryView = (props: inferSSRProps) => { - const { t } = useLocale(); - - return } />; +const GeneralQueryView = () => { + return ( + } + customLoader={} + /> + ); }; -const GeneralView = ({ localeProp, t, user }: GeneralViewProps) => { +const GeneralView = ({ localeProp }: GeneralViewProps) => { const router = useRouter(); const utils = trpc.useContext(); + const { t } = useLocale(); - // const { data: user, isLoading } = trpc.useQuery(["viewer.me"]); + const { data: user, isLoading } = trpc.useQuery(["viewer.me"]); const mutation = trpc.useMutation("viewer.updateProfile", { onSuccess: () => { showToast(t("settings_updated_successfully"), "success"); @@ -95,6 +104,8 @@ const GeneralView = ({ localeProp, t, user }: GeneralViewProps) => { }, }); + if (isLoading) return ; + return (
{ GeneralQueryView.getLayout = getLayout; export default GeneralQueryView; - -export const getServerSideProps = async (context: GetServerSidePropsContext) => { - const session = await getSession(context); - - if (!session?.user?.id) { - return { redirect: { permanent: false, destination: "/auth/login" } }; - } - - const user = await prisma.user.findUnique({ - where: { - id: session.user.id, - }, - select: { - timeZone: true, - timeFormat: true, - weekStart: true, - }, - }); - - if (!user) { - throw new Error("User seems logged in but cannot be found in the db"); - } - - return { - props: { - user, - }, - }; -}; diff --git a/apps/web/pages/v2/settings/my-account/profile.tsx b/apps/web/pages/v2/settings/my-account/profile.tsx index 33518b9564..4f3527c1fd 100644 --- a/apps/web/pages/v2/settings/my-account/profile.tsx +++ b/apps/web/pages/v2/settings/my-account/profile.tsx @@ -1,12 +1,10 @@ import crypto from "crypto"; -import { GetServerSidePropsContext } from "next"; import { signOut } from "next-auth/react"; import { useRef, useState, BaseSyntheticEvent } from "react"; import { Controller, useForm } from "react-hook-form"; -import { ErrorCode, getSession } from "@calcom/lib/auth"; +import { ErrorCode } 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"; @@ -20,21 +18,37 @@ import Meta from "@calcom/ui/v2/core/Meta"; import { Form, Label, TextField, PasswordField } from "@calcom/ui/v2/core/form/fields"; import { getLayout } from "@calcom/ui/v2/core/layouts/SettingsLayout"; import showToast from "@calcom/ui/v2/core/notifications"; - -import { inferSSRProps } from "@lib/types/inferSSRProps"; +import { SkeletonContainer, SkeletonText, SkeletonButton, SkeletonAvatar } from "@calcom/ui/v2/core/skeleton"; import TwoFactor from "@components/auth/TwoFactor"; +const SkeletonLoader = () => { + return ( + +
+
+ + +
+ + + + + +
+
+ ); +}; + interface DeleteAccountValues { totpCode: string; } -const ProfileView = (props: inferSSRProps) => { +const ProfileView = () => { const { t } = useLocale(); const utils = trpc.useContext(); - const { user } = props; - // const { data: user, isLoading } = trpc.useQuery(["viewer.me"]); + const { data: user, isLoading } = trpc.useQuery(["viewer.me"]); const mutation = trpc.useMutation("viewer.updateProfile", { onSuccess: () => { showToast(t("settings_updated_successfully"), "success"); @@ -50,6 +64,11 @@ const ProfileView = (props: inferSSRProps) => { const form = useForm(); + const emailMd5 = crypto + .createHash("md5") + .update(user?.email || "example@example.com") + .digest("hex"); + const onDeleteMeSuccessMutation = async () => { await utils.invalidateQueries(["viewer.me"]); showToast(t("Your account was deleted"), "success"); @@ -88,7 +107,7 @@ const ProfileView = (props: inferSSRProps) => { const formMethods = useForm({ defaultValues: { - avatar: user.avatar || "", + avatar: user?.avatar || "", username: user?.username || "", name: user?.name || "", bio: user?.bio || "", @@ -107,6 +126,8 @@ const ProfileView = (props: inferSSRProps) => { [ErrorCode.ThirdPartyIdentityProviderEnabled]: t("account_created_with_identity_provider"), }; + if (isLoading) return ; + return ( <> ) => { }}>
- {/* TODO upload new avatar */} ( <> - +
) => { ref={passwordRef} /> - {user.twoFactorEnabled && ( + {user?.twoFactorEnabled && ( @@ -240,39 +260,3 @@ const ProfileView = (props: inferSSRProps) => { ProfileView.getLayout = getLayout; export default ProfileView; - -export const getServerSideProps = async (context: GetServerSidePropsContext) => { - const session = await getSession(context); - - if (!session?.user?.id) { - return { redirect: { permanent: false, destination: "/auth/login" } }; - } - - const user = await prisma.user.findUnique({ - where: { - id: session.user.id, - }, - select: { - id: true, - username: true, - email: true, - name: true, - bio: true, - avatar: true, - twoFactorEnabled: true, - }, - }); - - if (!user) { - throw new Error("User seems logged in but cannot be found in the db"); - } - - return { - props: { - user: { - ...user, - emailMd5: crypto.createHash("md5").update(user.email).digest("hex"), - }, - }, - }; -}; diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index af2e6d4be0..9f72860837 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -1239,5 +1239,8 @@ "password_reset_leading":"If you did not receive the email soon, check that the email address you entered is correct, check your spam folder or reach out to support if the issue persists.", "password_updated":"Password updated!", "pending_payment": "Pending payment", - "confirmation_page_rainbow": "Token gate your event with tokens or NFTs on Ethereum, Polygon, and more." + "confirmation_page_rainbow": "Token gate your event with tokens or NFTs on Ethereum, Polygon, and more.", + "no_calendar_installed": "No calendar installed", + "no_calendar_installed_description": "You have not yet connected any of your calendars", + "add_a_calendar": "Add a calendar" } diff --git a/packages/trpc/server/routers/viewer.tsx b/packages/trpc/server/routers/viewer.tsx index de5be4a382..79ef21ffcc 100644 --- a/packages/trpc/server/routers/viewer.tsx +++ b/packages/trpc/server/routers/viewer.tsx @@ -176,6 +176,10 @@ const loggedInViewerRouter = createProtectedRouter() darkBrandColor: user.darkBrandColor, plan: user.plan, away: user.away, + bio: user.bio, + weekStart: user.weekStart, + theme: user.theme, + hideBranding: user.hideBranding, }; }, }) diff --git a/packages/ui/v2/core/layouts/SettingsLayout.tsx b/packages/ui/v2/core/layouts/SettingsLayout.tsx index befad7892c..7db6af7989 100644 --- a/packages/ui/v2/core/layouts/SettingsLayout.tsx +++ b/packages/ui/v2/core/layouts/SettingsLayout.tsx @@ -310,7 +310,7 @@ export default function SettingsLayout({ setSideContainerOpen(!sideContainerOpen)} /> }>
-
+
{children}
@@ -325,7 +325,7 @@ function ShellHeader() { const { meta } = useMeta(); const { t, isLocaleReady } = useLocale(); return ( -
+
{meta.backButton && (