Separate private and agency subscription plans, add LinkedIn OAuth integration

This commit is contained in:
2026-04-13 19:43:10 +02:00
parent 64465c3a67
commit da19bbac4b
2 changed files with 228 additions and 104 deletions
+168 -32
View File
@@ -17,21 +17,23 @@ DB_PATH = os.getenv("DB_PATH", "/var/www/autojobs/autojobs.db")
router = APIRouter(prefix="/autojobs/api")
# --- Plans & Pricing ---
PLANS = {
"free": {"name": "Free", "applications": 5, "ai_customize": 5, "price": 0, "type": "jobseeker"},
"starter": {"name": "Starter", "applications": 50, "ai_customize": 50, "price": 9, "type": "jobseeker"},
"pro": {"name": "Pro", "applications": 100, "ai_customize": 100, "price": 39, "type": "jobseeker"},
"ultra": {"name": "Ultra", "applications": 200, "ai_customize": 200, "price": 69, "type": "jobseeker"},
"business": {"name": "Business", "applications": 500, "ai_customize": 500, "price": 119, "type": "jobseeker"},
"unlimited": {"name": "Unlimited", "applications": 99999, "ai_customize": 99999, "price": 319, "type": "jobseeker"},
# Private User Plans (job seekers)
PRIVATE_PLANS = {
"free": {"name": "Free", "applications": 5, "ai_customize": 5, "price": 0},
"starter": {"name": "Starter", "applications": 50, "ai_customize": 50, "price": 9},
"pro": {"name": "Pro", "applications": 100, "ai_customize": 100, "price": 39},
"ultra": {"name": "Ultra", "applications": 200, "ai_customize": 200, "price": 69},
}
# Alias for backwards compatibility
PLANS = PRIVATE_PLANS
# Agency Plans (recruiting agencies - manage multiple clients)
AGENCY_PLANS = {
"agency_starter": {"name": "Agency Starter", "submissions": 1000, "clients": 10, "price": 555, "type": "agency"},
"agency_growth": {"name": "Agency Growth", "submissions": 3000, "clients": 50, "price": 999, "type": "agency"},
"agency_scale": {"name": "Agency Scale", "submissions": 5000, "clients": 150, "price": 1499, "type": "agency"},
"agency_enterprise": {"name": "Agency Enterprise", "submissions": 10000, "clients": 500, "price": 2222, "type": "agency"},
"agency_starter": {"name": "Agency Starter", "submissions": 1000, "clients": 10, "price": 555},
"agency_growth": {"name": "Agency Growth", "submissions": 3000, "clients": 50, "price": 999},
"agency_scale": {"name": "Agency Scale", "submissions": 5000, "clients": 150, "price": 1499},
"agency_enterprise": {"name": "Agency Enterprise", "submissions": 10000, "clients": 500, "price": 2222},
}
# --- Database ---
@@ -45,9 +47,13 @@ def init_db():
id TEXT PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
name TEXT,
user_type TEXT DEFAULT 'private',
plan TEXT DEFAULT 'free',
api_keys TEXT DEFAULT '{}',
profile TEXT DEFAULT '{}',
linkedin_access_token TEXT DEFAULT '',
linkedin_refresh_token TEXT DEFAULT '',
linkedin_profile_id TEXT DEFAULT '',
monthly_count INTEGER DEFAULT 0,
ai_customize_count INTEGER DEFAULT 0,
billing_cycle_start TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
@@ -131,10 +137,11 @@ def get_user_row(user_id: str):
return dict(user) if user else None
def check_plan_limit(user: dict, action: str, count: int = 1) -> dict:
"""Check if user has reached their plan limit. Returns {"allowed": bool, "remaining": int}"""
"""Check if user has reached their plan limit"""
plan_name = user.get("plan", "free")
user_type = user.get("user_type", "private")
# Check if agency plan
# Agency plans
if plan_name in AGENCY_PLANS:
plan = AGENCY_PLANS[plan_name]
if action == "submission":
@@ -143,10 +150,10 @@ def check_plan_limit(user: dict, action: str, count: int = 1) -> dict:
else:
return {"allowed": True, "remaining": 99999}
remaining = max(0, limit - used)
return {"allowed": remaining >= count, "remaining": remaining, "limit": limit, "used": used, "type": "agency"}
return {"allowed": remaining >= count, "remaining": remaining, "limit": limit, "used": used, "type": "agency", "user_type": "agency"}
# Jobseeker plan
plan = PLANS.get(plan_name, PLANS["free"])
# Private user plans
plan = PRIVATE_PLANS.get(plan_name, PRIVATE_PLANS["free"])
if action == "application":
used = user.get("monthly_count", 0)
limit = plan["applications"]
@@ -157,7 +164,7 @@ def check_plan_limit(user: dict, action: str, count: int = 1) -> dict:
return {"allowed": True, "remaining": 99999}
remaining = max(0, limit - used)
return {"allowed": remaining >= count, "remaining": remaining, "limit": limit, "used": used, "type": "jobseeker"}
return {"allowed": remaining >= count, "remaining": remaining, "limit": limit, "used": used, "type": "jobseeker", "user_type": "private"}
def increment_usage(user_id: str, action: str):
conn = sqlite3.connect(DB_PATH)
@@ -231,17 +238,133 @@ def health():
# --- User Routes ---
# --- LinkedIn OAuth ---
@router.get("/auth/linkedin")
def linkedin_auth(redirect_uri: str = "https://hostpioneers.com/autojobs/dashboard"):
"""Start LinkedIn OAuth flow"""
client_id = os.getenv("LINKEDIN_CLIENT_ID", "")
if not client_id:
raise HTTPException(status_code=500, detail="LinkedIn OAuth not configured")
scope = "openid profile email w_member_social"
state = str(uuid.uuid4())
auth_url = (
f"https://www.linkedin.com/oauth/v2/authorization"
f"?response_type=code"
f"&client_id={client_id}"
f"&redirect_uri={redirect_uri}"
f"&scope={scope}"
f"&state={state}"
)
return {"auth_url": auth_url}
@router.post("/auth/linkedin/callback")
def linkedin_callback(code: str, user=Depends(get_current_user)):
"""Exchange LinkedIn code for access token and store it"""
client_id = os.getenv("LINKEDIN_CLIENT_ID", "")
client_secret = os.getenv("LINKEDIN_CLIENT_SECRET", "")
redirect_uri = "https://hostpioneers.com/autojobs/dashboard"
import requests
token_url = "https://www.linkedin.com/oauth/v2/accessToken"
data = {
"grant_type": "authorization_code",
"code": code,
"redirect_uri": redirect_uri,
"client_id": client_id,
"client_secret": client_secret,
}
try:
resp = requests.post(token_url, data=data, timeout=10)
if resp.status_code == 200:
token_data = resp.json()
access_token = token_data.get("access_token", "")
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
c.execute("""
UPDATE users SET
linkedin_access_token = ?,
linkedin_refresh_token = ?
WHERE id = ?
""", (access_token, token_data.get("refresh_token", ""), user["id"]))
conn.commit()
conn.close()
return {"success": True, "message": "LinkedIn connected"}
else:
raise HTTPException(status_code=400, detail="Failed to get LinkedIn token")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/linkedin/resumes")
def get_linkedin_resumes(user=Depends(get_current_user)):
"""Get user's LinkedIn saved resumes"""
user_row = get_user_row(user["id"])
if not user_row or not user_row.get("linkedin_access_token"):
raise HTTPException(status_code=400, detail="LinkedIn not connected")
import requests
headers = {"Authorization": f"Bearer {user_row['linkedin_access_token']}"}
# Get LinkedIn profile to get resume info
try:
# LinkedIn Lite Profile
profile_resp = requests.get(
"https://api.linkedin.com/v2/me",
headers=headers,
timeout=10
)
# For MVP - return placeholder (LinkedIn API requires special approval for resume access)
# In production you'd use LinkedIn's resume API
return {
"resumes": [
{"id": "linkedin_default", "name": "LinkedIn Saved Resume", "source": "linkedin"}
],
"message": "Connect your LinkedIn to import your saved resumes"
}
except:
return {
"resumes": [],
"message": "Could not access LinkedIn. Please re-connect."
}
@router.post("/linkedin/disconnect")
def linkedin_disconnect(user=Depends(get_current_user)):
"""Disconnect LinkedIn account"""
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
c.execute("""
UPDATE users SET
linkedin_access_token = '',
linkedin_refresh_token = '',
linkedin_profile_id = ''
WHERE id = ?
""", (user["id"],))
conn.commit()
conn.close()
return {"success": True}
@router.post("/users")
def create_user(email: str, name: str = ""):
"""Create new user with free plan"""
def create_user(data: dict):
"""Create new user (private or agency)"""
user_id = str(uuid.uuid4())
email = data.get("email", "")
name = data.get("name", "")
user_type = data.get("user_type", "private")
plan = data.get("plan", "free")
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
try:
c.execute("""
INSERT INTO users (id, email, name, plan, monthly_count, ai_customize_count)
VALUES (?, ?, ?, 'free', 0, 0)
""", (user_id, email, name))
INSERT INTO users (id, email, name, user_type, plan, monthly_count, ai_customize_count)
VALUES (?, ?, ?, ?, ?, 0, 0)
""", (user_id, email, name, user_type, plan))
conn.commit()
except sqlite3.IntegrityError:
conn.close()
@@ -250,17 +373,24 @@ def create_user(email: str, name: str = ""):
return {"user_id": user_id, "email": email, "plan": "free"}
@router.post("/users/login")
def login(email: str):
def login(data: dict):
"""Login by email — returns user_id as token (MVP auth)"""
email = data.get("email", "")
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
c = conn.cursor()
c.execute("SELECT id, email, name, plan FROM users WHERE email = ?", (email,))
c.execute("SELECT id, email, name, user_type, plan FROM users WHERE email = ?", (email,))
row = c.fetchone()
conn.close()
if not row:
raise HTTPException(status_code=404, detail="User not found")
return {"user_id": row["id"], "email": row["email"], "name": row["name"], "plan": row["plan"]}
return {
"user_id": row["id"],
"email": row["email"],
"name": row["name"],
"user_type": row["user_type"],
"plan": row["plan"]
}
@router.get("/users/me")
def get_me(user=Depends(get_current_user)):
@@ -268,23 +398,29 @@ def get_me(user=Depends(get_current_user)):
user_row = get_user_row(user["id"])
if not user_row:
raise HTTPException(status_code=404, detail="User not found")
plan = PLANS.get(user_row["plan"], PLANS["free"])
user_type = user_row.get("user_type", "private")
if user_type == "agency":
plan = AGENCY_PLANS.get(user_row["plan"], AGENCY_PLANS["agency_starter"])
else:
plan = PRIVATE_PLANS.get(user_row["plan"], PRIVATE_PLANS["free"])
return {
"id": user_row["id"],
"email": user_row["email"],
"name": user_row["name"],
"user_type": user_type,
"plan": user_row["plan"],
"plan_details": {
"name": plan["name"],
"applications_limit": plan["applications"],
"ai_customize_limit": plan["ai_customize"],
"price": plan["price"]
},
"linkedin_connected": bool(user_row.get("linkedin_access_token")),
"usage": {
"applications_this_month": user_row["monthly_count"],
"ai_customizations_this_month": user_row["ai_customize_count"],
"applications_remaining": max(0, plan["applications"] - user_row["monthly_count"]),
"ai_customize_remaining": max(0, plan["ai_customize"] - user_row["ai_customize_count"])
"applications_remaining": max(0, plan.get("applications", plan.get("submissions", 99999)) - user_row["monthly_count"]),
"ai_customize_remaining": max(0, plan.get("ai_customize", 99999) - user_row["ai_customize_count"])
},
"billing_cycle_start": user_row["billing_cycle_start"],
"created_at": user_row["created_at"]
@@ -349,9 +485,9 @@ def get_profile(user=Depends(get_current_user)):
@router.get("/plans")
def list_plans():
"""List all available plans (jobseeker + agency)"""
"""List all available plans separated by user type"""
return {
"jobseeker_plans": PLANS,
"private_plans": PRIVATE_PLANS,
"agency_plans": AGENCY_PLANS
}
+60 -72
View File
@@ -1,14 +1,12 @@
import Link from "next/link"
const jobseekerPlans = [
const privatePlans = [
{
name: "Free",
price: "$0",
period: "forever",
apps: "5",
badge: "",
highlight: false,
features: ["5 AI job applications/month", "5 AI resume customizations/month", "Application tracker", "Add your own API keys"],
features: ["5 job applications/month", "5 AI resume customizations", "Basic application tracker"],
cta: "Get Started",
ctaStyle: "bg-white/10 hover:bg-white/20"
},
@@ -17,10 +15,8 @@ const jobseekerPlans = [
price: "$9",
period: "/mo",
apps: "50",
badge: "",
highlight: false,
features: ["50 AI job applications/month", "50 AI resume customizations/month", "Application tracker", "Add your own API keys"],
cta: "Start Applying",
features: ["50 job applications/month", "50 AI resume customizations", "Add your own API keys", "Email support"],
cta: "Start Now",
ctaStyle: "bg-blue-500 hover:bg-blue-600"
},
{
@@ -28,9 +24,9 @@ const jobseekerPlans = [
price: "$39",
period: "/mo",
apps: "100",
badge: "Popular",
badge: "Most Popular",
highlight: true,
features: ["100 AI job applications/month", "100 AI resume customizations/month", "Application tracker", "Add your own API keys", "Email notifications"],
features: ["100 job applications/month", "100 AI resume customizations", "Add your own API keys", "LinkedIn resume import", "Priority support"],
cta: "Go Pro",
ctaStyle: "bg-blue-500 hover:bg-blue-600"
},
@@ -39,9 +35,7 @@ const jobseekerPlans = [
price: "$69",
period: "/mo",
apps: "200",
badge: "",
highlight: false,
features: ["200 AI job applications/month", "200 AI resume customizations/month", "Priority processing", "Email + SMS notifications"],
features: ["200 job applications/month", "200 AI resume customizations", "Add your own API keys", "LinkedIn resume import", "SMS notifications"],
cta: "Go Ultra",
ctaStyle: "bg-slate-600 hover:bg-slate-500"
},
@@ -56,7 +50,7 @@ const agencyPlans = [
clients: "10",
badge: "",
highlight: false,
features: ["1,000 job submissions/month", "Up to 10 client profiles", "AI resume tailoring", "White-label dashboard", "Priority support"],
features: ["1,000 submissions/month", "10 client profiles", "White-label dashboard", "API access"],
cta: "Start Agency",
ctaStyle: "bg-purple-600 hover:bg-purple-500"
},
@@ -68,8 +62,8 @@ const agencyPlans = [
clients: "50",
badge: "",
highlight: true,
features: ["3,000 job submissions/month", "Up to 50 client profiles", "AI resume tailoring", "White-label dashboard", "Priority support"],
cta: "Grow Agency",
features: ["3,000 submissions/month", "50 client profiles", "White-label dashboard", "Priority support"],
cta: "Grow",
ctaStyle: "bg-purple-600 hover:bg-purple-500"
},
{
@@ -80,8 +74,8 @@ const agencyPlans = [
clients: "150",
badge: "",
highlight: false,
features: ["5,000 job submissions/month", "Up to 150 client profiles", "AI resume tailoring", "White-label dashboard", "Dedicated account manager"],
cta: "Scale Up",
features: ["5,000 submissions/month", "150 client profiles", "White-label dashboard", "Dedicated account manager"],
cta: "Scale",
ctaStyle: "bg-slate-600 hover:bg-slate-500"
},
{
@@ -92,8 +86,8 @@ const agencyPlans = [
clients: "500",
badge: "",
highlight: false,
features: ["10,000 job submissions/month", "Up to 500 client profiles", "AI resume tailoring", "White-label dashboard", "Dedicated account manager", "Custom integrations"],
cta: "Get Enterprise",
features: ["10,000 submissions/month", "500 client profiles", "White-label dashboard", "Dedicated account manager", "Custom integrations"],
cta: "Enterprise",
ctaStyle: "bg-slate-600 hover:bg-slate-500"
},
]
@@ -106,8 +100,10 @@ export default function LandingPage() {
<div className="text-2xl font-bold text-white">
Auto<span className="text-blue-400">Jobs</span>
</div>
<div className="flex gap-6">
<Link href="/autojobs/login" className="text-slate-300 hover:text-white transition">Login</Link>
<div className="flex gap-4">
<Link href="/autojobs/login" className="text-slate-300 hover:text-white transition flex items-center gap-2">
Login
</Link>
<Link href="/autojobs/signup" className="px-5 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg font-medium transition">
Get Started
</Link>
@@ -115,20 +111,18 @@ export default function LandingPage() {
</nav>
{/* Hero */}
<section className="py-24 px-6 text-center">
<section className="py-20 px-6 text-center">
<div className="max-w-4xl mx-auto">
<div className="inline-block px-4 py-1.5 rounded-full bg-blue-500/20 border border-blue-500/30 text-blue-300 text-sm mb-6">
Your Personal AI Job Agent
AI-Powered Job Application Automation
</div>
<h1 className="text-5xl md:text-6xl font-bold text-white mb-6 leading-tight">
Stop Applying to Jobs.
Stop Manually Applying.
<br />
<span className="text-blue-400">Let AI Do It For You.</span>
<span className="text-blue-400">Let AI Handle It.</span>
</h1>
<p className="text-xl text-slate-300 mb-10 max-w-2xl mx-auto leading-relaxed">
Upload your resume once. Set your keywords. Our AI finds every matching job,
rewrites your resume + cover letter for each one, and applies automatically
while you sleep.
Upload your resume once. Set your preferences. AI finds matching jobs, rewrites your resume for each one, and applies while you sleep.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Link
@@ -137,50 +131,59 @@ export default function LandingPage() {
>
Start Free 5 Applications
</Link>
<Link
href="/autojobs/login"
className="px-10 py-4 bg-white/10 hover:bg-white/20 text-white rounded-xl font-semibold text-lg border border-white/20 transition flex items-center justify-center gap-2"
>
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M19 3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14m-.5 15.5v-5.3a3.26 3.26 0 0 0-3.26-3.26c-.85 0-1.84.52-2.32 1.3v-1.11h-2.79v8.37h2.79v-4.93c0-.77.62-1.4 1.39-1.4a1.4 1.4 0 0 1 1.4 1.4v4.93h2.79M6.88 8.56a1.68 1.68 0 0 0 1.68-1.68c0-.93-.75-1.69-1.68-1.69a1.69 1.69 0 0 0-1.69 1.69c0 .93.76 1.68 1.69 1.68m1.39 9.94v-8.37H5.5v8.37h2.77z"/>
</svg>
Login with LinkedIn
</Link>
</div>
</div>
</section>
{/* How It Works */}
<section className="py-20 px-6 bg-slate-800/40">
<section className="py-16 px-6 bg-slate-800/40">
<div className="max-w-5xl mx-auto">
<h2 className="text-3xl font-bold text-white text-center mb-4">How AutoJobs Works</h2>
<h2 className="text-3xl font-bold text-white text-center mb-12">How It Works</h2>
<div className="grid md:grid-cols-3 gap-8">
{[
{
step: "01",
title: "Create Your Profile",
desc: "Upload your resume. Tell us what jobs you want — keywords, location, salary range."
desc: "Upload your resume. Connect your LinkedIn. Tell us what jobs you want — keywords, location, salary range."
},
{
step: "02",
title: "AI Finds & Customizes",
desc: "We search Jooble, JSearch, and more. AI rewrites your resume and writes cover letters."
desc: "We search across multiple job boards. For each match, AI rewrites your resume and writes a personalized cover letter."
},
{
step: "03",
title: "Apply & Track",
desc: "Apply with one click. Track every application status — from applied to offer."
desc: "Apply with one click or let AI apply automatically. Track every application status in your dashboard."
}
].map((item) => (
<div key={item.step} className="relative bg-slate-700/50 rounded-2xl p-8 border border-slate-600 hover:border-blue-500/50 transition">
<div key={item.step} className="bg-slate-700/50 rounded-2xl p-8 border border-slate-600">
<div className="text-5xl font-bold text-blue-500/20 mb-4">{item.step}</div>
<h3 className="text-xl font-semibold text-white mb-3">{item.title}</h3>
<p className="text-slate-300 leading-relaxed">{item.desc}</p>
<p className="text-slate-300">{item.desc}</p>
</div>
))}
</div>
</div>
</section>
{/* Jobseeker Pricing */}
{/* Private Plans */}
<section className="py-20 px-6">
<div className="max-w-6xl mx-auto text-center">
<h2 className="text-3xl font-bold text-white mb-4">For Job Seekers</h2>
<p className="text-slate-400 mb-12">Pick the plan that fits your job search.</p>
<h2 className="text-3xl font-bold text-white mb-2">For Job Seekers</h2>
<p className="text-slate-400 mb-12">Choose your monthly application limit</p>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6 max-w-5xl mx-auto">
{jobseekerPlans.map((plan) => (
{privatePlans.map((plan) => (
<div
key={plan.name}
className={`rounded-2xl p-6 border text-left relative ${
@@ -191,9 +194,7 @@ export default function LandingPage() {
>
{plan.badge && (
<div className="absolute -top-3 left-1/2 -translate-x-1/2">
<span className="px-3 py-1 bg-blue-500 text-white text-xs font-bold rounded-full">
{plan.badge}
</span>
<span className="px-3 py-1 bg-blue-500 text-white text-xs font-bold rounded-full">{plan.badge}</span>
</div>
)}
<div className="text-sm text-slate-400 font-medium mb-1">{plan.name}</div>
@@ -201,7 +202,7 @@ export default function LandingPage() {
<span className="text-3xl font-bold text-white">{plan.price}</span>
<span className="text-slate-400 text-sm">{plan.period}</span>
</div>
<div className="text-xs text-blue-400 mb-4">{plan.apps} apps + AI customizations/mo</div>
<div className="text-xs text-blue-400 mb-4">{plan.apps} apps/month</div>
<ul className="space-y-2 mb-6">
{plan.features.map((f) => (
<li key={f} className="text-slate-300 text-sm flex items-start gap-2">
@@ -210,7 +211,7 @@ export default function LandingPage() {
))}
</ul>
<Link
href="/autojobs/signup"
href="/autojobs/signup?type=private"
className={`block text-center px-4 py-2.5 rounded-lg font-medium text-white transition ${plan.ctaStyle}`}
>
{plan.cta}
@@ -218,17 +219,24 @@ export default function LandingPage() {
</div>
))}
</div>
<div className="mt-8 text-slate-500 text-sm flex items-center justify-center gap-2">
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M19 3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14m-.5 15.5v-5.3a3.26 3.26 0 0 0-3.26-3.26c-.85 0-1.84.52-2.32 1.3v-1.11h-2.79v8.37h2.79v-4.93c0-.77.62-1.4 1.39-1.4a1.4 1.4 0 0 1 1.4 1.4v4.93h2.79M6.88 8.56a1.68 1.68 0 0 0 1.68-1.68c0-.93-.75-1.69-1.68-1.69a1.69 1.69 0 0 0-1.69 1.69c0 .93.76 1.68 1.69 1.68m1.39 9.94v-8.37H5.5v8.37h2.77z"/>
</svg>
Login with LinkedIn import your saved resumes automatically
</div>
</div>
</section>
{/* Agency Pricing */}
{/* Agency Plans */}
<section className="py-20 px-6 bg-slate-800/40">
<div className="max-w-6xl mx-auto text-center">
<div className="inline-block px-4 py-1.5 rounded-full bg-purple-500/20 border border-purple-500/30 text-purple-300 text-sm mb-4">
For Recruiting Agencies
</div>
<h2 className="text-3xl font-bold text-white mb-4">Agency Plans Manage Multiple Clients</h2>
<p className="text-slate-400 mb-12">Run job applications for your entire client roster. Each plan includes client profile management and submission limits.</p>
<h2 className="text-3xl font-bold text-white mb-4">Agency Plans</h2>
<p className="text-slate-400 mb-12">Manage multiple clients. Each plan has a hard submission cap no unlimited.</p>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6 max-w-5xl mx-auto">
{agencyPlans.map((plan) => (
@@ -236,18 +244,16 @@ export default function LandingPage() {
key={plan.name}
className={`rounded-2xl p-6 border text-left relative ${
plan.highlight
? 'bg-gradient-to-br from-purple-600/30 to-pink-600/30 border-purple-500/50 shadow-lg shadow-purple-500/10'
? 'bg-gradient-to-br from-purple-600/30 to-pink-600/30 border-purple-500/50'
: 'bg-slate-700/50 border-slate-600'
}`}
>
{plan.badge && (
<div className="absolute -top-3 left-1/2 -translate-x-1/2">
<span className="px-3 py-1 bg-purple-500 text-white text-xs font-bold rounded-full">
{plan.badge}
</span>
<span className="px-3 py-1 bg-purple-500 text-white text-xs font-bold rounded-full">{plan.badge}</span>
</div>
)}
<div className="text-sm text-purple-400 font-medium mb-1">{plan.name}</div>
<div className="text-sm text-purple-400 font-medium mb-1">Agency {plan.name}</div>
<div className="flex items-baseline gap-1 mb-1">
<span className="text-3xl font-bold text-white">{plan.price}</span>
<span className="text-slate-400 text-sm">{plan.period}</span>
@@ -271,25 +277,7 @@ export default function LandingPage() {
</div>
<div className="mt-8 text-slate-500 text-sm">
<p>No unlimited agency plan all submissions are capped to prevent abuse.</p>
</div>
</div>
</section>
{/* Stats */}
<section className="py-16 px-6">
<div className="max-w-4xl mx-auto grid grid-cols-3 gap-8 text-center">
<div>
<div className="text-3xl font-bold text-white mb-1">&lt;$0.01</div>
<div className="text-slate-400 text-sm">Cost per application</div>
</div>
<div>
<div className="text-3xl font-bold text-white mb-1">15-25s</div>
<div className="text-slate-400 text-sm">AI time per job customization</div>
</div>
<div>
<div className="text-3xl font-bold text-white mb-1">500+</div>
<div className="text-slate-400 text-sm">Jobs found per search</div>
All plans have hard caps. No unlimited access for agencies.
</div>
</div>
</section>