diff --git a/.gitignore b/.gitignore index ab434eef3..c90ef2e4e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ # testing /coverage +/cypress.env.json /cypress/videos /cypress/screenshots /cypress/fixtures/login.json diff --git a/cypress.env.json.example b/cypress.env.json.example new file mode 100644 index 000000000..08fba94ca --- /dev/null +++ b/cypress.env.json.example @@ -0,0 +1,6 @@ +{ + "admin_username": "", + "admin_password": "", + "test_username": "", + "test_password": "" +} \ No newline at end of file diff --git a/cypress/integration/IconLogo.spec.py.js b/cypress/integration/IconLogo.spec.py.js new file mode 100644 index 000000000..16897783b --- /dev/null +++ b/cypress/integration/IconLogo.spec.py.js @@ -0,0 +1,20 @@ +describe('Icon Logo', () => { + it('Displays the organization logo when the user belongs to an organization', () => { + cy.visit('http://localhost:3001/login'); + cy.get('#userName').type(Cypress.env('test_username')); + cy.get('#password').type(Cypress.env('test_password')); + cy.contains(/log/i).click(); + cy.contains(/earnings/i).click(); + cy.wait(100); + cy.get('img').should('have.attr', 'alt', 'organization logo'); + }); + it('Displays the Greenstand logo when the user does not belongs to an organization', () => { + cy.visit('http://localhost:3001/login'); + cy.get('#userName').type(Cypress.env('admin_username')); + cy.get('#password').type(Cypress.env('admin_password')); + cy.contains(/log/i).click(); + cy.contains(/earnings/i).click(); + cy.wait(100); + cy.get('img').should('have.attr', 'alt', 'greenstand logo'); + }); +}); diff --git a/cypress/integration/messaging2.spec.py.js b/cypress/integration/messaging2.spec.py.js index 02a5bd004..0e013c12b 100644 --- a/cypress/integration/messaging2.spec.py.js +++ b/cypress/integration/messaging2.spec.py.js @@ -459,8 +459,8 @@ describe('Messaging', () => { ); cy.visit('http://localhost:3001/login'); - cy.get('#userName').type('admin'); - cy.get('#password').type('8pzPdcZAG6&Q'); + cy.get('#userName').type(Cypress.env('admin_username')); + cy.get('#password').type(Cypress.env('admin_password')); cy.contains(/log/i).click(); cy.contains(/inbox/i).click(); cy.contains( @@ -470,8 +470,8 @@ describe('Messaging', () => { }); it('Returns an error message if the user is not registered for messaging', () => { cy.visit('http://localhost:3001/login'); - cy.get('#userName').type('test1'); - cy.get('#password').type('EoCAyCPpW0'); + cy.get('#userName').type(Cypress.env('test_username')); + cy.get('#password').type(Cypress.env('test_password')); cy.contains(/log/i).click(); cy.contains(/inbox/i).click(); cy.contains( diff --git a/src/api/stakeholders.js b/src/api/stakeholders.js index ea146706f..28e6d765c 100644 --- a/src/api/stakeholders.js +++ b/src/api/stakeholders.js @@ -54,6 +54,24 @@ export default { } }, + // Returns a single stakeholder data + getStakeholder(id) { + try { + const query = `${STAKEHOLDER_API}/stakeholders/${id}`; + + const options = { + method: 'GET', + headers: { + 'content-type': 'application/json', + }, + }; + + return fetchJSON(query, options); + } catch (e) { + log.error('getStakeholder', e); + } + }, + deleteStakeholder(id, stakeholderData) { try { const orgId = id || getOrganizationId(); diff --git a/src/components/IconLogo.js b/src/components/IconLogo.js index f0280e1b9..27f1f4962 100644 --- a/src/components/IconLogo.js +++ b/src/components/IconLogo.js @@ -1,22 +1,47 @@ import { Link } from 'react-router-dom'; import logo from './images/logo.svg'; +import { AppContext } from '../context/AppContext'; +import { React, useContext } from 'react'; /* * Just a logo icon */ -import React from 'react'; export default function IconLogo() { + const appContext = useContext(AppContext); + const { logoPath, user } = appContext; + + // Hide logo if the logo URL hasn't been loaded or if the Greenstand logo is loaded + // and the user has an organization + function isVisible() { + if (!user) { + return 'visible'; + } + if (logoPath === '') { + return 'hidden'; + } else return 'visible'; + } + + // Logo styling objects for both org and Greenstand logos to be applied to img + const greenstandLogoStyle = { + maxWidth: 149, + maxHeight: 32, + marginBottom: '-6px', + visibility: isVisible(), + }; + + const orgLogoStyle = { + maxHeight: 50, + marginBottom: '-15px', + visibility: isVisible(), + }; + return ( Greenstand logo ); diff --git a/src/components/Login.js b/src/components/Login.js index 21ac609f4..05a80c34e 100644 --- a/src/components/Login.js +++ b/src/components/Login.js @@ -11,10 +11,11 @@ import { Container, CircularProgress, } from '@material-ui/core'; -import IconLogo from './IconLogo'; +import logo from './images/logo.svg'; import { withStyles } from '@material-ui/core/styles'; import { AppContext } from '../context/AppContext'; import classNames from 'classnames'; +import api from '../api/stakeholders'; import { useHistory, useLocation } from 'react-router-dom'; import axios from 'axios'; // import Copyright from 'components/Copyright' @@ -142,7 +143,21 @@ const Login = (props) => { if (res.status === 200) { const token = res.data.token; const user = res.data.user; - appContext.login(user, token, isRemember); + // GET logo URL from API if user belongs to an organization + // and apply it to logoPath state before completing login + if (user.policy.organization) { + const orgID = user.policy.organization.id; + try { + await api.getStakeholder(orgID).then((response) => { + const orgLogo = response.stakeholders[0].logo_url; + orgLogo && appContext.setLogoPath(orgLogo); + }); + } catch (e) { + console.error('Undefined User error:', e); + } finally { + appContext.login(user, token, isRemember); + } + } else appContext.login(user, token, isRemember); } else { setErrorMessage('Invalid username or password'); setLoading(false); @@ -167,7 +182,15 @@ const Login = (props) => {
- + Greenstand logo Admin Panel
diff --git a/src/context/AppContext.js b/src/context/AppContext.js index 82cd44d6c..8f0b24f20 100644 --- a/src/context/AppContext.js +++ b/src/context/AppContext.js @@ -32,9 +32,11 @@ import CompareIcon from '@material-ui/icons/Compare'; import CreditCardIcon from '@material-ui/icons/CreditCard'; import InboxRounded from '@material-ui/icons/InboxRounded'; import MapIcon from '@material-ui/icons/Map'; +import logo from '../components/images/logo.svg'; import AccountTreeIcon from '@material-ui/icons/AccountTree'; import { session, hasPermission, POLICIES } from '../models/auth'; import api from '../api/treeTrackerApi'; +import stakeholdersAPI from '../api/stakeholders'; import RegionsView from 'views/RegionsView'; import log from 'loglevel'; @@ -220,6 +222,7 @@ export const AppProvider = (props) => { const [userHasOrg, setUserHasOrg] = useState(false); const [orgList, setOrgList] = useState([]); const [orgId, setOrgId] = useState(undefined); + const [logoPath, setLogoPath] = useState(''); // TODO: The below `selectedFilters` state would be better placed under a // separate FilterContext in the future iterations when the need to share @@ -241,6 +244,22 @@ export const AppProvider = (props) => { } }, [orgList]); + // Gets organization logo url from the API + useEffect(() => { + if (user && user.policy.organization) { + const orgID = user.policy.organization.id; + try { + stakeholdersAPI.getStakeholder(orgID).then((response) => { + const orgLogo = response.stakeholders[0].logo_url; + orgLogo && setLogoPath(orgLogo); + }); + } catch (e) { + console.error('Undefined User error:', e); + setLogoPath(logo); + } + } else setLogoPath(logo); + }, [user]); + function checkSession() { const localToken = JSON.parse(localStorage.getItem('token')); const localUser = JSON.parse(localStorage.getItem('user')); @@ -339,6 +358,8 @@ export const AppProvider = (props) => { routes, orgId, orgList, + logoPath, + setLogoPath, userHasOrg, selectedFilters, updateSelectedFilter,