From fd0428bee97a5fa1fdbc806d74f59eb4299198ed Mon Sep 17 00:00:00 2001 From: Max Alekseenko Date: Wed, 21 Feb 2024 17:20:21 +0100 Subject: [PATCH 1/6] add top bar to dapp page --- types/client/marketplace.ts | 12 +- ui/marketplace/MarketplaceAppInfo.tsx | 50 +++++++++ ui/marketplace/MarketplaceAppInfo/Content.tsx | 53 +++++++++ .../MarketplaceAppInfo/SocialLink.tsx | 32 ++++++ .../MarketplaceAppInfo/TriggerButton.tsx | 29 +++++ .../MarketplaceAppInfo/WebsiteLink.tsx | 29 +++++ ui/marketplace/MarketplaceAppTopBar.tsx | 106 ++++++++++++++++++ ui/pages/MarketplaceApp.tsx | 24 ++-- ui/shared/layout/LayoutApp.tsx | 2 +- 9 files changed, 322 insertions(+), 15 deletions(-) create mode 100644 ui/marketplace/MarketplaceAppInfo.tsx create mode 100644 ui/marketplace/MarketplaceAppInfo/Content.tsx create mode 100644 ui/marketplace/MarketplaceAppInfo/SocialLink.tsx create mode 100644 ui/marketplace/MarketplaceAppInfo/TriggerButton.tsx create mode 100644 ui/marketplace/MarketplaceAppInfo/WebsiteLink.tsx create mode 100644 ui/marketplace/MarketplaceAppTopBar.tsx diff --git a/types/client/marketplace.ts b/types/client/marketplace.ts index eb132aac20..58ede5b258 100644 --- a/types/client/marketplace.ts +++ b/types/client/marketplace.ts @@ -11,13 +11,17 @@ export type MarketplaceAppPreview = { priority?: number; } -export type MarketplaceAppOverview = MarketplaceAppPreview & { +export type MarketplaceAppSocialInfo = { + twitter?: string; + telegram?: string; + github?: string | Array; + discord?: string; +} + +export type MarketplaceAppOverview = MarketplaceAppPreview & MarketplaceAppSocialInfo & { author: string; description: string; site?: string; - twitter?: string; - telegram?: string; - github?: string; } export enum MarketplaceCategory { diff --git a/ui/marketplace/MarketplaceAppInfo.tsx b/ui/marketplace/MarketplaceAppInfo.tsx new file mode 100644 index 0000000000..54dcee8212 --- /dev/null +++ b/ui/marketplace/MarketplaceAppInfo.tsx @@ -0,0 +1,50 @@ +import { + Popover, PopoverTrigger, PopoverContent, PopoverBody, + Modal, ModalContent, ModalCloseButton, useDisclosure, +} from '@chakra-ui/react'; +import React from 'react'; + +import type { MarketplaceAppOverview } from 'types/client/marketplace'; + +import useIsMobile from 'lib/hooks/useIsMobile'; + +import Content from './MarketplaceAppInfo/Content'; +import TriggerButton from './MarketplaceAppInfo/TriggerButton'; + +interface Props { + data: MarketplaceAppOverview; +} + +const MarketplaceAppInfo = ({ data }: Props) => { + const isMobile = useIsMobile(); + const { isOpen, onToggle, onClose } = useDisclosure(); + + if (isMobile) { + return ( + <> + + + + + + + + + ); + } + + return ( + + + + + + + + + + + ); +}; + +export default React.memo(MarketplaceAppInfo); diff --git a/ui/marketplace/MarketplaceAppInfo/Content.tsx b/ui/marketplace/MarketplaceAppInfo/Content.tsx new file mode 100644 index 0000000000..4c9e06094a --- /dev/null +++ b/ui/marketplace/MarketplaceAppInfo/Content.tsx @@ -0,0 +1,53 @@ +import { Flex, Text, Grid } from '@chakra-ui/react'; +import React from 'react'; + +import type { MarketplaceAppOverview } from 'types/client/marketplace'; + +import SocialLink from './SocialLink'; +import type { Props as SocialLinkProps } from './SocialLink'; +import WebsiteLink from './WebsiteLink'; + +interface Props { + data: MarketplaceAppOverview; +} + +const SOCIAL_LINKS: Array> = [ + { field: 'github', icon: 'social/github_filled', title: 'Github' }, + { field: 'twitter', icon: 'social/twitter_filled', title: 'Twitter' }, + { field: 'telegram', icon: 'social/telegram_filled', title: 'Telegram' }, + { field: 'discord', icon: 'social/discord_filled', title: 'Discord' }, +]; + +const Content = ({ data }: Props) => { + const socialLinks: Array = []; + SOCIAL_LINKS.forEach((link) => { + const href = data[link.field]; + if (href) { + if (Array.isArray(href)) { + href.forEach((href) => socialLinks.push({ ...link, href })); + } else { + socialLinks.push({ ...link, href }); + } + } + }); + + return ( + +
+ Project info + { data.shortDescription } + +
+ { socialLinks.length > 0 && ( +
+ Links + + { socialLinks.map((link, index) => ) } + +
+ ) } +
+ ); +}; + +export default Content; diff --git a/ui/marketplace/MarketplaceAppInfo/SocialLink.tsx b/ui/marketplace/MarketplaceAppInfo/SocialLink.tsx new file mode 100644 index 0000000000..da3464f6d6 --- /dev/null +++ b/ui/marketplace/MarketplaceAppInfo/SocialLink.tsx @@ -0,0 +1,32 @@ +import { Link } from '@chakra-ui/react'; +import React from 'react'; + +import type { MarketplaceAppSocialInfo } from 'types/client/marketplace'; + +import type { IconName } from 'ui/shared/IconSvg'; +import IconSvg from 'ui/shared/IconSvg'; + +export interface Props { + field: keyof MarketplaceAppSocialInfo; + icon: IconName; + title: string; + href?: string; +} + +const SocialLink = ({ href, icon, title }: Props) => { + return ( + + + { title } + + ); +}; + +export default SocialLink; diff --git a/ui/marketplace/MarketplaceAppInfo/TriggerButton.tsx b/ui/marketplace/MarketplaceAppInfo/TriggerButton.tsx new file mode 100644 index 0000000000..cef2f9c31e --- /dev/null +++ b/ui/marketplace/MarketplaceAppInfo/TriggerButton.tsx @@ -0,0 +1,29 @@ +import { Button } from '@chakra-ui/react'; +import React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +interface Props { + onClick: () => void; +} + +const TriggerButton = ({ onClick }: Props, ref: React.ForwardedRef) => { + return ( + + ); +}; + +export default React.forwardRef(TriggerButton); diff --git a/ui/marketplace/MarketplaceAppInfo/WebsiteLink.tsx b/ui/marketplace/MarketplaceAppInfo/WebsiteLink.tsx new file mode 100644 index 0000000000..fc6ba179c5 --- /dev/null +++ b/ui/marketplace/MarketplaceAppInfo/WebsiteLink.tsx @@ -0,0 +1,29 @@ +import { Link } from '@chakra-ui/react'; +import React from 'react'; + +import IconSvg from 'ui/shared/IconSvg'; + +interface Props { + url?: string | undefined; +} + +const WebsiteLink = ({ url }: Props) => { + if (!url) { + return null; + } + return ( + + + { (new URL(url)).hostname } + + ); +}; + +export default WebsiteLink; diff --git a/ui/marketplace/MarketplaceAppTopBar.tsx b/ui/marketplace/MarketplaceAppTopBar.tsx new file mode 100644 index 0000000000..6b83b63367 --- /dev/null +++ b/ui/marketplace/MarketplaceAppTopBar.tsx @@ -0,0 +1,106 @@ +import { chakra, Flex, Tooltip, Skeleton, Box } from '@chakra-ui/react'; +import React from 'react'; + +import type { MarketplaceAppOverview } from 'types/client/marketplace'; + +import { route } from 'nextjs-routes'; + +import { useAppContext } from 'lib/contexts/app'; +import type { IconName } from 'ui/shared/IconSvg'; +import IconSvg from 'ui/shared/IconSvg'; +import LinkExternal from 'ui/shared/LinkExternal'; +import LinkInternal from 'ui/shared/LinkInternal'; + +import MarketplaceAppInfo from './MarketplaceAppInfo'; + +type Props = { + isWalletConnected: boolean; + data: MarketplaceAppOverview | undefined; + isPending: boolean; +} + +const MarketplaceAppTopBar = ({ data, isPending, isWalletConnected }: Props) => { + const appProps = useAppContext(); + + const goBackUrl = React.useMemo(() => { + if (appProps.referrer && appProps.referrer.includes('/apps') && !appProps.referrer.includes('/apps/')) { + return appProps.referrer; + } + return route({ pathname: '/apps' }); + }, [ appProps.referrer ]); + + const message = React.useMemo(() => { + let icon: IconName = 'wallet'; + let iconColor = 'blackAlpha.800'; + let bgColor = 'orange.100'; + let text = 'Connect your wallet to Blockscout for full-featured access'; + + if (isWalletConnected && data?.internalWallet) { + icon = 'integration/full'; + iconColor = 'green.600'; + bgColor = 'green.100'; + text = 'Your wallet is connected with Blockscout'; + } else if (isWalletConnected) { + icon = 'integration/partial'; + text = 'Connect your wallet in the app below'; + } + + return { icon, iconColor, bgColor, text }; + }, [ isWalletConnected, data?.internalWallet ]); + + if (isPending) { + return ( + + + + + + + ); + } + + if (!data) { + return null; + } + + return ( + + + + + + + + + { message.text } + + + + + + { (new URL(data.url)).hostname } + + + ); +}; + +export default MarketplaceAppTopBar; diff --git a/ui/pages/MarketplaceApp.tsx b/ui/pages/MarketplaceApp.tsx index 98e65dcf57..ea73ead610 100644 --- a/ui/pages/MarketplaceApp.tsx +++ b/ui/pages/MarketplaceApp.tsx @@ -17,6 +17,7 @@ import * as metadata from 'lib/metadata'; import getQueryParamString from 'lib/router/getQueryParamString'; import ContentLoader from 'ui/shared/ContentLoader'; +import MarketplaceAppTopBar from '../marketplace/MarketplaceAppTopBar'; import useAutoConnectWallet from '../marketplace/useAutoConnectWallet'; import useMarketplaceWallet from '../marketplace/useMarketplaceWallet'; @@ -139,16 +140,19 @@ const MarketplaceApp = () => { throwOnResourceLoadError(query); return ( - - - + <> + + + + + ); }; diff --git a/ui/shared/layout/LayoutApp.tsx b/ui/shared/layout/LayoutApp.tsx index 7f02e34df8..8eaebf342a 100644 --- a/ui/shared/layout/LayoutApp.tsx +++ b/ui/shared/layout/LayoutApp.tsx @@ -20,7 +20,7 @@ const LayoutDefault = ({ children }: Props) => { > - + { children } From 4a3249395d9cda266c08973b6566cc7cb5d2eab1 Mon Sep 17 00:00:00 2001 From: Max Alekseenko Date: Wed, 21 Feb 2024 17:37:28 +0100 Subject: [PATCH 2/6] fix github links in MarketplaceAppModal --- ui/marketplace/MarketplaceAppModal.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ui/marketplace/MarketplaceAppModal.tsx b/ui/marketplace/MarketplaceAppModal.tsx index d997621f8a..297bcae1c3 100644 --- a/ui/marketplace/MarketplaceAppModal.tsx +++ b/ui/marketplace/MarketplaceAppModal.tsx @@ -50,12 +50,16 @@ const MarketplaceAppModal = ({ icon: 'social/tweet' as IconName, url: twitter, } : null, - github ? { - icon: 'social/git' as IconName, - url: github, - } : null, ].filter(Boolean); + if (github) { + if (Array.isArray(github)) { + github.forEach((url) => socialLinks.push({ icon: 'social/git', url })); + } else { + socialLinks.push({ icon: 'social/git', url: github }); + } + } + const handleFavoriteClick = useCallback(() => { onFavoriteClick(data.id, isFavorite); }, [ onFavoriteClick, data.id, isFavorite ]); From d8363acceb245baac1439a26539ead1f0db95351 Mon Sep 17 00:00:00 2001 From: Max Alekseenko Date: Wed, 21 Feb 2024 17:56:05 +0100 Subject: [PATCH 3/6] fix envs-validator --- deploy/tools/envs-validator/schema.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index fd2a2c656c..33fb8ab4f6 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -75,7 +75,12 @@ const marketplaceAppSchema: yup.ObjectSchema = yup site: yup.string().test(urlTest), twitter: yup.string().test(urlTest), telegram: yup.string().test(urlTest), - github: yup.string().test(urlTest), + github: yup.lazy(value => + Array.isArray(value) ? + yup.array().of(yup.string().required().test(urlTest)) : + yup.string().test(urlTest), + ), + discord: yup.string().test(urlTest), internalWallet: yup.boolean(), priority: yup.number(), }); From 99a839fc480355d20931e9d1a3acc37e304f78fc Mon Sep 17 00:00:00 2001 From: Max Alekseenko Date: Mon, 26 Feb 2024 18:42:27 +0100 Subject: [PATCH 4/6] post-review changes --- ui/marketplace/MarketplaceAppAlert.tsx | 56 ++++++++++++++ ui/marketplace/MarketplaceAppInfo.tsx | 2 +- ui/marketplace/MarketplaceAppInfo/Content.tsx | 8 +- ui/marketplace/MarketplaceAppTopBar.tsx | 73 ++++--------------- ui/pages/MarketplaceApp.tsx | 2 +- 5 files changed, 77 insertions(+), 64 deletions(-) create mode 100644 ui/marketplace/MarketplaceAppAlert.tsx diff --git a/ui/marketplace/MarketplaceAppAlert.tsx b/ui/marketplace/MarketplaceAppAlert.tsx new file mode 100644 index 0000000000..fd0358b025 --- /dev/null +++ b/ui/marketplace/MarketplaceAppAlert.tsx @@ -0,0 +1,56 @@ +import { Alert } from '@chakra-ui/react'; +import React from 'react'; + +import type { IconName } from 'ui/shared/IconSvg'; +import IconSvg from 'ui/shared/IconSvg'; + +import useMarketplaceWallet from './useMarketplaceWallet'; + +type Props = { + internalWallet: boolean | undefined; +} + +const MarketplaceAppTopBar = ({ internalWallet }: Props) => { + const { address } = useMarketplaceWallet(); + const isWalletConnected = Boolean(address); + + const message = React.useMemo(() => { + let icon: IconName = 'wallet'; + let text = 'Connect your wallet to Blockscout for full-featured access'; + let status: 'warning' | 'success' = 'warning'; + + if (isWalletConnected && internalWallet) { + icon = 'integration/full'; + text = 'Your wallet is connected with Blockscout'; + status = 'success'; + } else if (isWalletConnected) { + icon = 'integration/partial'; + text = 'Connect your wallet in the app below'; + } + + return { icon, text, status }; + }, [ isWalletConnected, internalWallet ]); + + return ( + + + { message.text } + + ); +}; + +export default MarketplaceAppTopBar; diff --git a/ui/marketplace/MarketplaceAppInfo.tsx b/ui/marketplace/MarketplaceAppInfo.tsx index 54dcee8212..5b25aea641 100644 --- a/ui/marketplace/MarketplaceAppInfo.tsx +++ b/ui/marketplace/MarketplaceAppInfo.tsx @@ -12,7 +12,7 @@ import Content from './MarketplaceAppInfo/Content'; import TriggerButton from './MarketplaceAppInfo/TriggerButton'; interface Props { - data: MarketplaceAppOverview; + data: MarketplaceAppOverview | undefined; } const MarketplaceAppInfo = ({ data }: Props) => { diff --git a/ui/marketplace/MarketplaceAppInfo/Content.tsx b/ui/marketplace/MarketplaceAppInfo/Content.tsx index 4c9e06094a..04b55b84b4 100644 --- a/ui/marketplace/MarketplaceAppInfo/Content.tsx +++ b/ui/marketplace/MarketplaceAppInfo/Content.tsx @@ -8,7 +8,7 @@ import type { Props as SocialLinkProps } from './SocialLink'; import WebsiteLink from './WebsiteLink'; interface Props { - data: MarketplaceAppOverview; + data: MarketplaceAppOverview | undefined; } const SOCIAL_LINKS: Array> = [ @@ -21,7 +21,7 @@ const SOCIAL_LINKS: Array> = [ const Content = ({ data }: Props) => { const socialLinks: Array = []; SOCIAL_LINKS.forEach((link) => { - const href = data[link.field]; + const href = data?.[link.field]; if (href) { if (Array.isArray(href)) { href.forEach((href) => socialLinks.push({ ...link, href })); @@ -35,8 +35,8 @@ const Content = ({ data }: Props) => {
Project info - { data.shortDescription } - + { data?.shortDescription } +
{ socialLinks.length > 0 && (
diff --git a/ui/marketplace/MarketplaceAppTopBar.tsx b/ui/marketplace/MarketplaceAppTopBar.tsx index 6b83b63367..e6ff35020f 100644 --- a/ui/marketplace/MarketplaceAppTopBar.tsx +++ b/ui/marketplace/MarketplaceAppTopBar.tsx @@ -1,4 +1,4 @@ -import { chakra, Flex, Tooltip, Skeleton, Box } from '@chakra-ui/react'; +import { chakra, Flex, Tooltip, Skeleton } from '@chakra-ui/react'; import React from 'react'; import type { MarketplaceAppOverview } from 'types/client/marketplace'; @@ -6,20 +6,19 @@ import type { MarketplaceAppOverview } from 'types/client/marketplace'; import { route } from 'nextjs-routes'; import { useAppContext } from 'lib/contexts/app'; -import type { IconName } from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg'; import LinkExternal from 'ui/shared/LinkExternal'; import LinkInternal from 'ui/shared/LinkInternal'; +import MarketplaceAppAlert from './MarketplaceAppAlert'; import MarketplaceAppInfo from './MarketplaceAppInfo'; type Props = { - isWalletConnected: boolean; data: MarketplaceAppOverview | undefined; - isPending: boolean; + isLoading: boolean; } -const MarketplaceAppTopBar = ({ data, isPending, isWalletConnected }: Props) => { +const MarketplaceAppTopBar = ({ data, isLoading }: Props) => { const appProps = useAppContext(); const goBackUrl = React.useMemo(() => { @@ -29,75 +28,33 @@ const MarketplaceAppTopBar = ({ data, isPending, isWalletConnected }: Props) => return route({ pathname: '/apps' }); }, [ appProps.referrer ]); - const message = React.useMemo(() => { - let icon: IconName = 'wallet'; - let iconColor = 'blackAlpha.800'; - let bgColor = 'orange.100'; - let text = 'Connect your wallet to Blockscout for full-featured access'; - - if (isWalletConnected && data?.internalWallet) { - icon = 'integration/full'; - iconColor = 'green.600'; - bgColor = 'green.100'; - text = 'Your wallet is connected with Blockscout'; - } else if (isWalletConnected) { - icon = 'integration/partial'; - text = 'Connect your wallet in the app below'; - } - - return { icon, iconColor, bgColor, text }; - }, [ isWalletConnected, data?.internalWallet ]); - - if (isPending) { - return ( - - - - - - - ); - } - - if (!data) { - return null; - } - return ( - + - - - { message.text } - - + + + + - + - { (new URL(data.url)).hostname } + + { data?.url ? (new URL(data.url)).hostname : '' } + ); diff --git a/ui/pages/MarketplaceApp.tsx b/ui/pages/MarketplaceApp.tsx index ea73ead610..f2ffb6c85c 100644 --- a/ui/pages/MarketplaceApp.tsx +++ b/ui/pages/MarketplaceApp.tsx @@ -141,7 +141,7 @@ const MarketplaceApp = () => { return ( <> - + Date: Tue, 27 Feb 2024 15:30:06 +0100 Subject: [PATCH 5/6] post-review changes and style fix --- ui/marketplace/MarketplaceAppAlert.tsx | 5 ++--- ui/marketplace/MarketplaceAppInfo/WebsiteLink.tsx | 9 ++++++++- ui/marketplace/MarketplaceAppTopBar.tsx | 10 ++++++++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/ui/marketplace/MarketplaceAppAlert.tsx b/ui/marketplace/MarketplaceAppAlert.tsx index fd0358b025..107a542e06 100644 --- a/ui/marketplace/MarketplaceAppAlert.tsx +++ b/ui/marketplace/MarketplaceAppAlert.tsx @@ -10,7 +10,7 @@ type Props = { internalWallet: boolean | undefined; } -const MarketplaceAppTopBar = ({ internalWallet }: Props) => { +const MarketplaceAppAlert = ({ internalWallet }: Props) => { const { address } = useMarketplaceWallet(); const isWalletConnected = Boolean(address); @@ -34,7 +34,6 @@ const MarketplaceAppTopBar = ({ internalWallet }: Props) => { return ( { ); }; -export default MarketplaceAppTopBar; +export default MarketplaceAppAlert; diff --git a/ui/marketplace/MarketplaceAppInfo/WebsiteLink.tsx b/ui/marketplace/MarketplaceAppInfo/WebsiteLink.tsx index fc6ba179c5..2a3dea9cca 100644 --- a/ui/marketplace/MarketplaceAppInfo/WebsiteLink.tsx +++ b/ui/marketplace/MarketplaceAppInfo/WebsiteLink.tsx @@ -11,6 +11,13 @@ const WebsiteLink = ({ url }: Props) => { if (!url) { return null; } + + function getHostname(url: string) { + try { + return new URL(url).hostname; + } catch (err) {} + } + return ( { mt={ 3 } > - { (new URL(url)).hostname } + { getHostname(url) } ); }; diff --git a/ui/marketplace/MarketplaceAppTopBar.tsx b/ui/marketplace/MarketplaceAppTopBar.tsx index e6ff35020f..9ffab1c895 100644 --- a/ui/marketplace/MarketplaceAppTopBar.tsx +++ b/ui/marketplace/MarketplaceAppTopBar.tsx @@ -28,6 +28,12 @@ const MarketplaceAppTopBar = ({ data, isLoading }: Props) => { return route({ pathname: '/apps' }); }, [ appProps.referrer ]); + function getHostname(url: string | undefined) { + try { + return new URL(url || '').hostname; + } catch (err) {} + } + return ( @@ -35,7 +41,7 @@ const MarketplaceAppTopBar = ({ data, isLoading }: Props) => { - + @@ -53,7 +59,7 @@ const MarketplaceAppTopBar = ({ data, isLoading }: Props) => { isLoading={ isLoading } > - { data?.url ? (new URL(data.url)).hostname : '' } + { getHostname(data?.url) } From 1ea0b6e9f5b115a76e1fb3112c72ae37b811b9a7 Mon Sep 17 00:00:00 2001 From: Max Alekseenko Date: Tue, 27 Feb 2024 16:01:48 +0100 Subject: [PATCH 6/6] remove margin --- ui/marketplace/MarketplaceAppTopBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/marketplace/MarketplaceAppTopBar.tsx b/ui/marketplace/MarketplaceAppTopBar.tsx index 9ffab1c895..cdf4c191f6 100644 --- a/ui/marketplace/MarketplaceAppTopBar.tsx +++ b/ui/marketplace/MarketplaceAppTopBar.tsx @@ -37,7 +37,7 @@ const MarketplaceAppTopBar = ({ data, isLoading }: Props) => { return ( - +