diff --git a/src/App.tsx b/src/App.tsx index 24d7724..2552ebf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,7 +3,7 @@ import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; import ModeSelection from "./pages/ModeSelection"; import FrameworkSelection from "./pages/FrameworkSelection"; import InterviewPage from "./pages/InterviewPage"; -import QuizSelection from "./pages/QuizSelection"; +import QuizSelectionPage from "./pages/QuizSelectionPage"; import InteractiveQuizPage from "./pages/InteractiveQuizPage"; import { ErrorBoundary } from "./core/components/ErrorBoundary"; import { ThemeProvider } from "./contexts/ThemeContext"; @@ -19,7 +19,7 @@ export default function App() { } /> } /> } /> - } /> + } /> } /> } /> diff --git a/src/components/interactive-quiz/InteractiveQuizResults.tsx b/src/components/interactive-quiz/InteractiveQuizResults.tsx index fb72c36..a35eeed 100644 --- a/src/components/interactive-quiz/InteractiveQuizResults.tsx +++ b/src/components/interactive-quiz/InteractiveQuizResults.tsx @@ -12,6 +12,7 @@ import { PerformanceBreakdown } from "./results/PerformanceBreakdown"; import { Recommendations } from "./results/Recommendations"; import { AnswerAnalysis } from "./results/AnswerAnalysis"; import { ActionButtons } from "./results/ActionButtons"; +import { ResultsDashboard } from "./results/ResultsDashboard"; import { analyzeAnswers, getPerformanceMessage } from "../../utils/quizAnalysis"; import type { InteractiveQuizQuestion } from "../../types/interactive-quiz"; @@ -22,7 +23,7 @@ export default function InteractiveQuizResults({ }: QuizResultsProps) { const [animatedScore, setAnimatedScore] = useState(0); const [animatedPercentage, setAnimatedPercentage] = useState(0); - const [selectedTab, setSelectedTab] = useState<"overview" | "analysis">("overview"); + const [selectedTab, setSelectedTab] = useState<"overview" | "analysis" | "dashboard">("overview"); const { session, @@ -69,21 +70,35 @@ export default function InteractiveQuizResults({ }, [score, percentage]); return ( -
-
- {/* Header */} -
-

- Quiz Complete! 🎯 +
+ {/* Background Pattern */} +
+
+
+ +
+ {/* Enhanced Header */} +
+
+
+ +
+ + Quiz Complete + +
+ +

+ Excellent Work! 🎯

-

- Here's how you performed +

+ Here's your detailed performance analysis

- {/* Main Score Card */} -
-
+ {/* Enhanced Main Score Card */} +
+
{/* Score Display */} - {/* Stats Grid */} - - - {/* Tab Navigation */} - - - {/* Tab Content */} + {/* Enhanced Stats Grid */} +
+ +
+ + {/* Enhanced Tab Navigation */} +
+ +
+ + {/* Enhanced Tab Content */} {selectedTab === "overview" && ( -
+
)} {selectedTab === "analysis" && } + + {selectedTab === "dashboard" && }
- {/* Action Buttons */} -
+ {/* Enhanced Action Buttons */} +
diff --git a/src/components/interactive-quiz/results/ResultsDashboard.tsx b/src/components/interactive-quiz/results/ResultsDashboard.tsx new file mode 100644 index 0000000..6f3d84d --- /dev/null +++ b/src/components/interactive-quiz/results/ResultsDashboard.tsx @@ -0,0 +1,291 @@ +/** + * Comprehensive Results Dashboard Component + * Advanced charts and statistics for quiz results + */ + +import { useEffect, useState } from "react"; +import type { QuizResultsProps } from "../../../types/quiz-results"; + +interface DashboardProps { + result: QuizResultsProps["result"]; +} + +export function ResultsDashboard({ result }: DashboardProps) { + const [animatedStats, setAnimatedStats] = useState({ + accuracy: 0, + speed: 0, + consistency: 0, + }); + + const { correctAnswers, totalQuestions, timeSpent, breakdown } = result; + + useEffect(() => { + // Animate dashboard stats + const accuracy = Math.round((correctAnswers / totalQuestions) * 100); + const avgTimePerQuestion = Math.round(timeSpent / totalQuestions); + const consistency = Math.min(100, Math.max(0, 100 - (avgTimePerQuestion - 30) * 2)); + + const duration = 1500; + const steps = 30; + const stepDuration = duration / steps; + + let currentStep = 0; + const timer = setInterval(() => { + currentStep++; + const progress = currentStep / steps; + + setAnimatedStats({ + accuracy: Math.round(accuracy * progress), + speed: Math.round(avgTimePerQuestion * (1 - progress * 0.3)), // Animate down + consistency: Math.round(consistency * progress), + }); + + if (currentStep >= steps) { + clearInterval(timer); + } + }, stepDuration); + + return () => clearInterval(timer); + }, [correctAnswers, totalQuestions, timeSpent]); + + const getDifficultyStats = () => { + const stats = { + easy: { correct: 0, total: 0, percentage: 0 }, + medium: { correct: 0, total: 0, percentage: 0 }, + hard: { correct: 0, total: 0, percentage: 0 }, + }; + + // breakdown is an object with category keys, not an array + Object.values(breakdown).forEach((item: { correct: number; total: number }) => { + // For now, we'll distribute evenly across difficulties + // In a real implementation, you'd have difficulty info in the breakdown + const easyCount = Math.floor(item.total / 3); + const mediumCount = Math.floor(item.total / 3); + const hardCount = item.total - easyCount - mediumCount; + + const easyCorrect = Math.floor(item.correct / 3); + const mediumCorrect = Math.floor(item.correct / 3); + const hardCorrect = item.correct - easyCorrect - mediumCorrect; + + stats.easy.total += easyCount; + stats.easy.correct += easyCorrect; + stats.medium.total += mediumCount; + stats.medium.correct += mediumCorrect; + stats.hard.total += hardCount; + stats.hard.correct += hardCorrect; + }); + + // Calculate percentages + Object.keys(stats).forEach((key) => { + const stat = stats[key as keyof typeof stats]; + if (stat.total > 0) { + stat.percentage = Math.round((stat.correct / stat.total) * 100); + } + }); + + return stats; + }; + + const difficultyStats = getDifficultyStats(); + + return ( +
+ {/* Performance Metrics Grid */} +
+ {/* Accuracy Chart */} +
+
+ +
+
+

+ Accuracy +

+
+ +
+
+ +
+
{animatedStats.accuracy}%
+
+ {correctAnswers} of {totalQuestions} correct +
+
+ + {/* Mini progress bar */} +
+
+
+
+
+ + {/* Speed Chart */} +
+
+ +
+
+

Speed

+
+ +
+
+ +
+
{animatedStats.speed}s
+
Average per question
+
+ + {/* Speed indicator */} +
+ {[...Array(5)].map((_, i) => ( +
+ ))} +
+
+
+ + {/* Consistency Chart */} +
+
+ +
+
+

+ Consistency +

+
+ 🎯 +
+
+ +
+
{animatedStats.consistency}%
+
+ Performance stability +
+
+ + {/* Consistency dots */} +
+ {[...Array(5)].map((_, i) => ( +
+ ))} +
+
+
+
+ + {/* Difficulty Breakdown */} +
+

+ Performance by Difficulty +

+ +
+ {Object.entries(difficultyStats).map(([difficulty, stats]) => ( +
+
+
+
+ + {difficulty} Questions + +
+
+ {stats.correct}/{stats.total} ({stats.percentage}%) +
+
+ +
+
+
+
+ ))} +
+
+ + {/* Performance Insights */} +
+

+ Performance Insights +

+ +
+
+
+
+ 📊 +
+ Best Category +
+

+ { + Object.entries(breakdown).reduce((best, [category, stats]) => + stats.correct / stats.total > best[1].correct / best[1].total + ? [category, stats] + : best + )[0] + } +

+
+ +
+
+
+ 🎯 +
+ Focus Area +
+

+ { + Object.entries(breakdown).reduce((worst, [category, stats]) => + stats.correct / stats.total < worst[1].correct / worst[1].total + ? [category, stats] + : worst + )[0] + } +

+
+
+
+
+ ); +} diff --git a/src/components/interactive-quiz/results/ScoreDisplay.tsx b/src/components/interactive-quiz/results/ScoreDisplay.tsx index d47e7ec..c2f02d5 100644 --- a/src/components/interactive-quiz/results/ScoreDisplay.tsx +++ b/src/components/interactive-quiz/results/ScoreDisplay.tsx @@ -1,7 +1,9 @@ /** - * Score Display Component for Quiz Results + * Enhanced Score Display Component for Quiz Results + * Professional circular progress chart with smooth animations */ +import { useEffect, useState } from "react"; import type { ScoreDisplayProps, PerformanceMessage } from "../../../types/quiz-results"; interface ScoreDisplayPropsInternal extends ScoreDisplayProps { @@ -14,65 +16,146 @@ export function ScoreDisplay({ totalQuestions, performance, }: ScoreDisplayPropsInternal) { + const [isVisible, setIsVisible] = useState(false); + const [strokeDasharray, setStrokeDasharray] = useState("0 100"); + + useEffect(() => { + setIsVisible(true); + // Animate the stroke-dasharray for smooth progress animation + const circumference = 2 * Math.PI * 45; // radius = 45 + const progress = (animatedPercentage / 100) * circumference; + setStrokeDasharray(`${progress} ${circumference}`); + }, [animatedPercentage]); + const getScoreColor = () => { - if (animatedPercentage >= 75) return "text-green-600"; - if (animatedPercentage >= 60) return "text-yellow-600"; + if (animatedPercentage >= 80) return "text-emerald-600"; + if (animatedPercentage >= 70) return "text-blue-600"; + if (animatedPercentage >= 60) return "text-amber-600"; return "text-red-600"; }; const getScoreColorValue = () => { - if (animatedPercentage >= 75) return "#059669"; + if (animatedPercentage >= 80) return "#059669"; + if (animatedPercentage >= 70) return "#2563eb"; if (animatedPercentage >= 60) return "#d97706"; return "#dc2626"; }; + const getScoreGradient = () => { + if (animatedPercentage >= 80) return "from-emerald-500 to-green-600"; + if (animatedPercentage >= 70) return "from-blue-500 to-indigo-600"; + if (animatedPercentage >= 60) return "from-amber-500 to-orange-600"; + return "from-red-500 to-rose-600"; + }; + return ( -
- {/* Score Circle */} -
- {/* Background circle */} -
- - {/* Progress circle */} -
- - {/* Inner circle */} -
- - {/* Score text */} +
+ {/* Enhanced Score Circle with SVG */} +
+ {/* Background circle with subtle shadow */} +
+ + {/* SVG Progress Circle */} + + {/* Background circle */} + + + {/* Progress circle with gradient */} + + + + + + + + + + + {/* Inner content with enhanced styling */}
-
+ {/* Score percentage with enhanced typography */} +
{animatedPercentage}%
-
Score
+ + {/* Score label with subtle styling */} +
+ Score +
+ + {/* Subtle performance indicator */} +
- {/* Score breakdown */} -
-
+ {/* Enhanced Score breakdown with better typography */} +
+
{correctAnswers}
-
/
-
+
/
+
{totalQuestions}
- {/* Performance message */} -
+ {/* Enhanced Performance message with better styling */} +
{performance.message}
- {/* Description */} -

- {correctAnswers} out of {totalQuestions} questions correct -

+ {/* Enhanced Description with subtle background */} +
+

+ {correctAnswers} out of {totalQuestions} questions correct +

+
+ + {/* Additional visual elements */} +
+ {[...Array(5)].map((_, i) => ( +
+ ))} +
); } diff --git a/src/components/interactive-quiz/results/TabNavigation.tsx b/src/components/interactive-quiz/results/TabNavigation.tsx index 0fb640a..83694d6 100644 --- a/src/components/interactive-quiz/results/TabNavigation.tsx +++ b/src/components/interactive-quiz/results/TabNavigation.tsx @@ -7,7 +7,7 @@ import type { TabNavigationProps } from "../../../types/quiz-results"; export function TabNavigation({ selectedTab, onTabChange }: TabNavigationProps) { return (
-
+
+
} persistor={persistor}> + + + + ); +} +\`\`\` + +**Advanced Configuration:** +\`\`\`javascript +const persistConfig = { + key: 'root', + storage, + transforms: [ + // Encrypt sensitive data + encryptTransform({ + secretKey: 'my-secret-key', + }), + ], + migrate: createMigrate({ + 1: (state) => { + // Handle state migration + return { ...state, version: 1 }; + }, + }), +}; +\`\`\` + +**Benefits:** +- **User experience**: Maintains state across sessions +- **Performance**: Reduces initial data loading +- **Flexibility**: Choose what to persist +- **Security**: Optional encryption support`, + category: "Redux Persistence", + difficulty: "intermediate", + tags: ["redux-persist", "localStorage", "state-persistence", "rehydration"], + }, + { + id: 107, + question: "How do you handle multiple reducers in Redux Toolkit?", + answer: `Redux Toolkit provides several ways to handle multiple reducers, with configureStore being the most common approach. + +**Method 1: configureStore (Recommended)** +\`\`\`javascript +import { configureStore } from '@reduxjs/toolkit'; +import authSlice from './features/auth/authSlice'; +import cartSlice from './features/cart/cartSlice'; +import productsSlice from './features/products/productsSlice'; + +const store = configureStore({ + reducer: { + auth: authSlice.reducer, + cart: cartSlice.reducer, + products: productsSlice.reducer, + }, +}); + +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; +\`\`\` + +**Method 2: combineReducers (Manual)** +\`\`\`javascript +import { combineReducers } from '@reduxjs/toolkit'; +import authReducer from './authReducer'; +import cartReducer from './cartReducer'; + +const rootReducer = combineReducers({ + auth: authReducer, + cart: cartReducer, +}); + +const store = configureStore({ + reducer: rootReducer, +}); +\`\`\` + +**Method 3: Feature-based Structure** +\`\`\`javascript +// store/index.ts +import { configureStore } from '@reduxjs/toolkit'; +import authReducer from '../features/auth/authSlice'; +import cartReducer from '../features/cart/cartSlice'; + +export const store = configureStore({ + reducer: { + auth: authReducer, + cart: cartReducer, + }, +}); + +// features/auth/authSlice.ts +export const authSlice = createSlice({ + name: 'auth', + initialState: { user: null, isAuthenticated: false }, + reducers: { + login: (state, action) => { + state.user = action.payload; + state.isAuthenticated = true; + }, + logout: (state) => { + state.user = null; + state.isAuthenticated = false; + }, + }, +}); +\`\`\` + +**Best Practices:** +- **Feature-based organization**: Group related slices together +- **Single responsibility**: Each slice handles one domain +- **Consistent naming**: Use descriptive slice names +- **Type safety**: Export RootState and AppDispatch types`, + category: "Redux Architecture", + difficulty: "intermediate", + tags: ["multiple-reducers", "configureStore", "combineReducers", "architecture"], + }, + { + id: 108, + question: "What are extraReducers in Redux Toolkit?", + answer: `extraReducers allows a slice to respond to actions not defined in its own reducers. This is essential for handling async thunks and cross-slice actions. + +**When to Use extraReducers:** +- **Async thunks**: Handle loading, success, and error states +- **Cross-slice actions**: Respond to actions from other slices +- **External actions**: Handle actions from libraries or legacy code + +**Example with Async Thunk:** +\`\`\`javascript +import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; + +// Async thunk +export const fetchUser = createAsyncThunk( + 'user/fetchUser', + async (userId, { rejectWithValue }) => { + try { + const response = await fetch(\`/api/users/\${userId}\`); + if (!response.ok) throw new Error('Failed to fetch user'); + return await response.json(); + } catch (error) { + return rejectWithValue(error.message); + } + } +); + +const userSlice = createSlice({ + name: 'user', + initialState: { + data: null, + loading: false, + error: null, + }, + reducers: { + clearUser: (state) => { + state.data = null; + state.error = null; + }, + }, + extraReducers: (builder) => { + builder + .addCase(fetchUser.pending, (state) => { + state.loading = true; + state.error = null; + }) + .addCase(fetchUser.fulfilled, (state, action) => { + state.loading = false; + state.data = action.payload; + }) + .addCase(fetchUser.rejected, (state, action) => { + state.loading = false; + state.error = action.payload; + }); + }, +}); +\`\`\` + +**Cross-Slice Actions:** +\`\`\`javascript +// authSlice.ts +export const logout = createAction('auth/logout'); + +// cartSlice.ts +const cartSlice = createSlice({ + name: 'cart', + initialState: { items: [] }, + reducers: { + addItem: (state, action) => { + state.items.push(action.payload); + }, + }, + extraReducers: (builder) => { + builder.addCase(logout, (state) => { + state.items = []; // Clear cart on logout + }); + }, +}); +\`\`\` + +**Benefits:** +- **Clean separation**: Keep async logic separate from sync reducers +- **Reusability**: Handle common patterns across slices +- **Type safety**: Full TypeScript support +- **Predictable**: Follows Redux patterns`, + category: "Redux Toolkit", + difficulty: "intermediate", + tags: ["extraReducers", "async-thunks", "cross-slice", "createAsyncThunk"], + }, + { + id: 109, + question: "What's the difference between mapStateToProps and useSelector()?", + answer: `mapStateToProps is the legacy approach used with connect() in class components, while useSelector() is the modern hook-based approach for functional components. + +**mapStateToProps (Legacy):** +\`\`\`javascript +import { connect } from 'react-redux'; + +class UserProfile extends Component { + render() { + const { user, loading, error } = this.props; + // Component logic + } +} + +const mapStateToProps = (state) => ({ + user: state.user.data, + loading: state.user.loading, + error: state.user.error, +}); + +const mapDispatchToProps = (dispatch) => ({ + fetchUser: (id) => dispatch(fetchUser(id)), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(UserProfile); +\`\`\` + +**useSelector() (Modern):** +\`\`\`javascript +import { useSelector, useDispatch } from 'react-redux'; + +function UserProfile() { + const user = useSelector((state) => state.user.data); + const loading = useSelector((state) => state.user.loading); + const error = useSelector((state) => state.user.error); + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(fetchUser(userId)); + }, [dispatch, userId]); + + // Component logic +} +\`\`\` + +**Key Differences:** + +| Feature | mapStateToProps | useSelector() | +|---------|----------------|---------------| +| **Component Type** | Class components | Functional components | +| **Boilerplate** | High (HOC wrapper) | Low (direct hook usage) | +| **Performance** | Manual optimization | Automatic optimization | +| **TypeScript** | Complex typing | Simple typing | +| **Testing** | Harder to test | Easier to test | +| **Reusability** | Limited | High | + +**Performance Comparison:** +\`\`\`javascript +// mapStateToProps - manual optimization needed +const mapStateToProps = (state, ownProps) => { + // Manual memoization required + return { + expensiveData: expensiveSelector(state, ownProps.id), + }; +}; + +// useSelector - automatic optimization +const expensiveData = useSelector((state) => + expensiveSelector(state, userId) +); +\`\`\` + +**Recommendation:** +- ✅ **Use useSelector()** for new projects +- ✅ **Migrate from mapStateToProps** when refactoring +- ❌ **Avoid mapStateToProps** unless maintaining legacy code`, + category: "React Redux", + difficulty: "intermediate", + tags: ["useSelector", "mapStateToProps", "connect", "hooks", "legacy"], + }, + { + id: 110, + question: "How would you structure a large Redux app?", + answer: `A well-structured Redux app follows feature-based organization with clear separation of concerns and scalable patterns. + +**Recommended Structure:** +\`\`\` +/src + /store + index.ts # Store configuration + rootReducer.ts # Root reducer (if needed) + /features + /auth + authSlice.ts # Auth reducer + actions + authAPI.ts # Auth async thunks + authSelectors.ts # Auth selectors + authTypes.ts # Auth TypeScript types + /cart + cartSlice.ts + cartSelectors.ts + cartTypes.ts + /products + productsSlice.ts + productsAPI.ts + productsSelectors.ts + productsTypes.ts + /shared + /components # Reusable components + /hooks # Custom hooks + /utils # Utility functions + /types # Global types +\`\`\` + +**Store Configuration:** +\`\`\`javascript +// store/index.ts +import { configureStore } from '@reduxjs/toolkit'; +import authReducer from '../features/auth/authSlice'; +import cartReducer from '../features/cart/cartSlice'; +import productsReducer from '../features/products/productsSlice'; + +export const store = configureStore({ + reducer: { + auth: authReducer, + cart: cartReducer, + products: productsReducer, + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + serializableCheck: { + ignoredActions: ['persist/PERSIST'], + }, + }), +}); + +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; +\`\`\` + +**Feature Slice Example:** +\`\`\`javascript +// features/auth/authSlice.ts +import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; + +export const loginUser = createAsyncThunk( + 'auth/login', + async (credentials, { rejectWithValue }) => { + try { + const response = await fetch('/api/auth/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(credentials), + }); + return await response.json(); + } catch (error) { + return rejectWithValue(error.message); + } + } +); + +const authSlice = createSlice({ + name: 'auth', + initialState: { + user: null, + token: null, + loading: false, + error: null, + }, + reducers: { + logout: (state) => { + state.user = null; + state.token = null; + state.error = null; + }, + clearError: (state) => { + state.error = null; + }, + }, + extraReducers: (builder) => { + builder + .addCase(loginUser.pending, (state) => { + state.loading = true; + state.error = null; + }) + .addCase(loginUser.fulfilled, (state, action) => { + state.loading = false; + state.user = action.payload.user; + state.token = action.payload.token; + }) + .addCase(loginUser.rejected, (state, action) => { + state.loading = false; + state.error = action.payload; + }); + }, +}); + +export const { logout, clearError } = authSlice.actions; +export default authSlice.reducer; +\`\`\` + +**Selectors:** +\`\`\`javascript +// features/auth/authSelectors.ts +import { createSelector } from '@reduxjs/toolkit'; + +const selectAuthState = (state) => state.auth; + +export const selectUser = createSelector( + [selectAuthState], + (auth) => auth.user +); + +export const selectIsAuthenticated = createSelector( + [selectUser], + (user) => !!user +); + +export const selectAuthLoading = createSelector( + [selectAuthState], + (auth) => auth.loading +); +\`\`\` + +**Best Practices:** +- **Feature isolation**: Each feature is self-contained +- **Consistent naming**: Use descriptive, consistent names +- **Type safety**: Export and use TypeScript types +- **Separation of concerns**: Keep API logic separate from UI logic +- **Reusable patterns**: Create shared utilities and hooks`, + category: "Redux Architecture", + difficulty: "senior", + tags: ["architecture", "structure", "features", "scalability", "best-practices"], + }, ]; export default REDUX_QUESTIONS; diff --git a/src/pages/FrameworkSelection.tsx b/src/pages/FrameworkSelection.tsx index 4835abe..c5d79d0 100644 --- a/src/pages/FrameworkSelection.tsx +++ b/src/pages/FrameworkSelection.tsx @@ -6,136 +6,189 @@ export default function FrameworkSelection() { const navigate = useNavigate(); return ( -
-
- {/* Header */} -
-

- Interview Prep Platform -

-

- Master 400 senior-level questions across 4 frameworks -

-
-
-
- 100% Free -
-
-
- Progress Tracking +
+ {/* Background Pattern */} +
+
+
+ +
+
+ {/* Enhanced Header */} +
+
+

+ Interview Prep Platform +

+

+ Master 569+{" "} + senior-level questions across{" "} + 4 frameworks +

-
-
- PWA Enabled + + {/* Enhanced Feature Indicators */} +
+
+
+ 100% Free +
+
+
+ + Progress Tracking + +
+
+
+ PWA Enabled +
-
- {/* Framework Cards Grid */} -
- {QUESTION_SETS.map((framework) => ( - - ))} -
+ {/* Enhanced CTA */} +
+ Start Learning + + + +
+
- {/* Features */} -
-
-
📊
-

Track Progress

-

- Per-framework progress tracking with completion stats and bookmarks -

+ {/* Hover Effect Overlay */} +
+ + ))}
-
-
🎯
-

Practice Modes

-

- Sequential, random, and bookmarked modes to suit your study style -

-
+ {/* Enhanced Features Section */} +
+
+
+ 📊 +
+

+ Track Progress +

+

+ Per-framework progress tracking with completion stats and bookmarks +

+
-
-
💡
-

Expert Answers

-

- Comprehensive senior-level answers to ace your technical interviews -

-
-
+
+
+ 🎯 +
+

+ Practice Modes +

+

+ Sequential, random, and bookmarked modes to suit your study style +

+
- {/* Footer */} -
-
-
-
-
-
+
+
+ 💡 +
+

+ Expert Answers +

+

+ Comprehensive senior-level answers to ace your technical interviews +

-

- Built with React 19, TypeScript, and Tailwind CSS -

-

- Crafted with ❤️ by{" "} - Hussein Tirawi -

-

- © 2024 All rights reserved -

- + + {/* Enhanced Footer */} +
+
+
+
+
+
+
+

+ Built with React 19, TypeScript, and Tailwind CSS +

+

+ Crafted with ❤️ by{" "} + + Hussein Tirawi + +

+

+ © 2024 All rights reserved +

+
diff --git a/src/pages/QuizPage.tsx b/src/pages/QuizPage.tsx index ad9835b..f4b0d49 100644 --- a/src/pages/QuizPage.tsx +++ b/src/pages/QuizPage.tsx @@ -246,6 +246,44 @@ export default function QuizPage() { onComplete={handleCompleteQuiz} />
+ + {/* Enhanced Footer */} +
+
+
+
+
+
+
+
+

+ Built with React 19, TypeScript, and Tailwind CSS +

+

+ Crafted with ❤️ by{" "} + + Hussein Tirawi + +

+

+ © 2024 All rights reserved +

+ +
+
+
); } diff --git a/src/pages/QuizSelectionPage.tsx b/src/pages/QuizSelectionPage.tsx new file mode 100644 index 0000000..4322c55 --- /dev/null +++ b/src/pages/QuizSelectionPage.tsx @@ -0,0 +1,1061 @@ +/** + * Quiz Selection Page - Modern card-based quiz selection with hover details + */ + +import React, { useState, useRef, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { FrameworkIcon } from "../components/common/icons/FrameworkIcon"; + +interface QuizCard { + id: string; + framework: string; + level: "junior" | "intermediate" | "senior"; + title: string; + subtitle: string; + skills: string[]; + duration: string; + evaluation: string; + description: string; + icon: string; +} + +const QUIZ_CARDS: QuizCard[] = [ + // Angular Cards + { + id: "angular-junior", + framework: "angular", + level: "junior", + title: "Junior Angular Developer | TypeScript", + subtitle: "Component Architecture & Services", + skills: [ + "Angular Components", + "TypeScript", + "Services", + "Dependency Injection", + "RxJS", + "Angular CLI", + "Modules", + "Directives", + ], + duration: "45 minutes", + evaluation: "Automatic", + description: + "This quiz evaluates fundamental Angular concepts including components, services, and basic TypeScript knowledge.", + icon: "angular", + }, + { + id: "angular-intermediate", + framework: "angular", + level: "intermediate", + title: "Intermediate Angular Developer | Advanced Features", + subtitle: "Advanced Patterns & State Management", + skills: [ + "Angular Forms", + "Routing", + "Guards", + "Interceptors", + "State Management", + "Testing", + "Performance", + "Angular Material", + ], + duration: "60 minutes", + evaluation: "Automatic", + description: + "This quiz covers intermediate Angular concepts including advanced routing, forms, and state management patterns.", + icon: "angular", + }, + { + id: "angular-senior", + framework: "angular", + level: "senior", + title: "Senior Angular Developer | Enterprise Architecture", + subtitle: "Enterprise Solutions & Performance", + skills: [ + "Architecture Patterns", + "Microservices", + "Performance Optimization", + "Security", + "Testing Strategies", + "CI/CD", + "Advanced RxJS", + "Angular Universal", + ], + duration: "75 minutes", + evaluation: "Automatic", + description: + "This quiz evaluates senior-level Angular expertise including enterprise architecture and performance optimization.", + icon: "angular", + }, + + // React Cards + { + id: "react-junior", + framework: "react", + level: "junior", + title: "Junior React Developer | JavaScript", + subtitle: "Component Basics & Hooks", + skills: [ + "React Components", + "JSX", + "Props", + "State", + "Hooks", + "Event Handling", + "Conditional Rendering", + "Lists", + ], + duration: "45 minutes", + evaluation: "Automatic", + description: + "This quiz covers fundamental React concepts including components, hooks, and basic JavaScript patterns.", + icon: "react", + }, + { + id: "react-intermediate", + framework: "react", + level: "intermediate", + title: "Intermediate React Developer | Advanced Patterns", + subtitle: "State Management & Performance", + skills: [ + "Context API", + "Custom Hooks", + "Performance", + "Testing", + "Routing", + "Forms", + "Error Boundaries", + "Code Splitting", + ], + duration: "60 minutes", + evaluation: "Automatic", + description: + "This quiz evaluates intermediate React skills including state management and performance optimization.", + icon: "react", + }, + { + id: "react-senior", + framework: "react", + level: "senior", + title: "Senior React Developer | Enterprise Solutions", + subtitle: "Architecture & Advanced Patterns", + skills: [ + "React Architecture", + "Design Patterns", + "Performance", + "Testing Strategies", + "SSR/SSG", + "Micro Frontends", + "Advanced Hooks", + "Concurrent Features", + ], + duration: "75 minutes", + evaluation: "Automatic", + description: + "This quiz covers senior-level React expertise including enterprise architecture and advanced patterns.", + icon: "react", + }, + + // Next.js Cards + { + id: "nextjs-junior", + framework: "nextjs", + level: "junior", + title: "Junior Next.js Developer | Full-Stack Basics", + subtitle: "Pages & API Routes", + skills: [ + "Next.js Pages", + "API Routes", + "File-based Routing", + "Static Generation", + "Image Optimization", + "CSS Modules", + "Environment Variables", + "Deployment", + ], + duration: "45 minutes", + evaluation: "Automatic", + description: + "This quiz covers fundamental Next.js concepts including pages, API routes, and basic full-stack development.", + icon: "nextjs", + }, + { + id: "nextjs-intermediate", + framework: "nextjs", + level: "intermediate", + title: "Intermediate Next.js Developer | App Router", + subtitle: "Modern Architecture & Performance", + skills: [ + "App Router", + "Server Components", + "Client Components", + "Streaming", + "Middleware", + "Caching", + "Performance", + "SEO", + ], + duration: "60 minutes", + evaluation: "Automatic", + description: + "This quiz evaluates intermediate Next.js skills including App Router and modern architecture patterns.", + icon: "nextjs", + }, + { + id: "nextjs-senior", + framework: "nextjs", + level: "senior", + title: "Senior Next.js Developer | Enterprise Architecture", + subtitle: "Advanced Patterns & Optimization", + skills: [ + "Enterprise Architecture", + "Microservices", + "Performance", + "Security", + "Advanced Caching", + "Edge Functions", + "Monitoring", + "Scalability", + ], + duration: "75 minutes", + evaluation: "Automatic", + description: + "This quiz covers senior-level Next.js expertise including enterprise architecture and advanced optimization.", + icon: "nextjs", + }, + + // Redux Cards + { + id: "redux-junior", + framework: "redux", + level: "junior", + title: "Junior Redux Developer | State Management", + subtitle: "Basic State Management", + skills: [ + "Redux Basics", + "Actions", + "Reducers", + "Store", + "React-Redux", + "useSelector", + "useDispatch", + "Provider", + ], + duration: "45 minutes", + evaluation: "Automatic", + description: + "This quiz covers fundamental Redux concepts including basic state management and React integration.", + icon: "redux", + }, + { + id: "redux-intermediate", + framework: "redux", + level: "intermediate", + title: "Intermediate Redux Developer | Advanced Patterns", + subtitle: "Middleware & Redux Toolkit", + skills: [ + "Redux Toolkit", + "Middleware", + "Async Actions", + "Selectors", + "DevTools", + "Performance", + "Testing", + "Best Practices", + ], + duration: "60 minutes", + evaluation: "Automatic", + description: + "This quiz evaluates intermediate Redux skills including middleware and Redux Toolkit patterns.", + icon: "redux", + }, + { + id: "redux-senior", + framework: "redux", + level: "senior", + title: "Senior Redux Developer | Enterprise Architecture", + subtitle: "Advanced State Management", + skills: [ + "Enterprise Patterns", + "Microservices", + "Performance", + "Security", + "Advanced Middleware", + "State Persistence", + "Monitoring", + "Scalability", + ], + duration: "75 minutes", + evaluation: "Automatic", + description: + "This quiz covers senior-level Redux expertise including enterprise architecture and advanced state management.", + icon: "redux", + }, +]; + +const DifficultyIndicator = ({ level }: { level: "junior" | "intermediate" | "senior" }) => { + const bars = level === "junior" ? 1 : level === "intermediate" ? 2 : 3; + const label = level.charAt(0).toUpperCase() + level.slice(1); + + return ( +
+
+ {[1, 2, 3].map((bar) => ( +
+ ))} +
+ {label} +
+ ); +}; + +const QuizCardComponent = ({ + card, + isHovered, + onMouseEnter, + onMouseLeave, + onClick, +}: { + card: QuizCard; + isHovered: boolean; + onMouseEnter: () => void; + onMouseLeave: () => void; + onClick: () => void; +}) => { + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + onClick(); + } + }; + + return ( +
+
+ {/* Header with Icons */} +
+
+
+ +
+
+ {card.framework.toUpperCase()} +
+
+ +
+ + {/* Title and Subtitle */} +
+

+ {card.title} +

+

+ {card.subtitle} +

+
+ + {/* Skills */} +
+
+ Tested Skills +
+
+ {card.skills.slice(0, 3).map((skill, index) => ( + + {skill} + + ))} + {card.skills.length > 3 && ( + + +{card.skills.length - 3} + + )} +
+
+ + {/* Duration and Evaluation */} +
+
+ + + + {card.duration} +
+
+ + + + {card.evaluation} +
+
+ + {/* Start Quiz Button - Always Visible */} +
+
+ + + + Start Quiz + + + +
+
+ + {/* Hover Gradient Overlay - Behind content */} +
+
+
+ ); +}; + +const QuizDetailsPopup = ({ + card, + isVisible, + position, + onClose, + onStartQuiz, +}: { + card: QuizCard | null; + isVisible: boolean; + position: { x: number; y: number }; + onClose: () => void; + onStartQuiz?: (card: QuizCard) => void; +}) => { + if (!card || !isVisible) return null; + + // Determine arrow direction based on popup position + const isLeftPosition = position.x < window.innerWidth / 2; + const arrowClass = isLeftPosition + ? "absolute -left-2 top-1/2 h-4 w-4 -translate-y-1/2 rotate-45 border-b border-l border-gray-200 bg-white" + : "absolute -right-2 top-1/2 h-4 w-4 -translate-y-1/2 rotate-45 border-t border-r border-gray-200 bg-white"; + + return ( + <> + {/* Mobile Overlay */} +
+ + {/* Desktop Popup */} +
+ {/* Arrow pointing to card */} +
+ + {/* Header */} +
+

{card.title}

+

{card.subtitle}

+
+ + {/* Tested Skills */} +
+
+ Tested Skills +
+
+ {card.skills.map((skill, index) => ( + + {skill} + + ))} +
+
+ + {/* Duration */} +
+
+ Duration +
+
+ {card.duration} +
+
+ + {/* Evaluation */} +
+
+ Evaluation +
+
+ {card.evaluation} +
+
+ + {/* Test Overview */} +
+
+ Test Overview +
+
+
+
Choice Questions
+
+ Assessing knowledge of {card.framework}, JavaScript, and related technologies +
+
+
+
+ Interactive Quiz - Level: {card.level.charAt(0).toUpperCase() + card.level.slice(1)} +
+
{card.description}
+
+
+
+
+ + {/* Mobile Modal */} +
+ {/* Close Button */} + + + {/* Header */} +
+

{card.title}

+

{card.subtitle}

+
+ + {/* Tested Skills */} +
+
+ Tested Skills +
+
+ {card.skills.map((skill, index) => ( + + {skill} + + ))} +
+
+ + {/* Duration */} +
+
+ Duration +
+
+ {card.duration} +
+
+ + {/* Evaluation */} +
+
+ Evaluation +
+
+ {card.evaluation} +
+
+ + {/* Test Overview */} +
+
+ Test Overview +
+
+
+
Choice Questions
+
+ Assessing knowledge of {card.framework}, JavaScript, and related technologies +
+
+
+
+ Interactive Quiz - Level: {card.level.charAt(0).toUpperCase() + card.level.slice(1)} +
+
{card.description}
+
+
+
+ + {/* Start Quiz Button */} + +
+ + ); +}; + +export default function QuizSelectionPage() { + const navigate = useNavigate(); + const [hoveredCard, setHoveredCard] = useState(null); + const [popupPosition, setPopupPosition] = useState({ x: 0, y: 0 }); + const [isMobile, setIsMobile] = useState(false); + const [searchTerm, setSearchTerm] = useState(""); + const [selectedFramework, setSelectedFramework] = useState("all"); + const [selectedLevel, setSelectedLevel] = useState("all"); + const cardRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); + + // Check if mobile on mount and resize + useEffect(() => { + const checkMobile = () => { + setIsMobile(window.innerWidth < 1024); + }; + + checkMobile(); + window.addEventListener("resize", checkMobile); + return () => window.removeEventListener("resize", checkMobile); + }, []); + + // Handle escape key to close popup + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === "Escape" && hoveredCard) { + closePopup(); + } + }; + + document.addEventListener("keydown", handleEscape); + return () => document.removeEventListener("keydown", handleEscape); + }, [hoveredCard]); + + const handleCardHover = (cardId: string) => { + if (isMobile) return; // Don't show hover on mobile + setHoveredCard(cardId); + + // Calculate popup position + const cardElement = cardRefs.current[cardId]; + if (cardElement) { + const rect = cardElement.getBoundingClientRect(); + const popupWidth = 448; // 28rem = 448px + const popupHeight = 400; // Estimated popup height + + // Calculate position with screen boundary checks + let x = rect.right + 20; + let y = rect.top + rect.height / 2; + + // Check if popup would go off right edge + if (x + popupWidth > window.innerWidth) { + x = rect.left - popupWidth - 20; // Position to the left instead + } + + // Check if popup would go off bottom edge + if (y + popupHeight / 2 > window.innerHeight) { + y = window.innerHeight - popupHeight / 2 - 20; + } + + // Check if popup would go off top edge + if (y - popupHeight / 2 < 20) { + y = popupHeight / 2 + 20; + } + + setPopupPosition({ x, y }); + } + }; + + const handleCardClick = (card: QuizCard) => { + if (isMobile) { + // On mobile, show details modal instead of navigating directly + setHoveredCard(card.id); + } else { + navigate(`/quiz/${card.framework}/${card.level}`); + } + }; + + const handleStartQuiz = (card: QuizCard) => { + navigate(`/quiz/${card.framework}/${card.level}`); + }; + + const closePopup = () => { + setHoveredCard(null); + }; + + // Filter cards based on search and filters + const filteredCards = QUIZ_CARDS.filter((card) => { + const matchesSearch = + card.title.toLowerCase().includes(searchTerm.toLowerCase()) || + card.subtitle.toLowerCase().includes(searchTerm.toLowerCase()) || + card.skills.some((skill) => skill.toLowerCase().includes(searchTerm.toLowerCase())); + + const matchesFramework = selectedFramework === "all" || card.framework === selectedFramework; + const matchesLevel = selectedLevel === "all" || card.level === selectedLevel; + + return matchesSearch && matchesFramework && matchesLevel; + }); + + // Get unique frameworks and levels for filter options + const frameworks = Array.from(new Set(QUIZ_CARDS.map((card) => card.framework))); + const levels = Array.from(new Set(QUIZ_CARDS.map((card) => card.level))); + + return ( +
+