mirror of
https://github.com/andwati/dokploy.git
synced 2026-11-10 22:15:16 +00:00
Enhance License Key Management: Add loading state for license key validation, implement query to check for valid license keys, and improve UI feedback during license key checks.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { Key } from "lucide-react";
|
import { Key, Loader2 } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -20,164 +20,174 @@ export function LicenseKeySettings() {
|
|||||||
api.licenseKey.validate.useMutation();
|
api.licenseKey.validate.useMutation();
|
||||||
const { mutateAsync: deactivateLicenseKey, isLoading: isDeactivating } =
|
const { mutateAsync: deactivateLicenseKey, isLoading: isDeactivating } =
|
||||||
api.licenseKey.deactivate.useMutation();
|
api.licenseKey.deactivate.useMutation();
|
||||||
|
const { data: haveValidLicenseKey, isLoading: isCheckingLicenseKey } =
|
||||||
|
api.licenseKey.haveValidLicenseKey.useQuery();
|
||||||
const [licenseKey, setLicenseKey] = useState("");
|
const [licenseKey, setLicenseKey] = useState("");
|
||||||
const [isValid, setIsValid] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data?.licenseKey) {
|
if (data?.licenseKey) {
|
||||||
setLicenseKey(data.licenseKey);
|
setLicenseKey(data.licenseKey);
|
||||||
validateLicenseKey({ licenseKey: data.licenseKey })
|
|
||||||
.then((valid) => {
|
|
||||||
console.log("valid", valid);
|
|
||||||
setIsValid(valid);
|
|
||||||
})
|
|
||||||
.catch(() => setIsValid(false));
|
|
||||||
}
|
}
|
||||||
}, [data?.licenseKey, validateLicenseKey]);
|
}, [data?.licenseKey]);
|
||||||
|
|
||||||
const enabled = !!data?.enableEnterpriseFeatures;
|
const enabled = !!data?.enableEnterpriseFeatures;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 rounded-lg border p-4">
|
<div className="flex flex-col gap-4 rounded-lg border p-4">
|
||||||
<div className="flex flex-col gap-2">
|
{isCheckingLicenseKey ? (
|
||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center gap-2 justify-center min-h-[25vh]">
|
||||||
<div className="flex items-center gap-2">
|
<Loader2 className="size-6 text-muted-foreground animate-spin" />
|
||||||
<Key className="size-6 text-muted-foreground" />
|
<span className="text-sm text-muted-foreground">
|
||||||
<CardTitle className="text-xl">License Key</CardTitle>
|
Checking license key...
|
||||||
</div>
|
</span>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-xs text-muted-foreground">
|
|
||||||
{enabled ? "Enabled" : "Disabled"}
|
|
||||||
</span>
|
|
||||||
<Switch
|
|
||||||
checked={enabled}
|
|
||||||
disabled={isLoading || isSaving || isDeactivating}
|
|
||||||
onCheckedChange={async (next) => {
|
|
||||||
try {
|
|
||||||
await updateEnterpriseSettings({
|
|
||||||
enableEnterpriseFeatures: next,
|
|
||||||
});
|
|
||||||
await utils.licenseKey.getEnterpriseSettings.invalidate();
|
|
||||||
toast.success("Enterprise features updated");
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
toast.error("Failed to update enterprise features");
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex items-center justify-between gap-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Key className="size-6 text-muted-foreground" />
|
||||||
|
<CardTitle className="text-xl">License Key</CardTitle>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p className="text-sm text-muted-foreground">
|
<div className="flex items-center gap-2">
|
||||||
To unlock extra features you need an enterprise license key. Contact
|
<span className="text-xs text-muted-foreground">
|
||||||
us{" "}
|
{enabled ? "Enabled" : "Disabled"}
|
||||||
<Link
|
</span>
|
||||||
href="http://localhost:3001/contact"
|
<Switch
|
||||||
target="_blank"
|
checked={enabled}
|
||||||
rel="noreferrer"
|
disabled={isLoading || isSaving || isDeactivating}
|
||||||
className="underline underline-offset-4"
|
onCheckedChange={async (next) => {
|
||||||
>
|
try {
|
||||||
here
|
await updateEnterpriseSettings({
|
||||||
</Link>
|
enableEnterpriseFeatures: next,
|
||||||
.
|
});
|
||||||
</p>
|
await utils.licenseKey.getEnterpriseSettings.invalidate();
|
||||||
</div>
|
toast.success("Enterprise features updated");
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error("Failed to update enterprise features");
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{enabled && (
|
<p className="text-sm text-muted-foreground">
|
||||||
<div className="grid gap-3 md:grid-cols-[1fr_auto] md:items-end">
|
To unlock extra features you need an enterprise license key.
|
||||||
<div className="space-y-2">
|
Contact us{" "}
|
||||||
<label className="text-sm font-medium" htmlFor="licenseKey">
|
<Link
|
||||||
License Key
|
href="http://localhost:3001/contact"
|
||||||
</label>
|
target="_blank"
|
||||||
<Input
|
rel="noreferrer"
|
||||||
id="licenseKey"
|
className="underline underline-offset-4"
|
||||||
placeholder="Enter your enterprise license key"
|
|
||||||
value={licenseKey}
|
|
||||||
onChange={(e) => setLicenseKey(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="md:justify-self-end flex gap-2">
|
|
||||||
{isValid && (
|
|
||||||
<DialogAction
|
|
||||||
title="Deactivate License Key"
|
|
||||||
description="Are you sure you want to deactivate this license key? This will disable enterprise features."
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
await deactivateLicenseKey();
|
|
||||||
await utils.licenseKey.getEnterpriseSettings.invalidate();
|
|
||||||
setIsValid(false);
|
|
||||||
toast.success("License key deactivated");
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
toast.error(
|
|
||||||
error instanceof Error
|
|
||||||
? error.message
|
|
||||||
: "Failed to deactivate license key",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={isDeactivating || !data?.licenseKey}
|
|
||||||
>
|
>
|
||||||
<Button
|
here
|
||||||
variant="destructive"
|
</Link>
|
||||||
disabled={isDeactivating || !data?.licenseKey}
|
.
|
||||||
>
|
</p>
|
||||||
Deactivate
|
|
||||||
</Button>
|
|
||||||
</DialogAction>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
disabled={isSaving || isValidating || isDeactivating}
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
const valid = await validateLicenseKey({ licenseKey });
|
|
||||||
if (valid) {
|
|
||||||
toast.success("License key is valid");
|
|
||||||
} else {
|
|
||||||
toast.error("License key is invalid");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
toast.error(
|
|
||||||
error instanceof Error
|
|
||||||
? error.message
|
|
||||||
: "Failed to validate license key",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Validate
|
|
||||||
</Button>
|
|
||||||
{!isValid && (
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
disabled={isSaving || isValidating || isDeactivating}
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
await activateLicenseKey({ licenseKey });
|
|
||||||
await utils.licenseKey.getEnterpriseSettings.invalidate();
|
|
||||||
// Re-validate after saving to update the Deactivate button visibility
|
|
||||||
const valid = await validateLicenseKey({ licenseKey });
|
|
||||||
setIsValid(valid);
|
|
||||||
toast.success("License key activated");
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
toast.error(
|
|
||||||
error instanceof Error
|
|
||||||
? error.message
|
|
||||||
: "Failed to activate license key",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Activate
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{enabled && (
|
||||||
|
<div className="grid gap-3 md:grid-cols-[1fr_auto] md:items-end">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium" htmlFor="licenseKey">
|
||||||
|
License Key
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
id="licenseKey"
|
||||||
|
placeholder="Enter your enterprise license key"
|
||||||
|
value={licenseKey}
|
||||||
|
onChange={(e) => setLicenseKey(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="md:justify-self-end flex gap-2">
|
||||||
|
{haveValidLicenseKey && (
|
||||||
|
<DialogAction
|
||||||
|
title="Deactivate License Key"
|
||||||
|
description="Are you sure you want to deactivate this license key? This will disable enterprise features."
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
await deactivateLicenseKey();
|
||||||
|
await utils.licenseKey.getEnterpriseSettings.invalidate();
|
||||||
|
await utils.licenseKey.haveValidLicenseKey.invalidate();
|
||||||
|
toast.success("License key deactivated");
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Failed to deactivate license key",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={isDeactivating || !haveValidLicenseKey}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
disabled={isDeactivating || !haveValidLicenseKey}
|
||||||
|
isLoading={isDeactivating}
|
||||||
|
>
|
||||||
|
Deactivate
|
||||||
|
</Button>
|
||||||
|
</DialogAction>
|
||||||
|
)}
|
||||||
|
{haveValidLicenseKey && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
disabled={
|
||||||
|
isSaving || isCheckingLicenseKey || isDeactivating
|
||||||
|
}
|
||||||
|
isLoading={isValidating}
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
const valid = await validateLicenseKey({ licenseKey });
|
||||||
|
console.log("valid", valid);
|
||||||
|
if (valid) {
|
||||||
|
toast.success("License key is valid");
|
||||||
|
} else {
|
||||||
|
toast.error("License key is invalid");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Failed to validate license key",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Validate
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{!haveValidLicenseKey && (
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
disabled={isSaving || isValidating || isDeactivating}
|
||||||
|
isLoading={isActivating}
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
await activateLicenseKey({ licenseKey });
|
||||||
|
await utils.licenseKey.getEnterpriseSettings.invalidate();
|
||||||
|
await utils.licenseKey.haveValidLicenseKey.invalidate();
|
||||||
|
toast.success("License key activated");
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error(
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Failed to activate license key",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Activate
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -126,6 +126,23 @@ export const licenseKeyRouter = createTRPCRouter({
|
|||||||
licenseKey: currentUser.licenseKey ?? "",
|
licenseKey: currentUser.licenseKey ?? "",
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
haveValidLicenseKey: adminProcedure.query(async ({ ctx }) => {
|
||||||
|
const currentUserId = ctx.user.id;
|
||||||
|
const currentUser = await db.query.user.findFirst({
|
||||||
|
where: eq(user.id, currentUserId),
|
||||||
|
});
|
||||||
|
if (!currentUser?.enableEnterpriseFeatures) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!currentUser.licenseKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return await validateLicenseKey(currentUser.licenseKey ?? "");
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
updateEnterpriseSettings: adminProcedure
|
updateEnterpriseSettings: adminProcedure
|
||||||
.input(
|
.input(
|
||||||
|
|||||||
Reference in New Issue
Block a user