diff --git a/apps/website/app/[locale]/license/success/page.tsx b/apps/website/app/[locale]/license/success/page.tsx index 02ab589..596f73b 100644 --- a/apps/website/app/[locale]/license/success/page.tsx +++ b/apps/website/app/[locale]/license/success/page.tsx @@ -80,8 +80,6 @@ export default function LicenseSuccess() { toast.success("Copied to clipboard"); }; - console.log(error); - return (
diff --git a/apps/website/app/[locale]/reset-license/page.tsx b/apps/website/app/[locale]/reset-license/page.tsx index f898edb..b6be0d5 100644 --- a/apps/website/app/[locale]/reset-license/page.tsx +++ b/apps/website/app/[locale]/reset-license/page.tsx @@ -1,12 +1,14 @@ "use client"; import { Container } from "@/components/Container"; +import { SERVER_LICENSE_URL } from "@/components/pricing"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { useState } from "react"; - +import { toast } from "sonner"; export default function ResetLicensePage() { const [email, setEmail] = useState(""); + const [showOtp, setShowOtp] = useState(false); const [isLoading, setIsLoading] = useState(false); const handleSubmit = async (e: React.FormEvent) => { @@ -14,24 +16,35 @@ export default function ResetLicensePage() { 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)); + const result = await fetch(`${SERVER_LICENSE_URL}/license/verification`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ email }), + }); - // toast({ - // title: "Success!", - // description: - // "If an account exists with this email, you will receive instructions to reset your license.", - // variant: "default", - // }); + const data = await result.json(); + console.log(data); - setEmail(""); + if (data.error) { + toast.error( + "Error sending verification code. Please try again later.", + { + description: data.error, + }, + ); + } else { + toast.success( + "We've sent you a code to verify your email. Please check your email for the code.", + ); + setShowOtp(true); + } } catch (error) { - // toast({ - // title: "Error", - // description: "Something went wrong. Please try again later.", - // variant: "destructive", - // }); + toast.error("Something went wrong. Please try again later.", { + duration: 15000, + description: error instanceof Error ? error.message : "Unknown error", + }); } finally { setIsLoading(false); } diff --git a/apps/website/app/layout.tsx b/apps/website/app/layout.tsx index 4624d6c..393afd6 100644 --- a/apps/website/app/layout.tsx +++ b/apps/website/app/layout.tsx @@ -64,7 +64,7 @@ export default async function RootLayout({ {children} - + diff --git a/apps/website/components/ui/input-otp.tsx b/apps/website/components/ui/input-otp.tsx new file mode 100644 index 0000000..aa6beed --- /dev/null +++ b/apps/website/components/ui/input-otp.tsx @@ -0,0 +1,71 @@ +"use client"; + +import { OTPInput, OTPInputContext } from "input-otp"; +import { Dot } from "lucide-react"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const InputOTP = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, containerClassName, ...props }, ref) => ( + +)); +InputOTP.displayName = "InputOTP"; + +const InputOTPGroup = React.forwardRef< + React.ElementRef<"div">, + React.ComponentPropsWithoutRef<"div"> +>(({ className, ...props }, ref) => ( +
+)); +InputOTPGroup.displayName = "InputOTPGroup"; + +const InputOTPSlot = React.forwardRef< + React.ElementRef<"div">, + React.ComponentPropsWithoutRef<"div"> & { index: number } +>(({ index, className, ...props }, ref) => { + const inputOTPContext = React.useContext(OTPInputContext); + const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]; + + return ( +
+ {char} + {hasFakeCaret && ( +
+
+
+ )} +
+ ); +}); +InputOTPSlot.displayName = "InputOTPSlot"; + +const InputOTPSeparator = React.forwardRef< + React.ElementRef<"div">, + React.ComponentPropsWithoutRef<"div"> +>(({ ...props }, ref) => ( +
+ +
+)); +InputOTPSeparator.displayName = "InputOTPSeparator"; + +export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }; diff --git a/apps/website/package.json b/apps/website/package.json index 313722a..b1fb652 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -36,6 +36,7 @@ "copy-to-clipboard": "3.3.3", "framer-motion": "^11.3.19", "hast-util-to-jsx-runtime": "2.3.5", + "input-otp": "^1.4.2", "lucide-react": "0.364.0", "next": "15.2.0", "next-intl": "^3.26.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 66ec4e1..0c2047c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -163,6 +163,9 @@ importers: hast-util-to-jsx-runtime: specifier: 2.3.5 version: 2.3.5 + input-otp: + specifier: ^1.4.2 + version: 1.4.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) lucide-react: specifier: 0.364.0 version: 0.364.0(react@18.2.0) @@ -2551,6 +2554,12 @@ packages: inline-style-parser@0.2.3: resolution: {integrity: sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==} + input-otp@1.4.2: + resolution: {integrity: sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + intl-messageformat@10.5.14: resolution: {integrity: sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==} @@ -6468,6 +6477,11 @@ snapshots: inline-style-parser@0.2.3: {} + input-otp@1.4.2(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) + intl-messageformat@10.5.14: dependencies: '@formatjs/ecma402-abstract': 2.0.0