From 7ab71c583c1880a98ae6df8647ec3ad44abc0b14 Mon Sep 17 00:00:00 2001 From: Gupta-02 Date: Mon, 15 Sep 2025 08:50:49 +0530 Subject: [PATCH] Update: committed latest changes --- client/README.md | 20 +++ client/package-lock.json | 64 ++++++++ client/package.json | 8 +- client/playwright.config.js | 25 ++++ client/src/components/Hero.jsx | 116 +++++++++++++++ client/src/components/MobileMenu.jsx | 214 +++------------------------ client/src/components/NavBar.jsx | 83 ++++++----- client/src/constants/navLinks.js | 20 +++ client/src/pages/Home.jsx | 167 +-------------------- client/tests/navbar.spec.js | 40 +++++ 10 files changed, 356 insertions(+), 401 deletions(-) create mode 100644 client/playwright.config.js create mode 100644 client/src/components/Hero.jsx create mode 100644 client/src/constants/navLinks.js create mode 100644 client/tests/navbar.spec.js 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 d768dbc5..57c72a46 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -34,6 +34,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", @@ -1250,6 +1251,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", @@ -5050,6 +5067,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 0a4ef1be..1c723175 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", @@ -46,6 +49,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 c4fd8b24..66d0e203 100644 --- a/client/src/components/MobileMenu.jsx +++ b/client/src/components/MobileMenu.jsx @@ -7,6 +7,7 @@ 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(); @@ -94,203 +95,26 @@ function MobileMenu({ isOpen, onClose, isLoggedIn, userdata }) {
- {/* Direct Navigation Links */} -
- ` - flex items-center space-x-2 px-4 py-2 rounded-lg text-base font-medium transition-all duration-200 - ${isActive - ? 'bg-primary text-white shadow-md' - : (isDark ? 'text-dark-text-primary hover:bg-dark-bg-tertiary hover:text-white' : 'text-light-text-primary hover:bg-light-bg-tertiary')} - `} - > - - Courses - - - ` - flex items-center space-x-2 px-4 py-2 rounded-lg text-base font-medium transition-all duration-200 - ${isActive - ? 'bg-primary text-white shadow-md' - : (isDark ? 'text-dark-text-primary hover:bg-dark-bg-tertiary hover:text-white' : 'text-light-text-primary hover:bg-light-bg-tertiary')} - `} - > - - Roadmaps - + {/* Direct Navigation Links (Desktop full) */} +
+ {NAV_LINKS.map(({ to, label, icon: Icon }) => ( + ` + group flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium tracking-wide transition-all duration-200 + ${isActive + ? 'bg-primary text-white shadow-md' + : isDark + ? 'text-dark-text-primary/90 hover:text-white hover:bg-dark-bg-tertiary/70' + : 'text-light-text-primary/90 hover:text-light-text-primary hover:bg-light-bg-tertiary'} + focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/60 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent + `} + > + + {label} + + ))} +
- ` - flex items-center space-x-2 px-4 py-2 rounded-lg text-base font-medium transition-all duration-200 - ${isActive - ? 'bg-primary text-white shadow-md' - : (isDark ? 'text-dark-text-primary hover:bg-dark-bg-tertiary hover:text-white' : 'text-light-text-primary hover:bg-light-bg-tertiary')} - `} - > - - Notes - + {/* Condensed Navigation Links (Between lg and xl) */} +
+ {NAV_LINKS.filter(l => CONDENSED_LINKS.includes(l.to)).map(({ to, label, icon: Icon }) => ( + ` + group flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-colors + ${isActive + ? 'bg-primary text-white shadow' + : isDark + ? 'text-dark-text-primary/90 hover:text-white hover:bg-dark-bg-tertiary/70' + : 'text-light-text-primary/90 hover:bg-light-bg-tertiary'} + focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/60 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent + `} + > + + {label === 'Questions' ? 'Q&A' : label} + + ))}
diff --git a/client/src/constants/navLinks.js b/client/src/constants/navLinks.js new file mode 100644 index 00000000..6056e008 --- /dev/null +++ b/client/src/constants/navLinks.js @@ -0,0 +1,20 @@ +// Central navigation link configuration used by NavBar & MobileMenu +// Each link: { to: string, label: string, icon: ReactIconComponent, group?: 'primary' | 'secondary' } +// Order matters for display. +import { FaHome, FaUser, FaCode, FaBookOpen, FaRoad, FaStickyNote, FaQuestionCircle, FaBookmark, FaUserFriends, FaEnvelope } from 'react-icons/fa'; + +export const NAV_LINKS = [ + { to: '/', label: 'Home', icon: FaHome }, + { to: '/about', label: 'About', icon: FaUser }, + { to: '/editor', label: 'Editor', icon: FaCode }, + { to: '/courses', label: 'Courses', icon: FaBookOpen }, + { to: '/roadmap', label: 'Roadmaps', icon: FaRoad }, + { to: '/notes', label: 'Notes', icon: FaStickyNote }, + { to: '/questions', label: 'Questions', icon: FaQuestionCircle }, + { to: '/bookmarks', label: 'Bookmarks', icon: FaBookmark }, + { to: '/contributors', label: 'Contributors', icon: FaUserFriends }, + { to: '/contact', label: 'Contact', icon: FaEnvelope } +]; + +// For condensed breakpoint (lg to import("../components/HomePageComponents/Testimo const NewsLetter = lazy(() => import("../components/HomePageComponents/NewsLetter")); const CallToAction = lazy(() => import("../components/HomePageComponents/CallToAction")); const Contribution = lazy(() => import("../components/HomePageComponents/Contributor")); +import Hero from "../components/Hero"; function Home() { const { theme } = useTheme(); @@ -179,170 +180,8 @@ function Home() {
- {/* Hero Section with Video Background on top of the grid */} -
- {/* Video Background */} - - - {/* Overlay */} -
- - {/* Animated Background Elements */} -
-
-
-
-
- - {/* Floating Icons */} -
- - - -
- -
- - - -
-
- - - -
- -
- - - -
- - {/* Main Content */} -
- {/* Badge */} - - - - 🚀 Join 1000+ learners worldwide - - - - {/* Main Headline */} - - Master Coding with - - Interactive Learning - - - - {/* Subtitle */} - - Discover the perfect learning path with hands-on projects, expert - guidance, and a community of passionate developers - - - {/* CTA Buttons */} - - - Start Learning Free - - - - - - Watch Demo - - - - - - - {/* Trust Indicators */} -
-

- Trusted by developers from 100+ countries -

- -
-
-
-
-
-
-
- - {/* Scroll Indicator */} -
-
-
-
-
-
+ {/* Hero Section */} + {/* Main Content Wrapper */}
diff --git a/client/tests/navbar.spec.js b/client/tests/navbar.spec.js new file mode 100644 index 00000000..0bbed064 --- /dev/null +++ b/client/tests/navbar.spec.js @@ -0,0 +1,40 @@ +import { test, expect } from '@playwright/test'; + +// Utility selectors +const hamburgerButton = 'button[aria-label="Open menu"]'; + +// Main nav labels expected on desktop xl breakpoint +const FULL_LINKS = ['Home','About','Editor','Courses','Roadmaps','Notes','Questions','Bookmarks','Contributors','Contact']; + +// Condensed subset (lg < xl) would show smaller set, but we focus on xl + mobile behavior. + +test.describe('Navbar responsive behavior', () => { + test('desktop (xl) shows full nav and hides hamburger', async ({ page }) => { + await page.setViewportSize({ width: 1400, height: 900 }); + await page.goto('/'); + + for (const label of FULL_LINKS) { + await expect(page.getByRole('link', { name: label })).toBeVisible(); + } + await expect(page.locator(hamburgerButton)).toBeHidden(); + }); + + test('mobile shows hamburger and links only after opening menu', async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto('/'); + + // Links should not all be visible inline + const inlineVisibility = await page.getByRole('link', { name: 'Courses' }).isVisible(); + // On small screens primary nav links should be absent (hidden via display none) until menu open. + // We don't strictly fail if one appears due to layout changes, but we assert hamburger is visible. + await expect(page.locator(hamburgerButton)).toBeVisible(); + + // Open menu + await page.click(hamburgerButton); + + // Now verify a couple of representative links appear inside mobile menu + for (const label of ['Home','Courses','Roadmaps']) { + await expect(page.getByRole('link', { name: label })).toBeVisible(); + } + }); +});