Skip to content

Commit

Permalink
Merge pull request #570 from lidofinance/develop
Browse files Browse the repository at this point in the history
Merge develop to main
  • Loading branch information
Jeday authored Dec 19, 2024
2 parents 47f7cf9 + 75ac190 commit a5156b1
Show file tree
Hide file tree
Showing 22 changed files with 294 additions and 71 deletions.
8 changes: 8 additions & 0 deletions IPFS.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@
"multiChainBanner": [],
"featureFlags": {
"ledgerLiveL2": true
},
"pages": {
"/withdrawals": {
"shouldDisable": true
},
"/rewards": {
"shouldDisable": true
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions assets/icons/lido-multichain/metis.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 54 additions & 0 deletions config/external-config/frontend-fallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useMemo } from 'react';
import {
Manifest,
ManifestConfig,
ManifestConfigPage,
ManifestConfigPageList,
ManifestEntry,
isManifestValid,
} from 'config/external-config';
import { getDexConfig } from 'features/withdrawals/request/withdrawal-rates';

import FallbackLocalManifest from 'IPFS.json' assert { type: 'json' };

export const getBackwardCompatibleConfig = (
config: ManifestEntry['config'],
): ManifestEntry['config'] => {
let pages: ManifestConfig['pages'];
const configPages = config.pages;
if (configPages) {
pages = (Object.keys(configPages) as ManifestConfigPage[])
.filter((key) => ManifestConfigPageList.has(key))
.reduce(
(acc, key) => {
if (acc) {
acc[key] = { ...configPages[key] };
}

return acc;
},
{} as ManifestConfig['pages'],
);
}

return {
enabledWithdrawalDexes: config.enabledWithdrawalDexes.filter(
(dex) => !!getDexConfig(dex),
),
featureFlags: { ...(config.featureFlags ?? {}) },
multiChainBanner: config.multiChainBanner ?? [],
pages,
};
};

export const useFallbackManifestEntry = (
prefetchedManifest: unknown,
chain: number,
): ManifestEntry => {
return useMemo(() => {
const isValid = isManifestValid(prefetchedManifest, chain);
return isValid
? prefetchedManifest[chain]
: (FallbackLocalManifest as unknown as Manifest)[chain];
}, [prefetchedManifest, chain]);
};
11 changes: 11 additions & 0 deletions config/external-config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,15 @@ export type {
Manifest,
ManifestEntry,
ExternalConfig,
ManifestConfigPage,
} from './types';
export { ManifestConfigPageList, ManifestConfigPageEnum } from './types';
export {
isManifestValid,
isManifestEntryValid,
isEnabledDexesValid,
isFeatureFlagsValid,
isMultiChainBannerValid,
isPagesValid,
shouldRedirectToRoot,
} from './utils';
21 changes: 21 additions & 0 deletions config/external-config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,29 @@ export type ManifestConfig = {
featureFlags: {
ledgerLiveL2?: boolean;
};
pages?: {
[page in ManifestConfigPage]?: {
shouldDisable?: boolean;
sections?: [string, ...string[]];
};
};
};

export enum ManifestConfigPageEnum {
Stake = '/',
Wrap = '/wrap',
Withdrawals = '/withdrawals',
Rewards = '/rewards',
Settings = '/settings',
Referral = '/referral',
}

export type ManifestConfigPage = `${ManifestConfigPageEnum}`;

export const ManifestConfigPageList = new Set<ManifestConfigPage>(
Object.values(ManifestConfigPageEnum),
);

export type ExternalConfig = Omit<ManifestEntry, 'config'> &
ManifestConfig & {
fetchMeta: UseQueryResult<ManifestEntry>;
Expand Down
6 changes: 3 additions & 3 deletions config/external-config/use-external-config-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { useQuery } from '@tanstack/react-query';
import { config } from 'config';
import { STRATEGY_LAZY } from 'consts/react-query-strategies';
import { IPFS_MANIFEST_URL } from 'consts/external-links';
import { isManifestEntryValid } from 'config/external-config';
import { standardFetcher } from 'utils/standardFetcher';

import {
getBackwardCompatibleConfig,
isManifestEntryValid,
useFallbackManifestEntry,
} from './utils';
} from './frontend-fallback';

import type { ExternalConfig, ManifestEntry } from './types';

export const useExternalConfigContext = (
Expand Down
116 changes: 66 additions & 50 deletions config/external-config/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,39 @@
import { useMemo } from 'react';
import type { Manifest, ManifestEntry } from './types';
import { getDexConfig } from 'features/withdrawals/request/withdrawal-rates';
import { config } from 'config';
import {
Manifest,
ManifestConfig,
ManifestConfigPage,
ManifestConfigPageEnum,
ManifestEntry,
} from 'config/external-config';

export const isMultiChainBannerValid = (config: object) => {
// allow empty config
if (!('multiChainBanner' in config) || !config.multiChainBanner) return true;

if (!Array.isArray(config.multiChainBanner)) return false;

const multiChainBanner = config.multiChainBanner;

if (
!multiChainBanner.every(
(chainId) => typeof chainId === 'number' && chainId > 0,
)
)
return false;

return !(new Set(multiChainBanner).size !== multiChainBanner.length);
};

export const isFeatureFlagsValid = (config: object) => {
// allow empty config
if (!('featureFlags' in config) || !config.featureFlags) return true;

import FallbackLocalManifest from 'IPFS.json' assert { type: 'json' };
// only objects
return !(typeof config.featureFlags !== 'object');
};

const isEnabledDexesValid = (config: object) => {
export const isEnabledDexesValid = (config: object) => {
if (
!(
'enabledWithdrawalDexes' in config &&
Expand All @@ -25,30 +54,18 @@ const isEnabledDexesValid = (config: object) => {
return new Set(enabledWithdrawalDexes).size === enabledWithdrawalDexes.length;
};

const isMultiChainBannerValid = (config: object) => {
// allow empty config
if (!('multiChainBanner' in config) || !config.multiChainBanner) return true;

if (!Array.isArray(config.multiChainBanner)) return false;

const multiChainBanner = config.multiChainBanner;

if (
!multiChainBanner.every(
(chainId) => typeof chainId === 'number' && chainId > 0,
)
)
return false;

return !(new Set(multiChainBanner).size !== multiChainBanner.length);
};
export const isPagesValid = (config: object) => {
if (!('pages' in config)) {
return true;
}

const isFeatureFlagsValid = (config: object) => {
// allow empty config
if (!('featureFlags' in config) || !config.featureFlags) return true;
const pages = config.pages as ManifestConfig['pages'];
if (pages && typeof pages === 'object') {
// INFO: exclude possible issue when stack interface can be deactivated
return !pages[ManifestConfigPageEnum.Stake]?.shouldDisable;
}

// only objects
return !(typeof config.featureFlags !== 'object');
return false;
};

export const isManifestEntryValid = (
Expand All @@ -65,25 +82,18 @@ export const isManifestEntryValid = (
) {
const config = entry.config;

return [isEnabledDexesValid, isMultiChainBannerValid, isFeatureFlagsValid]
return [
isEnabledDexesValid,
isMultiChainBannerValid,
isFeatureFlagsValid,
isPagesValid,
]
.map((validator) => validator(config))
.every((isValid) => isValid);
}
return false;
};

export const getBackwardCompatibleConfig = (
config: ManifestEntry['config'],
): ManifestEntry['config'] => {
return {
enabledWithdrawalDexes: config.enabledWithdrawalDexes.filter(
(dex) => !!getDexConfig(dex),
),
featureFlags: { ...(config.featureFlags ?? {}) },
multiChainBanner: config.multiChainBanner ?? [],
};
};

export const isManifestValid = (
manifest: unknown,
chain: number,
Expand All @@ -96,14 +106,20 @@ export const isManifestValid = (
return false;
};

export const useFallbackManifestEntry = (
prefetchedManifest: unknown,
chain: number,
): ManifestEntry => {
return useMemo(() => {
const isValid = isManifestValid(prefetchedManifest, chain);
return isValid
? prefetchedManifest[chain]
: (FallbackLocalManifest as unknown as Manifest)[chain];
}, [prefetchedManifest, chain]);
// Use in Next backend side
export const shouldRedirectToRoot = (
currentPath: string,
manifest: Manifest | null,
): boolean => {
const { defaultChain } = config;
const chainSettings = manifest?.[`${defaultChain}`];
const pages = chainSettings?.config?.pages;
const isDeactivate =
!!pages?.[currentPath as ManifestConfigPage]?.shouldDisable;
// https://nextjs.org/docs/messages/gsp-redirect-during-prerender
const isBuild = process.env.npm_lifecycle_event === 'build';

return (
currentPath !== ManifestConfigPageEnum.Stake && isDeactivate && !isBuild
);
};
1 change: 1 addition & 0 deletions consts/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export enum LIDO_MULTICHAIN_CHAINS {
'Mode Chain' = 34443,
'Zircuit Chain' = 48900,
Unichain = 130,
Metis = 1088,
}

// TODO: move to @lidofinance/lido-ethereum-sdk package
Expand Down
2 changes: 1 addition & 1 deletion pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import { HomePageIpfs } from 'features/ipfs';

import { getDefaultStaticProps } from 'utilsApi/get-default-static-props';

export const getStaticProps = getDefaultStaticProps();
export const getStaticProps = getDefaultStaticProps('/');

export default config.ipfsMode ? HomePageIpfs : StakePage;
2 changes: 1 addition & 1 deletion pages/referral.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ const Referral: FC = () => {
</Layout>
);
};
export const getStaticProps = getDefaultStaticProps();
export const getStaticProps = getDefaultStaticProps('/referral');

export default Referral;
2 changes: 1 addition & 1 deletion pages/rewards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ const Rewards: FC = () => {
);
};

export const getStaticProps = getDefaultStaticProps();
export const getStaticProps = getDefaultStaticProps('/rewards');

export default Rewards;
2 changes: 1 addition & 1 deletion pages/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const Settings: FC = () => {
);
};

export const getStaticProps = getDefaultStaticProps(async () => {
export const getStaticProps = getDefaultStaticProps('/settings', async () => {
if (!config.ipfsMode) return { notFound: true };

return { props: {} };
Expand Down
2 changes: 1 addition & 1 deletion pages/withdrawals/[mode].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const getStaticPaths: GetStaticPaths<
export const getStaticProps = getDefaultStaticProps<
WithdrawalsModePageParams,
WithdrawalsModePageParams
>(async ({ params }) => {
>('/withdrawals', async ({ params }) => {
if (!params?.mode) return { notFound: true };
return { props: { mode: params.mode } };
});
2 changes: 1 addition & 1 deletion pages/wrap/[[...mode]].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const getStaticPaths: GetStaticPaths<WrapModePageParams> = async () => {
export const getStaticProps = getDefaultStaticProps<
WrapModePageProps,
WrapModePageParams
>(async ({ params }) => {
>('/wrap', async ({ params }) => {
const mode = params?.mode;
if (!mode) return { props: { mode: 'wrap' } };
if (mode[0] === 'unwrap') return { props: { mode: 'unwrap' } };
Expand Down
47 changes: 47 additions & 0 deletions providers/external-forbidden-route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useState, useCallback, useMemo, ReactNode } from 'react';
import { useRouter } from 'next/router';

import { useRouterPath } from 'shared/hooks/use-router-path';
import { useConfig } from 'config';
import {
ManifestConfigPage,
ManifestConfigPageEnum,
} from 'config/external-config';
import { HOME_PATH } from 'consts/urls';

import { LayoutEffectSsrDelayed } from 'shared/components/layout-effect-ssr-delayed';

export const ExternalForbiddenRouteProvider = ({
children,
}: {
children: ReactNode;
}) => {
const [showContent, setShowContent] = useState(true);
const router = useRouter();
const path = useRouterPath();
const { pages } = useConfig().externalConfig;

const checkPathEffect = useCallback(() => {
if (pages) {
const paths = Object.keys(pages) as ManifestConfigPage[];
const forbiddenPath = paths.find((pathKey) => path.includes(pathKey));
if (
forbiddenPath &&
forbiddenPath !== ManifestConfigPageEnum.Stake &&
pages[forbiddenPath]?.shouldDisable
) {
setShowContent(false);
void router.push(HOME_PATH).finally(() => setShowContent(true));
}
}
}, [pages, path, router]);

const effectDeps = useMemo(() => [pages, path], [pages, path]);

return (
<>
<LayoutEffectSsrDelayed effect={checkPathEffect} deps={effectDeps} />
{showContent && children}
</>
);
};
Loading

0 comments on commit a5156b1

Please sign in to comment.