refactor: UI page layout component nesting and move app context to redux (#265)

* refactor: remove app context to use redux instead

* refactor: gradient overlay to single element

* refactor: app state backgroundColor to overlayColor

* refactor: app page nesting and positioning
This commit is contained in:
Felipe Mendes 2023-05-19 15:57:12 -03:00 committed by GitHub
parent 07db609798
commit 74d4a4eb9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 101 additions and 92 deletions

View File

@ -1,33 +0,0 @@
import { useState } from 'react';
import { createContext } from './utils';
export type AppContext = {
backgroundColor: string;
setBackgroundColor: (color: string) => void;
};
const [AppProvider, useContext] = createContext<AppContext>({
name: 'App.Context',
hookName: 'App.useContext',
providerName: 'App.Provider',
});
export abstract class App {
static readonly useContext = useContext;
static readonly Provider: React.FC<App.AppProps> = ({ children }) => {
const [backgroundColor, setBackgroundColor] = useState('');
return (
<AppProvider value={{ backgroundColor, setBackgroundColor }}>
{children}
</AppProvider>
);
};
}
export namespace App {
export type AppProps = {
children: React.ReactNode;
};
}

View File

@ -2,7 +2,6 @@ import { HashRouter, Navigate, Route, Routes } from 'react-router-dom';
import { themeGlobals } from '@/theme/globals'; import { themeGlobals } from '@/theme/globals';
import { App as AppContext } from './app.context';
import { AppPage, ToastProvider } from './components'; import { AppPage, ToastProvider } from './components';
import { import {
ComponentsTest, ComponentsTest,
@ -18,19 +17,17 @@ export const App: React.FC = () => {
<> <>
<HashRouter> <HashRouter>
<ToastProvider /> <ToastProvider />
<AppContext.Provider> <AppPage>
<AppPage> <Routes>
<Routes> <Route path="/" element={<ExploreView />} />
<Route path="/" element={<ExploreView />} /> <Route path="/mint" element={<Mint />} />
<Route path="/mint" element={<Mint />} /> <Route path="/create-ap/:id" element={<CreateAP />} />
<Route path="/create-ap/:id" element={<CreateAP />} /> <Route path="/nfa/:id" element={<IndexedNFAView />} />
<Route path="/nfa/:id" element={<IndexedNFAView />} /> {/** TODO remove for release */}
{/** TODO remove for release */} <Route path="/components-test" element={<ComponentsTest />} />
<Route path="/components-test" element={<ComponentsTest />} /> <Route path="*" element={<Navigate to="/" />} />
<Route path="*" element={<Navigate to="/" />} /> </Routes>
</Routes> </AppPage>
</AppPage>
</AppContext.Provider>
</HashRouter> </HashRouter>
</> </>
); );

View File

@ -1,6 +1,8 @@
import { App } from '@/app.context'; import React from 'react';
import { NavBar } from '@/components'; import { NavBar } from '@/components';
import { GradientOverlay } from './gradient-overlay';
import { PageStyles as PS } from './page.styles'; import { PageStyles as PS } from './page.styles';
export type AppPageProps = { export type AppPageProps = {
@ -8,18 +10,12 @@ export type AppPageProps = {
}; };
export const AppPage: React.FC<AppPageProps> = ({ children }: AppPageProps) => { export const AppPage: React.FC<AppPageProps> = ({ children }: AppPageProps) => {
const { backgroundColor } = App.useContext();
const background = `linear-gradient(180deg, #${backgroundColor}59 0%, #000000 30%)`;
return ( return (
<PS.Container <>
css={{ <GradientOverlay />
background: background,
}}
>
<NavBar /> <NavBar />
<PS.Content as="main">{children}</PS.Content> <PS.Content>{children}</PS.Content>
</PS.Container> </>
); );
}; };

View File

@ -0,0 +1,17 @@
import { useAppStore } from '@/store';
import { PageStyles as PS } from './page.styles';
export const GradientOverlay: React.FC = () => {
const { overlayColor } = useAppStore();
if (!overlayColor) return null;
return (
<PS.GradientOverlay
css={{
background: `linear-gradient(180deg, #${overlayColor}59 0%, transparent 30%)`,
}}
/>
);
};

View File

@ -1,12 +1,16 @@
import { styled } from '@/theme'; import { styled } from '@/theme';
export abstract class PageStyles { export const PageStyles = {
public static readonly Container = styled('div', { GradientOverlay: styled('div', {
minHeight: '100vh', position: 'absolute',
position: 'relative', inset: 0,
}); pointerEvents: 'none',
}),
public static readonly Content = styled('div', { Content: styled('main', {
position: 'relative',
display: 'flex',
flexDirection: 'column',
width: '100%', width: '100%',
minHeight: '85vh', minHeight: '85vh',
maxWidth: '$6xl', maxWidth: '$6xl',
@ -16,5 +20,5 @@ export abstract class PageStyles {
'@md': { '@md': {
padding: '0 $6', padding: '0 $6',
}, },
}); }),
} };

View File

@ -0,0 +1,33 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState } from '@/store';
import { useAppSelector } from '@/store/hooks';
export interface AppState {
overlayColor?: string;
}
const initialState: AppState = {
overlayColor: undefined,
};
export const appSlice = createSlice({
name: 'AppSlice',
initialState,
reducers: {
setOverlayColor: (state, action: PayloadAction<string>) => {
state.overlayColor = action.payload;
},
clearOverlayColor: (state) => {
state.overlayColor = undefined;
},
},
});
export const appActions = appSlice.actions;
const selectAppState = (state: RootState): AppState => state.app;
export const useAppStore = (): AppState => useAppSelector(selectAppState);
export default appSlice.reducer;

View File

@ -0,0 +1 @@
export * from './app-slice';

View File

@ -43,9 +43,9 @@ export const bunnyCDNActions = {
...asyncThunk, ...asyncThunk,
}; };
const selectENSState = (state: RootState): BunnyCDNState => state.bunnyCDN; const selectBunnyCDNState = (state: RootState): BunnyCDNState => state.bunnyCDN;
export const useBunnyCDNStore = (): BunnyCDNState => export const useBunnyCDNStore = (): BunnyCDNState =>
useAppSelector(selectENSState); useAppSelector(selectBunnyCDNState);
export default bunnyCDNSlice.reducer; export default bunnyCDNSlice.reducer;

View File

@ -3,3 +3,4 @@ export * from './github';
export * from './toasts'; export * from './toasts';
export * from './ens'; export * from './ens';
export * from './bunny-cdn'; export * from './bunny-cdn';
export * from './app';

View File

@ -1,5 +1,6 @@
import { configureStore } from '@reduxjs/toolkit'; import { configureStore } from '@reduxjs/toolkit';
import appReducer from './features/app/app-slice';
import bunnyCDNReducer from './features/bunny-cdn/bunny-cdn-slice'; import bunnyCDNReducer from './features/bunny-cdn/bunny-cdn-slice';
import ENSReducer from './features/ens/ens-slice'; import ENSReducer from './features/ens/ens-slice';
import fleekERC721Reducer from './features/fleek-erc721/fleek-erc721-slice'; import fleekERC721Reducer from './features/fleek-erc721/fleek-erc721-slice';
@ -8,6 +9,7 @@ import toastsReducer from './features/toasts/toasts-slice';
export const store = configureStore({ export const store = configureStore({
reducer: { reducer: {
app: appReducer,
bunnyCDN: bunnyCDNReducer, bunnyCDN: bunnyCDNReducer,
ENS: ENSReducer, ENS: ENSReducer,
fleekERC721: fleekERC721Reducer, fleekERC721: fleekERC721Reducer,

View File

@ -3,14 +3,8 @@ import { styled } from '@/theme';
export const CreateApStyles = { export const CreateApStyles = {
Container: styled(Flex, { Container: styled(Flex, {
height: '100%', flex: 1,
flexDirection: 'column', alignItems: 'center',
minHeight: '85vh', justifyContent: 'center',
alignItems: 'flex-start',
'@md': {
alignItems: 'center',
justifyContent: 'center',
},
}), }),
}; };

View File

@ -1,8 +1,8 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { App } from '@/app.context';
import { Button } from '@/components'; import { Button } from '@/components';
import { useAppStore } from '@/store';
import { parseNumberToHexColor } from '@/utils/color'; import { parseNumberToHexColor } from '@/utils/color';
import { IndexedNFA } from '../../indexed-nfa.context'; import { IndexedNFA } from '../../indexed-nfa.context';
@ -18,8 +18,8 @@ export const IndexedNFAAsideFragment: React.FC = () => {
const [top, setTop] = useState<number>(); const [top, setTop] = useState<number>();
const { nfa } = IndexedNFA.useContext(); const { nfa } = IndexedNFA.useContext();
const { backgroundColor } = App.useContext(); const { overlayColor } = useAppStore();
const background = `radial-gradient(closest-corner circle at 90% 45%, #${backgroundColor}8c 1% ,#${backgroundColor}57 20%, transparent 40%), radial-gradient(closest-corner circle at 60% 25%, #${backgroundColor} 3%, #${backgroundColor}73 30%, #181818 70%)`; const background = `radial-gradient(closest-corner circle at 90% 45%, #${overlayColor}8c 1% ,#${overlayColor}57 20%, transparent 40%), radial-gradient(closest-corner circle at 60% 25%, #${overlayColor} 3%, #${overlayColor}73 30%, #181818 70%)`;
useEffect(() => { useEffect(() => {
setTop(ref.current?.getBoundingClientRect().top); setTop(ref.current?.getBoundingClientRect().top);

View File

@ -3,8 +3,8 @@ import { ethers } from 'ethers';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { App } from '@/app.context';
import { getNFADetailDocument } from '@/graphclient'; import { getNFADetailDocument } from '@/graphclient';
import { appActions, useAppDispatch } from '@/store';
import { AppLog } from '@/utils'; import { AppLog } from '@/utils';
import { parseNumberToHexColor } from '@/utils/color'; import { parseNumberToHexColor } from '@/utils/color';
@ -18,14 +18,14 @@ import { IndexedNFAStyles as S } from './indexed-nfa.styles';
export const IndexedNFAView: React.FC = () => { export const IndexedNFAView: React.FC = () => {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const { setBackgroundColor } = App.useContext(); const dispatch = useAppDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
useEffect(() => { useEffect(() => {
return () => { return () => {
setBackgroundColor('000000'); dispatch(appActions.clearOverlayColor());
}; };
}, [setBackgroundColor]); }, [dispatch]);
const handleError = (error: unknown): void => { const handleError = (error: unknown): void => {
AppLog.errorToast( AppLog.errorToast(
@ -43,7 +43,9 @@ export const IndexedNFAView: React.FC = () => {
onCompleted(data) { onCompleted(data) {
if (!data.token) handleError(new Error('Token not found')); if (!data.token) handleError(new Error('Token not found'));
if (data.token?.color) if (data.token?.color)
setBackgroundColor(parseNumberToHexColor(data.token.color)); dispatch(
appActions.setOverlayColor(parseNumberToHexColor(data.token.color))
);
}, },
onError(error) { onError(error) {
handleError(error); handleError(error);

View File

@ -3,13 +3,8 @@ import { styled } from '@/theme';
export const MintStyles = { export const MintStyles = {
Container: styled(Flex, { Container: styled(Flex, {
height: '100%', flex: 1,
alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
minHeight: '85vh',
alignItems: 'flex-start',
'@md': {
alignItems: 'center',
},
}), }),
}; };