← back to jerof__job-tracker

Function bodies 234 total

All specs Real LLM only Function bodies
POST function · typescript · L5-L108 (104 LOC)
app/api/stripe/portal/route.ts
export async function POST(request: NextRequest) {
  // Check if Stripe is configured
  if (!isStripeConfigured()) {
    return NextResponse.json(
      { error: 'Stripe is not configured' },
      { status: 503 }
    );
  }

  try {
    // Get Supabase client
    const supabase = createServerClient();
    if (!supabase) {
      return NextResponse.json(
        { error: 'Database not configured' },
        { status: 503 }
      );
    }

    // Get authenticated user
    const authHeader = request.headers.get('authorization');
    let userId: string | null = null;

    if (authHeader?.startsWith('Bearer ')) {
      const token = authHeader.substring(7);
      const { data: { user } } = await supabase.auth.getUser(token);
      userId = user?.id || null;
    }

    // For development/testing, allow demo user
    if (!userId && process.env.NODE_ENV === 'development') {
      userId = 'demo-user-id';
    }

    if (!userId) {
      return NextResponse.json(
        { error: 'Unauthorized
POST function · typescript · L10-L50 (41 LOC)
app/api/stripe/webhooks/route.ts
export async function POST(request: NextRequest) {
  // Get raw body for signature verification
  const body = await request.text();
  const signature = request.headers.get('stripe-signature');

  if (!signature) {
    console.error('No stripe-signature header');
    return NextResponse.json({ error: 'No signature' }, { status: 400 });
  }

  // Verify webhook signature
  const event = constructWebhookEvent(body, signature);
  if (!event) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
  }

  // Get Supabase client
  const supabase = createServerClient();
  if (!supabase) {
    console.error('Supabase not configured');
    return NextResponse.json({ error: 'Database not configured' }, { status: 503 });
  }

  try {
    // Only handle checkout.session.completed
    if (event.type === 'checkout.session.completed') {
      await handleCheckoutCompleted(event.data.object as Stripe.Checkout.Session, supabase);
    } else {
      console.log(`Ignoring event t
handleCheckoutCompleted function · typescript · L55-L96 (42 LOC)
app/api/stripe/webhooks/route.ts
async function handleCheckoutCompleted(
  session: Stripe.Checkout.Session,
  supabase: NonNullable<ReturnType<typeof createServerClient>>
) {
  // Get user ID from metadata
  const userId = session.metadata?.userId;
  if (!userId) {
    console.error('No userId in checkout session metadata');
    return;
  }

  // Get line items to find price ID
  const lineItems = await stripe?.checkout.sessions.listLineItems(session.id);
  const priceId = lineItems?.data[0]?.price?.id;

  if (!priceId) {
    console.error('No price ID found in checkout session');
    return;
  }

  // Look up bundle from price ID
  const bundleType = getBundleFromPriceId(priceId);
  if (!bundleType) {
    console.error('Unknown price ID:', priceId);
    return;
  }

  // Get payment intent ID for idempotency
  const paymentId =
    typeof session.payment_intent === 'string'
      ? session.payment_intent
      : session.payment_intent?.id || session.id;

  // Add credits to user
  const success = await addCredits(us
logBillingEvent function · typescript · L101-L124 (24 LOC)
app/api/stripe/webhooks/route.ts
async function logBillingEvent(
  event: Stripe.Event,
  supabase: NonNullable<ReturnType<typeof createServerClient>>
) {
  const eventData = event.data.object as {
    metadata?: Record<string, string>;
    amount_total?: number;
  };

  const userId = eventData.metadata?.userId || null;
  const amountCents = eventData.amount_total || null;

  await supabase.from('billing_events').insert({
    user_id: userId,
    event_type: event.type,
    stripe_event_id: event.id,
    amount_cents: amountCents,
    currency: 'usd',
    metadata: {
      stripeEventType: event.type,
      livemode: event.livemode,
    },
  });
}
POST function · typescript · L5-L113 (109 LOC)
app/api/sync/fix-duplicates/route.ts
export async function POST(request: NextRequest) {
  try {
    const supabase = createServerClient();
    if (!supabase) {
      return NextResponse.json({ error: 'Database not configured' }, { status: 503 });
    }

    const { company } = await request.json();

    // Get applications
    const { data: apps } = await supabase
      .from('applications')
      .select('id, company, role, status, created_at')
      .ilike('company', `%${company}%`)
      .order('created_at', { ascending: true });

    if (!apps || apps.length < 2) {
      return NextResponse.json({ error: 'Need at least 2 applications to fix duplicates' }, { status: 400 });
    }

    // Get all emails for these apps
    const { data: allEmails } = await supabase
      .from('application_emails')
      .select('*')
      .in('application_id', apps.map(a => a.id));

    if (!allEmails) {
      return NextResponse.json({ error: 'No emails found' }, { status: 404 });
    }

    // Group emails by gmail_message_id to find 
POST function · typescript · L17-L98 (82 LOC)
app/api/sync/fix-emails/route.ts
export async function POST(request: NextRequest) {
  try {
    const supabase = createServerClient();
    if (!supabase) {
      return NextResponse.json({ error: 'Database not configured' }, { status: 503 });
    }

    const { company } = await request.json();

    if (!company) {
      return NextResponse.json({ error: 'Company name required' }, { status: 400 });
    }

    // Get all applications for this company
    const { data: apps } = await supabase
      .from('applications')
      .select('id, company, role, status')
      .ilike('company', `%${company}%`);

    if (!apps || apps.length === 0) {
      return NextResponse.json({ error: 'No applications found', company }, { status: 404 });
    }

    console.log(`Found ${apps.length} applications for "${company}":`, apps);

    // Get all emails linked to these applications
    const { data: emails } = await supabase
      .from('application_emails')
      .select('*')
      .in('application_id', apps.map(a => a.id));

    if 
POST function · typescript · L6-L76 (71 LOC)
app/api/sync/reset/route.ts
export async function POST(request: NextRequest) {
  try {
    const supabase = createServerClient();
    if (!supabase) {
      return NextResponse.json({ error: 'Database not configured' }, { status: 503 });
    }

    const { company } = await request.json();

    if (!company) {
      return NextResponse.json({ error: 'Company name required' }, { status: 400 });
    }

    // Find emails linked to this company's applications
    const { data: apps } = await supabase
      .from('applications')
      .select('id, company, role, status, source_email_id')
      .ilike('company', `%${company}%`);

    if (!apps || apps.length === 0) {
      return NextResponse.json({ error: 'No applications found for company', company }, { status: 404 });
    }

    console.log(`Found ${apps.length} applications for "${company}":`, apps);

    // Get email IDs to reset
    const emailIds = apps
      .filter(app => app.source_email_id)
      .map(app => app.source_email_id);

    // Also get emails fro
Methodology: Repobility · https://repobility.com/research/state-of-ai-code-2026/
normalizeCompanyName function · typescript · L13-L19 (7 LOC)
app/api/sync/route.ts
function normalizeCompanyName(name: string): string {
  return name
    .toLowerCase()
    .replace(/\s+(inc|llc|ltd|co|corp|corporation|ai|io|hq)\.?$/i, '')
    .replace(/[^a-z0-9]/g, '')
    .trim();
}
companiesMatch function · typescript · L22-L33 (12 LOC)
app/api/sync/route.ts
function companiesMatch(name1: string, name2: string): boolean {
  const norm1 = normalizeCompanyName(name1);
  const norm2 = normalizeCompanyName(name2);

  // Exact match after normalization
  if (norm1 === norm2) return true;

  // One contains the other (handles "Cleo" vs "Cleo AI")
  if (norm1.includes(norm2) || norm2.includes(norm1)) return true;

  return false;
}
normalizeRole function · typescript · L36-L42 (7 LOC)
app/api/sync/route.ts
function normalizeRole(role: string): string {
  return role
    .toLowerCase()
    .replace(/[^a-z0-9\s]/g, ' ')
    .replace(/\s+/g, ' ')
    .trim();
}
rolesMatch function · typescript · L45-L64 (20 LOC)
app/api/sync/route.ts
function rolesMatch(role1: string | null, role2: string | null): boolean {
  if (!role1 || !role2) return false;

  const norm1 = normalizeRole(role1);
  const norm2 = normalizeRole(role2);

  // Exact match after normalization
  if (norm1 === norm2) return true;

  // One contains the other (handles slight variations)
  if (norm1.includes(norm2) || norm2.includes(norm1)) return true;

  // Check if key words match (at least 2 significant words)
  const words1 = norm1.split(' ').filter(w => w.length > 2);
  const words2 = norm2.split(' ').filter(w => w.length > 2);
  const commonWords = words1.filter(w => words2.includes(w));
  if (commonWords.length >= 2) return true;

  return false;
}
GET function · typescript · L342-L364 (23 LOC)
app/api/sync/route.ts
export async function GET() {
  try {
    const supabase = createServerClient();
    if (!supabase) {
      return NextResponse.json({ connected: false, error: 'Database not configured' });
    }

    const { data, error } = await supabase
      .from('gmail_tokens')
      .select('id, updated_at')
      .single();

    console.log('Gmail status check:', { hasData: !!data, error: error?.message });

    return NextResponse.json({
      connected: !!data,
      lastSync: data?.updated_at || null,
    });
  } catch (err) {
    console.error('Gmail status error:', err);
    return NextResponse.json({ connected: false });
  }
}
DashboardLayout function · typescript · L3-L9 (7 LOC)
app/(app)/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return <AppLayout>{children}</AppLayout>;
}
AppLayout function · typescript · L10-L28 (19 LOC)
app/components/AppLayout.tsx
export function AppLayout({ children }: AppLayoutProps) {
  const [, setIsCollapsed] = useState(false);

  const handleCollapseChange = useCallback((collapsed: boolean) => {
    setIsCollapsed(collapsed);
  }, []);

  return (
    <div className="flex h-screen overflow-hidden bg-white dark:bg-slate-950">
      {/* Side Navigation */}
      <SideNav onCollapseChange={handleCollapseChange} />

      {/* Main Content */}
      <main className="flex-1 overflow-auto bg-slate-50 dark:bg-slate-950">
        {children}
      </main>
    </div>
  );
}
ApplicationCard function · typescript · L27-L181 (155 LOC)
app/components/ApplicationCard.tsx
export function ApplicationCard({ application, index, onClick }: ApplicationCardProps) {
  const formatDate = (dateString: string) => {
    const date = new Date(dateString);
    const now = new Date();
    const diffDays = Math.floor((now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24));

    if (diffDays === 0) return 'Today';
    if (diffDays === 1) return 'Yesterday';
    if (diffDays < 7) return `${diffDays}d ago`;

    return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
  };

  const statusColor = STATUS_COLORS[application.status];
  const closeReasonStyle = application.closeReason ? CLOSE_REASON_STYLES[application.closeReason] : null;

  return (
    <Draggable draggableId={application.id} index={index}>
      {(provided, snapshot) => (
        <div
          ref={provided.innerRef}
          {...provided.draggableProps}
          {...provided.dragHandleProps}
          onClick={() => onClick(application)}
          data-testid="application-card"
   
All rows above produced by Repobility · https://repobility.com
BuyCreditsModal function · typescript · L22-L167 (146 LOC)
app/components/billing/BuyCreditsModal.tsx
export function BuyCreditsModal({ isOpen, onClose, currentCredits }: BuyCreditsModalProps) {
  const [loadingBundle, setLoadingBundle] = useState<CreditBundle | null>(null);
  const [error, setError] = useState<string | null>(null);

  const handlePurchase = async (bundle: CreditBundle) => {
    const priceId = STRIPE_PRICES[bundle];

    if (!priceId) {
      setError('Payment not configured. Please try again later.');
      return;
    }

    setLoadingBundle(bundle);
    setError(null);

    try {
      const response = await fetch('/api/stripe/create-checkout', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ priceId }),
      });

      const data = await response.json();

      if (!response.ok) {
        throw new Error(data.error || 'Failed to start checkout');
      }

      if (data.url) {
        // Redirect to Stripe checkout
        window.location.href = data.url;
      } else {
        th
CreditSkeleton function · typescript · L38-L61 (24 LOC)
app/components/billing/CreditBalance.tsx
function CreditSkeleton({ variant }: { variant: 'sidebar' | 'header' | 'compact' }) {
  if (variant === 'compact') {
    return <div className="h-4 w-6 bg-slate-200 dark:bg-slate-700 rounded animate-pulse" />;
  }

  if (variant === 'header') {
    return (
      <div className="flex items-center gap-1.5">
        <div className="w-4 h-4 bg-slate-200 dark:bg-slate-700 rounded animate-pulse" />
        <div className="h-4 w-6 bg-slate-200 dark:bg-slate-700 rounded animate-pulse" />
      </div>
    );
  }

  // sidebar variant
  return (
    <div className="flex items-center justify-between px-2.5 py-2 w-full">
      <div className="flex items-center gap-2">
        <div className="w-5 h-5 bg-slate-200 dark:bg-slate-700 rounded animate-pulse" />
        <div className="h-4 w-24 bg-slate-200 dark:bg-slate-700 rounded animate-pulse" />
      </div>
    </div>
  );
}
CreditBalance function · typescript · L63-L219 (157 LOC)
app/components/billing/CreditBalance.tsx
export function CreditBalance({
  variant = 'sidebar',
  showBuyButton = true,
  onBuyClick
}: CreditBalanceProps) {
  const [balance, setBalance] = useState<CreditBalanceData | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  const fetchCredits = useCallback(async () => {
    try {
      const response = await fetch('/api/credits');

      if (!response.ok) {
        if (response.status === 401) {
          setError('Not logged in');
          return;
        }
        throw new Error('Failed to fetch credits');
      }

      const data = await response.json();
      setBalance(data);
      setError(null);
    } catch (err) {
      console.error('Error fetching credits:', err);
      setError('Could not load credits');
    } finally {
      setIsLoading(false);
    }
  }, []);

  // Fetch on mount
  useEffect(() => {
    fetchCredits();
  }, [fetchCredits]);

  // Re-fetch when window regains focus
  useEff
WarningIcon function · typescript · L11-L27 (17 LOC)
app/components/billing/LowCreditsWarning.tsx
function WarningIcon({ className = '' }: { className?: string }) {
  return (
    <svg
      className={className}
      fill="none"
      viewBox="0 0 24 24"
      stroke="currentColor"
      strokeWidth={2}
    >
      <path
        strokeLinecap="round"
        strokeLinejoin="round"
        d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
      />
    </svg>
  );
}
CloseIcon function · typescript · L29-L45 (17 LOC)
app/components/billing/LowCreditsWarning.tsx
function CloseIcon({ className = '' }: { className?: string }) {
  return (
    <svg
      className={className}
      fill="none"
      viewBox="0 0 24 24"
      stroke="currentColor"
      strokeWidth={2}
    >
      <path
        strokeLinecap="round"
        strokeLinejoin="round"
        d="M6 18L18 6M6 6l12 12"
      />
    </svg>
  );
}
getMessage function · typescript · L47-L55 (9 LOC)
app/components/billing/LowCreditsWarning.tsx
function getMessage(credits: number): string {
  if (credits === 0) {
    return "You're out of credits! Buy more to generate CVs.";
  }
  if (credits === 1) {
    return 'Only 1 credit left!';
  }
  return 'Running low on credits';
}
LowCreditsWarning function · typescript · L57-L241 (185 LOC)
app/components/billing/LowCreditsWarning.tsx
export function LowCreditsWarning({
  credits,
  onBuyClick,
  variant = 'banner',
}: LowCreditsWarningProps) {
  const [isDismissed, setIsDismissed] = useState(false);

  // Only show when credits <= 3
  if (credits > 3 || isDismissed) {
    return null;
  }

  const message = getMessage(credits);
  const isUrgent = credits === 0;

  // Banner variant - full-width yellow/amber bar
  if (variant === 'banner') {
    return (
      <div
        className={`
          w-full px-4 py-3
          ${isUrgent
            ? 'bg-red-50 dark:bg-red-900/30 border-b border-red-200 dark:border-red-800'
            : 'bg-amber-50 dark:bg-amber-900/30 border-b border-amber-200 dark:border-amber-800'
          }
        `}
        role="alert"
        data-testid="low-credits-warning"
      >
        <div className="flex items-center justify-between max-w-7xl mx-auto">
          <div className="flex items-center gap-3">
            <WarningIcon
              className={`w-5 h-5 shrink-0 ${
           
ChatMessage function · typescript · L13-L75 (63 LOC)
app/components/ChatMessage.tsx
export function ChatMessage({ message, onFollowupSelect, isLatest = false }: ChatMessageProps) {
  const isUser = message.role === 'user';

  return (
    <div className={`flex ${isUser ? 'justify-end' : 'justify-start'} mb-4`}>
      <div
        className={`
          max-w-[85%]
          ${isUser
            ? 'bg-blue-600 text-white rounded-2xl rounded-br-md px-4 py-2.5'
            : 'bg-slate-100 dark:bg-slate-800 text-slate-900 dark:text-slate-100 rounded-2xl rounded-bl-md px-4 py-3'
          }
        `}
      >
        {/* Message content */}
        {isUser ? (
          <div className="text-sm leading-relaxed whitespace-pre-wrap">
            {message.content}
          </div>
        ) : (
          <div className="text-[13px] leading-relaxed [&>h1]:text-base [&>h1]:font-bold [&>h1]:mt-3 [&>h1]:mb-1.5 [&>h2]:text-sm [&>h2]:font-semibold [&>h2]:mt-3 [&>h2]:mb-1 [&>h3]:text-[13px] [&>h3]:font-semibold [&>h3]:mt-2 [&>h3]:mb-1 [&>p]:my-1.5 [&>ul]:my-1.5 [&>ul]:pl-4 [&>ul>li]:
Repobility (the analyzer behind this table) · https://repobility.com
formatRelativeDate function · typescript · L11-L23 (13 LOC)
app/components/CVCard.tsx
function formatRelativeDate(dateString: string): string {
  const date = new Date(dateString);
  const now = new Date();
  const diffTime = now.getTime() - date.getTime();
  const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));

  if (diffDays === 0) return 'Today';
  if (diffDays === 1) return 'Yesterday';
  if (diffDays < 7) return `${diffDays} days ago`;
  if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`;

  return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
CVCard function · typescript · L25-L101 (77 LOC)
app/components/CVCard.tsx
export default function CVCard({ cv, onPreview, onDelete }: CVCardProps) {
  const isTailored = cv.type === 'tailored';

  return (
    <div
      onClick={() => onPreview(cv)}
      className="group relative bg-white border border-gray-200 rounded-xl p-4 hover:border-gray-300 hover:shadow-md transition-all duration-200 cursor-pointer"
    >
      {/* Delete button */}
      {isTailored && onDelete && (
        <button
          onClick={(e) => {
            e.stopPropagation();
            onDelete(cv);
          }}
          className="absolute top-3 right-3 p-1.5 text-gray-400 hover:text-red-500 hover:bg-red-50 rounded-lg opacity-0 group-hover:opacity-100 transition-all duration-200"
          title="Delete CV"
        >
          <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
          </svg>
        </button>
      )}

      {/* PDF Badge */}
CVPreviewModal function · typescript · L11-L116 (106 LOC)
app/components/CVPreviewModal.tsx
export default function CVPreviewModal({ cv, onClose }: CVPreviewModalProps) {
  // Handle escape key
  const handleKeyDown = useCallback((e: KeyboardEvent) => {
    if (e.key === 'Escape') {
      onClose();
    }
  }, [onClose]);

  useEffect(() => {
    document.addEventListener('keydown', handleKeyDown);
    document.body.style.overflow = 'hidden';
    return () => {
      document.removeEventListener('keydown', handleKeyDown);
      document.body.style.overflow = 'unset';
    };
  }, [handleKeyDown]);

  // Handle backdrop click
  const handleBackdropClick = (e: React.MouseEvent) => {
    if (e.target === e.currentTarget) {
      onClose();
    }
  };

  const handleDownload = () => {
    if (cv.url) {
      window.open(cv.url, '_blank');
    }
  };

  return (
    <div
      className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4"
      onClick={handleBackdropClick}
    >
      <div className="bg-white rounded-2xl shadow-2xl w-full max-w-4x
EmptyState function · typescript · L11-L193 (183 LOC)
app/components/EmptyState.tsx
export function EmptyState({ onConnectGmail, onAddApplication, gmailConnected }: EmptyStateProps) {
  const [isVisible, setIsVisible] = useState(false);
  const [hoveredButton, setHoveredButton] = useState<'gmail' | 'add' | null>(null);

  useEffect(() => {
    const timer = setTimeout(() => setIsVisible(true), 100);
    return () => clearTimeout(timer);
  }, []);

  return (
    <div
      className={`flex flex-col items-center justify-center py-16 px-6 max-w-lg mx-auto transition-all duration-500 ${
        isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4'
      }`}
    >
      {/* Animated Illustration */}
      <div className="relative mb-8">
        {/* Background circles with subtle animation */}
        <div className="absolute inset-0 flex items-center justify-center">
          <div className="w-40 h-40 rounded-full bg-gradient-to-br from-blue-100 to-purple-100 dark:from-blue-900/20 dark:to-purple-900/20 animate-pulse" style={{ animationDuration: '3s' }} />
 
KanbanColumn function · typescript · L74-L169 (96 LOC)
app/components/KanbanColumn.tsx
export function KanbanColumn({
  id,
  title,
  applications,
  onCardClick,
  isMobile = false,
}: KanbanColumnProps) {
  const config = COLUMN_CONFIG[id];

  return (
    <div
      className={`
        flex flex-col
        ${isMobile ? 'w-full h-full' : 'w-72 shrink-0'}
        bg-slate-50/50 dark:bg-slate-900/50
        rounded-xl
        transition-colors duration-200
      `}
    >
      {/* Column Header */}
      {!isMobile && (
        <div className="px-3 py-3 flex items-center justify-between">
          <div className="flex items-center gap-2">
            <span className={`w-2 h-2 rounded-full ${config.dotColor}`} />
            <h2 className="font-medium text-slate-700 dark:text-slate-200 text-sm">
              {title}
            </h2>
          </div>
          <span
            className={`
              text-xs font-medium
              px-2 py-0.5 rounded-full
              ${config.countBg} ${config.countText}
              tabular-nums
            `}
          >
useRevealOnScroll function · typescript · L8-L31 (24 LOC)
app/components/landing/LandingComponents.tsx
export function useRevealOnScroll(threshold = 0.1) {
  const ref = useRef<HTMLDivElement>(null);
  const [isVisible, setIsVisible] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsVisible(true);
          observer.disconnect();
        }
      },
      { threshold }
    );

    if (ref.current) {
      observer.observe(ref.current);
    }

    return () => observer.disconnect();
  }, [threshold]);

  return { ref, isVisible };
}
LandingNav function · typescript · L41-L101 (61 LOC)
app/components/landing/LandingComponents.tsx
export function LandingNav() {
  const [scrolled, setScrolled] = useState(false);

  useEffect(() => {
    const handleScroll = () => setScrolled(window.scrollY > 20);
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);

  return (
    <nav className={`
      fixed top-0 left-0 right-0 z-50
      px-6 transition-all duration-200
      ${scrolled
        ? 'py-3 bg-white/95 backdrop-blur-lg border-b border-neutral-200/50 shadow-sm'
        : 'py-4 bg-white/80 backdrop-blur-lg border-b border-neutral-200/30'
      }
    `}>
      <div className="max-w-7xl mx-auto flex items-center justify-between">
        {/* Logo */}
        <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-nav)" strokeWidth="1.5" strokeLinecap="round"/>
            <path d="M6 17C6 11 8.
NavLink function · typescript · L103-L112 (10 LOC)
app/components/landing/LandingComponents.tsx
function NavLink({ href, children }: NavLinkProps) {
  return (
    <a
      href={href}
      className="text-sm text-neutral-600 hover:text-neutral-900 transition-colors"
    >
      {children}
    </a>
  );
}
Generated by Repobility's multi-pass static-analysis pipeline (https://repobility.com)
Hero function · typescript · L126-L242 (117 LOC)
app/components/landing/LandingComponents.tsx
export function Hero({
  badge,
  headline,
  subheadline,
  primaryCTA,
  secondaryCTA,
  variant = 'light'
}: HeroProps) {
  const isDark = variant === 'dark';

  return (
    <section className={`relative min-h-screen flex items-center overflow-hidden ${isDark ? 'bg-neutral-950' : ''}`}>
      {/* Background */}
      {isDark ? (
        <div className="absolute inset-0">
          <div className="absolute top-1/4 left-1/2 -translate-x-1/2 w-[800px] h-[600px] rounded-full bg-gradient-to-br from-primary-600/30 to-info-600/20 blur-3xl" />
        </div>
      ) : (
        <>
          <div className="absolute inset-0 bg-gradient-to-br from-primary-50 via-white to-info-50" />
          <div
            className="absolute inset-0 opacity-80"
            style={{
              background: `
                radial-gradient(at 20% 30%, rgba(139, 92, 246, 0.12) 0%, transparent 50%),
                radial-gradient(at 80% 70%, rgba(59, 130, 246, 0.08) 0%, transparent 50%)
              `
 
TrustSignal function · typescript · L244-L253 (10 LOC)
app/components/landing/LandingComponents.tsx
function TrustSignal({ children }: { children: ReactNode }) {
  return (
    <div className="flex items-center gap-2 text-sm text-neutral-500">
      <svg className="w-5 h-5 text-success-500" fill="currentColor" viewBox="0 0 20 20">
        <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
      </svg>
      {children}
    </div>
  );
}
Section function · typescript · L264-L272 (9 LOC)
app/components/landing/LandingComponents.tsx
export function Section({ id, className = '', children }: SectionProps) {
  return (
    <section id={id} className={`py-16 md:py-24 lg:py-32 ${className}`}>
      <div className="max-w-7xl mx-auto px-6">
        {children}
      </div>
    </section>
  );
}
SectionHeader function · typescript · L284-L312 (29 LOC)
app/components/landing/LandingComponents.tsx
export function SectionHeader({ overline, title, description, centered = true }: SectionHeaderProps) {
  const { ref, isVisible } = useRevealOnScroll();

  return (
    <div
      ref={ref}
      className={`
        mb-12 md:mb-16
        ${centered ? 'text-center max-w-3xl mx-auto' : 'max-w-2xl'}
        transition-all duration-500
        ${isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-5'}
      `}
    >
      {overline && (
        <span className="text-sm font-semibold text-primary-600 uppercase tracking-wider">
          {overline}
        </span>
      )}
      <h2 className="mt-2 text-3xl md:text-4xl font-bold tracking-tight text-neutral-900">
        {title}
      </h2>
      {description && (
        <p className="mt-4 text-lg text-neutral-600">
          {description}
        </p>
      )}
    </div>
  );
}
FeatureCard function · typescript · L324-L353 (30 LOC)
app/components/landing/LandingComponents.tsx
export function FeatureCard({ icon, title, description, delay = 0 }: FeatureCardProps) {
  const { ref, isVisible } = useRevealOnScroll();

  return (
    <div
      ref={ref}
      className={`
        group p-6 md:p-8
        bg-white border border-neutral-200 rounded-2xl
        hover:shadow-lg hover:border-primary-200
        transition-all duration-300
        ${isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-5'}
      `}
      style={{ transitionDelay: `${delay}ms` }}
    >
      <div className="
        w-12 h-12 mb-4
        flex items-center justify-center
        bg-primary-50 text-primary-600
        rounded-xl
        group-hover:bg-primary-100
        transition-colors duration-300
      ">
        {icon}
      </div>
      <h3 className="text-lg font-semibold text-neutral-900 mb-2">{title}</h3>
      <p className="text-neutral-600 leading-relaxed">{description}</p>
    </div>
  );
}
StatsGrid function · typescript · L363-L385 (23 LOC)
app/components/landing/LandingComponents.tsx
export function StatsGrid({ stats }: { stats: StatProps[] }) {
  const { ref, isVisible } = useRevealOnScroll();

  return (
    <div
      ref={ref}
      className={`
        grid grid-cols-2 md:grid-cols-4 gap-8
        transition-all duration-500
        ${isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-5'}
      `}
    >
      {stats.map((stat, index) => (
        <div key={index} className="text-center">
          <p className="text-4xl md:text-5xl font-bold text-primary-600 font-mono tracking-tight">
            {stat.value}
          </p>
          <p className="text-neutral-600 mt-1">{stat.label}</p>
        </div>
      ))}
    </div>
  );
}
TestimonialCard function · typescript · L399-L426 (28 LOC)
app/components/landing/LandingComponents.tsx
export function TestimonialCard({ quote, author }: TestimonialProps) {
  return (
    <div className="p-8 bg-neutral-50 border border-neutral-100 rounded-2xl">
      <p className="text-lg text-neutral-700 leading-relaxed italic mb-6">
        "{quote}"
      </p>
      <div className="flex items-center gap-4">
        {author.avatar ? (
          <img
            src={author.avatar}
            alt={author.name}
            className="w-12 h-12 rounded-full object-cover"
          />
        ) : (
          <div className="w-12 h-12 rounded-full bg-primary-100 flex items-center justify-center">
            <span className="text-primary-700 font-semibold">
              {author.name.charAt(0)}
            </span>
          </div>
        )}
        <div>
          <p className="font-semibold text-neutral-900">{author.name}</p>
          <p className="text-sm text-neutral-500">{author.role}</p>
        </div>
      </div>
    </div>
  );
}
CTASection function · typescript · L438-L456 (19 LOC)
app/components/landing/LandingComponents.tsx
export function CTASection({ title, description, ctaText, ctaHref }: CTASectionProps) {
  return (
    <section className="py-16 md:py-24 bg-gradient-to-br from-primary-600 to-primary-700 text-white">
      <div className="max-w-4xl mx-auto px-6 text-center">
        <h2 className="text-3xl md:text-4xl font-bold mb-4">{title}</h2>
        <p className="text-lg opacity-90 mb-8 max-w-2xl mx-auto">{description}</p>
        <a
          href={ctaHref}
          className="inline-flex items-center justify-center gap-2 px-8 py-4 bg-white text-primary-700 font-semibold rounded-xl hover:bg-neutral-100 transition-colors shadow-lg"
        >
          {ctaText}
          <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
            <path strokeLinecap="round" strokeLinejoin="round" d="M13 7l5 5m0 0l-5 5m5-5H6" />
          </svg>
        </a>
      </div>
    </section>
  );
}
Methodology: Repobility · https://repobility.com/research/state-of-ai-code-2026/
Badge function · typescript · L476-L482 (7 LOC)
app/components/landing/LandingComponents.tsx
export function Badge({ variant = 'primary', children }: BadgeProps) {
  return (
    <span className={`px-2.5 py-1 text-xs font-medium rounded-full ${badgeStyles[variant]}`}>
      {children}
    </span>
  );
}
Button function · typescript · L511-L543 (33 LOC)
app/components/landing/LandingComponents.tsx
export function Button({
  variant = 'primary',
  size = 'md',
  children,
  onClick,
  href,
  className = ''
}: ButtonProps) {
  const baseClasses = `
    inline-flex items-center justify-center gap-2
    font-medium rounded-lg
    transition-all duration-150
    active:scale-[0.98]
    focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2
    ${buttonVariants[variant]}
    ${buttonSizes[size]}
    ${className}
  `;

  if (href) {
    return (
      <a href={href} className={baseClasses}>
        {children}
      </a>
    );
  }

  return (
    <button onClick={onClick} className={baseClasses}>
      {children}
    </button>
  );
}
LandingFooter function · typescript · L548-L623 (76 LOC)
app/components/landing/LandingComponents.tsx
export function LandingFooter() {
  return (
    <footer className="py-16 bg-neutral-50 border-t border-neutral-200">
      <div className="max-w-7xl mx-auto px-6">
        <div className="grid grid-cols-1 md:grid-cols-4 gap-8 mb-12">
          {/* Brand */}
          <div className="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" gradient
SkeletonCard function · typescript · L3-L12 (10 LOC)
app/components/LoadingSkeleton.tsx
function SkeletonCard() {
  return (
    <div className="bg-white rounded-lg border border-gray-200 p-3 mb-2 animate-pulse">
      <div className="h-5 bg-gray-200 rounded w-3/4 mb-2" />
      <div className="h-4 bg-gray-100 rounded w-1/2 mb-1" />
      <div className="h-3 bg-gray-100 rounded w-1/3" />
      <div className="mt-2 h-3 bg-gray-100 rounded w-16" />
    </div>
  );
}
SkeletonColumn function · typescript · L14-L30 (17 LOC)
app/components/LoadingSkeleton.tsx
function SkeletonColumn() {
  return (
    <div className="flex flex-col w-72 shrink-0 bg-gray-50 rounded-lg border border-gray-200">
      <div className="p-3 border-b border-gray-200">
        <div className="flex items-center justify-between">
          <div className="h-5 bg-gray-200 rounded w-24 animate-pulse" />
          <div className="h-4 bg-gray-200 rounded w-6 animate-pulse" />
        </div>
      </div>
      <div className="flex-1 p-2 min-h-[200px]">
        <SkeletonCard />
        <SkeletonCard />
        <SkeletonCard />
      </div>
    </div>
  );
}
LoadingSkeleton function · typescript · L32-L53 (22 LOC)
app/components/LoadingSkeleton.tsx
export function LoadingSkeleton({ count = 4 }: { count?: number }) {
  return (
    <div className="h-screen flex flex-col bg-gray-100">
      <header className="bg-white border-b border-gray-200 px-6 py-4">
        <div className="flex items-center justify-between">
          <div>
            <div className="h-6 bg-gray-200 rounded w-32 mb-2 animate-pulse" />
            <div className="h-4 bg-gray-100 rounded w-48 animate-pulse" />
          </div>
          <div className="h-10 bg-blue-200 rounded-lg w-28 animate-pulse" />
        </div>
      </header>
      <div className="flex-1 overflow-x-auto p-6">
        <div className="flex gap-4 h-full">
          {Array.from({ length: count }).map((_, i) => (
            <SkeletonColumn key={i} />
          ))}
        </div>
      </div>
    </div>
  );
}
LoadingSkeletonCards function · typescript · L55-L63 (9 LOC)
app/components/LoadingSkeleton.tsx
export function LoadingSkeletonCards({ count = 3 }: { count?: number }) {
  return (
    <>
      {Array.from({ length: count }).map((_, i) => (
        <SkeletonCard key={i} />
      ))}
    </>
  );
}
formatRelativeDate function · typescript · L11-L23 (13 LOC)
app/components/MasterCVCard.tsx
function formatRelativeDate(dateString: string): string {
  const date = new Date(dateString);
  const now = new Date();
  const diffTime = now.getTime() - date.getTime();
  const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));

  if (diffDays === 0) return 'Today';
  if (diffDays === 1) return 'Yesterday';
  if (diffDays < 7) return `${diffDays} days ago`;
  if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`;

  return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
All rows above produced by Repobility · https://repobility.com
MasterCVCard function · typescript · L25-L86 (62 LOC)
app/components/MasterCVCard.tsx
export default function MasterCVCard({ cv, onEdit, onAdd }: MasterCVCardProps) {
  // Empty state - no master CV
  if (!cv) {
    return (
      <div
        onClick={onAdd}
        className="relative bg-gradient-to-br from-amber-50 to-orange-50 border-2 border-dashed border-amber-200 rounded-xl p-4 hover:border-amber-300 hover:shadow-md transition-all duration-200 cursor-pointer min-h-[160px] flex flex-col items-center justify-center"
      >
        <div className="w-12 h-12 bg-amber-100 rounded-full flex items-center justify-center mb-3">
          <svg className="w-6 h-6 text-amber-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
          </svg>
        </div>
        <p className="text-sm font-medium text-amber-700 mb-1">Add Master CV</p>
        <p className="text-xs text-amber-600/70 text-center">Your base CV for tailoring to jobs</p>
      </div>
    );
  }

  retur
NavItem function · typescript · L14-L82 (69 LOC)
app/components/NavItem.tsx
export function NavItem({ icon, label, href, isActive, isCollapsed }: NavItemProps) {
  return (
    <Link
      href={href}
      className={`
        relative group flex items-center
        ${isCollapsed ? 'justify-center' : 'justify-start'}
        px-3 py-2.5
        rounded-lg
        transition-all duration-200 ease-out
        ${isActive
          ? 'bg-blue-600/10 text-blue-500'
          : 'text-slate-400 hover:bg-slate-800 hover:text-slate-200'
        }
      `}
    >
      {/* Icon */}
      <span className={`
        flex-shrink-0 w-5 h-5
        transition-transform duration-200
        ${isActive ? 'scale-110' : 'group-hover:scale-105'}
      `}>
        {icon}
      </span>

      {/* Label - hidden when collapsed */}
      {!isCollapsed && (
        <span className={`
          ml-3 text-sm font-medium
          transition-opacity duration-200
          whitespace-nowrap
        `}>
          {label}
        </span>
      )}

      {/* Active indicator */}
      {isAc
OnboardingCard function · typescript · L11-L29 (19 LOC)
app/components/onboarding/OnboardingCard.tsx
export function OnboardingCard({ children, className = '' }: OnboardingCardProps) {
  return (
    <motion.div
      initial={{ opacity: 0, scale: 0.95 }}
      animate={{ opacity: 1, scale: 1 }}
      transition={{ duration: 0.3, ease: [0.4, 0, 0.2, 1] }}
      className={`
        bg-white
        border border-gray-200
        rounded-2xl
        p-8
        shadow-xl shadow-gray-200/50
        ${className}
      `}
    >
      {children}
    </motion.div>
  );
}
‹ prevpage 2 / 5next ›