Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ft/climatemapped-improvements #991

Merged
merged 28 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1abd9dd
Add ability to select profile from admin
kelvinkipruto Nov 5, 2024
ade7d40
Fix profile
kelvinkipruto Nov 6, 2024
3b02e10
Merge branch 'main' into ft/climatemapped-improvements
kelvinkipruto Nov 6, 2024
1aca83b
Use profile id
kelvinkipruto Nov 7, 2024
18d69e6
Custom input for URL
kelvinkipruto Nov 7, 2024
7cd7bf9
Validate HURUMAp url
kelvinkipruto Nov 7, 2024
7cbea73
Fix dropdown search color and padding
kelvinkipruto Nov 7, 2024
de4fd47
Hide tutorial icon if tutorial is not enabled
kelvinkipruto Nov 7, 2024
a67c2be
regenerate snapshots
kelvinkipruto Nov 7, 2024
e3e5815
Use Set API
kelvinkipruto Nov 8, 2024
16ff009
Use named params
kelvinkipruto Nov 8, 2024
cede73e
Pass HURUMAPAPIURL
kelvinkipruto Nov 8, 2024
fa31657
fully use user provided api url
kelvinkipruto Nov 8, 2024
7d4e02c
Tiny fix
kelvinkipruto Nov 8, 2024
e014738
Merge branch 'main' into ft/climatemapped-improvements
kelvinkipruto Nov 8, 2024
d2c4463
Move root Geography
kelvinkipruto Nov 12, 2024
56ee358
Ensure URL is always valid
kelvinkipruto Nov 12, 2024
97b97e4
Improve validation
kelvinkipruto Nov 12, 2024
efbb8b4
Review fixes
kelvinkipruto Nov 12, 2024
d5e3d51
Refractor HURUmapURL
kelvinkipruto Nov 12, 2024
f5e7670
Use button in Explore nav
kelvinkipruto Nov 12, 2024
c030c49
Pass HurumapAPi URL
kelvinkipruto Nov 12, 2024
0ce09b9
Rename hurumapUrl
kelvinkipruto Nov 12, 2024
053e206
Rename BASE_URL to baseUrl
kelvinkipruto Nov 12, 2024
d06ca27
Use row to improve fields layout
kelvinkipruto Nov 13, 2024
2c7de42
Remove unused var
kelvinkipruto Nov 13, 2024
079ee9e
Merge branch 'main' into ft/climatemapped-improvements
kelvinkipruto Nov 13, 2024
f43a69a
Regenerate snapshots
kelvinkipruto Nov 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/climatemappedafrica/src/lib/data/blockify/hero.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
*/
export default async function hero(block, _api, _context, { hurumap }) {
const {
profileId,
profilePage,
rootGeography: { center, code, hasData: pinRootGeography },
} = hurumap ?? { rootGeography: {} };
Expand All @@ -20,7 +21,7 @@ export default async function hero(block, _api, _context, { hurumap }) {
country: "region",
};
const childLevel = childLevelMaps[level];
const { locations, preferredChildren } = await fetchProfile();
const { locations, preferredChildren } = await fetchProfile(profileId);
const preferredChildrenPerLevel = preferredChildren[level];
const { children } = geometries;
const preferredLevel =
Expand Down
9 changes: 7 additions & 2 deletions apps/climatemappedafrica/src/lib/data/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,12 @@ export async function getPageProps(api, context) {
const hurumapSettings = await api.findGlobal("settings-hurumap");
if (hurumapSettings?.enabled) {
// TODO(koech): Handle cases when fetching profile fails?
const profile = await fetchProfile();
const { page: hurumapPage, ...otherHurumapSettings } = hurumapSettings;
const {
page: hurumapPage,
profile: profileId,
...otherHurumapSettings
} = hurumapSettings;
const profile = await fetchProfile(profileId);
const { value: profilePage } = hurumapPage;
if (slug === profilePage.slug) {
variant = "explore";
Expand All @@ -136,6 +140,7 @@ export async function getPageProps(api, context) {
settings.hurumap = {
...otherHurumapSettings,
profile,
profileId,
profilePage,
};
}
Expand Down
10 changes: 8 additions & 2 deletions apps/climatemappedafrica/src/lib/hurumap/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import formatNumericalValue from "@/climatemappedafrica/utils/formatNumericalVal

const apiUrl = process.env.HURUMAP_API_URL || hurumap?.api?.url;

export async function fetchProfile() {
export async function fetchProfile(id) {
const { configuration } = await fetchJson(
new URL("/api/v1/profiles/1/?format=json", apiUrl),
new URL(`/api/v1/profiles/${id}/?format=json`, apiUrl),
);

const locations = configuration?.featured_locations?.map(
Expand All @@ -24,6 +24,12 @@ export async function fetchProfile() {
};
}

export async function fetchProfiles() {
const { results } = await fetchJson(new URL("/api/v1/profiles", apiUrl));
const profiles = results.map(({ name, id }) => ({ name, id }));
return profiles;
}

function formatProfileGeographyData(data, parent) {
if (!data) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ let cache = null;
let cacheExpiry = 0;

export default async function handler(req, res) {
const { id } = req.query;
if (req.method === "GET") {
const now = Date.now();

Expand All @@ -12,7 +13,7 @@ export default async function handler(req, res) {
}

try {
const result = await fetchProfile();
const result = await fetchProfile(id);
cache = result;
cacheExpiry = now + 5 * 60 * 1000;
return res.status(200).json(result);
Expand Down
25 changes: 25 additions & 0 deletions apps/climatemappedafrica/src/pages/api/hurumap/profiles/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { fetchProfiles } from "@/climatemappedafrica/lib/hurumap";

let cache = null;
let cacheExpiry = 0;

export default async function handler(req, res) {
if (req.method === "GET") {
const now = Date.now();

if (cache && now < cacheExpiry) {
return res.status(200).json(cache);
}

try {
const result = await fetchProfiles();
cache = result;
cacheExpiry = now + 5 * 60 * 1000;
return res.status(200).json(result);
} catch (err) {
return res.status(500).json(err.message);
}
}

return res.status(405).end();
}
108 changes: 108 additions & 0 deletions apps/climatemappedafrica/src/payload/fields/HURUMapURL/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { Button } from "payload/components/elements";
import {
Label,
TextInput,
useField,
useFormFields,
} from "payload/components/forms";
import { createElement, useState, useEffect } from "react";

function HURUMapURL(props) {
kelvinkipruto marked this conversation as resolved.
Show resolved Hide resolved
const {
admin: { description },
name,
path,
} = props;
const { value, setValue } = useField({ path });
const [isValid, setIsValid] = useState(null);
const [loading, setLoading] = useState(false);
const [isButtonDisabled, setIsButtonDisabled] = useState(true);
// eslint-disable-next-line no-unused-vars
const isHURUMapAPIURLValid = useFormFields(([_, dispatch]) => dispatch);

const validateURL = async () => {
if (!value) return;
setLoading(true);
try {
// For now we can use the profiles endpoint to check if the URL is validate
// Ideally we should have a dedicated endpoint for this
const response = await fetch(`${value}/profiles`);
setIsValid(response.ok);
isHURUMapAPIURLValid({
type: "UPDATE",
path: "isHURUMapAPIURLValid",
value: response.ok,
});
} catch (error) {
setIsValid(false);
isHURUMapAPIURLValid({
type: "UPDATE",
path: "isHURUMapAPIURLValid",
value: false,
});
} finally {
setLoading(false);
}
};

const isURLValid = (url) => {
const urlPattern = new RegExp(
"^(https?:\\/\\/)" + // protocol
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|" + // domain name
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved Hide resolved
"((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
"(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
"(\\#[-a-z\\d_]*)?$",
"i", // fragment locator
);
return !!urlPattern.test(url);
};

const handleInputChange = (e) => {
const newUrl = e.target.value;
setValue(newUrl);
setIsValid(null);
setIsButtonDisabled(!isURLValid(newUrl));
};

useEffect(() => {
setIsButtonDisabled(!isURLValid(value));
}, [value]);

return createElement(
"div",
{
id: "hurumap-url-wrapper",
},
createElement(TextInput, {
...props,
value,
onChange: handleInputChange,
}),
description &&
createElement(
"span",
{
className: "field-description",
},
description,
),
createElement(
Button,
{
type: "button",
onClick: () => validateURL(),
className: "btn btn--style-primary",
disabled: loading || isButtonDisabled,
},
loading ? "Checking..." : "Validate URL",
),
isValid !== null &&
createElement(Label, {
label: isValid ? "✓ URL is valid" : "✗ Invalid URL",
htmlFor: `field-${name}`,
}),
);
}

export default HURUMapURL;
27 changes: 19 additions & 8 deletions apps/climatemappedafrica/src/payload/fields/LocationSelect.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Select } from "payload/components/forms";
import {
Select,
useAllFormFields,
reduceFieldsToValues,
} from "payload/components/forms";
import { select } from "payload/dist/fields/validations";
import { createElement, useMemo } from "react";
import useSWR from "swr";
Expand All @@ -14,17 +18,24 @@ const getOptions = (locations) =>
value: location.code,
})) || [];

export async function validateLocation(value, { hasMany, required, t }) {
const data = await fetcher(`${apiUrl}/api/hurumap/profiles`);
const options = getOptions(data.locations);
export async function validateLocation(value, { data, hasMany, required, t }) {
const { profile } = data;
const res = await fetcher(`${apiUrl}/api/hurumap/profiles/${profile}`);
const options = getOptions(res.locations);
return select(value, { hasMany, options, required, t });
}

function LocationSelect(props) {
const { data } = useSWR(`${apiUrl}/api/hurumap/profiles`, fetcher, {
dedupingInterval: 60000,
revalidateOnFocus: false,
});
const [fields] = useAllFormFields();
const formData = reduceFieldsToValues(fields, true);
const { data } = useSWR(
`${apiUrl}/api/hurumap/profiles/${formData.profile}`,
fetcher,
{
dedupingInterval: 60000,
revalidateOnFocus: false,
},
);

const options = useMemo(
() =>
Expand Down
21 changes: 21 additions & 0 deletions apps/climatemappedafrica/src/payload/fields/ProfileSelect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Select } from "payload/components/forms";
import { createElement, useMemo } from "react";
import useSWR from "swr";

const apiUrl = process.env.PAYLOAD_PUBLIC_APP_URL;
const fetcher = (url) => fetch(url).then((res) => res.json());

function ProfileSelect(props) {
const { data } = useSWR(`${apiUrl}/api/hurumap/profiles`, fetcher, {
dedupingInterval: 60000,
revalidateOnFocus: false,
});

const options = useMemo(
() => data?.map(({ name, id }) => ({ label: name, value: id })) || [],
[data],
);
return createElement(Select, { ...props, options });
}

export default ProfileSelect;
16 changes: 16 additions & 0 deletions apps/climatemappedafrica/src/payload/globals/HURUMap/Profile.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
import ProfileSelect from "../../fields/ProfileSelect";

const Profile = {
label: "Profile",
fields: [
{
name: "profile",
type: "number",
label: {
en: "Profile to Use",
},
required: true,
hasMany: false,
admin: {
components: {
Field: ProfileSelect,
},
},
},
{
name: "page",
label: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ const RootGeography = {
en: "Root Geography",
},
type: "group",
admin: {
condition: (data) => Boolean(data?.profile),
},
fields: [
{
name: "code",
Expand Down
28 changes: 27 additions & 1 deletion apps/climatemappedafrica/src/payload/globals/HURUMap/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import HURUMapURL from "../../fields/HURUMapURL";

import DataPanels from "./DataPanels";
import Profile from "./Profile";
import RootGeography from "./RootGeography";
Expand All @@ -21,11 +23,35 @@ const HURUMap = {
type: "checkbox",
defaultValue: false,
},
{
name: "hurumapAPIURL",
label: "HURUMap API BASE URL",
type: "text",
admin: {
condition: (_, siblingData) => !!siblingData?.enableHURUMap,
components: {
Field: HURUMapURL,
},
description:
"The base URL for the HURUmap API. For example, https://hurumap.org/api/v1",
},
required: true,
},
{
name: "isHURUMapAPIURLValid",
type: "checkbox",
admin: {
hidden: true,
readOnly: true,
condition: (_, siblingData) => !!siblingData?.enableHURUMap,
},
},
{
type: "tabs",
tabs: [Profile, DataPanels, RootGeography, Tutorial],
admin: {
condition: (_, siblingData) => !!siblingData?.enableHURUMap,
condition: (_, siblingData) =>
!!siblingData?.enableHURUMap && !!siblingData?.isHURUMapAPIURLValid,
},
},
],
Expand Down
Loading