Skip to content

Commit 98e1382

Browse files
davidagustinclaude
andcommitted
feat: add JavaScript/TypeScript language toggle in drill mode
- Add toggle to include sibling language questions (JS ↔ TS) - Update problem selection to merge questions from both languages - Update category list to include categories from both languages - Enable seamless practice across similar languages Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 8c21b92 commit 98e1382

File tree

1 file changed

+70
-3
lines changed

1 file changed

+70
-3
lines changed

app/[language]/drill/page.tsx

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,15 @@ interface DrillConfig {
2121
questionCount: number;
2222
difficulty: Difficulty | 'all';
2323
selectedQuestionIds?: string[];
24+
includeSiblingLanguage?: boolean;
2425
}
2526

27+
// Define sibling language pairs
28+
const SIBLING_LANGUAGES: Partial<Record<LanguageId, LanguageId>> = {
29+
javascript: 'typescript',
30+
typescript: 'javascript',
31+
};
32+
2633
interface DrillState {
2734
currentIndex: number;
2835
answers: AnswerRecord[];
@@ -182,15 +189,36 @@ function formatTime(ms: number): string {
182189
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
183190
}
184191

185-
function getCategories(language: LanguageId): string[] {
192+
function getCategories(language: LanguageId, includeSibling = false): string[] {
186193
const problems = PROBLEMS_BY_LANGUAGE[language] || [];
187194
const categories = new Set(problems.map((p) => p.category));
188-
return Array.from(categories);
195+
196+
// Include sibling language categories if requested
197+
if (includeSibling) {
198+
const siblingLang = SIBLING_LANGUAGES[language];
199+
if (siblingLang) {
200+
const siblingProblems = PROBLEMS_BY_LANGUAGE[siblingLang] || [];
201+
for (const p of siblingProblems) {
202+
categories.add(p.category);
203+
}
204+
}
205+
}
206+
207+
return Array.from(categories).sort();
189208
}
190209

191210
function selectProblems(language: LanguageId, config: DrillConfig): Problem[] {
192211
let problems = PROBLEMS_BY_LANGUAGE[language] || [];
193212

213+
// Include sibling language problems if requested
214+
if (config.includeSiblingLanguage) {
215+
const siblingLang = SIBLING_LANGUAGES[language];
216+
if (siblingLang) {
217+
const siblingProblems = PROBLEMS_BY_LANGUAGE[siblingLang] || [];
218+
problems = [...problems, ...siblingProblems];
219+
}
220+
}
221+
194222
// If specific questions are selected, use only those
195223
if (config.selectedQuestionIds && config.selectedQuestionIds.length > 0) {
196224
const selectedSet = new Set(config.selectedQuestionIds);
@@ -302,7 +330,11 @@ interface SetupPhaseProps {
302330
}
303331

304332
function SetupPhase({ language, onStart }: SetupPhaseProps) {
305-
const categories = getCategories(language);
333+
const siblingLanguage = SIBLING_LANGUAGES[language];
334+
const [includeSibling, setIncludeSibling] = useState(false);
335+
336+
// Get categories based on whether sibling is included
337+
const categories = getCategories(language, includeSibling);
306338
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
307339
const [questionCount, setQuestionCount] = useState(10);
308340
const [difficulty, setDifficulty] = useState<Difficulty | 'all'>('all');
@@ -323,13 +355,20 @@ function SetupPhase({ language, onStart }: SetupPhaseProps) {
323355
difficulty,
324356
selectedQuestionIds:
325357
selectedQuestionIds.size > 0 ? Array.from(selectedQuestionIds) : undefined,
358+
includeSiblingLanguage: includeSibling,
326359
});
327360
};
328361

329362
// Get all problems for browsing (without the random selection limit)
330363
const allFilteredProblems = (() => {
331364
let problems = PROBLEMS_BY_LANGUAGE[language] || [];
332365

366+
// Include sibling language problems if toggled
367+
if (includeSibling && siblingLanguage) {
368+
const siblingProblems = PROBLEMS_BY_LANGUAGE[siblingLanguage] || [];
369+
problems = [...problems, ...siblingProblems];
370+
}
371+
333372
// Filter by categories
334373
if (selectedCategories.length > 0) {
335374
problems = problems.filter((p) => selectedCategories.includes(p.category));
@@ -397,6 +436,34 @@ function SetupPhase({ language, onStart }: SetupPhaseProps) {
397436
</div>
398437

399438
<div className="bg-zinc-900 rounded-xl p-6 shadow-sm border border-zinc-800 space-y-6">
439+
{/* Sibling Language Toggle (only for JS/TS) */}
440+
{siblingLanguage && (
441+
<div className="flex items-center justify-between p-4 bg-zinc-800/50 rounded-lg border border-zinc-700">
442+
<div>
443+
<span className="block text-sm font-medium text-zinc-300">
444+
Include {siblingLanguage.charAt(0).toUpperCase() + siblingLanguage.slice(1)}{' '}
445+
Questions
446+
</span>
447+
<span className="text-xs text-zinc-500">
448+
Practice both languages together since they share similar syntax
449+
</span>
450+
</div>
451+
<button
452+
type="button"
453+
onClick={() => setIncludeSibling(!includeSibling)}
454+
className={`relative w-14 h-8 rounded-full transition-colors duration-200 cursor-pointer ${
455+
includeSibling ? 'bg-blue-500' : 'bg-zinc-600'
456+
}`}
457+
>
458+
<div
459+
className={`absolute top-1 w-6 h-6 bg-white rounded-full transition-transform duration-200 ${
460+
includeSibling ? 'translate-x-7' : 'translate-x-1'
461+
}`}
462+
/>
463+
</button>
464+
</div>
465+
)}
466+
400467
{/* Categories */}
401468
<div>
402469
<span className="block text-sm font-medium text-zinc-300 mb-3">

0 commit comments

Comments
 (0)