diff --git a/.circleci/config.yml b/.circleci/config.yml index b361cbb..2b1dc3f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -23,7 +23,7 @@ executors: default-machine: machine: - image: ubuntu-2004:202201-02 + image: ubuntu-2204:2023.04.2 ## # Jobs diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6c6e226 --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +# RED THEME +PRIMARY_COLOR=#e80002 +SECONDARY_COLOR=#e80002 +ACCENT_COLOR=#9b0214 +TITLE='Airtel' +COUNTRY_LOGO="https://upload.wikimedia.org/wikipedia/commons/thumb/0/06/Flag_of_Zambia.svg/125px-Flag_of_Zambia.svg.png" +LOGO="https://upload.wikimedia.org/wikipedia/commons/thumb/f/fb/Bharti_Airtel_Logo.svg/150px-Bharti_Airtel_Logo.svg.png" + +# YELLOW THEME +PRIMARY_COLOR=#f9d342 +SECONDARY_COLOR=#f1b92a +ACCENT_COLOR=#a77e07' diff --git a/.github/workflows/ui-test.yaml b/.github/workflows/ui-test.yaml index 88c72a8..37b1b92 100644 --- a/.github/workflows/ui-test.yaml +++ b/.github/workflows/ui-test.yaml @@ -6,44 +6,45 @@ on: - '**' jobs: - ui_test: - timeout-minutes: 45 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - uses: cachix/install-nix-action@v22 - with: - nix_path: nixpkgs=https://github.com/NixOS/nixpkgs/archive/8e4fe32876ca15e3d5eb3ecd3ca0b224417f5f17.tar.gz - - - name: Install dependencies - run: nix-env -if ui-tests/default.nix - - - name: Start Docker Compose - working-directory: on-premise-deploy/docker-compose/ - run: docker-compose up -d - - - name: Wait for Docker Compose and Vault - # TODO: Use a script to ascertain docker health instead of flat wait time - run: sleep 120s - - - name: Populate payer transfer data with TTK - run: docker exec mojaloop-testing-toolkit npm run cli -- outbound -i collections/payer-tests/ -e environments/environment.json - - - name: Populate payee transfer data with TTK - run: docker exec mojaloop-testing-toolkit npm run cli -- outbound -i collections/payee-tests/ -e environments/environment.json - - - name: Install test dependencies - working-directory: ui-tests/tests - run: |- - npm ci - - - name: Upgrade browserlist - working-directory: ui-tests/tests - run: |- - npx browserslist@latest --update-db - - - name: Run tests - working-directory: ui-tests/tests - run: |- - ENV="local" PM4ML_ENDPOINT="http://localhost:8081" SIM_CORE_CONNECTOR_ENDPOINT="http://localhost:3003" npm run test:headless + # ui_test: + # timeout-minutes: 45 + # if: "!contains(github.event.head_commit.message, '[skip tests]')" + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v2 + # + # - uses: cachix/install-nix-action@v22 + # with: + # nix_path: nixpkgs=https://github.com/NixOS/nixpkgs/archive/8e4fe32876ca15e3d5eb3ecd3ca0b224417f5f17.tar.gz + # + # - name: Install dependencies + # run: nix-env -if ui-tests/default.nix + # + # - name: Start Docker Compose + # working-directory: on-premise-deploy/docker-compose/ + # run: docker-compose up -d + # + # - name: Wait for Docker Compose and Vault + # # TODO: Use a script to ascertain docker health instead of flat wait time + # run: sleep 120s + # + # - name: Populate payer transfer data with TTK + # run: docker exec mojaloop-testing-toolkit npm run cli -- outbound -i collections/payer-tests/ -e environments/environment.json + # + # - name: Populate payee transfer data with TTK + # run: docker exec mojaloop-testing-toolkit npm run cli -- outbound -i collections/payee-tests/ -e environments/environment.json + # + # - name: Install test dependencies + # working-directory: ui-tests/tests + # run: |- + # npm ci + # + # - name: Upgrade browserlist + # working-directory: ui-tests/tests + # run: |- + # npx browserslist@latest --update-db + # + # - name: Run tests + # working-directory: ui-tests/tests + # run: |- + # ENV="local" PM4ML_ENDPOINT="http://localhost:8081" SIM_CORE_CONNECTOR_ENDPOINT="http://localhost:3003" npm run test:headless diff --git a/package.json b/package.json index fb270fa..e7fadd1 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "serve": "^11.3.2", "svg-sprite-loader": "^5.0.0", "typescript": "~3.7.2", + "validator": "^13.12.0", "xlsx": "^0.17.5" }, "scripts": { @@ -104,6 +105,7 @@ ] }, "devDependencies": { + "@types/validator": "^13.12.2", "redux-mock-store": "^1.5.4", "standard-version": "^9.5.0" }, diff --git a/server/index.js b/server/index.js index 7d126cf..cc8275c 100644 --- a/server/index.js +++ b/server/index.js @@ -4,7 +4,7 @@ const app = express(); app.use(express.static(path.join(__dirname, 'build'))); -app.get('/config', function (req, res) { +app.get('/config', function(req, res) { res.send({ API_BASE_URL: process.env.API_BASE_URL, CHECK_SESSION_URL: process.env.CHECK_SESSION_URL, @@ -14,8 +14,19 @@ app.get('/config', function (req, res) { }); }); -app.get('/*', function (req, res) { +app.get('/uiConfig', function(req, res) { + res.send({ + PRIMARY_COLOR: process.env.PRIMARY_COLOR, + SECONDARY_COLOR: process.env.SECONDARY_COLOR, + ACCENT_COLOR: process.env.ACCENT_COLOR, + LOGO: process.env.LOGO, + TITLE: process.env.TITLE, + COUNTRY_LOGO: process.env.COUNTRY_LOGO, + }); +}); + +app.get('/*', function(req, res) { res.sendFile(path.join(__dirname, 'build', 'index.html')); }); -app.listen(8080); \ No newline at end of file +app.listen(8080); diff --git a/src/App/App.tsx b/src/App/App.tsx index f5e0575..aefef0c 100644 --- a/src/App/App.tsx +++ b/src/App/App.tsx @@ -1,6 +1,7 @@ import { Switch, Route, Redirect } from 'react-router-dom'; import React, { FC } from 'react'; import './App.css'; +import { useSelector } from 'react-redux'; import Layout from './Layout'; import TechnicalDashboard from './TechnicalDashboard'; import Transfers from './Transfers'; @@ -12,6 +13,8 @@ import FxpTechnicalDashboard from './FxpTechnicalDashboard'; // import FxpTransfers from './FxpConversions'; import FxpConversions from './FxpConversions'; +import { getUiConfig } from './selectors'; + interface AppProps { isSuccessToastVisible: boolean; isErrorModalVisible: boolean; @@ -28,6 +31,10 @@ const App: FC = ({ onCloseErrorModal, userInfo, }) => { + const uiConfig = useSelector(getUiConfig); + const { appTitle, countryLogo, appLogo } = uiConfig; + const activeConnectionName = 'Modusbox & Mojaloop Labs'; + const activeConnectionStatusColor = '#12d670'; return (
@@ -37,8 +44,11 @@ const App: FC = ({ } logoutUrl={userInfo ? userInfo.logoutUrl : undefined} kratos={userInfo?.kratos} - activeConnectionName="Modusbox & Mojaloop Labs" - activeConnectionStatusColor="#12d670" + activeConnectionName={activeConnectionName} + activeConnectionStatusColor={activeConnectionStatusColor} + appTitle={appTitle} + appLogo={appLogo} + countryLogo={countryLogo} /> diff --git a/src/App/FxpConversions/FxpConversions.tsx b/src/App/FxpConversions/FxpConversions.tsx index b0535d7..c0e011b 100644 --- a/src/App/FxpConversions/FxpConversions.tsx +++ b/src/App/FxpConversions/FxpConversions.tsx @@ -16,6 +16,8 @@ import FxpConversionsAvgTime from './components/FxpConversionAvgTime'; import FxpConversionsStatuses from './components/FxpConversionStatuses'; import './FxpConversions.css'; +import { getUiConfig } from '../selectors'; + const stateProps = (state: State) => ({ fxpConversionsErrors: selectors.getFxpConversionsErrors(state), fxpConversionsErrorsError: selectors.getFxpConversionsErrorsError(state), @@ -32,6 +34,7 @@ const stateProps = (state: State) => ({ fxpConversionsAvgTimeError: selectors.getFxpConversionsAvgTimeError(state), isFxpConversionsAvgTimePending: selectors.getIsFxpConversionsAvgTimePending(state), isFxpConversionDetailsModalVisible: selectors.getIsFxpConversionDetailsModalVisible(state), + uiConfig: getUiConfig(state), }); const dispatchProps = (dispatch: Dispatch) => ({ @@ -65,6 +68,7 @@ type FxpFxpConversionsProps = { onViewAllReconcilationErrorsButtonClick: () => void; onFxpConversionFinderButtonClick: () => void; onFxpConversionRowClick: (fxpConversionError: FxpConversionError) => void; + uiConfig: { primaryColor: string }; }; const FxpFxpConversions: FC = ({ @@ -86,7 +90,9 @@ const FxpFxpConversions: FC = ({ onViewAllReconcilationErrorsButtonClick, onFxpConversionFinderButtonClick, onFxpConversionRowClick, + uiConfig, }) => { + const { primaryColor } = uiConfig; return (
FXP Conversions Overview @@ -101,11 +107,13 @@ const FxpFxpConversions: FC = ({ data={fxpConversionsSuccessPerc} error={fxpConversionsSuccessPercError} isPending={isFxpConversionsSuccessPercPending} + legendColor={primaryColor} /> = ({ isPending, data, error }) => { +const FxpConversionsCharts: FC = ({ + isPending, + data, + error, + legendColor, +}) => { let content = null; if (isPending || !data) { content = ( @@ -23,8 +29,8 @@ const FxpConversionsCharts: FC = ({ isPending, data, content = ( } + legend={[{ label: 'Avg. FxpConversion Time in ms / Min', color: legendColor }]} + Graph={() => } /> ); } @@ -33,9 +39,13 @@ const FxpConversionsCharts: FC = ({ isPending, data, interface AverageFxpConversionTimeGraphProps { data: XYCoordinate[]; + chartColor: string; } -const AverageFxpConversionTimeGraph: FC = ({ data }) => { +const AverageFxpConversionTimeGraph: FC = ({ + data, + chartColor, +}) => { const series = { name: 'Average Response Time', data, @@ -66,7 +76,7 @@ const AverageFxpConversionTimeGraph: FC = ({ width: [2], curve: 'smooth', }, - colors: ['#4fc7e7'], + colors: [chartColor], tooltip: { x: { formatter: (val: string | number) => { diff --git a/src/App/FxpConversions/components/FxpConversionSuccessPerc/index.tsx b/src/App/FxpConversions/components/FxpConversionSuccessPerc/index.tsx index 0665cf3..ca6f2bd 100644 --- a/src/App/FxpConversions/components/FxpConversionSuccessPerc/index.tsx +++ b/src/App/FxpConversions/components/FxpConversionSuccessPerc/index.tsx @@ -7,12 +7,14 @@ interface FxpConversionsSuccessPercProps { isPending: boolean | undefined; data?: XYCoordinate[]; error: ErrorMessage; + legendColor: string; } const FxpConversionsSuccessPerc: FC = ({ isPending, data, error, + legendColor, }) => { let content = null; if (isPending || !data) { @@ -27,8 +29,8 @@ const FxpConversionsSuccessPerc: FC = ({ content = ( } + legend={[{ label: 'Percent / Min', color: legendColor }]} + Graph={() => } /> ); } @@ -37,9 +39,13 @@ const FxpConversionsSuccessPerc: FC = ({ interface SuccessfulFxpConversionGraphProps { data: XYCoordinate[]; + chartColor: string; } -const SuccessfulFxpConversionGraph: FC = ({ data }) => { +const SuccessfulFxpConversionGraph: FC = ({ + data, + chartColor, +}) => { const series = { name: 'Success Percentage', data, @@ -70,7 +76,7 @@ const SuccessfulFxpConversionGraph: FC = ({ d width: [2], curve: 'smooth', }, - colors: ['#4fc7e7'], + colors: [chartColor], tooltip: { x: { formatter: (val: string | number) => { diff --git a/src/App/Layout/Navbar/Navbar.css b/src/App/Layout/Navbar/Navbar.css index 01993ee..5936e8b 100644 --- a/src/App/Layout/Navbar/Navbar.css +++ b/src/App/Layout/Navbar/Navbar.css @@ -1,13 +1,10 @@ #navbar { flex: 0 0 55px; - width: 100%; - background: #987; - webkit-user-select: none; user-select: none; height: 55px; width: 100%; - background: linear-gradient(269.81deg, #043865 0.01%, #02182b 100%); - border-bottom: 5px solid #4fc7e7; + background: linear-gradient(269.81deg, var(--secondary-color) 0.01%, var(--accent-color) 100%); + border-bottom: 5px solid var(--primary-color); display: flex; align-items: center; } @@ -63,3 +60,13 @@ border-radius: 50%; margin-left: 10px; } +.navbar__dfsp-logo { + height: 30px; + margin-left: 15px; + object-fit: contain; +} +.navbar__country-logo { + height: 30px; + object-fit: cover; + margin-left: 15px; +} diff --git a/src/App/Layout/Navbar/index.tsx b/src/App/Layout/Navbar/index.tsx index 30daa31..0cb5782 100644 --- a/src/App/Layout/Navbar/index.tsx +++ b/src/App/Layout/Navbar/index.tsx @@ -2,58 +2,79 @@ import React, { FC } from 'react'; import { Icon } from 'components'; import './Navbar.css'; -type Navbar = { +type NavbarProps = { username?: string; logoutUrl?: string; activeConnectionName: string; activeConnectionStatusColor: string; kratos?: boolean; + appTitle: string; + appLogo: string; + countryLogo: string; }; -const Navbar: FC = ({ +const Navbar: FC = ({ username, activeConnectionName, activeConnectionStatusColor, logoutUrl, kratos, + appTitle, + appLogo, + countryLogo, }) => { - const clickFunc = () => { + const handleLogout = () => { if (logoutUrl) { if (kratos) { - fetch(`${logoutUrl}`, { - headers: { - accept: 'application/json', - }, + fetch(logoutUrl, { + headers: { accept: 'application/json' }, }) .then((response) => response.json()) .then(({ logout_url }) => { if (logout_url) window.location.assign(logout_url); }); - } else window.location.href = logoutUrl; + } else { + window.location.href = logoutUrl; + } } }; return (