diff --git a/internal/portal/src/app.tsx b/internal/portal/src/app.tsx index 5c775984..be14f512 100644 --- a/internal/portal/src/app.tsx +++ b/internal/portal/src/app.tsx @@ -15,6 +15,7 @@ import CreateDestination from "./scenes/CreateDestination/CreateDestination"; type ApiClient = { fetch: (path: string, init?: RequestInit) => Promise; + fetchRoot: (path: string, init?: RequestInit) => Promise; }; // API error response from the server @@ -96,32 +97,42 @@ function AuthenticatedApp({ tenant: TenantResponse; token: string; }) { + const handleResponse = async (res: Response) => { + if (!res.ok) { + let error: ApiError; + try { + const data = await res.json(); + error = new ApiError( + data.message || res.statusText, + data.status || res.status, + Array.isArray(data.data) ? data.data : undefined, + ); + } catch (e) { + error = new ApiError(res.statusText, res.status); + } + throw error; + } + return res.json(); + }; + + const makeHeaders = (init?: RequestInit) => ({ + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + ...init?.headers, + }); + const apiClient: ApiClient = { fetch: (path: string, init?: RequestInit) => { return fetch(`/api/v1/tenants/${tenant.id}/${path}`, { ...init, - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - ...init?.headers, - }, - }).then(async (res) => { - if (!res.ok) { - let error: ApiError; - try { - const data = await res.json(); - error = new ApiError( - data.message || res.statusText, - data.status || res.status, - Array.isArray(data.data) ? data.data : undefined, - ); - } catch (e) { - error = new ApiError(res.statusText, res.status); - } - throw error; - } - return res.json(); - }); + headers: makeHeaders(init), + }).then(handleResponse); + }, + fetchRoot: (path: string, init?: RequestInit) => { + return fetch(`/api/v1/${path}`, { + ...init, + headers: makeHeaders(init), + }).then(handleResponse); }, }; diff --git a/internal/portal/src/common/RetryDeliveryButton/RetryDeliveryButton.tsx b/internal/portal/src/common/RetryDeliveryButton/RetryDeliveryButton.tsx index c46d48d0..ffed12ef 100644 --- a/internal/portal/src/common/RetryDeliveryButton/RetryDeliveryButton.tsx +++ b/internal/portal/src/common/RetryDeliveryButton/RetryDeliveryButton.tsx @@ -5,7 +5,8 @@ import { showToast } from "../Toast/Toast"; import { ApiContext, formatError } from "../../app"; interface RetryDeliveryButtonProps { - attemptId: string; + eventId: string; + destinationId: string; disabled: boolean; loading: boolean; completed: (success: boolean) => void; @@ -14,7 +15,8 @@ interface RetryDeliveryButtonProps { } const RetryDeliveryButton: React.FC = ({ - attemptId, + eventId, + destinationId, disabled, loading, completed, @@ -29,8 +31,12 @@ const RetryDeliveryButton: React.FC = ({ e.stopPropagation(); setRetrying(true); try { - await apiClient.fetch(`attempts/${attemptId}/retry`, { + await apiClient.fetchRoot("retry", { method: "POST", + body: JSON.stringify({ + event_id: eventId, + destination_id: destinationId, + }), }); showToast("success", "Retry successful."); completed(true); @@ -41,7 +47,7 @@ const RetryDeliveryButton: React.FC = ({ setRetrying(false); }, - [apiClient, attemptId, completed], + [apiClient, eventId, destinationId, completed], ); return ( diff --git a/internal/portal/src/common/RetryEventButton/RetryEventButton.tsx b/internal/portal/src/common/RetryEventButton/RetryEventButton.tsx deleted file mode 100644 index c6435488..00000000 --- a/internal/portal/src/common/RetryEventButton/RetryEventButton.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React, { useCallback, useContext, useState, MouseEvent } from "react"; -import Button from "../Button/Button"; -import { ReplayIcon } from "../Icons"; -import { showToast } from "../Toast/Toast"; -import { ApiContext, formatError } from "../../app"; - -interface RetryEventButtonProps { - eventId: string; - destinationId: string; - disabled: boolean; - loading: boolean; - completed: (success: boolean) => void; -} - -const RetryEventButton: React.FC = ({ - eventId, - destinationId, - disabled, - loading, - completed, -}) => { - const apiClient = useContext(ApiContext); - const [retrying, setRetrying] = useState(false); - - const retryEvent = useCallback( - async (e: MouseEvent) => { - e.stopPropagation(); - setRetrying(true); - try { - await apiClient.fetch( - `destinations/${destinationId}/events/${eventId}/retry`, - { - method: "POST", - }, - ); - showToast("success", "Retry successful."); - completed(true); - } catch (error: unknown) { - showToast("error", "Retry failed. " + formatError(error)); - completed(false); - } - - setRetrying(false); - }, - [apiClient, destinationId, eventId, completed], - ); - - return ( - - ); -}; - -export default RetryEventButton; diff --git a/internal/portal/src/destination-types.tsx b/internal/portal/src/destination-types.tsx index 180dc8f5..80bf1082 100644 --- a/internal/portal/src/destination-types.tsx +++ b/internal/portal/src/destination-types.tsx @@ -1,11 +1,17 @@ +import { useContext } from "react"; import useSWR from "swr"; import { DestinationTypeReference } from "./typings/Destination"; +import { ApiContext } from "./app"; export function useDestinationTypes(): Record< string, DestinationTypeReference > { - const { data } = useSWR("destination-types"); + const apiClient = useContext(ApiContext); + const { data } = useSWR( + "destination-types", + (path: string) => apiClient.fetchRoot(path), + ); if (!data) { return {}; } diff --git a/internal/portal/src/scenes/CreateDestination/CreateDestination.tsx b/internal/portal/src/scenes/CreateDestination/CreateDestination.tsx index 4948f2fd..524b7c9f 100644 --- a/internal/portal/src/scenes/CreateDestination/CreateDestination.tsx +++ b/internal/portal/src/scenes/CreateDestination/CreateDestination.tsx @@ -12,9 +12,10 @@ import { useNavigate } from "react-router-dom"; import { useContext, useEffect, useState } from "react"; import { ApiContext, formatError } from "../../app"; import { showToast } from "../../common/Toast/Toast"; -import useSWR, { mutate } from "swr"; +import { mutate } from "swr"; import TopicPicker from "../../common/TopicPicker/TopicPicker"; import { DestinationTypeReference, Filter } from "../../typings/Destination"; +import { useDestinationTypes } from "../../destination-types"; import DestinationConfigFields from "../../common/DestinationConfigFields/DestinationConfigFields"; import FilterField from "../../common/FilterField/FilterField"; import { FilterSyntaxGuide } from "../../common/FilterSyntaxGuide/FilterSyntaxGuide"; @@ -30,7 +31,7 @@ type Step = { FormFields: (props: { defaultValue: Record; onChange: (value: Record) => void; - destinations?: DestinationTypeReference[]; + destinationTypes?: Record; }) => React.ReactNode; action: string; }; @@ -96,17 +97,17 @@ const DESTINATION_TYPE_STEP: Step = { return true; }, FormFields: ({ - destinations, + destinationTypes, defaultValue, onChange, }: { - destinations?: DestinationTypeReference[]; + destinationTypes?: Record; defaultValue: Record; onChange?: (value: Record) => void; }) => (
- {destinations?.map((destination) => ( + {Object.values(destinationTypes ?? {}).map((destination) => (