improve loading states:
* app loader * specific loaders with spinner
This commit is contained in:
parent
29101b1daf
commit
931384b468
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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user