diff --git a/client/README.md b/client/README.md index 58449df1..e5437d7c 100644 --- a/client/README.md +++ b/client/README.md @@ -9,6 +9,10 @@ Codify is a comprehensive web application that offers courses and roadmaps to va - User authentication and personalized profiles - Comprehensive admin panel for content and user management - Optimized responsive design for seamless mobile and desktop experiences +- New unified Hero section with stable layout (no button jumping) and video background +- Desktop navigation now shows primary links (Home, About, Editor, Courses, Roadmaps, Notes, Questions, Bookmarks, Contributors, Contact) without needing the hamburger menu +- Accessible focus states (focus-visible rings) added to all navigation links & CTAs +- Playwright end-to-end tests for responsive navbar behavior ## Technologies Used - **Frontend:** React, React Router, CSS 💻 @@ -52,6 +56,22 @@ https://github.com/user-attachments/assets/9ba51e5e-8e16-48f1-96d3-fe1e497541a0 ## Usage Once the application is running, navigate to `http://localhost:5173` in your web browser to access the app. You can create an account, log in, and start learning about bitwise operations. +### Running E2E Tests (Playwright) +1. From the `client` directory install dependencies (including Playwright): this will auto-install browsers on first run. +2. Run tests: +``` +pnpm test:e2e (or) npm run test:e2e +``` +Optional: +``` +npm run test:e2e:ui # Interactive UI mode +npm run test:e2e:headed # Run in headed browsers +``` + +The navbar test validates: +- Desktop (xl) shows all primary links and hides the hamburger. +- Mobile view shows hamburger and reveals links after opening the menu. + ## Contributing Contributions are welcome! Please follow these steps: 1. Fork the repository. diff --git a/client/package-lock.json b/client/package-lock.json index 12b06f71..78361051 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -35,6 +35,7 @@ "swiper": "^11.2.10" }, "devDependencies": { + "@playwright/test": "^1.48.2", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.7.0", @@ -1272,6 +1273,22 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.55.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0.tgz", + "integrity": "sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.55.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@remix-run/router": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.18.0.tgz", @@ -5071,6 +5088,53 @@ "node": ">= 6" } }, + "node_modules/playwright": { + "version": "1.55.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0.tgz", + "integrity": "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.55.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.55.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz", + "integrity": "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", diff --git a/client/package.json b/client/package.json index bf0945c0..62b83945 100644 --- a/client/package.json +++ b/client/package.json @@ -7,7 +7,10 @@ "dev": "vite", "build": "vite build", "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview" + "preview": "vite preview", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", + "test:e2e:headed": "playwright test --headed" }, "dependencies": { "@codemirror/lang-cpp": "^6.0.3", @@ -47,6 +50,7 @@ "eslint-plugin-react-refresh": "^0.4.7", "postcss": "^8.4.41", "tailwindcss": "^3.4.17", - "vite": "^6.3.5" + "vite": "^6.3.5", + "@playwright/test": "^1.48.2" } } diff --git a/client/playwright.config.js b/client/playwright.config.js new file mode 100644 index 00000000..d6760f81 --- /dev/null +++ b/client/playwright.config.js @@ -0,0 +1,25 @@ +// @ts-check +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './tests', + timeout: 30 * 1000, + expect: { timeout: 5000 }, + fullyParallel: true, + retries: 0, + reporter: [['list']], + use: { + baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:5173', + trace: 'retain-on-failure' + }, + projects: [ + { + name: 'Desktop Chrome', + use: { ...devices['Desktop Chrome'] } + }, + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 7'] } + } + ] +}); diff --git a/client/src/components/Hero.jsx b/client/src/components/Hero.jsx new file mode 100644 index 00000000..14cdc87e --- /dev/null +++ b/client/src/components/Hero.jsx @@ -0,0 +1,116 @@ +import React from 'react'; +import { motion } from 'framer-motion'; +import { useTheme } from '../context/ThemeContext'; + +/** + * Hero section extracted from Home page for clarity & reusability. + * Adjusted to avoid layout shift / button jumping by: + * - Providing explicit min-height using CSS variable offset for navbar. + * - Reserving space for async content (no conditional height changes). + * - Using flex + gap with consistent sizing. + */ +function Hero() { + const { theme } = useTheme(); + const isDark = theme === 'dark'; + + return ( +
+ {/* Video Background */} + + + {/* Overlay */} +
+ + {/* Ambient Shapes */} +
+
+
+
+
+ + {/* Core Content */} +
+ + + 🚀 Join 1000+ learners worldwide + + + + Master Coding with + Interactive Learning + + + + Discover the perfect learning path with hands-on projects, expert guidance, and a community of passionate developers. + + + + + Start Learning Free + + + + Watch Demo + + + + +
+

Trusted by developers from 100+ countries

+
+
+
+
+
+
+ + {/* Scroll Indicator */} +
+
+
+
+
+
+
+ ); +} + +export default Hero; diff --git a/client/src/components/MobileMenu.jsx b/client/src/components/MobileMenu.jsx index d552458f..818bb740 100644 --- a/client/src/components/MobileMenu.jsx +++ b/client/src/components/MobileMenu.jsx @@ -5,6 +5,9 @@ import { RiCloseLargeLine } from 'react-icons/ri'; import { FaBook, FaBookReader, FaBookmark, FaEnvelope, FaGraduationCap, FaHome, FaRoad, FaSignInAlt, FaSignOutAlt, FaUser, FaUserPlus, FaUserTie, FaArrowUp, FaArrowDown, FaQuestionCircle } from 'react-icons/fa'; import { useTheme } from '../context/ThemeContext'; import { FaCode } from "react-icons/fa"; +import ThemeSwitcher from './ThemeSwitcher'; +import ThemeColorSelector from './ThemeColorSelector'; +import { NAV_LINKS } from "../constants/navLinks"; function MobileMenu({ isOpen, onClose, isLoggedIn, userdata }) { const { theme } = useTheme(); @@ -110,214 +113,27 @@ function MobileMenu({ isOpen, onClose, isLoggedIn, userdata }) {
-