diff --git a/changelog/v0.0.36/app-details-api-keys-section.yaml b/changelog/v0.0.36/app-details-api-keys-section.yaml new file mode 100644 index 00000000..214a08fd --- /dev/null +++ b/changelog/v0.0.36/app-details-api-keys-section.yaml @@ -0,0 +1,5 @@ +changelog: + - type: FIX + issueLink: https://github.com/solo-io/solo-projects/issues/6881 + description: >- + Adds an API keys section to the App Details page. diff --git a/projects/ui/src/Apis/api-types.ts b/projects/ui/src/Apis/api-types.ts index f342a2f6..b3cc6376 100644 --- a/projects/ui/src/Apis/api-types.ts +++ b/projects/ui/src/Apis/api-types.ts @@ -108,6 +108,16 @@ export type App = { teamId: string; }; +export type ApiKey = { + id: string; + createdAt: string; + updatedAt: string; + deletedAt: string; + apiKey: string; + name: string; + metadata: Record; +}; + export type Team = { createdAt: string; description: string; diff --git a/projects/ui/src/Apis/gg_hooks.ts b/projects/ui/src/Apis/gg_hooks.ts index 9a83abf7..5b6b3143 100644 --- a/projects/ui/src/Apis/gg_hooks.ts +++ b/projects/ui/src/Apis/gg_hooks.ts @@ -4,6 +4,7 @@ import useSWRMutation from "swr/mutation"; import { AuthContext } from "../Context/AuthContext"; import { omitErrorMessageResponse } from "../Utility/utility"; import { + ApiKey, ApiProductDetails, ApiProductSummary, ApiVersion, @@ -19,15 +20,15 @@ import { import { fetchJSON, useMultiSwrWithAuth, useSwrWithAuth } from "./utility"; // -// Queries +// region Queries // -// User +// region User export function useGetCurrentUser() { return useSwrWithAuth("/me"); } -// Apps +// region Apps + API Keys export function useListAppsForTeam(team: Team) { return useSwrWithAuth(`/teams/${team.id}/apps`); } @@ -55,8 +56,11 @@ export function useListFlatAppsForTeamsOmitErrors(teams: Team[]) { export function useGetAppDetails(id?: string) { return useSwrWithAuth(`/apps/${id}`); } +export function useListApiKeysForApp(appId: string) { + return useSwrWithAuth(`/apps/${appId}/api-keys`); +} -// Teams +// region Teams const TEAMS_SWR_KEY = "teams"; export function useListTeams() { return useSwrWithAuth(`/teams`); @@ -68,7 +72,7 @@ export function useGetTeamDetails(id?: string) { return useSwrWithAuth(`/teams/${id}`); } -// Api Products +// region API Products export function useListApiProducts() { return useSwrWithAuth("/api-products"); } @@ -79,7 +83,7 @@ export function useGetApiProductVersions(id?: string) { return useSwrWithAuth(`/api-products/${id}/versions`); } -// Subscriptions +// region Subscriptions // this is an admin endpoint export function useListSubscriptionsForStatus(status: SubscriptionStatus) { const swrResponse = useSwrWithAuth( @@ -115,7 +119,7 @@ export function useListSubscriptionsForApps(apps: App[]) { } // -// Mutations +// region Mutations // const getLatestAuthHeaders = (latestAccessToken: string | undefined) => { @@ -129,7 +133,7 @@ const getLatestAuthHeaders = (latestAccessToken: string | undefined) => { type MutationWithArgs = { arg: T }; // ------------------------ // -// Create Team +// region Create Team type CreateTeamParams = MutationWithArgs<{ name: string; description: string }>; @@ -149,7 +153,7 @@ export function useCreateTeamMutation() { } // ------------------------ // -// Create Team Member +// region Create Team Member type AddTeamMemberParams = MutationWithArgs<{ email: string; teamId: string }>; @@ -169,7 +173,7 @@ export function useAddTeamMemberMutation() { } // ------------------------ // -// Remove Team Member +// region Remove Team Member type AdminRemoveTeamMemberParams = MutationWithArgs<{ teamId: string; @@ -194,7 +198,7 @@ export function useRemoveTeamMemberMutation() { } // ------------------------ // -// Create App +// region Create App type CreateAppParams = MutationWithArgs<{ name: string; description: string }>; @@ -220,7 +224,7 @@ export function useCreateAppMutation(teamId: string | undefined) { } // ------------------------ // -// Update App +// region Update App type UpdateAppParams = MutationWithArgs<{ appId: string; @@ -247,7 +251,7 @@ export function useUpdateAppMutation() { } // ------------------------ // -// Update Team +// region Update Team type UpdateTeamParams = MutationWithArgs<{ teamId: string; @@ -272,7 +276,7 @@ export function useUpdateTeamMutation() { } // ------------------------ // -// Create App and Subscription +// region Create App and Subscription type CreateAppAndSubscriptionParams = MutationWithArgs<{ appName: string; @@ -315,7 +319,7 @@ export function useCreateAppAndSubscriptionMutation() { } // ------------------------ // -// Create Subscription +// region Create Subscription type CreateSubscriptionParams = MutationWithArgs<{ apiProductId: string; @@ -344,7 +348,7 @@ export function useCreateSubscriptionMutation(appId: string) { } // -------------------------------- // -// (Admin) Approve/Reject Subscription +// region (Admin) Approve/Reject Subscription type UpdateSubscriptionParams = MutationWithArgs<{ subscription: Subscription; @@ -387,7 +391,7 @@ export function useAdminRejectSubscriptionMutation() { } // -------------------------------- // -// Delete Subscription +// region Delete Subscription export function useDeleteSubscriptionMutation() { const { latestAccessToken } = useContext(AuthContext); @@ -406,7 +410,7 @@ export function useDeleteSubscriptionMutation() { } // -------------------------------- // -// Delete Team +// region Delete Team type DeleteTeamParams = MutationWithArgs<{ teamId: string }>; @@ -424,7 +428,7 @@ export function useDeleteTeamMutation() { } // -------------------------------- // -// Delete App +// region Delete App type DeleteAppParams = MutationWithArgs<{ appId: string }>; @@ -440,3 +444,39 @@ export function useDeleteAppMutation() { }; return useSWRMutation(`delete-team`, deleteApp); } + +// -------------------------------- // +// region Create API Key + +type CreateApiKeyParams = MutationWithArgs<{ apiKeyName: string }>; + +export function useCreateApiKeyMutation(appId: string) { + const { latestAccessToken } = useContext(AuthContext); + const createApiKey = async (_: string, { arg }: CreateApiKeyParams) => { + return await fetchJSON(`/apps/${appId}/api-keys`, { + method: "POST", + headers: getLatestAuthHeaders(latestAccessToken), + body: JSON.stringify(arg), + }); + }; + return useSWRMutation( + `/apps/${appId}/api-keys`, + createApiKey + ); +} + +// -------------------------------- // +// region Delete API Key + +type DeleteApiKeyParams = MutationWithArgs<{ apiKeyId: string }>; + +export function useDeleteApiKeyMutation(appId: string) { + const { latestAccessToken } = useContext(AuthContext); + const deleteApiKey = async (_: string, { arg }: DeleteApiKeyParams) => { + await fetchJSON(`/api-keys/${arg.apiKeyId}`, { + method: "DELETE", + headers: getLatestAuthHeaders(latestAccessToken), + }); + }; + return useSWRMutation(`/apps/${appId}/api-keys`, deleteApiKey); +} diff --git a/projects/ui/src/Components/ApiDetails/gloo-gateway-components/ApiProductDetailsPageBody.tsx b/projects/ui/src/Components/ApiDetails/gloo-gateway-components/ApiProductDetailsPageBody.tsx index b3285d25..0ea6fdd2 100644 --- a/projects/ui/src/Components/ApiDetails/gloo-gateway-components/ApiProductDetailsPageBody.tsx +++ b/projects/ui/src/Components/ApiDetails/gloo-gateway-components/ApiProductDetailsPageBody.tsx @@ -7,7 +7,7 @@ import { ApiVersionSchema, } from "../../../Apis/api-types"; import { ContentWidthDiv } from "../../../Styles/ContentWidthHelpers"; -import { SimpleEmptyContent } from "../../Common/EmptyData"; +import { EmptyData } from "../../Common/EmptyData"; import DocsTabContent from "./DocsTab/DocsTabContent"; import SchemaTabContent from "./SchemaTab/SchemaTabContent"; @@ -71,7 +71,6 @@ export function ApiProductDetailsPageBody({ */} ) : ( - + You may add documentation for this API in the{" "} - + spec.versions[your-version].openapiMetadata.description {" "} - field of this{" "} - ApiProduct resource. - Markdown is supported. + field of this ApiProduct resource. Markdown is + supported. - + )} diff --git a/projects/ui/src/Components/ApiDetails/gloo-gateway-components/SchemaTab/SchemaTabContent.tsx b/projects/ui/src/Components/ApiDetails/gloo-gateway-components/SchemaTab/SchemaTabContent.tsx index 15bc5ae3..198596a6 100644 --- a/projects/ui/src/Components/ApiDetails/gloo-gateway-components/SchemaTab/SchemaTabContent.tsx +++ b/projects/ui/src/Components/ApiDetails/gloo-gateway-components/SchemaTab/SchemaTabContent.tsx @@ -1,20 +1,14 @@ -import { Box } from "@mantine/core"; -import { - ApiProductSummary, - ApiVersion, - ApiVersionSchema, -} from "../../../../Apis/api-types"; +import { Box, Code } from "@mantine/core"; +import { ApiVersion, ApiVersionSchema } from "../../../../Apis/api-types"; import { EmptyData } from "../../../Common/EmptyData"; import { ErrorBoundary } from "../../../Common/ErrorBoundary"; import { ApiSchemaDisplay } from "./ApiSchemaDisplay"; const SchemaTabContent = ({ - apiProduct, selectedApiVersion, apiProductVersions, apiVersionSpec, }: { - apiProduct: ApiProductSummary; selectedApiVersion: ApiVersion; apiProductVersions: ApiVersion[]; apiVersionSpec: ApiVersionSchema | undefined; @@ -22,9 +16,10 @@ const SchemaTabContent = ({ if (!apiProductVersions.length) { return ( - + + Add a version to the spec.versions field of this{" "} + ApiProduct for data to appear. + ); } @@ -36,9 +31,13 @@ const SchemaTabContent = ({ // There is a selected API version, but no schema. return ( - + + The schema was not returned for this ApiProduct version. +
+ Verify that your OpenApi spec was generated correctly in the + corresponding ApiDoc resource for this{" "} + Service. +
); } diff --git a/projects/ui/src/Components/Apis/EmptyApisPage.tsx b/projects/ui/src/Components/Apis/EmptyApisPage.tsx index 51dfd438..cd1dd706 100644 --- a/projects/ui/src/Components/Apis/EmptyApisPage.tsx +++ b/projects/ui/src/Components/Apis/EmptyApisPage.tsx @@ -5,7 +5,7 @@ import { AuthContext } from "../../Context/AuthContext"; import { apisImageURL } from "../../user_variables.tmplr"; import { BannerHeading } from "../Common/Banner/BannerHeading"; import { BannerHeadingTitle } from "../Common/Banner/BannerHeadingTitle"; -import { SimpleEmptyContent } from "../Common/EmptyData"; +import { EmptyData } from "../Common/EmptyData"; import { PageContainer } from "../Common/PageContainer"; import { StyledApisListMain } from "./gloo-mesh-gateway-components/ApisPage.style"; @@ -31,19 +31,15 @@ export const EmptyApisPageContent = () => { const { isLoggedIn } = useContext(AuthContext); if (!!isLoggedIn) - return ( - - No API Products have been created. - - ); + return No API Products have been created.; return ( - + To view API Products in private Portals, please log in.
To view API Products in public Portals, the Portal resource must have{" "} spec.visibility.public = true.
-
+ ); }; diff --git a/projects/ui/src/Components/Apis/gloo-gateway-components/ApisTab/ApisList.tsx b/projects/ui/src/Components/Apis/gloo-gateway-components/ApisTab/ApisList.tsx index 7e8d8f4d..51b8459f 100644 --- a/projects/ui/src/Components/Apis/gloo-gateway-components/ApisTab/ApisList.tsx +++ b/projects/ui/src/Components/Apis/gloo-gateway-components/ApisTab/ApisList.tsx @@ -78,8 +78,13 @@ export function ApisList({ if (apiProductsList === undefined) { return ; } + if (!apiProductsList.length) { + return ; + } if (!filteredApiProductsList.length) { - return ; + return ( + + ); } if (preferGridView) { return ( diff --git a/projects/ui/src/Components/Apis/gloo-mesh-gateway-components/ApisList.tsx b/projects/ui/src/Components/Apis/gloo-mesh-gateway-components/ApisList.tsx index 46d5f7c0..bd193c01 100644 --- a/projects/ui/src/Components/Apis/gloo-mesh-gateway-components/ApisList.tsx +++ b/projects/ui/src/Components/Apis/gloo-mesh-gateway-components/ApisList.tsx @@ -72,7 +72,7 @@ export function ApisList({ } if (!displayedApisList.length) { - return ; + return ; } return ( <> diff --git a/projects/ui/src/Components/Apps/Details/ApiKeysSection/AddApiKeysSubSection.tsx b/projects/ui/src/Components/Apps/Details/ApiKeysSection/AddApiKeysSubSection.tsx new file mode 100644 index 00000000..fffb9533 --- /dev/null +++ b/projects/ui/src/Components/Apps/Details/ApiKeysSection/AddApiKeysSubSection.tsx @@ -0,0 +1,91 @@ +import { Box, Input } from "@mantine/core"; +import { FormEvent, useEffect, useRef, useState } from "react"; +import toast from "react-hot-toast"; +import { di } from "react-magnetic-di"; +import { App } from "../../../../Apis/api-types"; +import { useCreateApiKeyMutation } from "../../../../Apis/gg_hooks"; +import { DetailsPageStyles } from "../../../../Styles/shared/DetailsPageStyles"; +import { Accordion } from "../../../Common/Accordion"; +import { Button } from "../../../Common/Button"; +import ViewCreatedApiKeyModal from "../Modals/ViewCreatedApiKeyModal"; + +const AddApiKeysSubSection = ({ + open, + onClose, + app, +}: { + open: boolean; + onClose: () => void; + app: App; +}) => { + di(useCreateApiKeyMutation); + + // + // Form Fields + // + const [formAppName, setFormAppName] = useState(""); + + // + // Form + // + const formRef = useRef(null); + const isFormDisabled = !open || !formAppName; + useEffect(() => { + // The form resets here when `open` changes. + setFormAppName(""); + }, [open]); + + // + // Form Submit + // + const [createdApiKey, setCreatedApiKey] = useState(""); + const { trigger: createApiKey } = useCreateApiKeyMutation(app.id); + const onSubmit = async (e?: FormEvent) => { + e?.preventDefault(); + const isValid = formRef.current?.reportValidity(); + if (!isValid || isFormDisabled) { + return; + } + const res = await toast.promise(createApiKey({ apiKeyName: formAppName }), { + error: "There was an error creating the API Key.", + loading: "Creating the API Key...", + success: "Created the API Key!", + }); + onClose(); + setCreatedApiKey(res.apiKey); + }; + + // + // Render + // + return ( + <> + + + + setFormAppName(e.target.value)} + /> + + + + + setCreatedApiKey("")} + /> + + ); +}; + +export default AddApiKeysSubSection; diff --git a/projects/ui/src/Components/Apps/Details/ApiKeysSection/AppApiKeysSection.tsx b/projects/ui/src/Components/Apps/Details/ApiKeysSection/AppApiKeysSection.tsx new file mode 100644 index 00000000..5a000b9d --- /dev/null +++ b/projects/ui/src/Components/Apps/Details/ApiKeysSection/AppApiKeysSection.tsx @@ -0,0 +1,135 @@ +import { Box, Flex } from "@mantine/core"; +import { useContext, useMemo, useState } from "react"; +import { di } from "react-magnetic-di"; +import { APIKey, App } from "../../../../Apis/api-types"; +import { + useListApiKeysForApp, + useListAppsForTeam, +} from "../../../../Apis/gg_hooks"; +import { AuthContext } from "../../../../Context/AuthContext"; +import { DetailsPageStyles } from "../../../../Styles/shared/DetailsPageStyles"; +import { GridCardStyles } from "../../../../Styles/shared/GridCard.style"; +import { UtilityStyles } from "../../../../Styles/shared/Utility.style"; +import { formatDateToMMDDYYYY } from "../../../../Utility/utility"; +import { Button } from "../../../Common/Button"; +import CustomPagination, { + pageOptions, + useCustomPagination, +} from "../../../Common/CustomPagination"; +import { EmptyData } from "../../../Common/EmptyData"; +import { Loading } from "../../../Common/Loading"; +import Table from "../../../Common/Table"; +import ToggleAddButton from "../../../Common/ToggleAddButton"; +import ConfirmDeleteApiKeyModal from "../Modals/ConfirmDeleteApiKeyModal"; +import AddApiKeysSubSection from "./AddApiKeysSubSection"; + +const AppApiKeysSection = ({ app }: { app: App }) => { + di(useListAppsForTeam); + const { isAdmin } = useContext(AuthContext); + const { isLoading, data: apiKeys } = useListApiKeysForApp(app.id); + const [showAddApiKeySubSection, setShowAddApiKeySubSection] = useState(false); + + const customPaginationData = useCustomPagination( + apiKeys ?? [], + pageOptions.table + ); + const { paginatedData } = customPaginationData; + + const [confirmDeleteApiKey, setConfirmDeleteApiKey] = useState(); + + const rows = useMemo(() => { + return paginatedData?.map((apiKey) => { + return ( + ( + + {apiKey.name} + {formatDateToMMDDYYYY(new Date(apiKey.createdAt))} + {/* {JSON.stringify(apiKey.metadata)} */} + + + + + + + ) ?? [] + ); + }); + }, [paginatedData]); + + if (isLoading) { + return ; + } + return ( + + + API Keys + {!isAdmin && ( + + setShowAddApiKeySubSection(!showAddApiKeySubSection) + } + /> + )} + + setShowAddApiKeySubSection(false)} + /> + {!apiKeys?.length ? ( + + + + ) : ( + + + + + + + + + + + + {rows} + + + + + +
NameCreated + + Delete + +
+ + + +
+
+
+
+ )} + setConfirmDeleteApiKey(undefined)} + /> +
+ ); +}; + +export default AppApiKeysSection; diff --git a/projects/ui/src/Components/Apps/Details/ApiSubscriptionsSection/AppApiSubscriptionsSection.tsx b/projects/ui/src/Components/Apps/Details/ApiSubscriptionsSection/AppApiSubscriptionsSection.tsx index 0dc43e45..ee2caa1e 100644 --- a/projects/ui/src/Components/Apps/Details/ApiSubscriptionsSection/AppApiSubscriptionsSection.tsx +++ b/projects/ui/src/Components/Apps/Details/ApiSubscriptionsSection/AppApiSubscriptionsSection.tsx @@ -43,8 +43,8 @@ const AppApiSubscriptionsSection = ({ /> {subscriptions.length === 0 && ( - - + + )} diff --git a/projects/ui/src/Components/Apps/Details/AppDetailsPageContent.tsx b/projects/ui/src/Components/Apps/Details/AppDetailsPageContent.tsx index 11575485..a4b2a4a2 100644 --- a/projects/ui/src/Components/Apps/Details/AppDetailsPageContent.tsx +++ b/projects/ui/src/Components/Apps/Details/AppDetailsPageContent.tsx @@ -1,19 +1,34 @@ -import { Box, Flex, Loader } from "@mantine/core"; +import { Box, Flex, Loader, Tooltip } from "@mantine/core"; +import { useMemo } from "react"; import { di } from "react-magnetic-di"; +import { NavLink } from "react-router-dom"; import { App } from "../../../Apis/api-types"; -import { useListSubscriptionsForApp } from "../../../Apis/gg_hooks"; +import { + useListSubscriptionsForApp, + useListTeams, +} from "../../../Apis/gg_hooks"; +import { Icon } from "../../../Assets/Icons"; +import { UtilityStyles } from "../../../Styles/shared/Utility.style"; +import { getTeamDetailsLink } from "../../../Utility/link-builders"; import { BannerHeading } from "../../Common/Banner/BannerHeading"; import { BannerHeadingTitle } from "../../Common/Banner/BannerHeadingTitle"; import { PageContainer } from "../../Common/PageContainer"; +import AppApiKeysSection from "./ApiKeysSection/AppApiKeysSection"; import AppApiSubscriptionsSection from "./ApiSubscriptionsSection/AppApiSubscriptionsSection"; import AppAuthenticationSection from "./AuthenticationSection/AppAuthenticationSection"; import EditAppButtonWithModal from "./EditAppButtonWithModal"; export const AppDetailsPageContent = ({ app }: { app: App }) => { - di(useListSubscriptionsForApp); + di(useListSubscriptionsForApp, useListTeams); const { isLoading: isLoadingSubscriptions, data: subscriptions } = useListSubscriptionsForApp(app.id); + const { data: teams } = useListTeams(); + const team = useMemo( + () => teams?.find((t) => t.id === app.teamId), + [teams, app] + ); + // Mock data for testing // app.idpClientId = "4df81266-f855-466d-8ded-699056780850"; // app.idpClientName = "test-idp"; @@ -27,6 +42,7 @@ export const AppDetailsPageContent = ({ app }: { app: App }) => { title={ } stylingTweaks={{ fontSize: "32px", lineHeight: "36px", @@ -34,7 +50,31 @@ export const AppDetailsPageContent = ({ app }: { app: App }) => { additionalInfo={} /> } - description={app.description} + description={ + <> + {!!team && !!team.name && ( + + + + + + {team?.name} + + + + + )} + {app.description} + + } breadcrumbItems={[ { label: "Home", link: "/" }, { label: "Apps", link: "/apps" }, @@ -44,6 +84,9 @@ export const AppDetailsPageContent = ({ app }: { app: App }) => { {appHasOAuthClient && } + + + {isLoadingSubscriptions || subscriptions === undefined ? ( ) : ( diff --git a/projects/ui/src/Components/Apps/Details/Modals/ConfirmDeleteApiKeyModal.tsx b/projects/ui/src/Components/Apps/Details/Modals/ConfirmDeleteApiKeyModal.tsx new file mode 100644 index 00000000..4d01a8e8 --- /dev/null +++ b/projects/ui/src/Components/Apps/Details/Modals/ConfirmDeleteApiKeyModal.tsx @@ -0,0 +1,61 @@ +import { Box, CloseButton, Flex } from "@mantine/core"; +import { FormEvent } from "react"; +import toast from "react-hot-toast"; +import { di } from "react-magnetic-di"; +import { useDeleteApiKeyMutation } from "../../../../Apis/gg_hooks"; +import { FormModalStyles } from "../../../../Styles/shared/FormModalStyles"; +import { Button } from "../../../Common/Button"; + +const ConfirmDeleteApiKeyModal = ({ + apiKeyId, + appId, + open, + onClose, +}: { + apiKeyId: string; + appId: string; + open: boolean; + onClose: () => void; +}) => { + di(useDeleteApiKeyMutation); + const { trigger: deleteApiKey } = useDeleteApiKeyMutation(appId); + const onConfirm = async (e?: FormEvent) => { + e?.preventDefault(); + await toast.promise(deleteApiKey({ apiKeyId }), { + error: (e) => "There was an error deleting the API Key. " + e, + loading: "Deleting the API Key...", + success: "Deleted the API Key!", + }); + onClose(); + }; + + // + // Render + // + return ( + + +
+ Delete API Key + + Are you sure that you want to delete this API Key? + +
+ +
+ + + + + + + +
+ ); +}; + +export default ConfirmDeleteApiKeyModal; diff --git a/projects/ui/src/Components/Apps/Details/Modals/ViewCreatedApiKeyModal.tsx b/projects/ui/src/Components/Apps/Details/Modals/ViewCreatedApiKeyModal.tsx new file mode 100644 index 00000000..7cedc78a --- /dev/null +++ b/projects/ui/src/Components/Apps/Details/Modals/ViewCreatedApiKeyModal.tsx @@ -0,0 +1,100 @@ +import { Alert, Box, CloseButton, Flex } from "@mantine/core"; +import { useEffect, useState } from "react"; +import toast from "react-hot-toast"; +import { Icon } from "../../../../Assets/Icons"; +import { FormModalStyles } from "../../../../Styles/shared/FormModalStyles"; +import { copyToClipboard } from "../../../../Utility/utility"; +import { Button } from "../../../Common/Button"; + +const ViewCreatedApiKeyModal = ({ + apiKey, + open, + onCloseModal, +}: { + apiKey: string; + open: boolean; + onCloseModal: () => void; +}) => { + const [hasCopiedKey, setHasCopiedKey] = useState(false); + + useEffect(() => { + // Reset state on close. + if (!open) { + setHasCopiedKey(false); + } + }, [open]); + + const handleOnClose = () => { + if (!hasCopiedKey) { + return; + } + onCloseModal(); + }; + + // + // Render + // + return ( + + + + Created API Key + + {hasCopiedKey && ( + + )} + + + + } + title="Warning!" + color="orange" + > + This API Key value will not be available later. Please click the API + Key value to copy and secure this value now. + + + + + + + + + + ); +}; + +export default ViewCreatedApiKeyModal; diff --git a/projects/ui/src/Components/Apps/PageContent/AppsList.tsx b/projects/ui/src/Components/Apps/PageContent/AppsList.tsx index 7e8ee4fe..18135daa 100644 --- a/projects/ui/src/Components/Apps/PageContent/AppsList.tsx +++ b/projects/ui/src/Components/Apps/PageContent/AppsList.tsx @@ -85,8 +85,11 @@ export function AppsList({ if (isLoading) { return ; } + if (!appsList?.length) { + return ; + } if (!filteredAppsList.length) { - return ; + return ; } return ( diff --git a/projects/ui/src/Components/Common/EmptyData.tsx b/projects/ui/src/Components/Common/EmptyData.tsx index ca9135e1..bde84dbe 100644 --- a/projects/ui/src/Components/Common/EmptyData.tsx +++ b/projects/ui/src/Components/Common/EmptyData.tsx @@ -1,6 +1,7 @@ import { css } from "@emotion/react"; import styled from "@emotion/styled"; -import { Box, Flex, Text } from "@mantine/core"; +import { Box } from "@mantine/core"; +import { borderRadiusConstants } from "../../Styles/constants"; const StyledEmptyContentOuter = styled.div( ({ theme }) => css` @@ -8,15 +9,16 @@ const StyledEmptyContentOuter = styled.div( justify-content: center; text-align: center; line-height: 2rem; - background-color: ${theme.marchGrey}; + background-color: white; + box-shadow: 1px 1px 5px ${theme.splashBlue}; + border: 1px solid ${theme.splashBlue}; + border-radius: ${borderRadiusConstants.small}; + margin-bottom: 30px; padding: 30px; ` ); -/** - * This is typically used for Empty sections with more custom content. - */ -export const SimpleEmptyContent = (props: { +export const EmptyData = (props: { children?: React.ReactNode; title?: React.ReactNode; }) => { @@ -26,46 +28,19 @@ export const SimpleEmptyContent = (props: { {props.title && ( {props.title} )} - {props.children} + {!!props.children && ( + + {props.children} + + )}
); }; - -/** - * This is typically used for Empty data for topics. - */ -export function EmptyData( - props: - | { - topic: string; - message?: string; - } - | { - topicMessageOverride: React.ReactNode; - } -) { - return ( - - - {"topicMessageOverride" in props ? ( - <>{props.topicMessageOverride} - ) : ( - <>No {props.topic} results were found - )} - - {"message" in props && !!props.message && ( - - {props.message} - - )} - - ); -} diff --git a/projects/ui/src/Components/Common/SubscriptionsList/SubscriptionsList.tsx b/projects/ui/src/Components/Common/SubscriptionsList/SubscriptionsList.tsx index 14328fb1..b401c7eb 100644 --- a/projects/ui/src/Components/Common/SubscriptionsList/SubscriptionsList.tsx +++ b/projects/ui/src/Components/Common/SubscriptionsList/SubscriptionsList.tsx @@ -35,7 +35,7 @@ const SubscriptionsList = ({ return null; } if (subscriptions.length === 0) { - return ; + return ; } return ( diff --git a/projects/ui/src/Components/Teams/Details/AppsSection/TeamAppsSection.tsx b/projects/ui/src/Components/Teams/Details/AppsSection/TeamAppsSection.tsx index 39515fd9..e7407a2a 100644 --- a/projects/ui/src/Components/Teams/Details/AppsSection/TeamAppsSection.tsx +++ b/projects/ui/src/Components/Teams/Details/AppsSection/TeamAppsSection.tsx @@ -86,7 +86,7 @@ const TeamAppsSection = ({ team }: { team: Team }) => { )} {!apps?.length ? ( - + ) : ( diff --git a/projects/ui/src/Components/Teams/Details/TeamDetailsPageContent.tsx b/projects/ui/src/Components/Teams/Details/TeamDetailsPageContent.tsx index e1494227..8c600b02 100644 --- a/projects/ui/src/Components/Teams/Details/TeamDetailsPageContent.tsx +++ b/projects/ui/src/Components/Teams/Details/TeamDetailsPageContent.tsx @@ -1,6 +1,7 @@ import { Box, Flex } from "@mantine/core"; import { useContext } from "react"; import { Team } from "../../../Apis/api-types"; +import { Icon } from "../../../Assets/Icons"; import { AuthContext } from "../../../Context/AuthContext"; import { BannerHeading } from "../../Common/Banner/BannerHeading"; import { BannerHeadingTitle } from "../../Common/Banner/BannerHeadingTitle"; @@ -17,6 +18,7 @@ const TeamDetailsPageContent = ({ team }: { team: Team }) => { title={ } stylingTweaks={{ fontSize: "32px", lineHeight: "36px", diff --git a/projects/ui/src/Components/Teams/Details/UsersSection/TeamUsersSection.tsx b/projects/ui/src/Components/Teams/Details/UsersSection/TeamUsersSection.tsx index 12bf55a9..fa1559a6 100644 --- a/projects/ui/src/Components/Teams/Details/UsersSection/TeamUsersSection.tsx +++ b/projects/ui/src/Components/Teams/Details/UsersSection/TeamUsersSection.tsx @@ -100,7 +100,8 @@ const TeamUsersSection = ({ team }: { team: Team }) => { /> {!members?.length ? ( - + {/* We never should get here, since the user must be a member. */} + ) : ( diff --git a/projects/ui/src/Components/Teams/TeamsList/TeamsList.tsx b/projects/ui/src/Components/Teams/TeamsList/TeamsList.tsx index 119914c6..0b711ce4 100644 --- a/projects/ui/src/Components/Teams/TeamsList/TeamsList.tsx +++ b/projects/ui/src/Components/Teams/TeamsList/TeamsList.tsx @@ -15,8 +15,8 @@ export function TeamsList() { if (teamsList === undefined || isLoading) { return ; } - if (!teamsList.length) { - return ; + if (!teamsList?.length) { + return ; } return ( diff --git a/projects/ui/src/Components/UsagePlans/UsagePlanList/APIUsagePlansList.tsx b/projects/ui/src/Components/UsagePlans/UsagePlanList/APIUsagePlansList.tsx index 0cd03c09..0808d446 100644 --- a/projects/ui/src/Components/UsagePlans/UsagePlanList/APIUsagePlansList.tsx +++ b/projects/ui/src/Components/UsagePlans/UsagePlanList/APIUsagePlansList.tsx @@ -46,7 +46,7 @@ export function APIUsagePlansList() { )) ) : ( - + )} ); diff --git a/projects/ui/src/Styles/colors.ts b/projects/ui/src/Styles/colors.ts index 06513481..d8c35710 100644 --- a/projects/ui/src/Styles/colors.ts +++ b/projects/ui/src/Styles/colors.ts @@ -43,6 +43,8 @@ const baseColors = { const colorMap = { ...baseColors, + januaryGreyDark1: Color(baseColors.januaryGrey).darken(0.01).hex(), + marchGreyDark3: Color(baseColors.marchGrey).darken(0.03).hex(), marchGreyDark5: Color(baseColors.marchGrey).darken(0.05).hex(), marchGreyDark10: Color(baseColors.marchGrey).darken(0.1).hex(), diff --git a/projects/ui/src/Styles/global-styles/mantine-overrides.style.ts b/projects/ui/src/Styles/global-styles/mantine-overrides.style.ts index e020250e..bacc83a9 100644 --- a/projects/ui/src/Styles/global-styles/mantine-overrides.style.ts +++ b/projects/ui/src/Styles/global-styles/mantine-overrides.style.ts @@ -63,4 +63,9 @@ export const mantineGlobalStyles = css` } } } + + code.mantine-Code-root { + background-color: ${colors.januaryGreyDark1}; + white-space: nowrap; + } `;