diff --git a/src/app/[orgId]/settings/clients/machine/[clientId]/credentials/page.tsx b/src/app/[orgId]/settings/clients/machine/[clientId]/credentials/page.tsx index 7de4361e..a2894637 100644 --- a/src/app/[orgId]/settings/clients/machine/[clientId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/clients/machine/[clientId]/credentials/page.tsx @@ -1,27 +1,37 @@ "use client"; -import RegenerateCredentialsModal from "@app/components/RegenerateCredentialsModal"; -import { SecurityFeaturesAlert } from "@app/components/SecurityFeaturesAlert"; +import { useState } from "react"; import { SettingsContainer, SettingsSection, SettingsSectionBody, SettingsSectionDescription, + SettingsSectionFooter, SettingsSectionHeader, SettingsSectionTitle } from "@app/components/Settings"; import { Button } from "@app/components/ui/button"; -import { useClientContext } from "@app/hooks/useClientContext"; +import { createApiClient, formatAxiosError } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; +import { toast } from "@app/hooks/useToast"; +import { useParams, useRouter } from "next/navigation"; +import { useTranslations } from "next-intl"; +import { PickClientDefaultsResponse } from "@server/routers/client"; +import { useClientContext } from "@app/hooks/useClientContext"; +import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext"; import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext"; -import { toast } from "@app/hooks/useToast"; -import { createApiClient } from "@app/lib/api"; import { build } from "@server/build"; -import { PickClientDefaultsResponse } from "@server/routers/client"; -import { useTranslations } from "next-intl"; -import { useParams, useRouter } from "next/navigation"; -import { useState } from "react"; +import { SecurityFeaturesAlert } from "@app/components/SecurityFeaturesAlert"; +import { + InfoSection, + InfoSectionContent, + InfoSections, + InfoSectionTitle +} from "@app/components/InfoSection"; +import CopyToClipboard from "@app/components/CopyToClipboard"; +import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; +import { InfoIcon } from "lucide-react"; export default function CredentialsPage() { const { env } = useEnvContext(); @@ -34,6 +44,11 @@ export default function CredentialsPage() { const [modalOpen, setModalOpen] = useState(false); const [clientDefaults, setClientDefaults] = useState(null); + const [currentOlmId, setCurrentOlmId] = useState(null); + const [regeneratedSecret, setRegeneratedSecret] = useState( + null + ); + const [showCredentialsAlert, setShowCredentialsAlert] = useState(false); const { licenseStatus, isUnlocked } = useLicenseStatusContext(); const subscription = useSubscriptionStatusContext(); @@ -46,76 +61,150 @@ export default function CredentialsPage() { }; const handleConfirmRegenerate = async () => { - const res = await api.get(`/org/${orgId}/pick-client-defaults`); - if (res && res.status === 200) { - const data = res.data.data; + try { + const res = await api.get(`/org/${orgId}/pick-client-defaults`); + if (res && res.status === 200) { + const data = res.data.data; - const rekeyRes = await api.post( - `/re-key/${client?.clientId}/regenerate-client-secret`, - { - secret: data.olmSecret + const rekeyRes = await api.post( + `/re-key/${client?.clientId}/regenerate-client-secret`, + { + secret: data.olmSecret + } + ); + + if (rekeyRes && rekeyRes.status === 200) { + const rekeyData = rekeyRes.data.data; + if (rekeyData && rekeyData.olmId) { + setCurrentOlmId(rekeyData.olmId); + setRegeneratedSecret(data.olmSecret); + setClientDefaults({ + ...data, + olmId: rekeyData.olmId + }); + setShowCredentialsAlert(true); + } } - ); - if (rekeyRes && rekeyRes.status === 200) { - const rekeyData = rekeyRes.data.data; - setClientDefaults({ - ...data, - olmId: rekeyData.olmId, + toast({ + title: t("credentialsSaved"), + description: t("credentialsSavedDescription") }); } - + } catch (error) { toast({ - title: t("credentialsSaved"), - description: t("credentialsSavedDescription") + variant: "destructive", + title: t("error") || "Error", + description: + formatAxiosError(error) || + t("credentialsRegenerateError") || + "Failed to regenerate credentials" }); - - router.refresh(); } }; - const getCredentials = () => { - if (clientDefaults) { - return { - Id: clientDefaults.olmId, - Secret: clientDefaults.olmSecret - }; - } - return undefined; + const getConfirmationString = () => { + return client?.name || client?.clientId?.toString() || "My client"; }; + const displayOlmId = currentOlmId || clientDefaults?.olmId || null; + const displaySecret = regeneratedSecret || null; + return ( <> - {t("generatedcredentials")} + {t("clientOlmCredentials")} - {t("regenerateCredentials")} + {t("clientOlmCredentialsDescription")} - - + + + + {t("olmEndpoint")} + + + + + + + + {t("olmId")} + + + {displayOlmId ? ( + + ) : ( + {"••••••••••••••••"} + )} + + + + + {t("olmSecretKey")} + + + {displaySecret ? ( + + ) : ( + {"••••••••••••••••"} + )} + + + + + {showCredentialsAlert && displaySecret && ( + + + + {t("clientCredentialsSave")} + + + {t("clientCredentialsSaveDescription")} + + + )} + + - + - { + setModalOpen(val); + // Prevent modal from reopening during refresh + if (!val) { + setTimeout(() => { + router.refresh(); + }, 150); + } + }} + dialog={ +
+

{t("regenerateCredentialsConfirmation")}

+

{t("regenerateCredentialsWarning")}

+
+ } + buttonText={t("regenerateCredentialsButton")} + onConfirm={handleConfirmRegenerate} + string={getConfirmationString()} + title={t("regenerateCredentials")} + warningText={t("cannotbeUndone")} /> );