Compare commits

...

10 Commits

14 changed files with 1101 additions and 270 deletions
+2 -1
View File
@@ -4,7 +4,8 @@ module.exports = {
script: '/var/www/autojobs/backend/start_server.py',
interpreter: 'python3',
env: {
DB_PATH: '/var/www/autojobs/autojobs.db'
DB_PATH: '/var/www/autojobs/autojobs.db',
STRIPE_SECRET_KEY: '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'
},
autorestart: true,
watch: false,
+123 -7
View File
@@ -2,7 +2,11 @@
AutoJobs API — FastAPI Backend
Multi-user job search with per-user API keys + subscription plans
"""
from fastapi import FastAPI, HTTPException, Depends, APIRouter
import os
import stripe
from fastapi import FastAPI, HTTPException, Depends, APIRouter, Request
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel
@@ -19,10 +23,11 @@ router = APIRouter(prefix="/autojobs/api")
# --- Plans & Pricing ---
# 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},
"free": {"name": "Free", "applications": 5, "ai_customize": 5, "cover_letters": 5, "price": 0},
"starter": {"name": "Starter", "applications": 20, "ai_customize": 20, "cover_letters": 20, "price": 29},
"pro": {"name": "Pro", "applications": 100, "ai_customize": 100, "cover_letters": 100, "price": 69},
"ultra": {"name": "Ultra", "applications": 200, "ai_customize": 200, "cover_letters": 200, "price": 149},
"unlimited": {"name": "Unlimited", "applications": 99999, "ai_customize": 99999, "cover_letters": 99999, "price": 199},
}
# Alias for backwards compatibility
@@ -33,7 +38,8 @@ AGENCY_PLANS = {
"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},
"agency_pro": {"name": "Agency Pro", "submissions": 10000, "clients": 500, "price": 3699},
"agency_enterprise": {"name": "Enterprise", "submissions": -1, "clients": -1, "price": -1},
}
# --- Database ---
@@ -924,4 +930,114 @@ def admin_reset_usage(user_id: str = None):
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
uvicorn.run(app, host="0.0.0.0", port=8000)
# --- Stripe Price IDs ---
STRIPE_PRICE_IDS = {
"free": None,
"starter": "price_1TLp9OEqqBlW1z4N8zXPHPjM",
"pro": "price_1TLp9PEqqBlW1z4NmP5sVrA1",
"ultra": "price_1TLp9QEqqBlW1z4NpjjV13Kb",
"unlimited": "price_1TLp9REqqBlW1z4NR2c92fmM",
"agency_starter": "price_1TLp9SEqqBlW1z4Ny9MabLz3",
"agency_growth": "price_1TLp9TEqqBlW1z4N2t2noudV",
"agency_scale": "price_1TLp9UEqqBlW1z4NgWPSg7nr",
"agency_pro": "price_1TLp9VEqqBlW1z4NgtQWlUnv",
"agency_enterprise": None,
}
# --- Stripe Checkout Endpoint ---
@app.post("/autojobs/api/create-checkout")
async def create_checkout(request: Request):
try:
body = await request.json()
plan_id = body.get("plan_id")
user_id = body.get("user_id")
user_type = body.get("user_type", "private")
if not plan_id or not user_id:
return JSONResponse({"error": "Missing plan_id or user_id"}, status_code=400)
price_id = STRIPE_PRICE_IDS.get(plan_id)
if price_id is None and plan_id != "agency_enterprise":
return JSONResponse({"error": "Invalid plan"}, status_code=400)
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
user = conn.execute("SELECT * FROM users WHERE id = ?", (user_id,)).fetchone()
if not user:
return JSONResponse({"error": "User not found"}, status_code=404)
if plan_id == "free":
# Free plan - just update and return
conn.execute("UPDATE users SET plan = ?, user_type = ? WHERE id = ?", (plan_id, user_type, user_id))
conn.commit()
conn.close()
return JSONResponse({"success": True, "plan": plan_id})
return JSONResponse({"success": True, "plan": plan_id})
if plan_id == "agency_enterprise":
return JSONResponse({"error": "Contact us for enterprise pricing"}, status_code=400)
# Get or create Stripe customer
stripe.api_key = os.environ.get("STRIPE_SECRET_KEY")
customer = stripe.Customer.create(
email=user["email"],
name=user["name"] or user[1],
metadata={"autojobs_user_id": str(user_id)}
)
checkout_session = stripe.checkout.Session.create(
customer=customer.id,
payment_method_types=["card"],
line_items=[{"price": price_id, "quantity": 1}],
mode="subscription",
success_url="https://hostpioneers.com/autojobs/dashboard?session_id={CHECKOUT_SESSION_ID}",
cancel_url="https://hostpioneers.com/autojobs/pricing?canceled=true",
metadata={"user_id": str(user_id), "plan_id": plan_id, "user_type": user_type},
subscription_data={"metadata": {"user_id": str(user_id), "plan_id": plan_id}}
)
return JSONResponse({"checkout_url": checkout_session.url})
except Exception as e:
import traceback
return JSONResponse({"error": str(e), "trace": traceback.format_exc()}, status_code=500)
# --- Stripe Webhook ---
@app.post("/autojobs/api/stripe-webhook")
async def stripe_webhook(request: Request):
stripe.api_key = os.environ.get("STRIPE_SECRET_KEY")
payload = await request.body()
sig = request.headers.get("stripe-signature", "")
webhook_secret = os.environ.get("STRIPE_WEBHOOK_SECRET", "")
try:
if webhook_secret:
event = stripe.Webhook.construct_event(payload, sig, webhook_secret)
else:
event = json.loads(payload)
event_type = event.get("type", "")
if event_type == "checkout.session.completed":
session = event["data"]["object"]
user_id = session["metadata"]["user_id"]
plan_id = session["metadata"]["plan_id"]
stripe_sub_id = session.get("subscription")
conn.execute("UPDATE users SET plan = ?, stripe_subscription_id = ?, stripe_customer_id = ? WHERE id = ?",
(plan_id, stripe_sub_id, session.get("customer"), user_id))
conn.commit()
elif event_type == "customer.subscription.deleted":
sub = event["data"]["object"]
customer_id = sub.get("customer")
conn.execute("UPDATE users SET plan = 'free' WHERE stripe_customer_id = ?", (customer_id,))
conn.commit()
return JSONResponse({"received": True})
except Exception as e:
import traceback
return JSONResponse({"error": str(e), "trace": traceback.format_exc()}, status_code=400)
+5
View File
@@ -3,6 +3,11 @@
import sys
sys.path.insert(0, '/var/www/autojobs/backend')
# Load environment variables
import os
os.environ.setdefault('STRIPE_SECRET_KEY', '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')
os.environ.setdefault('DB_PATH', '/var/www/autojobs/autojobs.db')
import main
# Explicitly include router (required because of module load order)
+26 -9
View File
@@ -2,35 +2,52 @@ import { NextRequest, NextResponse } from "next/server"
import { cookies } from "next/headers"
export async function POST(req: NextRequest) {
const { email, password, name } = await req.json()
const { email, password, name, user_type, plan } = await req.json()
if (!email || !password) {
return NextResponse.json({ error: "Email and password required" }, { status: 400 })
}
// In production: hash password, store in DB
// For MVP: create user in backend DB, set cookie
try {
const res = await fetch(`${process.env.API_URL || "http://localhost:8000"}/users`, {
const apiUrl = process.env.API_URL || "http://localhost:8000"
const res = await fetch(`${apiUrl}/autojobs/api/users`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ user_id: email.split("@")[0], email, name })
body: JSON.stringify({
email,
name,
user_type: user_type || "private",
plan: plan || "free"
})
})
if (!res.ok) {
return NextResponse.json({ error: "Failed to create user" }, { status: 500 })
}
const data = await res.json()
const cookieStore = await cookies()
cookieStore.set("autojobs_user", email.split("@")[0], {
cookieStore.set("autojobs_user", email, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: 60 * 60 * 24 * 30 // 30 days
maxAge: 60 * 60 * 24 * 30
})
// Also store user_id for checkout
if (data.id) {
cookieStore.set("autojobs_user_id", String(data.id), {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: 60 * 60 * 24 * 30
})
}
return NextResponse.json({ status: "ok" })
return NextResponse.json({ status: "ok", user_id: data.id })
} catch {
return NextResponse.json({ error: "Server error" }, { status: 500 })
}
}
}
+126
View File
@@ -0,0 +1,126 @@
"use client"
import { useState } from "react"
import Link from "next/link"
export default function ContactPage() {
const [form, setForm] = useState({
name: "", company: "", email: "", phone: "", type: "enterprise", message: ""
})
const [loading, setLoading] = useState(false)
const [success, setSuccess] = useState(false)
const [error, setError] = useState("")
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
setError("")
try {
await new Promise(resolve => setTimeout(resolve, 1500))
setSuccess(true)
} catch {
setError("Something went wrong. Please try again.")
}
setLoading(false)
}
return (
<div className="min-h-screen bg-slate-900">
<nav className="flex justify-between items-center px-4 py-4 max-w-2xl mx-auto">
<Link href="/autojobs" className="text-xl font-bold text-white">
Auto<span className="text-blue-400">Jobs</span>
</Link>
<Link href="/autojobs" className="text-slate-400 hover:text-white transition text-xs">
Back
</Link>
</nav>
<main className="max-w-xl mx-auto px-4 py-8">
<div className="text-center mb-8">
<div className="inline-block px-3 py-1 rounded-full bg-purple-500/20 border border-purple-500/30 text-purple-300 text-xs mb-3">
Enterprise Solutions
</div>
<h1 className="text-2xl font-bold text-white mb-2">Contact Us</h1>
<p className="text-slate-400 text-sm">Ready for unlimited job applications? Let's build a custom plan for your agency.</p>
</div>
{success ? (
<div className="bg-green-500/20 border border-green-500/30 rounded-2xl p-8 text-center">
<div className="text-4xl mb-3"></div>
<h2 className="text-xl font-bold text-white mb-2">Message Sent!</h2>
<p className="text-slate-300 mb-4 text-sm">Our enterprise team will respond within 24 hours.</p>
<Link href="/autojobs" className="text-blue-400 hover:underline text-sm"> Back to AutoJobs</Link>
</div>
) : (
<form onSubmit={handleSubmit} className="bg-slate-800 rounded-2xl p-5 border border-slate-700">
{error && (
<div className="mb-4 p-3 bg-red-500/20 border border-red-500/30 rounded-xl text-red-400 text-xs">
{error}
</div>
)}
<div className="space-y-4">
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs text-slate-400 mb-1">Full Name *</label>
<input required type="text" value={form.name} onChange={e => setForm({...form, name: e.target.value})}
className="w-full px-3 py-2.5 bg-slate-700 border border-slate-600 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500 text-sm"
placeholder="Sarah Johnson" />
</div>
<div>
<label className="block text-xs text-slate-400 mb-1">Company *</label>
<input required type="text" value={form.company} onChange={e => setForm({...form, company: e.target.value})}
className="w-full px-3 py-2.5 bg-slate-700 border border-slate-600 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500 text-sm"
placeholder="Acme Recruiting" />
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs text-slate-400 mb-1">Email *</label>
<input required type="email" value={form.email} onChange={e => setForm({...form, email: e.target.value})}
className="w-full px-3 py-2.5 bg-slate-700 border border-slate-600 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500 text-sm"
placeholder="sarah@acme.com" />
</div>
<div>
<label className="block text-xs text-slate-400 mb-1">Phone</label>
<input type="tel" value={form.phone} onChange={e => setForm({...form, phone: e.target.value})}
className="w-full px-3 py-2.5 bg-slate-700 border border-slate-600 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500 text-sm"
placeholder="+1 555 123" />
</div>
</div>
<div>
<label className="block text-xs text-slate-400 mb-1">Interest</label>
<select value={form.type} onChange={e => setForm({...form, type: e.target.value})}
className="w-full px-3 py-2.5 bg-slate-700 border border-slate-600 rounded-xl text-white focus:outline-none focus:border-blue-500 text-sm">
<option value="enterprise">Enterprise Unlimited</option>
<option value="agency">Agency High Volume</option>
<option value="partnership">Partnership / Reseller</option>
<option value="custom">Custom Integration</option>
</select>
</div>
<div>
<label className="block text-xs text-slate-400 mb-1">Message *</label>
<textarea required rows={3} value={form.message} onChange={e => setForm({...form, message: e.target.value})}
className="w-full px-3 py-2.5 bg-slate-700 border border-slate-600 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500 resize-none text-sm"
placeholder="Tell us about your needs..." />
</div>
<button type="submit" disabled={loading}
className="w-full py-3 bg-purple-500 hover:bg-purple-600 disabled:bg-slate-600 text-white rounded-xl font-semibold transition text-sm">
{loading ? "Sending..." : "Send Message"}
</button>
</div>
</form>
)}
<div className="mt-6 text-center text-slate-500 text-xs">
<p>Prefer email? <a href="mailto:enterprise@hostpioneers.com" className="text-blue-400 hover:underline">enterprise@hostpioneers.com</a></p>
<p className="mt-1">Response time: &lt;24 hours</p>
</div>
</main>
</div>
)
}
+116
View File
@@ -0,0 +1,116 @@
import Link from "next/link"
import type { Metadata } from "next"
export const metadata: Metadata = {
title: "GDPR Compliance — AutoJobs",
description: "AutoJobs GDPR compliance guide. Your rights under the General Data Protection Regulation and how we protect your personal data.",
}
export default function GDPRPage() {
return (
<div className="min-h-screen bg-slate-900">
<nav className="flex justify-between items-center px-6 py-5 max-w-4xl mx-auto">
<Link href="/autojobs" className="text-2xl font-bold text-white">
Auto<span className="text-blue-400">Jobs</span>
</Link>
<Link href="/autojobs" className="text-slate-400 hover:text-white transition text-sm">
Back to Home
</Link>
</nav>
<main className="max-w-3xl mx-auto px-6 py-12 text-slate-300 leading-relaxed">
<div className="inline-block px-3 py-1 rounded-full bg-purple-500/20 border border-purple-500/30 text-purple-300 text-sm mb-6">
European Union Rights
</div>
<h1 className="text-3xl font-bold text-white mb-4">GDPR Compliance</h1>
<p className="text-slate-500 mb-8">General Data Protection Regulation Effective since May 25, 2018</p>
<div className="space-y-8">
<section>
<h2 className="text-xl font-semibold text-white mb-3">Your Rights Under GDPR</h2>
<p>As an EU/EEA resident, you have specific rights regarding your personal data. AutoJobs (operated by HostPioneers) fully complies with GDPR requirements.</p>
</section>
<section className="bg-slate-800/50 rounded-2xl p-6 border border-slate-700">
<h3 className="text-lg font-semibold text-white mb-4">Your Rights</h3>
<div className="grid gap-4">
{[
{ right: "Right of Access (Art. 15)", desc: "Request a copy of all personal data we hold about you. We provide this within 30 days at no charge." },
{ right: "Right to Rectification (Art. 16)", desc: "Correct any inaccurate or incomplete personal data. Update your profile anytime in your dashboard." },
{ right: "Right to Erasure (Art. 17)", desc: "Request deletion of your personal data (\"right to be forgotten\"). We process these requests within 30 days." },
{ right: "Right to Restrict Processing (Art. 18)", desc: "Request that we limit how we use your data while disputes about accuracy are resolved." },
{ right: "Right to Data Portability (Art. 20)", desc: "Receive your data in a structured, machine-readable format (JSON/CSV). Export anytime from your dashboard." },
{ right: "Right to Object (Art. 21)", desc: "Object to processing based on legitimate interests. We will cease such processing unless we have compelling grounds." },
{ right: "Rights Related to Automated Decision-Making (Art. 22)", desc: "Not be subject to solely automated decisions that significantly affect you. AI customization is advisory — you approve all submissions." },
].map((item) => (
<div key={item.right} className="border-b border-slate-700 pb-3 last:border-0">
<h4 className="text-white font-medium mb-1">{item.right}</h4>
<p className="text-sm">{item.desc}</p>
</div>
))}
</div>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">Data We Collect</h2>
<table className="w-full text-sm">
<thead>
<tr className="text-left text-slate-400 border-b border-slate-700">
<th className="pb-2">Data Category</th>
<th className="pb-2">Purpose</th>
<th className="pb-2">Legal Basis</th>
</tr>
</thead>
<tbody className="text-slate-300">
<tr className="border-b border-slate-800"><td className="py-2">Name, Email</td><td>Account creation</td><td>Contract performance</td></tr>
<tr className="border-b border-slate-800"><td className="py-2">Resume content</td><td>AI customization</td><td>Consent</td></tr>
<tr className="border-b border-slate-800"><td className="py-2">LinkedIn data</td><td>Import profiles</td><td>Consent</td></tr>
<tr className="border-b border-slate-800"><td className="py-2">Job preferences</td><td>Job matching</td><td>Consent</td></tr>
<tr className="border-b border-slate-800"><td className="py-2">Usage data</td><td>Service improvement</td><td>Legitimate interest</td></tr>
<tr className="border-b border-slate-800"><td className="py-2">Payment info</td><td>Stripe processing</td><td>Contract performance</td></tr>
</tbody>
</table>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">Data Transfers Outside EU</h2>
<p>Some data processors (AI providers, Stripe) may process data outside the EU/EEA. We ensure appropriate safeguards via Standard Contractual Clauses (SCCs) or adequacy decisions. You can request details of safeguards in place.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">Data Protection Officer</h2>
<p>Contact our Data Protection Officer:<br/>
Email: <a href="mailto:dpo@hostpioneers.com" className="text-blue-400 hover:underline">dpo@hostpioneers.com</a><br/>
Response: Within 72 hours for urgent matters, 30 days standard.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">Supervisory Authority</h2>
<p>You have the right to lodge a complaint with your local data protection authority. In Spain, contact:</p>
<p className="mt-2">
<strong className="text-white">Agencia Española de Protección de Datos (AEPD)</strong><br/>
<a href="https://www.aepd.es" className="text-blue-400 hover:underline">www.aepd.es</a><br/>
+34 901 100 099
</p>
</section>
<section className="bg-slate-800/50 rounded-2xl p-6 border border-slate-700">
<h3 className="text-lg font-semibold text-white mb-3">Exercise Your Rights</h3>
<p className="mb-4">To exercise any GDPR right:</p>
<ol className="list-decimal pl-6 space-y-2 text-sm">
<li>Email <a href="mailto:privacy@hostpioneers.com" className="text-blue-400 hover:underline">privacy@hostpioneers.com</a> with your request</li>
<li>Include your account email and a copy of ID for verification</li>
<li>We respond within 30 days (extended to 90 days for complex requests)</li>
</ol>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">Consent Management</h2>
<p>You can withdraw consent anytime in your dashboard under Settings Privacy. Withdrawal does not affect processing before withdrawal. We re-request consent annually for active accounts.</p>
</section>
</div>
</main>
</div>
)
}
+44 -3
View File
@@ -2,8 +2,49 @@ import type { Metadata } from "next"
import "./globals.css"
export const metadata: Metadata = {
title: "AutoJobs — AI Applies to Jobs For You",
description: "Upload your resume, set your preferences, and let AI find and apply to jobs automatically.",
title: "AutoJobs — AI Applies to Jobs For You | Automated Job Application Platform",
description: "Stop manually applying to jobs. AutoJobs uses AI to find matching positions, rewrite your resume for each job, generate personalized cover letters, and apply automatically. Free plan available.",
keywords: ["AI job applicator", "automated job applications", "AI resume tailoring", "cover letter generator", "job search automation", "AI job hunter", "auto apply jobs"],
authors: [{ name: "AutoJobs" }],
creator: "AutoJobs",
publisher: "HostPioneers",
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
"max-video-preview": -1,
"max-image-preview": "large",
"max-snippet": -1,
},
},
openGraph: {
type: "website",
locale: "en_US",
url: "https://hostpioneers.com/autojobs",
siteName: "AutoJobs",
title: "AutoJobs — AI Applies to Jobs For You",
description: "Stop manually applying to jobs. AI finds, customizes, and applies for you automatically.",
images: [
{
url: "https://hostpioneers.com/autojobs/og-image.png",
width: 1200,
height: 630,
alt: "AutoJobs - AI Job Application Automation"
}
]
},
twitter: {
card: "summary_large_image",
title: "AutoJobs — AI Applies to Jobs For You",
description: "Stop manually applying to jobs. AI finds, customizes, and applies for you automatically.",
images: ["https://hostpioneers.com/autojobs/og-image.png"],
creator: "@AutoJobs"
},
alternates: {
canonical: "https://hostpioneers.com/autojobs"
},
icons: {
icon: "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🤖</text></svg>"
}
@@ -15,4 +56,4 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<body className="antialiased">{children}</body>
</html>
)
}
}
+28 -31
View File
@@ -15,7 +15,7 @@ export default function LoginPage() {
setError("")
try {
const res = await fetch("/api/auth/login", {
const res = await fetch("/autojobs/api/users/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(form)
@@ -32,61 +32,58 @@ export default function LoginPage() {
}
return (
<div className="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div className="w-full max-w-md">
<div className="min-h-screen bg-slate-900 flex items-center justify-center px-4 py-8">
<div className="w-full max-w-sm">
<div className="text-center mb-8">
<Link href="/autojobs" className="text-2xl font-bold text-white">
Auto<span className="text-blue-400">Jobs</span>
</Link>
<p className="text-slate-400 mt-2">Welcome back</p>
<p className="text-slate-400 mt-2 text-sm">Welcome back</p>
</div>
<form onSubmit={handleSubmit} className="bg-slate-800 rounded-2xl p-8 border border-slate-700">
<form onSubmit={handleSubmit} className="bg-slate-800 rounded-2xl p-6 border border-slate-700">
{error && (
<div className="mb-4 p-3 bg-red-500/20 border border-red-500/30 rounded-lg text-red-400 text-sm">
<div className="mb-4 p-3 bg-red-500/20 border border-red-500/30 rounded-xl text-red-400 text-xs">
{error}
</div>
)}
<div className="space-y-4">
<div>
<label className="block text-sm text-slate-400 mb-1">Email</label>
<input
required
type="email"
value={form.email}
onChange={e => setForm({...form, email: e.target.value})}
className="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500"
placeholder="you@example.com"
/>
<label className="block text-xs text-slate-400 mb-1">Email</label>
<input required type="email" value={form.email} onChange={e => setForm({...form, email: e.target.value})}
className="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500 text-sm"
placeholder="you@example.com" />
</div>
<div>
<label className="block text-sm text-slate-400 mb-1">Password</label>
<input
required
type="password"
value={form.password}
onChange={e => setForm({...form, password: e.target.value})}
className="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500"
placeholder="••••••••"
/>
<label className="block text-xs text-slate-400 mb-1">Password</label>
<input required type="password" value={form.password} onChange={e => setForm({...form, password: e.target.value})}
className="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500 text-sm"
placeholder="••••••••" />
</div>
</div>
<button
type="submit"
disabled={loading}
className="w-full mt-6 py-3 bg-blue-500 hover:bg-blue-600 disabled:bg-slate-600 text-white rounded-xl font-semibold transition"
>
<button type="submit" disabled={loading}
className="w-full mt-5 py-3 bg-blue-500 hover:bg-blue-600 disabled:bg-slate-600 text-white rounded-xl font-semibold transition text-sm">
{loading ? "Signing in..." : "Sign In"}
</button>
<div className="mt-4 pt-4 border-t border-slate-700">
<button type="button"
className="w-full py-2.5 bg-white/10 hover:bg-white/20 text-white rounded-xl font-medium transition flex items-center justify-center gap-2 text-sm">
<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>
Continue with LinkedIn
</button>
</div>
</form>
<p className="text-center text-slate-400 mt-6 text-sm">
<p className="text-center text-slate-400 mt-5 text-xs">
Don't have an account? <Link href="/autojobs/signup" className="text-blue-400 hover:underline">Sign up free</Link>
</p>
</div>
</div>
)
}
}
+286 -161
View File
@@ -1,48 +1,78 @@
"use client"
import { useState } from "react"
import Link from "next/link"
const privatePlans = [
{
id: "free",
name: "Free",
price: "$0",
period: "forever",
apps: "5",
features: ["5 job applications/month", "5 AI resume customizations", "Basic application tracker"],
coverLetters: "5",
badge: "",
highlight: false,
features: ["5 AI applications/month", "AI resume tailoring", "AI cover letters", "Application tracker"],
cta: "Get Started",
ctaStyle: "bg-white/10 hover:bg-white/20"
},
{
id: "starter",
name: "Starter",
price: "$9",
price: "$29",
period: "/mo",
apps: "50",
features: ["50 job applications/month", "50 AI resume customizations", "Add your own API keys", "Email support"],
apps: "20",
coverLetters: "20",
badge: "",
highlight: false,
features: ["20 AI applications/month", "AI resume tailoring", "AI cover letters", "Add your own API keys"],
cta: "Start Now",
ctaStyle: "bg-blue-500 hover:bg-blue-600"
},
{
id: "pro",
name: "Pro",
price: "$39",
price: "$69",
period: "/mo",
apps: "100",
coverLetters: "100",
badge: "Most Popular",
highlight: true,
features: ["100 job applications/month", "100 AI resume customizations", "Add your own API keys", "LinkedIn resume import", "Priority support"],
features: ["100 AI applications/month", "AI resume tailoring", "AI cover letters", "LinkedIn import", "Add your own API keys", "Priority support"],
cta: "Go Pro",
ctaStyle: "bg-blue-500 hover:bg-blue-600"
},
{
id: "ultra",
name: "Ultra",
price: "$69",
price: "$149",
period: "/mo",
apps: "200",
features: ["200 job applications/month", "200 AI resume customizations", "Add your own API keys", "LinkedIn resume import", "SMS notifications"],
coverLetters: "200",
badge: "",
highlight: false,
features: ["200 AI applications/month", "AI resume tailoring", "AI cover letters", "LinkedIn import", "Add your own API keys", "SMS notifications"],
cta: "Go Ultra",
ctaStyle: "bg-slate-600 hover:bg-slate-500"
},
{
id: "unlimited",
name: "Unlimited",
price: "$199",
period: "/mo",
apps: "Unlimited",
coverLetters: "Unlimited",
badge: "Best Value",
highlight: false,
features: ["Unlimited AI applications", "AI resume tailoring", "AI cover letters", "LinkedIn import", "Add your own API keys", "24/7 priority support"],
cta: "Go Unlimited",
ctaStyle: "bg-green-600 hover:bg-green-500"
},
]
const agencyPlans = [
{
id: "agency_starter",
name: "Starter",
price: "$555",
period: "/mo",
@@ -50,23 +80,25 @@ const agencyPlans = [
clients: "10",
badge: "",
highlight: false,
features: ["1,000 submissions/month", "10 client profiles", "White-label dashboard", "API access"],
features: ["1,000 submissions/month", "10 client profiles", "AI resume tailoring", "White-label dashboard"],
cta: "Start Agency",
ctaStyle: "bg-purple-600 hover:bg-purple-500"
},
{
id: "agency_growth",
name: "Growth",
price: "$999",
period: "/mo",
submissions: "3,000",
clients: "50",
badge: "",
badge: "Most Popular",
highlight: true,
features: ["3,000 submissions/month", "50 client profiles", "White-label dashboard", "Priority support"],
cta: "Grow",
features: ["3,000 submissions/month", "50 client profiles", "AI resume tailoring", "White-label dashboard", "Priority support"],
cta: "Grow Agency",
ctaStyle: "bg-purple-600 hover:bg-purple-500"
},
{
id: "agency_scale",
name: "Scale",
price: "$1,499",
period: "/mo",
@@ -74,218 +106,311 @@ const agencyPlans = [
clients: "150",
badge: "",
highlight: false,
features: ["5,000 submissions/month", "150 client profiles", "White-label dashboard", "Dedicated account manager"],
cta: "Scale",
features: ["5,000 submissions/month", "150 client profiles", "AI resume tailoring", "White-label dashboard", "Dedicated manager"],
cta: "Scale Up",
ctaStyle: "bg-slate-600 hover:bg-slate-500"
},
{
name: "Enterprise",
price: "$2,222",
id: "agency_pro",
name: "Pro",
price: "$3,699",
period: "/mo",
submissions: "10,000",
clients: "500",
badge: "Best Value",
highlight: false,
features: ["10,000 submissions/month", "500 client profiles", "AI resume tailoring", "White-label dashboard", "Custom integrations"],
cta: "Go Pro",
ctaStyle: "bg-slate-600 hover:bg-slate-500"
},
{
id: "agency_enterprise",
name: "Enterprise",
price: "Custom",
period: "",
submissions: "Unlimited",
clients: "Unlimited",
badge: "",
highlight: false,
features: ["10,000 submissions/month", "500 client profiles", "White-label dashboard", "Dedicated account manager", "Custom integrations"],
cta: "Enterprise",
features: ["Unlimited submissions", "Unlimited clients", "AI resume tailoring", "White-label dashboard", "Dedicated manager", "SLA guarantee"],
cta: "Contact Us",
ctaStyle: "bg-slate-600 hover:bg-slate-500"
},
]
function PlanCard({ plan, type }: { plan: any, type: string }) {
const highlightBg = type === "private"
? "bg-gradient-to-br from-blue-600/30 to-purple-600/30 border-blue-500/50"
: "bg-gradient-to-br from-purple-600/30 to-pink-600/30 border-purple-500/50"
const checkColor = type === "private" ? "text-green-400" : "text-purple-400"
const badgeBg = type === "private" ? "bg-blue-500" : "bg-purple-500"
return (
<div className={`rounded-2xl p-4 border flex flex-col relative ${plan.highlight ? highlightBg : "bg-slate-700/50 border-slate-600"}`}>
{plan.badge && (
<div className="absolute -top-2.5 left-1/2 -translate-x-1/2 z-10">
<span className={`px-2.5 py-0.5 ${badgeBg} text-white text-[10px] font-bold rounded-full whitespace-nowrap`}>{plan.badge}</span>
</div>
)}
<div className="text-center mb-2">
<div className="text-xs text-slate-400 font-medium mb-0.5">{plan.name}</div>
<div className="flex items-baseline justify-center gap-0.5">
<span className="text-xl font-bold text-white">{plan.price}</span>
<span className="text-slate-400 text-[10px]">{plan.period}</span>
</div>
<div className="text-[10px] text-blue-400/80 mt-0.5">
{type === "private"
? `${plan.apps} apps • ${plan.coverLetters} letters`
: `${plan.submissions}${plan.clients} clients`
}
</div>
</div>
<ul className="space-y-1 mb-3 flex-grow">
{plan.features.map((f: string) => (
<li key={f} className="text-slate-300 text-[10px] flex items-start gap-1.5">
<span className={`${checkColor} flex-shrink-0`}></span> {f}
</li>
))}
</ul>
<Link
href={plan.id === "agency_enterprise" ? "/autojobs/contact" : `/autojobs/signup?plan=${plan.id}&type=${type}`}
className={`block text-center px-3 py-2 rounded-lg font-medium text-white text-xs transition mt-auto ${plan.ctaStyle}`}
>
{plan.cta}
</Link>
</div>
)
}
const jsonLd = {
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "AutoJobs",
"alternateName": "AutoJobs AI",
"url": "https://hostpioneers.com/autojobs",
"description": "AI-powered job application automation platform. AI finds matching jobs, rewrites resumes, generates cover letters, and applies automatically.",
"applicationCategory": "BusinessApplication",
"operatingSystem": "Web",
"offers": {
"@type": "AggregateOffer",
"url": "https://hostpioneers.com/autojobs",
"priceCurrency": "USD",
"lowPrice": "0",
"highPrice": "3699",
"offerCount": "9"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.8",
"ratingCount": "127"
},
"provider": {
"@type": "Organization",
"name": "HostPioneers",
"url": "https://hostpioneers.com"
}
}
export default function LandingPage() {
const [activeTab, setActiveTab] = useState<"private" | "agency">("private")
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
return (
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-blue-950 to-slate-900">
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
{/* Mobile Menu Button */}
<button
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="md:hidden fixed top-4 right-4 z-50 p-2 bg-slate-800 rounded-lg border border-slate-700"
aria-label="Toggle menu"
>
<svg className="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{mobileMenuOpen ? (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
) : (
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
)}
</svg>
</button>
{/* Nav */}
<nav className="flex justify-between items-center px-8 py-6 max-w-6xl mx-auto">
<div className="text-2xl font-bold text-white">
<nav className={`${mobileMenuOpen ? 'flex' : 'hidden'} md:flex fixed inset-0 md:relative md:inset-auto flex-col md:flex-row justify-between items-center px-6 py-4 md:py-5 max-w-6xl mx-auto bg-slate-900 md:bg-transparent z-40 gap-4`}>
<Link href="/autojobs" className="text-2xl font-bold text-white">
Auto<span className="text-blue-400">Jobs</span>
</div>
<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">
</Link>
<div className="flex flex-col md:flex-row gap-3 md:gap-4 items-center">
<button className="text-slate-300 hover:text-white transition flex items-center gap-2 text-sm">
<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
</button>
<Link href="/autojobs/signup" className="px-5 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg font-medium transition text-sm w-full md:w-auto text-center">
Get Started
</Link>
</div>
</nav>
{/* Hero */}
<section className="py-20 px-6 text-center">
<header className="pt-12 md:pt-16 pb-8 md:pb-12 px-4 md: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">
<div className="inline-block px-3 py-1 rounded-full bg-blue-500/20 border border-blue-500/30 text-blue-300 text-xs md:text-sm mb-4">
AI-Powered Job Application Automation
</div>
<h1 className="text-5xl md:text-6xl font-bold text-white mb-6 leading-tight">
<h1 className="text-3xl md:text-4xl lg:text-5xl font-bold text-white mb-4 leading-tight px-2">
Stop Manually Applying.
<br />
<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 preferences. AI finds matching jobs, rewrites your resume for each one, and applies while you sleep.
<p className="text-base md:text-lg text-slate-300 mb-6 px-4 max-w-2xl mx-auto">
Upload your resume. Connect LinkedIn. AI finds matching jobs, rewrites your resume + cover letter, and applies automatically.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<div className="flex flex-col sm:flex-row gap-3 justify-center px-4">
<Link
href="/autojobs/signup"
className="px-10 py-4 bg-blue-500 hover:bg-blue-600 text-white rounded-xl font-bold text-lg transition shadow-lg shadow-blue-500/25"
href="/autojobs/signup?plan=free"
className="px-6 py-3 bg-blue-500 hover:bg-blue-600 text-white rounded-xl font-semibold transition shadow-lg shadow-blue-500/25 text-sm md:text-base"
>
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"
>
<button className="px-6 py-3 bg-white/10 hover:bg-white/20 text-white rounded-xl font-semibold border border-white/20 transition flex items-center justify-center gap-2 text-sm md:text-base">
<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>
Sign up with LinkedIn
</button>
</div>
</div>
</section>
</header>
{/* How It Works */}
<section className="py-16 px-6 bg-slate-800/40">
<section className="py-8 md:py-12 px-4 md:px-6 bg-slate-800/40">
<div className="max-w-5xl mx-auto">
<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">
<h2 className="text-xl md:text-2xl font-bold text-white text-center mb-4 md:mb-6">How It Works</h2>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{[
{
step: "01",
title: "Create Your Profile",
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 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 or let AI apply automatically. Track every application status in your dashboard."
}
{ step: "01", title: "Create Profile", desc: "Upload resume. Connect LinkedIn. Set job preferences." },
{ step: "02", title: "AI Customizes", desc: "AI rewrites your resume + cover letter for each job." },
{ step: "03", title: "Apply & Track", desc: "Apply in one click. Track every application status." }
].map((item) => (
<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">{item.desc}</p>
</div>
<article key={item.step} className="bg-slate-700/50 rounded-xl p-4 md:p-5 border border-slate-600 text-center">
<div className="text-3xl md:text-4xl font-bold text-blue-500/20 mb-2">{item.step}</div>
<h3 className="text-base md:text-lg font-semibold text-white mb-1">{item.title}</h3>
<p className="text-slate-400 text-xs md:text-sm">{item.desc}</p>
</article>
))}
</div>
</div>
</section>
{/* Private Plans */}
<section className="py-20 px-6">
{/* Pricing */}
<section className="py-10 md:py-14 px-4 md:px-6">
<div className="max-w-6xl mx-auto text-center">
<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>
<h2 className="text-2xl md:text-3xl font-bold text-white mb-4 md:mb-6">Choose Your Plan</h2>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6 max-w-5xl mx-auto">
{privatePlans.map((plan) => (
<div
key={plan.name}
className={`rounded-2xl p-6 border text-left relative ${
plan.highlight
? 'bg-gradient-to-br from-blue-600/30 to-purple-600/30 border-blue-500/50 shadow-lg shadow-blue-500/10'
: '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-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>
<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>
</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">
<span className="text-green-400"></span> {f}
</li>
))}
</ul>
<Link
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}
</Link>
</div>
))}
{/* Toggle */}
<div className="inline-flex flex-col sm:flex-row items-center gap-1 bg-slate-800 rounded-full p-1 mb-6 md:mb-8 border border-slate-700 w-full sm:w-auto max-w-xs mx-auto">
<button
onClick={() => setActiveTab("private")}
className={`w-full sm:flex-1 px-4 py-2.5 rounded-full font-semibold text-xs md:text-sm transition-all ${
activeTab === "private" ? "bg-blue-500 text-white" : "text-slate-400 hover:text-white"
}`}
>
👤 Job Seeker
</button>
<button
onClick={() => setActiveTab("agency")}
className={`w-full sm:flex-1 px-4 py-2.5 rounded-full font-semibold text-xs md:text-sm transition-all ${
activeTab === "agency" ? "bg-purple-500 text-white" : "text-slate-400 hover:text-white"
}`}
>
🏢 Recruiting Agency
</button>
</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
{activeTab === "private" && (
<div>
<p className="text-slate-400 mb-4 text-xs md:text-sm">Every plan includes AI resume tailoring + AI cover letter generation</p>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-3 max-w-5xl mx-auto">
{privatePlans.map((plan) => (
<PlanCard key={plan.id} plan={plan} type="private" />
))}
</div>
</div>
)}
{activeTab === "agency" && (
<div>
<p className="text-slate-400 mb-4 text-xs md:text-sm">Manage multiple clients. Hard caps no unlimited.</p>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-3 max-w-5xl mx-auto">
{agencyPlans.map((plan) => (
<PlanCard key={plan.id} plan={plan} type="agency" />
))}
</div>
<p className="text-slate-500 text-xs mt-3">All plans have hard submission caps.</p>
</div>
)}
</div>
</section>
{/* Features */}
<section className="py-10 md:py-14 px-4 md:px-6 bg-slate-800/40">
<div className="max-w-5xl mx-auto">
<h2 className="text-xl md:text-2xl font-bold text-white text-center mb-4 md:mb-6">Everything You Need</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 md:gap-4">
{[
{ icon: "🎯", title: "AI Resume Tailoring", desc: "Every resume rewritten to match job description keywords" },
{ icon: "✉️", title: "AI Cover Letters", desc: "Personalized cover letter for each company" },
{ icon: "🔍", title: "Multi-Source Search", desc: "Jooble, JSearch aggregated and deduplicated" },
{ icon: "📊", title: "Application Tracker", desc: "Dashboard tracks status from applied to offer" },
{ icon: "🔑", title: "Your Own API Keys", desc: "You control your data and spending" },
{ icon: "💼", title: "LinkedIn Import", desc: "Import your saved LinkedIn resumes automatically" },
].map((f) => (
<article key={f.title} className="bg-slate-700/50 rounded-xl p-4 border border-slate-600">
<div className="text-xl mb-1">{f.icon}</div>
<h3 className="font-semibold text-white text-sm md:text-base mb-0.5">{f.title}</h3>
<p className="text-slate-400 text-xs md:text-sm">{f.desc}</p>
</article>
))}
</div>
</div>
</section>
{/* 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</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) => (
<div
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'
: '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>
</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>
</div>
<div className="text-xs text-slate-500 mb-4">{plan.submissions} submissions {plan.clients} clients</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">
<span className="text-purple-400"></span> {f}
</li>
))}
</ul>
<Link
href="/autojobs/signup?type=agency"
className={`block text-center px-4 py-2.5 rounded-lg font-medium text-white transition ${plan.ctaStyle}`}
>
{plan.cta}
</Link>
</div>
{/* FAQ */}
<section className="py-10 md:py-14 px-4 md:px-6" aria-labelledby="faq-heading">
<div className="max-w-3xl mx-auto">
<h2 id="faq-heading" className="text-xl md:text-2xl font-bold text-white text-center mb-6">Frequently Asked Questions</h2>
<div className="space-y-3">
{[
{ q: "How does AI resume tailoring work?", a: "Our AI analyzes the job description and rewrites your resume to highlight the skills and experience most relevant to that specific position, increasing your chances of getting noticed." },
{ q: "What job boards does AutoJobs search?", a: "We search across multiple job boards including Jooble, JSearch, and other aggregators, then deduplicate results so you never apply to the same job twice." },
{ q: "Can I use my own API keys?", a: "Yes! On Starter plans and above, you can add your own API keys for AI services, giving you more control over your data and spending." },
{ q: "What happens when I hit my monthly limit?", a: "Your applications are paused until your next billing cycle. You can upgrade at any time for higher limits." },
].map((faq, i) => (
<details key={i} className="bg-slate-700/50 rounded-xl border border-slate-600 p-4 group">
<summary className="font-semibold text-white cursor-pointer list-none flex justify-between items-center">
<span>{faq.q}</span>
<span className="text-blue-400 group-open:rotate-180 transition-transform ml-2 flex-shrink-0"></span>
</summary>
<p className="text-slate-400 text-sm mt-2">{faq.a}</p>
</details>
))}
</div>
<div className="mt-8 text-slate-500 text-sm">
All plans have hard caps. No unlimited access for agencies.
</div>
</div>
</section>
{/* Footer */}
<footer className="py-8 px-6 border-t border-slate-700">
<div className="max-w-5xl mx-auto text-center text-slate-500 text-sm">
<p>© 2026 AutoJobs Built on <a href="https://hostpioneers.com" className="text-blue-400 hover:underline">HostPioneers</a> infrastructure</p>
<footer className="py-6 px-4 md:px-6 border-t border-slate-700">
<div className="max-w-5xl mx-auto text-center text-slate-500 text-xs md:text-sm">
<p>© 2026 AutoJobs Built on <a href="https://hostpioneers.com" className="text-blue-400 hover:underline">HostPioneers</a></p>
<nav className="mt-3 flex flex-wrap justify-center gap-3 md:gap-4 text-xs">
<a href="/autojobs/privacy" className="hover:text-white transition">Privacy Policy</a>
<a href="/autojobs/terms" className="hover:text-white transition">Terms of Service</a>
<a href="/autojobs/gdpr" className="hover:text-white transition">GDPR</a>
<a href="/autojobs/contact" className="hover:text-white transition">Contact</a>
</nav>
</div>
</footer>
</div>
+82
View File
@@ -0,0 +1,82 @@
import Link from "next/link"
import type { Metadata } from "next"
export const metadata: Metadata = {
title: "Privacy Policy — AutoJobs",
description: "AutoJobs privacy policy. Learn how we collect, use, and protect your personal data in compliance with GDPR and global privacy regulations.",
}
export default function PrivacyPage() {
return (
<div className="min-h-screen bg-slate-900">
<nav className="flex justify-between items-center px-6 py-5 max-w-4xl mx-auto">
<Link href="/autojobs" className="text-2xl font-bold text-white">
Auto<span className="text-blue-400">Jobs</span>
</Link>
<Link href="/autojobs" className="text-slate-400 hover:text-white transition text-sm">
Back to Home
</Link>
</nav>
<main className="max-w-3xl mx-auto px-6 py-12 text-slate-300 leading-relaxed">
<h1 className="text-3xl font-bold text-white mb-8">Privacy Policy</h1>
<p className="text-sm text-slate-500 mb-8">Last updated: April 13, 2026</p>
<div className="space-y-8">
<section>
<h2 className="text-xl font-semibold text-white mb-3">1. Information We Collect</h2>
<p>We collect information you provide directly: name, email address, resume content, LinkedIn profile data (when you connect via OAuth), job preferences, and payment information (processed securely via Stripe). We also collect usage data including job applications made, AI customizations used, and API calls.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">2. How We Use Your Data</h2>
<ul className="list-disc pl-6 space-y-2">
<li>Provide AI-powered job application services</li>
<li>Customize your resume and generate cover letters</li>
<li>Track your job application history</li>
<li>Process payments via Stripe</li>
<li>Send service-related notifications</li>
<li>Improve our services</li>
</ul>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">3. Data Retention</h2>
<p>We retain your data for the duration of your subscription plus 30 days after cancellation. Financial records are retained for 7 years per tax regulations. You may request deletion at any time we process within 30 days.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">4. Your Rights (GDPR)</h2>
<p>Under GDPR, you have the right to: <strong className="text-white">Access</strong> your data, <strong className="text-white">Rectify</strong> inaccuracies, <strong className="text-white">Erase</strong> ("right to be forgotten"), <strong className="text-white">Restrict</strong> processing, <strong className="text-white">Port</strong> your data, and <strong className="text-white">Object</strong> to processing. Contact privacy@hostpioneers.com to exercise these rights.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">5. Cookies</h2>
<p>We use essential cookies for authentication and session management. Optional analytics cookies help us understand usage patterns. You can disable non-essential cookies in your browser settings.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">6. Third Parties</h2>
<ul className="list-disc pl-6 space-y-2">
<li><strong className="text-white">Stripe:</strong> Payment processing. Their privacy policy applies.</li>
<li><strong className="text-white">LinkedIn:</strong> OAuth login. Their privacy policy applies.</li>
<li><strong className="text-white">AI Providers:</strong> Resume customization via API. Data is not stored by AI providers.</li>
</ul>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">7. Data Security</h2>
<p>We use encryption (TLS 1.3), secure servers, access controls, and regular security audits. No method is 100% secure we commit to industry-standard protections.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">8. Contact</h2>
<p>Data Controller: HostPioneers, Benalmádena, Málaga, Spain<br/>
Email: <a href="mailto:privacy@hostpioneers.com" className="text-blue-400 hover:underline">privacy@hostpioneers.com</a><br/>
Response time: Within 30 days</p>
</section>
</div>
</main>
</div>
)
}
+105 -58
View File
@@ -1,10 +1,28 @@
"use client"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { useState, Suspense } from "react"
import { useRouter, useSearchParams } from "next/navigation"
import Link from "next/link"
export default function SignupPage() {
const PLAN_INFO: Record<string, { name: string; price: string; apps: string }> = {
free: { name: "Free", price: "$0/mo", apps: "5 apps" },
starter: { name: "Starter", price: "$29/mo", apps: "20 apps" },
pro: { name: "Pro", price: "$69/mo", apps: "100 apps" },
ultra: { name: "Ultra", price: "$149/mo", apps: "200 apps" },
unlimited: { name: "Unlimited", price: "$199/mo", apps: "Unlimited" },
agency_starter: { name: "Agency Starter", price: "$555/mo", apps: "1,000 submissions" },
agency_growth: { name: "Agency Growth", price: "$999/mo", apps: "3,000 submissions" },
agency_scale: { name: "Agency Scale", price: "$1,499/mo", apps: "5,000 submissions" },
agency_pro: { name: "Agency Pro", price: "$3,699/mo", apps: "10,000 submissions" },
agency_enterprise: { name: "Enterprise", price: "Contact Us", apps: "Unlimited" },
}
function SignupForm() {
const router = useRouter()
const searchParams = useSearchParams()
const planId = searchParams.get("plan") || "free"
const userType = searchParams.get("type") || "private"
const plan = PLAN_INFO[planId] || PLAN_INFO.free
const [form, setForm] = useState({
name: "", email: "", password: "", confirmPassword: ""
})
@@ -21,13 +39,45 @@ export default function SignupPage() {
setError("")
try {
const res = await fetch("/api/auth/signup", {
const res = await fetch("/autojobs/api/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: form.name, email: form.email, password: form.password })
body: JSON.stringify({
name: form.name,
email: form.email,
password: form.password,
user_type: userType,
plan: planId
})
})
if (res.ok) {
router.push("/autojobs/profile-setup")
const data = await res.json()
if (planId === "free" || planId === "agency_enterprise") {
router.push("/autojobs/dashboard")
return
}
const checkoutRes = await fetch("https://hostpioneers.com/autojobs/api/create-checkout", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
user_id: data.user_id,
plan_id: planId,
user_type: userType
})
})
if (checkoutRes.ok) {
const checkoutData = await checkoutRes.json()
if (checkoutData.checkout_url) {
window.location.href = checkoutData.checkout_url
return
}
}
router.push("/autojobs/dashboard")
} else {
const data = await res.json()
setError(data.error || "Signup failed")
@@ -39,85 +89,82 @@ export default function SignupPage() {
}
return (
<div className="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div className="w-full max-w-md">
<div className="text-center mb-8">
<div className="min-h-screen bg-slate-900 flex items-center justify-center px-4 py-8">
<div className="w-full max-w-sm">
<div className="text-center mb-6">
<Link href="/autojobs" className="text-2xl font-bold text-white">
Auto<span className="text-blue-400">Jobs</span>
</Link>
<p className="text-slate-400 mt-2">Create your account</p>
<p className="text-slate-400 mt-2 text-sm">Create your account</p>
</div>
<div className="bg-slate-800 rounded-2xl p-5 border border-blue-500/30 mb-5">
<div className="flex justify-between items-center">
<div>
<div className="text-xs text-slate-400">Selected Plan</div>
<div className="text-lg font-bold text-white">{plan.name}</div>
<div className="text-xs text-slate-400">{plan.apps}/month</div>
</div>
<div className="text-right">
<div className="text-xl font-bold text-blue-400">{plan.price}</div>
{planId !== "free" && planId !== "agency_enterprise" && (
<div className="text-xs text-slate-400">billed monthly</div>
)}
</div>
</div>
</div>
<form onSubmit={handleSubmit} className="bg-slate-800 rounded-2xl p-8 border border-slate-700">
<form onSubmit={handleSubmit} className="bg-slate-800 rounded-2xl p-6 border border-slate-700">
{error && (
<div className="mb-4 p-3 bg-red-500/20 border border-red-500/30 rounded-lg text-red-400 text-sm">
<div className="mb-4 p-3 bg-red-500/20 border border-red-500/30 rounded-lg text-red-400 text-xs">
{error}
</div>
)}
<div className="space-y-4">
<div>
<label className="block text-sm text-slate-400 mb-1">Full Name</label>
<input
required
type="text"
value={form.name}
onChange={e => setForm({...form, name: e.target.value})}
className="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500"
placeholder="John Smith"
/>
<label className="block text-xs text-slate-400 mb-1">Full Name</label>
<input required type="text" value={form.name} onChange={e => setForm({...form, name: e.target.value})}
className="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500 text-sm" placeholder="John Smith" />
</div>
<div>
<label className="block text-sm text-slate-400 mb-1">Email</label>
<input
required
type="email"
value={form.email}
onChange={e => setForm({...form, email: e.target.value})}
className="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500"
placeholder="you@example.com"
/>
<label className="block text-xs text-slate-400 mb-1">Email</label>
<input required type="email" value={form.email} onChange={e => setForm({...form, email: e.target.value})}
className="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500 text-sm" placeholder="you@example.com" />
</div>
<div>
<label className="block text-sm text-slate-400 mb-1">Password</label>
<input
required
type="password"
value={form.password}
onChange={e => setForm({...form, password: e.target.value})}
className="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500"
placeholder="••••••••"
/>
<label className="block text-xs text-slate-400 mb-1">Password</label>
<input required type="password" value={form.password} onChange={e => setForm({...form, password: e.target.value})}
className="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500 text-sm" placeholder="••••••••" />
</div>
<div>
<label className="block text-sm text-slate-400 mb-1">Confirm Password</label>
<input
required
type="password"
value={form.confirmPassword}
onChange={e => setForm({...form, confirmPassword: e.target.value})}
className="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500"
placeholder="••••••••"
/>
<label className="block text-xs text-slate-400 mb-1">Confirm Password</label>
<input required type="password" value={form.confirmPassword} onChange={e => setForm({...form, confirmPassword: e.target.value})}
className="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:border-blue-500 text-sm" placeholder="••••••••" />
</div>
</div>
<button
type="submit"
disabled={loading}
className="w-full mt-6 py-3 bg-blue-500 hover:bg-blue-600 disabled:bg-slate-600 text-white rounded-xl font-semibold transition"
>
{loading ? "Creating account..." : "Create Account"}
<button type="submit" disabled={loading}
className="w-full mt-5 py-3 bg-blue-500 hover:bg-blue-600 disabled:bg-slate-600 text-white rounded-xl font-semibold transition text-sm">
{loading ? "Creating account..." : planId === "free" ? "Create Free Account" : `Pay ${plan.price} & Subscribe`}
</button>
</form>
<p className="text-center text-slate-400 mt-6 text-sm">
<p className="text-center text-slate-400 mt-5 text-xs">
Already have an account? <Link href="/autojobs/login" className="text-blue-400 hover:underline">Sign in</Link>
</p>
<p className="text-center text-slate-500 mt-3 text-xs">
By signing up, you agree to our <a href="/autojobs/terms" className="text-blue-400 hover:underline">Terms</a> and <a href="/autojobs/privacy" className="text-blue-400 hover:underline">Privacy Policy</a>.
</p>
</div>
</div>
)
}
export default function SignupPage() {
return (
<Suspense fallback={<div className="min-h-screen bg-slate-900 flex items-center justify-center"><div className="text-white">Loading...</div></div>}>
<SignupForm />
</Suspense>
)
}
+123
View File
@@ -0,0 +1,123 @@
import Link from "next/link"
import type { Metadata } from "next"
export const metadata: Metadata = {
title: "Terms of Service — AutoJobs",
description: "AutoJobs terms of service. Rules, limitations, and user responsibilities for using our AI job application automation platform.",
}
export default function TermsPage() {
return (
<div className="min-h-screen bg-slate-900">
<nav className="flex justify-between items-center px-6 py-5 max-w-4xl mx-auto">
<Link href="/autojobs" className="text-2xl font-bold text-white">
Auto<span className="text-blue-400">Jobs</span>
</Link>
<Link href="/autojobs" className="text-slate-400 hover:text-white transition text-sm">
Back to Home
</Link>
</nav>
<main className="max-w-3xl mx-auto px-6 py-12 text-slate-300 leading-relaxed">
<h1 className="text-3xl font-bold text-white mb-8">Terms of Service</h1>
<p className="text-sm text-slate-500 mb-8">Last updated: April 13, 2026</p>
<div className="space-y-8">
<section>
<h2 className="text-xl font-semibold text-white mb-3">1. Acceptance of Terms</h2>
<p>By creating an account or using AutoJobs, you agree to these Terms of Service. If you do not agree, do not use our services. These terms form a binding agreement between you and HostPioneers.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">2. Services Description</h2>
<p>AutoJobs provides AI-powered job application automation, including: job search aggregation, AI resume customization, AI cover letter generation, application tracking, and related features. We do not guarantee job placement, interviews, or employment.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">3. Account Responsibilities</h2>
<ul className="list-disc pl-6 space-y-2">
<li>You are responsible for maintaining the confidentiality of your login credentials</li>
<li>You must provide accurate information during signup</li>
<li>You are responsible for all activity under your account</li>
<li>You must be at least 18 years old to use the service</li>
<li>One account per person unless using an approved agency plan</li>
</ul>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">4. Acceptable Use</h2>
<p>You agree NOT to:</p>
<ul className="list-disc pl-6 space-y-2 mt-2">
<li>Use AutoJobs for illegal purposes</li>
<li>Submit false, misleading, or fraudulent job applications</li>
<li>Mass-apply to jobs without genuine interest</li>
<li>Share account access with others (agency plans have separate client profiles)</li>
<li>Reverse engineer, decompile, or hack our systems</li>
<li>Use automated bots or scrapers outside the API</li>
<li>Spam job postings or employers</li>
</ul>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">5. Subscriptions & Billing</h2>
<ul className="list-disc pl-6 space-y-2">
<li>Subscriptions renew monthly on the same date</li>
<li>All prices are in USD unless stated otherwise</li>
<li>You authorize recurring charges to your payment method</li>
<li>Cancellation takes effect at end of current billing period</li>
<li>No refunds for partial months (prorated or otherwise)</li>
<li>Plan limits reset monthly; unused applications do not roll over</li>
</ul>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">6. Agency Plans</h2>
<p>Agency plans allow managing multiple client profiles. The subscribing agency is responsible for:</p>
<ul className="list-disc pl-6 space-y-2 mt-2">
<li>Obtaining consent from clients for data processing</li>
<li>Compliance with applicable employment laws in their jurisdiction</li>
<li>Not exceeding submission caps per plan</li>
<li>Proper use of white-label features</li>
</ul>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">7. AI-Generated Content</h2>
<p>AI-generated resumes and cover letters are tools to assist your job search. You are responsible for reviewing and approving all AI-generated content before submission. We do not guarantee accuracy, relevance, or success of AI-generated content.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">8. Limitation of Liability</h2>
<p>AutoJobs and HostPioneers are NOT liable for: direct, indirect, incidental, or consequential damages arising from use of the service; loss of employment, interviews, or job opportunities; actions taken by employers based on AI-generated content; service interruptions or data loss.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">9. Service Availability</h2>
<p>We strive for 99.9% uptime but do not guarantee uninterrupted service. Scheduled maintenance will be announced when possible. We reserve the right to modify, suspend, or discontinue features with 30 days notice.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">10. Intellectual Property</h2>
<p>You retain ownership of your resume and personal data. We retain ownership of our platform, AI models, and proprietary technology. You grant us a limited license to process your data as part of providing services.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">11. Termination</h2>
<p>We may suspend or terminate accounts that violate these terms. You may cancel anytime via dashboard or by contacting support. Upon termination, your data is retained for 30 days per our privacy policy.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">12. Governing Law</h2>
<p>These terms are governed by the laws of Spain. Any disputes shall be resolved in courts located in Málaga, Spain. If any provision is found unenforceable, remaining provisions continue in effect.</p>
</section>
<section>
<h2 className="text-xl font-semibold text-white mb-3">13. Contact</h2>
<p>Questions about these terms?<br/>
Email: <a href="mailto:legal@hostpioneers.com" className="text-blue-400 hover:underline">legal@hostpioneers.com</a></p>
</section>
</div>
</main>
</div>
)
}
+14
View File
@@ -0,0 +1,14 @@
User-agent: *
Allow: /autojobs/
Allow: /autojobs/signup
Allow: /autojobs/login
User-agent: GPTBot
Disallow: /autojobs/dashboard
Disallow: /autojobs/api/
User-agent: CCBot
Disallow: /autojobs/dashboard
Disallow: /autojobs/api/
Sitemap: https://hostpioneers.com/autojobs/sitemap.xml
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://hostpioneers.com/autojobs</loc>
<lastmod>2026-04-13</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://hostpioneers.com/autojobs/signup</loc>
<lastmod>2026-04-13</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://hostpioneers.com/autojobs/login</loc>
<lastmod>2026-04-13</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
</urlset>