diff --git a/cypress/fixtures/940.json b/cypress/fixtures/940.json new file mode 100644 index 000000000..ffc48e973 --- /dev/null +++ b/cypress/fixtures/940.json @@ -0,0 +1,17 @@ +{ + "id": 940, + "first_name": "Sebastian ", + "last_name": "Gaertner", + "image_url": "https://treetracker-dev-images.s3.eu-central-1.amazonaws.com/2020.10.19.09.47.53_-5.508107173727935_38.981361706266256_39f0cc9d-0f13-4547-8142-150f15cabb67_IMG_20201019_094513_6614320100195503436.jpg", + "trees_planted": 4, + "about": "Greenway is a Youth-Driven Environmental Protection Organization providing alternative solutions to single-use plastic and planting carbon-sucking trees for socio-economic development and reducing climate crisis. Our social work includes reforestation, landscape restoration, climate education, awareness campaign, conducting research, outreach activities, and collaborating with key stakeholders to implement sustainable solutions.", + "mission": "To combat climate change, desertification, land degradation, carbon emission by inspiring healthier communities affected by severe climate disorder and modestly reducing pollution by 2050.", + "created_time": "2018-01-01", + "country": "Tanzania", + "links": { + "featured_trees": "/trees?planter_id=940&limit=4", + "associated_organizations": "/organizations?planter_id=940", + "species": "/species?planter_id=940" + } + } + \ No newline at end of file diff --git a/cypress/tests/integration/organizations/[organizationid].cy.js b/cypress/tests/integration/organizations/[organizationid].cy.js index 72b827931..3e8c0e917 100644 --- a/cypress/tests/integration/organizations/[organizationid].cy.js +++ b/cypress/tests/integration/organizations/[organizationid].cy.js @@ -15,7 +15,9 @@ describe('Organizations', () => { prepareNocks({ organization: { ...org, photo_url } }); }); - cy.visit(path); + cy.visit(path, { + failOnStatusCode: false, + }); cy.url().should('include', '/organizations'); cy.contains(org.name); diff --git a/cypress/tests/integration/stakeholder/[stakeholderid].cy.js b/cypress/tests/integration/stakeholder/[stakeholderid].cy.js new file mode 100644 index 000000000..3b552481b --- /dev/null +++ b/cypress/tests/integration/stakeholder/[stakeholderid].cy.js @@ -0,0 +1,60 @@ +import species from '../../../../doc/examples/species/1.json'; +import stakeholder from '../../../../doc/examples/stakeholders/180Earth.json'; +import planter from '../../../fixtures/940.json'; +import exampleTreeData from '../../../fixtures/tree186734.json'; +import { prepareNocks, clearNocks } from '../nockRoutes'; + +beforeEach(() => { + clearNocks(); +}); + +describe('Stakeholder', () => { + const imageFixturePath = `images/organization.png`; + return it(`stakeholder test`, () => { + const path = `/stakeholder/${stakeholder.id}`; + cy.fixture(imageFixturePath).then((image) => { + const blob = Cypress.Blob.base64StringToBlob(image, 'images/png'); + const photo_url = Cypress.Blob.createObjectURL(blob); + prepareNocks({ stakeholder: { ...stakeholder, photo_url } }); + }); + + cy.task('nockIntercept', { + hostname: 'https://dev-k8s.treetracker.org', + method: 'get', + path: '/query/v2/stakeholder/1', + statusCode: 200, + body: stakeholder, + }); + + cy.task('nockIntercept', { + hostname: 'https://dev-k8s.treetracker.org', + method: 'get', + path: '/query/v2/trees?stakeholder_id=1', + statusCode: 200, + body: { total: 1, offset: 0, limit: 4, trees: [exampleTreeData] }, + }); + + cy.task('nockIntercept', { + hostname: 'https://dev-k8s.treetracker.org', + method: 'get', + path: '/query/v2/planters?stakeholder_id=1', + statusCode: 200, + body: { total: 1, offset: 0, limit: 4, planters: [planter] }, + }); + + cy.task('nockIntercept', { + hostname: 'https://dev-k8s.treetracker.org', + method: 'get', + path: '/query/v2/species?stakeholder_id=1', + statusCode: 200, + body: { total: 1, offset: 0, limit: 4, species: [species] }, + }); + + cy.visit(path, { + failOnStatusCode: false, + }); + + cy.url().should('include', '/stakeholder'); + cy.screenshot(); + }); +}); diff --git a/doc/examples/stakeholders/180Earth.json b/doc/examples/stakeholders/180Earth.json new file mode 100644 index 000000000..0d02adac0 --- /dev/null +++ b/doc/examples/stakeholders/180Earth.json @@ -0,0 +1,17 @@ +{ + "id": 1, + "type": "organization", + "org_name": "180Earth", + "first_name": "John", + "last_name": "Doe", + "email": "abc@gmail.com", + "phone": "1234567890", + "website": "www.180earth.com", + "logo_url": "https://180.earth/wp-content/uploads/2020/01/Asset-1.png", + "map": "Shirimatunda, Tanzania", + "created_at": "November 11, 2019", + "updated_at": "April 1, 2024", + "active": "true", + "entity_id": 1 + } + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 07d05357e..6d9ea7bfb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "client", - "version": "2.6.0-v2.5", + "version": "2.6.0-v2.9", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "client", - "version": "2.6.0-v2.5", + "version": "2.6.0-v2.9", "dependencies": { "@emotion/cache": "^11.5.0", "@emotion/react": "^11.5.0", diff --git a/src/models/api.js b/src/models/api.js index 817e4a80e..889a48f88 100644 --- a/src/models/api.js +++ b/src/models/api.js @@ -234,3 +234,17 @@ export async function getTokenById(id) { throw err; } } + +export async function getStakeholderById(id) { + try { + const url = apiPaths.stakeholder(id); + const begin = Date.now(); + const res = await axios.get(url); + const data = await res.data; + log.warn('url:', url, 'took:', Date.now() - begin); + return data; + } catch (err) { + log.error(err); + throw err; + } +} diff --git a/src/models/apiPaths.js b/src/models/apiPaths.js index 6232e34a9..dc30dc9c8 100644 --- a/src/models/apiPaths.js +++ b/src/models/apiPaths.js @@ -23,6 +23,7 @@ const apiPaths = { filterSpeciesByWalletId: (id = '') => urlJoin(host, `/species?wallet_id=${id}`), tokens: (id = '') => urlJoin(host, `/tokens/${id}`), + stakeholder: (id = '') => urlJoin(host, `/stakeholder/${id}`), }; export default apiPaths; diff --git a/src/pages/organizations/[organizationid].js b/src/pages/organizations/[organizationid].js index 84cca8650..26f21bd7e 100644 --- a/src/pages/organizations/[organizationid].js +++ b/src/pages/organizations/[organizationid].js @@ -285,9 +285,7 @@ export default function Organization(props) { - `/organizations/${organization.id}/trees/${item.id}` - } + link={(item) => `/trees/${item.id}`} /> @@ -525,6 +525,8 @@ export default function Planter(props) { } async function serverSideData(params) { + console.log('serverSideData'); + console.log(params); const id = params.planterid; const planter = await getPlanterById(id); const data = await getOrgLinks(planter.links); diff --git a/src/pages/stakeholder/[stakeholderid].js b/src/pages/stakeholder/[stakeholderid].js new file mode 100644 index 000000000..1c0e63dba --- /dev/null +++ b/src/pages/stakeholder/[stakeholderid].js @@ -0,0 +1,508 @@ +import { Box, Divider, Grid, Typography, Avatar } from '@mui/material'; +import Portal from '@mui/material/Portal'; +import log from 'loglevel'; +import { marked } from 'marked'; +import moment from 'moment'; +import { useRouter } from 'next/router'; +import React, { useEffect, useMemo } from 'react'; +import Badge from 'components/Badge'; +import CustomWorldMap from 'components/CustomWorldMap'; +import FeaturedTreesSlider from 'components/FeaturedTreesSlider'; +import HeadTag from 'components/HeadTag'; +import ImpactSection from 'components/ImpactSection'; +import PlanterQuote from 'components/PlanterQuote'; +import ProfileAvatar from 'components/ProfileAvatar'; +import ProfileCover from 'components/ProfileCover'; +import TreeSpeciesCard from 'components/TreeSpeciesCard'; +import Crumbs from 'components/common/Crumbs'; +import CustomCard from 'components/common/CustomCard'; +import Icon from 'components/common/CustomIcon'; +import Info from 'components/common/Info'; +import { useDrawerContext } from 'context/DrawerContext'; +import { useMobile } from 'hooks/globalHooks'; +import CalendarIcon from 'images/icons/calendar.svg'; +import LocationIcon from 'images/icons/location.svg'; +import PeopleIcon from 'images/icons/people.svg'; +import TreeIcon from 'images/icons/tree.svg'; +import imagePlaceholder from 'images/image-placeholder.png'; +import SearchIcon from 'images/search.svg'; +import { useMapContext } from 'mapContext'; +import { + getOrganizationById, + getOrgLinks, + getStakeholderById, +} from 'models/api'; +import * as pathResolver from 'models/pathResolver'; +import { + getLocationString, + getContinent, + wrapper, + requestAPI, +} from 'models/utils'; + +export default function Stakeholder(props) { + log.warn('props for stakeholder page:', props); + const { stakeholder, nextExtraIsEmbed } = props; + const mapContext = useMapContext(); + const [isPlanterTab, setIsPlanterTab] = React.useState(true); + // eslint-disable-next-line + const [continent, setContinent] = React.useState(null); + const router = useRouter(); + const isMobile = useMobile(); + const { featuredTrees, associated_planters } = stakeholder; + console.log('planters', associated_planters.planters); + const { setTitlesData } = useDrawerContext(); + + async function updateContinent() { + const tree = stakeholder?.featuredTrees?.trees[0]; + if (tree) { + const { lat, lon } = tree; + const newContinent = await getContinent(lat, lon); + setContinent(newContinent.name); + } + } + + useEffect(() => { + setTitlesData({ + name: stakeholder.map, + createAt: stakeholder.created_at, + }); + }, [stakeholder.created_at, stakeholder.map, setTitlesData]); + + useEffect(() => { + async function reload() { + // manipulate the map + const { map } = mapContext; + if (map && stakeholder) { + // map.flyTo(tree.lat, tree.lon, 16); + await map.setFilters({ + map_name: stakeholder.map, + }); + const bounds = pathResolver.getBounds(router); + if (bounds) { + log.warn('goto bounds found in url'); + await map.gotoBounds(bounds); + } else { + const view = await map.getInitialView(); + await map.gotoView(view.center.lat, view.center.lon, view.zoomLevel); + } + } else { + log.warn('no data:', map, stakeholder); + } + } + reload(); + + updateContinent(); + // eslint-disable-next-line + }, [mapContext, stakeholder]); + + const logo_url = stakeholder.logo_url || imagePlaceholder; + const name = + stakeholder.first_name && stakeholder.last_name + ? `${stakeholder.first_name} ${stakeholder.last_name}` + : '---'; + const org_name = stakeholder.org_name || '---'; + + const BadgeSection = useMemo( + () => ( + <> + + + + ), + [], + ); + + return ( + <> + + [t.spacing(0, 4), 6], + width: 1, + boxSizing: 'border-box', + }, + nextExtraIsEmbed && { + padding: (t) => [t.spacing(0, 4), 6 * 0.6], + }, + ]} + > + {!isMobile && ( + + , + name: 'Home', + url: '/', + }, + { + icon: logo_url, + name: `${org_name}`, + }, + ]} + /> + + + + )} + + + + + + + {!isMobile && ( + + {name} + + Organization: {org_name} /> + + + + Stakeholder since + + + } + /> + + + + + + {BadgeSection} + + + )} + + {isMobile && ( + document.getElementById('drawer-title-container')} + > + + {name} + + Organization: {org_name}} + /> + + + + + + {BadgeSection} + + + + )} + {isMobile && ( + + document.getElementById('drawer-title-container-min') + } + > + + + {name} + + + )} + + + Featured trees by {name} + `/trees/${item.id}`} + /> + + + setIsPlanterTab(true)} + iconURI={TreeIcon} + iconProps={{ + sx: { + '& path': { + fill: ({ palette }) => palette.primary.main, + }, + }, + }} + title="Tree Captures Collected" + text={stakeholder?.featuredTrees?.total || '---'} + disabled={!isPlanterTab} + /> + + + setIsPlanterTab(false)} + iconURI={PeopleIcon} + iconProps={{ + sx: { + '& path': { + fill: ({ palette }) => palette.text.primary, + }, + }, + }} + title="Hired Planters" + text={associated_planters.total || '---'} + disabled={isPlanterTab} + /> + + + + + + + + Species of trees planted + + {stakeholder?.species?.species?.length > 0 ? ( + + {stakeholder?.species?.species?.map((s) => ( +
  • + + +
  • + ))} +
    + ) : ( + NO DATA YET + )} +
    +
    + + + {/* {associated_planters?.planters?.map((planter) => ( + + ))} */} + {/* Placeholder quote card, remove after API gets data */} + {/* {[ + { + name: 'Jirgna O', + quote: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Culpa iusto + nesciunt quasi praesentium non cupiditate ratione nihil. Perferendis, + velit ipsa illo, odit unde atque doloribus tempora distinctio facere + dolorem expedita error.`, + photo: + 'https://treetracker-production.nyc3.digitaloceanspaces.com/2019.07.10.18.32.42_b4fad89a-10b6-40cc-a134-0085d0e581d2_IMG_20190710_183201_8089920786231467340.jpg', + location: 'Shiramtunda, Tanzania', + }, + { + name: 'Samwell A', + quote: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Culpa iusto + nesciunt quasi praesentium non cupiditate ratione nihil. Perferendis, + velit ipsa illo, odit unde atque doloribus tempora distinctio facere + dolorem expedita error.`, + photo: + 'https://treetracker-production.nyc3.digitaloceanspaces.com/2018.11.20.12.11.07_e7a81cf4-2d37-45ee-9d5a-47bdfd7c43cc_IMG_20181120_121037_7990135604649410080.jpg', + location: 'Addis Ababa, Ethisa', + }, + ].map((planter, i) => ( */} + {associated_planters?.planters?.length > 0 ? ( + + {associated_planters?.planters + ?.sort((e1) => (e1.about ? -1 : 1)) + .map((planter, i) => ( + + + + ))} + + ) : ( + NO DATA YET + )} + + + + + + + {nextExtraIsEmbed && ( + document.getElementById('embed-logo-container')} + > + + + )} + + ); +} + +async function serverSideData(params) { + console.log('serverSideData'); + console.log(params); + const id = params.stakeholderid; + const stakeholder = await getStakeholderById(id); + const [featuredTrees, associated_planters, species] = await Promise.all([ + (async () => { + const data = await requestAPI(`/trees?stakeholder_id=${id}`); + return data; + })(), + (async () => { + const data = await requestAPI(`/planters?stakeholder_id=${id}`); + return data; + })(), + (async () => { + const data = await requestAPI(`/species?stakeholder_id=${id}`); + return data; + })(), + ]); + // const organization = await getOrganizationById(stakeholder.organization_id); + // const orgLinks = await getOrgLinks(organization.links); + const props = { + stakeholder: { + ...stakeholder, + featuredTrees, + associated_planters, + species, + // ...orgLinks, + }, + }; + return props; +} + +const getStaticProps = wrapper(async ({ params }) => { + const props = await serverSideData(params); + return { + props, + revalidate: Number(process.env.NEXT_CACHE_REVALIDATION_OVERRIDE) || 30, + }; +}); + +// eslint-disable-next-line +const getStaticPaths = async () => { + return { + paths: [], + fallback: 'blocking', + }; +}; + +export { getStaticProps, getStaticPaths }; diff --git a/src/pages/top.js b/src/pages/top.js index aa68b017a..f974f3be1 100644 --- a/src/pages/top.js +++ b/src/pages/top.js @@ -16,7 +16,12 @@ import Filter from 'components/common/Filter'; import { useFullscreen } from 'hooks/globalHooks'; import Search from 'images/search.svg'; import { useMapContext } from 'mapContext'; -import { getCaptures, getCountryLeaderboard, getFeaturedGrowers, getFeaturedTrees } from 'models/api'; +import { + getCaptures, + getCountryLeaderboard, + getFeaturedGrowers, + getFeaturedTrees, +} from 'models/api'; import * as utils from 'models/utils'; function Top(props) { @@ -82,168 +87,172 @@ function Top(props) { map.rerender(); } - return (<> - - - {!isFullscreen && false && ( - - - - )} + return ( + <> + + + {!isFullscreen && false && ( + + + + )} - {/* {!isFullscreen && } */} + {/* {!isFullscreen && } */} - - - , - name: 'Home', - url: '/', - }, - { - name: 'tree spotlight', - }, - ]} - /> - - - {captures?.length > 0 && ( - <> - - Featured captures this week - - {false && ( // going to be replaced by search filter component - ( - - ) - )} - - - - - )} - {organizations.length > 0 && ( - <> - - - Featured organizations this week - - `/organizations/${id}`} - color="primary" - planters={organizations} - isMobile={isFullscreen} + + , + name: 'Home', + url: '/', + }, + { + name: 'tree spotlight', + }, + ]} /> - - )} - {growers.length > 0 && ( - <> - - Featured planters this week - - `/planters/${id}`} - color="secondary" - planters={growers} - isMobile={isFullscreen} - /> - - )} - {wallets.length > 0 && ( - <> - Featured wallets this week - `/wallets/${id}`} - color="secondary" - planters={wallets} - isMobile={isFullscreen} /> - - )} - - Check out the global leaders in the tree planting effort - - [t.spacing(4, 0, 0, 0), t.spacing(8, 0, 0, 0)], - }} - > - { - setContinentTag(continent); + + {captures?.length > 0 && ( + <> + + Featured captures this week + + {false && ( // going to be replaced by search filter component + + + + )} + + + + + )} + {organizations.length > 0 && ( + <> + + + Featured organizations this week + + `/organizations/${id}`} + color="primary" + planters={organizations} + isMobile={isFullscreen} + /> + + )} + {growers.length > 0 && ( + <> + + Featured planters this week + + `/planters/${id}`} + color="secondary" + planters={growers} + isMobile={isFullscreen} + /> + + )} + {wallets.length > 0 && ( + <> + Featured wallets this week + `/wallets/${id}`} + color="secondary" + planters={wallets} + isMobile={isFullscreen} + /> + + )} + - - - - - {isFullscreen && ( - + > + Check out the global leaders in the tree planting effort + [t.spacing(4, 0, 0, 0), t.spacing(8, 0, 0, 0)], }} > - - Treetracker Spotlight - - - - )} - {isFullscreen && ( - - - Treetracker Spotlight + { + setContinentTag(continent); + }} + /> - - )} - ); + + + + {isFullscreen && ( + + + + Treetracker Spotlight + + + + )} + {isFullscreen && ( + + + Treetracker Spotlight + + + )} + + ); } async function serverSideData(params) { + console.log('top'); + console.log('serverSideData', params); const [captures, countries, growers, organizations, wallets] = await Promise.all([ getCaptures(), //