Performance improvements (lazy loading) for booking pages (#1762)
This commit is contained in:
		
							parent
							
								
									2e5deae7c0
								
							
						
					
					
						commit
						6fe824088a
					
				
					 4 changed files with 110 additions and 82 deletions
				
			
		
							
								
								
									
										28
									
								
								apps/web/components/ui/AvatarSSR.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								apps/web/components/ui/AvatarSSR.tsx
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,28 @@ | ||||||
|  | import { User } from "@prisma/client"; | ||||||
|  | 
 | ||||||
|  | import classNames from "@lib/classNames"; | ||||||
|  | import { defaultAvatarSrc } from "@lib/profile"; | ||||||
|  | 
 | ||||||
|  | export type AvatarProps = { | ||||||
|  |   user: Pick<User, "name" | "username" | "avatar"> & { emailMd5?: string }; | ||||||
|  |   className?: string; | ||||||
|  |   size?: number; | ||||||
|  |   title?: string; | ||||||
|  |   alt: string; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // An SSR Supported version of Avatar component.
 | ||||||
|  | // FIXME: title support is missing
 | ||||||
|  | export function AvatarSSR(props: AvatarProps) { | ||||||
|  |   const { user, size } = props; | ||||||
|  |   const nameOrUsername = user.name || user.username || ""; | ||||||
|  |   const className = classNames("rounded-full", props.className, size && `h-${size} w-${size}`); | ||||||
|  |   let imgSrc; | ||||||
|  |   const alt = props.alt || nameOrUsername; | ||||||
|  |   if (user.avatar) { | ||||||
|  |     imgSrc = user.avatar; | ||||||
|  |   } else if (user.emailMd5) { | ||||||
|  |     imgSrc = defaultAvatarSrc({ md5: user.emailMd5 }); | ||||||
|  |   } | ||||||
|  |   return imgSrc ? <img alt={alt} className={className} src={imgSrc}></img> : null; | ||||||
|  | } | ||||||
|  | @ -1,6 +1,8 @@ | ||||||
| import { ArrowRightIcon } from "@heroicons/react/outline"; | import { ArrowRightIcon } from "@heroicons/react/outline"; | ||||||
| import { BadgeCheckIcon } from "@heroicons/react/solid"; | import { BadgeCheckIcon } from "@heroicons/react/solid"; | ||||||
|  | import crypto from "crypto"; | ||||||
| import { GetServerSidePropsContext } from "next"; | import { GetServerSidePropsContext } from "next"; | ||||||
|  | import dynamic from "next/dynamic"; | ||||||
| import Link from "next/link"; | import Link from "next/link"; | ||||||
| import { useRouter } from "next/router"; | import { useRouter } from "next/router"; | ||||||
| import React, { useState } from "react"; | import React, { useState } from "react"; | ||||||
|  | @ -9,24 +11,23 @@ import { JSONObject } from "superjson/dist/types"; | ||||||
| 
 | 
 | ||||||
| import { useLocale } from "@lib/hooks/useLocale"; | import { useLocale } from "@lib/hooks/useLocale"; | ||||||
| import useTheme from "@lib/hooks/useTheme"; | import useTheme from "@lib/hooks/useTheme"; | ||||||
| import showToast from "@lib/notification"; |  | ||||||
| import prisma from "@lib/prisma"; | import prisma from "@lib/prisma"; | ||||||
| import { inferSSRProps } from "@lib/types/inferSSRProps"; | import { inferSSRProps } from "@lib/types/inferSSRProps"; | ||||||
| 
 | 
 | ||||||
| import EventTypeDescription from "@components/eventtype/EventTypeDescription"; | import { AvatarSSR } from "@components/ui/AvatarSSR"; | ||||||
| import { HeadSeo } from "@components/seo/head-seo"; |  | ||||||
| import Avatar from "@components/ui/Avatar"; |  | ||||||
| 
 | 
 | ||||||
| import { ssrInit } from "@server/lib/ssr"; | import { ssrInit } from "@server/lib/ssr"; | ||||||
| 
 | 
 | ||||||
| import CryptoSection from "../ee/components/web3/CryptoSection"; | const EventTypeDescription = dynamic(() => import("@components/eventtype/EventTypeDescription")); | ||||||
|  | const HeadSeo = dynamic(() => import("@components/seo/head-seo").then((mod) => mod.HeadSeo)); | ||||||
|  | const CryptoSection = dynamic(() => import("../ee/components/web3/CryptoSection")); | ||||||
| 
 | 
 | ||||||
| interface EvtsToVerify { | interface EvtsToVerify { | ||||||
|   [evtId: string]: boolean; |   [evtId: string]: boolean; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default function User(props: inferSSRProps<typeof getServerSideProps>) { | export default function User(props: inferSSRProps<typeof getServerSideProps>) { | ||||||
|   const { isReady, Theme } = useTheme(props.user.theme); |   const { Theme } = useTheme(props.user.theme); | ||||||
|   const { user, eventTypes } = props; |   const { user, eventTypes } = props; | ||||||
|   const { t } = useLocale(); |   const { t } = useLocale(); | ||||||
|   const router = useRouter(); |   const router = useRouter(); | ||||||
|  | @ -35,7 +36,6 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) { | ||||||
| 
 | 
 | ||||||
|   const nameOrUsername = user.name || user.username || ""; |   const nameOrUsername = user.name || user.username || ""; | ||||||
|   const [evtsToVerify, setEvtsToVerify] = useState<EvtsToVerify>({}); |   const [evtsToVerify, setEvtsToVerify] = useState<EvtsToVerify>({}); | ||||||
| 
 |  | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|       <Theme /> |       <Theme /> | ||||||
|  | @ -46,80 +46,75 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) { | ||||||
|         username={(user.username as string) || ""} |         username={(user.username as string) || ""} | ||||||
|         // avatar={user.avatar || undefined}
 |         // avatar={user.avatar || undefined}
 | ||||||
|       /> |       /> | ||||||
|       {isReady && ( |       <div className="h-screen dark:bg-black"> | ||||||
|         <div className="h-screen dark:bg-black"> |         <main className="mx-auto max-w-3xl px-4 py-24"> | ||||||
|           <main className="mx-auto max-w-3xl px-4 py-24"> |           <div className="mb-8 text-center"> | ||||||
|             <div className="mb-8 text-center"> |             <AvatarSSR user={user} className="mx-auto mb-4 h-24 w-24" alt={nameOrUsername}></AvatarSSR> | ||||||
|               <Avatar |             <h1 className="font-cal mb-1 text-3xl font-bold text-neutral-900 dark:text-white"> | ||||||
|                 imageSrc={user.avatar} |               {nameOrUsername} | ||||||
|                 className="mx-auto mb-4 h-24 w-24 rounded-full" |               {user.verified && ( | ||||||
|                 alt={nameOrUsername} |                 <BadgeCheckIcon className="-mt-1 inline h-6 w-6 text-blue-500 dark:text-white" /> | ||||||
|               /> |               )} | ||||||
|               <h1 className="font-cal mb-1 text-3xl font-bold text-neutral-900 dark:text-white"> |             </h1> | ||||||
|                 {nameOrUsername} |             <p className="text-neutral-500 dark:text-white">{user.bio}</p> | ||||||
|                 {user.verified && ( |           </div> | ||||||
|                   <BadgeCheckIcon className="mx-1 -mt-1 inline h-6 w-6 text-blue-500 dark:text-white" /> |           <div className="space-y-6" data-testid="event-types"> | ||||||
|                 )} |             {!user.away && | ||||||
|               </h1> |               eventTypes.map((type) => ( | ||||||
|               <p className="text-neutral-500 dark:text-white">{user.bio}</p> |                 <div | ||||||
|             </div> |                   key={type.id} | ||||||
|             <div className="space-y-6" data-testid="event-types"> |                   style={{ display: "flex" }} | ||||||
|               {!user.away && |                   className="group hover:border-brand relative rounded-sm border border-neutral-200 bg-white hover:bg-gray-50 dark:border-0 dark:bg-neutral-900 dark:hover:border-neutral-600"> | ||||||
|                 eventTypes.map((type) => ( |                   <ArrowRightIcon className="absolute right-3 top-3 h-4 w-4 text-black opacity-0 transition-opacity group-hover:opacity-100 dark:text-white" /> | ||||||
|                   <div |                   <Link | ||||||
|                     key={type.id} |                     href={{ | ||||||
|                     style={{ display: "flex" }} |                       pathname: `/${user.username}/${type.slug}`, | ||||||
|                     className="group hover:border-brand relative rounded-sm border border-neutral-200 bg-white hover:bg-gray-50 dark:border-0 dark:bg-neutral-900 dark:hover:border-neutral-600"> |                       query, | ||||||
|                     <ArrowRightIcon className="absolute right-3 top-3 h-4 w-4 text-black opacity-0 transition-opacity group-hover:opacity-100 dark:text-white" /> |                     }}> | ||||||
|                     <Link |                     <a | ||||||
|                       href={{ |                       onClick={async (e) => { | ||||||
|                         pathname: `/${user.username}/${type.slug}`, |                         // If a token is required for this event type, add a click listener that checks whether the user verified their wallet or not
 | ||||||
|                         query, |                         if (type.metadata.smartContractAddress && !evtsToVerify[type.id]) { | ||||||
|                       }}> |                           const showToast = (await import("@lib/notification")).default; | ||||||
|                       <a |                           e.preventDefault(); | ||||||
|                         onClick={(e) => { |                           showToast( | ||||||
|                           // If a token is required for this event type, add a click listener that checks whether the user verified their wallet or not
 |                             "You must verify a wallet with a token belonging to the specified smart contract first", | ||||||
|                           if (type.metadata.smartContractAddress && !evtsToVerify[type.id]) { |                             "error" | ||||||
|                             e.preventDefault(); |                           ); | ||||||
|                             showToast( |                         } | ||||||
|                               "You must verify a wallet with a token belonging to the specified smart contract first", |                       }} | ||||||
|                               "error" |                       className="block w-full px-6 py-4" | ||||||
|                             ); |                       data-testid="event-type-link"> | ||||||
|                           } |                       <h2 className="grow font-semibold text-neutral-900 dark:text-white">{type.title}</h2> | ||||||
|                         }} |                       <EventTypeDescription eventType={type} /> | ||||||
|                         className="block w-full px-6 py-4" |                     </a> | ||||||
|                         data-testid="event-type-link"> |                   </Link> | ||||||
|                         <h2 className="grow font-semibold text-neutral-900 dark:text-white">{type.title}</h2> |                   {type.isWeb3Active && type.metadata.smartContractAddress && ( | ||||||
|                         <EventTypeDescription eventType={type} /> |                     <CryptoSection | ||||||
|                       </a> |                       id={type.id} | ||||||
|                     </Link> |                       pathname={`/${user.username}/${type.slug}`} | ||||||
|                     {type.isWeb3Active && type.metadata.smartContractAddress && ( |                       smartContractAddress={type.metadata.smartContractAddress as string} | ||||||
|                       <CryptoSection |                       verified={evtsToVerify[type.id]} | ||||||
|                         id={type.id} |                       setEvtsToVerify={setEvtsToVerify} | ||||||
|                         pathname={`/${user.username}/${type.slug}`} |                       oneStep | ||||||
|                         smartContractAddress={type.metadata.smartContractAddress as string} |                     /> | ||||||
|                         verified={evtsToVerify[type.id]} |                   )} | ||||||
|                         setEvtsToVerify={setEvtsToVerify} |  | ||||||
|                         oneStep |  | ||||||
|                       /> |  | ||||||
|                     )} |  | ||||||
|                   </div> |  | ||||||
|                 ))} |  | ||||||
|             </div> |  | ||||||
|             {eventTypes.length === 0 && ( |  | ||||||
|               <div className="overflow-hidden rounded-sm shadow"> |  | ||||||
|                 <div className="p-8 text-center text-gray-400 dark:text-white"> |  | ||||||
|                   <h2 className="font-cal text-3xl font-semibold text-gray-600 dark:text-white"> |  | ||||||
|                     {t("uh_oh")} |  | ||||||
|                   </h2> |  | ||||||
|                   <p className="mx-auto max-w-md">{t("no_event_types_have_been_setup")}</p> |  | ||||||
|                 </div> |                 </div> | ||||||
|  |               ))} | ||||||
|  |           </div> | ||||||
|  |           {eventTypes.length === 0 && ( | ||||||
|  |             <div className="overflow-hidden rounded-sm shadow"> | ||||||
|  |               <div className="p-8 text-center text-gray-400 dark:text-white"> | ||||||
|  |                 <h2 className="font-cal text-3xl font-semibold text-gray-600 dark:text-white"> | ||||||
|  |                   {t("uh_oh")} | ||||||
|  |                 </h2> | ||||||
|  |                 <p className="mx-auto max-w-md">{t("no_event_types_have_been_setup")}</p> | ||||||
|               </div> |               </div> | ||||||
|             )} |             </div> | ||||||
|           </main> |           )} | ||||||
|           <Toaster position="bottom-right" /> |         </main> | ||||||
|         </div> |         <Toaster position="bottom-right" /> | ||||||
|       )} |       </div> | ||||||
|     </> |     </> | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  | @ -224,7 +219,10 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => | ||||||
| 
 | 
 | ||||||
|   return { |   return { | ||||||
|     props: { |     props: { | ||||||
|       user, |       user: { | ||||||
|  |         ...user, | ||||||
|  |         emailMd5: crypto.createHash("md5").update(user.email).digest("hex"), | ||||||
|  |       }, | ||||||
|       eventTypes, |       eventTypes, | ||||||
|       trpcState: ssr.dehydrate(), |       trpcState: ssr.dehydrate(), | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  | @ -245,7 +245,7 @@ model Booking { | ||||||
|   payment             Payment[] |   payment             Payment[] | ||||||
|   destinationCalendar DestinationCalendar? |   destinationCalendar DestinationCalendar? | ||||||
|   cancellationReason  String? |   cancellationReason  String? | ||||||
|   rejectionReason  String? |   rejectionReason     String? | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| model Schedule { | model Schedule { | ||||||
|  |  | ||||||
|  | @ -27,7 +27,9 @@ | ||||||
|     "husky": "^7.0.1", |     "husky": "^7.0.1", | ||||||
|     "lint-staged": "^11.1.2", |     "lint-staged": "^11.1.2", | ||||||
|     "prettier": "^2.5.1", |     "prettier": "^2.5.1", | ||||||
|     "prettier-plugin-tailwindcss": "^0.1.6", |     "prettier-plugin-tailwindcss": "^0.1.6" | ||||||
|  |   }, | ||||||
|  |   "dependencies": { | ||||||
|     "turbo": "latest" |     "turbo": "latest" | ||||||
|   }, |   }, | ||||||
|   "lint-staged": { |   "lint-staged": { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 hariombalhara
						hariombalhara