feat: add next-themes and sonner for improved theming and notifications

This commit is contained in:
Mauricio Siu
2025-03-23 13:38:32 -06:00
parent 7df27e67b0
commit 56037ef88e
5 changed files with 86 additions and 5 deletions

View File

@@ -6,6 +6,7 @@ import copy from "copy-to-clipboard";
import { import {
CheckCircle2, CheckCircle2,
Copy, Copy,
CopyIcon,
Loader2, Loader2,
Mail, Mail,
RefreshCcw, RefreshCcw,
@@ -14,6 +15,7 @@ import {
import Link from "next/link"; import Link from "next/link";
import { redirect, useSearchParams } from "next/navigation"; import { redirect, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { toast } from "sonner";
interface LicenseSessionResponse { interface LicenseSessionResponse {
type: "basic" | "professional" | "business"; type: "basic" | "professional" | "business";
@@ -70,6 +72,7 @@ export default function LicenseSuccess() {
copy(data?.key ?? ""); copy(data?.key ?? "");
setCopied(true); setCopied(true);
setTimeout(() => setCopied(false), 2000); setTimeout(() => setCopied(false), 2000);
toast.success("Copied to clipboard");
}; };
return ( return (
@@ -169,15 +172,30 @@ export default function LicenseSuccess() {
Steps to enable paid features Steps to enable paid features
</p> </p>
<ul> <ul>
<li> <li className="flex items-center gap-2">
<span>1. Web Server</span> <span>1. Install Dokploy Instance on your server</span>
<code className="text-green-500 font-mono bg-black rounded-lg p-1">
curl -sSL https://dokploy.com/install.sh | sh
</code>
<CopyIcon
className="w-4 h-4 cursor-pointer"
onClick={() => {
copy(
"curl -sSL https://dokploy.com/install.sh | sh",
);
toast.success("Copied to clipboard");
}}
/>
</li> </li>
<li> <li>
<span>2. Enable Paid Features</span> <span>2. Go to your web server section</span>
</li>
<li>
<span>3. Enable Paid Features</span>
</li> </li>
<li> <li>
<span> <span>
3. Copy the Key Below and Paste it in the license key 4. Copy the Key Below and Paste it in the license key
field and click on validate field and click on validate
</span> </span>
</li> </li>

View File

@@ -1,3 +1,4 @@
import { Toaster } from "@/components/ui/sonner";
import clsx from "clsx"; import clsx from "clsx";
import type { Metadata } from "next"; import type { Metadata } from "next";
import { NextIntlClientProvider } from "next-intl"; import { NextIntlClientProvider } from "next-intl";
@@ -63,6 +64,7 @@ export default async function RootLayout({
<body> <body>
<NextIntlClientProvider messages={messages}> <NextIntlClientProvider messages={messages}>
{children} {children}
<Toaster />
</NextIntlClientProvider> </NextIntlClientProvider>
</body> </body>
</html> </html>

View File

@@ -0,0 +1,31 @@
"use client";
import { useTheme } from "next-themes";
import { Toaster as Sonner } from "sonner";
type ToasterProps = React.ComponentProps<typeof Sonner>;
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme();
return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
toastOptions={{
classNames: {
toast:
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
description: "group-[.toast]:text-muted-foreground",
actionButton:
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
cancelButton:
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
},
}}
{...props}
/>
);
};
export { Toaster };

View File

@@ -12,7 +12,6 @@
}, },
"browserslist": "defaults, not ie <= 11", "browserslist": "defaults, not ie <= 11",
"dependencies": { "dependencies": {
"copy-to-clipboard": "3.3.3",
"@headlessui/react": "^2.2.0", "@headlessui/react": "^2.2.0",
"@headlessui/tailwindcss": "^0.2.0", "@headlessui/tailwindcss": "^0.2.0",
"@prettier/plugin-xml": "^3.4.1", "@prettier/plugin-xml": "^3.4.1",
@@ -34,11 +33,13 @@
"canvas-confetti": "^1.9.3", "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",
"copy-to-clipboard": "3.3.3",
"framer-motion": "^11.3.19", "framer-motion": "^11.3.19",
"hast-util-to-jsx-runtime": "2.3.5", "hast-util-to-jsx-runtime": "2.3.5",
"lucide-react": "0.364.0", "lucide-react": "0.364.0",
"next": "15.2.0", "next": "15.2.0",
"next-intl": "^3.26.5", "next-intl": "^3.26.5",
"next-themes": "^0.4.6",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
@@ -52,6 +53,7 @@
"sharp": "^0.33.5", "sharp": "^0.33.5",
"shiki": "1.22.2", "shiki": "1.22.2",
"slugify": "^1.6.6", "slugify": "^1.6.6",
"sonner": "^2.0.1",
"tailwind-merge": "^2.2.2", "tailwind-merge": "^2.2.2",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",

28
pnpm-lock.yaml generated
View File

@@ -172,6 +172,9 @@ importers:
next-intl: next-intl:
specifier: ^3.26.5 specifier: ^3.26.5
version: 3.26.5(next@15.2.0(@babel/core@7.26.9)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0) version: 3.26.5(next@15.2.0(@babel/core@7.26.9)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)
next-themes:
specifier: ^0.4.6
version: 0.4.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
prettier: prettier:
specifier: ^3.3.3 specifier: ^3.3.3
version: 3.3.3 version: 3.3.3
@@ -211,6 +214,9 @@ importers:
slugify: slugify:
specifier: ^1.6.6 specifier: ^1.6.6
version: 1.6.6 version: 1.6.6
sonner:
specifier: ^2.0.1
version: 2.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
tailwind-merge: tailwind-merge:
specifier: ^2.2.2 specifier: ^2.2.2
version: 2.4.0 version: 2.4.0
@@ -3001,6 +3007,12 @@ packages:
react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
next-themes@0.4.6:
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
peerDependencies:
react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
next@15.0.3: next@15.0.3:
resolution: {integrity: sha512-ontCbCRKJUIoivAdGB34yCaOcPgYXr9AAkV/IwqFfWWTXEPUgLYkSkqBhIk9KK7gGmgjc64B+RdoeIDM13Irnw==} resolution: {integrity: sha512-ontCbCRKJUIoivAdGB34yCaOcPgYXr9AAkV/IwqFfWWTXEPUgLYkSkqBhIk9KK7gGmgjc64B+RdoeIDM13Irnw==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
@@ -3512,6 +3524,12 @@ packages:
resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==} resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==}
engines: {node: '>=8.0.0'} engines: {node: '>=8.0.0'}
sonner@2.0.1:
resolution: {integrity: sha512-FRBphaehZ5tLdLcQ8g2WOIRE+Y7BCfWi5Zyd8bCvBjiW8TxxAyoWZIxS661Yz6TGPqFQ4VLzOF89WEYhfynSFQ==}
peerDependencies:
react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
source-map-js@1.2.1: source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -7144,6 +7162,11 @@ snapshots:
react: 18.3.1 react: 18.3.1
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
next-themes@0.4.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
next@15.0.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): next@15.0.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies: dependencies:
'@next/env': 15.0.3 '@next/env': 15.0.3
@@ -7712,6 +7735,11 @@ snapshots:
slugify@1.6.6: {} slugify@1.6.6: {}
sonner@2.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
source-map-js@1.2.1: {} source-map-js@1.2.1: {}
source-map@0.7.4: {} source-map@0.7.4: {}