-
Notifications
You must be signed in to change notification settings - Fork 3
feat: main pages loading spinner #1083
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 ( | ||
| <div className={style.loading_container}> | ||
| <DotLoader | ||
| color={accentColor} | ||
| size={size} | ||
| speedMultiplier={2} | ||
| /> | ||
| </div> | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| .loading_container { | ||
| display: flex; | ||
| justify-content: center; | ||
| align-items: center; | ||
| min-height: 50vh; | ||
| width: 100%; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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<NetworksProps, NetworksSta | |
| } | ||
|
|
||
| render (): React.ReactElement { | ||
| if (this.state.networks !== []) { | ||
| if (this.state.networks.length === 0) { | ||
| return ( | ||
| <> | ||
| <TopBar title="Networks" user={this.props.user.stUser?.email} /> | ||
| <NetworkList networks={this.state.networks} userNetworks={this.state.userNetworks} /> | ||
| <Loading /> | ||
| </> | ||
| ) | ||
| } | ||
| return ( | ||
| <> | ||
| <TopBar title="Networks" user={this.props.user.stUser?.email} /> | ||
| <NetworkList networks={this.state.networks} userNetworks={this.state.userNetworks} /> | ||
| </> | ||
| ) | ||
|
Comment on lines
+85
to
+98
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix the loading condition to avoid conflating empty state with loading state. Using Add a dedicated interface NetworksState {
networks: Network[]
userNetworks: UserNetworksInfo[]
+ loading: boolean
}
export default class Networks extends React.Component<NetworksProps, NetworksState> {
constructor (props: NetworksProps) {
super(props)
this.props = props
this.state = {
networks: [],
- userNetworks: []
+ userNetworks: [],
+ loading: true
}
}
async componentDidMount (): Promise<void> {
await this.fetchNetworks()
+ this.setState({ loading: false })
}Then update the render condition: render (): React.ReactElement {
- if (this.state.networks.length === 0) {
+ if (this.state.loading) {
return (
<>
<TopBar title="Networks" user={this.props.user.stUser?.email} />
<Loading />
</>
)
}
🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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<WalletsProps, WalletsState> { | ||||||||||||||||||||||||||||||||||
|
|
@@ -56,13 +58,15 @@ export default class Wallets extends React.Component<WalletsProps, WalletsState> | |||||||||||||||||||||||||||||||||
| this.state = { | ||||||||||||||||||||||||||||||||||
| walletsWithPaymentInfo: [], | ||||||||||||||||||||||||||||||||||
| userAddresses: [], | ||||||||||||||||||||||||||||||||||
| networksInfo: [] | ||||||||||||||||||||||||||||||||||
| networksInfo: [], | ||||||||||||||||||||||||||||||||||
| loading: true | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| async componentDidMount (): Promise<void> { | ||||||||||||||||||||||||||||||||||
| await this.fetchWallets() | ||||||||||||||||||||||||||||||||||
| await this.fetchNetworks() | ||||||||||||||||||||||||||||||||||
| this.setState({ loading: false }) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
66
to
70
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling to prevent infinite loading state. If Apply this diff to add proper error handling: async componentDidMount (): Promise<void> {
- await this.fetchWallets()
- await this.fetchNetworks()
- this.setState({ loading: false })
+ try {
+ await this.fetchWallets()
+ await this.fetchNetworks()
+ } catch (error) {
+ console.error('Error loading wallet data:', error)
+ // Optionally show error UI to user
+ } finally {
+ this.setState({ loading: false })
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| async fetchWallets (): Promise<void> { | ||||||||||||||||||||||||||||||||||
|
|
@@ -102,6 +106,14 @@ export default class Wallets extends React.Component<WalletsProps, WalletsState> | |||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| render (): React.ReactElement { | ||||||||||||||||||||||||||||||||||
| if (this.state.loading) { | ||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||
| <TopBar title="Wallets" user={this.props.user.stUser?.email} /> | ||||||||||||||||||||||||||||||||||
| <Loading /> | ||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||
| <TopBar title="Wallets" user={this.props.user.stUser?.email} /> | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reconsider the date refactoring to avoid initial render flash.
This refactor introduces
dateStringstate initialized as an empty string, which causes a brief flash of empty content before theuseEffectpopulates it. The date calculation could be done directly without state, or the initial state could be set to a default value.Additionally, this change appears unrelated to the loading spinner feature and introduces unnecessary complexity.
Consider one of these solutions:
Solution 1 (preferred): Compute date directly without state
Solution 2: Initialize with a computed value to prevent flash
export default function TopBar ({ title, user }: TopBarProps): JSX.Element { - const [dateString, setDateString] = useState<string>('') + const [dateString, setDateString] = useState<string>(() => { + const currentDate = new Date() + const month = currentDate.toLocaleString('en-US', { month: 'long' }) + const day = currentDate.getDate() + const year = currentDate.getFullYear() + return `${month} ${day}, ${year}` + }) - 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}`) - }, [])Also applies to: 11-19, 26-26
🤖 Prompt for AI Agents