Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
run: npm ci

- name: Run unit and integration tests
run: npm test -- --run --reporter=verbose
run: npm test -- --run --reporter=verbose --pool=threads --poolOptions.threads.maxThreads=4
env:
CI: true

Expand Down Expand Up @@ -71,9 +71,27 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Detect Playwright version
id: playwright-version
run: |
PLAYWRIGHT_VERSION=$(node -p "require('./package.json').devDependencies?.playwright || require('./package.json').dependencies?.playwright || ''")
echo "version=${PLAYWRIGHT_VERSION}" >> $GITHUB_OUTPUT

- name: Cache Playwright browsers
uses: actions/cache@v4
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.playwright-version.outputs.version }}-${{ hashFiles('**/package-lock.json') }}

- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: npx playwright install --with-deps chromium

- name: Install Playwright deps only (if cached)
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: npx playwright install-deps chromium

- name: Start dev server
run: |
npm run dev &
Expand All @@ -93,7 +111,7 @@ jobs:
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}

- name: Run Playwright tests
run: npx playwright test --project=chromium
run: npx playwright test --project=chromium --workers=2
env:
CI: true
# Add any required env variables for e2e tests
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,9 @@ supabase/migrations/

# Documentation (internal notes)
documents/

# Playwright
/test-results/
/playwright-report/
/playwright/.auth/
tests/playwright/.auth/
8 changes: 4 additions & 4 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ export default defineConfig({
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Use multiple workers for faster CI - GitHub runners have 2 cores */
workers: process.env.CI ? 2 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:8080',

/* Slow down actions for debugging (set to 0 for normal speed) */
/* Slow down actions for debugging locally (disabled in CI for speed) */
launchOptions: {
slowMo: 500, // 500ms delay between actions - remove or set to 0 for fast tests
slowMo: process.env.CI ? 0 : 500, // 500ms delay locally for debugging, 0 in CI for speed
},

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
Expand Down
19 changes: 15 additions & 4 deletions src/components/ProgressRadar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,22 @@ const ProgressRadar = () => {

useEffect(() => {
const fetchTotals = async () => {
const { count, error } = await supabase
// Fetch all problems with their categories to filter out non-algorithm problems
const { data, error } = await supabase
.from("problems")
.select("id", { count: "exact", head: true });
if (!error && typeof count === "number") {
setTotalProblems(count);
.select("id, categories!inner(name)");

if (!error && data) {
// Filter out System Design and Data Structure Implementations (same logic as technical interview)
const algorithmProblems = data.filter((problem) => {
const categoryName = (problem.categories as { name?: string })?.name || "";
return (
categoryName !== "System Design" &&
categoryName !== "Data Structure Implementations" &&
!problem.id.startsWith("sd_")
);
});
setTotalProblems(algorithmProblems.length);
}
};
fetchTotals();
Expand Down
5 changes: 5 additions & 0 deletions src/components/coaching/overlay/CorrectCodeDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,13 @@ export const CorrectCodeDialog: React.FC<CorrectCodeDialogProps> = ({
};

const handleInsert = async () => {
logger.debug('[CorrectCodeDialog] handleInsert called');
logger.debug('[CorrectCodeDialog] onInsertCode exists:', !!onInsertCode);
logger.debug('[CorrectCodeDialog] isInserting:', isInserting);
if (onInsertCode) {
logger.debug('[CorrectCodeDialog] Calling onInsertCode...');
await onInsertCode();
logger.debug('[CorrectCodeDialog] onInsertCode completed');
onClose();
}
};
Expand Down
39 changes: 31 additions & 8 deletions src/components/flashcards/FlashcardDeckManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ import {
Star,
Clock,
Brain,
Play,
} from "lucide-react";
import { useFlashcards } from "@/hooks/useFlashcards";
import type { FlashcardDeck } from "@/types/api";
import { useNavigate } from "react-router-dom";
import { toast } from "sonner";
import { FlashcardReviewInterface } from "./FlashcardReviewInterface";

interface FlashcardDeckManagerProps {
userId: string;
Expand All @@ -43,6 +45,7 @@ export const FlashcardDeckManager = ({ userId }: FlashcardDeckManagerProps) => {
const navigate = useNavigate();
const {
flashcards,
dueCards,
removeFromFlashcards,
isRemovingFromFlashcards,
isLoading,
Expand All @@ -52,6 +55,7 @@ export const FlashcardDeckManager = ({ userId }: FlashcardDeckManagerProps) => {
const [masteryFilter, setMasteryFilter] = useState<string>("all");
const [selectedCard, setSelectedCard] = useState<FlashcardDeck | null>(null);
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [showReviewModal, setShowReviewModal] = useState(false);

// Filter flashcards based on search and mastery level
const filteredCards = flashcards.filter((card) => {
Expand Down Expand Up @@ -80,15 +84,15 @@ export const FlashcardDeckManager = ({ userId }: FlashcardDeckManagerProps) => {
const getMasteryLabel = (level: number, reviewCount: number) => {
// If card has been reviewed but still shows as level 0, it should be Learning
if (level === 0 && reviewCount > 0) {
return { label: "Learning", color: "bg-blue-100 text-blue-800" };
return { label: "Learning", color: "bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-200" };
}

switch (level) {
case 0: return { label: "New", color: "bg-gray-100 text-gray-800" };
case 1: return { label: "Learning", color: "bg-blue-100 text-blue-800" };
case 2: return { label: "Good", color: "bg-green-100 text-green-800" };
case 3: return { label: "Mastered", color: "bg-purple-100 text-purple-800" };
default: return { label: "Unknown", color: "bg-gray-100 text-gray-800" };
case 0: return { label: "New", color: "bg-gray-100 text-gray-800 dark:bg-gray-900/40 dark:text-gray-200" };
case 1: return { label: "Learning", color: "bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-200" };
case 2: return { label: "Good", color: "bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-200" };
case 3: return { label: "Mastered", color: "bg-purple-100 text-purple-800 dark:bg-purple-900/40 dark:text-purple-200" };
default: return { label: "Unknown", color: "bg-gray-100 text-gray-800 dark:bg-gray-900/40 dark:text-gray-200" };
}
};

Expand Down Expand Up @@ -135,7 +139,7 @@ export const FlashcardDeckManager = ({ userId }: FlashcardDeckManagerProps) => {
<Button
variant="ghost"
size="icon"
onClick={() => navigate("/profile")}
onClick={() => navigate(-1)}
className="hover:bg-secondary"
>
<ArrowLeft className="w-5 h-5" />
Expand All @@ -147,6 +151,18 @@ export const FlashcardDeckManager = ({ userId }: FlashcardDeckManagerProps) => {
</p>
</div>
</div>
{/* Start Review Button */}
<Button
onClick={() => setShowReviewModal(true)}
disabled={dueCards.length === 0}
className="gap-2"
>
<Play className="w-4 h-4" />
{dueCards.length > 0
? `Review ${dueCards.length} Card${dueCards.length > 1 ? 's' : ''}`
: 'No Cards Due'
}
</Button>
</div>

{/* Stats Summary */}
Expand Down Expand Up @@ -357,7 +373,14 @@ export const FlashcardDeckManager = ({ userId }: FlashcardDeckManagerProps) => {
</DialogFooter>
</DialogContent>
</Dialog>

{/* Flashcard Review Modal */}
<FlashcardReviewInterface
isOpen={showReviewModal}
onClose={() => setShowReviewModal(false)}
userId={userId}
/>
</div>
</div>
);
};
};
21 changes: 19 additions & 2 deletions src/features/admin/components/AdminDashboardNew.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Users, Activity, Search, ArrowLeft } from "lucide-react";
import { Users, Activity, Search, ArrowLeft, BarChart3 } from "lucide-react";
import { useNavigate } from "react-router-dom";

// Feature imports
import { useAdminDashboard } from "../hooks/useAdminDashboard";
import { useAdminAnalytics } from "../hooks/useAdminAnalytics";
import { useUserManagement } from "../hooks/useUserManagement";
import { OverviewCards } from "./OverviewCards";
import { UserCard } from "./UserCard";
import { ApiUsageTab } from "./ApiUsageTab";
import { AnalyticsTab } from "./AnalyticsTab";
import { SetLimitsDialog } from "./SetLimitsDialog";
import { SetCooldownDialog } from "./SetCooldownDialog";
import { AdminDashboardSkeleton } from "./AdminDashboardSkeleton";
Expand All @@ -33,12 +35,18 @@ export function AdminDashboardNew() {
refetchOverviewStats,
} = useAdminDashboard();

const { analytics, loading: analyticsLoading, refresh: refreshAnalytics } = useAdminAnalytics();

// User management hook
const handleUpdate = useCallback(() => {
refetchUserStats();
refetchOverviewStats();
}, [refetchUserStats, refetchOverviewStats]);

const handleRefresh = useCallback(async () => {
await Promise.all([refresh(), refreshAnalytics()]);
}, [refresh, refreshAnalytics]);

const {
grantPremium,
revokePremium,
Expand Down Expand Up @@ -120,7 +128,7 @@ export function AdminDashboardNew() {
{/* Header */}
<div className="flex items-center justify-between">
<h1 className="text-3xl font-bold">Admin Dashboard</h1>
<Button onClick={refresh} variant="outline">
<Button onClick={handleRefresh} variant="outline">
<Activity className="h-4 w-4 mr-2" />
Refresh Data
</Button>
Expand All @@ -140,6 +148,10 @@ export function AdminDashboardNew() {
<Activity className="h-4 w-4 mr-2" />
API Usage
</TabsTrigger>
<TabsTrigger value="analytics">
<BarChart3 className="h-4 w-4 mr-2" />
Analytics
</TabsTrigger>
</TabsList>

{/* Users Tab */}
Expand Down Expand Up @@ -188,6 +200,11 @@ export function AdminDashboardNew() {
<TabsContent value="usage" className="space-y-4">
<ApiUsageTab openRouterStats={openRouterStats} />
</TabsContent>

{/* Analytics Tab */}
<TabsContent value="analytics" className="space-y-4">
<AnalyticsTab stats={analytics} loading={analyticsLoading} />
</TabsContent>
</Tabs>

{/* Dialogs */}
Expand Down
23 changes: 19 additions & 4 deletions src/features/admin/components/AdminProblemManagement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ import {
SelectValue,
} from "@/components/ui/select";
import { useToast } from "@/hooks/use-toast";
import { Loader2, Plus, Pencil, Search } from "lucide-react";
import { Loader2, Plus, Pencil, Search, Download } from "lucide-react";
import { AdminProblemDialog, type Problem } from "@/features/admin/components/AdminProblemDialog";
import { LeetCodeImportDialog } from "@/features/admin/components/LeetCodeImportDialog";

interface Category {
id: string;
Expand All @@ -31,6 +32,7 @@ const AdminProblemManagement = () => {
const [categories, setCategories] = useState<Category[]>([]);
const [loading, setLoading] = useState(true);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [isImportDialogOpen, setIsImportDialogOpen] = useState(false);
const [selectedProblemId, setSelectedProblemId] = useState<string | null>(null);

// Filter state
Expand Down Expand Up @@ -98,9 +100,14 @@ const AdminProblemManagement = () => {
<div className="container mx-auto py-10 px-4">
<div className="flex justify-between items-center mb-8">
<h1 className="text-3xl font-bold">Problem Administration</h1>
<Button onClick={handleAddProblem}>
<Plus className="mr-2 h-4 w-4" /> Add Problem
</Button>
<div className="flex gap-2">
<Button variant="outline" onClick={() => setIsImportDialogOpen(true)}>
<Download className="mr-2 h-4 w-4" /> Import from LeetCode
</Button>
<Button onClick={handleAddProblem}>
<Plus className="mr-2 h-4 w-4" /> Add Problem
</Button>
</div>
</div>

{/* Filters */}
Expand Down Expand Up @@ -219,6 +226,14 @@ const AdminProblemManagement = () => {
}}
categories={categories}
/>

<LeetCodeImportDialog
open={isImportDialogOpen}
onOpenChange={setIsImportDialogOpen}
onImported={(importedProblem) => {
setProblems(prev => [importedProblem, ...prev]);
}}
/>
</div>
);
};
Expand Down
Loading
Loading