diff --git a/client/src/components/results-container.tsx b/client/src/components/results-container.tsx index 78c5eaa..775d890 100644 --- a/client/src/components/results-container.tsx +++ b/client/src/components/results-container.tsx @@ -1,6 +1,16 @@ import { useState, useEffect } from "react"; import { useMutation, useQuery } from "@tanstack/react-query"; -import { apiClient, CITES_API_ENDPOINTS, CitesLegislation, Distribution } from "@/lib/api"; +import { + apiClient, + CITES_API_ENDPOINTS, + IUCN_API_ENDPOINTS, + CitesLegislation, + Distribution, + IucnSpeciesResponse, + IucnThreatsResponse, + IucnHabitatsResponse, + IucnMeasuresResponse +} from "@/lib/api"; import { useToast } from "@/hooks/use-toast"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; @@ -57,6 +67,47 @@ export default function ResultsContainer({ }, enabled: !!currentSpecies?.id, }); + + // IUCN API Queries + const { data: iucnData, isLoading: isLoadingIucnSpecies } = useQuery({ + queryKey: ['/api/iucn/species', currentSpecies?.full_name], + queryFn: async () => { + if (!currentSpecies?.full_name) return null; + const response = await apiClient.getIucnSpeciesByName(currentSpecies.full_name); + return response.success ? response.data : null; + }, + enabled: !!currentSpecies?.full_name, + }); + + const { data: iucnThreats, isLoading: isLoadingIucnThreats } = useQuery({ + queryKey: ['/api/iucn/threats', currentSpecies?.full_name], + queryFn: async () => { + if (!currentSpecies?.full_name) return null; + const response = await apiClient.getIucnThreats(currentSpecies.full_name); + return response.success ? response.data : null; + }, + enabled: !!currentSpecies?.full_name, + }); + + const { data: iucnHabitats, isLoading: isLoadingIucnHabitats } = useQuery({ + queryKey: ['/api/iucn/habitats', currentSpecies?.full_name], + queryFn: async () => { + if (!currentSpecies?.full_name) return null; + const response = await apiClient.getIucnHabitats(currentSpecies.full_name); + return response.success ? response.data : null; + }, + enabled: !!currentSpecies?.full_name, + }); + + const { data: iucnMeasures, isLoading: isLoadingIucnMeasures } = useQuery({ + queryKey: ['/api/iucn/measures', currentSpecies?.full_name], + queryFn: async () => { + if (!currentSpecies?.full_name) return null; + const response = await apiClient.getIucnMeasures(currentSpecies.full_name); + return response.success ? response.data : null; + }, + enabled: !!currentSpecies?.full_name, + }); const saveSpeciesMutation = useMutation({ mutationFn: async () => { diff --git a/client/src/components/species-tabs.tsx b/client/src/components/species-tabs.tsx index 597f959..c0edf79 100644 --- a/client/src/components/species-tabs.tsx +++ b/client/src/components/species-tabs.tsx @@ -9,6 +9,10 @@ interface SpeciesTabsProps { citesLegislation: any; distributions: any; references: any; + iucnData: any; + iucnThreats: any; + iucnHabitats: any; + iucnMeasures: any; apiResponse: string; isLoading: boolean; } @@ -18,6 +22,10 @@ export default function SpeciesTabs({ citesLegislation, distributions, references, + iucnData, + iucnThreats, + iucnHabitats, + iucnMeasures, apiResponse, isLoading }: SpeciesTabsProps) { @@ -56,6 +64,7 @@ export default function SpeciesTabs({ Overview CITES Legislation Distribution + Conservation Status References API Response @@ -266,6 +275,125 @@ export default function SpeciesTabs({ + {/* Conservation Status Tab */} + +
+
+

IUCN Red List Status

+ {iucnData?.result && iucnData.result.length > 0 ? ( +
+
+
+ + {iucnData.result[0].category === 'EX' ? 'Extinct' : + iucnData.result[0].category === 'EW' ? 'Extinct in the Wild' : + iucnData.result[0].category === 'CR' ? 'Critically Endangered' : + iucnData.result[0].category === 'EN' ? 'Endangered' : + iucnData.result[0].category === 'VU' ? 'Vulnerable' : + iucnData.result[0].category === 'NT' ? 'Near Threatened' : + iucnData.result[0].category === 'LC' ? 'Least Concern' : + iucnData.result[0].category === 'DD' ? 'Data Deficient' : + iucnData.result[0].category === 'NE' ? 'Not Evaluated' : + iucnData.result[0].category} + +
+

Assessed: {iucnData.result[0].assessment_date}

+

Criteria: {iucnData.result[0].criteria || 'Not specified'}

+

+ Population trend: { + iucnData.result[0].population_trend === 'decreasing' ? 'Decreasing ↓' : + iucnData.result[0].population_trend === 'increasing' ? 'Increasing ↑' : + iucnData.result[0].population_trend === 'stable' ? 'Stable →' : + 'Unknown' + } +

+
+ ) : ( +

No IUCN Red List data available for this species.

+ )} +
+
+

Habitat Types

+ {iucnHabitats?.result && iucnHabitats.result.length > 0 ? ( +
    + {iucnHabitats.result.map((habitat: any, index: number) => ( +
  • + + + {habitat.habitat} + {habitat.suitability && ` (${habitat.suitability})`} + {habitat.majorimportance === 'Yes' && ' - Major importance'} + +
  • + ))} +
+ ) : ( +

No habitat information available.

+ )} +
+
+ +
+

Threats

+ {iucnThreats?.result && iucnThreats.result.length > 0 ? ( +
+ + + + + + + + + + + + {iucnThreats.result.map((threat: any, index: number) => ( + + + + + + + + ))} + +
ThreatTimingScopeSeverityScore
{threat.title}{threat.timing || 'Unknown'}{threat.scope || 'Unknown'}{threat.severity || 'Unknown'}{threat.score || 'Unknown'}
+
+ ) : ( +

No threat information available.

+ )} +
+ +
+

Conservation Measures

+ {iucnMeasures?.result && iucnMeasures.result.length > 0 ? ( + + ) : ( +

No conservation measures information available.

+ )} +
+
+ {/* References Tab */}
diff --git a/server/routes.ts b/server/routes.ts index 7a40615..e440b28 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -1,4 +1,4 @@ -import type { Express, Request, Response } from "express"; +import type { Express, Request, Response, NextFunction } from "express"; import { createServer, type Server } from "http"; import { storage } from "./storage"; import axios from "axios"; @@ -7,6 +7,7 @@ import { ZodError } from "zod"; import { fromZodError } from "zod-validation-error"; const CITES_BASE_URL = "https://api.speciesplus.net/api/v1"; +const IUCN_BASE_URL = "https://apiv3.iucnredlist.org/api/v3"; export async function registerRoutes(app: Express): Promise { // API Token routes @@ -212,6 +213,191 @@ export async function registerRoutes(app: Express): Promise { } }); + // IUCN Red List API routes + app.get("/api/iucn/species", async (req: Request, res: Response) => { + try { + const { name } = req.query; + + if (!name) { + return res.status(400).json({ + success: false, + message: "Species name is required" + }); + } + + const apiKey = process.env.IUCN_API_KEY; + if (!apiKey) { + return res.status(401).json({ + success: false, + message: "IUCN API key is not configured" + }); + } + + try { + const response = await axios.get(`${IUCN_BASE_URL}/species/name/${name}`, { + params: { token: apiKey } + }); + + res.json({ + success: true, + data: response.data + }); + } catch (error) { + if (axios.isAxiosError(error) && error.response) { + return res.status(error.response.status).json({ + success: false, + message: error.response.data?.message || "Error from IUCN Red List API", + status: error.response.status + }); + } + throw error; + } + } catch (error) { + res.status(500).json({ + success: false, + message: "Failed to retrieve IUCN species data" + }); + } + }); + + app.get("/api/iucn/threats", async (req: Request, res: Response) => { + try { + const { name } = req.query; + + if (!name) { + return res.status(400).json({ + success: false, + message: "Species name is required" + }); + } + + const apiKey = process.env.IUCN_API_KEY; + if (!apiKey) { + return res.status(401).json({ + success: false, + message: "IUCN API key is not configured" + }); + } + + try { + const response = await axios.get(`${IUCN_BASE_URL}/threats/species/name/${name}`, { + params: { token: apiKey } + }); + + res.json({ + success: true, + data: response.data + }); + } catch (error) { + if (axios.isAxiosError(error) && error.response) { + return res.status(error.response.status).json({ + success: false, + message: error.response.data?.message || "Error from IUCN Red List API", + status: error.response.status + }); + } + throw error; + } + } catch (error) { + res.status(500).json({ + success: false, + message: "Failed to retrieve IUCN threats data" + }); + } + }); + + app.get("/api/iucn/habitats", async (req: Request, res: Response) => { + try { + const { name } = req.query; + + if (!name) { + return res.status(400).json({ + success: false, + message: "Species name is required" + }); + } + + const apiKey = process.env.IUCN_API_KEY; + if (!apiKey) { + return res.status(401).json({ + success: false, + message: "IUCN API key is not configured" + }); + } + + try { + const response = await axios.get(`${IUCN_BASE_URL}/habitats/species/name/${name}`, { + params: { token: apiKey } + }); + + res.json({ + success: true, + data: response.data + }); + } catch (error) { + if (axios.isAxiosError(error) && error.response) { + return res.status(error.response.status).json({ + success: false, + message: error.response.data?.message || "Error from IUCN Red List API", + status: error.response.status + }); + } + throw error; + } + } catch (error) { + res.status(500).json({ + success: false, + message: "Failed to retrieve IUCN habitats data" + }); + } + }); + + app.get("/api/iucn/measures", async (req: Request, res: Response) => { + try { + const { name } = req.query; + + if (!name) { + return res.status(400).json({ + success: false, + message: "Species name is required" + }); + } + + const apiKey = process.env.IUCN_API_KEY; + if (!apiKey) { + return res.status(401).json({ + success: false, + message: "IUCN API key is not configured" + }); + } + + try { + const response = await axios.get(`${IUCN_BASE_URL}/measures/species/name/${name}`, { + params: { token: apiKey } + }); + + res.json({ + success: true, + data: response.data + }); + } catch (error) { + if (axios.isAxiosError(error) && error.response) { + return res.status(error.response.status).json({ + success: false, + message: error.response.data?.message || "Error from IUCN Red List API", + status: error.response.status + }); + } + throw error; + } + } catch (error) { + res.status(500).json({ + success: false, + message: "Failed to retrieve IUCN conservation measures data" + }); + } + }); + const httpServer = createServer(app); return httpServer; }