mirror of
https://github.com/Dokploy/website
synced 2025-06-26 18:16:01 +00:00
feat: add canvas-confetti for celebratory animations and implement LicenseSuccess page
This commit is contained in:
111
apps/website/app/[locale]/license/success/page.tsx
Normal file
111
apps/website/app/[locale]/license/success/page.tsx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
"use client";
|
||||||
|
import { Container } from "@/components/Container";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import confetti from "canvas-confetti";
|
||||||
|
import { CheckCircle2, Copy, Terminal } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export default function LicenseSuccess() {
|
||||||
|
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("")}`;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Launch confetti when the page loads
|
||||||
|
confetti({
|
||||||
|
particleCount: 150,
|
||||||
|
spread: 100,
|
||||||
|
origin: { y: 0.6 },
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const copyToClipboard = () => {
|
||||||
|
navigator.clipboard.writeText(apiKey);
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
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" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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-12">
|
||||||
|
Your Dokploy license has been successfully activated. Here's your
|
||||||
|
API key to get started.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<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="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}`}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</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">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full sm:w-auto hover:bg-zinc-800"
|
||||||
|
>
|
||||||
|
View Documentation
|
||||||
|
</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>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -167,8 +167,6 @@ export function Pricing() {
|
|||||||
|
|
||||||
const { sessionId } = await response.json();
|
const { sessionId } = await response.json();
|
||||||
|
|
||||||
console.log(sessionId);
|
|
||||||
|
|
||||||
// Redirect to Stripe checkout
|
// Redirect to Stripe checkout
|
||||||
const { error } = await stripe.redirectToCheckout({
|
const { error } = await stripe.redirectToCheckout({
|
||||||
sessionId,
|
sessionId,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"@types/turndown": "^5.0.5",
|
"@types/turndown": "^5.0.5",
|
||||||
"autoprefixer": "^10.4.12",
|
"autoprefixer": "^10.4.12",
|
||||||
"axios": "^1.8.1",
|
"axios": "^1.8.1",
|
||||||
|
"canvas-confetti": "^1.9.3",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"framer-motion": "^11.3.19",
|
"framer-motion": "^11.3.19",
|
||||||
@@ -62,6 +63,7 @@
|
|||||||
"@babel/parser": "^7.26.9",
|
"@babel/parser": "^7.26.9",
|
||||||
"@babel/plugin-syntax-typescript": "^7.25.9",
|
"@babel/plugin-syntax-typescript": "^7.25.9",
|
||||||
"@biomejs/biome": "1.7.0",
|
"@biomejs/biome": "1.7.0",
|
||||||
|
"@types/canvas-confetti": "^1.9.0",
|
||||||
"@types/react": "18.3.5",
|
"@types/react": "18.3.5",
|
||||||
"@types/react-dom": "18.3.0",
|
"@types/react-dom": "18.3.0",
|
||||||
"prettier-plugin-tailwindcss": "^0.5.14"
|
"prettier-plugin-tailwindcss": "^0.5.14"
|
||||||
|
|||||||
8
apps/website/public/grid.svg
Normal file
8
apps/website/public/grid.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
|
||||||
|
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/>
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
<rect width="100%" height="100%" fill="url(#grid)" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 340 B |
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@@ -145,6 +145,9 @@ importers:
|
|||||||
axios:
|
axios:
|
||||||
specifier: ^1.8.1
|
specifier: ^1.8.1
|
||||||
version: 1.8.1
|
version: 1.8.1
|
||||||
|
canvas-confetti:
|
||||||
|
specifier: ^1.9.3
|
||||||
|
version: 1.9.3
|
||||||
class-variance-authority:
|
class-variance-authority:
|
||||||
specifier: ^0.7.0
|
specifier: ^0.7.0
|
||||||
version: 0.7.0
|
version: 0.7.0
|
||||||
@@ -236,6 +239,9 @@ importers:
|
|||||||
'@biomejs/biome':
|
'@biomejs/biome':
|
||||||
specifier: 1.7.0
|
specifier: 1.7.0
|
||||||
version: 1.7.0
|
version: 1.7.0
|
||||||
|
'@types/canvas-confetti':
|
||||||
|
specifier: ^1.9.0
|
||||||
|
version: 1.9.0
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: 18.3.5
|
specifier: 18.3.5
|
||||||
version: 18.3.5
|
version: 18.3.5
|
||||||
@@ -1693,6 +1699,9 @@ packages:
|
|||||||
'@types/acorn@4.0.6':
|
'@types/acorn@4.0.6':
|
||||||
resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==}
|
resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==}
|
||||||
|
|
||||||
|
'@types/canvas-confetti@1.9.0':
|
||||||
|
resolution: {integrity: sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg==}
|
||||||
|
|
||||||
'@types/conventional-commits-parser@5.0.0':
|
'@types/conventional-commits-parser@5.0.0':
|
||||||
resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==}
|
resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==}
|
||||||
|
|
||||||
@@ -1900,6 +1909,9 @@ packages:
|
|||||||
caniuse-lite@1.0.30001679:
|
caniuse-lite@1.0.30001679:
|
||||||
resolution: {integrity: sha512-j2YqID/YwpLnKzCmBOS4tlZdWprXm3ZmQLBH9ZBXFOhoxLA46fwyBvx6toCBWBmnuwUY/qB3kEU6gFx8qgCroA==}
|
resolution: {integrity: sha512-j2YqID/YwpLnKzCmBOS4tlZdWprXm3ZmQLBH9ZBXFOhoxLA46fwyBvx6toCBWBmnuwUY/qB3kEU6gFx8qgCroA==}
|
||||||
|
|
||||||
|
canvas-confetti@1.9.3:
|
||||||
|
resolution: {integrity: sha512-rFfTURMvmVEX1gyXFgn5QMn81bYk70qa0HLzcIOSVEyl57n6o9ItHeBtUSWdvKAPY0xlvBHno4/v3QPrT83q9g==}
|
||||||
|
|
||||||
ccount@2.0.1:
|
ccount@2.0.1:
|
||||||
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
||||||
|
|
||||||
@@ -5440,6 +5452,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.5
|
'@types/estree': 1.0.5
|
||||||
|
|
||||||
|
'@types/canvas-confetti@1.9.0': {}
|
||||||
|
|
||||||
'@types/conventional-commits-parser@5.0.0':
|
'@types/conventional-commits-parser@5.0.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.17.16
|
'@types/node': 20.17.16
|
||||||
@@ -5641,6 +5655,8 @@ snapshots:
|
|||||||
|
|
||||||
caniuse-lite@1.0.30001679: {}
|
caniuse-lite@1.0.30001679: {}
|
||||||
|
|
||||||
|
canvas-confetti@1.9.3: {}
|
||||||
|
|
||||||
ccount@2.0.1: {}
|
ccount@2.0.1: {}
|
||||||
|
|
||||||
chalk@2.4.2:
|
chalk@2.4.2:
|
||||||
|
|||||||
Reference in New Issue
Block a user