153 lines
5.1 KiB
TypeScript
153 lines
5.1 KiB
TypeScript
import { useEffect, useMemo, useState } from "react";
|
|
import { Link, useNavigate } from "react-router-dom";
|
|
import PageMeta from "../../components/common/PageMeta";
|
|
import SignUpFormUnified from "../../components/auth/SignUpFormUnified";
|
|
|
|
interface Plan {
|
|
id: number;
|
|
name: string;
|
|
slug: string;
|
|
price: string | number;
|
|
billing_cycle: string;
|
|
is_active: boolean;
|
|
max_users: number;
|
|
max_sites: number;
|
|
max_keywords: number;
|
|
max_ahrefs_queries: number;
|
|
included_credits: number;
|
|
features: string[];
|
|
}
|
|
|
|
export default function SignUp() {
|
|
const navigate = useNavigate();
|
|
const planSlug = useMemo(() => {
|
|
const params = new URLSearchParams(window.location.search);
|
|
return params.get("plan") || "";
|
|
}, []);
|
|
|
|
const [plans, setPlans] = useState<Plan[]>([]);
|
|
const [plansLoading, setPlansLoading] = useState(true);
|
|
const [selectedPlan, setSelectedPlan] = useState<Plan | null>(null);
|
|
const [geoChecked, setGeoChecked] = useState(false);
|
|
|
|
// Check geo location and redirect PK users to /signup/pk
|
|
// Using free public API: https://api.country.is (no signup required, CORS enabled)
|
|
useEffect(() => {
|
|
const checkGeoAndRedirect = async () => {
|
|
try {
|
|
// Free public geo API - no signup required
|
|
const response = await fetch('https://api.country.is/', {
|
|
signal: AbortSignal.timeout(3000), // 3 second timeout
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
const countryCode = data?.country;
|
|
|
|
if (countryCode === 'PK') {
|
|
// Preserve query params when redirecting
|
|
const queryString = window.location.search;
|
|
navigate(`/signup/pk${queryString}`, { replace: true });
|
|
return;
|
|
}
|
|
}
|
|
} catch (err) {
|
|
// Silently fail - continue with global signup
|
|
console.log('Geo detection failed, using global signup');
|
|
}
|
|
setGeoChecked(true);
|
|
};
|
|
|
|
checkGeoAndRedirect();
|
|
}, [navigate]);
|
|
|
|
useEffect(() => {
|
|
const fetchPlans = async () => {
|
|
setPlansLoading(true);
|
|
try {
|
|
const API_BASE_URL = import.meta.env.VITE_BACKEND_URL || "https://api.igny8.com/api";
|
|
const res = await fetch(`${API_BASE_URL}/v1/auth/plans/`);
|
|
const data = await res.json();
|
|
const allPlans = data?.results || [];
|
|
|
|
// Show all active plans (including free plan)
|
|
const publicPlans = allPlans
|
|
.filter((p: Plan) => p.is_active)
|
|
.sort((a: Plan, b: Plan) => {
|
|
const priceA = typeof a.price === 'number' ? a.price : parseFloat(String(a.price || 0));
|
|
const priceB = typeof b.price === 'number' ? b.price : parseFloat(String(b.price || 0));
|
|
return priceA - priceB;
|
|
});
|
|
|
|
setPlans(publicPlans);
|
|
|
|
// Auto-select plan from URL or default to first plan
|
|
if (planSlug) {
|
|
const plan = publicPlans.find((p: Plan) => p.slug === planSlug);
|
|
if (plan) {
|
|
setSelectedPlan(plan);
|
|
} else {
|
|
setSelectedPlan(publicPlans[0] || null);
|
|
}
|
|
} else {
|
|
setSelectedPlan(publicPlans[0] || null);
|
|
}
|
|
} catch (e) {
|
|
console.error('Failed to load plans:', e);
|
|
} finally {
|
|
setPlansLoading(false);
|
|
}
|
|
};
|
|
fetchPlans();
|
|
}, [planSlug]);
|
|
|
|
// Don't render until geo check is complete (prevents flash)
|
|
if (!geoChecked) {
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 flex items-center justify-center">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-brand-500"></div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<PageMeta
|
|
title="Sign Up - IGNY8"
|
|
description="Create your IGNY8 account and start building topical authority with AI-powered content"
|
|
/>
|
|
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800">
|
|
<div className="flex min-h-screen">
|
|
{/* Left Side - Signup Form */}
|
|
<SignUpFormUnified
|
|
plans={plans}
|
|
selectedPlan={selectedPlan}
|
|
onPlanSelect={setSelectedPlan}
|
|
plansLoading={plansLoading}
|
|
/>
|
|
|
|
{/* Right Side - Pricing Plans */}
|
|
<div className="hidden lg:flex lg:w-1/2 bg-gradient-to-br from-brand-50 to-purple-50 dark:from-gray-900 dark:to-gray-800 p-8 xl:p-12 items-start justify-center relative">
|
|
{/* Logo - Top Right */}
|
|
<Link to="/" className="absolute top-6 right-6">
|
|
<img
|
|
src="/images/logo/IGNY8_LIGHT_LOGO.png"
|
|
alt="IGNY8"
|
|
className="h-12 w-auto"
|
|
/>
|
|
</Link>
|
|
|
|
<div className="w-full max-w-2xl mt-20">
|
|
|
|
{/* Pricing Plans Component Will Load Here */}
|
|
<div id="signup-pricing-plans" className="w-full">
|
|
{/* Plans will be rendered by SignUpFormUnified */}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|