← back to jerof__job-tracker

Function bodies 234 total

All specs Real LLM only Function bodies
HowItWorks function · typescript · L650-L746 (97 LOC)
app/(marketing)/page.tsx
function HowItWorks() {
  const steps = [
    {
      number: '1',
      title: 'Save jobs instantly',
      description: 'Use our Chrome extension to save jobs from LinkedIn, Indeed, or any job board with one click.',
      icon: (
        <svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
          <path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" />
        </svg>
      )
    },
    {
      number: '2',
      title: 'Everything syncs',
      description: 'Connect Gmail and watch your applications update automatically. Interview invites, rejections — all tracked.',
      icon: (
        <svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
          <path strokeLinecap="round" strokeLinejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
        </svg>
      )
    },
    {
      number: '3',
      title:
Testimonials function · typescript · L751-L836 (86 LOC)
app/(marketing)/page.tsx
function Testimonials() {
  const testimonials = [
    {
      quote: "The Chrome extension is a game changer. I save jobs from LinkedIn in one click while browsing, and they're all organized in my board when I'm ready to apply.",
      author: "Sarah Chen",
      role: "Senior Product Manager",
      result: "Hired at Stripe",
      avatar: "S"
    },
    {
      quote: "Gmail sync is magic. Applied to 47 jobs and never lost track of a single one. When Spotify called for an interview, I had all my research notes ready.",
      author: "Marcus Johnson",
      role: "Software Engineer",
      result: "Hired at Spotify",
      avatar: "M"
    },
    {
      quote: "The AI research chat told me exactly what to expect in my Notion interview. I walked in knowing their values, recent news, and the right questions to ask.",
      author: "Priya Patel",
      role: "Marketing Director",
      result: "3 offers in 4 weeks",
      avatar: "P"
    }
  ];

  return (
    <section className="py-24 
CTASection function · typescript · L841-L882 (42 LOC)
app/(marketing)/page.tsx
function CTASection() {
  return (
    <section className="py-24 lg:py-32 bg-gray-900 relative overflow-hidden">
      {/* Decorative */}
      <div className="absolute top-0 left-0 w-96 h-96 bg-gray-800 rounded-full blur-3xl -translate-x-1/2 -translate-y-1/2" />
      <div className="absolute bottom-0 right-0 w-96 h-96 bg-gray-800 rounded-full blur-3xl translate-x-1/2 translate-y-1/2" />

      <motion.div
        initial="hidden"
        whileInView="visible"
        viewport={{ once: true }}
        variants={fadeInUp}
        className="relative z-10 max-w-4xl mx-auto px-6 text-center"
      >
        <h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-white mb-4">
          Your next job is out there. Go get it.
        </h2>
        <p className="text-lg text-gray-400 mb-8 max-w-2xl mx-auto">
          Track every application, research any company, and generate tailored CVs — all in one place. Free to start.
        </p>
        <div className="flex flex-col sm:flex-ro
Footer function · typescript · L887-L962 (76 LOC)
app/(marketing)/page.tsx
function Footer() {
  return (
    <footer className="py-16 bg-white border-t border-gray-100">
      <div className="max-w-7xl mx-auto px-6">
        <div className="grid grid-cols-2 md:grid-cols-4 gap-8 mb-12">
          {/* Brand */}
          <div className="col-span-2 md:col-span-1">
            <a href="/" className="flex items-center gap-1.5 mb-4">
              <svg className="w-6 h-6" viewBox="0 0 24 24" fill="none">
                <path d="M3 18C3 10 7 4 12 4C17 4 21 10 21 18" stroke="url(#logo-gradient-footer)" strokeWidth="1.5" strokeLinecap="round"/>
                <path d="M6 17C6 11 8.5 6.5 12 6.5C15.5 6.5 18 11 18 17" stroke="url(#logo-gradient-footer)" strokeWidth="1.5" strokeLinecap="round"/>
                <path d="M9 16C9 12 10.5 9 12 9C13.5 9 15 12 15 16" stroke="url(#logo-gradient-footer)" strokeWidth="1.5" strokeLinecap="round"/>
                <defs>
                  <linearGradient id="logo-gradient-footer" x1="3" y1="4" x2="21" y2="18" gradientUnits="user
LandingPage function · typescript · L967-L980 (14 LOC)
app/(marketing)/page.tsx
export default function LandingPage() {
  return (
    <main className="scroll-smooth bg-white">
      <Nav />
      <HeroSection />
      <LogoBar />
      <FeatureSections />
      <HowItWorks />
      <Testimonials />
      <CTASection />
      <Footer />
    </main>
  );
}
Nav function · typescript · L96-L130 (35 LOC)
app/(marketing)/pricing/page.tsx
function Nav() {
  return (
    <nav className="fixed top-0 left-0 right-0 z-50 px-6 py-4 bg-white/80 backdrop-blur-lg border-b border-gray-100">
      <div className="max-w-7xl mx-auto flex items-center justify-between">
        <a href="/" className="flex items-center gap-1.5">
          <svg className="w-6 h-6" viewBox="0 0 24 24" fill="none">
            <path d="M3 18C3 10 7 4 12 4C17 4 21 10 21 18" stroke="url(#logo-gradient-pricing-nav)" strokeWidth="1.5" strokeLinecap="round"/>
            <path d="M6 17C6 11 8.5 6.5 12 6.5C15.5 6.5 18 11 18 17" stroke="url(#logo-gradient-pricing-nav)" strokeWidth="1.5" strokeLinecap="round"/>
            <path d="M9 16C9 12 10.5 9 12 9C13.5 9 15 12 15 16" stroke="url(#logo-gradient-pricing-nav)" strokeWidth="1.5" strokeLinecap="round"/>
            <defs>
              <linearGradient id="logo-gradient-pricing-nav" x1="3" y1="4" x2="21" y2="18" gradientUnits="userSpaceOnUse">
                <stop stopColor="#059669"/>
                <stop of
PricingHero function · typescript · L135-L184 (50 LOC)
app/(marketing)/pricing/page.tsx
function PricingHero() {
  return (
    <section className="relative pt-32 pb-16 bg-gradient-to-b from-indigo-50 via-white to-white">
      <div className="absolute top-20 left-10 w-72 h-72 bg-indigo-100/50 rounded-full blur-3xl" />
      <div className="absolute top-40 right-10 w-96 h-96 bg-blue-50/50 rounded-full blur-3xl" />

      <div className="relative z-10 max-w-7xl mx-auto px-6 text-center">
        <motion.div initial="hidden" animate="visible" variants={staggerContainer}>
          <motion.div variants={fadeInUp}>
            <span className="inline-flex items-center gap-2 px-4 py-2 mb-6 rounded-full bg-indigo-100 border border-indigo-200">
              <span className="text-sm font-medium text-indigo-700">No subscriptions. Pay once, use forever.</span>
            </span>
          </motion.div>

          <motion.h1 variants={fadeInUp} className="text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight text-gray-900 mb-4">
            Buy credits.{' '}
            <span 
Want this analysis on your repo? https://repobility.com/scan/
PricingCard function · typescript · L189-L280 (92 LOC)
app/(marketing)/pricing/page.tsx
function PricingCard({ tier }: { tier: typeof pricingTiers[0] }) {
  const isPopular = tier.popular;
  const isFree = tier.price === 0;

  return (
    <motion.div
      variants={fadeInUp}
      className={`relative rounded-2xl p-8 ${
        isPopular
          ? 'bg-indigo-600 text-white ring-4 ring-indigo-600 ring-offset-4 ring-offset-white scale-105'
          : 'bg-white border border-gray-200'
      }`}
    >
      {tier.badge && (
        <span className="absolute -top-3 left-1/2 -translate-x-1/2 px-3 py-1 bg-amber-400 text-amber-900 text-xs font-bold rounded-full">
          {tier.badge}
        </span>
      )}

      {isPopular && (
        <span className="absolute -top-3 left-1/2 -translate-x-1/2 px-3 py-1 bg-white text-indigo-600 text-xs font-bold rounded-full">
          Most Popular
        </span>
      )}

      <div className="text-center mb-6">
        <h3 className={`text-xl font-bold mb-1 ${isPopular ? 'text-white' : 'text-gray-900'}`}>
          {tier.name}
     
PricingCardsSection function · typescript · L282-L324 (43 LOC)
app/(marketing)/pricing/page.tsx
function PricingCardsSection() {
  return (
    <section className="py-16 bg-white">
      <div className="max-w-7xl mx-auto px-6">
        <motion.div
          initial="hidden"
          whileInView="visible"
          viewport={{ once: true }}
          variants={staggerContainer}
          className="grid md:grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-4 items-start"
        >
          {pricingTiers.map((tier) => (
            <PricingCard key={tier.name} tier={tier} />
          ))}
        </motion.div>

        {/* Value comparison */}
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true }}
          className="mt-16 text-center"
        >
          <p className="text-gray-500 mb-4">Cost per tailored CV</p>
          <div className="flex flex-wrap justify-center gap-8">
            <div className="text-center">
              <p className="text-2xl font-bold text-gray-900">$0.90</p>
              <
WhyCreditsSection function · typescript · L329-L396 (68 LOC)
app/(marketing)/pricing/page.tsx
function WhyCreditsSection() {
  const reasons = [
    {
      icon: (
        <svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
          <path strokeLinecap="round" strokeLinejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
        </svg>
      ),
      title: 'Job search is temporary',
      description: 'You search for 2-6 months, land a job, then stop. Why pay monthly for something you won\'t use?'
    },
    {
      icon: (
        <svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
          <path strokeLinecap="round" strokeLinejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
        </svg>
      ),
      title: 'No subscription guilt',
      description: 'No forgetting to cancel. No paying when you\'re not using it. Buy once, use when you need.'
    },
    {
      icon: (
        <svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"
FAQSection function · typescript · L401-L462 (62 LOC)
app/(marketing)/pricing/page.tsx
function FAQSection() {
  const faqs = [
    {
      q: 'Do credits expire?',
      a: 'No! Your credits never expire. Buy them today, use them in 2 years. They\'re yours forever.'
    },
    {
      q: 'What counts as one CV credit?',
      a: 'One credit = one tailored CV generated for a specific job. You can download it as many times as you want.'
    },
    {
      q: 'Can I use the free tier forever?',
      a: 'Yes! The free tier gives you 3 CVs per month, forever. No credit card required.'
    },
    {
      q: 'What if I run out of credits?',
      a: 'Just buy another pack. Your new credits add to any remaining ones.'
    },
    {
      q: 'Is there a refund policy?',
      a: 'Yes, 14-day money-back guarantee. If you\'re not happy, we\'ll refund you, no questions asked.'
    },
    {
      q: 'Can I upgrade later?',
      a: 'Absolutely. Buy any pack anytime. Credits stack, so you never lose what you have.'
    },
  ];

  return (
    <section className="py-20 bg-white">
    
CTASection function · typescript · L467-L497 (31 LOC)
app/(marketing)/pricing/page.tsx
function CTASection() {
  return (
    <section className="py-20 bg-gradient-to-br from-indigo-500 to-indigo-700 relative overflow-hidden">
      <div className="absolute top-0 left-0 w-96 h-96 bg-white/10 rounded-full blur-3xl -translate-x-1/2 -translate-y-1/2" />
      <div className="absolute bottom-0 right-0 w-96 h-96 bg-indigo-400/20 rounded-full blur-3xl translate-x-1/2 translate-y-1/2" />

      <motion.div
        initial={{ opacity: 0, y: 20 }}
        whileInView={{ opacity: 1, y: 0 }}
        viewport={{ once: true }}
        className="relative z-10 max-w-4xl mx-auto px-6 text-center"
      >
        <h2 className="text-3xl md:text-4xl lg:text-5xl font-bold text-white mb-4">
          Start free. Buy credits when you need them.
        </h2>
        <p className="text-lg text-indigo-100 mb-8 max-w-2xl mx-auto">
          Try 3 free CVs. See the magic. Then decide.
        </p>
        <a
          href="/cv-input"
          className="inline-flex items-center gap-2 px-8 py-
Footer function · typescript · L502-L526 (25 LOC)
app/(marketing)/pricing/page.tsx
function Footer() {
  return (
    <footer className="py-16 bg-white border-t border-gray-100">
      <div className="max-w-7xl mx-auto px-6">
        <div className="flex flex-col md:flex-row justify-between items-center gap-4">
          <a href="/" className="flex items-center gap-1.5">
            <svg className="w-6 h-6" viewBox="0 0 24 24" fill="none">
              <path d="M3 18C3 10 7 4 12 4C17 4 21 10 21 18" stroke="url(#logo-gradient-pricing-footer)" strokeWidth="1.5" strokeLinecap="round"/>
              <path d="M6 17C6 11 8.5 6.5 12 6.5C15.5 6.5 18 11 18 17" stroke="url(#logo-gradient-pricing-footer)" strokeWidth="1.5" strokeLinecap="round"/>
              <path d="M9 16C9 12 10.5 9 12 9C13.5 9 15 12 15 16" stroke="url(#logo-gradient-pricing-footer)" strokeWidth="1.5" strokeLinecap="round"/>
              <defs>
                <linearGradient id="logo-gradient-pricing-footer" x1="3" y1="4" x2="21" y2="18" gradientUnits="userSpaceOnUse">
                  <stop stopColor=
PricingPage function · typescript · L531-L543 (13 LOC)
app/(marketing)/pricing/page.tsx
export default function PricingPage() {
  return (
    <main className="scroll-smooth bg-white min-h-screen">
      <Nav />
      <PricingHero />
      <PricingCardsSection />
      <WhyCreditsSection />
      <FAQSection />
      <CTASection />
      <Footer />
    </main>
  );
}
AddJobPage function · typescript · L15-L203 (189 LOC)
app/(onboarding)/add-job/page.tsx
export default function AddJobPage() {
  const router = useRouter();
  const [jobUrl, setJobUrl] = useState('');
  const [company, setCompany] = useState('');
  const [role, setRole] = useState('');
  const [showManualEntry, setShowManualEntry] = useState(false);
  const [isLoading, setIsLoading] = useState(false);

  const handleGenerate = async () => {
    setIsLoading(true);

    // Save job details to localStorage
    localStorage.setItem('onboarding_job', JSON.stringify({
      url: jobUrl,
      company: company || 'Example Company',
      role: role || 'Software Engineer',
    }));

    // Simulate API call for fetching job details / generating CV
    setTimeout(() => {
      router.push('/success');
    }, 2000);
  };

  const handleSkip = () => {
    localStorage.setItem('onboarding_job', JSON.stringify({
      url: '',
      company: '',
      role: '',
      skipped: true,
    }));
    router.push('/success');
  };

  const hasInput = jobUrl.trim() || (company.trim() && role
Repobility · code-quality intelligence · https://repobility.com
CVInputPage function · typescript · L29-L193 (165 LOC)
app/(onboarding)/cv-input/page.tsx
export default function CVInputPage() {
  const router = useRouter();
  const fileInputRef = useRef<HTMLInputElement>(null);

  // Use lazy initialization to avoid effect-based setState
  const [cvText, setCvText] = useState(() => getInitialCVText());
  const [isUploading, setIsUploading] = useState(false);
  const [fileName, setFileName] = useState<string | null>(null);

  // Save CV text to localStorage
  const saveCVText = (text: string) => {
    setCvText(text);
    localStorage.setItem('onboarding_cv_text', text);
    localStorage.setItem('onboarding_cv_source', 'paste');
  };

  const handleContinue = () => {
    if (cvText.trim()) {
      localStorage.setItem('onboarding_cv_text', cvText);
      localStorage.setItem('onboarding_cv_source', 'paste');
    }
    router.push('/questions');
  };

  const handleNoCVClick = () => {
    localStorage.setItem('onboarding_cv_text', '');
    localStorage.setItem('onboarding_cv_source', 'scratch');
    router.push('/questions');
  };

  cons
OnboardingLayout function · typescript · L19-L82 (64 LOC)
app/(onboarding)/layout.tsx
export default function OnboardingLayout({ children }: { children: ReactNode }) {
  const pathname = usePathname();
  const currentStep = stepMap[pathname] || 1;

  // Don't show progress on success page
  const showProgress = pathname !== '/success';

  return (
    <div className="min-h-screen bg-gradient-to-b from-gray-50 via-white to-white">
      {/* Subtle decorative elements */}
      <div className="fixed top-20 left-10 w-72 h-72 bg-gray-200/50 rounded-full blur-3xl pointer-events-none" />
      <div className="fixed bottom-20 right-10 w-96 h-96 bg-gray-100/60 rounded-full blur-3xl pointer-events-none" />

      {/* Grid pattern overlay */}
      <div
        className="fixed inset-0 pointer-events-none"
        style={{
          backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' width='32' height='32' fill='none' stroke='rgb(148 163 184 / 0.05)'%3e%3cpath d='M0 .5H31.5V32'/%3e%3c/svg%3e")`,
        }}
      />

      {/* Lo
getInitialState function · typescript · L62-L78 (17 LOC)
app/(onboarding)/questions/page.tsx
function getInitialState() {
  if (typeof window === 'undefined') {
    return { questions: QUESTIONS_WITH_CV, answers: {} };
  }

  const cvSource = localStorage.getItem('onboarding_cv_source');
  const cvText = localStorage.getItem('onboarding_cv_text');
  const savedAnswers = localStorage.getItem('onboarding_answers');

  const questions = cvSource === 'scratch' || !cvText || cvText.trim() === ''
    ? QUESTIONS_NO_CV
    : QUESTIONS_WITH_CV;

  const answers = savedAnswers ? JSON.parse(savedAnswers) : {};

  return { questions, answers };
}
QuestionsPage function · typescript · L80-L254 (175 LOC)
app/(onboarding)/questions/page.tsx
export default function QuestionsPage() {
  const router = useRouter();

  // Initialize all state from localStorage to avoid useEffect setState
  const initialState = useMemo(() => getInitialState(), []);

  const [questions] = useState(initialState.questions);
  const [currentIndex, setCurrentIndex] = useState(0);
  const [answers, setAnswers] = useState<Record<string, string>>(initialState.answers);
  const [currentAnswer, setCurrentAnswer] = useState(() => {
    const firstQuestion = initialState.questions[0];
    return firstQuestion ? (initialState.answers[firstQuestion.id] || '') : '';
  });
  const [skipMessage, setSkipMessage] = useState<string | null>(null);

  const currentQuestion = questions[currentIndex];
  const isLastQuestion = currentIndex === questions.length - 1;

  const saveAndProceed = useCallback((includeCurrentAnswer: boolean) => {
    const updatedAnswers = { ...answers };

    if (includeCurrentAnswer && currentAnswer.trim()) {
      updatedAnswers[currentQues
enhanceCV function · typescript · L22-L60 (39 LOC)
app/(onboarding)/review/page.tsx
    async function enhanceCV() {
      try {
        // Get stored CV text and answers from localStorage
        const cvText = localStorage.getItem('onboarding_cv_text') || '';
        const answersJson = localStorage.getItem('onboarding_answers');
        const answers = answersJson ? JSON.parse(answersJson) : {};

        // Call the enhance-cv API with the user's actual data
        const response = await fetch('/api/onboarding/enhance-cv', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            cvText,
            answers,
          }),
        });

        const data = await response.json();

        if (!response.ok || !data.success) {
          throw new Error(data.error || 'Failed to enhance CV');
        }

        setMasterCV(data.masterCV);
      } catch (err) {
        console.error('Error enhancing CV:', err);
        setError(err instanceof Error ? err.message : 'Failed to 
Confetti function · typescript · L9-L53 (45 LOC)
app/(onboarding)/success/page.tsx
function Confetti() {
  const colors = ['#8B5CF6', '#A78BFA', '#C4B5FD', '#60A5FA', '#34D399', '#FBBF24'];
  const [windowHeight, setWindowHeight] = useState(0);

  useEffect(() => {
    setWindowHeight(window.innerHeight);
  }, []);

  if (windowHeight === 0) return null;

  return (
    <div className="fixed inset-0 pointer-events-none overflow-hidden">
      {Array.from({ length: 50 }).map((_, i) => {
        // Generate random values for each confetti piece
        const leftPos = `${(i * 17 + 7) % 100}%`;
        const rotateAmount = ((i * 23) % 720) - 360;
        const duration = 2 + (i % 3);
        const delay = (i % 10) * 0.05;

        return (
          <motion.div
            key={i}
            className="absolute w-3 h-3 rounded-sm"
            style={{
              backgroundColor: colors[i % colors.length],
              left: leftPos,
              top: -20,
            }}
            initial={{ y: -20, rotate: 0, opacity: 1 }}
            animate={{
              y:
SuccessPage function · typescript · L72-L266 (195 LOC)
app/(onboarding)/success/page.tsx
export default function SuccessPage() {
  const router = useRouter();
  const [showConfetti, setShowConfetti] = useState(true);
  const [mounted, setMounted] = useState(false);

  // Initialize job details on mount
  const [jobDetails, setJobDetails] = useState<{ company: string; role: string } | null>(null);

  useEffect(() => {
    setMounted(true);
    setJobDetails(getInitialJobDetails());

    // Hide confetti after animation
    const timer = setTimeout(() => setShowConfetti(false), 4000);
    return () => clearTimeout(timer);
  }, []);

  const handleDownload = () => {
    // Placeholder for PDF download functionality
    alert('PDF download will be implemented with the API integration.');
  };

  const handleGoToDashboard = () => {
    // Mark onboarding as complete
    localStorage.setItem('onboarding_completed', 'true');
    router.push('/dashboard');
  };

  return (
    <>
      {mounted && showConfetti && <Confetti />}

      <OnboardingCard className="text-center">
      
updateBadge function · javascript · L20-L35 (16 LOC)
extension/background.js
async function updateBadge() {
  try {
    const { authToken } = await chrome.storage.local.get(['authToken']);

    if (authToken) {
      // User is logged in - clear badge
      chrome.action.setBadgeText({ text: '' });
    } else {
      // User needs to log in - show indicator
      chrome.action.setBadgeText({ text: '!' });
      chrome.action.setBadgeBackgroundColor({ color: BADGE_COLORS.error });
    }
  } catch (e) {
    console.error('Error updating badge:', e);
  }
}
If a scraper extracted this row, it came from Repobility (https://repobility.com)
checkDuplicate function · javascript · L45-L74 (30 LOC)
extension/background.js
async function checkDuplicate(company, role) {
  try {
    const { authToken, userId } = await chrome.storage.local.get(['authToken', 'userId']);

    if (!authToken || !userId) {
      return { isDuplicate: false };
    }

    const response = await fetch(
      `${API_BASE_URL}/api/applications/check-duplicate?` +
      new URLSearchParams({ company, role, user_id: userId }),
      {
        headers: {
          'Authorization': `Bearer ${authToken}`,
        },
      }
    );

    if (!response.ok) {
      return { isDuplicate: false };
    }

    const data = await response.json();
    return { isDuplicate: data.exists, existingJob: data.job };

  } catch (e) {
    console.error('Error checking duplicate:', e);
    return { isDuplicate: false };
  }
}
extractGeneric function · javascript · L150-L166 (17 LOC)
extension/content.js
function extractGeneric() {
  const data = {};

  // Try to get title from h1 or page title
  const h1 = document.querySelector('h1');
  if (h1) {
    data.role = h1.textContent.trim();
  }

  // Try to get company from meta tags or common elements
  const metaCompany = document.querySelector('meta[property="og:site_name"]');
  if (metaCompany) {
    data.company = metaCompany.content;
  }

  return data;
}
extractJobData function · javascript · L169-L183 (15 LOC)
extension/content.js
function extractJobData() {
  const url = window.location.href;

  // Find matching extractor
  for (const [name, extractor] of Object.entries(extractors)) {
    if (extractor.match(url)) {
      console.log(`Using ${name} extractor`);
      return extractor.extract();
    }
  }

  // Fall back to generic extraction
  console.log('Using generic extractor');
  return extractGeneric();
}
extractJobDataFromPage function · javascript · L16-L151 (136 LOC)
extension/popup.js
function extractJobDataFromPage() {
  const url = window.location.href;
  const data = {};

  // LinkedIn
  if (url.includes('linkedin.com')) {
    const companyEl = document.querySelector('.job-details-jobs-unified-top-card__company-name a') ||
                      document.querySelector('.jobs-unified-top-card__company-name a') ||
                      document.querySelector('[data-tracking-control-name="public_jobs_topcard-org-name"]') ||
                      document.querySelector('.topcard__org-name-link') ||
                      document.querySelector('.job-details-jobs-unified-top-card__company-name');
    if (companyEl) data.company = companyEl.textContent.trim();

    const titleEl = document.querySelector('.job-details-jobs-unified-top-card__job-title h1') ||
                    document.querySelector('.jobs-unified-top-card__job-title') ||
                    document.querySelector('.topcard__title') ||
                    document.querySelector('h1.t-24');
    if (titleE
checkDuplicate function · javascript · L197-L216 (20 LOC)
extension/popup.js
async function checkDuplicate(url) {
  try {
    const response = await fetch(
      `${API_BASE_URL}/api/extension/check-duplicate?url=${encodeURIComponent(url)}`
    );

    if (response.ok) {
      const data = await response.json();
      if (data.isDuplicate) {
        const app = data.existingApplication;
        showStatus(
          `Already saved: ${app.company} - ${app.role} (${app.status})`,
          'warning'
        );
      }
    }
  } catch (e) {
    console.log('Could not check for duplicates');
  }
}
getUserBillingInfo function · typescript · L42-L84 (43 LOC)
lib/billing.ts
export async function getUserBillingInfo(userId: string): Promise<UserBillingInfo | null> {
  const supabase = createServerClient();
  if (!supabase) {
    console.error('Supabase not configured');
    return null;
  }

  // Try to get existing billing record
  const { data, error } = await supabase
    .from('user_billing')
    .select('*')
    .eq('user_id', userId)
    .single();

  if (error && error.code !== 'PGRST116') {
    // PGRST116 = not found
    console.error('Error fetching billing info:', error);
    return null;
  }

  // If no record exists, create default free tier
  if (!data) {
    const { data: newData, error: insertError } = await supabase
      .from('user_billing')
      .insert({
        user_id: userId,
        subscription_tier: 'free',
        cv_generation_limit: 3,
        cv_generations_used: 0,
      })
      .select()
      .single();

    if (insertError) {
      console.error('Error creating billing record:', insertError);
      return null;
    }

  
formatBillingInfo function · typescript · L89-L106 (18 LOC)
lib/billing.ts
function formatBillingInfo(data: Record<string, unknown>): UserBillingInfo {
  const tier = (data.subscription_tier as SubscriptionTier) || 'free';
  const cvUsed = (data.cv_generations_used as number) || 0;
  const cvLimit = (data.cv_generation_limit as number) || 3;
  const isUnlimited = tier === 'power';

  return {
    userId: data.user_id as string,
    tier,
    cvUsed,
    cvLimit: isUnlimited ? -1 : cvLimit,
    cvRemaining: isUnlimited ? -1 : Math.max(0, cvLimit - cvUsed),
    billingCycleStart: data.billing_cycle_start as string,
    subscriptionEndsAt: (data.subscription_ends_at as string) || null,
    stripeCustomerId: (data.stripe_customer_id as string) || null,
    stripeSubscriptionId: (data.stripe_subscription_id as string) || null,
  };
}
canGenerateCV function · typescript · L111-L157 (47 LOC)
lib/billing.ts
export async function canGenerateCV(userId: string): Promise<CanGenerateCVResult> {
  const supabase = createServerClient();
  if (!supabase) {
    return {
      canGenerate: false,
      reason: 'error',
      used: 0,
      limit: 0,
      tier: 'free',
    };
  }

  // Use the database function for atomic check
  const { data, error } = await supabase.rpc('check_can_generate_cv', {
    p_user_id: userId,
  });

  if (error) {
    console.error('Error checking CV generation:', error);
    return {
      canGenerate: false,
      reason: 'error',
      used: 0,
      limit: 0,
      tier: 'free',
    };
  }

  const result = data?.[0];
  if (!result) {
    return {
      canGenerate: false,
      reason: 'error',
      used: 0,
      limit: 0,
      tier: 'free',
    };
  }

  return {
    canGenerate: result.can_generate,
    reason: result.reason as CanGenerateCVResult['reason'],
    used: result.used,
    limit: result.limit_val,
    tier: result.tier as SubscriptionTier,
  };
}
Repobility · open methodology · https://repobility.com/research/
upgradeTier function · typescript · L209-L254 (46 LOC)
lib/billing.ts
export async function upgradeTier(
  userId: string,
  tier: SubscriptionTier,
  stripeCustomerId?: string,
  stripeSubscriptionId?: string,
  subscriptionEndsAt?: Date
): Promise<boolean> {
  const supabase = createServerClient();
  if (!supabase) {
    return false;
  }

  const limit = TIER_LIMITS[tier].cvLimit;

  const { error } = await supabase
    .from('user_billing')
    .upsert({
      user_id: userId,
      subscription_tier: tier,
      cv_generation_limit: limit === -1 ? 999999 : limit, // Store large number for unlimited
      stripe_customer_id: stripeCustomerId,
      stripe_subscription_id: stripeSubscriptionId,
      subscription_ends_at: subscriptionEndsAt?.toISOString(),
      billing_cycle_start: new Date().toISOString(),
      cv_generations_used: 0, // Reset on upgrade
    })
    .eq('user_id', userId);

  if (error) {
    console.error('Error upgrading tier:', error);
    return false;
  }

  // Log the billing event
  await supabase.from('billing_events').inser
downgradeTier function · typescript · L259-L288 (30 LOC)
lib/billing.ts
export async function downgradeTier(userId: string): Promise<boolean> {
  const supabase = createServerClient();
  if (!supabase) {
    return false;
  }

  const { error } = await supabase
    .from('user_billing')
    .update({
      subscription_tier: 'free',
      cv_generation_limit: TIER_LIMITS.free.cvLimit,
      stripe_subscription_id: null,
      subscription_ends_at: null,
    })
    .eq('user_id', userId);

  if (error) {
    console.error('Error downgrading tier:', error);
    return false;
  }

  // Log the billing event
  await supabase.from('billing_events').insert({
    user_id: userId,
    event_type: 'subscription_cancelled',
    metadata: {},
  });

  return true;
}
resetMonthlyUsage function · typescript · L293-L307 (15 LOC)
lib/billing.ts
export async function resetMonthlyUsage(): Promise<number> {
  const supabase = createServerClient();
  if (!supabase) {
    return 0;
  }

  const { data, error } = await supabase.rpc('reset_monthly_usage');

  if (error) {
    console.error('Error resetting monthly usage:', error);
    return 0;
  }

  return data || 0;
}
recordBillingEvent function · typescript · L312-L340 (29 LOC)
lib/billing.ts
export async function recordBillingEvent(
  userId: string,
  eventType: string,
  stripeEventId: string,
  amountCents?: number,
  currency?: string,
  metadata?: Record<string, unknown>
): Promise<boolean> {
  const supabase = createServerClient();
  if (!supabase) {
    return false;
  }

  const { error } = await supabase.from('billing_events').insert({
    user_id: userId,
    event_type: eventType,
    stripe_event_id: stripeEventId,
    amount_cents: amountCents,
    currency: currency || 'usd',
    metadata: metadata || {},
  });

  if (error) {
    console.error('Error recording billing event:', error);
    return false;
  }

  return true;
}
getBillingHistory function · typescript · L345-L354 (10 LOC)
lib/billing.ts
export async function getBillingHistory(
  userId: string,
  limit: number = 10
): Promise<Array<{
  id: string;
  eventType: string;
  amountCents: number | null;
  currency: string;
  createdAt: string;
}>> {
findUserByStripeCustomerId function · typescript · L384-L404 (21 LOC)
lib/billing.ts
export async function findUserByStripeCustomerId(
  stripeCustomerId: string
): Promise<string | null> {
  const supabase = createServerClient();
  if (!supabase) {
    return null;
  }

  const { data, error } = await supabase
    .from('user_billing')
    .select('user_id')
    .eq('stripe_customer_id', stripeCustomerId)
    .single();

  if (error) {
    console.error('Error finding user by Stripe customer ID:', error);
    return null;
  }

  return data?.user_id || null;
}
getCreditBalance function · typescript · L48-L99 (52 LOC)
lib/credits.ts
export async function getCreditBalance(userId: string): Promise<CreditBalance> {
  const supabase = createServerClient();
  if (!supabase) {
    console.error('Supabase not configured');
    return { credits: 0, totalPurchased: 0, lastFreeReset: '' };
  }

  // Try to get existing credit record
  const { data, error } = await supabase
    .from('user_credits')
    .select('*')
    .eq('user_id', userId)
    .single();

  if (error && error.code !== 'PGRST116') {
    // PGRST116 = not found
    console.error('Error fetching credit balance:', error);
    return { credits: 0, totalPurchased: 0, lastFreeReset: '' };
  }

  // If no record exists, create one with free credits
  if (!data) {
    const now = new Date().toISOString();
    const { data: newData, error: insertError } = await supabase
      .from('user_credits')
      .insert({
        user_id: userId,
        credits: FREE_CREDITS_AMOUNT,
        total_purchased: 0,
        last_free_reset: now,
      })
      .select()
      .s
addCredits function · typescript · L155-L217 (63 LOC)
lib/credits.ts
export async function addCredits(
  userId: string,
  bundle: CreditBundle,
  stripePaymentId: string,
  stripeSessionId?: string
): Promise<boolean> {
  const supabase = createServerClient();
  if (!supabase) {
    return false;
  }

  const bundleConfig = CREDIT_BUNDLES[bundle];
  if (!bundleConfig) {
    console.error('Invalid bundle type:', bundle);
    return false;
  }

  // Check for duplicate payment (idempotency)
  const { data: existing } = await supabase
    .from('credit_purchases')
    .select('id')
    .eq('stripe_payment_id', stripePaymentId)
    .single();

  if (existing) {
    console.warn('Duplicate payment detected:', stripePaymentId);
    return true; // Already processed, return success
  }

  // Get current balance
  const balance = await getCreditBalance(userId);

  // Add credits atomically
  const { error: updateError } = await supabase
    .from('user_credits')
    .update({
      credits: balance.credits + bundleConfig.credits,
      total_purchased: balance
Want this analysis on your repo? https://repobility.com/scan/
getPurchaseHistory function · typescript · L222-L250 (29 LOC)
lib/credits.ts
export async function getPurchaseHistory(
  userId: string,
  limit: number = 10
): Promise<CreditPurchase[]> {
  const supabase = createServerClient();
  if (!supabase) {
    return [];
  }

  const { data, error } = await supabase
    .from('credit_purchases')
    .select('id, bundle_type, credits_purchased, amount_cents, created_at')
    .eq('user_id', userId)
    .order('created_at', { ascending: false })
    .limit(limit);

  if (error) {
    console.error('Error fetching purchase history:', error);
    return [];
  }

  return (data || []).map((row) => ({
    id: row.id,
    bundleType: row.bundle_type as CreditBundle,
    creditsPurchased: row.credits_purchased,
    amountCents: row.amount_cents,
    createdAt: row.created_at,
  }));
}
checkAndResetFreeCredits function · typescript · L256-L299 (44 LOC)
lib/credits.ts
export async function checkAndResetFreeCredits(userId: string): Promise<void> {
  const supabase = createServerClient();
  if (!supabase) {
    return;
  }

  const balance = await getCreditBalance(userId);
  if (!balance.lastFreeReset) {
    return;
  }

  const lastReset = new Date(balance.lastFreeReset);
  const now = new Date();

  // Check if a month has passed since last reset
  const monthsSinceReset =
    (now.getFullYear() - lastReset.getFullYear()) * 12 +
    (now.getMonth() - lastReset.getMonth());

  if (monthsSinceReset < 1) {
    return; // Not yet eligible for reset
  }

  // Reset free credits (add them, don't replace existing)
  const { error } = await supabase
    .from('user_credits')
    .update({
      credits: balance.credits + FREE_CREDITS_AMOUNT,
      last_free_reset: now.toISOString(),
    })
    .eq('user_id', userId);

  if (error) {
    console.error('Error resetting free credits:', error);
    return;
  }

  // Log the reset
  await supabase.from('credit_u
getAnthropic function · typescript · L5-L12 (8 LOC)
lib/cv-analyzer.ts
function getAnthropic(): Anthropic {
  if (!anthropicClient) {
    anthropicClient = new Anthropic({
      apiKey: process.env.ANTHROPIC_API_KEY,
    });
  }
  return anthropicClient;
}
analyzeCV function · typescript · L126-L212 (87 LOC)
lib/cv-analyzer.ts
export async function analyzeCV(cvText: string): Promise<CVAnalysisResult> {
  // Handle empty or minimal CV input
  const trimmedCV = cvText.trim();

  if (!trimmedCV || trimmedCV.length < 50) {
    return {
      questions: NO_CV_QUESTIONS,
      cvQuality: 'none',
    };
  }

  try {
    const prompt = CV_ANALYSIS_PROMPT.replace('{cvText}', trimmedCV.slice(0, 10000));

    const response = await getAnthropic().messages.create({
      model: 'claude-3-5-haiku-20241022',
      max_tokens: 1000,
      messages: [
        {
          role: 'user',
          content: prompt,
        },
      ],
    });

    const content = response.content[0];
    if (content.type !== 'text') {
      throw new Error('Unexpected response type from Claude');
    }

    // Parse the JSON response
    let result: CVAnalysisResult;
    try {
      // Clean up potential markdown code blocks
      let jsonText = content.text.trim();
      const jsonMatch = jsonText.match(/```(?:json)?\s*([\s\S]*?)```/);
      i
triggerCVGeneration function · typescript · L13-L18 (6 LOC)
lib/cv-auto-generate.ts
export function triggerCVGeneration(applicationId: string): void {
  // Fire and forget - don't await
  generateCVForApplication(applicationId).catch((error) => {
    console.error(`[CV Auto-Gen] Failed for ${applicationId}:`, error);
  });
}
generateCVForApplication function · typescript · L20-L137 (118 LOC)
lib/cv-auto-generate.ts
async function generateCVForApplication(applicationId: string): Promise<void> {
  console.log(`[CV Auto-Gen] Starting for application ${applicationId}`);

  const supabase = createServerClient();
  if (!supabase) {
    console.error('[CV Auto-Gen] Database not configured');
    return;
  }

  // Check if CV already exists
  const { data: app, error: fetchError } = await supabase
    .from('applications')
    .select('id, company, role, job_url, tailored_cv_url')
    .eq('id', applicationId)
    .single();

  if (fetchError || !app) {
    console.error('[CV Auto-Gen] Application not found:', fetchError);
    return;
  }

  // Skip if already has CV or no job URL
  if (app.tailored_cv_url) {
    console.log('[CV Auto-Gen] CV already exists, skipping');
    return;
  }

  if (!app.job_url) {
    console.log('[CV Auto-Gen] No job URL, skipping');
    return;
  }

  // Get master CV
  const { data: cvData, error: cvError } = await supabase
    .from('cvs')
    .select('raw_text')
    .order
getAnthropic function · typescript · L5-L12 (8 LOC)
lib/cv-enhancer.ts
function getAnthropic(): Anthropic {
  if (!anthropicClient) {
    anthropicClient = new Anthropic({
      apiKey: process.env.ANTHROPIC_API_KEY,
    });
  }
  return anthropicClient;
}
enhanceCV function · typescript · L90-L158 (69 LOC)
lib/cv-enhancer.ts
export async function enhanceCV(input: CVEnhanceInput): Promise<CVEnhanceResult> {
  const { cvText, answers } = input;

  // Format answers for the prompt
  const formattedAnswers = Object.entries(answers)
    .filter(([, value]) => value && value.trim().length > 0)
    .map(([key, value]) => `${formatQuestionKey(key)}: ${value}`)
    .join('\n');

  if (!formattedAnswers) {
    // No additional info provided, just clean up the CV
    if (!cvText || cvText.trim().length < 50) {
      throw new Error('No CV text or answers provided');
    }
    return {
      masterCV: cvText.trim(),
      tokensUsed: 0,
    };
  }

  // Determine which prompt to use
  const isFromScratch = !cvText || cvText.trim().length < 100;
  let prompt: string;

  if (isFromScratch) {
    prompt = FROM_SCRATCH_PROMPT.replace('{answers}', formattedAnswers);
  } else {
    prompt = CV_ENHANCEMENT_PROMPT
      .replace('{cvText}', cvText.trim().slice(0, 8000))
      .replace('{answers}', formattedAnswers);
  }

  tr
Repobility · code-quality intelligence · https://repobility.com
formatQuestionKey function · typescript · L163-L178 (16 LOC)
lib/cv-enhancer.ts
function formatQuestionKey(key: string): string {
  const keyMap: Record<string, string> = {
    job_title: 'Current/Recent Job Title',
    company_duration: 'Company and Duration',
    responsibilities: 'Main Responsibilities',
    achievement: 'Key Achievement',
    achievements: 'Key Achievements',
    skills: 'Top Skills',
    education: 'Education',
    target_role: 'Target Role',
    recent: 'Recent Activity',
    project: 'Notable Project',
  };

  return keyMap[key] || key.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
}
getAnthropic function · typescript · L5-L12 (8 LOC)
lib/cv-tailor.ts
function getAnthropic(): Anthropic {
  if (!anthropicClient) {
    anthropicClient = new Anthropic({
      apiKey: process.env.ANTHROPIC_API_KEY,
    });
  }
  return anthropicClient;
}
generateTailoredCV function · typescript · L285-L360 (76 LOC)
lib/cv-tailor.ts
export async function generateTailoredCV(input: TailoredCVInput): Promise<TailoredCVResult> {
  const { masterCV, jobDescription, company, role, feedback } = input;

  if (!masterCV || masterCV.trim().length < 50) {
    throw new Error('Master CV is too short or empty');
  }

  if (!jobDescription || jobDescription.trim().length < 50) {
    throw new Error('Job description is too short or empty');
  }

  // Build the prompt with all context
  const feedbackSection = feedback
    ? `USER FEEDBACK FOR REVISION:\n${feedback}\n\nPlease incorporate this feedback while generating the CV.`
    : '';

  const prompt = CV_GENERATION_PROMPT
    .replace('{masterCV}', masterCV.trim())
    .replace('{jobDescription}', jobDescription.trim().slice(0, 8000))
    .replace('{company}', company || 'Unknown Company')
    .replace('{role}', role || 'Unknown Role')
    .replace('{feedback}', feedbackSection);

  try {
    const response = await getAnthropic().messages.create({
      model: 'claude-sonnet-4
‹ prevpage 4 / 5next ›