restyle client regen credentials

This commit is contained in:
miloschwartz
2025-12-06 11:47:50 -05:00
parent 3f4fae8f09
commit 1d303feca2

View File

@@ -1,27 +1,37 @@
"use client"; "use client";
import RegenerateCredentialsModal from "@app/components/RegenerateCredentialsModal"; import { useState } from "react";
import { SecurityFeaturesAlert } from "@app/components/SecurityFeaturesAlert";
import { import {
SettingsContainer, SettingsContainer,
SettingsSection, SettingsSection,
SettingsSectionBody, SettingsSectionBody,
SettingsSectionDescription, SettingsSectionDescription,
SettingsSectionFooter,
SettingsSectionHeader, SettingsSectionHeader,
SettingsSectionTitle SettingsSectionTitle
} from "@app/components/Settings"; } from "@app/components/Settings";
import { Button } from "@app/components/ui/button"; 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 { 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 { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext"; import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
import { toast } from "@app/hooks/useToast";
import { createApiClient } from "@app/lib/api";
import { build } from "@server/build"; import { build } from "@server/build";
import { PickClientDefaultsResponse } from "@server/routers/client"; import { SecurityFeaturesAlert } from "@app/components/SecurityFeaturesAlert";
import { useTranslations } from "next-intl"; import {
import { useParams, useRouter } from "next/navigation"; InfoSection,
import { useState } from "react"; 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() { export default function CredentialsPage() {
const { env } = useEnvContext(); const { env } = useEnvContext();
@@ -34,6 +44,11 @@ export default function CredentialsPage() {
const [modalOpen, setModalOpen] = useState(false); const [modalOpen, setModalOpen] = useState(false);
const [clientDefaults, setClientDefaults] = const [clientDefaults, setClientDefaults] =
useState<PickClientDefaultsResponse | null>(null); useState<PickClientDefaultsResponse | null>(null);
const [currentOlmId, setCurrentOlmId] = useState<string | null>(null);
const [regeneratedSecret, setRegeneratedSecret] = useState<string | null>(
null
);
const [showCredentialsAlert, setShowCredentialsAlert] = useState(false);
const { licenseStatus, isUnlocked } = useLicenseStatusContext(); const { licenseStatus, isUnlocked } = useLicenseStatusContext();
const subscription = useSubscriptionStatusContext(); const subscription = useSubscriptionStatusContext();
@@ -46,76 +61,150 @@ export default function CredentialsPage() {
}; };
const handleConfirmRegenerate = async () => { const handleConfirmRegenerate = async () => {
const res = await api.get(`/org/${orgId}/pick-client-defaults`); try {
if (res && res.status === 200) { const res = await api.get(`/org/${orgId}/pick-client-defaults`);
const data = res.data.data; if (res && res.status === 200) {
const data = res.data.data;
const rekeyRes = await api.post( const rekeyRes = await api.post(
`/re-key/${client?.clientId}/regenerate-client-secret`, `/re-key/${client?.clientId}/regenerate-client-secret`,
{ {
secret: data.olmSecret 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) { toast({
const rekeyData = rekeyRes.data.data; title: t("credentialsSaved"),
setClientDefaults({ description: t("credentialsSavedDescription")
...data,
olmId: rekeyData.olmId,
}); });
} }
} catch (error) {
toast({ toast({
title: t("credentialsSaved"), variant: "destructive",
description: t("credentialsSavedDescription") title: t("error") || "Error",
description:
formatAxiosError(error) ||
t("credentialsRegenerateError") ||
"Failed to regenerate credentials"
}); });
router.refresh();
} }
}; };
const getCredentials = () => { const getConfirmationString = () => {
if (clientDefaults) { return client?.name || client?.clientId?.toString() || "My client";
return {
Id: clientDefaults.olmId,
Secret: clientDefaults.olmSecret
};
}
return undefined;
}; };
const displayOlmId = currentOlmId || clientDefaults?.olmId || null;
const displaySecret = regeneratedSecret || null;
return ( return (
<> <>
<SettingsContainer> <SettingsContainer>
<SettingsSection> <SettingsSection>
<SettingsSectionHeader> <SettingsSectionHeader>
<SettingsSectionTitle> <SettingsSectionTitle>
{t("generatedcredentials")} {t("clientOlmCredentials")}
</SettingsSectionTitle> </SettingsSectionTitle>
<SettingsSectionDescription> <SettingsSectionDescription>
{t("regenerateCredentials")} {t("clientOlmCredentialsDescription")}
</SettingsSectionDescription> </SettingsSectionDescription>
</SettingsSectionHeader> </SettingsSectionHeader>
<SettingsSectionBody> <SettingsSectionBody>
<SecurityFeaturesAlert /> <InfoSections cols={3}>
<InfoSection>
<InfoSectionTitle>
{t("olmEndpoint")}
</InfoSectionTitle>
<InfoSectionContent>
<CopyToClipboard
text={env.app.dashboardUrl}
/>
</InfoSectionContent>
</InfoSection>
<InfoSection>
<InfoSectionTitle>
{t("olmId")}
</InfoSectionTitle>
<InfoSectionContent>
{displayOlmId ? (
<CopyToClipboard text={displayOlmId} />
) : (
<span>{"••••••••••••••••"}</span>
)}
</InfoSectionContent>
</InfoSection>
<InfoSection>
<InfoSectionTitle>
{t("olmSecretKey")}
</InfoSectionTitle>
<InfoSectionContent>
{displaySecret ? (
<CopyToClipboard text={displaySecret} />
) : (
<span>{"••••••••••••••••"}</span>
)}
</InfoSectionContent>
</InfoSection>
</InfoSections>
{showCredentialsAlert && displaySecret && (
<Alert variant="neutral" className="mt-4">
<InfoIcon className="h-4 w-4" />
<AlertTitle className="font-semibold">
{t("clientCredentialsSave")}
</AlertTitle>
<AlertDescription>
{t("clientCredentialsSaveDescription")}
</AlertDescription>
</Alert>
)}
</SettingsSectionBody>
<SettingsSectionFooter>
<Button <Button
onClick={() => setModalOpen(true)} onClick={() => setModalOpen(true)}
disabled={isSecurityFeatureDisabled()} disabled={isSecurityFeatureDisabled()}
> >
{t("regeneratecredentials")} {t("regenerateCredentialsButton")}
</Button> </Button>
</SettingsSectionBody> </SettingsSectionFooter>
</SettingsSection> </SettingsSection>
</SettingsContainer> </SettingsContainer>
<RegenerateCredentialsModal <ConfirmDeleteDialog
open={modalOpen} open={modalOpen}
onOpenChange={setModalOpen} setOpen={(val) => {
type="client-olm" setModalOpen(val);
onConfirmRegenerate={handleConfirmRegenerate} // Prevent modal from reopening during refresh
dashboardUrl={env.app.dashboardUrl} if (!val) {
credentials={getCredentials()} setTimeout(() => {
router.refresh();
}, 150);
}
}}
dialog={
<div className="space-y-2">
<p>{t("regenerateCredentialsConfirmation")}</p>
<p>{t("regenerateCredentialsWarning")}</p>
</div>
}
buttonText={t("regenerateCredentialsButton")}
onConfirm={handleConfirmRegenerate}
string={getConfirmationString()}
title={t("regenerateCredentials")}
warningText={t("cannotbeUndone")}
/> />
</> </>
); );