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 { BadgeCheckIcon } from "@heroicons/react/solid"; | ||||
| import crypto from "crypto"; | ||||
| import { GetServerSidePropsContext } from "next"; | ||||
| import dynamic from "next/dynamic"; | ||||
| import Link from "next/link"; | ||||
| import { useRouter } from "next/router"; | ||||
| import React, { useState } from "react"; | ||||
|  | @ -9,24 +11,23 @@ import { JSONObject } from "superjson/dist/types"; | |||
| 
 | ||||
| import { useLocale } from "@lib/hooks/useLocale"; | ||||
| import useTheme from "@lib/hooks/useTheme"; | ||||
| import showToast from "@lib/notification"; | ||||
| import prisma from "@lib/prisma"; | ||||
| import { inferSSRProps } from "@lib/types/inferSSRProps"; | ||||
| 
 | ||||
| import EventTypeDescription from "@components/eventtype/EventTypeDescription"; | ||||
| import { HeadSeo } from "@components/seo/head-seo"; | ||||
| import Avatar from "@components/ui/Avatar"; | ||||
| import { AvatarSSR } from "@components/ui/AvatarSSR"; | ||||
| 
 | ||||
| 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 { | ||||
|   [evtId: string]: boolean; | ||||
| } | ||||
| 
 | ||||
| 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 { t } = useLocale(); | ||||
|   const router = useRouter(); | ||||
|  | @ -35,7 +36,6 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) { | |||
| 
 | ||||
|   const nameOrUsername = user.name || user.username || ""; | ||||
|   const [evtsToVerify, setEvtsToVerify] = useState<EvtsToVerify>({}); | ||||
| 
 | ||||
|   return ( | ||||
|     <> | ||||
|       <Theme /> | ||||
|  | @ -46,80 +46,75 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) { | |||
|         username={(user.username as string) || ""} | ||||
|         // avatar={user.avatar || undefined}
 | ||||
|       /> | ||||
|       {isReady && ( | ||||
|         <div className="h-screen dark:bg-black"> | ||||
|           <main className="mx-auto max-w-3xl px-4 py-24"> | ||||
|             <div className="mb-8 text-center"> | ||||
|               <Avatar | ||||
|                 imageSrc={user.avatar} | ||||
|                 className="mx-auto mb-4 h-24 w-24 rounded-full" | ||||
|                 alt={nameOrUsername} | ||||
|               /> | ||||
|               <h1 className="font-cal mb-1 text-3xl font-bold text-neutral-900 dark:text-white"> | ||||
|                 {nameOrUsername} | ||||
|                 {user.verified && ( | ||||
|                   <BadgeCheckIcon className="mx-1 -mt-1 inline h-6 w-6 text-blue-500 dark:text-white" /> | ||||
|                 )} | ||||
|               </h1> | ||||
|               <p className="text-neutral-500 dark:text-white">{user.bio}</p> | ||||
|             </div> | ||||
|             <div className="space-y-6" data-testid="event-types"> | ||||
|               {!user.away && | ||||
|                 eventTypes.map((type) => ( | ||||
|                   <div | ||||
|                     key={type.id} | ||||
|                     style={{ display: "flex" }} | ||||
|                     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"> | ||||
|                     <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 | ||||
|                       href={{ | ||||
|                         pathname: `/${user.username}/${type.slug}`, | ||||
|                         query, | ||||
|                       }}> | ||||
|                       <a | ||||
|                         onClick={(e) => { | ||||
|                           // If a token is required for this event type, add a click listener that checks whether the user verified their wallet or not
 | ||||
|                           if (type.metadata.smartContractAddress && !evtsToVerify[type.id]) { | ||||
|                             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} /> | ||||
|                       </a> | ||||
|                     </Link> | ||||
|                     {type.isWeb3Active && type.metadata.smartContractAddress && ( | ||||
|                       <CryptoSection | ||||
|                         id={type.id} | ||||
|                         pathname={`/${user.username}/${type.slug}`} | ||||
|                         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 className="h-screen dark:bg-black"> | ||||
|         <main className="mx-auto max-w-3xl px-4 py-24"> | ||||
|           <div className="mb-8 text-center"> | ||||
|             <AvatarSSR user={user} className="mx-auto mb-4 h-24 w-24" alt={nameOrUsername}></AvatarSSR> | ||||
|             <h1 className="font-cal mb-1 text-3xl font-bold text-neutral-900 dark:text-white"> | ||||
|               {nameOrUsername} | ||||
|               {user.verified && ( | ||||
|                 <BadgeCheckIcon className="-mt-1 inline h-6 w-6 text-blue-500 dark:text-white" /> | ||||
|               )} | ||||
|             </h1> | ||||
|             <p className="text-neutral-500 dark:text-white">{user.bio}</p> | ||||
|           </div> | ||||
|           <div className="space-y-6" data-testid="event-types"> | ||||
|             {!user.away && | ||||
|               eventTypes.map((type) => ( | ||||
|                 <div | ||||
|                   key={type.id} | ||||
|                   style={{ display: "flex" }} | ||||
|                   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"> | ||||
|                   <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 | ||||
|                     href={{ | ||||
|                       pathname: `/${user.username}/${type.slug}`, | ||||
|                       query, | ||||
|                     }}> | ||||
|                     <a | ||||
|                       onClick={async (e) => { | ||||
|                         // If a token is required for this event type, add a click listener that checks whether the user verified their wallet or not
 | ||||
|                         if (type.metadata.smartContractAddress && !evtsToVerify[type.id]) { | ||||
|                           const showToast = (await import("@lib/notification")).default; | ||||
|                           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} /> | ||||
|                     </a> | ||||
|                   </Link> | ||||
|                   {type.isWeb3Active && type.metadata.smartContractAddress && ( | ||||
|                     <CryptoSection | ||||
|                       id={type.id} | ||||
|                       pathname={`/${user.username}/${type.slug}`} | ||||
|                       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> | ||||
|             )} | ||||
|           </main> | ||||
|           <Toaster position="bottom-right" /> | ||||
|         </div> | ||||
|       )} | ||||
|             </div> | ||||
|           )} | ||||
|         </main> | ||||
|         <Toaster position="bottom-right" /> | ||||
|       </div> | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
|  | @ -224,7 +219,10 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => | |||
| 
 | ||||
|   return { | ||||
|     props: { | ||||
|       user, | ||||
|       user: { | ||||
|         ...user, | ||||
|         emailMd5: crypto.createHash("md5").update(user.email).digest("hex"), | ||||
|       }, | ||||
|       eventTypes, | ||||
|       trpcState: ssr.dehydrate(), | ||||
|     }, | ||||
|  |  | |||
|  | @ -245,7 +245,7 @@ model Booking { | |||
|   payment             Payment[] | ||||
|   destinationCalendar DestinationCalendar? | ||||
|   cancellationReason  String? | ||||
|   rejectionReason  String? | ||||
|   rejectionReason     String? | ||||
| } | ||||
| 
 | ||||
| model Schedule { | ||||
|  |  | |||
|  | @ -27,7 +27,9 @@ | |||
|     "husky": "^7.0.1", | ||||
|     "lint-staged": "^11.1.2", | ||||
|     "prettier": "^2.5.1", | ||||
|     "prettier-plugin-tailwindcss": "^0.1.6", | ||||
|     "prettier-plugin-tailwindcss": "^0.1.6" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "turbo": "latest" | ||||
|   }, | ||||
|   "lint-staged": { | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 hariombalhara
						hariombalhara