improve loading states:
* app loader * specific loaders with spinner
This commit is contained in:
		
							
								
								
									
										15
									
								
								app/core/components/spinner.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								app/core/components/spinner.module.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | .ring { | ||||||
|  | 	display: inline-block; | ||||||
|  | 	width: 50px; | ||||||
|  | 	height: 50px; | ||||||
|  | 	border: 3px solid rgba(0, 0, 0, 0.15); | ||||||
|  | 	border-radius: 50%; | ||||||
|  | 	border-top-color: currentColor; | ||||||
|  | 	animation: spin 1s ease-in-out infinite; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @keyframes spin { | ||||||
|  | 	to { | ||||||
|  | 		transform: rotate(360deg); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								app/core/components/spinner.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/core/components/spinner.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | import clsx from "clsx"; | ||||||
|  |  | ||||||
|  | import styles from "./spinner.module.css"; | ||||||
|  |  | ||||||
|  | export default function Spinner() { | ||||||
|  | 	return ( | ||||||
|  | 		<div className="h-full flex"> | ||||||
|  | 			<div className={clsx(styles.ring, "m-auto text-primary-400")} /> | ||||||
|  | 		</div> | ||||||
|  | 	); | ||||||
|  | } | ||||||
| @@ -29,7 +29,7 @@ function NavLink({ path, label, icon }: NavLinkProps) { | |||||||
|  |  | ||||||
| 	return ( | 	return ( | ||||||
| 		<div className="flex flex-col items-center justify-around h-full"> | 		<div className="flex flex-col items-center justify-around h-full"> | ||||||
| 			<Link href={path}> | 			<Link href={path} prefetch={false}> | ||||||
| 				<a | 				<a | ||||||
| 					className={clsx("flex flex-col items-center", { | 					className={clsx("flex flex-col items-center", { | ||||||
| 						"text-primary-500": isActiveRoute, | 						"text-primary-500": isActiveRoute, | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import type { ErrorInfo, FunctionComponent } from "react"; | import type { ErrorInfo, FunctionComponent } from "react"; | ||||||
| import { Component } from "react"; | import { Component, Suspense } from "react"; | ||||||
| import { | import { | ||||||
| 	Head, | 	Head, | ||||||
| 	withRouter, | 	withRouter, | ||||||
| @@ -14,6 +14,7 @@ import type { WithRouterProps } from "next/dist/client/with-router"; | |||||||
| import appLogger from "../../../../integrations/logger"; | import appLogger from "../../../../integrations/logger"; | ||||||
|  |  | ||||||
| import Footer from "./footer"; | import Footer from "./footer"; | ||||||
|  | import Loader from "./loader"; | ||||||
|  |  | ||||||
| type Props = { | type Props = { | ||||||
| 	title: string; | 	title: string; | ||||||
| @@ -23,7 +24,7 @@ type Props = { | |||||||
|  |  | ||||||
| const logger = appLogger.child({ module: "Layout" }); | const logger = appLogger.child({ module: "Layout" }); | ||||||
|  |  | ||||||
| const Layout: FunctionComponent<Props> = ({ children, title, pageTitle = title, hideFooter = false }) => { | const AppLayout: FunctionComponent<Props> = ({ children, title, pageTitle = title, hideFooter = false }) => { | ||||||
| 	return ( | 	return ( | ||||||
| 		<> | 		<> | ||||||
| 			{pageTitle ? ( | 			{pageTitle ? ( | ||||||
| @@ -32,16 +33,18 @@ const Layout: FunctionComponent<Props> = ({ children, title, pageTitle = title, | |||||||
| 				</Head> | 				</Head> | ||||||
| 			) : null} | 			) : null} | ||||||
|  |  | ||||||
| 			<div className="h-full w-full overflow-hidden fixed bg-gray-100"> | 			<Suspense fallback={<Loader />}> | ||||||
| 				<div className="flex flex-col w-full h-full"> | 				<div className="h-full w-full overflow-hidden fixed bg-gray-100"> | ||||||
| 					<div className="flex flex-col flex-1 w-full overflow-y-auto"> | 					<div className="flex flex-col w-full h-full"> | ||||||
| 						<main className="flex flex-col flex-1 my-0 h-full"> | 						<div className="flex flex-col flex-1 w-full overflow-y-auto"> | ||||||
| 							<ErrorBoundary>{children}</ErrorBoundary> | 							<main className="flex flex-col flex-1 my-0 h-full"> | ||||||
| 						</main> | 								<ErrorBoundary>{children}</ErrorBoundary> | ||||||
|  | 							</main> | ||||||
|  | 						</div> | ||||||
|  | 						{!hideFooter ? <Footer /> : null} | ||||||
| 					</div> | 					</div> | ||||||
| 					{!hideFooter ? <Footer /> : null} |  | ||||||
| 				</div> | 				</div> | ||||||
| 			</div> | 			</Suspense> | ||||||
| 		</> | 		</> | ||||||
| 	); | 	); | ||||||
| }; | }; | ||||||
| @@ -109,4 +112,4 @@ const ErrorBoundary = withRouter( | |||||||
| 	}, | 	}, | ||||||
| ); | ); | ||||||
|  |  | ||||||
| export default Layout; | export default AppLayout; | ||||||
|   | |||||||
							
								
								
									
										749
									
								
								app/core/layouts/layout/loader-gradient.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										749
									
								
								app/core/layouts/layout/loader-gradient.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										8
									
								
								app/core/layouts/layout/loader.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								app/core/layouts/layout/loader.module.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | #gradientCanvas { | ||||||
|  | 	width: 100%; | ||||||
|  | 	height: 100%; | ||||||
|  | 	--gradient-color-1: #c3e4ff; | ||||||
|  | 	--gradient-color-2: #6ec3f4; | ||||||
|  | 	--gradient-color-3: #eae2ff; | ||||||
|  | 	--gradient-color-4: #b9beff; | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								app/core/layouts/layout/loader.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/core/layouts/layout/loader.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | import { useEffect } from "react"; | ||||||
|  |  | ||||||
|  | import Logo from "../../components/logo"; | ||||||
|  | import { Gradient } from "./loader-gradient.js"; | ||||||
|  |  | ||||||
|  | import styles from "./loader.module.css"; | ||||||
|  |  | ||||||
|  | export default function Loader() { | ||||||
|  | 	useEffect(() => { | ||||||
|  | 		const gradient = new Gradient(); | ||||||
|  | 		// @ts-ignore | ||||||
|  | 		gradient.initGradient(`#${styles.gradientCanvas}`); | ||||||
|  | 	}, []); | ||||||
|  |  | ||||||
|  | 	return ( | ||||||
|  | 		<div className="min-h-screen min-w-screen relative"> | ||||||
|  | 			<div className="relative z-10 min-h-screen flex flex-col justify-center py-12 px-4 sm:px-6 lg:px-8"> | ||||||
|  | 				<div className="flex flex-col sm:mx-auto sm:w-full sm:max-w-sm"> | ||||||
|  | 					<Logo className="mx-auto h-12 w-12" /> | ||||||
|  | 					<span className="mt-2 text-center text-lg leading-9 text-gray-900">Loading up Shellphone...</span> | ||||||
|  | 				</div> | ||||||
|  | 			</div> | ||||||
|  | 			<canvas id={styles.gradientCanvas} className="absolute top-0 z-0" /> | ||||||
|  | 		</div> | ||||||
|  | 	); | ||||||
|  | } | ||||||
| @@ -3,13 +3,14 @@ import type { BlitzPage } from "blitz"; | |||||||
| import { Routes, dynamic } from "blitz"; | import { Routes, dynamic } from "blitz"; | ||||||
| import { atom, useAtom } from "jotai"; | import { atom, useAtom } from "jotai"; | ||||||
|  |  | ||||||
| import Layout from "app/core/layouts/layout"; | import AppLayout from "app/core/layouts/layout"; | ||||||
| import ConversationsList from "../components/conversations-list"; | import ConversationsList from "../components/conversations-list"; | ||||||
| import NewMessageButton from "../components/new-message-button"; | import NewMessageButton from "../components/new-message-button"; | ||||||
| import MissingTwilioCredentials from "app/core/components/missing-twilio-credentials"; | import MissingTwilioCredentials from "app/core/components/missing-twilio-credentials"; | ||||||
| import useNotifications from "app/core/hooks/use-notifications"; | import useNotifications from "app/core/hooks/use-notifications"; | ||||||
| import useCurrentUser from "app/core/hooks/use-current-user"; | import useCurrentUser from "app/core/hooks/use-current-user"; | ||||||
| import PageTitle from "../../core/components/page-title"; | import PageTitle from "../../core/components/page-title"; | ||||||
|  | import Spinner from "../../core/components/spinner"; | ||||||
|  |  | ||||||
| const Messages: BlitzPage = () => { | const Messages: BlitzPage = () => { | ||||||
| 	const { hasFilledTwilioCredentials, hasPhoneNumber } = useCurrentUser(); | 	const { hasFilledTwilioCredentials, hasPhoneNumber } = useCurrentUser(); | ||||||
| @@ -26,7 +27,7 @@ const Messages: BlitzPage = () => { | |||||||
| 		return ( | 		return ( | ||||||
| 			<> | 			<> | ||||||
| 				<MissingTwilioCredentials /> | 				<MissingTwilioCredentials /> | ||||||
| 				<PageTitle className="filter blur-sm absolute top-0" title="Messages" /> | 				<PageTitle className="filter blur-sm select-none absolute top-0" title="Messages" /> | ||||||
| 			</> | 			</> | ||||||
| 		); | 		); | ||||||
| 	} | 	} | ||||||
| @@ -35,7 +36,8 @@ const Messages: BlitzPage = () => { | |||||||
| 		<> | 		<> | ||||||
| 			<PageTitle title="Messages" /> | 			<PageTitle title="Messages" /> | ||||||
| 			<section className="flex flex-grow flex-col"> | 			<section className="flex flex-grow flex-col"> | ||||||
| 				<Suspense fallback="Loading..."> | 				<Suspense fallback={<Spinner />}> | ||||||
|  | 					{/* TODO: skeleton conversations list */} | ||||||
| 					<ConversationsList /> | 					<ConversationsList /> | ||||||
| 				</Suspense> | 				</Suspense> | ||||||
| 			</section> | 			</section> | ||||||
| @@ -52,7 +54,7 @@ const NewMessageBottomSheet = dynamic(() => import("../components/new-message-bo | |||||||
|  |  | ||||||
| export const bottomSheetOpenAtom = atom(false); | export const bottomSheetOpenAtom = atom(false); | ||||||
|  |  | ||||||
| Messages.getLayout = (page) => <Layout title="Messages">{page}</Layout>; | Messages.getLayout = (page) => <AppLayout title="Messages">{page}</AppLayout>; | ||||||
|  |  | ||||||
| Messages.authenticate = { redirectTo: Routes.SignIn().pathname }; | Messages.authenticate = { redirectTo: Routes.SignIn().pathname }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ import type { BlitzPage } from "blitz"; | |||||||
| import { Routes, useRouter } from "blitz"; | import { Routes, useRouter } from "blitz"; | ||||||
| import { IoChevronBack, IoInformationCircle, IoCall } from "react-icons/io5"; | import { IoChevronBack, IoInformationCircle, IoCall } from "react-icons/io5"; | ||||||
|  |  | ||||||
| import Layout from "../../../core/layouts/layout"; | import AppLayout from "../../../core/layouts/layout"; | ||||||
| import Conversation from "../../components/conversation"; | import Conversation from "../../components/conversation"; | ||||||
| import useConversation from "../../hooks/use-conversation"; | import useConversation from "../../hooks/use-conversation"; | ||||||
|  |  | ||||||
| @@ -14,7 +14,7 @@ const ConversationPage: BlitzPage = () => { | |||||||
| 	const conversation = useConversation(recipient)[0]; | 	const conversation = useConversation(recipient)[0]; | ||||||
|  |  | ||||||
| 	return ( | 	return ( | ||||||
| 		<Layout title={pageTitle} hideFooter> | 		<AppLayout title={pageTitle} hideFooter> | ||||||
| 			<header className="absolute top-0 w-screen h-12 backdrop-filter backdrop-blur-sm bg-white bg-opacity-75 border-b grid grid-cols-3 items-center"> | 			<header className="absolute top-0 w-screen h-12 backdrop-filter backdrop-blur-sm bg-white bg-opacity-75 border-b grid grid-cols-3 items-center"> | ||||||
| 				<span className="col-start-1 col-span-1 pl-2 cursor-pointer" onClick={router.back}> | 				<span className="col-start-1 col-span-1 pl-2 cursor-pointer" onClick={router.back}> | ||||||
| 					<IoChevronBack className="h-8 w-8" /> | 					<IoChevronBack className="h-8 w-8" /> | ||||||
| @@ -28,7 +28,7 @@ const ConversationPage: BlitzPage = () => { | |||||||
| 			<Suspense fallback={<div className="pt-12">Loading messages with {recipient}</div>}> | 			<Suspense fallback={<div className="pt-12">Loading messages with {recipient}</div>}> | ||||||
| 				<Conversation /> | 				<Conversation /> | ||||||
| 			</Suspense> | 			</Suspense> | ||||||
| 		</Layout> | 		</AppLayout> | ||||||
| 	); | 	); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ import "app/core/styles/index.css"; | |||||||
| const { publicRuntimeConfig } = getConfig(); | const { publicRuntimeConfig } = getConfig(); | ||||||
|  |  | ||||||
| export default function App({ Component, pageProps }: AppProps) { | export default function App({ Component, pageProps }: AppProps) { | ||||||
| 	const session = useSession(); | 	const session = useSession({ suspense: false }); | ||||||
| 	usePanelbear(publicRuntimeConfig.panelBear.siteId); | 	usePanelbear(publicRuntimeConfig.panelBear.siteId); | ||||||
| 	useEffect(() => { | 	useEffect(() => { | ||||||
| 		if (session.userId) { | 		if (session.userId) { | ||||||
| @@ -42,7 +42,8 @@ export default function App({ Component, pageProps }: AppProps) { | |||||||
| 			FallbackComponent={RootErrorFallback} | 			FallbackComponent={RootErrorFallback} | ||||||
| 			onReset={useQueryErrorResetBoundary().reset} | 			onReset={useQueryErrorResetBoundary().reset} | ||||||
| 		> | 		> | ||||||
| 			<Suspense fallback="Silence, ca pousse">{getLayout(<Component {...pageProps} />)}</Suspense> | 			{/* TODO: better default fallback */} | ||||||
|  | 			<Suspense fallback={null}>{getLayout(<Component {...pageProps} />)}</Suspense> | ||||||
| 		</ErrorBoundary> | 		</ErrorBoundary> | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| import { useRouter } from "blitz"; | import { useRouter } from "blitz"; | ||||||
|  |  | ||||||
| import Layout from "../core/layouts/layout"; | import AppLayout from "../core/layouts/layout"; | ||||||
|  |  | ||||||
| export default function Offline() { | export default function Offline() { | ||||||
| 	const router = useRouter(); | 	const router = useRouter(); | ||||||
| 	return ( | 	return ( | ||||||
| 		<Layout title="App went offline"> | 		<AppLayout title="App went offline"> | ||||||
| 			<h2 className="mt-6 text-center text-3xl leading-9 font-extrabold text-gray-900"> | 			<h2 className="mt-6 text-center text-3xl leading-9 font-extrabold text-gray-900"> | ||||||
| 				Oops, looks like you went offline. | 				Oops, looks like you went offline. | ||||||
| 			</h2> | 			</h2> | ||||||
| @@ -17,6 +17,6 @@ export default function Offline() { | |||||||
| 					</span> | 					</span> | ||||||
| 				</button> | 				</button> | ||||||
| 			</p> | 			</p> | ||||||
| 		</Layout> | 		</AppLayout> | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,11 +2,12 @@ import { Suspense } from "react"; | |||||||
| import type { BlitzPage } from "blitz"; | import type { BlitzPage } from "blitz"; | ||||||
| import { Routes } from "blitz"; | import { Routes } from "blitz"; | ||||||
|  |  | ||||||
| import Layout from "app/core/layouts/layout"; | import AppLayout from "app/core/layouts/layout"; | ||||||
| import PhoneCallsList from "../components/phone-calls-list"; | import PhoneCallsList from "../components/phone-calls-list"; | ||||||
| import MissingTwilioCredentials from "app/core/components/missing-twilio-credentials"; | import MissingTwilioCredentials from "app/core/components/missing-twilio-credentials"; | ||||||
| import useCurrentUser from "app/core/hooks/use-current-user"; | import useCurrentUser from "app/core/hooks/use-current-user"; | ||||||
| import PageTitle from "../../core/components/page-title"; | import PageTitle from "../../core/components/page-title"; | ||||||
|  | import Spinner from "../../core/components/spinner"; | ||||||
|  |  | ||||||
| const PhoneCalls: BlitzPage = () => { | const PhoneCalls: BlitzPage = () => { | ||||||
| 	const { hasFilledTwilioCredentials, hasPhoneNumber } = useCurrentUser(); | 	const { hasFilledTwilioCredentials, hasPhoneNumber } = useCurrentUser(); | ||||||
| @@ -15,7 +16,7 @@ const PhoneCalls: BlitzPage = () => { | |||||||
| 		return ( | 		return ( | ||||||
| 			<> | 			<> | ||||||
| 				<MissingTwilioCredentials /> | 				<MissingTwilioCredentials /> | ||||||
| 				<PageTitle className="filter blur-sm absolute top-0" title="Calls" /> | 				<PageTitle className="filter blur-sm select-none absolute top-0" title="Calls" /> | ||||||
| 			</> | 			</> | ||||||
| 		); | 		); | ||||||
| 	} | 	} | ||||||
| @@ -24,7 +25,8 @@ const PhoneCalls: BlitzPage = () => { | |||||||
| 		<> | 		<> | ||||||
| 			<PageTitle className="pl-12" title="Calls" /> | 			<PageTitle className="pl-12" title="Calls" /> | ||||||
| 			<section className="flex flex-grow flex-col"> | 			<section className="flex flex-grow flex-col"> | ||||||
| 				<Suspense fallback="Loading..."> | 				<Suspense fallback={<Spinner />}> | ||||||
|  | 					{/* TODO: skeleton phone calls list */} | ||||||
| 					<PhoneCallsList /> | 					<PhoneCallsList /> | ||||||
| 				</Suspense> | 				</Suspense> | ||||||
| 			</section> | 			</section> | ||||||
| @@ -32,7 +34,7 @@ const PhoneCalls: BlitzPage = () => { | |||||||
| 	); | 	); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| PhoneCalls.getLayout = (page) => <Layout title="Calls">{page}</Layout>; | PhoneCalls.getLayout = (page) => <AppLayout title="Calls">{page}</AppLayout>; | ||||||
|  |  | ||||||
| PhoneCalls.authenticate = { redirectTo: Routes.SignIn() }; | PhoneCalls.authenticate = { redirectTo: Routes.SignIn() }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ import { Transition } from "@headlessui/react"; | |||||||
| import { IoBackspace, IoCall } from "react-icons/io5"; | import { IoBackspace, IoCall } from "react-icons/io5"; | ||||||
|  |  | ||||||
| import { Direction } from "db"; | import { Direction } from "db"; | ||||||
| import Layout from "app/core/layouts/layout"; | import AppLayout from "app/core/layouts/layout"; | ||||||
| import Keypad from "../components/keypad"; | import Keypad from "../components/keypad"; | ||||||
| import usePhoneCalls from "../hooks/use-phone-calls"; | import usePhoneCalls from "../hooks/use-phone-calls"; | ||||||
| import useKeyPress from "../hooks/use-key-press"; | import useKeyPress from "../hooks/use-key-press"; | ||||||
| @@ -161,7 +161,7 @@ const pressBackspaceAtom = atom(null, (get, set) => { | |||||||
| 	set(phoneNumberAtom, (prevState) => prevState.slice(0, -1)); | 	set(phoneNumberAtom, (prevState) => prevState.slice(0, -1)); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| KeypadPage.getLayout = (page) => <Layout title="Keypad">{page}</Layout>; | KeypadPage.getLayout = (page) => <AppLayout title="Keypad">{page}</AppLayout>; | ||||||
|  |  | ||||||
| KeypadPage.authenticate = { redirectTo: Routes.SignIn() }; | KeypadPage.authenticate = { redirectTo: Routes.SignIn() }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,10 +10,11 @@ export default function BillingHistory() { | |||||||
| 		skip, | 		skip, | ||||||
| 		pagesNumber, | 		pagesNumber, | ||||||
| 		currentPage, | 		currentPage, | ||||||
| 		goToPreviousPage, | 		lastPage, | ||||||
| 		hasPreviousPage, | 		hasPreviousPage, | ||||||
| 		goToNextPage, |  | ||||||
| 		hasNextPage, | 		hasNextPage, | ||||||
|  | 		goToPreviousPage, | ||||||
|  | 		goToNextPage, | ||||||
| 		setPage, | 		setPage, | ||||||
| 	} = usePaymentsHistory(); | 	} = usePaymentsHistory(); | ||||||
|  |  | ||||||
| @@ -104,8 +105,8 @@ export default function BillingHistory() { | |||||||
| 										Previous | 										Previous | ||||||
| 									</button> | 									</button> | ||||||
| 									<p className="text-sm text-gray-700 self-center"> | 									<p className="text-sm text-gray-700 self-center"> | ||||||
| 										Page <span className="font-medium">1</span> of{" "} | 										Page <span className="font-medium">{currentPage}</span> of{" "} | ||||||
| 										<span className="font-medium">4</span> | 										<span className="font-medium">{lastPage}</span> | ||||||
| 									</p> | 									</p> | ||||||
| 									<button | 									<button | ||||||
| 										onClick={goToNextPage} | 										onClick={goToNextPage} | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ type Props = { | |||||||
| const PaddleLink: FunctionComponent<Props> = ({ onClick, text }) => ( | const PaddleLink: FunctionComponent<Props> = ({ onClick, text }) => ( | ||||||
| 	<button className="flex space-x-2 items-center text-left" onClick={onClick}> | 	<button className="flex space-x-2 items-center text-left" onClick={onClick}> | ||||||
| 		<HiExternalLink className="w-6 h-6 flex-shrink-0" /> | 		<HiExternalLink className="w-6 h-6 flex-shrink-0" /> | ||||||
| 		<span className="transition-colors duration-150 border-b border-transparent hover:border-primary-500"> | 		<span className="font-medium transition-colors duration-150 border-b border-transparent hover:border-primary-500"> | ||||||
| 			{text} | 			{text} | ||||||
| 		</span> | 		</span> | ||||||
| 	</button> | 	</button> | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| import type { FunctionComponent } from "react"; | import type { FunctionComponent } from "react"; | ||||||
|  | import { Suspense } from "react"; | ||||||
| import { Link, Routes, useMutation, useRouter } from "blitz"; | import { Link, Routes, useMutation, useRouter } from "blitz"; | ||||||
| import clsx from "clsx"; | import clsx from "clsx"; | ||||||
| import { | import { | ||||||
| @@ -10,9 +11,10 @@ import { | |||||||
| 	IoPersonCircleOutline, | 	IoPersonCircleOutline, | ||||||
| } from "react-icons/io5"; | } from "react-icons/io5"; | ||||||
|  |  | ||||||
| import Layout from "app/core/layouts/layout"; | import AppLayout from "app/core/layouts/layout"; | ||||||
| import logout from "app/auth/mutations/logout"; |  | ||||||
| import Divider from "./divider"; | import Divider from "./divider"; | ||||||
|  | import Spinner from "../../core/components/spinner"; | ||||||
|  | import logout from "app/auth/mutations/logout"; | ||||||
|  |  | ||||||
| const subNavigation = [ | const subNavigation = [ | ||||||
| 	{ name: "Account", href: Routes.Account(), icon: IoPersonCircleOutline }, | 	{ name: "Account", href: Routes.Account(), icon: IoPersonCircleOutline }, | ||||||
| @@ -26,7 +28,7 @@ const SettingsLayout: FunctionComponent = ({ children }) => { | |||||||
| 	const [logoutMutation] = useMutation(logout); | 	const [logoutMutation] = useMutation(logout); | ||||||
|  |  | ||||||
| 	return ( | 	return ( | ||||||
| 		<Layout title="Settings"> | 		<AppLayout title="Settings"> | ||||||
| 			<header className="bg-gray-100 px-2 sm:px-6 lg:px-8"> | 			<header className="bg-gray-100 px-2 sm:px-6 lg:px-8"> | ||||||
| 				<header className="flex"> | 				<header className="flex"> | ||||||
| 					<span className="flex items-center cursor-pointer" onClick={router.back}> | 					<span className="flex items-center cursor-pointer" onClick={router.back}> | ||||||
| @@ -43,7 +45,7 @@ const SettingsLayout: FunctionComponent = ({ children }) => { | |||||||
| 								const isCurrentPage = item.href.pathname === router.pathname; | 								const isCurrentPage = item.href.pathname === router.pathname; | ||||||
|  |  | ||||||
| 								return ( | 								return ( | ||||||
| 									<Link key={item.name} href={item.href}> | 									<Link key={item.name} href={item.href} prefetch> | ||||||
| 										<a | 										<a | ||||||
| 											className={clsx( | 											className={clsx( | ||||||
| 												isCurrentPage | 												isCurrentPage | ||||||
| @@ -79,10 +81,12 @@ const SettingsLayout: FunctionComponent = ({ children }) => { | |||||||
| 						</nav> | 						</nav> | ||||||
| 					</aside> | 					</aside> | ||||||
|  |  | ||||||
| 					<div className="flex-grow overflow-y-auto space-y-6 px-2 sm:px-6 lg:col-span-9">{children}</div> | 					<div className="flex-grow overflow-y-auto space-y-6 px-2 sm:px-6 lg:col-span-9"> | ||||||
|  | 						<Suspense fallback={<Spinner />}>{children}</Suspense> | ||||||
|  | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
| 			</main> | 			</main> | ||||||
| 		</Layout> | 		</AppLayout> | ||||||
| 	); | 	); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ export default function usePaymentsHistory() { | |||||||
| 		.fill(-1) | 		.fill(-1) | ||||||
| 		.map((_, i) => i + 1); | 		.map((_, i) => i + 1); | ||||||
| 	const currentPage = Math.floor((skip / count) * totalPages) + 1; | 	const currentPage = Math.floor((skip / count) * totalPages) + 1; | ||||||
|  | 	const lastPage = pagesNumber[pagesNumber.length - 1]; | ||||||
| 	const hasPreviousPage = skip > 0; | 	const hasPreviousPage = skip > 0; | ||||||
| 	const hasNextPage = hasMore && !!nextPage; | 	const hasNextPage = hasMore && !!nextPage; | ||||||
| 	const goToPreviousPage = () => hasPreviousPage && setSkip(skip - itemsPerPage); | 	const goToPreviousPage = () => hasPreviousPage && setSkip(skip - itemsPerPage); | ||||||
| @@ -26,6 +27,7 @@ export default function usePaymentsHistory() { | |||||||
| 		skip, | 		skip, | ||||||
| 		pagesNumber, | 		pagesNumber, | ||||||
| 		currentPage, | 		currentPage, | ||||||
|  | 		lastPage, | ||||||
| 		hasPreviousPage, | 		hasPreviousPage, | ||||||
| 		hasNextPage, | 		hasNextPage, | ||||||
| 		goToPreviousPage, | 		goToPreviousPage, | ||||||
|   | |||||||
| @@ -1,4 +1,3 @@ | |||||||
| import { Suspense } from "react"; |  | ||||||
| import type { BlitzPage } from "blitz"; | import type { BlitzPage } from "blitz"; | ||||||
| import { Routes, dynamic } from "blitz"; | import { Routes, dynamic } from "blitz"; | ||||||
|  |  | ||||||
| @@ -8,10 +7,8 @@ import PhoneNumberForm from "../../components/phone/phone-number-form"; | |||||||
| const PhoneSettings: BlitzPage = () => { | const PhoneSettings: BlitzPage = () => { | ||||||
| 	return ( | 	return ( | ||||||
| 		<div className="flex flex-col space-y-6"> | 		<div className="flex flex-col space-y-6"> | ||||||
| 			<Suspense fallback="Loading..."> | 			<TwilioApiForm /> | ||||||
| 				<TwilioApiForm /> | 			<PhoneNumberForm /> | ||||||
| 				<PhoneNumberForm /> |  | ||||||
| 			</Suspense> |  | ||||||
| 		</div> | 		</div> | ||||||
| 	); | 	); | ||||||
| }; | }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 m5r
					m5r