diff --git a/apps/connect/README.md b/apps/connect/README.md index 1ebe379f5..a3a31726c 100644 --- a/apps/connect/README.md +++ b/apps/connect/README.md @@ -25,3 +25,45 @@ If you are developing a production application, we recommend updating the config - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list + + +## Create a new banner + +Go to `apps/connect/public/data/banners.json` and add a new item with this format: + +``` +[ + { + "id": "string", + "background": "red", + "content": { + "text": "Lorem ipsum", + "color": "papayawhip", + "size": "30px" + }, + "button": { + "label": "Click here!", + "background": "red", + "href": "https://portalbridge.com" + }, + "since": "2023-12-22T01:06:52.211Z", + "until": "2023-12-25T01:06:52.211Z" + }, + { + "id": "string", + "background": "yellow", + "content": { + "text": "Lorem ipsum", + "color": "white", + "size": "15px" + }, + "button": { + "label": "Click here!", + "background": "green", + "href": "https://portalbridge.com" + }, + "since": "2023-12-22T01:06:52.211Z", + "until": "2023-12-25T01:06:52.211Z" + } +] +``` \ No newline at end of file diff --git a/apps/connect/package-lock.json b/apps/connect/package-lock.json index 23e512028..2c63a23e5 100644 --- a/apps/connect/package-lock.json +++ b/apps/connect/package-lock.json @@ -13,11 +13,14 @@ "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.14.11", "@mui/material": "^5.12.1", + "@tanstack/react-query": "^5.14.2", + "dompurify": "^3.0.6", "react": "^18.2.0", "react-dom": "^18.2.0", "vite-plugin-static-copy": "^0.17.0" }, "devDependencies": { + "@types/dompurify": "^3.0.5", "@types/node": "^20.7.0", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", @@ -32,35 +35,6 @@ "vite": "^4.4.5" } }, - "../wormhole-connect/wormhole-connect-loader": { - "name": "@wormhole-foundation/wormhole-connect", - "version": "0.0.12", - "extraneous": true, - "license": "ISC", - "dependencies": { - "@mui/material": "^5.12.1" - }, - "devDependencies": { - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^13.4.0", - "@testing-library/user-event": "^13.5.0", - "@types/jest": "^27.5.2", - "@types/node": "^16.18.23", - "@types/react": "^18.0.37", - "@types/react-dom": "^18.0.11", - "babel-loader": "^9.1.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "ts-loader": "^9.4.2", - "typescript": "^4.9.5", - "webpack": "^5.79.0", - "webpack-cli": "^5.0.1" - }, - "peerDependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0" - } - }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "dev": true, @@ -430,6 +404,26 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, "node_modules/@eslint/js": { "version": "8.50.0", "dev": true, @@ -833,6 +827,36 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@tanstack/query-core": { + "version": "5.14.2", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.14.2", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.14.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.13", "dev": true, @@ -884,6 +908,11 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "6.7.3", "dev": true, @@ -1095,21 +1124,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/ajv": { - "version": "6.12.6", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "dev": true, @@ -1371,6 +1385,10 @@ "csstype": "^3.0.2" } }, + "node_modules/dompurify": { + "version": "3.0.6", + "license": "(MPL-2.0 OR Apache-2.0)" + }, "node_modules/error-ex": { "version": "1.3.2", "license": "MIT", @@ -1521,6 +1539,26 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, "node_modules/espree": { "version": "9.6.1", "dev": true, @@ -1941,11 +1979,6 @@ "version": "2.3.1", "license": "MIT" }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "dev": true, - "license": "MIT" - }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "dev": true, @@ -2257,9 +2290,8 @@ }, "node_modules/prettier": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz", - "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, diff --git a/apps/connect/package.json b/apps/connect/package.json index 1c3c1777c..3b87c46cb 100644 --- a/apps/connect/package.json +++ b/apps/connect/package.json @@ -20,11 +20,14 @@ "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.14.11", "@mui/material": "^5.12.1", + "dompurify": "^3.0.6", + "@tanstack/react-query": "^5.14.2", "react": "^18.2.0", "react-dom": "^18.2.0", "vite-plugin-static-copy": "^0.17.0" }, "devDependencies": { + "@types/dompurify": "^3.0.5", "@types/node": "^20.7.0", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", diff --git a/apps/connect/public/data/banners.json b/apps/connect/public/data/banners.json new file mode 100644 index 000000000..c44dc44f3 --- /dev/null +++ b/apps/connect/public/data/banners.json @@ -0,0 +1,3 @@ +[ + +] \ No newline at end of file diff --git a/apps/connect/src/components/atoms/Bar.tsx b/apps/connect/src/components/atoms/Bar.tsx index 34678f193..1cef8391a 100644 --- a/apps/connect/src/components/atoms/Bar.tsx +++ b/apps/connect/src/components/atoms/Bar.tsx @@ -2,11 +2,13 @@ import styled from "@mui/material/styles/styled"; export type BarProps = { background: string; - children: JSX.Element | JSX.Element[] | null; + color?: string; + size?: string; + children: string[] | JSX.Element | JSX.Element[] | null; }; -const Container = styled("div")>( - ({ theme, background }) => ({ +const Container = styled("div")( + ({ theme, background, color, size }) => ({ display: "flex", alignItems: "center", justifyContent: "center", @@ -21,12 +23,18 @@ const Container = styled("div")>( }, textAlign: "center", fontWeight: 500, - fontSize: "16px", + color: color || theme.palette.text.primary, + fontSize: size || "16px", letterSpacing: "0.02em", background, + marginBottom: theme.spacing(0.5), }) ); -export default function Bar({ background, children }: BarProps) { - return {children}; +export default function Bar({ background, color, size, children }: BarProps) { + return ( + + {children} + + ); } diff --git a/apps/connect/src/components/atoms/NewsBar.tsx b/apps/connect/src/components/atoms/NewsBar.tsx index 5db9204d3..3865020b0 100644 --- a/apps/connect/src/components/atoms/NewsBar.tsx +++ b/apps/connect/src/components/atoms/NewsBar.tsx @@ -1,5 +1,6 @@ import NewBarButton from "./NewsBarButton"; import useBannerMessageConfig, { + useMessages, type Message, } from "../../hooks/useBannerMessage"; import Bar from "./Bar"; @@ -10,14 +11,30 @@ export type NewsBarProps = { export default function NewsBar({ messages }: NewsBarProps) { const message = useBannerMessageConfig(messages); + const banners = useMessages(); return ( - message && ( - - <> - {message.content} - {message.button ? : null} - - - ) + <> + {message && ( + + <> + {message.content} + {message.button ? : null} + + + )} + {banners && + banners.map((banner) => ( + + <> + {banner.content.text} + {banner.button ? : null} + + + ))} + ); } diff --git a/apps/connect/src/hooks/useBannerMessage.ts b/apps/connect/src/hooks/useBannerMessage.ts index c9b452029..80e112e18 100644 --- a/apps/connect/src/hooks/useBannerMessage.ts +++ b/apps/connect/src/hooks/useBannerMessage.ts @@ -1,4 +1,5 @@ import { useState, useEffect } from "react"; +import { useQuery } from "@tanstack/react-query"; export type Message = { background: string; @@ -40,3 +41,59 @@ export default function useBannerMessageConfig(messages: Message[]) { }, [messages]); return message; } + +export type Banner = { + id: string; + background: string; + button?: { + href: string; + label?: string; + background: string; + }; + content: { + text: string; + color?: string; + size?: string; + }; + since: Date; + until: Date; +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function parse(banner: Record): Banner { + const id = banner.id; + const background = banner.background; + const since = new Date(banner.since); + const until = new Date(banner.until); + const content = { + text: banner.content.text, + color: banner.content.color, + size: banner.content.size, + }; + const button = banner.button; + return { id, background, since, until, content, button }; +} + +async function fetchMessages( + location: string = "/data/banners.json" +): Promise { + const response = await fetch(location); + if (response.status !== 200) { + return []; + } else { + const json = await response.json(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return json.map((banner: Record) => parse(banner)); + } +} + +export function useMessages() { + const now = new Date(); + const allMessages = useQuery({ + queryKey: ["messages"], + queryFn: () => fetchMessages(), + }); + return allMessages.data?.filter( + ({ until, since }: Banner) => since < now && until > now + ); +} diff --git a/apps/connect/src/main.tsx b/apps/connect/src/main.tsx index 3c89545ed..b0fad0a61 100644 --- a/apps/connect/src/main.tsx +++ b/apps/connect/src/main.tsx @@ -2,6 +2,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import CssBaseline from "@mui/material/CssBaseline"; import ThemeProvider from "@mui/material/styles/ThemeProvider"; +import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; import theme from "./theme/portal.ts"; import Background from "./components/atoms/Background.tsx"; import App from "./App.tsx"; @@ -13,13 +14,17 @@ if (redirects && redirects?.source?.length > 0) { } } +const client = new QueryClient(); + ReactDOM.createRoot(document.getElementById("root")!).render( - - - - + + + + + + );