Skip to content
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

Support for white-labeling Explorer #1244

Merged
merged 1 commit into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changelog/1244.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for white-labeling Explorer
13 changes: 13 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,16 @@ REACT_APP_BUILD_VERSION=
# REACT_APP_TESTNET_API=https://testnet.nexus.stg.oasis.io/v1/
REACT_APP_API=https://nexus.oasis.io/v1/
REACT_APP_TESTNET_API=https://testnet.nexus.oasis.io/v1/
REACT_APP_TITLE=Oasis Explorer
REACT_APP_DESC=Official explorer for the Oasis Network.
REACT_APP_SOCIAL_TELEGRAM=https://t.me/oasisprotocolcommunity
REACT_APP_SOCIAL_TWITTER=https://twitter.com/oasisprotocol
REACT_APP_SOCIAL_DISCORD=https://oasis.io/discord
REACT_APP_SOCIAL_YOUTUBE=https://www.youtube.com/channel/UC35UFPcZ2F1wjPxhPrSsESQ
REACT_APP_SOCIAL_REDDIT=https://www.reddit.com/r/oasisnetwork/
# REACT_APP_SOCIAL_LINKEDIN=https://www.linkedin.com/company/oasisprotocol
# REACT_APP_SOCIAL_DOCS=https://oasisprotocol.org/developers#overview
# REACT_APP_SOCIAL_HOME=https://oasisprotocol.org/
csillag marked this conversation as resolved.
Show resolved Hide resolved
REACT_APP_PROD_URL=https://explorer.oasis.io, https://explorer.prd.oasis.io
REACT_APP_STAGING_URL=https://explorer.stg.oasis.io
REACT_APP_SHOW_BUILD_BANNERS=true
13 changes: 13 additions & 0 deletions .env.production
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
REACT_APP_BUILD_DATETIME=
REACT_APP_BUILD_SHA=
REACT_APP_BUILD_VERSION=
REACT_APP_TITLE=Oasis Explorer
REACT_APP_DESC=Official explorer for the Oasis Network.
REACT_APP_SOCIAL_TELEGRAM=https://t.me/oasisprotocolcommunity
REACT_APP_SOCIAL_TWITTER=https://twitter.com/oasisprotocol
REACT_APP_SOCIAL_DISCORD=https://oasis.io/discord
REACT_APP_SOCIAL_YOUTUBE=https://www.youtube.com/channel/UC35UFPcZ2F1wjPxhPrSsESQ
REACT_APP_SOCIAL_REDDIT=https://www.reddit.com/r/oasisnetwork/
# REACT_APP_SOCIAL_LINKEDIN=https://www.linkedin.com/company/oasisprotocol
# REACT_APP_SOCIAL_DOCS=https://oasisprotocol.org/developers#overview
# REACT_APP_SOCIAL_HOME=https://oasisprotocol.org/
REACT_APP_PROD_URL=https://explorer.oasis.io, https://explorer.prd.oasis.io
REACT_APP_STAGING_URL=https://explorer.stg.oasis.io
REACT_APP_SHOW_BUILD_BANNERS=true
9 changes: 9 additions & 0 deletions .parcelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "@parcel/config-default",
"transformers": {
"*.html": [
"@plasmohq/parcel-transformer-inject-env",
"..."
]
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
"@emotion/jest": "11.11.0",
"@parcel/packager-raw-url": "2.11.0",
"@parcel/transformer-webmanifest": "2.11.0",
"@plasmohq/parcel-transformer-inject-env": "^0.2.11",
"@storybook/addon-actions": "7.6.13",
"@storybook/addon-essentials": "7.6.13",
"@storybook/addon-interactions": "7.6.13",
Expand Down
8 changes: 4 additions & 4 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
<meta name="theme-color" content="#000000" />
<link rel="apple-touch-icon" href="./logo192.png" />
<link rel="manifest" href="./app.webmanifest" />
<meta property="og:title" content="Oasis Explorer" />
<meta property="og:description" content="Official explorer for the Oasis Network." />
<meta property="og:title" content="$REACT_APP_TITLE" />
<meta property="og:description" content="$REACT_APP_DESC" />
<meta property="og:image" content="./oasis-og-image.jpg" />
<meta property="og:type" content="website" />
<title>Oasis Explorer</title>
<meta name="description" content="Official explorer for the Oasis Network." />
<title>$REACT_APP_TITLE</title>
<meta name="description" content="$REACT_APP_DESC" />
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
Expand Down
10 changes: 6 additions & 4 deletions src/app/components/BuildBanner/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { deploys } from '../../../config'
import { deploys, getAppTitle } from '../../../config'
import { StickyAlert } from '../StickyAlert'

const useBuildBanners = process.env.REACT_APP_SHOW_BUILD_BANNERS === 'true'

export const BuildBanner: FC = () => {
const { t } = useTranslation()

if (window.location.origin === deploys.localhost) {
if (!useBuildBanners || window.location.origin === deploys.localhost) {
return null
}
if (deploys.production.includes(window.location.origin)) {
return null
}
if (window.location.origin === deploys.staging) {
if (deploys.staging.includes(window.location.origin)) {
return <StickyAlert severity="warning">{t('banner.buildStaging')}</StickyAlert>
}
return <StickyAlert severity="warning">{t('banner.buildPreview')}</StickyAlert>
return <StickyAlert severity="warning">{t('banner.buildPreview', { appTitle: getAppTitle() })}</StickyAlert>
}
5 changes: 2 additions & 3 deletions src/app/components/PageLayout/Logotype.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Box from '@mui/material/Box'
import { Link as RouterLink } from 'react-router-dom'
import { OasisIcon } from '../CustomIcons/OasisIcon'
import Typography from '@mui/material/Typography'
import { useTranslation } from 'react-i18next'
import { getAppTitle } from '../../../config'

interface LogotypeProps {
color?: string
Expand All @@ -22,7 +22,6 @@ export const HomePageLink: FC<LogotypeProps> = ({ color, showText }) => {
}

export const Logotype: FC<LogotypeProps> = ({ color, showText }) => {
const { t } = useTranslation()
const theme = useTheme()
const { isMobile } = useScreenSize()
const logoSize = isMobile ? 32 : 40
Expand All @@ -40,7 +39,7 @@ export const Logotype: FC<LogotypeProps> = ({ color, showText }) => {
<OasisIcon sx={{ fontSize: logoSize }} />
{showText && (
<Typography variant="h1" color={color || theme.palette.layout.main} sx={{ whiteSpace: 'nowrap' }}>
{t('pageTitle')}
{getAppTitle()}
</Typography>
)}
</Box>
Expand Down
3 changes: 2 additions & 1 deletion src/app/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { isValidBlockHeight } from '../../utils/helpers'
import { typingDelay } from '../../../styles/theme'
import { isValidMnemonic } from '../../utils/helpers'
import Collapse from '@mui/material/Collapse'
import { getAppTitle } from '../../../config'

export type SearchVariant = 'button' | 'icon' | 'expandable'

Expand Down Expand Up @@ -163,7 +164,7 @@ const SearchCmp: FC<SearchProps> = ({ scope, variant, disabled, onFocusChange: o
const hasError = !!errorMessage

const warningMessage = hasPrivacyProblem
? t('search.error.privacy', { appName: t('appName'), wordsOfPower })
? t('search.error.privacy', { appName: getAppTitle(), wordsOfPower })
: undefined
const hasWarning = !!warningMessage

Expand Down
106 changes: 77 additions & 29 deletions src/app/components/Social/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC } from 'react'
import React, { FC, ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import Box from '@mui/material/Box'
import Grid from '@mui/material/Unstable_Grid2'
Expand All @@ -11,17 +11,21 @@ import twitter from './images/twitter.svg'
import discord from './images/discord.svg'
import youtube from './images/youtube.svg'
import reddit from './images/reddit.svg'
import LinkedInIcon from '@mui/icons-material/LinkedIn'
import DocsIcon from '@mui/icons-material/MenuBook'
import HomeIcon from '@mui/icons-material/Cottage'
import { COLORS } from '../../../styles/theme/colors'
import { socialMedia } from '../../utils/externalLinks'

type SocialLinkProps = {
label: string
href: string
isMobile: boolean
img: string
imgSrc?: string
img?: ReactNode
}

const SocialLink: FC<SocialLinkProps> = ({ label, href, isMobile, img }) => {
const SocialLink: FC<SocialLinkProps> = ({ label, href, isMobile, imgSrc, img }) => {
return (
<Link
href={href}
Expand All @@ -38,13 +42,18 @@ const SocialLink: FC<SocialLinkProps> = ({ label, href, isMobile, img }) => {
target="_blank"
>
<Box sx={{ display: 'flex', justifyContent: 'center', mb: 3 }}>
<img src={img} alt={label} height={40} />
<>
{imgSrc && <img src={imgSrc} alt={label} height={40} />}
{img}
</>
</Box>
<Typography sx={{ fontSize: 18, fontWeight: 700, mb: isMobile ? 4 : 0 }}>{label}</Typography>
</Link>
)
}

const iconProps = { sx: { fontSize: 50, margin: '-4px' } }

export const Social: FC = () => {
const { t } = useTranslation()
const { isMobile } = useScreenSize()
Expand Down Expand Up @@ -84,31 +93,70 @@ export const Social: FC = () => {
height: '100%',
}}
>
<SocialLink
isMobile={isMobile}
label={t('social.telegram')}
href={socialMedia.telegram}
img={telegram}
/>
<SocialLink
isMobile={isMobile}
label={t('social.twitter')}
href={socialMedia.twitter}
img={twitter}
/>
<SocialLink
isMobile={isMobile}
label={t('social.discord')}
href={socialMedia.discord}
img={discord}
/>
<SocialLink
isMobile={isMobile}
label={t('social.youtube')}
href={socialMedia.youtube}
img={youtube}
/>
<SocialLink isMobile={isMobile} label={t('social.reddit')} href={socialMedia.reddit} img={reddit} />
{socialMedia.telegram && (
<SocialLink
isMobile={isMobile}
label={t('social.telegram')}
href={socialMedia.telegram}
imgSrc={telegram}
/>
)}
{socialMedia.twitter && (
<SocialLink
isMobile={isMobile}
label={t('social.twitter')}
href={socialMedia.twitter}
imgSrc={twitter}
/>
)}
{socialMedia.discord && (
<SocialLink
isMobile={isMobile}
label={t('social.discord')}
href={socialMedia.discord}
imgSrc={discord}
/>
)}
{socialMedia.youtube && (
<SocialLink
isMobile={isMobile}
label={t('social.youtube')}
href={socialMedia.youtube}
imgSrc={youtube}
/>
)}
{socialMedia.reddit && (
<SocialLink
isMobile={isMobile}
label={t('social.reddit')}
href={socialMedia.reddit}
imgSrc={reddit}
/>
)}
{socialMedia.linkedin && (
<SocialLink
isMobile={isMobile}
label={t('social.linkedin')}
href={socialMedia.linkedin}
img={<LinkedInIcon {...iconProps} />}
/>
)}
{socialMedia.docs && (
<SocialLink
isMobile={isMobile}
label={t('social.docs')}
href={socialMedia.docs}
img={<DocsIcon {...iconProps} />}
/>
)}
{socialMedia.home && (
<SocialLink
isMobile={isMobile}
label={t('social.home')}
href={socialMedia.home}
img={<HomeIcon {...iconProps} />}
/>
)}
</Box>
</Grid>
</Grid>
Expand Down
7 changes: 5 additions & 2 deletions src/app/utils/__tests__/externalLinks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,20 @@ onlyRunOnCI('externalLinks', () => {
expect(Object.entries(linksGroup).length).toBeGreaterThan(0)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const [_linkName, url] of Object.entries(linksGroup)) {
if (url === undefined) continue
expect(typeof url).toBe('string')
expect(url).toMatch(/^(https|mailto):/)
}
}
})

describe('should be reachable', () => {
const { reddit, twitter } = externalLinksModule.socialMedia
for (const [linksGroupName, linksGroup] of Object.entries(externalLinksModule)) {
for (const [linkName, url] of Object.entries(linksGroup)) {
if (url.startsWith(externalLinksModule.socialMedia.reddit)) continue // Reddit often returns 504
if (url.startsWith(externalLinksModule.socialMedia.twitter)) continue // redirect loop
if (!url || typeof url !== 'string') continue
if (!!reddit && url.startsWith(reddit)) continue // Reddit often returns 504
if (!!twitter && url.startsWith(twitter)) continue // redirect loop
if (url.startsWith(externalLinksModule.referrals.coinGecko)) continue // CoinGecko has CloudFlare DDOS protection
if (url.startsWith(externalLinksModule.github.commit)) continue // We store only partial url in constants
if (url.startsWith(externalLinksModule.github.releaseTag)) continue // We store only partial url in constants
Expand Down
13 changes: 8 additions & 5 deletions src/app/utils/externalLinks.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { Layer } from '../../oasis-nexus/api'

export const socialMedia = {
telegram: 'https://t.me/oasisprotocolcommunity',
twitter: 'https://twitter.com/oasisprotocol',
discord: 'https://oasis.io/discord',
telegram: process.env.REACT_APP_SOCIAL_TELEGRAM,
twitter: process.env.REACT_APP_SOCIAL_TWITTER,
discord: process.env.REACT_APP_SOCIAL_DISCORD,
// This API link is for testing if invite is still valid.
isDiscordStillValid: 'https://oasis.io/discord/invite-api-check',
youtube: 'https://www.youtube.com/channel/UC35UFPcZ2F1wjPxhPrSsESQ',
reddit: 'https://www.reddit.com/r/oasisnetwork/',
youtube: process.env.REACT_APP_SOCIAL_YOUTUBE,
reddit: process.env.REACT_APP_SOCIAL_REDDIT,
linkedin: process.env.REACT_APP_SOCIAL_LINKEDIN,
docs: process.env.REACT_APP_SOCIAL_DOCS,
home: process.env.REACT_APP_SOCIAL_HOME,
}

const docsUrl = 'https://docs.oasis.io/'
Expand Down
14 changes: 12 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,21 @@ export const paraTimesConfig = {
[Layer.consensus]: null,
} satisfies LayersConfig

const parseUrl = (input: string | undefined): string[] =>
input
? input
.split(',')
.filter(s => !!s)
.map(s => s.trim())
: []

export const deploys = {
production: ['https://explorer.oasis.io', 'https://explorer.prd.oasis.io'],
staging: 'https://explorer.stg.oasis.io',
production: parseUrl(process.env.REACT_APP_PROD_URL),
staging: parseUrl(process.env.REACT_APP_STAGING_URL),
csillag marked this conversation as resolved.
Show resolved Hide resolved
localhost: 'http://localhost:1234',
}

const stableDeploys = [...deploys.production, deploys.staging]
export const isStableDeploy = stableDeploys.some(url => window.location.origin === url)

export const getAppTitle = () => process.env.REACT_APP_TITLE
25 changes: 25 additions & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
declare global {
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test'
REACT_APP_BUILD_DATETIME: string
REACT_APP_BUILD_SHA: string
REACT_APP_BUILD_VERSION: string
REACT_APP_API: string
REACT_APP_TESTNET_API: string
REACT_APP_SHOW_BUILD_BANNERS?: 'true' | 'false'
REACT_APP_TITLE: string
REACT_APP_DESC: string
REACT_APP_SOCIAL_TELEGRAM?: string
REACT_APP_SOCIAL_TWITTER?: string
REACT_APP_SOCIAL_DISCORD?: string
REACT_APP_SOCIAL_YOUTUBE?: string
REACT_APP_SOCIAL_REDDIT?: string
READ_APP_SOCIAL_LINKEDIN?: string
READ_APP_SOCIAL_DOCS?: string
READ_APP_SOCIAL_HOME?: string
csillag marked this conversation as resolved.
Show resolved Hide resolved
REACT_APP_PROD_URL: string
REACT_APP_STAGING_URL?: string
}
}
}
Loading
Loading