diff --git a/components/Loading/index.tsx b/components/Loading/index.tsx new file mode 100644 index 000000000..ef08e829e --- /dev/null +++ b/components/Loading/index.tsx @@ -0,0 +1,31 @@ +import React, { useState, useEffect } from 'react' +import { DotLoader } from 'react-spinners' +import style from './loading.module.css' + +interface LoadingProps { + size?: number + color?: string +} + +export default function Loading ({ size = 60, color }: LoadingProps): React.ReactElement { + const [accentColor, setAccentColor] = useState(color ?? '#0ac18e') + + useEffect(() => { + if (color === undefined) { + const computedColor = getComputedStyle(document.documentElement).getPropertyValue('--accent-color').trim() + if (computedColor !== '') { + setAccentColor(computedColor) + } + } + }, [color]) + + return ( +
+ +
+ ) +} diff --git a/components/Loading/loading.module.css b/components/Loading/loading.module.css new file mode 100644 index 000000000..cd834c1a5 --- /dev/null +++ b/components/Loading/loading.module.css @@ -0,0 +1,7 @@ +.loading_container { + display: flex; + justify-content: center; + align-items: center; + min-height: 50vh; + width: 100%; +} diff --git a/components/TopBar/index.tsx b/components/TopBar/index.tsx index 27dd8fbc5..82b4fe8cd 100644 --- a/components/TopBar/index.tsx +++ b/components/TopBar/index.tsx @@ -1,4 +1,5 @@ import Link from 'next/link' +import { useState, useEffect } from 'react' import style from './topbar.module.css' interface TopBarProps { @@ -7,17 +8,22 @@ interface TopBarProps { } export default function TopBar ({ title, user }: TopBarProps): JSX.Element { - const currentDate = new Date() - const month = currentDate.toLocaleString('en-US', { month: 'long' }) - const day = currentDate.getDate() - const year = currentDate.getFullYear() + const [dateString, setDateString] = useState('') + + useEffect(() => { + const currentDate = new Date() + const month = currentDate.toLocaleString('en-US', { month: 'long' }) + const day = currentDate.getDate() + const year = currentDate.getFullYear() + setDateString(`${month} ${day}, ${year}`) + }, []) return (

{title}

- {month} {day}, {year} + {dateString}
diff --git a/package.json b/package.json index 9dfc0d30e..7eacc7f7a 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "react-dom": "^18.2.0", "react-hook-form": "^7.32.2", "react-select": "^5.8.3", + "react-spinners": "^0.14.1", "react-table": "^7.8.0", "react-timezone-select": "^3.2.8", "react-to-print": "^3.1.0", diff --git a/pages/admin/index.tsx b/pages/admin/index.tsx index 384eb217b..5744b8e9e 100644 --- a/pages/admin/index.tsx +++ b/pages/admin/index.tsx @@ -13,6 +13,7 @@ import { multiBlockchainClient } from 'services/chronikService' import { MainNetworkSlugsType } from 'constants/index' import SubscribedAddresses from 'components/Admin/SubscribedAddresses' import ChronikURLs from 'components/Admin/ChronikURLs' +import Loading from 'components/Loading' export const getServerSideProps: GetServerSideProps = async (context) => { // this runs on the backend, so we must call init on supertokens-node SDK @@ -57,6 +58,7 @@ interface IProps { export default function Admin ({ user, isAdmin, chronikUrls }: IProps): JSX.Element { const router = useRouter() const [users, setUsers] = useState([]) + const [loading, setLoading] = useState(true) useEffect(() => { if (user === null || !isAdmin) { @@ -68,10 +70,19 @@ export default function Admin ({ user, isAdmin, chronikUrls }: IProps): JSX.Elem void (async () => { const usersJSON = await (await fetch('/api/users')).json() setUsers(usersJSON) + setLoading(false) })() }, []) if (user !== null && isAdmin) { + if (loading) { + return ( +
+

Admin Dashboard

+ +
+ ) + } return <>

Admin Dashboard

diff --git a/pages/button/[id].tsx b/pages/button/[id].tsx index d7646e0dd..d6de1c40a 100644 --- a/pages/button/[id].tsx +++ b/pages/button/[id].tsx @@ -1,5 +1,4 @@ import React, { useState, useEffect } from 'react' -import Page from 'components/Page' import { PaybuttonDetail } from 'components/Paybutton' import { PaybuttonWithAddresses } from 'services/paybuttonService' import { PaybuttonTransactions } from 'components/Transaction' @@ -18,6 +17,7 @@ import { fetchUserProfileFromId } from 'services/userService' import { removeUnserializableFields } from 'utils' import moment from 'moment-timezone' import Button from 'components/Button' +import Loading from 'components/Loading' export const getServerSideProps: GetServerSideProps = async (context) => { supertokensNode.init(SuperTokensConfig.backendConfig()) @@ -214,6 +214,6 @@ export default function PayButton (props: PaybuttonProps): React.ReactElement { } return ( - + ) } diff --git a/pages/buttons/index.tsx b/pages/buttons/index.tsx index 6d520f1a9..ca875d126 100644 --- a/pages/buttons/index.tsx +++ b/pages/buttons/index.tsx @@ -10,6 +10,7 @@ import { GetServerSideProps } from 'next' import TopBar from 'components/TopBar' import { fetchUserWithSupertokens, UserWithSupertokens } from 'services/userService' import { removeUnserializableFields } from 'utils/index' +import Loading from 'components/Loading' export const getServerSideProps: GetServerSideProps = async (context) => { // this runs on the backend, so we must call init on supertokens-node SDK @@ -47,18 +48,20 @@ interface PaybuttonsState { paybuttons: PaybuttonWithAddresses[] wallets: WalletWithAddressesWithPaybuttons[] error: String + loading: boolean } export default class Buttons extends React.Component { constructor (props: PaybuttonsProps) { super(props) this.props = props - this.state = { paybuttons: [], wallets: [], error: '' } + this.state = { paybuttons: [], wallets: [], error: '', loading: true } } async componentDidMount (): Promise { await this.fetchPaybuttons() await this.fetchWallets() + this.setState({ loading: false }) } async fetchPaybuttons (): Promise { @@ -113,6 +116,14 @@ export default class Buttons extends React.Component + + + + ) + } return ( <> diff --git a/pages/dashboard/index.tsx b/pages/dashboard/index.tsx index edce4804e..57d543e25 100644 --- a/pages/dashboard/index.tsx +++ b/pages/dashboard/index.tsx @@ -15,6 +15,7 @@ import { fetchUserWithSupertokens, UserWithSupertokens } from 'services/userServ import moment from 'moment-timezone' import SettingsIcon from '../../assets/settings-slider-icon.png' import Image from 'next/image' +import Loading from 'components/Loading' const Chart = dynamic(async () => await import('components/Chart'), { ssr: false @@ -148,7 +149,14 @@ export default function Dashboard ({ user }: PaybuttonsProps): React.ReactElemen } }, [activePeriodString, dashboardData]) - if (dashboardData === undefined || activePeriod === undefined) return <> + if (dashboardData === undefined || activePeriod === undefined) { + return ( + <> + + + + ) + } return ( <> diff --git a/pages/networks/index.tsx b/pages/networks/index.tsx index 3c96578d9..44f0678d3 100644 --- a/pages/networks/index.tsx +++ b/pages/networks/index.tsx @@ -9,6 +9,7 @@ import { UserNetworksInfo } from 'services/networkService' import TopBar from 'components/TopBar' import { fetchUserWithSupertokens, UserWithSupertokens } from 'services/userService' import { removeUnserializableFields } from 'utils/index' +import Loading from 'components/Loading' export const getServerSideProps: GetServerSideProps = async (context) => { // this runs on the backend, so we must call init on supertokens-node SDK @@ -81,13 +82,19 @@ export default class Networks extends React.Component - + ) } + return ( + <> + + + + ) } } diff --git a/pages/payments/index.tsx b/pages/payments/index.tsx index 39ae08c99..f428ec5e7 100644 --- a/pages/payments/index.tsx +++ b/pages/payments/index.tsx @@ -26,6 +26,7 @@ import InvoiceModal from 'components/Transaction/InvoiceModal' import { TransactionWithAddressAndPricesAndInvoices } from 'services/transactionService' import { fetchOrganizationForUser } from 'services/organizationService' import { InvoiceWithTransaction } from 'services/invoiceService' +import Loading from 'components/Loading' export const getServerSideProps: GetServerSideProps = async (context) => { // this runs on the backend, so we must call init on supertokens-node SDK @@ -76,6 +77,7 @@ export default function Payments ({ user, userId, organization }: PaybuttonsProp const [selectedTransactionYears, setSelectedTransactionYears] = useState([]) const [loading, setLoading] = useState(false) + const [pageLoading, setPageLoading] = useState(true) const [buttons, setButtons] = useState([]) const [selectedButtonIds, setSelectedButtonIds] = useState([]) const [showFilters, setShowFilters] = useState(false) @@ -178,6 +180,7 @@ export default function Payments ({ user, userId, organization }: PaybuttonsProp setPaybuttonNetworks(networkIds) setTransactionYears(years) + setPageLoading(false) } useEffect(() => { @@ -474,6 +477,15 @@ export default function Payments ({ user, userId, organization }: PaybuttonsProp setEndDate('') } + if (pageLoading) { + return ( + <> + + + + ) + } + return ( <> diff --git a/pages/wallets/index.tsx b/pages/wallets/index.tsx index 3ddadffdb..a1fdfe0ad 100644 --- a/pages/wallets/index.tsx +++ b/pages/wallets/index.tsx @@ -11,6 +11,7 @@ import { UserNetworksInfo } from 'services/networkService' import TopBar from 'components/TopBar' import { fetchUserWithSupertokens, UserWithSupertokens } from 'services/userService' import { removeUnserializableFields } from 'utils/index' +import Loading from 'components/Loading' export const getServerSideProps: GetServerSideProps = async (context) => { // this runs on the backend, so we must call init on supertokens-node SDK @@ -48,6 +49,7 @@ interface WalletsState { walletsWithPaymentInfo: WalletWithPaymentInfo[] userAddresses: AddressWithPaybuttons[] networksInfo: UserNetworksInfo[] + loading: boolean } export default class Wallets extends React.Component { @@ -56,13 +58,15 @@ export default class Wallets extends React.Component this.state = { walletsWithPaymentInfo: [], userAddresses: [], - networksInfo: [] + networksInfo: [], + loading: true } } async componentDidMount (): Promise { await this.fetchWallets() await this.fetchNetworks() + this.setState({ loading: false }) } async fetchWallets (): Promise { @@ -102,6 +106,14 @@ export default class Wallets extends React.Component } render (): React.ReactElement { + if (this.state.loading) { + return ( + <> + + + + ) + } return ( <> diff --git a/yarn.lock b/yarn.lock index 6af26e033..76913219a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2294,7 +2294,7 @@ chronik-client-cashtokens@^3.1.1-rc0: dependencies: "@types/ws" "^8.2.1" axios "^1.6.3" - ecashaddrjs "file:../../../.cache/yarn/v6/npm-chronik-client-cashtokens-3.1.1-rc0-e5e4a3538e8010b70623974a731bf2712506c5e3-integrity/node_modules/ecashaddrjs" + ecashaddrjs "file:../.cache/yarn/v6/npm-chronik-client-cashtokens-3.1.1-rc0-e5e4a3538e8010b70623974a731bf2712506c5e3-integrity/node_modules/ecashaddrjs" isomorphic-ws "^4.0.1" protobufjs "^6.8.8" ws "^8.3.0" @@ -2830,7 +2830,7 @@ ecashaddrjs@^1.0.7: big-integer "1.6.36" bs58check "^3.0.1" -ecashaddrjs@^2.0.0, "ecashaddrjs@file:../.cache/yarn/v6/npm-chronik-client-cashtokens-3.1.1-rc0-e5e4a3538e8010b70623974a731bf2712506c5e3-integrity/node_modules/ecashaddrjs": +ecashaddrjs@^2.0.0, "ecashaddrjs@file:../../../.cache/yarn/v6/npm-chronik-client-cashtokens-3.1.1-rc0-e5e4a3538e8010b70623974a731bf2712506c5e3-integrity/node_modules/ecashaddrjs": version "2.0.0" resolved "https://registry.yarnpkg.com/ecashaddrjs/-/ecashaddrjs-2.0.0.tgz#d45ede7fb6168815dbcf664b8e0a6872e485d874" integrity sha512-EvK1V4D3+nIEoD0ggy/b0F4lW39/72R9aOs/scm6kxMVuXu16btc+H74eQv7okNfXaQWKgolEekZkQ6wfcMMLw== @@ -6008,6 +6008,11 @@ react-select@^5.8.3: react-transition-group "^4.3.0" use-isomorphic-layout-effect "^1.2.0" +react-spinners@^0.14.1: + version "0.14.1" + resolved "https://registry.yarnpkg.com/react-spinners/-/react-spinners-0.14.1.tgz#de7d7d6b3e6d4f29d9620c65495b502c7dd90812" + integrity sha512-2Izq+qgQ08HTofCVEdcAQCXFEYfqTDdfeDQJeo/HHQiQJD4imOicNLhkfN2eh1NYEWVOX4D9ok2lhuDB0z3Aag== + react-table@^7.8.0: version "7.8.0" resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.8.0.tgz#07858c01c1718c09f7f1aed7034fcfd7bda907d2"