mirror of
https://github.com/Dokploy/website
synced 2025-06-26 18:16:01 +00:00
feat: add copy-to-clipboard functionality and loading state to LicenseSuccess page
This commit is contained in:
parent
6ac4e6b040
commit
89ee55a822
@ -1,31 +1,68 @@
|
||||
"use client";
|
||||
import { Container } from "@/components/Container";
|
||||
import { SERVER_LICENSE_URL } from "@/components/pricing";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import confetti from "canvas-confetti";
|
||||
import { CheckCircle2, Copy, Mail, Terminal } from "lucide-react";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { CheckCircle2, Copy, Loader2, Mail, Terminal } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { redirect, useSearchParams } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
interface LicenseSessionResponse {
|
||||
type: "basic" | "professional" | "business";
|
||||
billingType: "monthly" | "yearly";
|
||||
key: string;
|
||||
}
|
||||
|
||||
export default function LicenseSuccess() {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [copied, setCopied] = useState(false);
|
||||
// Generate a realistic-looking API key
|
||||
const apiKey = `dk_live_${Array.from(
|
||||
crypto.getRandomValues(new Uint8Array(24)),
|
||||
)
|
||||
.map((b) => b.toString(16).padStart(2, "0"))
|
||||
.join("")}`;
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const query = useSearchParams();
|
||||
|
||||
const sessionId = query.get("session_id");
|
||||
|
||||
if (!sessionId) {
|
||||
redirect("/");
|
||||
}
|
||||
|
||||
const [data, setData] = useState<LicenseSessionResponse | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
console.log(`${SERVER_LICENSE_URL}/license/session?sessionId=${sessionId}`);
|
||||
fetch(`${SERVER_LICENSE_URL}/license/session?sessionId=${sessionId}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => setData(data))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
setError(err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [sessionId]);
|
||||
|
||||
useEffect(() => {
|
||||
// Launch confetti when the page loads
|
||||
confetti({
|
||||
particleCount: 150,
|
||||
spread: 100,
|
||||
origin: { y: 0.6 },
|
||||
});
|
||||
}, []);
|
||||
if (data) {
|
||||
confetti({
|
||||
particleCount: 150,
|
||||
spread: 100,
|
||||
origin: { y: 0.6 },
|
||||
});
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const copyToClipboard = () => {
|
||||
navigator.clipboard.writeText(apiKey);
|
||||
copy(data?.key ?? "");
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
@ -33,86 +70,112 @@ export default function LicenseSuccess() {
|
||||
return (
|
||||
<div className="relative min-h-screen bg-gradient-to-b from-black via-zinc-900 to-black">
|
||||
<div className="absolute inset-0 bg-[url('/grid.svg')] bg-center [mask-image:linear-gradient(180deg,white,rgba(255,255,255,0))]" />
|
||||
|
||||
<Container className="relative pt-24 pb-28">
|
||||
<div className="mx-auto max-w-3xl text-center">
|
||||
<div className="flex justify-center mb-8">
|
||||
<div className="rounded-full bg-green-500/10 p-4">
|
||||
<CheckCircle2 className="h-16 w-16 text-green-500" />
|
||||
{loading ? (
|
||||
<div className="relative pt-48 pb-28">
|
||||
<div className="mx-auto max-w-4xl text-center flex justify-center items-center h-full">
|
||||
<Loader2 className="h-16 w-16 text-zinc-500 animate-spin" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative pt-24 pb-28">
|
||||
<div className="mx-auto max-w-4xl text-center">
|
||||
<div className="flex justify-center mb-8">
|
||||
<div className="rounded-full bg-green-500/10 p-4">
|
||||
<CheckCircle2 className="h-16 w-16 text-green-500" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 className="text-5xl font-bold tracking-tight text-white sm:text-6xl mb-6">
|
||||
Thank you for your purchase!
|
||||
</h1>
|
||||
<h1 className="text-5xl font-bold tracking-tight text-white sm:text-6xl mb-6">
|
||||
Thank you for your purchase!
|
||||
</h1>
|
||||
|
||||
<p className="text-xl leading-8 text-zinc-400 mb-6">
|
||||
Your Dokploy license has been successfully activated. Here's your
|
||||
API key to get started.
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-center gap-2 mb-12">
|
||||
<Mail className="h-5 w-5 text-zinc-500" />
|
||||
<p className="text-sm text-zinc-500">
|
||||
We've also sent your API key to your email for safekeeping
|
||||
<p className="text-xl leading-8 text-zinc-400 mb-6">
|
||||
Your Dokploy license has been successfully activated. Here's your
|
||||
API key to get started.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-black/50 backdrop-blur-sm border border-zinc-800 rounded-xl p-8 mb-12">
|
||||
<div className="flex flex-col items-center space-y-6">
|
||||
<Terminal className="h-10 w-10 text-zinc-500" />
|
||||
<div className="space-y-4 w-full">
|
||||
<div className="flex items-center justify-between space-x-4 bg-black/50 rounded-lg p-4 border border-zinc-800">
|
||||
<code className="text-green-500 text-lg font-mono">
|
||||
{apiKey}
|
||||
</code>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={copyToClipboard}
|
||||
className="transition-all duration-200 hover:bg-green-500/10 hover:text-green-500"
|
||||
>
|
||||
{copied ? (
|
||||
<CheckCircle2 className="h-5 w-5" />
|
||||
) : (
|
||||
<Copy className="h-5 w-5" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-2 mb-12">
|
||||
<Mail className="h-5 w-5 text-zinc-500" />
|
||||
<p className="text-sm text-zinc-500">
|
||||
We've also sent your API key to your email for safekeeping
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="text-left space-y-3">
|
||||
<p className="text-zinc-400 text-sm">
|
||||
To start using your license, add this API key to your
|
||||
configuration file:
|
||||
</p>
|
||||
<pre className="bg-black/50 rounded-lg p-4 overflow-x-auto border border-zinc-800">
|
||||
<code className="text-sm font-mono text-zinc-300">
|
||||
{`# .env
|
||||
DOKPLOY_LICENSE_KEY=${apiKey}`}
|
||||
<div className="bg-black/50 backdrop-blur-sm border border-zinc-800 rounded-xl p-8 mb-12">
|
||||
<div className="flex flex-col items-center space-y-6">
|
||||
<Terminal className="h-10 w-10 text-zinc-500" />
|
||||
<div className="space-y-4 w-full">
|
||||
<div className="text-left space-y-3">
|
||||
<p className="text-zinc-400 text-sm">
|
||||
Steps to enable paid features
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<span>1. Web Server</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>2. Enable Paid Features</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
3. Copy the Key Below and Paste it in the license key
|
||||
field and click on validate
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex items-center justify-between space-x-4 bg-black/50 rounded-lg p-4 border border-zinc-800">
|
||||
<code className="text-green-500 text-lg font-mono">
|
||||
{data?.key}
|
||||
</code>
|
||||
</pre>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={copyToClipboard}
|
||||
className="transition-all duration-200 hover:bg-green-500/10 hover:text-green-500"
|
||||
>
|
||||
{copied ? (
|
||||
<CheckCircle2 className="h-5 w-5" />
|
||||
) : (
|
||||
<Copy className="h-5 w-5" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row justify-center gap-4">
|
||||
<Link href="https://docs.dokploy.com/docs/core/installation">
|
||||
<div className="flex flex-col sm:flex-row justify-center gap-4">
|
||||
<Link href="https://docs.dokploy.com/docs/core/installation">
|
||||
<Button className="w-full sm:w-auto hover:bg-zinc-800">
|
||||
View Documentation
|
||||
</Button>
|
||||
</Link>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full sm:w-auto hover:bg-zinc-800"
|
||||
className="rounded-full bg-[#5965F2] hover:bg-[#4A55E0]"
|
||||
asChild
|
||||
>
|
||||
View Documentation
|
||||
<Link
|
||||
href="https://discord.gg/2tBnJ3jDJc"
|
||||
aria-label="Dokploy on GitHub"
|
||||
target="_blank"
|
||||
className="flex flex-row items-center gap-2 text-white"
|
||||
>
|
||||
<svg
|
||||
role="img"
|
||||
className="h-6 w-6 fill-white"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
|
||||
</svg>
|
||||
Discord
|
||||
</Link>
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="https://discord.gg/dokploy">
|
||||
<Button className="w-full sm:w-auto bg-gradient-to-r from-indigo-500 to-purple-500 hover:from-indigo-600 hover:to-purple-600">
|
||||
Join our Discord
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ function SwirlyDoodle(props: React.ComponentPropsWithoutRef<"svg">) {
|
||||
);
|
||||
}
|
||||
|
||||
const SERVER_LICENSE_URL =
|
||||
export const SERVER_LICENSE_URL =
|
||||
process.env.NODE_ENV === "development"
|
||||
? "http://localhost:4002/api"
|
||||
: "https://licenses.dokploy.com";
|
||||
|
@ -12,6 +12,7 @@
|
||||
},
|
||||
"browserslist": "defaults, not ie <= 11",
|
||||
"dependencies": {
|
||||
"copy-to-clipboard": "3.3.3",
|
||||
"@headlessui/react": "^2.2.0",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@prettier/plugin-xml": "^3.4.1",
|
||||
|
@ -154,6 +154,9 @@ importers:
|
||||
clsx:
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.1
|
||||
copy-to-clipboard:
|
||||
specifier: 3.3.3
|
||||
version: 3.3.3
|
||||
framer-motion:
|
||||
specifier: ^11.3.19
|
||||
version: 11.3.19(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
@ -2035,6 +2038,9 @@ packages:
|
||||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
copy-to-clipboard@3.3.3:
|
||||
resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==}
|
||||
|
||||
cosmiconfig-typescript-loader@5.0.0:
|
||||
resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==}
|
||||
engines: {node: '>=v16'}
|
||||
@ -3643,6 +3649,9 @@ packages:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
|
||||
toggle-selection@1.0.6:
|
||||
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
|
||||
|
||||
trim-lines@3.0.1:
|
||||
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
|
||||
|
||||
@ -5780,6 +5789,10 @@ snapshots:
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
copy-to-clipboard@3.3.3:
|
||||
dependencies:
|
||||
toggle-selection: 1.0.6
|
||||
|
||||
cosmiconfig-typescript-loader@5.0.0(@types/node@20.17.16)(cosmiconfig@9.0.0(typescript@5.6.3))(typescript@5.6.3):
|
||||
dependencies:
|
||||
'@types/node': 20.17.16
|
||||
@ -7868,6 +7881,8 @@ snapshots:
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
|
||||
toggle-selection@1.0.6: {}
|
||||
|
||||
trim-lines@3.0.1: {}
|
||||
|
||||
trough@2.2.0: {}
|
||||
|
Loading…
Reference in New Issue
Block a user