← back to jryan5150__heaton-web-directory-prod

Function bodies 115 total

All specs Real LLM only Function bodies
parseRow function · typescript · L240-L255 (16 LOC)
src/lib/nextiva-sync.ts
function parseRow(row: NextivaCSVRow): ParsedPerson {
  const rawName = (row.Name || '').trim()
  const { firstName, lastName } = parseName(rawName)

  return {
    firstName,
    lastName,
    email: (row.Email || '').trim(),
    phoneNumber: (row['Phone Number'] || '').trim(),
    extension: (row.Extension || '').trim(),
    team: (row.Team || '').trim(),
    location: normalizeLocation(row.Location || ''),
    role: (row.Role || '').trim(),
    rawName,
  }
}
computeFieldChanges function · typescript · L261-L269 (9 LOC)
src/lib/nextiva-sync.ts
function computeFieldChanges(
  employee: {
    extension: string | null
    phoneNumber: string | null
    location: string
    team: string
  },
  nextiva: ParsedPerson
): Record<string, { old: string; new: string }> {
runNextivaSync function · typescript · L316-L506 (191 LOC)
src/lib/nextiva-sync.ts
export async function runNextivaSync(
  rows: NextivaCSVRow[],
  triggeredBy: string,
  source: 'manual' | 'automated'
): Promise<SyncResult> {
  const result: SyncResult = {
    totalRows: rows.length,
    matched: 0,
    created: 0,
    updated: 0,
    skipped: 0,
    errors: 0,
    details: {
      autoUpdated: [],
      pendingReview: [],
      skippedEntries: [],
      errorEntries: [],
    },
  }

  // Batch-load all employees and sync mappings upfront to avoid N+1
  const [allEmployees, allMappings] = await Promise.all([
    prisma.employee.findMany({
      select: {
        id: true,
        firstName: true,
        lastName: true,
        email: true,
        extension: true,
        phoneNumber: true,
        location: true,
        team: true,
      },
    }),
    prisma.syncMapping.findMany(),
  ])

  // Build lookup indexes for fast matching
  const employeeByEmail = new Map<string, (typeof allEmployees)[number]>()
  for (const emp of allEmployees) {
    if (emp.email) {
 
initializeLiveSearch function · typescript · L18-L91 (74 LOC)
src/lib/searchFilter.ts
export function initializeLiveSearch(
  searchInputId: string,
  employeeCardsSelector: string,
  onSearchCallback?: (searchTerm: string, visibleCount: number) => void
): () => void {
  const searchInput = document.getElementById(searchInputId) as HTMLInputElement;

  if (!searchInput) {
    console.error(`Search input with id "${searchInputId}" not found`);
    return () => {};
  }

  let debounceTimer: NodeJS.Timeout;

  const handleSearch = () => {
    // Clear previous debounce timer
    clearTimeout(debounceTimer);

    // Debounce the search to avoid excessive filtering
    debounceTimer = setTimeout(() => {
      const searchTerm = searchInput.value.toLowerCase().trim();
      const employeeCards = document.querySelectorAll(employeeCardsSelector) as NodeListOf<SearchableElement>;
      let visibleCount = 0;

      employeeCards.forEach((card) => {
        // Get searchable text from data attribute or card content
        const searchableText = card.dataset.searchText || card.tex
updateEmptyState function · typescript · L96-L120 (25 LOC)
src/lib/searchFilter.ts
function updateEmptyState(visibleCount: number): void {
  const gridContainer = document.querySelector('.employee-grid');

  if (!gridContainer) return;

  // Remove existing empty state
  const existingEmptyState = gridContainer.querySelector('.empty-state');
  if (existingEmptyState) {
    existingEmptyState.remove();
  }

  // Add empty state if no visible cards
  if (visibleCount === 0) {
    const emptyState = document.createElement('div');
    emptyState.className = 'empty-state';
    emptyState.innerHTML = `
      <svg class="empty-state-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
      </svg>
      <h3 class="empty-state-title">No employees found</h3>
      <p class="empty-state-description">Try adjusting your search terms</p>
    `;
    gridContainer.appendChild(emptyState);
  }
}
clearSearch function · typescript · L125-L132 (8 LOC)
src/lib/searchFilter.ts
export function clearSearch(searchInputId: string): void {
  const searchInput = document.getElementById(searchInputId) as HTMLInputElement;

  if (searchInput) {
    searchInput.value = '';
    searchInput.dispatchEvent(new Event('input', { bubbles: true }));
  }
}
prepareEmployeeCards function · typescript · L138-L161 (24 LOC)
src/lib/searchFilter.ts
export function prepareEmployeeCards(employeeCardsSelector: string): void {
  const employeeCards = document.querySelectorAll(employeeCardsSelector) as NodeListOf<SearchableElement>;

  employeeCards.forEach((card) => {
    // Extract all text content from name, title, department, etc.
    const name = card.querySelector('.employee-name')?.textContent || '';
    const title = card.querySelector('.employee-title')?.textContent || '';
    const department = card.querySelector('.employee-department')?.textContent || '';
    const email = card.querySelector('.employee-email')?.textContent || '';
    const location = card.querySelector('.employee-location')?.textContent || '';

    // Combine all searchable text
    const searchText = [name, title, department, email, location]
      .filter(Boolean)
      .join(' ')
      .toLowerCase();

    // Store in data attribute for faster searching
    card.dataset.searchText = searchText;

    // Ensure card has transition properties
    card.style
Generated by Repobility's multi-pass static-analysis pipeline (https://repobility.com)
highlightSearchTerm function · typescript · L166-L193 (28 LOC)
src/lib/searchFilter.ts
export function highlightSearchTerm(searchTerm: string, employeeCardsSelector: string): void {
  if (!searchTerm) return;

  const employeeCards = document.querySelectorAll(employeeCardsSelector);

  employeeCards.forEach((card) => {
    const textElements = card.querySelectorAll('.employee-name, .employee-title, .employee-department');

    textElements.forEach((element) => {
      const originalText = element.getAttribute('data-original-text') || element.textContent || '';

      // Store original text if not already stored
      if (!element.getAttribute('data-original-text')) {
        element.setAttribute('data-original-text', originalText);
      }

      // Create highlighted version
      const escaped = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
    const regex = new RegExp(`(${escaped})`, 'gi');
      const highlightedText = originalText.replace(
        regex,
        '<mark style="background-color: var(--accent-color-light); padding: 2px 4px; border-radius: 3px;">$1<
removeHighlighting function · typescript · L198-L211 (14 LOC)
src/lib/searchFilter.ts
export function removeHighlighting(employeeCardsSelector: string): void {
  const employeeCards = document.querySelectorAll(employeeCardsSelector);

  employeeCards.forEach((card) => {
    const textElements = card.querySelectorAll('.employee-name, .employee-title, .employee-department');

    textElements.forEach((element) => {
      const originalText = element.getAttribute('data-original-text');
      if (originalText) {
        element.textContent = originalText;
      }
    });
  });
}
getAllowedIPs function · typescript · L9-L46 (38 LOC)
src/middleware.ts
async function getAllowedIPs(requestUrl: string): Promise<string[]> {
  const now = Date.now()

  if (cachedIPs && now - cacheTimestamp < CACHE_TTL_MS) {
    return cachedIPs
  }

  try {
    const origin = new URL(requestUrl).origin
    const response = await fetch(
      `${origin}/api/internal/allowed-ips`,
      {
        headers: {
          'x-internal-secret': process.env.INTERNAL_API_SECRET || '',
        },
      }
    )

    if (!response.ok) {
      console.error(
        `Failed to fetch allowed IPs: ${response.status} ${response.statusText}`
      )
      // If we have stale cached data, use it as fallback
      if (cachedIPs) return cachedIPs
      return []
    }

    const data = await response.json()
    cachedIPs = data.ips ?? []
    cacheTimestamp = now
    return cachedIPs!
  } catch (error) {
    console.error('Error fetching allowed IPs:', error)
    // Fallback to stale cache if available
    if (cachedIPs) return cachedIPs
    return []
  }
}
getClientIP function · typescript · L48-L57 (10 LOC)
src/middleware.ts
function getClientIP(request: NextRequest): string | null {
  // Vercel sets x-forwarded-for; fall back to x-real-ip
  const forwarded = request.headers.get('x-forwarded-for')
  if (forwarded) {
    // x-forwarded-for can contain multiple IPs; first is the client
    return forwarded.split(',')[0].trim()
  }

  return request.headers.get('x-real-ip')
}
blockedResponse function · typescript · L59-L110 (52 LOC)
src/middleware.ts
function blockedResponse(): NextResponse {
  const html = `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Access Restricted</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      display: flex;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
      background: #f8fafc;
      color: #1e293b;
    }
    .container {
      text-align: center;
      max-width: 480px;
      padding: 2rem;
    }
    .icon {
      font-size: 3rem;
      margin-bottom: 1rem;
    }
    h1 {
      font-size: 1.5rem;
      font-weight: 600;
      margin-bottom: 0.75rem;
    }
    p {
      color: #64748b;
      line-height: 1.6;
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="icon">🔒</div>
    <h1>Access Restricted</h1>
    <p>This dire
isPublicRoute function · typescript · L116-L127 (12 LOC)
src/middleware.ts
function isPublicRoute(pathname: string): boolean {
  // Root page
  if (pathname === '/') return true

  // Directory pages
  if (pathname.startsWith('/heaton-directory')) return true

  // Public employee API (GET) — restrict IPs here; POST/PUT have auth checks
  if (pathname === '/api/employees') return true

  return false
}
isExemptFromIPCheck function · typescript · L133-L153 (21 LOC)
src/middleware.ts
function isExemptFromIPCheck(pathname: string): boolean {
  // Admin portal — protected by session auth, accessible from any IP
  if (pathname.startsWith('/admin')) return true

  // Admin API routes — protected by their own auth
  if (pathname.startsWith('/api/admin')) return true

  // NextAuth API routes — handles Microsoft SSO OIDC flow
  if (pathname.startsWith('/api/auth')) return true

  // Internal API routes — protected by internal secret
  if (pathname.startsWith('/api/internal')) return true

  // Next.js internals and static assets
  if (pathname.startsWith('/_next')) return true
  if (pathname.startsWith('/favicon')) return true
  if (pathname === '/robots.txt') return true
  if (pathname === '/sitemap.xml') return true

  return false
}
middleware function · typescript · L155-L185 (31 LOC)
src/middleware.ts
export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl

  // --- IP Restriction for Public Routes ---
  if (!isExemptFromIPCheck(pathname) && isPublicRoute(pathname)) {
    const clientIP = getClientIP(request)

    if (clientIP) {
      const allowedIPs = await getAllowedIPs(request.url)

      // If no IPs configured, allow all traffic (fail-open for initial setup)
      if (allowedIPs.length > 0 && !allowedIPs.includes(clientIP)) {
        return blockedResponse()
      }
    }
    // If we can't determine the client IP, allow the request through
    // (better to fail open than block legitimate users)
  }

  // --- Admin Auth Protection ---
  if (pathname.startsWith('/admin') && pathname !== '/admin/login') {
    const user = await getSessionFromRequest(request)

    if (!user) {
      const loginUrl = new URL('/admin/login', request.url)
      return NextResponse.redirect(loginUrl)
    }
  }

  return NextResponse.next()
}
‹ prevpage 3 / 3