← back to devfellowship__dfl-iterate

Function bodies 89 total

All specs Real LLM only Function bodies
ActiveInstrumentation function · typescript · L42-L62 (21 LOC)
src/components/observability/ObservabilityProvider.tsx
function ActiveInstrumentation({
  enableWebVitals,
  enableQueryTracking,
  children,
}: {
  enableWebVitals: boolean;
  enableQueryTracking: boolean;
  children: ReactNode;
}) {
  if (enableWebVitals) {
    // eslint-disable-next-line react-hooks/rules-of-hooks
    useWebVitals();
  }

  if (enableQueryTracking) {
    // eslint-disable-next-line react-hooks/rules-of-hooks
    useQueryErrorReporter();
  }

  return <>{children}</>;
}
ObservabilityProvider function · typescript · L66-L198 (133 LOC)
src/components/observability/ObservabilityProvider.tsx
export function ObservabilityProvider({
  serviceName,
  serviceVersion,
  collectorUrl,
  apiKey,
  enabled,
  enableWebVitals = true,
  enableQueryTracking = true,
  enableFetchTracing = true,
  children,
}: ObservabilityProviderProps) {
  const env = (
    import.meta as ImportMeta & { env: Record<string, string | boolean> }
  ).env;

  const isEnabled =
    enabled ?? (env?.VITE_OTEL_ENABLED === "true" || env?.PROD === true);

  const providersRef = useRef<OtelProviders | null>(null);

  const resolvedServiceName =
    serviceName ?? (env?.VITE_APP_NAME as string) ?? "unknown";
  const resolvedServiceVersion =
    serviceVersion ?? (env?.VITE_APP_VERSION as string) ?? "0.0.0";
  const resolvedCollectorUrl =
    collectorUrl ?? "https://otel.devfellowship.com";
  const resolvedApiKey = apiKey ?? (env?.VITE_OTEL_API_KEY as string);

  useEffect(() => {
    if (!isEnabled) return;

    // Initialize OTel SDK
    const providers = initOtelBrowser({
      serviceName: resolvedServiceNam
DynamicPreview function · typescript · L15-L93 (79 LOC)
src/components/preview/DynamicPreview.tsx
export function DynamicPreview({ 
  status, 
  previewState, 
  errorMessage,
  lastCompletedActivity 
}: DynamicPreviewProps) {
  const isBroken = status === ProjectStatus.BROKEN;
  const [showBadge, setShowBadge] = useState(false);
  const [currentBadge, setCurrentBadge] = useState<string | undefined>();

  // Show update badge when activity completes
  useEffect(() => {
    if (previewState.updateBadge && lastCompletedActivity !== undefined) {
      setCurrentBadge(previewState.updateBadge);
      setShowBadge(true);
      const timer = setTimeout(() => setShowBadge(false), 2500);
      return () => clearTimeout(timer);
    }
  }, [lastCompletedActivity, previewState.updateBadge]);

  return (
    <div className="h-full w-full flex flex-col bg-card border border-border rounded-xl overflow-hidden">
      {/* Toolbar */}
      <div className="shrink-0 flex items-center justify-between px-4 py-2 border-b border-border bg-muted/30">
        <div className="flex items-center gap-2">
    
MockPreview function · typescript · L100-L272 (173 LOC)
src/components/preview/DynamicPreview.tsx
function MockPreview({ previewState, lastCompletedActivity }: MockPreviewProps) {
  const { headerStyle, cardsStyle, stateManagement, checkoutWorking, cartCount } = previewState;
  
  const isHeaderHighlighted = lastCompletedActivity === 0;
  const isCardsHighlighted = lastCompletedActivity === 1;
  const isCartHighlighted = lastCompletedActivity === 2;
  const isCheckoutHighlighted = lastCompletedActivity === 3;

  const products = [
    { name: 'Luva de Boxe Pro', price: 'R$ 299,90', emoji: '🥊' },
    { name: 'Saco de Pancada', price: 'R$ 459,90', emoji: '🎯' },
    { name: 'Bandagem Elástica', price: 'R$ 29,90', emoji: '🩹' },
    { name: 'Protetor Bucal', price: 'R$ 49,90', emoji: '😬' },
  ];

  const stateLabel = stateManagement === 'context' 
    ? 'Context' 
    : stateManagement === 'zustand' 
      ? 'Zustand' 
      : stateManagement === 'localstorage' 
        ? 'LocalStorage' 
        : null;

  return (
    <div className="min-h-full">
      {/* Header */}
      <motion.div 
ProjectPreview function · typescript · L11-L58 (48 LOC)
src/components/preview/ProjectPreview.tsx
export function ProjectPreview({ status, errorMessage }: ProjectPreviewProps) {
  const isBroken = status === ProjectStatus.BROKEN;

  return (
    <div className="h-full w-full flex flex-col bg-card border border-border rounded-xl overflow-hidden">
      {/* Toolbar */}
      <div className="shrink-0 flex items-center justify-between px-4 py-2 border-b border-border bg-muted/30">
        <div className="flex items-center gap-2">
          <div className="flex gap-1.5">
            <div className="w-3 h-3 rounded-full bg-destructive/60" />
            <div className="w-3 h-3 rounded-full bg-warning/60" />
            <div className="w-3 h-3 rounded-full bg-success/60" />
          </div>
          <span className="text-xs text-muted-foreground ml-2">BoxShop Preview</span>
        </div>
        <div className="flex items-center gap-1">
          <Button variant="ghost" size="icon" className="h-7 w-7">
            <RefreshCw className="w-3.5 h-3.5" />
          </Button>
          <Butt
MockPreview function · typescript · L60-L107 (48 LOC)
src/components/preview/ProjectPreview.tsx
function MockPreview() {
  return (
    <div className="min-h-full">
      {/* Mock Header */}
      <div className="bg-white border-b border-gray-100 px-6 py-4">
        <div className="flex items-center justify-between">
          <div className="flex items-center gap-2">
            <span className="text-2xl">🥊</span>
            <span className="font-bold text-gray-900 text-lg">BoxShop</span>
          </div>
          <div className="flex items-center gap-6 text-sm text-gray-600">
            <span className="hover:text-gray-900 cursor-pointer">Home</span>
            <span className="hover:text-gray-900 cursor-pointer">Produtos</span>
            <span className="hover:text-gray-900 cursor-pointer">Carrinho (3)</span>
          </div>
        </div>
      </div>

      {/* Mock Product Grid */}
      <div className="p-6">
        <h2 className="text-xl font-bold text-gray-900 mb-6">Equipamentos de Boxe</h2>
        <div className="grid grid-cols-2 gap-4">
          {[
           
GitLog function · typescript · L25-L87 (63 LOC)
src/components/project/GitLog.tsx
export function GitLog({ entries, isOpen, onToggle }: GitLogProps) {
  return (
    <>
      {/* Button rendered externally now via LessonPage */}

      <AnimatePresence>
        {isOpen && (
          <>
            <motion.div
              className="fixed inset-0 bg-black/50 z-40"
              initial={{ opacity: 0 }}
              animate={{ opacity: 1 }}
              exit={{ opacity: 0 }}
              onClick={onToggle}
            />
            <motion.div
              className="fixed right-0 top-0 h-full w-full max-w-md bg-card border-l border-border z-50 flex flex-col"
              initial={{ x: '100%' }}
              animate={{ x: 0 }}
              exit={{ x: '100%' }}
              transition={{ type: 'spring', damping: 25, stiffness: 300 }}
            >
              <div className="flex items-center justify-between px-4 py-3 border-b border-border">
                <div className="flex items-center gap-2">
                  <Terminal className="w-5 h-5 text-prim
Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
Badge function · typescript · L25-L27 (3 LOC)
src/components/ui/badge.tsx
function Badge({ className, variant, ...props }: BadgeProps) {
  return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
}
Calendar function · typescript · L10-L51 (42 LOC)
src/components/ui/calendar.tsx
function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
  return (
    <DayPicker
      showOutsideDays={showOutsideDays}
      className={cn("p-3", className)}
      classNames={{
        months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
        month: "space-y-4",
        caption: "flex justify-center pt-1 relative items-center",
        caption_label: "text-sm font-medium",
        nav: "space-x-1 flex items-center",
        nav_button: cn(
          buttonVariants({ variant: "outline" }),
          "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
        ),
        nav_button_previous: "absolute left-1",
        nav_button_next: "absolute right-1",
        table: "w-full border-collapse space-y-1",
        head_row: "flex",
        head_cell: "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
        row: "flex w-full mt-2",
        cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-se
useCarousel function · typescript · L31-L39 (9 LOC)
src/components/ui/carousel.tsx
function useCarousel() {
  const context = React.useContext(CarouselContext);

  if (!context) {
    throw new Error("useCarousel must be used within a <Carousel />");
  }

  return context;
}
useChart function · typescript · L22-L30 (9 LOC)
src/components/ui/chart.tsx
function useChart() {
  const context = React.useContext(ChartContext);

  if (!context) {
    throw new Error("useChart must be used within a <ChartContainer />");
  }

  return context;
}
getPayloadConfigFromPayload function · typescript · L278-L301 (24 LOC)
src/components/ui/chart.tsx
function getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {
  if (typeof payload !== "object" || payload === null) {
    return undefined;
  }

  const payloadPayload =
    "payload" in payload && typeof payload.payload === "object" && payload.payload !== null
      ? payload.payload
      : undefined;

  let configLabelKey: string = key;

  if (key in payload && typeof payload[key as keyof typeof payload] === "string") {
    configLabelKey = payload[key as keyof typeof payload] as string;
  } else if (
    payloadPayload &&
    key in payloadPayload &&
    typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
  ) {
    configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;
  }

  return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config];
}
useSidebar function · typescript · L34-L41 (8 LOC)
src/components/ui/sidebar.tsx
function useSidebar() {
  const context = React.useContext(SidebarContext);
  if (!context) {
    throw new Error("useSidebar must be used within a SidebarProvider.");
  }

  return context;
}
Skeleton function · typescript · L3-L5 (3 LOC)
src/components/ui/skeleton.tsx
function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
  return <div className={cn("animate-pulse rounded-md bg-muted", className)} {...props} />;
}
Toaster function · typescript · L4-L24 (21 LOC)
src/components/ui/toaster.tsx
export function Toaster() {
  const { toasts } = useToast();

  return (
    <ToastProvider>
      {toasts.map(function ({ id, title, description, action, ...props }) {
        return (
          <Toast key={id} {...props}>
            <div className="grid gap-1">
              {title && <ToastTitle>{title}</ToastTitle>}
              {description && <ToastDescription>{description}</ToastDescription>}
            </div>
            {action}
            <ToastClose />
          </Toast>
        );
      })}
      <ToastViewport />
    </ToastProvider>
  );
}
Same scanner, your repo: https://repobility.com — Repobility
Header function · typescript · L15-L26 (12 LOC)
src/consts/ai-responses.ts
function Header() {
  return (
    <div style={{background: 'white', padding: '20px'}}>
      <img src="/logo.png" />
      <div>
        <a href="/">Home</a>
        <a href="/products">Produtos</a>
        <a href="/cart">Carrinho (3)</a>
      </div>
    </div>
  )
}
useActivityPage function · typescript · L9-L164 (156 LOC)
src/hooks/useActivityPage.ts
export function useActivityPage() {
  const [currentActivityIndex, setCurrentActivityIndex] = useState(0);
  const [activities, setActivities] = useState<Activity[]>(activitiesData);
  
  const { 
    project, 
    gitLog, 
    updateFile, 
    setStatus, 
    addDecision, 
    addGitLogEntry 
  } = useProject();
  
  const { 
    text: aiResponse, 
    isStreaming: isAIStreaming, 
    streamText, 
    reset: resetAI 
  } = useAIStreaming();

  const currentActivity = useMemo(() => 
    activities[currentActivityIndex], 
    [activities, currentActivityIndex]
  );

  const canAdvance = useMemo(() => 
    currentActivity?.status === ActivityStatus.COMPLETED,
    [currentActivity]
  );

  const triggerAIResponse = useCallback(async (responseKey: string) => {
    const response = aiResponses[responseKey];
    if (response) {
      await streamText(response.text, 15);
    }
  }, [streamText]);

  const completeActivity = useCallback((activityId: string) => {
    setActivities(prev => prev.
useAIHistory function · typescript · L21-L44 (24 LOC)
src/hooks/useAIHistory.ts
export function useAIHistory(): UseAIHistoryReturn {
  const [messages, setMessages] = useState<AIMessage[]>([]);

  const addMessage = useCallback((msg: Omit<AIMessage, 'id' | 'timestamp'>) => {
    const newMessage: AIMessage = {
      ...msg,
      id: crypto.randomUUID(),
      timestamp: new Date(),
    };
    setMessages(prev => [newMessage, ...prev]);
  }, []);

  const getMessageForActivity = useCallback((activityId: string) => {
    return messages.find(m => m.activityId === activityId);
  }, [messages]);

  return {
    messages,
    addMessage,
    getMessageForActivity,
    messageCount: messages.length,
    latestMessage: messages[0],
  };
}
useAIStreaming function · typescript · L4-L35 (32 LOC)
src/hooks/useAIStreaming.ts
export function useAIStreaming() {
  const [text, setText] = useState('');
  const [isStreaming, setIsStreaming] = useState(false);
  const abortRef = useRef(false);

  const streamText = useCallback(async (fullText: string, delayMs = 20) => {
    abortRef.current = false;
    setIsStreaming(true);
    setText('');

    for (let i = 0; i < fullText.length; i++) {
      if (abortRef.current) break;
      await delay(delayMs);
      setText(prev => prev + fullText[i]);
    }

    setIsStreaming(false);
  }, []);

  const stopStreaming = useCallback(() => {
    abortRef.current = true;
    setIsStreaming(false);
  }, []);

  const reset = useCallback(() => {
    setText('');
    setIsStreaming(false);
    abortRef.current = false;
  }, []);

  return { text, isStreaming, streamText, stopStreaming, reset };
}
useIsMobile function · typescript · L5-L19 (15 LOC)
src/hooks/use-mobile.tsx
export function useIsMobile() {
  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined);

  React.useEffect(() => {
    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
    const onChange = () => {
      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
    };
    mql.addEventListener("change", onChange);
    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
    return () => mql.removeEventListener("change", onChange);
  }, []);

  return !!isMobile;
}
usePreviewState function · typescript · L22-L85 (64 LOC)
src/hooks/usePreviewState.ts
export function usePreviewState(
  currentActivityIndex: number,
  completedActivities: number[],
  decisions: Decision[]
): PreviewState {
  return useMemo(() => {
    let state = { ...initialState };
    
    // Show state UP TO the current activity
    // If viewing activity 0, show initial state
    // If viewing activity 1, show state after activity 0 completed (if it was)
    
    // After Activity 1 completed AND viewing activity 1+: Header styled
    if (completedActivities.includes(0) && currentActivityIndex >= 1) {
      state.headerStyle = 'styled';
      if (currentActivityIndex === 1) {
        state.updateBadge = '✨ Header atualizado';
      }
    }
    
    // After Activity 2 completed AND viewing activity 2+: Cards enhanced
    if (completedActivities.includes(1) && currentActivityIndex >= 2) {
      state.cardsStyle = 'enhanced';
      if (currentActivityIndex === 2) {
        state.updateBadge = '⚡ Performance otimizada';
      }
    }
    
    // After Activity 3 co
useProject function · typescript · L7-L53 (47 LOC)
src/hooks/useProject.ts
export function useProject() {
  const [project, setProject] = useState<ProjectState>(initialProjectState);
  const [gitLog, setGitLog] = useState<GitLogEntry[]>(initialGitLog);

  const updateFile = useCallback((path: string, content: string) => {
    setProject(prev => ({
      ...prev,
      files: prev.files.map(file =>
        file.path === path ? { ...file, content } : file
      ),
    }));
  }, []);

  const setStatus = useCallback((status: ProjectStatus) => {
    setProject(prev => ({ ...prev, status }));
  }, []);

  const addDecision = useCallback((decision: Decision) => {
    setProject(prev => ({
      ...prev,
      decisions: [...prev.decisions, decision],
    }));
  }, []);

  const addGitLogEntry = useCallback((entry: Omit<GitLogEntry, 'id' | 'timestamp'>) => {
    const newEntry: GitLogEntry = {
      ...entry,
      id: `log-${Date.now()}`,
      timestamp: new Date(),
    };
    setGitLog(prev => [newEntry, ...prev]);
  }, []);

  const getFile = useCallback((path: 
useReadAndChoose function · typescript · L7-L22 (16 LOC)
src/hooks/useReadAndChoose.ts
export function useReadAndChoose({ onDecide }: UseReadAndChooseParams){
    const [selectedOption, setSelectedOption] = useState<string | null>(null);
    const [isConfirming, setIsConfirming] = useState(false);

    const handleConfirm = () => {
        if (selectedOption) {
            setIsConfirming(true);
            setTimeout(() => {
                onDecide(selectedOption);
                setIsConfirming(false);
            }, 500);
        }
    };

    return { selectedOption, setSelectedOption, isConfirming, handleConfirm};
}
If a scraper extracted this row, it came from Repobility (https://repobility.com)
useSoundEffects function · typescript · L13-L46 (34 LOC)
src/hooks/useSoundEffects.ts
export function useSoundEffects() {
  const audioRefs = useRef<Record<string, HTMLAudioElement>>({});

  const play = useCallback((sound: SoundType, volume: number = 0.5) => {
    try {
      // Reuse or create audio element
      if (!audioRefs.current[sound]) {
        audioRefs.current[sound] = new Audio(SOUNDS[sound]);
      }
      
      const audio = audioRefs.current[sound];
      audio.volume = Math.min(1, Math.max(0, volume));
      audio.currentTime = 0;
      audio.play().catch(() => {
        // Ignore autoplay restrictions
      });
    } catch {
      // Ignore errors
    }
  }, []);

  const playSuccess = useCallback(() => play('success', 0.4), [play]);
  const playError = useCallback(() => play('error', 0.3), [play]);
  const playClick = useCallback(() => play('click', 0.2), [play]);
  const playCelebration = useCallback(() => play('celebration', 0.5), [play]);

  return {
    play,
    playSuccess,
    playError,
    playClick,
    playCelebration,
  };
}
genId function · typescript · L24-L27 (4 LOC)
src/hooks/use-toast.ts
function genId() {
  count = (count + 1) % Number.MAX_SAFE_INTEGER;
  return count.toString();
}
dispatch function · typescript · L128-L133 (6 LOC)
src/hooks/use-toast.ts
function dispatch(action: Action) {
  memoryState = reducer(memoryState, action);
  listeners.forEach((listener) => {
    listener(memoryState);
  });
}
toast function · typescript · L137-L164 (28 LOC)
src/hooks/use-toast.ts
function toast({ ...props }: Toast) {
  const id = genId();

  const update = (props: ToasterToast) =>
    dispatch({
      type: "UPDATE_TOAST",
      toast: { ...props, id },
    });
  const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });

  dispatch({
    type: "ADD_TOAST",
    toast: {
      ...props,
      id,
      open: true,
      onOpenChange: (open) => {
        if (!open) dismiss();
      },
    },
  });

  return {
    id: id,
    dismiss,
    update,
  };
}
useToast function · typescript · L166-L184 (19 LOC)
src/hooks/use-toast.ts
function useToast() {
  const [state, setState] = React.useState<State>(memoryState);

  React.useEffect(() => {
    listeners.push(setState);
    return () => {
      const index = listeners.indexOf(setState);
      if (index > -1) {
        listeners.splice(index, 1);
      }
    };
  }, [state]);

  return {
    ...state,
    toast,
    dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
  };
}
cn function · typescript · L4-L6 (3 LOC)
src/lib/utils.ts
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}
HomePage function · typescript · L8-L83 (76 LOC)
src/pages/HomePage.tsx
export default function HomePage() {
  const navigate = useNavigate();

  const handleStartLesson = (lessonId: string) => {
    navigate(`/lesson/${lessonId}`);
  };

  return (
    <div className="h-screen bg-background flex flex-col overflow-hidden">
      {/* Header */}
      <header className="shrink-0 border-b border-border">
        <div className="container mx-auto px-4 py-6">
          <div className="flex items-center gap-3">
            <span className="text-2xl font-bold text-gradient">iterate</span>
            <span className="text-sm text-muted-foreground">by DevFellowship</span>
          </div>
        </div>
      </header>

      <main className="flex-1 overflow-auto">
        <div className="container mx-auto px-4 py-16">
          {/* Hero Text */}
          <motion.div 
            className="text-center mb-16"
            initial={{ opacity: 0, y: 20 }}
            animate={{ opacity: 1, y: 0 }}
          >
            <h1 className="text-4xl md:text-5xl lg:text-6
LessonCard function · typescript · L91-L137 (47 LOC)
src/pages/HomePage.tsx
function LessonCard({ lesson, index, onStart }: LessonCardProps) {
  return (
    <motion.div
      className="card-interactive p-6"
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ delay: 0.1 * index }}
      whileHover={{ scale: 1.01 }}
    >
      <div className="flex flex-col md:flex-row md:items-center gap-6">
        {/* Icon */}
        <div className="w-16 h-16 rounded-2xl bg-primary/10 flex items-center justify-center shrink-0">
          <span className="text-4xl">🥊</span>
        </div>

        {/* Content */}
        <div className="flex-1">
          <h3 className="text-xl font-bold text-foreground mb-2">
            {lesson.title}
          </h3>
          <p className="text-muted-foreground mb-4">
            {lesson.description}
          </p>
          <div className="flex flex-wrap items-center gap-4 text-sm text-muted-foreground">
            <div className="flex items-center gap-1.5">
              <Clock className="w-4
Repobility · code-quality intelligence platform · https://repobility.com
LessonPage function · typescript · L28-L415 (388 LOC)
src/pages/LessonPage.tsx
export default function LessonPage() {
  const { lessonId } = useParams<{ lessonId: string }>();
  const navigate = useNavigate();
  
  // Sound effects
  const { playSuccess, playError, playCelebration } = useSoundEffects();
  
  // Drawer states
  const [gitLogOpen, setGitLogOpen] = useState(false);
  const [aiHistoryOpen, setAiHistoryOpen] = useState(false);
  const [showLessonComplete, setShowLessonComplete] = useState(false);
  const [lastCompletedActivity, setLastCompletedActivity] = useState<number | undefined>();
  
  // Result modal state
  const [showResult, setShowResult] = useState(false);
  const [resultData, setResultData] = useState<{
    isSuccess: boolean;
    xpEarned: number;
    feedback: string;
    activityTitle: string;
    isLastActivity: boolean;
  } | null>(null);

  // Game state
  const [lives, setLives] = useState(3);
  const [streak, setStreak] = useState(3);
  const [xp, setXp] = useState(0);

  // AI History
  const { messages: aiMessages, addMessage } =
Header function · typescript · L61-L72 (12 LOC)
src/test-utils/activities.dummy.ts
function Header() {
  return (
    <div style={{background: 'white', padding: '20px'}}>
      <img src="/logo.png" />
      <div>
        <a href="/">Home</a>
        <a href="/products">Produtos</a>
        <a href="/cart">Carrinho (3)</a>
      </div>
    </div>
  )
}
CheckoutPage function · typescript · L163-L180 (18 LOC)
src/test-utils/activities.dummy.ts
export function CheckoutPage() {
  const { items } = useCart(); // items pode ser undefined!
  
  const total = items.map(item => item.price * item.quantity)
    .reduce((a, b) => a + b, 0);

  return (
    <div className="checkout">
      <h1>Checkout</h1>
      {items.map(item => (
        <div key={item.id}>
          {item.name} - R$ {item.price}
        </div>
      ))}
      <p>Total: R$ {total}</p>
    </div>
  );
}`,
ProductList function · typescript · L201-L224 (24 LOC)
src/test-utils/activities.dummy.ts
export function ProductList({ products }: { products: Product[] }) {
  const [filter, setFilter] = useState('');
  
  // TODO: Otimize com useMemo
  const filteredProducts = products.filter(p => 
    p.name.toLowerCase().includes(filter.toLowerCase())
  );
  
  const total = filteredProducts.reduce((sum, p) => sum + p.price, 0);

  return (
    <div>
      <input 
        value={filter} 
        onChange={e => setFilter(e.target.value)}
        placeholder="Filtrar produtos..."
      />
      <p>Total: R$ {total.toFixed(2)}</p>
      {filteredProducts.map(p => (
        <div key={p.id}>{p.name} - R$ {p.price}</div>
      ))}
    </div>
  );
}`,
PromoBadge function · typescript · L242-L249 (8 LOC)
src/test-utils/activities.dummy.ts
export function PromoBadge() {
  return (
    <span className="promo-badge">
      {/* TODO: Estilize para parecer com a referência */}
      PROMOÇÃO
    </span>
  );
}
App function · typescript · L12-L21 (10 LOC)
src/test-utils/project.dummy.ts
export default function App() {
  return (
    <div className="min-h-screen bg-white">
      <Header />
      <main className="container mx-auto px-4 py-8">
        <ProductGrid />
      </main>
    </div>
  );
}`,
ProductCard function · typescript · L43-L82 (40 LOC)
src/test-utils/project.dummy.ts
export function ProductCard({ id, name, price, image, description }: ProductCardProps) {
  const [quantity, setQuantity] = useState(1);
  
  // ⚠️ Problema: recalcula a cada render
  const formattedPrice = new Intl.NumberFormat('pt-BR', {
    style: 'currency',
    currency: 'BRL',
  }).format(price);
  
  // ⚠️ Problema: nova função a cada render
  const handleAddToCart = () => {
    console.log('Adding to cart:', { id, quantity });
  };

  return (
    <div className="bg-white border border-black/10 rounded-lg overflow-hidden">
      <img src={image} alt={name} className="w-full h-48 object-cover" />
      <div className="p-4">
        <h3 className="font-bold text-black">{name}</h3>
        <p className="text-gray-600 text-sm mt-1">{description}</p>
        <p className="text-xl font-bold text-black mt-2">{formattedPrice}</p>
        <div className="flex items-center gap-2 mt-4">
          <input
            type="number"
            min="1"
            value={quantity}
            
ProductGrid function · typescript · L121-L132 (12 LOC)
src/test-utils/project.dummy.ts
export function ProductGrid() {
  return (
    <div>
      <h2 className="text-2xl font-bold text-black mb-6">Equipamentos de Boxe</h2>
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
        {products.map((product) => (
          <ProductCard key={product.id} {...product} />
        ))}
      </div>
    </div>
  );
}`,
‹ prevpage 2 / 2