Docs
Utilities & Libraries

Utilities & Libraries

Comprehensive guide to utility functions, custom hooks, and shared libraries used throughout the YouTube Analyzer application.

This section documents all utility functions, custom React hooks, validation schemas, and shared libraries that power the YouTube Analyzer application.

Core Utilities

lib/utils.ts

Central utility functions used throughout the application.

Core Functions

// Class name utility for Tailwind CSS
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}
 
// Date formatting
export function formatDate(input: string | number): string {
  const date = new Date(input);
  return date.toLocaleDateString("en-US", {
    month: "long",
    day: "numeric", 
    year: "numeric",
  });
}
 
// Absolute URL generation
export function absoluteUrl(path: string) {
  return `${env.NEXT_PUBLIC_APP_URL}${path}`;
}

Metadata Construction

export function constructMetadata({
  title = siteConfig.name,
  description = siteConfig.description,
  image = siteConfig.ogImage,
  icons = "/favicon.ico",
  noIndex = false,
}: {
  title?: string;
  description?: string;
  image?: string;
  icons?: string;
  noIndex?: boolean;
} = {}): Metadata {
  return {
    title,
    description,
    keywords: ["Next.js", "React", "Prisma", "YouTube", "Analysis"],
    authors: [{ name: "sfoerster" }],
    openGraph: {
      type: "website",
      locale: "en_US",
      url: siteConfig.url,
      title,
      description,
      siteName: title,
    },
    twitter: {
      card: "summary_large_image",
      title,
      description,
      images: [image],
    },
    // ... additional metadata
  };
}

Helper Functions

// Time ago formatting
export const timeAgo = (timestamp: Date, timeOnly?: boolean): string => {
  if (!timestamp) return "never";
  return `${ms(Date.now() - new Date(timestamp).getTime())}${
    timeOnly ? "" : " ago"
  }`;
};
 
// Fetch wrapper with error handling
export async function fetcher<JSON = any>(
  input: RequestInfo,
  init?: RequestInit,
): Promise<JSON> {
  const res = await fetch(input, init);
  if (!res.ok) {
    const json = await res.json();
    if (json.error) {
      const error = new Error(json.error) as Error & { status: number };
      error.status = res.status;
      throw error;
    } else {
      throw new Error("An unexpected error occurred");
    }
  }
  return res.json();
}
 
// Number formatting
export function nFormatter(num: number, digits?: number) {
  const lookup = [
    { value: 1, symbol: "" },
    { value: 1e3, symbol: "k" },
    { value: 1e6, symbol: "M" },
    { value: 1e9, symbol: "G" },
    { value: 1e12, symbol: "T" },
    { value: 1e15, symbol: "P" },
    { value: 1e18, symbol: "E" }
  ];
  const rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
  var item = lookup.slice().reverse().find(function(item) {
    return num >= item.value;
  });
  return item ? (num / item.value).toFixed(digits || 1).replace(rx, "$1") + item.symbol : "0";
}
 
// String utilities
export function capitalize(str: string) {
  if (!str || typeof str !== "string") return str;
  return str.charAt(0).toUpperCase() + str.slice(1);
}
 
export const truncate = (str: string, length: number) => {
  if (!str || str.length <= length) return str;
  return `${str.slice(0, length)}...`;
};

Custom React Hooks

hooks/use-media-query.ts

Responsive design hook for device detection.

export function useMediaQuery() {
  const [device, setDevice] = useState<"mobile" | "sm" | "tablet" | "desktop" | null>(null);
  const [dimensions, setDimensions] = useState<{ width: number; height: number; } | null>(null);
 
  useEffect(() => {
    const checkDevice = () => {
      if (window.matchMedia("(max-width: 640px)").matches) {
        setDevice("mobile");
      } else if (window.matchMedia("(max-width: 768px)").matches) {
        setDevice("sm");
      } else if (window.matchMedia("(min-width: 641px) and (max-width: 1024px)").matches) {
        setDevice("tablet");
      } else {
        setDevice("desktop");
      }
      setDimensions({ width: window.innerWidth, height: window.innerHeight });
    };
 
    checkDevice();
    window.addEventListener("resize", checkDevice);
    return () => window.removeEventListener("resize", checkDevice);
  }, []);
 
  return {
    device,
    width: dimensions?.width,
    height: dimensions?.height,
    isMobile: device === "mobile",
    isSm: device === "sm", 
    isTablet: device === "tablet",
    isDesktop: device === "desktop",
  };
}

hooks/use-intersection-observer.ts

Viewport intersection detection for animations and lazy loading.

interface Args extends IntersectionObserverInit {
  freezeOnceVisible?: boolean;
}
 
function useIntersectionObserver(
  elementRef: RefObject<Element>,
  { threshold = 0, root = null, rootMargin = "0%", freezeOnceVisible = false }: Args,
): IntersectionObserverEntry | undefined {
  const [entry, setEntry] = useState<IntersectionObserverEntry>();
 
  const frozen = entry?.isIntersecting && freezeOnceVisible;
 
  const updateEntry = ([entry]: IntersectionObserverEntry[]): void => {
    setEntry(entry);
  };
 
  useEffect(() => {
    const node = elementRef?.current;
    const hasIOSupport = !!window.IntersectionObserver;
 
    if (!hasIOSupport || frozen || !node) return;
 
    const observerParams = { threshold, root, rootMargin };
    const observer = new IntersectionObserver(updateEntry, observerParams);
 
    observer.observe(node);
 
    return () => observer.disconnect();
  }, [elementRef?.current, JSON.stringify(threshold), root, rootMargin, frozen]);
 
  return entry;
}

hooks/use-local-storage.ts

Persistent local storage state management.

const useLocalStorage = <T>(
  key: string,
  initialValue: T,
): [T, (value: T) => void] => {
  const [storedValue, setStoredValue] = useState(initialValue);
 
  useEffect(() => {
    const item = window.localStorage.getItem(key);
    if (item) {
      setStoredValue(JSON.parse(item));
    }
  }, [key]);
 
  const setValue = (value: T) => {
    setStoredValue(value);
    window.localStorage.setItem(key, JSON.stringify(value));
  };
 
  return [storedValue, setValue];
};

hooks/use-scroll.ts

Scroll position detection for navigation styling.

export function useScroll(threshold: number) {
  const [scrolled, setScrolled] = useState(false);
 
  const onScroll = useCallback(() => {
    setScrolled(window.pageYOffset > threshold);
  }, [threshold]);
 
  useEffect(() => {
    window.addEventListener("scroll", onScroll);
    return () => window.removeEventListener("scroll", onScroll);
  }, [onScroll]);
 
  return scrolled;
}

hooks/use-lock-body.ts

Body scroll locking for modals and overlays.

export function useLockBody() {
  React.useLayoutEffect((): (() => void) => {
    const originalStyle: string = window.getComputedStyle(document.body).overflow;
    document.body.style.overflow = "hidden";
    return () => (document.body.style.overflow = originalStyle);
  }, []);
}

hooks/use-mounted.ts

Client-side hydration detection.

export function useMounted() {
  const [mounted, setMounted] = React.useState(false);
 
  React.useEffect(() => {
    setMounted(true);
  }, []);
 
  return mounted;
}

Validation Schemas

lib/validations/auth.ts

Authentication form validation using Zod.

import * as z from "zod";
 
export const userAuthSchema = z.object({
  email: z.string().email(),
});

lib/validations/user.ts

User profile validation schemas.

import { UserRole } from "@prisma/client";
import * as z from "zod";
 
export const userNameSchema = z.object({
  name: z.string().min(3).max(32),
});
 
export const userRoleSchema = z.object({
  role: z.nativeEnum(UserRole),
});

lib/validations/og.ts

Open Graph image generation validation.

import * as z from "zod";
 
export const ogImageSchema = z.object({
  heading: z.string(),
  type: z.string(),
  mode: z.enum(["light", "dark"]).default("dark"),
});

Shared Components & Libraries

Icons Library

The components/shared/icons.tsx file provides a centralized icon system using Lucide React icons and custom SVG icons.

import { LucideIcon, LucideProps } from "lucide-react";
 
export type Icon = LucideIcon;
 
export const Icons = {
  // Lucide icons
  add: Plus,
  arrowRight: ArrowRight,
  billing: CreditCard,
  dashboard: LayoutPanelLeft,
  // ... more icons
 
  // Custom SVG icons
  gitHub: ({ ...props }: LucideProps) => (
    <svg aria-hidden="true" focusable="false" role="img" {...props}>
      {/* SVG path */}
    </svg>
  ),
  google: ({ ...props }: LucideProps) => (
    <svg aria-hidden="true" focusable="false" role="img" {...props}>
      {/* SVG path */}
    </svg>
  ),
  // ... more custom icons
};

Usage

import { Icons } from "@/components/shared/icons";
 
// In component
<Icons.dashboard className="h-4 w-4" />
<Icons.gitHub className="h-6 w-6" />

UI Components

The application uses a comprehensive UI component library built on top of Radix UI primitives with Tailwind CSS styling. Key components include:

  • Form Components: Input, Label, Button, Select, Textarea
  • Layout Components: Card, Container, Skeleton, Separator
  • Feedback Components: Toast, Alert, Badge, Progress
  • Navigation Components: Dropdown Menu, Tabs, Accordion
  • Data Display: Table, Chart, Avatar, Tooltip
  • Overlay Components: Dialog, Sheet, Popover, Modal

Component Architecture

// Example: Button component with variants
const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline: "border border-input hover:bg-accent hover:text-accent-foreground",
        // ... more variants
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 rounded-md px-3",
        lg: "h-11 rounded-md px-8",
        // ... more sizes
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);

Service Libraries

YouTube API Service

// lib/youtube.ts
export class YouTubeService {
  async getChannelInfo(channelId: string) {
    // Channel data retrieval
  }
 
  async getChannelVideos(channelId: string, maxResults: number) {
    // Video list retrieval
  }
 
  async getVideoDetails(videoIds: string[]) {
    // Detailed video information
  }
}

OpenAI Integration

// lib/openai-streaming.ts
export async function generateAnalysis(
  videos: VideoData[],
  analysisType: string
) {
  // Streaming AI analysis generation
}

Email Service

// lib/email.ts
export const sendVerificationRequest = async ({ identifier, url, provider }) => {
  const user = await getUserByEmail(identifier);
  const userVerified = user?.emailVerified ? true : false;
  
  await resend.emails.send({
    from: provider.from,
    to: identifier,
    subject: userVerified ? "Sign-in link" : "Activate your account",
    react: MagicLinkEmail({
      firstName: user?.name,
      actionUrl: url,
      mailType: userVerified ? "login" : "register",
      siteName: siteConfig.name,
    }),
  });
};

Development Guidelines

Utility Function Standards

  1. Pure Functions: Utilities should be pure functions when possible
  2. Error Handling: Include proper error handling and validation
  3. TypeScript: Use strict typing for all parameters and return values
  4. Documentation: Include JSDoc comments for complex functions
  5. Testing: Add unit tests for critical utility functions

Custom Hook Best Practices

  1. Single Responsibility: Each hook should have a single, clear purpose
  2. Naming Convention: Use the use prefix consistently
  3. Dependencies: Minimize external dependencies
  4. Performance: Use useCallback and useMemo appropriately
  5. Cleanup: Always clean up event listeners and subscriptions

Validation Schema Guidelines

  1. Zod Integration: Use Zod for all form and API validation
  2. Reusable Schemas: Create composable schemas for common patterns
  3. Error Messages: Provide clear, user-friendly error messages
  4. Type Safety: Leverage TypeScript integration with z.infer

Component Library Standards

  1. Accessibility: Ensure all components meet WCAG guidelines
  2. Responsive Design: Components should work across all screen sizes
  3. Theming: Support light/dark mode and customizable themes
  4. Performance: Optimize for rendering performance
  5. Storybook: Document components with Storybook where applicable

Common Patterns

Data Fetching with Error Handling

const { data, error, isLoading } = useSWR(
  '/api/analyses',
  fetcher,
  {
    onError: (error) => {
      toast.error('Failed to load analyses');
    }
  }
);

Form Handling with Validation

const form = useForm<FormData>({
  resolver: zodResolver(userNameSchema),
  defaultValues: { name: user?.name || "" },
});
 
const onSubmit = handleSubmit(async (data) => {
  try {
    await updateUserName(data);
    toast.success('Name updated successfully');
  } catch (error) {
    toast.error('Failed to update name');
  }
});

Responsive Component Usage

const { isMobile, isTablet } = useMediaQuery();
 
return (
  <div className={cn(
    "grid gap-4",
    isMobile ? "grid-cols-1" : isTablet ? "grid-cols-2" : "grid-cols-3"
  )}>
    {/* Content */}
  </div>
);

This utilities documentation provides a comprehensive reference for all the helper functions, hooks, and shared libraries that make the YouTube Analyzer application maintainable and efficient.