From 9ae5646a64827a363d522d5ff5e07c4a0af3e44f Mon Sep 17 00:00:00 2001 From: Magnus-SmariSma <20734986-Magnus-SmariSma@users.noreply.replit.com> Date: Thu, 20 Mar 2025 23:02:43 +0000 Subject: [PATCH] Add IUCN API integration and improve authentication. This includes adding IUCN API token management, displaying API version, and updating the UI to reflect both CITES and IUCN API statuses. Replit-Commit-Author: Agent Replit-Commit-Session-Id: e931b5ab-041b-42e7-baf1-50017869cef6 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/e19c6a51-7e4c-4bb8-a6a6-46dc00f0ec99/396f4320-2dbc-4ff6-85bf-f132094a1754.jpg --- client/src/components/api-status.tsx | 11 +- .../src/components/authentication-panel.tsx | 145 +++++++++++++----- client/src/lib/api.ts | 18 ++- client/src/pages/home.tsx | 16 +- 4 files changed, 142 insertions(+), 48 deletions(-) diff --git a/client/src/components/api-status.tsx b/client/src/components/api-status.tsx index 8d24970..41a8e07 100644 --- a/client/src/components/api-status.tsx +++ b/client/src/components/api-status.tsx @@ -11,6 +11,7 @@ interface ApiStatusProps { export default function ApiStatus({ citesToken }: ApiStatusProps) { const [iucnStatus, setIucnStatus] = useState<'checking' | 'connected' | 'error'>('checking'); const [citesStatus, setCitesStatus] = useState<'checking' | 'connected' | 'error'>('checking'); + const [iucnApiVersion, setIucnApiVersion] = useState(null); // Check CITES API status const { @@ -53,8 +54,10 @@ export default function ApiStatus({ citesToken }: ApiStatusProps) { setIucnStatus('checking'); } else if (iucnApiData?.connected) { setIucnStatus('connected'); + setIucnApiVersion((iucnApiData as any)?.apiVersion || 'v3'); // Use v3 as default if not specified } else { setIucnStatus('error'); + setIucnApiVersion(null); } }, [isIucnLoading, iucnApiData]); @@ -90,13 +93,17 @@ export default function ApiStatus({ citesToken }: ApiStatusProps) { variant={iucnStatus === 'connected' ? 'default' : 'destructive'} className="text-xs px-2 py-0.5" > - IUCN API: {iucnStatus === 'checking' ? 'Checking...' : iucnStatus === 'connected' ? 'Connected' : 'Not Connected'} + IUCN API: {iucnStatus === 'checking' + ? 'Checking...' + : iucnStatus === 'connected' + ? `Connected (${iucnApiVersion})` + : 'Not Connected'} {iucnStatus === 'connected' - ? 'IUCN Red List API is connected and working' + ? `IUCN Red List API ${iucnApiVersion} is connected and working` : iucnStatus === 'checking' ? 'Checking IUCN Red List API connection...' : iucnApiData?.message || 'IUCN Red List API connection issue. Check the API key.'} diff --git a/client/src/components/authentication-panel.tsx b/client/src/components/authentication-panel.tsx index 424ec70..b719aef 100644 --- a/client/src/components/authentication-panel.tsx +++ b/client/src/components/authentication-panel.tsx @@ -1,5 +1,5 @@ -import { useState } from "react"; -import { useMutation } from "@tanstack/react-query"; +import { useState, useEffect } from "react"; +import { useMutation, useQuery } from "@tanstack/react-query"; import { queryClient } from "@/lib/queryClient"; import { apiClient } from "@/lib/api"; import { useToast } from "@/hooks/use-toast"; @@ -7,6 +7,7 @@ import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; interface AuthenticationPanelProps { token: string; @@ -15,24 +16,43 @@ interface AuthenticationPanelProps { export default function AuthenticationPanel({ token, onSave }: AuthenticationPanelProps) { const { toast } = useToast(); - const [apiToken, setApiToken] = useState(token || ""); + const [citesToken, setCitesToken] = useState(token || ""); + const [iucnToken, setIucnToken] = useState(""); + const [activeTab, setActiveTab] = useState("cites"); + + // Fetch both tokens from the server + const { data: tokenData } = useQuery({ + queryKey: ['/api/token'], + queryFn: async () => { + return await apiClient.getTokens(); + }, + enabled: true, // Always fetch the tokens + }); + + // Update local state when token data is fetched + useEffect(() => { + if (tokenData) { + setCitesToken(tokenData.token || ""); + setIucnToken(tokenData.iucnToken || ""); + } + }, [tokenData]); const saveTokenMutation = useMutation({ - mutationFn: async (token: string) => { - return await apiClient.saveToken(token); + mutationFn: async (params: { citesToken: string; iucnToken?: string }) => { + return await apiClient.saveToken(params.citesToken, params.iucnToken); }, onSuccess: (response) => { if (response.success) { queryClient.invalidateQueries({ queryKey: ['/api/token'] }); toast({ title: "Success", - description: "API token has been saved successfully", + description: "API tokens have been saved successfully", }); onSave(); } else { toast({ title: "Error", - description: response.message || "Failed to save API token", + description: response.message || "Failed to save API tokens", variant: "destructive", }); } @@ -40,29 +60,36 @@ export default function AuthenticationPanel({ token, onSave }: AuthenticationPan onError: () => { toast({ title: "Error", - description: "Failed to save API token", + description: "Failed to save API tokens", variant: "destructive", }); } }); - const handleSaveToken = () => { - if (!apiToken.trim()) { + const handleSaveTokens = () => { + if (!citesToken.trim()) { toast({ title: "Validation Error", - description: "Please enter an API token", + description: "Please enter a CITES API token", variant: "destructive", }); return; } - saveTokenMutation.mutate(apiToken); + saveTokenMutation.mutate({ + citesToken, + iucnToken: iucnToken.trim() ? iucnToken : undefined + }); }; - const handleGetNewToken = () => { + const handleGetCitesToken = () => { window.open("https://api.speciesplus.net/", "_blank"); }; + const handleGetIucnToken = () => { + window.open("https://apiv4.iucnredlist.org/api/v4/docs", "_blank"); + }; + return ( @@ -81,37 +108,77 @@ export default function AuthenticationPanel({ token, onSave }: AuthenticationPan API Authentication -
- - setApiToken(e.target.value)} - /> -
-
+ + + + CITES+ API + IUCN Red List API + + + +
+ + setCitesToken(e.target.value)} + /> +
+ The CITES+ API provides species listings, distributions, and legislation information. +
+ +
+
+ + +
+ + setIucnToken(e.target.value)} + /> +
+ The IUCN Red List API provides conservation status, threats, habitats, and conservation measures. +
+ +
+
+
+ +
- -
-
- Your token is stored securely and is only sent to the CITES+ API. +
+ Both tokens are stored securely. +
diff --git a/client/src/lib/api.ts b/client/src/lib/api.ts index ad5d384..f0e1e4c 100644 --- a/client/src/lib/api.ts +++ b/client/src/lib/api.ts @@ -195,9 +195,13 @@ export const apiClient = { }, // Token management - async saveToken(token: string): Promise> { + async saveToken(token: string, iucnToken?: string): Promise> { try { - const response = await axios.post>("/api/token", { token, isActive: true }); + const response = await axios.post>("/api/token", { + token, + iucnToken, + isActive: true + }); return response.data; } catch (error: any) { return { @@ -207,6 +211,16 @@ export const apiClient = { } }, + async getTokens(): Promise { + try { + const response = await axios.get("/api/token"); + return response.data; + } catch (error) { + console.error("Failed to retrieve API tokens:", error); + return { token: null, iucnToken: null }; + } + }, + async getToken(): Promise { try { const response = await axios.get("/api/token"); diff --git a/client/src/pages/home.tsx b/client/src/pages/home.tsx index 566952e..1acaf64 100644 --- a/client/src/pages/home.tsx +++ b/client/src/pages/home.tsx @@ -14,9 +14,15 @@ export default function Home() { const [showAuthPanel, setShowAuthPanel] = useState(false); const [selectedSpecies, setSelectedSpecies] = useState(null); - // Get the active token - const { data: tokenData } = useQuery({ + // Get the active tokens + const { data: tokensData } = useQuery({ queryKey: ['/api/token'], + queryFn: async () => await apiClient.getTokens(), + initialData: { token: null, iucnToken: null }, + }); + + const { data: citesTokenData } = useQuery({ + queryKey: ['/api/token/cites'], queryFn: async () => await apiClient.getToken(), initialData: null, }); @@ -88,7 +94,7 @@ export default function Home() {

CITES+ Species Lookup

- +
@@ -120,13 +126,13 @@ export default function Home() {
{showAuthPanel && ( setShowAuthPanel(false)} /> )}