Function bodies 89 total
ActivityCard function · typescript · L30-L91 (62 LOC)src/components/activity/ActivityCard.tsx
export function ActivityCard({ activity, index, onClick, isActive }: ActivityCardProps) {
const Icon = typeIcons[activity.type];
const isLocked = activity.status === ActivityStatus.LOCKED;
const isCompleted = activity.status === ActivityStatus.COMPLETED;
const isCurrent = activity.status === ActivityStatus.CURRENT;
return (
<motion.button
onClick={onClick}
disabled={isLocked}
className={cn(
"relative w-full p-4 rounded-xl border text-left transition-all duration-300",
isLocked && "opacity-40 cursor-not-allowed grayscale",
isCurrent && "border-primary/50 bg-card shadow-lg",
isCompleted && "border-success/30 bg-success/5",
!isLocked && !isCurrent && !isCompleted && "border-border bg-card/50 hover:bg-card hover:border-border/80",
isActive && isCurrent && "ring-2 ring-primary/50"
)}
whileHover={!isLocked ? { scale: 1.02 } : undefined}
whileTap={!isLocked ? { scale: 0.98 } : undefined}
>
ActivityHeader function · typescript · L9-L15 (7 LOC)src/components/activity/ActivityHeader.tsx
export function ActivityHeader({ activity }: ActivityHeaderProps) {
return (
<h1 className="text-2xl font-bold text-foreground">
{activity.title}
</h1>
);
}BreakAndFix function · typescript · L17-L86 (70 LOC)src/components/activity/BreakAndFix.tsx
export function BreakAndFix({ activity, errorMessage, onFix, onError, onRequestHint }: BreakAndFixProps) {
const [code, setCode] = useState(activity.aiGeneratedCode || '');
const [isTesting, setIsTesting] = useState(false);
const handleTest = async () => {
setIsTesting(true);
await new Promise(resolve => setTimeout(resolve, 1500));
const hasOptionalChaining = code.includes('?.') || code.includes('|| []') || code.includes('?? []');
setIsTesting(false);
if (hasOptionalChaining) {
onFix(code);
} else {
onError();
}
};
return (
<ActivityGameCard
type={activity.type}
title={activity.title}
question="Encontre e corrija o bug no código"
actions={
<>
<GameButton
onClick={handleTest}
disabled={isTesting}
variant="primary"
icon={<Play className="w-5 h-5" />}
>
{isTesting ? 'Testando...' : 'Testar'}
ConstrainedEdit function · typescript · L15-L73 (59 LOC)src/components/activity/ConstrainedEdit.tsx
export function ConstrainedEdit({ activity, onSubmit }: ConstrainedEditProps) {
const initialCode = useMemo(() => {
const file = initialProjectFiles.find(f => f.path === activity.targetFiles[0]);
return file?.content || '';
}, [activity.targetFiles]);
const [code, setCode] = useState(initialCode);
return (
<ActivityGameCard
type={activity.type}
title={activity.title}
question="Melhore o código nas regiões destacadas"
actions={
<GameButton onClick={() => onSubmit(code)} variant="primary" icon={<Check className="w-5 h-5" />}>
Aplicar
</GameButton>
}
>
<div className="flex-1 flex flex-col gap-4 overflow-hidden">
{/* Editable Regions Hints */}
{activity.editableRegions && (
<motion.div
className="flex flex-wrap gap-2 shrink-0"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
>
{activity.editableRegions.mapDecisionFork function · typescript · L14-L58 (45 LOC)src/components/activity/DecisionFork.tsx
export function DecisionFork({ activity, onDecide }: DecisionForkProps) {
const [selectedOption, setSelectedOption] = useState<string | null>(null);
const [isConfirming, setIsConfirming] = useState(false);
const handleConfirm = () => {
if (selectedOption) {
setIsConfirming(true);
setTimeout(() => {
onDecide(selectedOption);
}, 500);
}
};
return (
<ActivityGameCard
type={activity.type}
title={activity.title}
question="Qual abordagem você prefere?"
actions={
<GameButton
onClick={handleConfirm}
disabled={!selectedOption || isConfirming}
variant="primary"
icon={isConfirming ? <Sparkles className="w-5 h-5 animate-pulse" /> : undefined}
>
{isConfirming ? 'Aplicando...' : 'Confirmar'}
</GameButton>
}
>
{/* Options Grid */}
<div className="grid gap-4 md:grid-cols-3 h-full content-center">
{activity.options?.map((optiOptionCard function · typescript · L68-L113 (46 LOC)src/components/activity/DecisionFork.tsx
function OptionCard({ option, index, isSelected, onSelect, disabled }: OptionCardProps) {
return (
<motion.button
onClick={onSelect}
disabled={disabled}
className={cn(
"relative p-5 rounded-2xl border-2 text-left transition-all duration-200",
"bg-card hover:bg-card/80",
isSelected
? "border-primary ring-4 ring-primary/20 shadow-lg"
: "border-border hover:border-primary/40"
)}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 * index }}
whileHover={!disabled ? { y: -4, scale: 1.02 } : undefined}
whileTap={!disabled ? { scale: 0.98 } : undefined}
>
{/* Selection indicator */}
{isSelected && (
<motion.div
className="absolute top-3 right-3 w-7 h-7 rounded-full bg-primary flex items-center justify-center"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
>
<Check className="w-4 h-4ImageZoomModal function · typescript · L11-L64 (54 LOC)src/components/activity/ImageZoomModal.tsx
export function ImageZoomModal({ isOpen, imageUrl, caption, onClose }: ImageZoomModalProps) {
return (
<AnimatePresence>
{isOpen && (
<motion.div
className="fixed inset-0 z-50 flex items-center justify-center p-6"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
>
{/* Backdrop */}
<motion.div
className="absolute inset-0 bg-black/90 backdrop-blur-md"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
{/* Modal Content */}
<motion.div
className="relative max-w-[90vw] max-h-[90vh]"
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.9, opacity: 0 }}
onClick={(e) => e.stopPropagation()}
>
{/* Close Button */}
<button
Generated by Repobility's multi-pass static-analysis pipeline (https://repobility.com)
QualityReview function · typescript · L16-L106 (91 LOC)src/components/activity/QualityReview.tsx
export function QualityReview({ activity, onApprove, onRegenerate, onEdit }: QualityReviewProps) {
const [isEditing, setIsEditing] = useState(false);
const [code, setCode] = useState(activity.aiGeneratedCode || '');
const [showWarning, setShowWarning] = useState(false);
const handleApprove = () => {
if (activity.expectedIssues && activity.expectedIssues.length > 0) {
setShowWarning(true);
}
onApprove();
};
const handleEdit = () => {
setIsEditing(true);
};
const handleSaveEdit = () => {
onEdit(code);
setIsEditing(false);
};
return (
<ActivityGameCard
type={activity.type}
title={activity.title}
question={isEditing ? "Salve suas edições quando terminar" : "O código está pronto para produção?"}
actions={
isEditing ? (
<>
<GameButton onClick={handleSaveEdit} variant="primary" icon={<Check className="w-5 h-5" />}>
Salvar
</GameButton>
<GameButtVideoChallenge function · typescript · L16-L137 (122 LOC)src/components/activity/VideoChallenge.tsx
export function VideoChallenge({ activity, onComplete }: VideoChallengeProps) {
const [watched, setWatched] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const [code, setCode] = useState(activity.aiGeneratedCode || '');
const videoConfig = activity.videoConfig;
if (!videoConfig) {
return <div>Video configuration missing</div>;
}
const handleVideoClose = () => {
setIsModalOpen(false);
setWatched(true);
};
const thumbnailUrl = videoConfig.thumbnailUrl ||
`https://img.youtube.com/vi/${videoConfig.youtubeId}/maxresdefault.jpg`;
return (
<ActivityGameCard
type={ActivityType.VIDEO_CHALLENGE}
title={activity.title}
question="Aplique o que você aprendeu no vídeo"
actions={
<GameButton
variant="primary"
onClick={() => onComplete(code)}
disabled={!watched}
>
✓ APLICAR
</GameButton>
}
>
<div className="flex flex-col gapVideoModal function · typescript · L11-L66 (56 LOC)src/components/activity/VideoModal.tsx
export function VideoModal({ isOpen, videoId, title, onClose }: VideoModalProps) {
return (
<AnimatePresence>
{isOpen && (
<motion.div
className="fixed inset-0 z-50 flex items-center justify-center p-6"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
>
{/* Backdrop */}
<motion.div
className="absolute inset-0 bg-black/90 backdrop-blur-md"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
{/* Modal Content */}
<motion.div
className="relative w-full max-w-4xl bg-card rounded-2xl overflow-hidden"
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.9, opacity: 0 }}
onClick={(e) => e.stopPropagation()}
>
{/* Close Button */}
VisualImplementation function · typescript · L16-L91 (76 LOC)src/components/activity/VisualImplementation.tsx
export function VisualImplementation({ activity, onComplete }: VisualImplementationProps) {
const [isZoomed, setIsZoomed] = useState(false);
const [code, setCode] = useState(activity.aiGeneratedCode || '');
const visualConfig = activity.visualConfig;
if (!visualConfig) {
return <div>Visual configuration missing</div>;
}
return (
<ActivityGameCard
type={ActivityType.VISUAL_IMPLEMENTATION}
title={activity.title}
question="Aproxime seu código do design de referência"
actions={
<GameButton
variant="primary"
onClick={() => onComplete(code)}
>
👁️ COMPARAR
</GameButton>
}
>
<div className="flex flex-col gap-4 flex-1 overflow-hidden">
{/* Reference Image */}
<div className="shrink-0">
<p className="text-sm text-muted-foreground mb-2 font-semibold">Referência</p>
<motion.div
className="relative rounded-xl overflow-hidden cursor-zoAIResponse function · typescript · L9-L84 (76 LOC)src/components/ai/AIResponse.tsx
export function AIResponse({ text, isStreaming }: AIResponseProps) {
if (!text && !isStreaming) return null;
// Parse markdown-like syntax for code blocks
const renderContent = (content: string) => {
const parts = content.split(/(```[\s\S]*?```)/g);
return parts.map((part, i) => {
if (part.startsWith('```') && part.endsWith('```')) {
const lines = part.slice(3, -3).split('\n');
const language = lines[0].trim();
const code = lines.slice(1).join('\n');
return (
<div key={i} className="my-3 rounded-lg overflow-hidden bg-[#1e1e1e] border border-border">
{language && (
<div className="px-3 py-1.5 bg-muted/50 border-b border-border text-xs text-muted-foreground">
{language}
</div>
)}
<pre className="p-4 overflow-x-auto">
<code className="text-sm font-mono text-foreground">{code}</code>
</pre>
</div>
ChooseCard function · typescript · L14-L50 (37 LOC)src/components/atoms/ChooseCard/ChooseCard.tsx
export function ChooseCard({ choice, index, isSelected, onSelect, disabled }: ChooseCardProps) {
return (
<motion.button
onClick={onSelect}
disabled={disabled}
className={cn(
"relative p-4 rounded-2xl border-2 flex flex-col items-center justify-start transition-all duration-200",
"bg-card hover:bg-card/80",
isSelected
? "border-primary ring-4 ring-primary/20 shadow-lg"
: "border-border hover:border-primary/40"
)}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 * index }}
>
{/* Selection indicator */}
{isSelected && (
<motion.div
className="absolute top-3 right-3 w-7 h-7 rounded-full bg-primary flex items-center justify-center"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
>
<Check className="w-4 h-4 text-primary-foreground" />
CodeEditor function · typescript · L12-L102 (91 LOC)src/components/editor/CodeEditor.tsx
export function CodeEditor({
value,
onChange,
language = 'typescript',
readOnly = false,
highlightLines = [],
fontSize = 14,
}: CodeEditorProps) {
const handleEditorChange = (newValue: string | undefined) => {
if (onChange && newValue !== undefined) {
onChange(newValue);
}
};
return (
<div className="editor-container h-full" style={{ minHeight: '300px' }}>
<Editor
height="100%"
language={language === 'typescript' ? 'typescript' : language}
value={value}
onChange={handleEditorChange}
theme="vs-dark"
options={{
readOnly,
minimap: { enabled: false },
fontSize,
lineHeight: fontSize * 1.6,
fontFamily: "'JetBrains Mono', monospace",
fontLigatures: true,
lineNumbers: 'on',
scrollBeyondLastLine: false,
wordWrap: 'on',
padding: { top: 16, bottom: 16 },
renderLineHighlight: 'all',
cursorBbuildTree function · typescript · L20-L60 (41 LOC)src/components/editor/FileTree.tsx
function buildTree(files: ProjectFile[]): TreeNode[] {
const root: TreeNode[] = [];
const pathMap = new Map<string, TreeNode>();
// Sort files by path
const sortedFiles = [...files].sort((a, b) => a.path.localeCompare(b.path));
sortedFiles.forEach(file => {
const parts = file.path.split('/');
let currentPath = '';
parts.forEach((part, index) => {
const isLast = index === parts.length - 1;
const parentPath = currentPath;
currentPath = currentPath ? `${currentPath}/${part}` : part;
if (!pathMap.has(currentPath)) {
const node: TreeNode = {
name: part,
path: currentPath,
isDirectory: !isLast,
children: [],
file: isLast ? file : undefined,
};
pathMap.set(currentPath, node);
if (parentPath) {
const parent = pathMap.get(parentPath);
if (parent) {
parent.children.push(node);
}
} else {
root.push(node);
Want this analysis on your repo? https://repobility.com/scan/
FileTree function · typescript · L62-L78 (17 LOC)src/components/editor/FileTree.tsx
export function FileTree({ files, onFileSelect, selectedFile }: FileTreeProps) {
const tree = buildTree(files);
return (
<div className="text-sm">
{tree.map(node => (
<TreeNodeComponent
key={node.path}
node={node}
onFileSelect={onFileSelect}
selectedFile={selectedFile}
depth={0}
/>
))}
</div>
);
}TreeNodeComponent function · typescript · L87-L146 (60 LOC)src/components/editor/FileTree.tsx
function TreeNodeComponent({ node, onFileSelect, selectedFile, depth }: TreeNodeComponentProps) {
const [isOpen, setIsOpen] = useState(true);
const isSelected = node.file?.path === selectedFile;
if (node.isDirectory) {
return (
<div>
<button
onClick={() => setIsOpen(!isOpen)}
className={cn(
"flex items-center gap-1.5 w-full px-2 py-1 hover:bg-muted/50 rounded text-left",
"text-muted-foreground hover:text-foreground transition-colors"
)}
style={{ paddingLeft: `${depth * 12 + 8}px` }}
>
{isOpen ? (
<ChevronDown className="w-3.5 h-3.5 shrink-0" />
) : (
<ChevronRight className="w-3.5 h-3.5 shrink-0" />
)}
{isOpen ? (
<FolderOpen className="w-4 h-4 text-primary shrink-0" />
) : (
<Folder className="w-4 h-4 text-primary shrink-0" />
)}
<span>{node.name}</span>
</button>
ActivityGameCard function · typescript · L52-L98 (47 LOC)src/components/game/ActivityGameCard.tsx
export function ActivityGameCard({ type, title, question, children, actions }: ActivityGameCardProps) {
const config = typeConfig[type];
const Icon = config.icon;
return (
<motion.div
className="flex flex-col h-full"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
{/* Header with type and title */}
<div className="text-center mb-4 shrink-0">
<motion.div
className={`inline-flex items-center gap-2 ${config.color} mb-2`}
initial={{ scale: 0.9 }}
animate={{ scale: 1 }}
>
<Icon className="w-5 h-5" />
<span className="font-display text-sm font-bold tracking-widest">
{config.label}
</span>
</motion.div>
<h1 className="font-display text-2xl font-black text-foreground">
{title}
</h1>
</div>
{/* Main content area - explicit flex-1 with overflow */}
<div classAIHistoryButton function · typescript · L9-L21 (13 LOC)src/components/game/AIHistoryButton.tsx
export function AIHistoryButton({ messageCount, onClick }: AIHistoryButtonProps) {
return (
<motion.button
onClick={onClick}
className="flex items-center gap-2 px-3 py-2 rounded-full bg-card border border-border hover:bg-muted transition-colors shadow-lg"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<Bot className="w-4 h-4 text-muted-foreground" />
<span className="text-sm font-bold text-foreground">{messageCount}</span>
</motion.button>
);
}AIHistoryDrawer function · typescript · L14-L164 (151 LOC)src/components/game/AIHistoryDrawer.tsx
export function AIHistoryDrawer({ isOpen, onClose, messages }: AIHistoryDrawerProps) {
const [expandedId, setExpandedId] = useState<string | null>(null);
const toggleExpand = (id: string) => {
setExpandedId(prev => prev === id ? null : id);
};
// Parse markdown-like syntax
const renderMessage = (content: string) => {
const lines = content.split('\n');
return lines.map((line, i) => {
const parts = line.split(/(\*\*.*?\*\*)/g);
const rendered = parts.map((part, j) => {
if (part.startsWith('**') && part.endsWith('**')) {
return <strong key={j} className="font-semibold text-primary">{part.slice(2, -2)}</strong>;
}
const codeParts = part.split(/(`[^`]+`)/g);
return codeParts.map((cp, k) => {
if (cp.startsWith('`') && cp.endsWith('`')) {
return (
<code key={k} className="px-1 py-0.5 rounded bg-muted text-primary text-xs font-mono">
{cp.slice(1, -1)}
CelebrationOverlay function · typescript · L13-L114 (102 LOC)src/components/game/CelebrationOverlay.tsx
export function CelebrationOverlay({ isVisible, xpEarned, message, onContinue }: CelebrationOverlayProps) {
// Play celebration sound effect (optional)
useEffect(() => {
if (isVisible) {
// Could add sound here
}
}, [isVisible]);
return (
<AnimatePresence>
{isVisible && (
<motion.div
className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
{/* Confetti particles */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
{[...Array(20)].map((_, i) => (
<motion.div
key={i}
className="absolute w-3 h-3 rounded-full"
style={{
left: `${Math.random() * 100}%`,
background: ['#f39325', '#22c55e', '#fbbf24', '#ef4444', '#3b82f6'][i % 5],
GameButton function · typescript · L22-L53 (32 LOC)src/components/game/GameButton.tsx
export function GameButton({
className,
variant = 'primary',
icon,
children,
disabled,
onClick,
type = 'button'
}: GameButtonProps) {
return (
<motion.button
type={type}
onClick={onClick}
className={cn(
"inline-flex items-center justify-center gap-2",
"px-6 py-3 min-w-[140px]",
"font-display font-bold text-base uppercase tracking-wide",
"rounded-2xl",
"transition-all duration-100",
"disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none",
variantStyles[variant],
className
)}
whileHover={!disabled ? { y: -2 } : undefined}
whileTap={!disabled ? { y: 2 } : undefined}
disabled={disabled}
>
{icon}
{children}
</motion.button>
);
}GameHeader function · typescript · L11-L64 (54 LOC)src/components/game/GameHeader.tsx
export function GameHeader({ lives, streak, xp }: GameHeaderProps) {
return (
<header className="shrink-0 h-14 px-4 flex items-center justify-between border-b border-border bg-card/50">
{/* Logo */}
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-lg bg-primary flex items-center justify-center">
<span className="text-lg font-black text-primary-foreground">i</span>
</div>
<span className="font-display font-bold text-foreground hidden sm:block">iterate</span>
</div>
{/* Stats */}
<div className="flex items-center gap-4">
{/* Lives */}
<motion.div
className="flex items-center gap-1"
initial={{ scale: 0.9 }}
animate={{ scale: 1 }}
>
{[...Array(3)].map((_, i) => (
<motion.div
key={i}
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ delay: i * 0.1 }}
Open data scored by Repobility · https://repobility.com
LessonCompleteScreen function · typescript · L21-L219 (199 LOC)src/components/game/LessonCompleteScreen.tsx
export function LessonCompleteScreen({
lessonTitle,
stats,
decisions,
githubRepo,
onGoHome,
onShare,
}: LessonCompleteScreenProps) {
const [copied, setCopied] = useState(false);
const handleCopy = async () => {
await navigator.clipboard.writeText(`https://${githubRepo}`);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
const handleOpenRepo = () => {
window.open(`https://${githubRepo}`, '_blank');
};
return (
<div className="min-h-screen bg-background flex items-center justify-center p-6">
<motion.div
className="max-w-2xl w-full"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
>
{/* Celebration Header */}
<motion.div
className="text-center mb-8"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ delay: 0.2, type: 'spring', damping: 10 }}
>
<span className="text-7xl">🎉</span>
</motion.diProgressPills function · typescript · L13-L68 (56 LOC)src/components/game/ProgressPills.tsx
export function ProgressPills({ activities, currentIndex, onActivityClick }: ProgressPillsProps) {
return (
<div className="flex items-center justify-center gap-2 py-4">
{activities.map((activity, index) => {
const isCompleted = activity.status === ActivityStatus.COMPLETED;
const isCurrent = index === currentIndex;
const isLocked = activity.status === ActivityStatus.LOCKED;
return (
<div key={activity.id} className="flex items-center">
{/* Connector line */}
{index > 0 && (
<div
className={cn(
"w-8 h-1 rounded-full -mr-1",
index <= currentIndex ? "bg-primary" : "bg-muted"
)}
/>
)}
<motion.button
onClick={() => !isLocked && onActivityClick(index)}
disabled={isLocked}
className={cn(
"relative w-12 h-12 rounded-full flex iResultModal function · typescript · L14-L162 (149 LOC)src/components/game/ResultModal.tsx
export function ResultModal({
isOpen,
isSuccess,
xpEarned = 25,
activityTitle,
aiFeedback,
onContinue,
isLessonComplete = false,
}: ResultModalProps) {
// Parse markdown-like syntax
const renderFeedback = (content: string) => {
const lines = content.split('\n');
return lines.map((line, i) => {
// Handle bold text
const parts = line.split(/(\*\*.*?\*\*)/g);
const rendered = parts.map((part, j) => {
if (part.startsWith('**') && part.endsWith('**')) {
return <strong key={j} className="font-semibold text-primary">{part.slice(2, -2)}</strong>;
}
// Handle inline code
const codeParts = part.split(/(`[^`]+`)/g);
return codeParts.map((cp, k) => {
if (cp.startsWith('`') && cp.endsWith('`')) {
return (
<code key={k} className="px-1.5 py-0.5 rounded bg-muted text-primary text-xs font-mono">
{cp.slice(1, -1)}
</code>
);
AppShell function · typescript · L17-L69 (53 LOC)src/components/layout/AppShell.tsx
export function AppShell({
children,
projectName,
projectStatus,
currentActivity,
totalActivities,
trackTitle,
onBack,
activityButtons,
}: AppShellProps) {
const progress = (currentActivity / totalActivities) * 100;
return (
<div className="h-screen bg-background flex flex-col overflow-hidden">
<Header
projectName={projectName}
projectStatus={projectStatus}
trackTitle={trackTitle}
onBack={onBack}
/>
<motion.main
className="flex-1 flex flex-col overflow-hidden"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
{/* Gamified Progress Bar */}
<div className="shrink-0 px-4 py-3 bg-card/30 border-b border-border">
<div className="flex items-center gap-3">
{/* Activity Buttons */}
{activityButtons}
{/* Progress Bar */}
<div className="flex-1 h-5 bg-muted rHeader function · typescript · L33-L80 (48 LOC)src/components/layout/Header.tsx
export function Header({
projectName,
projectStatus,
trackTitle,
onBack,
}: HeaderProps) {
const status = statusConfig[projectStatus];
return (
<header className="shrink-0 z-50 border-b border-border bg-background">
<div className="flex items-center justify-between h-12 px-4">
{/* Left section - Logo */}
<div className="flex items-center gap-3">
{onBack && (
<Button
variant="ghost"
size="icon"
onClick={onBack}
className="h-8 w-8"
>
<ArrowLeft className="h-4 w-4" />
</Button>
)}
<div className="flex items-center gap-2">
<span className="text-lg font-bold text-gradient">iterate</span>
<span className="text-xs text-muted-foreground">by DevFellowship</span>
</div>
</div>
{/* Center - Track Title */}
{trackTitle && (
<div className="absoReadAndChoose function · typescript · L18-L67 (50 LOC)src/components/molecules/ReadAndChoose/ReadAndChoose.tsx
export function ReadAndChoose({ activity, onDecide }: ReadAndChooseProps) {
const code = activity.aiGeneratedCode || '';
const { selectedOption, setSelectedOption, isConfirming, handleConfirm} =
useReadAndChoose({ onDecide});
return (
<ActivityGameCard
type={activity.type}
title={activity.title}
question={activity.objective || ''}
actions={
<GameButton
onClick={handleConfirm}
disabled={!selectedOption || isConfirming}
variant="primary"
icon={isConfirming ? <Sparkles className="w-5 h-5 animate-pulse" /> : undefined}
>
{isConfirming ? 'Aplicando...' : 'Confirmar'}
</GameButton>
}
>
<div className="flex-1 flex flex-col gap-4 pb-4 overflow-hidden">
{/* Code Snippet (Read-only) */}
<div className="shrink-0 rounded-xl overflow-hidden border border-border">
<CodeEditor
value={code}
language="typescript"
rErrorBoundary class · typescript · L18-L82 (65 LOC)src/components/observability/ErrorBoundary.tsx
export class ErrorBoundary extends Component<
ErrorBoundaryProps,
ErrorBoundaryState
> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
const tracer = trace.getTracer("@dfl/observability");
tracer.startActiveSpan("react-error-boundary", (span) => {
span.setAttribute("error.message", error.message);
span.setAttribute("error.stack", error.stack ?? "");
span.setAttribute(
"error.component_stack",
errorInfo.componentStack ?? "",
);
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message,
});
span.recordException(error);
span.end();
});
}
private handleReload = (): void => {
window.location.reload();
};
render(): ReactNode {
constructor method · typescript · L22-L25 (4 LOC)src/components/observability/ErrorBoundary.tsx
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false, error: null };
}Repobility · code-quality intelligence platform · https://repobility.com
getDerivedStateFromError method · typescript · L27-L29 (3 LOC)src/components/observability/ErrorBoundary.tsx
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error };
}componentDidCatch method · typescript · L31-L50 (20 LOC)src/components/observability/ErrorBoundary.tsx
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
const tracer = trace.getTracer("@dfl/observability");
tracer.startActiveSpan("react-error-boundary", (span) => {
span.setAttribute("error.message", error.message);
span.setAttribute("error.stack", error.stack ?? "");
span.setAttribute(
"error.component_stack",
errorInfo.componentStack ?? "",
);
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message,
});
span.recordException(error);
span.end();
});
}render method · typescript · L56-L81 (26 LOC)src/components/observability/ErrorBoundary.tsx
render(): ReactNode {
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback;
}
return (
<div className="flex min-h-[200px] flex-col items-center justify-center gap-4 rounded-lg border border-red-200 bg-red-50 p-8 text-center dark:border-red-900 dark:bg-red-950">
<h2 className="text-lg font-semibold text-red-800 dark:text-red-200">
Something went wrong
</h2>
<p className="max-w-md text-sm text-red-600 dark:text-red-400">
An unexpected error occurred. Please try reloading the page.
</p>
<button
onClick={this.handleReload}
className="rounded-md bg-red-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:bg-red-700 dark:hover:bg-red-600"
>
Reload page
</button>
</div>
);
}
useQueryErrorReporter function · typescript · L12-L68 (57 LOC)src/components/observability/hooks/useQueryErrorReporter.ts
export function useQueryErrorReporter(): void {
let queryClient: QueryClient | null = null;
try {
// useQueryClient internally calls useContext (preserving hook order)
// then throws if no provider — the try-catch is safe here.
// eslint-disable-next-line react-hooks/rules-of-hooks
queryClient = useQueryClient();
} catch {
// Not inside a QueryClientProvider — query error reporting disabled
}
useEffect(() => {
if (!queryClient) return;
const tracer = trace.getTracer("@dfl/observability");
const unsubscribe = queryClient.getQueryCache().subscribe((event) => {
if (event.type === "updated" && event.action.type === "error") {
const query = event.query;
const error = query.state.error;
tracer.startActiveSpan("react-query-error", (span) => {
const dynamicAttrs = getDynamicAttributes();
for (const [key, value] of Object.entries(dynamicAttrs)) {
span.setAttribute(key, value);
}useWebVitals function · typescript · L12-L56 (45 LOC)src/components/observability/hooks/useWebVitals.ts
export function useWebVitals(): void {
const initialized = useRef(false);
useEffect(() => {
if (initialized.current) return;
initialized.current = true;
const meter = metrics.getMeter("web-vitals");
const lcpHistogram = meter.createHistogram("web_vital.lcp", {
description: "Largest Contentful Paint (ms)",
unit: "ms",
});
const clsHistogram = meter.createHistogram("web_vital.cls", {
description: "Cumulative Layout Shift",
unit: "",
});
const inpHistogram = meter.createHistogram("web_vital.inp", {
description: "Interaction to Next Paint (ms)",
unit: "ms",
});
const ttfbHistogram = meter.createHistogram("web_vital.ttfb", {
description: "Time to First Byte (ms)",
unit: "ms",
});
const report = (
histogram: ReturnType<typeof meter.createHistogram>,
metric: Metric,
) => {
histogram.record(metric.value, {
"metric.id": metric.id,
"metric.rating": metric.instrumentFetch function · typescript · L14-L82 (69 LOC)src/components/observability/lib/fetch-instrumentation.ts
export function instrumentFetch(tracer: Tracer): void {
if (_originalFetch) {
// Already instrumented — avoid double-wrapping.
return;
}
_originalFetch = window.fetch.bind(window);
window.fetch = async (
input: RequestInfo | URL,
init?: RequestInit,
): Promise<Response> => {
const url =
input instanceof Request ? input.url : input instanceof URL ? input.href : input;
const method = init?.method ?? (input instanceof Request ? input.method : "GET");
// Filter out OTel collector traffic to avoid infinite loops.
const collectorUrl = (globalThis as Record<string, unknown>).__otelCollectorUrl;
if (typeof collectorUrl === "string" && url.startsWith(collectorUrl)) {
return _originalFetch!(input, init);
}
const dynamicAttrs = getDynamicAttributes();
return tracer.startActiveSpan(
`HTTP ${method.toUpperCase()}`,
{
kind: SpanKind.CLIENT,
attributes: {
"http.method": method.toUpperCase(uninstrumentFetch function · typescript · L87-L92 (6 LOC)src/components/observability/lib/fetch-instrumentation.ts
export function uninstrumentFetch(): void {
if (_originalFetch) {
window.fetch = _originalFetch;
_originalFetch = null;
}
}initOtelBrowser function · typescript · L27-L78 (52 LOC)src/components/observability/lib/otel-init.ts
export function initOtelBrowser(config: OtelBrowserConfig): OtelProviders {
const { serviceName, serviceVersion, collectorUrl, apiKey } = config;
const headers: Record<string, string> = {};
if (apiKey) {
headers["x-api-key"] = apiKey;
}
const staticAttributes = getStaticAttributes();
const resource = resourceFromAttributes({
"service.name": serviceName,
"service.version": serviceVersion,
...staticAttributes,
});
// --- Traces ---
const traceExporter = new OTLPTraceExporter({
url: `${collectorUrl}/v1/traces`,
headers,
});
const tracerProvider = new WebTracerProvider({
resource,
spanProcessors: [new BatchSpanProcessor(traceExporter)],
});
tracerProvider.register();
// --- Metrics ---
const metricExporter = new OTLPMetricExporter({
url: `${collectorUrl}/v1/metrics`,
headers,
});
const metricReader = new PeriodicExportingMetricReader({
exporter: metricExporter,
exportIntervalMillis: 60_000,
});
cGenerated by Repobility's multi-pass static-analysis pipeline (https://repobility.com)
shutdownOtel function · typescript · L80-L93 (14 LOC)src/components/observability/lib/otel-init.ts
export function shutdownOtel(providers: OtelProviders): void {
const { tracerProvider, meterProvider } = providers;
trace.disable();
metrics.disable();
tracerProvider.shutdown().catch((err) => {
console.warn("[observability] tracer shutdown error:", err);
});
meterProvider.shutdown().catch((err) => {
console.warn("[observability] meter shutdown error:", err);
});
}setUserId function · typescript · L13-L15 (3 LOC)src/components/observability/lib/resource-attributes.ts
export function setUserId(id: string): void {
_userId = id;
}setUserRole function · typescript · L17-L19 (3 LOC)src/components/observability/lib/resource-attributes.ts
export function setUserRole(role: string): void {
_userRole = role;
}getSessionId function · typescript · L22-L30 (9 LOC)src/components/observability/lib/resource-attributes.ts
function getSessionId(): string {
const key = "__otel_session_id";
let id = sessionStorage.getItem(key);
if (!id) {
id = crypto.randomUUID();
sessionStorage.setItem(key, id);
}
return id;
}detectBrowser function · typescript · L38-L59 (22 LOC)src/components/observability/lib/resource-attributes.ts
function detectBrowser(): BrowserInfo {
const ua = navigator.userAgent;
if (ua.includes("Firefox/")) {
const match = ua.match(/Firefox\/([\d.]+)/);
return { name: "Firefox", version: match?.[1] ?? "unknown" };
}
if (ua.includes("Edg/")) {
const match = ua.match(/Edg\/([\d.]+)/);
return { name: "Edge", version: match?.[1] ?? "unknown" };
}
if (ua.includes("Chrome/") && !ua.includes("Edg/")) {
const match = ua.match(/Chrome\/([\d.]+)/);
return { name: "Chrome", version: match?.[1] ?? "unknown" };
}
if (ua.includes("Safari/") && !ua.includes("Chrome/")) {
const match = ua.match(/Version\/([\d.]+)/);
return { name: "Safari", version: match?.[1] ?? "unknown" };
}
return { name: "unknown", version: "unknown" };
}detectOS function · typescript · L62-L70 (9 LOC)src/components/observability/lib/resource-attributes.ts
function detectOS(): string {
const ua = navigator.userAgent;
if (ua.includes("Win")) return "Windows";
if (ua.includes("Mac")) return "macOS";
if (ua.includes("Linux")) return "Linux";
if (ua.includes("Android")) return "Android";
if (/iPhone|iPad|iPod/.test(ua)) return "iOS";
return "unknown";
}detectDeviceType function · typescript · L73-L78 (6 LOC)src/components/observability/lib/resource-attributes.ts
function detectDeviceType(): string {
const width = window.innerWidth;
if (width < 768) return "mobile";
if (width < 1024) return "tablet";
return "desktop";
}getEffectiveConnectionType function · typescript · L81-L86 (6 LOC)src/components/observability/lib/resource-attributes.ts
function getEffectiveConnectionType(): string | undefined {
const nav = navigator as Navigator & {
connection?: { effectiveType?: string };
};
return nav.connection?.effectiveType;
}Want this analysis on your repo? https://repobility.com/scan/
getStaticAttributes function · typescript · L94-L115 (22 LOC)src/components/observability/lib/resource-attributes.ts
export function getStaticAttributes(): Record<string, string> {
const browser = detectBrowser();
const attrs: Record<string, string> = {
"browser.name": browser.name,
"browser.version": browser.version,
"os.type": detectOS(),
"device.type": detectDeviceType(),
"session.id": getSessionId(),
};
// Vite env-based app metadata
const appName = (import.meta as ImportMeta & { env: Record<string, string> })
.env?.VITE_APP_NAME;
const appVersion = (
import.meta as ImportMeta & { env: Record<string, string> }
).env?.VITE_APP_VERSION;
if (appName) attrs["app.name"] = appName;
if (appVersion) attrs["app.version"] = appVersion;
return attrs;
}getDynamicAttributes function · typescript · L121-L138 (18 LOC)src/components/observability/lib/resource-attributes.ts
export function getDynamicAttributes(): Record<string, string | number> {
const attrs: Record<string, string | number> = {
"viewport.width": window.innerWidth,
"viewport.height": window.innerHeight,
};
if (_userId) attrs["user.id"] = _userId;
if (_userRole) attrs["user.role"] = _userRole;
const connType = getEffectiveConnectionType();
if (connType) attrs["connection.effectiveType"] = connType;
// page.route is typically set by the ObservabilityProvider via React Router
const route = (globalThis as Record<string, unknown>).__otelCurrentRoute;
if (typeof route === "string") attrs["page.route"] = route;
return attrs;
}trackEvent function · typescript · L15-L41 (27 LOC)src/components/observability/lib/track-event.ts
export function trackEvent(
name: string,
attributes?: Record<string, string | number | boolean>,
): void {
const tracer = trace.getTracer("@dfl/observability");
tracer.startActiveSpan(name, (span) => {
const dynamicAttrs = getDynamicAttributes();
span.setAttribute("event.name", name);
// Attach dynamic resource attributes (viewport, user, route, etc.)
for (const [key, value] of Object.entries(dynamicAttrs)) {
span.setAttribute(key, value);
}
// Attach caller-provided attributes
if (attributes) {
for (const [key, value] of Object.entries(attributes)) {
span.setAttribute(key, value);
}
}
span.setStatus({ code: SpanStatusCode.OK });
span.end();
});
}page 1 / 2next ›