mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-03 17:26:38 +00:00
add new tier select form
This commit is contained in:
@@ -2113,6 +2113,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"newPricingLicenseForm": {
|
||||||
|
"title": "Get a license",
|
||||||
|
"description": "Choose a plan and tell us how you plan to use Pangolin.",
|
||||||
|
"chooseTier": "Choose your plan",
|
||||||
|
"viewPricingLink": "See pricing, features, and limits",
|
||||||
|
"tiers": {
|
||||||
|
"starter": {
|
||||||
|
"title": "Starter",
|
||||||
|
"description": "Ideal for small teams and getting started. Includes core features and support."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"title": "Scale",
|
||||||
|
"description": "For growing teams and production use. Higher limits and priority support."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalUseOnly": "Personal use only (free license — no checkout)",
|
||||||
|
"buttons": {
|
||||||
|
"continueToCheckout": "Continue to checkout"
|
||||||
|
},
|
||||||
|
"toasts": {
|
||||||
|
"checkoutError": {
|
||||||
|
"title": "Checkout error",
|
||||||
|
"description": "Could not start checkout. Please try again."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"priority": "Priority",
|
"priority": "Priority",
|
||||||
"priorityDescription": "Higher priority routes are evaluated first. Priority = 100 means automatic ordering (system decides). Use another number to enforce manual priority.",
|
"priorityDescription": "Higher priority routes are evaluated first. Priority = 100 means automatic ordering (system decides). Use another number to enforce manual priority.",
|
||||||
"instanceName": "Instance Name",
|
"instanceName": "Instance Name",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { useRouter } from "next/navigation";
|
|||||||
import { toast } from "@app/hooks/useToast";
|
import { toast } from "@app/hooks/useToast";
|
||||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
import GenerateLicenseKeyForm from "./GenerateLicenseKeyForm";
|
import NewPricingLicenseForm from "./NewPricingLicenseForm";
|
||||||
|
|
||||||
type GnerateLicenseKeysTableProps = {
|
type GnerateLicenseKeysTableProps = {
|
||||||
licenseKeys: GeneratedLicenseKey[];
|
licenseKeys: GeneratedLicenseKey[];
|
||||||
@@ -198,7 +198,7 @@ export default function GenerateLicenseKeysTable({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<GenerateLicenseKeyForm
|
<NewPricingLicenseForm
|
||||||
open={showGenerateForm}
|
open={showGenerateForm}
|
||||||
setOpen={setShowGenerateForm}
|
setOpen={setShowGenerateForm}
|
||||||
orgId={orgId}
|
orgId={orgId}
|
||||||
|
|||||||
893
src/components/NewPricingLicenseForm.tsx
Normal file
893
src/components/NewPricingLicenseForm.tsx
Normal file
@@ -0,0 +1,893 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@app/components/ui/button";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage
|
||||||
|
} from "@app/components/ui/form";
|
||||||
|
import { Input } from "@app/components/ui/input";
|
||||||
|
import { Checkbox } from "@app/components/ui/checkbox";
|
||||||
|
import { toast } from "@app/hooks/useToast";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useForm, type Resolver } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
import CopyTextBox from "@app/components/CopyTextBox";
|
||||||
|
import {
|
||||||
|
Credenza,
|
||||||
|
CredenzaBody,
|
||||||
|
CredenzaClose,
|
||||||
|
CredenzaContent,
|
||||||
|
CredenzaDescription,
|
||||||
|
CredenzaFooter,
|
||||||
|
CredenzaHeader,
|
||||||
|
CredenzaTitle
|
||||||
|
} from "@app/components/Credenza";
|
||||||
|
import { formatAxiosError } from "@app/lib/api";
|
||||||
|
import { createApiClient } from "@app/lib/api";
|
||||||
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
|
import { GenerateNewLicenseResponse } from "@server/routers/generatedLicense/types";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import React from "react";
|
||||||
|
import { StrategySelect, StrategyOption } from "./StrategySelect";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
|
||||||
|
import { InfoIcon } from "lucide-react";
|
||||||
|
import { useUserContext } from "@app/hooks/useUserContext";
|
||||||
|
|
||||||
|
const TIER_TO_LICENSE_ID = {
|
||||||
|
starter: "small_license",
|
||||||
|
scale: "big_license"
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type FormProps = {
|
||||||
|
open: boolean;
|
||||||
|
setOpen: (open: boolean) => void;
|
||||||
|
orgId: string;
|
||||||
|
onGenerated?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function NewPricingLicenseForm({
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
orgId,
|
||||||
|
onGenerated
|
||||||
|
}: FormProps) {
|
||||||
|
const t = useTranslations();
|
||||||
|
const { env } = useEnvContext();
|
||||||
|
const api = createApiClient({ env });
|
||||||
|
const { user } = useUserContext();
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [generatedKey, setGeneratedKey] = useState<string | null>(null);
|
||||||
|
const [personalUseOnly, setPersonalUseOnly] = useState(false);
|
||||||
|
const [selectedTier, setSelectedTier] = useState<"starter" | "scale">(
|
||||||
|
"starter"
|
||||||
|
);
|
||||||
|
|
||||||
|
const personalFormSchema = z.object({
|
||||||
|
email: z.email(),
|
||||||
|
firstName: z.string().min(1),
|
||||||
|
lastName: z.string().min(1),
|
||||||
|
primaryUse: z.string().min(1),
|
||||||
|
country: z.string().min(1),
|
||||||
|
phoneNumber: z.string().optional(),
|
||||||
|
agreedToTerms: z.boolean().refine((val) => val === true),
|
||||||
|
complianceConfirmed: z.boolean().refine((val) => val === true)
|
||||||
|
});
|
||||||
|
|
||||||
|
const businessFormSchema = z.object({
|
||||||
|
email: z.email(),
|
||||||
|
firstName: z.string().min(1),
|
||||||
|
lastName: z.string().min(1),
|
||||||
|
primaryUse: z.string().min(1),
|
||||||
|
industry: z.string().min(1),
|
||||||
|
companyName: z.string().min(1),
|
||||||
|
companyWebsite: z.string().optional(),
|
||||||
|
companyPhoneNumber: z.string().optional(),
|
||||||
|
agreedToTerms: z.boolean().refine((val) => val === true),
|
||||||
|
complianceConfirmed: z.boolean().refine((val) => val === true)
|
||||||
|
});
|
||||||
|
|
||||||
|
type PersonalFormData = z.infer<typeof personalFormSchema>;
|
||||||
|
type BusinessFormData = z.infer<typeof businessFormSchema>;
|
||||||
|
|
||||||
|
const personalForm = useForm<PersonalFormData>({
|
||||||
|
resolver: zodResolver(personalFormSchema) as Resolver<PersonalFormData>,
|
||||||
|
defaultValues: {
|
||||||
|
email: user?.email || "",
|
||||||
|
firstName: "",
|
||||||
|
lastName: "",
|
||||||
|
primaryUse: "",
|
||||||
|
country: "",
|
||||||
|
phoneNumber: "",
|
||||||
|
agreedToTerms: false,
|
||||||
|
complianceConfirmed: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const businessForm = useForm<BusinessFormData>({
|
||||||
|
resolver: zodResolver(businessFormSchema) as Resolver<BusinessFormData>,
|
||||||
|
defaultValues: {
|
||||||
|
email: user?.email || "",
|
||||||
|
firstName: "",
|
||||||
|
lastName: "",
|
||||||
|
primaryUse: "",
|
||||||
|
industry: "",
|
||||||
|
companyName: "",
|
||||||
|
companyWebsite: "",
|
||||||
|
companyPhoneNumber: "",
|
||||||
|
agreedToTerms: false,
|
||||||
|
complianceConfirmed: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
resetForm();
|
||||||
|
setGeneratedKey(null);
|
||||||
|
setPersonalUseOnly(false);
|
||||||
|
setSelectedTier("starter");
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
personalForm.reset({
|
||||||
|
email: user?.email || "",
|
||||||
|
firstName: "",
|
||||||
|
lastName: "",
|
||||||
|
primaryUse: "",
|
||||||
|
country: "",
|
||||||
|
phoneNumber: "",
|
||||||
|
agreedToTerms: false,
|
||||||
|
complianceConfirmed: false
|
||||||
|
});
|
||||||
|
businessForm.reset({
|
||||||
|
email: user?.email || "",
|
||||||
|
firstName: "",
|
||||||
|
lastName: "",
|
||||||
|
primaryUse: "",
|
||||||
|
industry: "",
|
||||||
|
companyName: "",
|
||||||
|
companyWebsite: "",
|
||||||
|
companyPhoneNumber: "",
|
||||||
|
agreedToTerms: false,
|
||||||
|
complianceConfirmed: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const tierOptions: StrategyOption<"starter" | "scale">[] = [
|
||||||
|
{
|
||||||
|
id: "starter",
|
||||||
|
title: t("newPricingLicenseForm.tiers.starter.title"),
|
||||||
|
description: t("newPricingLicenseForm.tiers.starter.description")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "scale",
|
||||||
|
title: t("newPricingLicenseForm.tiers.scale.title"),
|
||||||
|
description: t("newPricingLicenseForm.tiers.scale.description")
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const submitLicenseRequest = async (
|
||||||
|
payload: Record<string, unknown>
|
||||||
|
): Promise<void> => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await api.put<
|
||||||
|
AxiosResponse<GenerateNewLicenseResponse>
|
||||||
|
>(`/org/${orgId}/license`, payload);
|
||||||
|
|
||||||
|
if (response.data.data?.licenseKey?.licenseKey) {
|
||||||
|
setGeneratedKey(response.data.data.licenseKey.licenseKey);
|
||||||
|
onGenerated?.();
|
||||||
|
toast({
|
||||||
|
title: t("generateLicenseKeyForm.toasts.success.title"),
|
||||||
|
description: t(
|
||||||
|
"generateLicenseKeyForm.toasts.success.description"
|
||||||
|
),
|
||||||
|
variant: "default"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
toast({
|
||||||
|
title: t("generateLicenseKeyForm.toasts.error.title"),
|
||||||
|
description: formatAxiosError(
|
||||||
|
e,
|
||||||
|
t("generateLicenseKeyForm.toasts.error.description")
|
||||||
|
),
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmitPersonal = async (values: PersonalFormData) => {
|
||||||
|
await submitLicenseRequest({
|
||||||
|
email: values.email,
|
||||||
|
useCaseType: "personal",
|
||||||
|
personal: {
|
||||||
|
firstName: values.firstName,
|
||||||
|
lastName: values.lastName,
|
||||||
|
aboutYou: { primaryUse: values.primaryUse },
|
||||||
|
personalInfo: {
|
||||||
|
country: values.country,
|
||||||
|
phoneNumber: values.phoneNumber || ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
business: undefined,
|
||||||
|
consent: {
|
||||||
|
agreedToTerms: values.agreedToTerms,
|
||||||
|
acknowledgedPrivacyPolicy: values.agreedToTerms,
|
||||||
|
complianceConfirmed: values.complianceConfirmed
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContinueToCheckout = async () => {
|
||||||
|
const valid = await businessForm.trigger();
|
||||||
|
if (!valid) return;
|
||||||
|
|
||||||
|
const values = businessForm.getValues();
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const tier = TIER_TO_LICENSE_ID[selectedTier];
|
||||||
|
const response = await api.post<AxiosResponse<string>>(
|
||||||
|
`/org/${orgId}/billing/create-checkout-session-license`,
|
||||||
|
{ tier }
|
||||||
|
);
|
||||||
|
const checkoutUrl = response.data.data;
|
||||||
|
if (checkoutUrl) {
|
||||||
|
window.location.href = checkoutUrl;
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: t(
|
||||||
|
"newPricingLicenseForm.toasts.checkoutError.title"
|
||||||
|
),
|
||||||
|
description: t(
|
||||||
|
"newPricingLicenseForm.toasts.checkoutError.description"
|
||||||
|
),
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: t("newPricingLicenseForm.toasts.checkoutError.title"),
|
||||||
|
description: formatAxiosError(
|
||||||
|
error,
|
||||||
|
t("newPricingLicenseForm.toasts.checkoutError.description")
|
||||||
|
),
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setOpen(false);
|
||||||
|
setGeneratedKey(null);
|
||||||
|
resetForm();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Credenza open={open} onOpenChange={handleClose}>
|
||||||
|
<CredenzaContent className="max-w-4xl">
|
||||||
|
<CredenzaHeader>
|
||||||
|
<CredenzaTitle>
|
||||||
|
{t("newPricingLicenseForm.title")}
|
||||||
|
</CredenzaTitle>
|
||||||
|
<CredenzaDescription>
|
||||||
|
{t("newPricingLicenseForm.description")}
|
||||||
|
</CredenzaDescription>
|
||||||
|
</CredenzaHeader>
|
||||||
|
<CredenzaBody>
|
||||||
|
<div className="space-y-6">
|
||||||
|
{generatedKey ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<CopyTextBox
|
||||||
|
text={generatedKey}
|
||||||
|
wrapText={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* Tier selection - required when not personal use */}
|
||||||
|
{!personalUseOnly && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium">
|
||||||
|
{t(
|
||||||
|
"newPricingLicenseForm.chooseTier"
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
<StrategySelect
|
||||||
|
options={tierOptions}
|
||||||
|
defaultValue={selectedTier}
|
||||||
|
onChange={(value) =>
|
||||||
|
setSelectedTier(value)
|
||||||
|
}
|
||||||
|
cols={2}
|
||||||
|
/>
|
||||||
|
<a
|
||||||
|
href="https://pangolin.net/pricing"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-sm text-primary hover:underline"
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"newPricingLicenseForm.viewPricingLink"
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Personal use only checkbox at the bottom of options */}
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="personal-use-only"
|
||||||
|
checked={personalUseOnly}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
setPersonalUseOnly(
|
||||||
|
checked === true
|
||||||
|
);
|
||||||
|
if (checked) {
|
||||||
|
businessForm.reset();
|
||||||
|
} else {
|
||||||
|
personalForm.reset();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="personal-use-only"
|
||||||
|
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"newPricingLicenseForm.personalUseOnly"
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* License disclosure - only when personal use */}
|
||||||
|
{personalUseOnly && (
|
||||||
|
<Alert variant="neutral">
|
||||||
|
<InfoIcon className="h-4 w-4" />
|
||||||
|
<AlertTitle>
|
||||||
|
{t(
|
||||||
|
"generateLicenseKeyForm.alerts.commercialUseDisclosure.title"
|
||||||
|
)}
|
||||||
|
</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
{t(
|
||||||
|
"generateLicenseKeyForm.alerts.commercialUseDisclosure.description"
|
||||||
|
)
|
||||||
|
.split(
|
||||||
|
"Fossorial Commercial License Terms"
|
||||||
|
)
|
||||||
|
.map((part, index) => (
|
||||||
|
<span key={index}>
|
||||||
|
{part}
|
||||||
|
{index === 0 && (
|
||||||
|
<a
|
||||||
|
href="https://pangolin.net/fcl.html"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
Fossorial
|
||||||
|
Commercial
|
||||||
|
License Terms
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Personal form: only when personal use only is checked */}
|
||||||
|
{personalUseOnly && (
|
||||||
|
<Form {...personalForm}>
|
||||||
|
<form
|
||||||
|
onSubmit={personalForm.handleSubmit(
|
||||||
|
onSubmitPersonal
|
||||||
|
)}
|
||||||
|
className="space-y-4"
|
||||||
|
id="new-pricing-license-personal-form"
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={
|
||||||
|
personalForm.control
|
||||||
|
}
|
||||||
|
name="firstName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
"generateLicenseKeyForm.form.firstName"
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={
|
||||||
|
personalForm.control
|
||||||
|
}
|
||||||
|
name="lastName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
"generateLicenseKeyForm.form.lastName"
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={personalForm.control}
|
||||||
|
name="primaryUse"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
"generateLicenseKeyForm.form.primaryUseQuestion"
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={
|
||||||
|
personalForm.control
|
||||||
|
}
|
||||||
|
name="country"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
"generateLicenseKeyForm.form.country"
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={
|
||||||
|
personalForm.control
|
||||||
|
}
|
||||||
|
name="phoneNumber"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
"generateLicenseKeyForm.form.phoneNumberOptional"
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4 pt-4">
|
||||||
|
<FormField
|
||||||
|
control={
|
||||||
|
personalForm.control
|
||||||
|
}
|
||||||
|
name="agreedToTerms"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
field.value
|
||||||
|
}
|
||||||
|
onCheckedChange={
|
||||||
|
field.onChange
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className="space-y-1 leading-none">
|
||||||
|
<FormLabel className="text-sm font-normal">
|
||||||
|
<div>
|
||||||
|
{t(
|
||||||
|
"signUpTerms.IAgreeToThe"
|
||||||
|
)}{" "}
|
||||||
|
<a
|
||||||
|
href="https://pangolin.net/terms-of-service.html"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"signUpTerms.termsOfService"
|
||||||
|
)}{" "}
|
||||||
|
</a>
|
||||||
|
{t(
|
||||||
|
"signUpTerms.and"
|
||||||
|
)}{" "}
|
||||||
|
<a
|
||||||
|
href="https://pangolin.net/privacy-policy.html"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"signUpTerms.privacyPolicy"
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</FormLabel>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={
|
||||||
|
personalForm.control
|
||||||
|
}
|
||||||
|
name="complianceConfirmed"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
field.value
|
||||||
|
}
|
||||||
|
onCheckedChange={
|
||||||
|
field.onChange
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className="space-y-1 leading-none">
|
||||||
|
<FormLabel className="text-sm font-normal">
|
||||||
|
<div>
|
||||||
|
{t(
|
||||||
|
"generateLicenseKeyForm.form.complianceConfirmation"
|
||||||
|
)}{" "}
|
||||||
|
<a
|
||||||
|
href="https://pangolin.net/fcl.html"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
https://pangolin.net/fcl.html
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</FormLabel>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Business form: when not personal use - enter business info then continue to checkout */}
|
||||||
|
{!personalUseOnly && (
|
||||||
|
<Form {...businessForm}>
|
||||||
|
<form
|
||||||
|
className="space-y-4"
|
||||||
|
id="new-pricing-license-business-form"
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={
|
||||||
|
businessForm.control
|
||||||
|
}
|
||||||
|
name="firstName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
"generateLicenseKeyForm.form.firstName"
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={
|
||||||
|
businessForm.control
|
||||||
|
}
|
||||||
|
name="lastName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
"generateLicenseKeyForm.form.lastName"
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={businessForm.control}
|
||||||
|
name="primaryUse"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
"generateLicenseKeyForm.form.primaryUseQuestion"
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={businessForm.control}
|
||||||
|
name="industry"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
"generateLicenseKeyForm.form.industryQuestion"
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={businessForm.control}
|
||||||
|
name="companyName"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
"generateLicenseKeyForm.form.companyName"
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<FormField
|
||||||
|
control={
|
||||||
|
businessForm.control
|
||||||
|
}
|
||||||
|
name="companyWebsite"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
"generateLicenseKeyForm.form.companyWebsite"
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={
|
||||||
|
businessForm.control
|
||||||
|
}
|
||||||
|
name="companyPhoneNumber"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
"generateLicenseKeyForm.form.companyPhoneNumber"
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4 pt-4">
|
||||||
|
<FormField
|
||||||
|
control={
|
||||||
|
businessForm.control
|
||||||
|
}
|
||||||
|
name="agreedToTerms"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
field.value
|
||||||
|
}
|
||||||
|
onCheckedChange={
|
||||||
|
field.onChange
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className="space-y-1 leading-none">
|
||||||
|
<FormLabel className="text-sm font-normal">
|
||||||
|
<div>
|
||||||
|
{t(
|
||||||
|
"signUpTerms.IAgreeToThe"
|
||||||
|
)}{" "}
|
||||||
|
<a
|
||||||
|
href="https://pangolin.net/terms-of-service.html"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"signUpTerms.termsOfService"
|
||||||
|
)}{" "}
|
||||||
|
</a>
|
||||||
|
{t(
|
||||||
|
"signUpTerms.and"
|
||||||
|
)}{" "}
|
||||||
|
<a
|
||||||
|
href="https://pangolin.net/privacy-policy.html"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"signUpTerms.privacyPolicy"
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</FormLabel>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={
|
||||||
|
businessForm.control
|
||||||
|
}
|
||||||
|
name="complianceConfirmed"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
field.value
|
||||||
|
}
|
||||||
|
onCheckedChange={
|
||||||
|
field.onChange
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className="space-y-1 leading-none">
|
||||||
|
<FormLabel className="text-sm font-normal">
|
||||||
|
<div>
|
||||||
|
{t(
|
||||||
|
"generateLicenseKeyForm.form.complianceConfirmation"
|
||||||
|
)}{" "}
|
||||||
|
<a
|
||||||
|
href="https://pangolin.net/fcl.html"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
https://pangolin.net/fcl.html
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</FormLabel>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CredenzaBody>
|
||||||
|
<CredenzaFooter>
|
||||||
|
<CredenzaClose asChild>
|
||||||
|
<Button variant="outline">
|
||||||
|
{t("generateLicenseKeyForm.buttons.close")}
|
||||||
|
</Button>
|
||||||
|
</CredenzaClose>
|
||||||
|
|
||||||
|
{!generatedKey && personalUseOnly && (
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
form="new-pricing-license-personal-form"
|
||||||
|
disabled={loading}
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"generateLicenseKeyForm.buttons.generateLicenseKey"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!generatedKey && !personalUseOnly && (
|
||||||
|
<Button
|
||||||
|
onClick={handleContinueToCheckout}
|
||||||
|
disabled={loading}
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"newPricingLicenseForm.buttons.continueToCheckout"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</CredenzaFooter>
|
||||||
|
</CredenzaContent>
|
||||||
|
</Credenza>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user