mirror of
https://github.com/Dokploy/website
synced 2025-06-26 18:16:01 +00:00
feat: implement Reset License page and update navigation
This commit is contained in:
77
apps/website/app/[locale]/reset-license/page.tsx
Normal file
77
apps/website/app/[locale]/reset-license/page.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
"use client";
|
||||
|
||||
import { Container } from "@/components/Container";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function ResetLicensePage() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
// Here you would add the API call to reset the license
|
||||
// For now, we'll just simulate a success response
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
|
||||
// toast({
|
||||
// title: "Success!",
|
||||
// description:
|
||||
// "If an account exists with this email, you will receive instructions to reset your license.",
|
||||
// variant: "default",
|
||||
// });
|
||||
|
||||
setEmail("");
|
||||
} catch (error) {
|
||||
// toast({
|
||||
// title: "Error",
|
||||
// description: "Something went wrong. Please try again later.",
|
||||
// variant: "destructive",
|
||||
// });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Container className="relative pt-20 pb-16 text-center">
|
||||
<div className="mx-auto max-w-2xl">
|
||||
<h1 className="font-display text-4xl font-medium tracking-tight text-white sm:text-5xl">
|
||||
Reset Your License
|
||||
</h1>
|
||||
<p className="mt-4 text-lg text-muted-foreground">
|
||||
Enter your email address and we'll send you instructions to reset your
|
||||
license.
|
||||
</p>
|
||||
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="mt-10 flex flex-col items-center gap-4"
|
||||
>
|
||||
<div className="w-full max-w-sm">
|
||||
<Input
|
||||
type="email"
|
||||
placeholder="Enter your email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
className="w-full"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full max-w-sm"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? "Sending..." : "Reset License"}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -168,6 +168,7 @@ export function Header() {
|
||||
{t("navigation.docs")}
|
||||
</NavLink>
|
||||
<NavLink href="/blog">{t("navigation.blog")}</NavLink>
|
||||
<NavLink href="/reset-license">Reset License</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-4 md:gap-x-5">
|
||||
|
||||
@@ -87,6 +87,7 @@ export function Pricing() {
|
||||
const router = useRouter();
|
||||
const t = useTranslations("Pricing");
|
||||
const [isAnnual, setIsAnnual] = useState(false);
|
||||
const [isSelfHostedAnnual, setIsSelfHostedAnnual] = useState(false);
|
||||
const [serverQuantity, setServerQuantity] = useState(1);
|
||||
const featured = true;
|
||||
|
||||
@@ -176,7 +177,6 @@ export function Pricing() {
|
||||
</p>
|
||||
|
||||
<ul
|
||||
role="list"
|
||||
className={clsx(
|
||||
" mt-4 flex flex-col gap-y-2 text-sm",
|
||||
featured ? "text-white" : "text-slate-200",
|
||||
@@ -264,7 +264,6 @@ export function Pricing() {
|
||||
</p>
|
||||
|
||||
<ul
|
||||
role="list"
|
||||
className={clsx(
|
||||
" mt-4 flex flex-col gap-y-2 text-sm",
|
||||
featured ? "text-white" : "text-slate-200",
|
||||
@@ -373,6 +372,164 @@ export function Pricing() {
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="w-full border-t border-border/30 my-16" />
|
||||
|
||||
{/* Self-hosted License Section */}
|
||||
<div className="w-full">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="font-display text-3xl tracking-tight text-white sm:text-4xl mb-4">
|
||||
{t("plan.selfHosted.title")}
|
||||
</h2>
|
||||
<p className="text-lg text-muted-foreground">
|
||||
{t("plan.selfHosted.description")}
|
||||
</p>
|
||||
<div className="mt-8">
|
||||
<Tabs
|
||||
defaultValue="monthly"
|
||||
value={isSelfHostedAnnual ? "annual" : "monthly"}
|
||||
onValueChange={(e) => setIsSelfHostedAnnual(e === "annual")}
|
||||
>
|
||||
<TabsList>
|
||||
<TabsTrigger value="monthly">
|
||||
{t("plan.selfHosted.billingCycle.monthly")}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="annual">
|
||||
{t("plan.selfHosted.billingCycle.annual")}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 max-w-6xl mx-auto">
|
||||
{/* Basic License */}
|
||||
<section className="flex flex-col rounded-3xl border-2 border-dashed border-muted px-4 py-8">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<p className="text-2xl font-semibold tracking-tight text-primary">
|
||||
{isSelfHostedAnnual
|
||||
? t("plan.selfHosted.basic.priceAnnual")
|
||||
: t("plan.selfHosted.basic.priceMonthly")}{" "}
|
||||
USD
|
||||
</p>
|
||||
<span className="text-muted-foreground">
|
||||
/ {isSelfHostedAnnual ? "year" : "month"}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="mt-4 text-lg font-medium text-white">
|
||||
{t("plan.selfHosted.basic.title")}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("plan.selfHosted.basic.description")}
|
||||
</p>
|
||||
<ul className="mt-4 flex flex-col gap-y-2 text-sm">
|
||||
{Object.keys(t.raw("plan.selfHosted.basic.features")).map(
|
||||
(key) => (
|
||||
<li key={key} className="flex text-muted-foreground">
|
||||
<CheckIcon />
|
||||
<span className="ml-2">
|
||||
{t(`plan.selfHosted.basic.features.${key}`)}
|
||||
</span>
|
||||
</li>
|
||||
),
|
||||
)}
|
||||
</ul>
|
||||
<div className="mt-6 flex flex-col gap-2">
|
||||
<Button asChild>
|
||||
<Link href="https://app.dokploy.com/buy/basic">
|
||||
{t("plan.selfHosted.buyNow")}
|
||||
</Link>
|
||||
</Button>
|
||||
<Button variant="outline">
|
||||
{t("plan.selfHosted.contactSales")}
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Professional License */}
|
||||
<section className="flex flex-col rounded-3xl border-2 border-dashed border-muted px-4 py-8 bg-black/50">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<p className="text-2xl font-semibold tracking-tight text-primary">
|
||||
{isSelfHostedAnnual
|
||||
? t("plan.selfHosted.professional.priceAnnual")
|
||||
: t("plan.selfHosted.professional.priceMonthly")}{" "}
|
||||
USD
|
||||
</p>
|
||||
<span className="text-muted-foreground">
|
||||
/ {isSelfHostedAnnual ? "year" : "month"}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="mt-4 text-lg font-medium text-white">
|
||||
{t("plan.selfHosted.professional.title")}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("plan.selfHosted.professional.description")}
|
||||
</p>
|
||||
<ul className="mt-4 flex flex-col gap-y-2 text-sm">
|
||||
{Object.keys(
|
||||
t.raw("plan.selfHosted.professional.features"),
|
||||
).map((key) => (
|
||||
<li key={key} className="flex text-muted-foreground">
|
||||
<CheckIcon />
|
||||
<span className="ml-2">
|
||||
{t(`plan.selfHosted.professional.features.${key}`)}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-6 flex flex-col gap-2">
|
||||
<Button asChild>
|
||||
<Link href="https://app.dokploy.com/buy/professional">
|
||||
{t("plan.selfHosted.buyNow")}
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Enterprise License */}
|
||||
<section className="flex flex-col rounded-3xl border-2 border-dashed border-muted px-4 py-8">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<p className="text-2xl font-semibold tracking-tight text-primary">
|
||||
{isSelfHostedAnnual
|
||||
? t("plan.selfHosted.enterprise.priceAnnual")
|
||||
: t("plan.selfHosted.enterprise.priceMonthly")}{" "}
|
||||
USD
|
||||
</p>
|
||||
<span className="text-muted-foreground">
|
||||
/ {isSelfHostedAnnual ? "year" : "month"}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="mt-4 text-lg font-medium text-white">
|
||||
{t("plan.selfHosted.enterprise.title")}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("plan.selfHosted.enterprise.description")}
|
||||
</p>
|
||||
<ul className="mt-4 flex flex-col gap-y-2 text-sm">
|
||||
{Object.keys(
|
||||
t.raw("plan.selfHosted.enterprise.features"),
|
||||
).map((key) => (
|
||||
<li key={key} className="flex text-muted-foreground">
|
||||
<CheckIcon />
|
||||
<span className="ml-2">
|
||||
{t(`plan.selfHosted.enterprise.features.${key}`)}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-6 flex flex-col gap-2">
|
||||
<Button asChild>
|
||||
<Link href="https://app.dokploy.com/buy/enterprise">
|
||||
{t("plan.selfHosted.buyNow")}
|
||||
</Link>
|
||||
</Button>
|
||||
<Button variant="outline">
|
||||
{t("plan.selfHosted.contactSales")}
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
@@ -177,6 +177,51 @@
|
||||
"f8": "New Updates"
|
||||
},
|
||||
"go": "Subscribe"
|
||||
},
|
||||
"selfHosted": {
|
||||
"title": "Self-Hosted License",
|
||||
"description": "Deploy and manage Dokploy on your own infrastructure with enterprise-grade features and support",
|
||||
"basic": {
|
||||
"title": "Basic License",
|
||||
"description": "Perfect for small teams and startups",
|
||||
"priceAnnual": "$89.99",
|
||||
"priceMonthly": "$9.99",
|
||||
"features": {
|
||||
"f1": "Remote Servers Monitoring",
|
||||
"f2": "Priority Support",
|
||||
"f3": "New Updates"
|
||||
}
|
||||
},
|
||||
"professional": {
|
||||
"title": "Premium License",
|
||||
"description": "For growing businesses",
|
||||
"priceAnnual": "$199.99",
|
||||
"priceMonthly": "$19.99",
|
||||
"features": {
|
||||
"f1": "Remote Servers Monitoring",
|
||||
"f2": "Priority Support",
|
||||
"f3": "New Updates",
|
||||
"f4": "Custom branding"
|
||||
}
|
||||
},
|
||||
"enterprise": {
|
||||
"title": "Business License",
|
||||
"description": "For large organizations",
|
||||
"priceAnnual": "$299.99",
|
||||
"priceMonthly": "$29.99",
|
||||
"features": {
|
||||
"f1": "Remote Servers Monitoring",
|
||||
"f2": "24/7 Premium support",
|
||||
"f3": "Security updates",
|
||||
"f4": "Custom branding",
|
||||
"f5": "Custom features"
|
||||
}
|
||||
},
|
||||
"buyNow": "Buy Now",
|
||||
"billingCycle": {
|
||||
"monthly": "Monthly",
|
||||
"annual": "Annual"
|
||||
}
|
||||
}
|
||||
},
|
||||
"faq": {
|
||||
@@ -214,5 +259,14 @@
|
||||
"backToBlog": "Back to Blog",
|
||||
"tags": "Tags",
|
||||
"postsTaggedWith": "Posts tagged with"
|
||||
},
|
||||
"Header": {
|
||||
"signIn": "Sign in",
|
||||
"signInCloud": "Sign in to Dokploy Cloud",
|
||||
"pricing": "Pricing",
|
||||
"faqs": "FAQs",
|
||||
"docs": "Documentation",
|
||||
"blog": "Blog",
|
||||
"resetLicense": "Reset License"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user