From 80dff04cc92563a635362d01b73655cd68dcdbca Mon Sep 17 00:00:00 2001 From: Juan Cazala Date: Wed, 13 Nov 2024 12:55:28 -0300 Subject: [PATCH] feat: navbar extra button from contentful (#565) * feat: load extra button from contentful * feat: cache navbar button data using TTL in contentful * chore: move contentful stuff to config * chore: linted --- src/components/Navbar/MainMenu/MainMenu.tsx | 30 +++++++++++- src/components/Navbar/MenuItem/MenuItem.css | 21 ++++++++ src/components/Navbar/MenuItem/MenuItem.tsx | 20 ++++++-- .../Navbar/MenuItem/MenuItem.types.ts | 2 + src/components/Navbar/Navbar.types.ts | 3 +- src/components/Navbar/utils.ts | 49 +++++++++++++++++++ src/config/env/dev.json | 6 ++- src/config/env/prod.json | 6 ++- src/config/env/stg.json | 6 ++- 9 files changed, 134 insertions(+), 9 deletions(-) diff --git a/src/components/Navbar/MainMenu/MainMenu.tsx b/src/components/Navbar/MainMenu/MainMenu.tsx index 25602cd2..4878e474 100644 --- a/src/components/Navbar/MainMenu/MainMenu.tsx +++ b/src/components/Navbar/MainMenu/MainMenu.tsx @@ -1,10 +1,10 @@ -import React from 'react' +import React, { useEffect, useRef, useState } from 'react' import classNames from 'classnames' import { MenuItem } from '../MenuItem/MenuItem' import { NavbarPages } from '../Navbar.types' import { MainMenuProps } from './MainMenu.types' -import { getNavbarPagesUrls } from '../utils' +import { getExtraButton, getNavbarPagesUrls, NavbarExtraButton } from '../utils' import './MainMenu.css' @@ -13,6 +13,22 @@ export const MainMenu = (props: MainMenuProps) => { const urls = getNavbarPagesUrls() + // load extra button + const isMounted = useRef(false) + const [extraButton, setExtraButton] = useState(null) + useEffect(() => { + isMounted.current = true + if (!extraButton) { + getExtraButton().then((button) => { + if (!isMounted.current) return + setExtraButton(button) + }) + } + return () => { + isMounted.current = false + } + }, [extraButton, isMounted, setExtraButton]) + return (
{ title={i18n.explore} mainUrl={urls[NavbarPages.EXPLORE]} /> + {extraButton && extraButton.visible ? ( + + ) : null}
) } diff --git a/src/components/Navbar/MenuItem/MenuItem.css b/src/components/Navbar/MenuItem/MenuItem.css index 1d771f34..27fca789 100644 --- a/src/components/Navbar/MenuItem/MenuItem.css +++ b/src/components/Navbar/MenuItem/MenuItem.css @@ -12,6 +12,27 @@ fill: var(--text); } +.dui-navbar + .dui-navbar-wrapper + .item:not(.mobile).dui-menu-item.extra.has-background { + height: calc(100% - 24px); + padding: 12px; + margin-top: 12px; +} + +.item.mobile.dui-menu-item.extra.has-background { + margin-top: 40px; + padding: 20px; + width: calc(100% - 50px - 40px); + border-radius: 8px; + justify-content: center; + border-bottom: none; +} + +.item.mobile.dui-menu-item.extra.has-background .dui-icon-container.centered { + display: none; +} + @media (max-width: 991px) { .item.dui-menu-item.mobile { cursor: pointer; diff --git a/src/components/Navbar/MenuItem/MenuItem.tsx b/src/components/Navbar/MenuItem/MenuItem.tsx index f7a72e4d..fcd2f046 100644 --- a/src/components/Navbar/MenuItem/MenuItem.tsx +++ b/src/components/Navbar/MenuItem/MenuItem.tsx @@ -8,8 +8,16 @@ import { MenuItemProps } from './MenuItem.types' import './MenuItem.css' export const MenuItem = (props: MenuItemProps) => { - const { activePage, section, title, onToggleShowSubMenu, isMobile, mainUrl } = - props + const { + activePage, + section, + title, + onToggleShowSubMenu, + isMobile, + mainUrl, + textColor, + backgroundColor + } = props const mainRedirect = useCallback(() => { mainUrl && window.open(mainUrl, '_self') @@ -27,7 +35,13 @@ export const MenuItem = (props: MenuItemProps) => { onMouseLeave={(e: React.MouseEvent) => !isMobile && onToggleShowSubMenu(e, false, section) } - className={classNames('dui-menu-item', section, isMobile && 'mobile')} + className={classNames( + 'dui-menu-item', + section, + isMobile && 'mobile', + backgroundColor && 'has-background' + )} + style={{ color: textColor, backgroundColor }} > {title} {isMobile && } diff --git a/src/components/Navbar/MenuItem/MenuItem.types.ts b/src/components/Navbar/MenuItem/MenuItem.types.ts index c8d9aff4..c6fb39f3 100644 --- a/src/components/Navbar/MenuItem/MenuItem.types.ts +++ b/src/components/Navbar/MenuItem/MenuItem.types.ts @@ -11,4 +11,6 @@ export type MenuItemProps = { ) => void mainUrl?: string isMobile?: boolean + textColor?: string + backgroundColor?: string } diff --git a/src/components/Navbar/Navbar.types.ts b/src/components/Navbar/Navbar.types.ts index 3a74d010..27d98190 100644 --- a/src/components/Navbar/Navbar.types.ts +++ b/src/components/Navbar/Navbar.types.ts @@ -9,7 +9,8 @@ export enum NavbarPages { CREATE = 'create', EXPLORE = 'explore', LEARN = 'learn', - GOVERNANCE = 'governance' + GOVERNANCE = 'governance', + EXTRA = 'extra' } export type NavbarMenuI18nProps = Record diff --git a/src/components/Navbar/utils.ts b/src/components/Navbar/utils.ts index 4ae4a8f7..68bfe11d 100644 --- a/src/components/Navbar/utils.ts +++ b/src/components/Navbar/utils.ts @@ -10,3 +10,52 @@ export const getNavbarPagesUrls = () => { [NavbarPages.GOVERNANCE]: config.get('GOVERNANCE_URL') } } + +export type NavbarExtraButton = { + text: string + link: string + visible: boolean + textColor?: `#${string}` + backgroundColor?: `#${string}` + id?: string + ttl: number +} + +export type LocalStorageNavbarExtraButton = { + button: NavbarExtraButton + expiresAt: number +} + +export const getExtraButton = async () => { + const cachedExtraButton = localStorage.getItem('navbarExtraButton') + if (cachedExtraButton) { + try { + const parsed = JSON.parse( + cachedExtraButton + ) as LocalStorageNavbarExtraButton + if (parsed.expiresAt > Date.now()) { + return parsed.button + } + } catch (error) { + // error parsing cached data, ignore and fetch from Contentful + } + } + try { + const SPACE_ID = config.get('CONTENTFUL_SPACE_ID') + const ENV = config.get('CONTENTFUL_ENV') + const ACCESS_TOKEN = config.get('CONTENTFUL_NAVBAR_ACCESS_TOKEN') + const ENTRY_ID = config.get('CONTENTFUL_NAVBAR_ENTRY_ID') + const CONTENTFUL_URL = `https://cdn.contentful.com/spaces/${SPACE_ID}/environments/${ENV}/entries/${ENTRY_ID}?access_token=${ACCESS_TOKEN}` + const response = await fetch(CONTENTFUL_URL) + const entry = await response.json() + const button = entry.fields as NavbarExtraButton + localStorage.setItem( + 'navbarExtraButton', + JSON.stringify({ button, expiresAt: Date.now() + button.ttl * 1000 }) + ) + return button + } catch (error) { + console.error(error) + return null + } +} diff --git a/src/config/env/dev.json b/src/config/env/dev.json index a92b1883..61628816 100644 --- a/src/config/env/dev.json +++ b/src/config/env/dev.json @@ -34,5 +34,9 @@ "DOCS_DAO_URL": "https://docs.decentraland.org/player/general/dao/overview/what-is-the-dao/", "BLOG_URL": "https://decentraland.zone/blog/", "LANDING_CREATORS_URL": "https://decentraland.zone/create/", - "DOWNLOAD_URL": "https://decentraland.zone/download/" + "DOWNLOAD_URL": "https://decentraland.zone/download/", + "CONTENTFUL_SPACE_ID": "ea2ybdmmn1kv", + "CONTENTFUL_ENV": "master", + "CONTENTFUL_NAVBAR_ACCESS_TOKEN": "9dieh3AHS6uwb_YNMjxlO6FCibAFFJVdg2YzA5t6U-Y", + "CONTENTFUL_NAVBAR_ENTRY_ID": "18g1DzIyBxvu0steSwKyQr" } diff --git a/src/config/env/prod.json b/src/config/env/prod.json index ec924ff8..f5ee7364 100644 --- a/src/config/env/prod.json +++ b/src/config/env/prod.json @@ -34,5 +34,9 @@ "DOCS_DAO_URL": "https://docs.decentraland.org/player/general/dao/overview/what-is-the-dao/", "BLOG_URL": "https://decentraland.org/blog/", "LANDING_CREATORS_URL": "https://decentraland.org/create/", - "DOWNLOAD_URL": "https://decentraland.org/download/" + "DOWNLOAD_URL": "https://decentraland.org/download/", + "CONTENTFUL_SPACE_ID": "ea2ybdmmn1kv", + "CONTENTFUL_ENV": "master", + "CONTENTFUL_NAVBAR_ACCESS_TOKEN": "9dieh3AHS6uwb_YNMjxlO6FCibAFFJVdg2YzA5t6U-Y", + "CONTENTFUL_NAVBAR_ENTRY_ID": "18g1DzIyBxvu0steSwKyQr" } diff --git a/src/config/env/stg.json b/src/config/env/stg.json index 745ad31f..dc8df340 100644 --- a/src/config/env/stg.json +++ b/src/config/env/stg.json @@ -34,5 +34,9 @@ "DOCS_DAO_URL": "https://docs.decentraland.org/player/general/dao/overview/what-is-the-dao/", "BLOG_URL": "https://decentraland.today/blog/", "LANDING_CREATORS_URL": "https://decentraland.today/create/", - "DOWNLOAD_URL": "https://decentraland.todat/download/" + "DOWNLOAD_URL": "https://decentraland.todat/download/", + "CONTENTFUL_SPACE_ID": "ea2ybdmmn1kv", + "CONTENTFUL_ENV": "master", + "CONTENTFUL_NAVBAR_ACCESS_TOKEN": "9dieh3AHS6uwb_YNMjxlO6FCibAFFJVdg2YzA5t6U-Y", + "CONTENTFUL_NAVBAR_ENTRY_ID": "18g1DzIyBxvu0steSwKyQr" }