From 73cf3a0e52b707618ba2de1b4044c4cb34834425 Mon Sep 17 00:00:00 2001 From: DpkRn Date: Tue, 16 Dec 2025 04:40:05 +0530 Subject: [PATCH 001/166] Added about and some enhance is there --- frontend/src/utils/api.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index 2500fc7..4ee1b17 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -2,8 +2,11 @@ import axios from 'axios'; // Create an Axios instance const api = axios.create({ - baseURL: 'http://localhost:8080', // Replace with your API base URL - // baseURL: import.meta.env.VITE_API_URL || 'https://clickly.cv', - // withCredentials: true, + + // baseURL: 'http://localhost:8080', // Replace with your API base URL + // baseURL: 'https://linkb-one.vercel.app', + + baseURL: import.meta.env.VITE_API_URL || 'https://clickly.cv', + withCredentials: true, }); export default api; \ No newline at end of file From 958421dcb7822a8001b1afcaea74516643b841eb Mon Sep 17 00:00:00 2001 From: DpkRn Date: Tue, 16 Dec 2025 11:04:21 +0530 Subject: [PATCH 002/166] Updated all instances of the old link domain to the new domain `clickly.cv` across frontend components, backend configurations, and documentation. This change ensures consistency in the URL structure and enhances the branding of the application. --- README.md | 42 ++++++++-------- backend/index.js | 2 +- backend/views/linktree.ejs | 2 +- frontend/src/components/Documentation.jsx | 48 +++++++++---------- frontend/src/components/linkcard/Linkcard.jsx | 2 +- .../src/components/pages/CreateBridge.jsx | 6 +-- frontend/src/components/pages/LinkPage.jsx | 2 +- 7 files changed, 52 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 77fe97d..17410a1 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ **Transform your social media presence with memorable, personalized links that never expire** -[🚀 Live Demo](https://linkb-one.vercel.app) • [📖 Documentation](./frontend/src/components/Documentation.jsx) • [🐛 Report Bug](https://github.com/DpkRn/LinkBridger/issues) • [💡 Request Feature](https://github.com/DpkRn/LinkBridger/issues) • [💬 Discuss](https://github.com/DpkRn/LinkBridger/discussions) +[🚀 Live Demo](https://clickly.cv) • [📖 Documentation](./frontend/src/components/Documentation.jsx) • [🐛 Report Bug](https://github.com/DpkRn/LinkBridger/issues) • [💡 Request Feature](https://github.com/DpkRn/LinkBridger/issues) • [💬 Discuss](https://github.com/DpkRn/LinkBridger/discussions) ![LinkBridger Banner](https://via.placeholder.com/1200x400/4F46E5/FFFFFF?text=LinkBridger+-+Your+Personalized+Link+Manager) @@ -63,7 +63,7 @@ In today's digital-first world, professionals, creators, and developers manage m LinkBridger bridges this gap by providing: -- 🎯 **Personalized URLs** using your username and platform name (e.g., `linkb-one.vercel.app/yourname/linkedin`) +- 🎯 **Personalized URLs** using your username and platform name (e.g., `clickly.cv/yourname/linkedin`) - 🌐 **Single Hub Link** that acts as a landing page for all your profiles - 📊 **Built-in Analytics** to track clicks and understand your audience - 🔄 **Centralized Updates** that reflect instantly across all platforms @@ -76,7 +76,7 @@ LinkBridger bridges this gap by providing: 1. **Sign Up**: Create an account with your email and choose a username 2. **Add Platforms**: Add any social media platform with its destination URL -3. **Get Your Links**: Receive personalized links like `linkb-one.vercel.app/username/platform` +3. **Get Your Links**: Receive personalized links like `clickly.cv/username/platform` 4. **Share Everywhere**: Use your links on resumes, business cards, email signatures, and social media 5. **Update Anytime**: Change destination URLs anytime - all your shared links update automatically 6. **Track Performance**: Monitor clicks and engagement through the dashboard @@ -127,13 +127,13 @@ LinkBridger bridges this gap by providing: ### 1. **Memorable & Professional Links** 🎯 **Before**: `https://www.linkedin.com/in/john-doe-software-engineer-123456789/` -**After**: `https://linkb-one.vercel.app/johndoe/linkedin` +**After**: `https://clickly.cv/johndoe/linkedin` Your audience will remember it! The pattern is simple: `domain/username/platform`. Once someone knows your username, they can easily guess your other platform links. ### 2. **Single Hub for All Profiles** 🌐 -Share one link (`https://linkb-one.vercel.app/yourname`) that acts as a beautiful landing page for all your social profiles. Visitors can browse and choose which platform to visit, creating a professional digital business card. +Share one link (`https://clickly.cv/yourname`) that acts as a beautiful landing page for all your social profiles. Visitors can browse and choose which platform to visit, creating a professional digital business card. **Benefits:** - One link to remember instead of dozens @@ -300,7 +300,7 @@ Getting started is simple: ### Why LinkBridger is Superior #### 1. **Brand Identity** 🎯 -Your links become part of your brand identity, not generic shortened URLs. When someone sees `linkb-one.vercel.app/yourname/linkedin`, they immediately know it's your link and can easily remember the pattern for other platforms. +Your links become part of your brand identity, not generic shortened URLs. When someone sees `clickly.cv/yourname/linkedin`, they immediately know it's your link and can easily remember the pattern for other platforms. #### 2. **User Trust** 🤝 Transparent, readable URLs build more trust than mysterious short codes. Users can see where the link will take them before clicking, reducing phishing concerns. @@ -355,18 +355,18 @@ See LinkBridger in action with these real-world examples: **Single Hub Link** (Access all profiles - acts as a digital business card): ``` -https://linkb-one.vercel.app/dpkrn +https://clickly.cv/dpkrn ``` Visit this link to see a beautiful landing page with all social profiles! **Individual Platform Links** (Direct redirects to specific platforms): -- **LinkedIn**: [`https://linkb-one.vercel.app/dpkrn/linkedin`](https://linkb-one.vercel.app/dpkrn/linkedin) -- **GitHub**: [`https://linkb-one.vercel.app/dpkrn/github`](https://linkb-one.vercel.app/dpkrn/github) -- **LeetCode**: [`https://linkb-one.vercel.app/dpkrn/leetcode`](https://linkb-one.vercel.app/dpkrn/leetcode) -- **Portfolio**: [`https://linkb-one.vercel.app/dpkrn/portfolio`](https://linkb-one.vercel.app/dpkrn/portfolio) -- **Instagram**: [`https://linkb-one.vercel.app/dpkrn/instagram`](https://linkb-one.vercel.app/dpkrn/instagram) -- **Facebook**: [`https://linkb-one.vercel.app/dpkrn/facebook`](https://linkb-one.vercel.app/dpkrn/facebook) -- **Codeforces**: [`https://linkb-one.vercel.app/dpkrn/codeforces`](https://linkb-one.vercel.app/dpkrn/codeforces) +- **LinkedIn**: [`https://clickly.cv/dpkrn/linkedin`](https://clickly.cv/dpkrn/linkedin) +- **GitHub**: [`https://clickly.cv/dpkrn/github`](https://clickly.cv/dpkrn/github) +- **LeetCode**: [`https://clickly.cv/dpkrn/leetcode`](https://clickly.cv/dpkrn/leetcode) +- **Portfolio**: [`https://clickly.cv/dpkrn/portfolio`](https://clickly.cv/dpkrn/portfolio) +- **Instagram**: [`https://clickly.cv/dpkrn/instagram`](https://clickly.cv/dpkrn/instagram) +- **Facebook**: [`https://clickly.cv/dpkrn/facebook`](https://clickly.cv/dpkrn/facebook) +- **Codeforces**: [`https://clickly.cv/dpkrn/codeforces`](https://clickly.cv/dpkrn/codeforces) **Notice**: Only the platform name changes; the username remains consistent across all links! This makes it incredibly easy to remember and share. @@ -814,7 +814,7 @@ Contributors will be: [![GitHub](https://img.shields.io/badge/GitHub-DpkRn-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/DpkRn) [![Email](https://img.shields.io/badge/Email-d.wizard.techno@gmail.com-D14836?style=for-the-badge&logo=gmail&logoColor=white)](mailto:d.wizard.techno@gmail.com) [![LinkedIn](https://img.shields.io/badge/LinkedIn-Profile-0077B5?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/in/dpkrn) -[![Portfolio](https://img.shields.io/badge/Portfolio-Website-FF6B6B?style=for-the-badge&logo=firefox&logoColor=white)](https://linkb-one.vercel.app/dpkrn) +[![Portfolio](https://img.shields.io/badge/Portfolio-Website-FF6B6B?style=for-the-badge&logo=firefox&logoColor=white)](https://clickly.cv/dpkrn) **Passionate about building innovative solutions and contributing to open source** @@ -844,14 +844,14 @@ To create tools that simplify digital life and empower users to build their onli **Professional Links:** - 💼 **LinkedIn**: [Connect on LinkedIn](https://www.linkedin.com/in/dpkrn) - Let's network! - 🐙 **GitHub**: [@DpkRn](https://github.com/DpkRn) - Check out my other projects -- 🌐 **Portfolio**: [View Portfolio](https://linkb-one.vercel.app/dpkrn) - See my work +- 🌐 **Portfolio**: [View Portfolio](https://clickly.cv/dpkrn) - See my work - 📧 **Email**: [d.wizard.techno@gmail.com](mailto:d.wizard.techno@gmail.com) - Let's collaborate! **Social Media:** -- 📸 **Instagram**: [Follow on Instagram](https://linkb-one.vercel.app/dpkrn/instagram) (if available) -- 👨‍💻 **GitHub Profile**: [View GitHub Profile](https://linkb-one.vercel.app/dpkrn/github) -- 💻 **LeetCode**: [LeetCode Profile](https://linkb-one.vercel.app/dpkrn/leetcode) (if available) -- 🎯 **Codeforces**: [Codeforces Profile](https://linkb-one.vercel.app/dpkrn/codeforces) (if available) +- 📸 **Instagram**: [Follow on Instagram](https://clickly.cv/dpkrn/instagram) (if available) +- 👨‍💻 **GitHub Profile**: [View GitHub Profile](https://clickly.cv/dpkrn/github) +- 💻 **LeetCode**: [LeetCode Profile](https://clickly.cv/dpkrn/leetcode) (if available) +- 🎯 **Codeforces**: [Codeforces Profile](https://clickly.cv/dpkrn/codeforces) (if available) ### 🎓 Learning & Growth @@ -950,7 +950,7 @@ To everyone who: ### 🚀 Ready to Get Started? -[**Try LinkBridger Now**](https://linkb-one.vercel.app) • [**View Documentation**](./frontend/src/components/Documentation.jsx) • [**Contribute**](#-contributing) +[**Try LinkBridger Now**](https://clickly.cv) • [**View Documentation**](./frontend/src/components/Documentation.jsx) • [**Contribute**](#-contributing) **Questions?** [Email us](mailto:d.wizard.techno@gmail.com) - We're here to help! 💬 diff --git a/backend/index.js b/backend/index.js index 3b7b1bb..a03c2cc 100644 --- a/backend/index.js +++ b/backend/index.js @@ -69,7 +69,7 @@ app.use(helmet.contentSecurityPolicy({ scriptSrc: ["'self'", "https://vercel.live", "https://*.vercel.app"], // Allow Vercel scripts imgSrc: ["'self'", "data:", "https://res.cloudinary.com"], // Add your image host if needed styleSrc: ["'self'", "'unsafe-inline'"], // Allow inline styles if needed - connectSrc: ["'self'", "https://linkb-one.vercel.app","https://linkb-one.vercel.app/*","https://clickly.cv/*"], // Add your API backend here + connectSrc: ["'self'", "https://clickly.cv","https://clickly.cv/*"], // Add your API backend here // Add more directives as needed } })); diff --git a/backend/views/linktree.ejs b/backend/views/linktree.ejs index d4996a1..d58d2f4 100644 --- a/backend/views/linktree.ejs +++ b/backend/views/linktree.ejs @@ -24,7 +24,7 @@

- https://linkb-one.vercel.app/dpkrn + https://clickly.cv/dpkrn

@@ -226,10 +226,10 @@ const Documentation = () => {

- https://linkb-one.vercel.app/dpkrn/ + https://clickly.cv/dpkrn/ {" "}

@@ -264,7 +264,7 @@ const Documentation = () => { { img: "logo.png", title: "All links at one place", - desc: "Get a single special link for all your platforms, just like Linktree. Share one URL (e.g., https://linkb-one.vercel.app/username) and let your audience access all your profiles from one place.", + desc: "Get a single special link for all your platforms, just like Linktree. Share one URL (e.g., https://clickly.cv/username) and let your audience access all your profiles from one place.", }, { img: "update.webp", @@ -334,7 +334,7 @@ const Documentation = () => {
  • Choose a Username: Pick a username that's easy to remember (e.g., dpkrn). Your link will follow this format: - `https://linkb-one.vercel.app/your-username/instagram`. + `https://clickly.cv/your-username/instagram`.
  • Verify Your Account: Complete email verification to @@ -361,19 +361,19 @@ const Documentation = () => {

    Instagram:{" "} - https://linkb-one.vercel.app/dpkrn/instagram + https://clickly.cv/dpkrn/instagram

    LeetCode:{" "} - https://linkb-one.vercel.app/dpkrn/leetcode + https://clickly.cv/dpkrn/leetcode

    diff --git a/frontend/src/components/linkcard/Linkcard.jsx b/frontend/src/components/linkcard/Linkcard.jsx index 886d482..2155b83 100644 --- a/frontend/src/components/linkcard/Linkcard.jsx +++ b/frontend/src/components/linkcard/Linkcard.jsx @@ -95,7 +95,7 @@ const Linkcard = ({ sources }) => {
    - {`https://linkb-one.vercel.app/${username}/${source}`} + {`https://clickly.cv/${username}/${source}`} { {showBridge&&
    - {`https://linkb-one.vercel.app/${username}/${source}`} + {`https://clickly.cv/${username}/${source}`} {

    Old Link (will become invalid):

    - https://linkb-one.vercel.app/{username}/{editLinkData.source} + https://clickly.cv/{username}/{editLinkData.source}

    New Link:

    - https://linkb-one.vercel.app/{username}/{platform.toLowerCase()} + https://clickly.cv/{username}/{platform.toLowerCase()}

    diff --git a/frontend/src/components/pages/LinkPage.jsx b/frontend/src/components/pages/LinkPage.jsx index 4aa3984..b570688 100644 --- a/frontend/src/components/pages/LinkPage.jsx +++ b/frontend/src/components/pages/LinkPage.jsx @@ -72,7 +72,7 @@ const LinkPage = () => { Your linktree is live on:
    - {`https://linkb-one.vercel.app/${username}`} + {`https://clickly.cv/${username}`} Date: Thu, 18 Dec 2025 02:43:39 +0530 Subject: [PATCH 003/166] Enhanced HomePage with new interactive features and improved typography. Added Poppins and Montserrat fonts for better visual appeal. Updated routing to set HomePage as the landing page. Fixed mouse position calculation for gradient effects and optimized performance in AuthPage and Documentation components. Redesigned AuthPage with modern animations and improved user experience. Updated Documentation to include new features and enhanced layout. --- frontend/CHANGELOG.md | 180 ++- frontend/index.html | 1 + frontend/src/App.css | 153 +- frontend/src/App.jsx | 3 +- frontend/src/assets/fonts.css | 9 +- frontend/src/components/AuthPage.jsx | 761 +++++---- frontend/src/components/Documentation.jsx | 1648 +++++++++++++++----- frontend/src/components/pages/HomePage.jsx | 684 +++++++- 8 files changed, 2685 insertions(+), 754 deletions(-) diff --git a/frontend/CHANGELOG.md b/frontend/CHANGELOG.md index 1deb23f..efbf809 100644 --- a/frontend/CHANGELOG.md +++ b/frontend/CHANGELOG.md @@ -7,7 +7,185 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Changed - 2024-12-19 +### Fixed - 2024-12-19 ++- **Added Missing Features to HomePage and Documentation**: Added two important features that were missing ++ - **All Links at One Place**: Added feature highlighting the hub link functionality ++ - Users can access all their links by visiting https://clickly.cv/username (without platform name) ++ - Creates a beautiful landing page showing all profiles in one place ++ - Perfect for sharing in bios, resumes, and business cards ++ - Added to HomePage features section with FaHome icon ++ - Updated in Documentation page with enhanced description ++ - **Real-Time Email Notifications**: Added email notification feature ++ - Get instant email notifications every time someone visits your links ++ - Stay informed about who's checking out your profiles in real-time ++ - Perfect for tracking engagement and knowing when potential clients or employers view your links ++ - Added to HomePage features section with FaEnvelope icon ++ - Added to Documentation page features section ++ - Mentioned in "How It Works" section for better visibility + ++- **Documentation Page Complete Redesign**: Completely redesigned documentation page with modern animations and interactive elements ++ - Added interactive mouse-tracking gradient orbs that follow cursor movement ++ - Implemented glassmorphism design with backdrop blur effects throughout ++ - Enhanced all sections with smooth scroll animations using Framer Motion ++ - Redesigned feature cards with gradient icons, hover effects, and scale animations ++ - Added interactive FAQ section with smooth expand/collapse animations ++ - Enhanced "How It Works" section with step-by-step cards and icons ++ - Redesigned testimonials with star ratings and hover effects ++ - Added gradient buttons with hover animations ++ - Improved visual hierarchy with gradient text headings ++ - Added animated grid background pattern for depth ++ - Enhanced all links with hover effects and smooth transitions ++ - Redesigned future enhancements section with gradient cards ++ - Improved spacing, typography, and responsive design ++ - Added icon components from react-icons for better visual appeal ++ - Maintained all original content and functionality ++ - Fully responsive design optimized for all screen sizes ++ - **NEW: Use Cases Section**: Added comprehensive use cases for different user types ++ - Job Seekers, Content Creators, Developers, Students, Businesses, and Freelancers ++ - Each use case includes description, icon, gradient styling, and example applications ++ - Interactive cards with hover effects and smooth animations ++ - **NEW: Best Practices Section**: Added best practices guide for optimal LinkBridger usage ++ - Six key best practices with icons and detailed explanations + - Tips on username selection, platform naming, link updates, analytics, testing, and sharing ++ - **NEW: Platform Support Section**: Added comprehensive list of supported platforms ++ - Visual grid showcasing 30+ supported platforms (LinkedIn, GitHub, Instagram, etc.) ++ - Interactive platform cards with hover effects ++ - Emphasizes that any platform with a URL can be supported ++ - **NEW: Security & Privacy Section**: Added security and privacy information ++ - Four key security features: Secure Authentication, Privacy First, HTTPS Encryption, Secure Infrastructure ++ - Gradient icon cards with detailed explanations ++ - Builds user trust and confidence ++ - **NEW: Troubleshooting Section**: Added comprehensive troubleshooting guide ++ - Six common issues with detailed solutions ++ - Color-coded icons for different issue types ++ - Contact information for additional support ++ - Interactive cards with hover effects + ++- **Mouse Position Calculation Bug**: Fixed incorrect mouse position calculation in HomePage.jsx ++ - Previous calculation `(e.clientX / window.innerWidth - 0.5) * 20` produced values in -10 to 10 pixel range ++ - These small/negative values caused incorrect gradient positioning when used in CSS `radial-gradient` ++ - Changed to percentage-based calculation: `(e.clientX / window.innerWidth) * 100` for x and `(e.clientY / window.innerHeight) * 100` for y ++ - Updated gradient to use percentages (`${mousePosition.x}% ${mousePosition.y}%`) instead of pixels ++ - Gradient now correctly follows mouse cursor across entire viewport (0% to 100%) ++ - Fixed backgroundPosition animation to use percentages with proper bounds checking ++ - Eliminates off-screen gradient placement and ensures smooth mouse tracking + ++- **State Update on Unmounted Component**: Fixed memory leak warnings in AuthPage.jsx ++ - Removed `setLoading(false)` calls before `navigate()` in both `handleSignUp` and `handleLogin` ++ - After navigation, component unmounts, causing finally block's `setLoading(false)` to update unmounted component ++ - Now only the finally block handles `setLoading(false)`, preventing state updates on unmounted components ++ - Moved form state resets (`setLoginEmail`, `setLoginPassword`) before navigation to ensure they execute ++ - Eliminates React warnings about memory leaks and state updates on unmounted components + ++- **AuthPage Complete Redesign**: Completely redesigned login/signup page with modern animations ++ - Removed old CSS-based design that had z-index conflicts preventing input clicks ++ - Created new modern design with glassmorphism effects and gradient backgrounds ++ - Added interactive mouse-tracking gradient orbs that follow cursor movement ++ - Implemented smooth form transitions with Framer Motion AnimatePresence ++ - Added animated toggle buttons with layoutId for smooth tab switching ++ - Modern input fields with icons, focus states, and proper z-index hierarchy ++ - Gradient buttons with hover animations and loading states ++ - Animated grid background pattern for depth ++ - Feature pills showcasing key benefits ++ - Fully responsive design for all screen sizes ++ - All input fields now properly clickable and functional ++ - Maintained all original authentication logic and functionality ++ - Added password visibility toggles with eye icons ++ - Username availability indicator with animated icons ++ - Smooth entrance animations for all form elements + ++- **Mouse Position Throttling Logic Bug**: Fixed incorrect throttle timestamp update in RAF callback ++ - `lastUpdateTime` was being updated inside RAF callback, causing continuous unnecessary RAF scheduling ++ - When RAF was scheduled, `lastUpdateTime` remained old, so subsequent mousemove events would see `now - lastUpdateTime < throttleDelay` as true ++ - This caused multiple RAFs to be scheduled unnecessarily, even though `rafId === null` check prevented duplicates ++ - Fixed by updating `lastUpdateTime` immediately when scheduling RAF, not inside the callback ++ - Now subsequent mousemove events correctly see we're still within throttle window ++ - Prevents unnecessary RAF scheduling and improves performance + ++- **Mouse Position Throttling Bug**: Fixed stale mouse position in RAF callback ++ - RAF callback was capturing event coordinates from first mousemove event via closure ++ - When multiple mousemove events fired before RAF executed, it used stale coordinates ++ - Added `latestMousePositionRef` to store latest position, updated on every mousemove ++ - RAF callback now reads from ref at execution time, ensuring latest position is used ++ - Eliminates lag in mouse tracking gradient effect ++ - Mouse tracking now smoothly follows actual cursor movement + ++- **AuthPage Functionality**: Fixed login and signup forms not working properly ++ - Added Font Awesome CDN link to index.html for icon support ++ - Changed button types from "button" to "submit" for proper form submission ++ - Added `onSubmit` handlers to forms instead of `onClick` on buttons ++ - Added form validation with `required` and `minLength` attributes ++ - Added `disabled` state to buttons during loading to prevent multiple submissions ++ - Improved user experience with proper form validation and loading states ++ - Fixed email input types to use `type="email"` for better validation + ++- **AuthPage CSS Overlap Issue**: Fixed login page overlapping HomePage ++ - Scoped all AuthPage CSS classes to `.auth-container` to prevent global style conflicts ++ - Added `isolation: isolate` to create new stacking context for AuthPage ++ - Updated all `.container` references in App.css to `.auth-container` to prevent affecting other pages ++ - Scoped form, input-field, panel, button, and animation styles to AuthPage only ++ - Prevents AuthPage styles from leaking to HomePage and other components ++ - Ensures proper z-index isolation between pages ++ ++- **AnimatedCounter Performance Issue**: Fixed component recreation on every parent re-render ++ - Moved `AnimatedCounter` component outside of `HomePage` to prevent recreation on re-renders ++ - `AnimatedCounter` was being recreated on every `mousePosition` state update, causing unmount/remount cycles ++ - This interrupted animations and canceled pending `requestAnimationFrame` calls ++ - Component is now stable and only re-renders when its own props change ++ - Added `statsInView` as a prop instead of using closure to maintain proper dependency tracking ++ - Optimized mouse position updates with throttling (16ms delay, ~60fps) to reduce unnecessary re-renders ++ - Added `passive: true` to mouse event listener for better performance ++ - Proper cleanup of `requestAnimationFrame` in mouse handler cleanup function ++ ++- **AnimatedCounter Memory Leak**: Fixed memory leak in AnimatedCounter component ++ - Added proper cleanup for `requestAnimationFrame` using `cancelAnimationFrame` ++ - Added `isMountedRef` to prevent state updates on unmounted components ++ - Stored animation frame ID in `animationFrameRef` for proper cancellation ++ - Prevents React warnings about updating state on unmounted components ++ - Eliminates memory leaks when component unmounts during animation ++ ++- **Twitter Icon Import Error**: Fixed import error for Twitter icon ++ - Changed `SiTwitter` to `SiX` from `react-icons/si` (Twitter rebranded to X) ++ - Resolves "does not provide an export named 'SiTwitter'" error ++ - Updated to use correct export name for X/Twitter icon ++ ++### Added - 2024-12-19 ++- **Interactive Home Page**: Completely redesigned landing page with modern animations and investor-friendly content ++ - Hero section with typewriter effect and gradient text animations ++ - Flip words animation for platform names (LinkedIn, GitHub, Instagram, etc.) ++ - Mouse-tracking background effects for interactive experience ++ - Animated scroll indicator with smooth transitions ++ - Platform icons showcase with hover animations ++ - Statistics section with animated counters (10,000+ users, 50,000+ links, 1M+ clicks, 99% uptime) ++ - Interactive feature cards with gradient icons and hover effects ++ - Benefits section for three target audiences (Professionals, Creators, Developers) ++ - Final CTA section with gradient background ++ - Navigation header for non-authenticated users with logo and action buttons ++ - Fully responsive design optimized for mobile, tablet, and desktop devices ++ - Mobile-first approach with breakpoints: sm (640px), md (768px), lg (1024px) ++ - Responsive text sizes: text-2xl sm:text-3xl md:text-4xl lg:text-5xl ++ - Flexible grid layouts: grid-cols-2 md:grid-cols-4 for stats, md:grid-cols-2 lg:grid-cols-3 for features ++ - Responsive padding and spacing: px-4 sm:px-6 lg:px-8, gap-4 sm:gap-6 md:gap-8 ++ - Mobile-optimized buttons: full-width on mobile, auto-width on larger screens ++ - Responsive navigation: logo text hidden on very small screens, compact button sizes ++ - Touch-friendly interactive elements with appropriate sizing for mobile devices ++ - Dark mode support throughout all sections ++ - Smooth scroll animations using Framer Motion ++ - Performance optimized with `useInView` hooks for scroll-triggered animations ++ ++- **Enhanced Typography**: Upgraded font system for better visual appeal ++ - Added Poppins font family for body text (weights 300-900) ++ - Added Montserrat font family for headings (weights 300-900) ++ - Maintained Inter font as fallback ++ - Improved font weights and letter spacing for better readability ++ - Enhanced typography hierarchy across the application ++ ++- **Routing Updates**: Updated App.jsx to use new HomePage as landing page ++ - Changed root route (`/`) to display new interactive HomePage ++ - HomePage now serves as the primary landing experience for non-authenticated users ++ - Maintains existing Documentation page at `/doc` route ++ ++### Changed - 2024-12-19 - **Edit Link Functionality**: Redesigned edit link feature to reuse CreateBridge component - Removed prompt dialog for editing links - Edit button now populates CreateBridge form with existing link data diff --git a/frontend/index.html b/frontend/index.html index f1849a8..d7f1c92 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,6 +4,7 @@ + LinkBridger diff --git a/frontend/src/App.css b/frontend/src/App.css index 4354741..6283316 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -11,24 +11,28 @@ input { font-family: "Poppins", sans-serif; } -.container { +/* Scoped to AuthPage only - use .auth-container class */ +.auth-container, +.container.auth-container { position: relative; - width: 100vw; - min-width: 100vw; + width: 100vw; + min-width: 100vw; /* background-color: #fff; */ min-height: 100vh; overflow: hidden; + isolation: isolate; /* Create new stacking context */ } -.forms-container { +.auth-container .forms-container { position: absolute; width: 100%; height: 100%; top: 0; left: 0; + z-index: 1; } -.signin-signup { +.auth-container .signin-signup { position: absolute; top: 50%; transform: translate(-50%, -50%); @@ -40,7 +44,7 @@ input { z-index: 5; } -form { +.auth-container form { display: flex; align-items: center; justify-content: center; @@ -52,27 +56,27 @@ form { grid-row: 1 / 2; } -form.sign-up-form { +.auth-container form.sign-up-form { opacity: 0; z-index: 1; } -form.sign-in-form { +.auth-container form.sign-in-form { z-index: 2; } -.title { +.auth-container .title { font-size: 2.2rem; color: #444; margin-bottom: 10px; transition: color 0.3s ease; } -.dark .title { +.dark .auth-container .title { color: #ffffff; } -.input-field { +.auth-container .input-field { max-width: 380px; width: 100%; background-color: #ffffff; @@ -86,11 +90,11 @@ form.sign-in-form { transition: background-color 0.3s ease; } -.dark .input-field { +.dark .auth-container .input-field { background-color: #374151; } -.input-field i { +.auth-container .input-field i { text-align: center; line-height: 55px; color: #acacac; @@ -98,11 +102,11 @@ form.sign-in-form { font-size: 1.1rem; } -.dark .input-field i { +.dark .auth-container .input-field i { color: #9ca3af; } -.input-field input { +.auth-container .input-field input { background: none; outline: none; border: none; @@ -113,31 +117,31 @@ form.sign-in-form { transition: color 0.3s ease; } -.dark .input-field input { +.dark .auth-container .input-field input { color: #f3f4f6; } -.input-field input::placeholder { +.auth-container .input-field input::placeholder { color: #aaa; font-weight: 500; transition: color 0.3s ease; } -.dark .input-field input::placeholder { +.dark .auth-container .input-field input::placeholder { color: #9ca3af; } -.social-text { +.auth-container .social-text { padding: 0.7rem 0; font-size: 1rem; } -.social-media { +.auth-container .social-media { display: flex; justify-content: center; } -.social-icon { +.auth-container .social-icon { height: 46px; width: 46px; display: flex; @@ -152,12 +156,12 @@ form.sign-in-form { transition: 0.3s; } -.social-icon:hover { +.auth-container .social-icon:hover { color: #4481eb; border-color: #4481eb; } -.btn { +.auth-container .btn { width: 150px; background-color: #5995fd; border: none; @@ -172,10 +176,10 @@ form.sign-in-form { transition: 0.5s; } -.btn:hover { +.auth-container .btn:hover { background-color: #4d84e2; } -.panels-container { +.auth-container .panels-container { position: absolute; height: 100%; width: 100%; @@ -183,9 +187,10 @@ form.sign-in-form { left: 0; display: grid; grid-template-columns: repeat(2, 1fr); + z-index: 1; } -.container:before { +.auth-container:before { content: ""; position: absolute; height: 2000px; @@ -199,13 +204,13 @@ form.sign-in-form { z-index: 6; } -.image { +.auth-container .image { width: 100%; transition: transform 1.1s ease-in-out; transition-delay: 0.4s; } -.panel { +.auth-container .panel { display: flex; flex-direction: column; align-items: flex-end; @@ -214,34 +219,34 @@ form.sign-in-form { z-index: 6; } -.left-panel { +.auth-container .left-panel { pointer-events: all; padding: 3rem 17% 2rem 12%; } -.right-panel { +.auth-container .right-panel { pointer-events: none; padding: 3rem 12% 2rem 17%; } -.panel .content { +.auth-container .panel .content { color: #fff; transition: transform 0.9s ease-in-out; transition-delay: 0.6s; } -.panel h3 { +.auth-container .panel h3 { font-weight: 600; line-height: 1; font-size: 1.5rem; } -.panel p { +.auth-container .panel p { font-size: 0.95rem; padding: 0.7rem 0; } -.btn.transparent { +.auth-container .btn.transparent { margin: 0; background: none; border: 2px solid #fff; @@ -251,73 +256,73 @@ form.sign-in-form { font-size: 0.8rem; } -.right-panel .image, -.right-panel .content { +.auth-container .right-panel .image, +.auth-container .right-panel .content { transform: translateX(800px); } /* ANIMATION */ -.container.sign-up-mode:before { +.auth-container.sign-up-mode:before { transform: translate(100%, -50%); right: 52%; } -.container.sign-up-mode .left-panel .image, -.container.sign-up-mode .left-panel .content { +.auth-container.sign-up-mode .left-panel .image, +.auth-container.sign-up-mode .left-panel .content { transform: translateX(-800px); } -.container.sign-up-mode .signin-signup { +.auth-container.sign-up-mode .signin-signup { left: 25%; } -.container.sign-up-mode form.sign-up-form { +.auth-container.sign-up-mode form.sign-up-form { opacity: 1; z-index: 2; } -.container.sign-up-mode form.sign-in-form { +.auth-container.sign-up-mode form.sign-in-form { opacity: 0; z-index: 1; } -.container.sign-up-mode .right-panel .image, -.container.sign-up-mode .right-panel .content { +.auth-container.sign-up-mode .right-panel .image, +.auth-container.sign-up-mode .right-panel .content { transform: translateX(0%); } -.container.sign-up-mode .left-panel { +.auth-container.sign-up-mode .left-panel { pointer-events: none; } -.container.sign-up-mode .right-panel { +.auth-container.sign-up-mode .right-panel { pointer-events: all; } @media (max-width: 870px) { - .container { + .auth-container { min-height: 800px; height: 100vh; } - .signin-signup { + .auth-container .signin-signup { width: 100%; top: 95%; transform: translate(-50%, -100%); transition: 1s 0.8s ease-in-out; } - .signin-signup, - .container.sign-up-mode .signin-signup { + .auth-container .signin-signup, + .auth-container.sign-up-mode .signin-signup { left: 50%; } - .panels-container { + .auth-container .panels-container { grid-template-columns: 1fr; grid-template-rows: 1fr 2fr 1fr; } - .panel { + .auth-container .panel { flex-direction: row; justify-content: space-around; align-items: center; @@ -325,42 +330,42 @@ form.sign-in-form { grid-column: 1 / 2; } - .right-panel { + .auth-container .right-panel { grid-row: 3 / 4; } - .left-panel { + .auth-container .left-panel { grid-row: 1 / 2; } - .image { + .auth-container .image { width: 200px; transition: transform 0.9s ease-in-out; transition-delay: 0.6s; } - .panel .content { + .auth-container .panel .content { padding-right: 15%; transition: transform 0.9s ease-in-out; transition-delay: 0.8s; } - .panel h3 { + .auth-container .panel h3 { font-size: 1.2rem; } - .panel p { + .auth-container .panel p { font-size: 0.7rem; padding: 0.5rem 0; } - .btn.transparent { + .auth-container .btn.transparent { width: 110px; height: 35px; font-size: 0.7rem; } - .container:before { + .auth-container:before { width: 1500px; height: 1500px; transform: translateX(-50%); @@ -371,54 +376,54 @@ form.sign-in-form { transition: 2s ease-in-out; } - .container.sign-up-mode:before { + .auth-container.sign-up-mode:before { transform: translate(-50%, 100%); bottom: 32%; right: initial; } - .container.sign-up-mode .left-panel .image, - .container.sign-up-mode .left-panel .content { + .auth-container.sign-up-mode .left-panel .image, + .auth-container.sign-up-mode .left-panel .content { transform: translateY(-300px); } - .container.sign-up-mode .right-panel .image, - .container.sign-up-mode .right-panel .content { + .auth-container.sign-up-mode .right-panel .image, + .auth-container.sign-up-mode .right-panel .content { transform: translateY(0px); } - .right-panel .image, - .right-panel .content { + .auth-container .right-panel .image, + .auth-container .right-panel .content { transform: translateY(300px); } - .container.sign-up-mode .signin-signup { + .auth-container.sign-up-mode .signin-signup { top: 5%; transform: translate(-50%, 0); } } @media (max-width: 570px) { - form { + .auth-container form { padding: 0 1.5rem; } - .image { + .auth-container .image { display: none; } - .panel .content { + .auth-container .panel .content { padding: 0.5rem 1rem; } - .container { + .auth-container { padding: 1.5rem; } - .container:before { + .auth-container:before { bottom: 72%; left: 50%; } - .container.sign-up-mode:before { + .auth-container.sign-up-mode:before { bottom: 28%; left: 50%; } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 25e4cbd..fe6aacf 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -12,6 +12,7 @@ import Nav from './components/navbar/Nav' import VerifiedPage from './components/VerifiedPage' import PasswordReset from './components/PasswordReset' import Documentation from './components/Documentation' +import HomePage from './components/pages/HomePage' import LinkPage from './components/pages/LinkPage' import ProfilePage from './components/pages/Profile' import NotFound from './components/pages/NotFound' @@ -122,7 +123,7 @@ function App() { }/> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/frontend/src/assets/fonts.css b/frontend/src/assets/fonts.css index d841e97..161c07e 100644 --- a/frontend/src/assets/fonts.css +++ b/frontend/src/assets/fonts.css @@ -1,20 +1,23 @@ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700;800;900&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700;800;900&display=swap'); * { - font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif; + font-family: 'Poppins', 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } body { - font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif; + font-family: 'Poppins', 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif; font-weight: 400; line-height: 1.6; letter-spacing: -0.01em; } h1, h2, h3, h4, h5, h6 { - font-weight: 600; + font-family: 'Montserrat', 'Poppins', 'Inter', sans-serif; + font-weight: 700; line-height: 1.2; letter-spacing: -0.02em; } diff --git a/frontend/src/components/AuthPage.jsx b/frontend/src/components/AuthPage.jsx index 21abada..609913d 100644 --- a/frontend/src/components/AuthPage.jsx +++ b/frontend/src/components/AuthPage.jsx @@ -1,69 +1,42 @@ import React, { useRef, useEffect, useState } from "react"; -import "../App.css"; +import { motion, AnimatePresence } from "framer-motion"; import toast from "react-hot-toast"; import api from "../utils/api"; import { useDispatch } from "react-redux"; import { setAuthenticated, setUser } from "../redux/userSlice"; import { Link, useNavigate } from "react-router-dom"; import { GiSkullCrossedBones } from "react-icons/gi"; -import { FaCheck } from "react-icons/fa"; +import { FaCheck, FaEnvelope, FaLock, FaUser, FaEye, FaEyeSlash } from "react-icons/fa"; +import { HiSparkles } from "react-icons/hi2"; const AuthPage = () => { - //redux thing const dispatch = useDispatch(); - const navigate = useNavigate(); - const [loading, setLoading] = useState(false); - - // Create references for the elements you want to manipulate - const signInBtnRef = useRef(null); - const signUpBtnRef = useRef(null); - const containerRef = useRef(null); - + const [isSignUpMode, setIsSignUpMode] = useState(false); const [isAvailable, setAvailable] = useState(false); const [isShow, setShow] = useState(false); const [isShowSignup, setShowSignup] = useState(false); + const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); const [loginemail, setLoginEmail] = useState(""); const [loginpassword, setLoginPassword] = useState(""); - const [signinemail, setSigninEmail] = useState(""); const [signinpassword, setSigninPassword] = useState(""); const [username, setUsername] = useState(""); - // Using useEffect to set up the event listeners after rendering + // Mouse tracking for interactive background useEffect(() => { - const signInBtn = signInBtnRef.current; - const signUpBtn = signUpBtnRef.current; - const container = containerRef.current; - - const handleSignUpClick = () => { - container.classList.add("sign-up-mode"); - }; - - const handleSignInClick = () => { - container.classList.remove("sign-up-mode"); - }; - - // Add event listeners - if (signUpBtn && signInBtn) { - signUpBtn.addEventListener("click", handleSignUpClick); - signInBtn.addEventListener("click", handleSignInClick); - } - - // Clean up event listeners on component unmount - return () => { - if (signUpBtn && signInBtn) { - signUpBtn.removeEventListener("click", handleSignUpClick); - signInBtn.removeEventListener("click", handleSignInClick); - } + const handleMouseMove = (e) => { + setMousePosition({ + x: (e.clientX / window.innerWidth) * 100, + y: (e.clientY / window.innerHeight) * 100, + }); }; + window.addEventListener("mousemove", handleMouseMove, { passive: true }); + return () => window.removeEventListener("mousemove", handleMouseMove); }, []); - const validateSpace = () => {}; - - //Authentication const handleSignUp = async (e) => { e.preventDefault(); try { @@ -75,7 +48,6 @@ const AuthPage = () => { ); if (res.status === 201 && res.data.success) { toast.success(res.data.message); - setLoading(false); navigate("/verify", { state: { username: username, @@ -99,7 +71,6 @@ const AuthPage = () => { const handleLogin = async (e) => { e.preventDefault(); try { - setLoading(true); const res = await api.post( "/auth/signin", @@ -107,18 +78,15 @@ const AuthPage = () => { { withCredentials: true } ); if (res.status === 200 && res.data.success) { - // console.log(res.data.user.username) dispatch(setUser(res.data.user)); dispatch(setAuthenticated(true)); - navigate("/home", { replace: true }); - setLoading(false); setLoginEmail(""); setLoginPassword(""); - toast.success(`welcome ${res.data.user.username}`); + toast.success(`Welcome ${res.data.user.username}!`); + navigate("/home", { replace: true }); } } catch (err) { console.log(err); - const message = err.response?.data?.message || "Network Slow ! Try again"; toast.error(message); } finally { @@ -127,253 +95,510 @@ const AuthPage = () => { }; const checkAvailablity = async (usrnm) => { - if (usrnm.length < 5) return; + if (usrnm.length < 5) { + setAvailable(false); + return; + } try { const res = await api.post("/auth/checkavailablity", { username: usrnm }); if (res.status === 209 && res.data.success) { - console.log("not available"); setAvailable(false); } if (res.status === 200 && res.data.success) { - console.log("available"); setAvailable(true); } } catch (err) { console.log(err); setAvailable(false); - const message = err.response?.data?.message || "Server Internal Error !"; } }; + + const containerVariants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + duration: 0.6, + staggerChildren: 0.1, + }, + }, + }; + + const itemVariants = { + hidden: { opacity: 0, y: 20 }, + visible: { + opacity: 1, + y: 0, + transition: { duration: 0.5 }, + }, + }; + return ( - <> -
    -
    -
    - {/* Sign-in form */} -
    -

    Log in

    -
    - +
    + {/* Animated Background */} +
    + {/* Gradient Orbs */} + + + + + {/* Animated Grid */} +
    +
    - { - if (e.target.value.includes(" ")) { - toast.error("space not allowed"); - return; - } - setLoginEmail(e.target.value); - }} - /> -
    -
    - - { - if (e.target.value.includes(" ")) { - toast.error("space not allowed"); - return; - } - setLoginPassword(e.target.value); - }} - /> -
    -
    -
    - { - setShow((state) => !state); - }} - /> -
    -
    - +
    + +
    + {/* Left Side - Welcome Content */} + + +
    +
    +
    -
    - -

    Or Sign in with social platforms

    - + + + Welcome to LinkBridger + + + - Forgot password? - - {/* */} - + Transform your social media presence with personalized, memorable links that never expire. + - {/* Sign-up form */} -
    -

    Sign up

    + {/* Feature Pills */} + + {["Personalized Links", "Analytics", "Free Forever"].map((feature, idx) => ( + + {feature} + + ))} + + -
    - { - if (e.target.value.includes(" ")) { - toast.error("space not allowed"); - return; - } - checkAvailablity(e.target.value); - setUsername(e.target.value); - }} - /> - {!isAvailable ? ( - - ) : ( - - )} -
    + {/* Right Side - Auth Forms */} + +
    + {/* Form Container */} + + {/* Animated Border Gradient */} +
    + + {/* Toggle Buttons */} +
    + setIsSignUpMode(false)} + className={`flex-1 py-3 px-4 rounded-lg font-semibold text-sm transition-all duration-300 relative ${ + !isSignUpMode + ? "text-white" + : "text-gray-400 dark:text-gray-500" + }`} + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + > + {!isSignUpMode && ( + + )} + Sign In + + + setIsSignUpMode(true)} + className={`flex-1 py-3 px-4 rounded-lg font-semibold text-sm transition-all duration-300 relative ${ + isSignUpMode + ? "text-white" + : "text-gray-400 dark:text-gray-500" + }`} + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + > + {isSignUpMode && ( + + )} + Sign Up + +
    -
    - { - if (e.target.value.includes(" ")) { - toast.error("space not allowed"); - return; - } - setSigninEmail(e.target.value); - }} - /> -
    + {/* Forms */} + + {!isSignUpMode ? ( + + + Welcome Back + -
    - { - if (e.target.value.includes(" ")) { - toast.error("space not allowed"); - return; - } - setSigninPassword(e.target.value); - }} - /> -
    -
    -
    - { - setShowSignup((state) => !state); - }} - /> -
    -
    - -
    -
    + +
    + +
    + { + if (e.target.value.includes(" ")) { + toast.error("Space not allowed"); + return; + } + setLoginEmail(e.target.value); + }} + className="w-full pl-12 pr-4 py-4 bg-white/10 dark:bg-gray-800/50 border border-white/20 dark:border-gray-700 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-300 backdrop-blur-sm" + /> +
    - -

    Or Sign up with social platforms

    + +
    + +
    + { + if (e.target.value.includes(" ")) { + toast.error("Space not allowed"); + return; + } + setLoginPassword(e.target.value); + }} + className="w-full pl-12 pr-12 py-4 bg-white/10 dark:bg-gray-800/50 border border-white/20 dark:border-gray-700 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-300 backdrop-blur-sm" + /> + +
    - {/* */} - -
    -
    + + + + Forgot password? + + - {/* Panels */} -
    -
    -
    -

    New here ?

    -

    Generate Links You'll Never Forget - Get Started!

    - -
    - -
    -
    -
    -

    One of us ?

    -

    Turn Usernames into Smart Links - Quick and Simple!

    - -
    - + + + {loading ? ( + <> + + + + + Signing In... + + ) : ( + "Sign In" + )} + + + + + ) : ( + + + Create Account + + + +
    + +
    + { + if (e.target.value.includes(" ")) { + toast.error("Space not allowed"); + return; + } + checkAvailablity(e.target.value); + setUsername(e.target.value); + }} + className="w-full pl-12 pr-12 py-4 bg-white/10 dark:bg-gray-800/50 border border-white/20 dark:border-gray-700 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-300 backdrop-blur-sm" + /> +
    + {username.length >= 5 && ( + + {isAvailable ? ( + + ) : ( + + )} + + )} +
    +
    + + +
    + +
    + { + if (e.target.value.includes(" ")) { + toast.error("Space not allowed"); + return; + } + setSigninEmail(e.target.value); + }} + className="w-full pl-12 pr-4 py-4 bg-white/10 dark:bg-gray-800/50 border border-white/20 dark:border-gray-700 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-300 backdrop-blur-sm" + /> +
    + + +
    + +
    + { + if (e.target.value.includes(" ")) { + toast.error("Space not allowed"); + return; + } + setSigninPassword(e.target.value); + }} + className="w-full pl-12 pr-12 py-4 bg-white/10 dark:bg-gray-800/50 border border-white/20 dark:border-gray-700 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-300 backdrop-blur-sm" + /> + +
    + + + setShowSignup(!isShowSignup)} + className="w-4 h-4 rounded border-gray-400 text-purple-600 focus:ring-purple-500" + /> + + + + = 5 && !isAvailable)} + initial={{ opacity: 0, y: 10 }} + animate={{ opacity: 1, y: 0 }} + transition={{ delay: 0.5 }} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + className="w-full py-4 bg-gradient-to-r from-purple-600 via-pink-600 to-blue-600 text-white font-bold rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed relative overflow-hidden group" + > + + {loading ? ( + <> + + + + + Creating Account... + + ) : ( + "Create Account" + )} + + + +
    + )} + + +
    +
    -
    +
    - +
    ); }; diff --git a/frontend/src/components/Documentation.jsx b/frontend/src/components/Documentation.jsx index f00cce1..e626766 100644 --- a/frontend/src/components/Documentation.jsx +++ b/frontend/src/components/Documentation.jsx @@ -1,6 +1,6 @@ -import React, { useState, useRef } from "react"; +import React, { useState, useRef, useEffect } from "react"; import { useInView } from "framer-motion"; -import { motion } from "framer-motion"; +import { motion, AnimatePresence } from "framer-motion"; import { useSelector } from "react-redux"; import { useLocation, useNavigate } from "react-router-dom"; import { TypewriterEffect } from "./ui/typewriter-effect"; @@ -13,15 +13,64 @@ import { FlipWords } from "./ui/flip-words"; import { BackgroundBeamsWithCollision } from "./ui/background-beams-with-collision"; import { ContainerScroll } from "./ui/container-scroll-animation"; import Footer from "./footer/Footer"; +import { + FaRocket, + FaLink, + FaChartLine, + FaSyncAlt, + FaCog, + FaChevronDown, + FaChevronUp, + FaStar, + FaArrowRight, + FaCheckCircle, + FaLightbulb, + FaShieldAlt, + FaClock, + FaPalette, + FaBriefcase, + FaUserTie, + FaCode, + FaGraduationCap, + FaUsers, + FaLock, + FaEye, + FaServer, + FaExclamationTriangle, + FaQuestionCircle, + FaCheck, + FaTimes, + FaHome, + FaEnvelope +} from "react-icons/fa"; const Documentation = () => { const navigate = useNavigate(); const sidebarMenu = useSelector((store) => store.page.sidebarMenu); const location = useLocation(); + const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); + const [openFAQ, setOpenFAQ] = useState([false, false, false]); + const [openFeature, setOpenFeature] = useState(null); + const [hoveredFeature, setHoveredFeature] = useState(null); + + // Mouse tracking for interactive background + useEffect(() => { + const handleMouseMove = (e) => { + setMousePosition({ + x: (e.clientX / window.innerWidth) * 100, + y: (e.clientY / window.innerHeight) * 100, + }); + }; + window.addEventListener("mousemove", handleMouseMove, { passive: true }); + return () => window.removeEventListener("mousemove", handleMouseMove); + }, []); - // Function to navigate to the register page const handleGetStarted = () => { - navigate("/login"); // Assuming your registration page route is /register + navigate("/login"); + }; + + const toggleFAQ = (idx) => { + setOpenFAQ((prev) => prev.map((v, i) => (i === idx ? !v : v))); }; const words = [ @@ -50,6 +99,7 @@ const Documentation = () => { className: "text-4xl font-bold text-black dark:text-gray-200", }, ]; + const plateforms = [ "linkedin", "github", @@ -59,438 +109,1232 @@ const Documentation = () => { "facebook", "codeforce", ]; - // Animation variants - const fadeInUp = { - hidden: { opacity: 0, y: 40 }, - visible: { opacity: 1, y: 0, transition: { duration: 0.6 } }, + + const features = [ + { + img: "easy-to-remember.webp", + title: "Personalized Smart Links", + desc: "Generate easy-to-remember links for your social profiles using your username and platform names.", + icon: FaLink, + gradient: "from-blue-500 to-cyan-500", + }, + { + img: "logo.png", + title: "All Links at One Place", + desc: "Access all your profiles with a single hub link. Simply visit https://clickly.cv/yourname (without any platform name) to see all your links in one beautiful, organized page. Perfect for sharing in bios, resumes, and business cards.", + icon: FaHome, + gradient: "from-violet-500 to-purple-500", + }, + { + img: "update.webp", + title: "Centralized Link Management", + desc: "Update your social profile links in one place, and the change reflects everywhere.", + icon: FaSyncAlt, + gradient: "from-green-500 to-emerald-500", + }, + { + img: "click.webp", + title: "Real-Time Email Notifications", + desc: "Get instant email notifications every time someone visits your links. Stay informed about who's checking out your profiles in real-time. Perfect for tracking engagement and knowing when potential clients or employers view your links.", + icon: FaEnvelope, + gradient: "from-cyan-500 to-blue-500", + }, + { + img: "click.webp", + title: "Click Tracking", + desc: "Keep track of how many times your social profile links are clicked.", + icon: FaChartLine, + gradient: "from-orange-500 to-red-500", + }, + { + img: "easysetup.webp", + title: "Easy to Setup", + desc: "No complicated setup; just choose your username, add the platform, and you're ready to go!", + icon: FaCog, + gradient: "from-indigo-500 to-purple-500", + }, + ]; + + const futureFeatures = [ + { + title: "Advanced Analytics", + desc: "See detailed reports on clicks, traffic sources, and engagement levels for each link.", + icon: FaChartLine, + gradient: "from-blue-500 to-cyan-500", + }, + { + title: "Custom Link Themes", + desc: "Add custom themes or styles to your personalized links to match your branding or style preferences.", + icon: FaPalette, + gradient: "from-purple-500 to-pink-500", + }, + { + title: "Link Expiration", + desc: "Set expiration dates for temporary links, ensuring they are only accessible for a certain period.", + icon: FaClock, + gradient: "from-orange-500 to-red-500", + }, + { + title: "Link Password Protection", + desc: "Add a layer of security by allowing password protection on sensitive links.", + icon: FaShieldAlt, + gradient: "from-green-500 to-emerald-500", + }, + ]; + + const containerVariants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.1, + }, + }, }; - // FAQ collapse state - const [openFAQ, setOpenFAQ] = useState([false, false, false]); - // For Key Features expand/collapse - const [openFeature, setOpenFeature] = useState(null); - const toggleFAQ = (idx) => { - setOpenFAQ((prev) => prev.map((v, i) => (i === idx ? !v : v))); + const itemVariants = { + hidden: { opacity: 0, y: 30 }, + visible: { + opacity: 1, + y: 0, + transition: { duration: 0.6 }, + }, }; return ( - +
    + {/* Animated Background */} +
    + {/* Gradient Orbs */} + + + + + {/* Animated Grid */} +
    +
    + + {/* Header */} {location.pathname === "/" && ( -
    - + - -
    + Get Started + +
    )} - - - - {/* Introduction Section */} - -

    Introduction

    -

    - Welcome to LinkBridger, a tool designed to make your social - media links easier to remember and manage. Whether you're sharing - your Instagram, GitHub, or LinkedIn profile, LinkBridger allows you - to generate personalized URLs that are simple and customizable. It - also tracks how often your links are clicked and allows centralized - updating, so any changes you make will reflect across all platforms - instantly. -

    -
    - - - - - -

    - Have You Ever Wondered How AuthorLink Has Been Personalized: -

    - - } + +
    + + {/* Hero Section */} + +
    + +
    + + + +
    + + {/* Introduction Section */} + -
    -
    -

    - LinkedIn: - - https://clickly.cv/dpkrn/linkedin - -

    -

    - GitHub:{" "} - + + Introduction + + + Welcome to LinkBridger, a tool designed to make your social + media links easier to remember and manage. Whether you're sharing + your Instagram, GitHub, or LinkedIn profile, LinkBridger allows you + to generate personalized URLs that are simple and customizable. Access all your links at one place by visiting https://clickly.cv/yourname (without any platform name) - it creates a beautiful landing page showing all your profiles. Plus, get real-time email notifications every time someone visits your links! It + also tracks how often your links are clicked and allows centralized + updating, so any changes you make will reflect across all platforms + instantly. + + + + + {/* Example Links Section */} + + + Have You Ever Wondered How AuthorLink Has Been Personalized: + + } + > +

    + + + + {/* Special Link Section */} + + + + + + - https://clickly.cv/dpkrn/portfolio - -

    -

    - Instagram:{" "} - + https://clickly.cv/dpkrn + + + - https://clickly.cv/dpkrn/instagram - -

    -

    - Facebook:{" "} - + + Change only the platform name to redirect to all profiles + +

    + + - https://clickly.cv/dpkrn/facebook - -

    -

    - Codeforces:{" "} - + https://clickly.cv/dpkrn/ + + + +

    + +
    + + + {/* Get Started CTA */} + + + + Get Started + + + Create an account and start managing your personalized links today! + + + + + {/* Key Features Section */} + + + Key Features + +
    + {features.map((feature, idx) => { + const ref = useRef(null); + const inView = useInView(ref, { once: true, margin: "-100px" }); + const IconComponent = feature.icon; + const isLeft = idx % 2 === 0; + + return ( + setHoveredFeature(idx)} + onHoverEnd={() => setHoveredFeature(null)} + className={`group relative bg-white/10 dark:bg-gray-900/50 backdrop-blur-xl rounded-3xl shadow-2xl border border-white/20 dark:border-gray-700/50 p-6 md:p-10 overflow-hidden ${ + isLeft ? "" : "md:flex-row-reverse" + } flex flex-col md:flex-row items-center gap-6 md:gap-8 cursor-pointer transition-all duration-300 ${ + hoveredFeature === idx ? "scale-105" : "scale-100" + }`} > - https://clickly.cv/dpkrn/codeforces - -

    -

    - Only the platform name has been changed. All else remains the same. -

    -
    + {/* Gradient Background on Hover */} + + + {/* Icon */} + + + + + {/* Image */} + + + {/* Content */} +
    + + {feature.title} + + + {feature.desc} + +
    +
    + ); + })}
    -
    -
    - - - -
    - -
    -
    - - It will provide a special link for all your platforms. - -
    + + + {/* How It Works Section */} + + + + How It Works + + + The core idea behind LinkBridger is to simplify the + management of social media links. Instead of sharing long, + hard-to-remember URLs, you create a single, personalized URL that + automatically redirects users to the correct platform. Access all your links at one place by visiting https://clickly.cv/yourname (without any platform name). Plus, get real-time email notifications every time someone visits your links! + + +
    + {[ + { + step: "1", + title: "Create an Account", + desc: "Sign up using your email and create an account on LinkBridger.", + icon: FaRocket, + }, + { + step: "2", + title: "Choose a Username", + desc: "Pick a username that's easy to remember (e.g., dpkrn). Your link will follow this format: https://clickly.cv/your-username/instagram.", + icon: FaLink, + }, + { + step: "3", + title: "Verify Your Account", + desc: "Complete email verification to activate your account.", + icon: FaCheckCircle, + }, + { + step: "4", + title: "Create a New Link", + desc: "Enter the platform name (e.g., Instagram, LinkedIn) in lowercase. Paste your profile URL in the 'Destination URL' field. Click 'Create Link' to generate your personalized link.", + icon: FaCog, + }, + { + step: "5", + title: "Share the Link", + desc: "Copy and share your smart link across various platforms. Share your hub link (https://clickly.cv/yourname) to let visitors see all your profiles in one place.", + icon: FaSyncAlt, + }, + { + step: "6", + title: "Get Real-Time Notifications", + desc: "Receive instant email notifications every time someone visits your links. Stay informed about engagement and track who's viewing your profiles in real-time.", + icon: FaEnvelope, + }, + ].map((item, idx) => { + const IconComponent = item.icon; + return ( + + + + +
    +

    + {item.step}. {item.title} +

    +

    + {item.desc} +

    +
    +
    + ); + })}
    -

    - - https://clickly.cv/dpkrn - - -

    -
    -
    - - Change only the platform name to redirect to all profiles - +

    Example:

    +
    + + Instagram: https://clickly.cv/dpkrn/instagram + + + LeetCode: https://clickly.cv/dpkrn/leetcode +
    -
    -
    -

    - + + + + {/* Click Tracking Section */} + + + + + + + + Click Tracking + + + - https://clickly.cv/dpkrn/ - {" "} - -

    - -
    - - {/* Get Started Button */} - - -

    - Create an account and start managing your personalized links today! -

    -
    - - {/* Key Features Section */} - -
    -

    Key Features

    -
    - {/* Interactive Feature Cards with alternate sides and scroll-in focus */} - {[ - { - img: "easy-to-remember.webp", - title: "Personalized Smart Links", - desc: "Generate easy-to-remember links for your social profiles using your username and platform names.", - }, - { - img: "logo.png", - title: "All links at one place", - desc: "Get a single special link for all your platforms, just like Linktree. Share one URL (e.g., https://clickly.cv/username) and let your audience access all your profiles from one place.", - }, - { - img: "update.webp", - title: "Centralized Link Management", - desc: "Update your social profile links in one place, and the change reflects everywhere.", - }, - { - img: "click.webp", - title: "Click Tracking", - desc: "Keep track of how many times your social profile links are clicked.", - }, - { - img: "easysetup.webp", - title: "Easy to Setup", - desc: "No complicated setup; just choose your username, add the platform, and you're ready to go!", - }, - - ].map((feature, idx) => { - const ref = useRef(null); - const inView = useInView(ref, { once: true, margin: "-100px" }); - const isLeft = idx % 2 === 0; - return ( + With LinkBridger, you can track how many times each of your + links has been clicked. This allows you to monitor the engagement on + your social media profiles across different platforms. Access the + analytics section from your dashboard to see detailed statistics + about each link's performance. + + + + + {/* Future Enhancements Section */} + + + Future Enhancements + +
    + {futureFeatures.map((feature, idx) => { + const IconComponent = feature.icon; + return ( + + +
    + + + +

    + {feature.title} +

    +

    + {feature.desc} +

    +
    +
    + ); + })} +
    +
    + + {/* Use Cases Section */} + + + Use Cases + +
    + {[ + { + title: "Job Seekers", + desc: "Create professional links for your resume, LinkedIn, portfolio, and GitHub. Share one memorable link with recruiters.", + icon: FaBriefcase, + gradient: "from-blue-500 to-cyan-500", + examples: ["Resume sharing", "Interview preparation", "Professional networking"], + }, + { + title: "Content Creators", + desc: "Manage all your social media profiles from one place. Share your LinkBridger link in bio and watch engagement grow.", + icon: FaUserTie, + gradient: "from-purple-500 to-pink-500", + examples: ["Instagram bio links", "YouTube descriptions", "TikTok profiles"], + }, + { + title: "Developers", + desc: "Showcase your GitHub, portfolio, blog, and coding profiles. Perfect for developer portfolios and tech resumes.", + icon: FaCode, + gradient: "from-green-500 to-emerald-500", + examples: ["Portfolio websites", "GitHub profiles", "Tech blogs"], + }, + { + title: "Students", + desc: "Share academic profiles, LinkedIn, research papers, and project portfolios. Great for college applications and networking.", + icon: FaGraduationCap, + gradient: "from-orange-500 to-red-500", + examples: ["College applications", "Academic networking", "Project showcases"], + }, + { + title: "Businesses", + desc: "Create branded links for your company's social media presence. Manage multiple team member profiles efficiently.", + icon: FaUsers, + gradient: "from-indigo-500 to-purple-500", + examples: ["Team profiles", "Brand consistency", "Social media management"], + }, + { + title: "Freelancers", + desc: "Consolidate your work samples, client testimonials, and contact information in one professional link.", + icon: FaRocket, + gradient: "from-pink-500 to-rose-500", + examples: ["Client proposals", "Portfolio sharing", "Service showcases"], + }, + ].map((useCase, idx) => { + const IconComponent = useCase.icon; + return ( + + +
    + + + +

    + {useCase.title} +

    +

    + {useCase.desc} +

    +
    + {useCase.examples.map((example, i) => ( +
    + + {example} +
    + ))} +
    +
    +
    + ); + })} +
    +
    + + {/* Best Practices Section */} + setOpenFeature(idx)} - // style={{ marginLeft: inView ? "" : "none", outlineOffset: inView ? "2px" : "0" }} + className="bg-white/10 dark:bg-gray-900/50 backdrop-blur-xl rounded-3xl shadow-2xl border border-white/20 dark:border-gray-700/50 p-6 md:p-10 lg:p-12" + whileHover={{ scale: 1.01 }} > - -
    -

    - {feature.title}: {feature.desc} -

    + + + + + + Best Practices + + +
    + {[ + { + title: "Choose a Memorable Username", + desc: "Pick a username that's easy to remember and spell. Avoid numbers and special characters when possible. Your username becomes part of your brand identity.", + icon: FaCheck, + color: "text-green-400", + }, + { + title: "Keep Platform Names Consistent", + desc: "Use lowercase and consistent naming (e.g., 'linkedin' not 'LinkedIn' or 'linked-in'). This makes your links predictable and easy to remember.", + icon: FaCheck, + color: "text-green-400", + }, + { + title: "Update Links Regularly", + desc: "Keep your destination URLs up to date. Since all your shared links automatically update, you only need to change them once in your dashboard.", + icon: FaSyncAlt, + color: "text-blue-400", + }, + { + title: "Use Analytics to Optimize", + desc: "Monitor which links get the most clicks. Use this data to prioritize which platforms to feature prominently in your profile.", + icon: FaChartLine, + color: "text-purple-400", + }, + { + title: "Test Your Links", + desc: "Always test your links after creating or updating them. Make sure they redirect correctly to the intended destination.", + icon: FaCheckCircle, + color: "text-cyan-400", + }, + { + title: "Share Your Hub Link", + desc: "Prominently feature your main hub link (username) in your email signature, business cards, and social media bios for maximum visibility.", + icon: FaRocket, + color: "text-pink-400", + }, + ].map((practice, idx) => { + const IconComponent = practice.icon; + return ( + + + + +
    +

    + {practice.title} +

    +

    + {practice.desc} +

    +
    +
    + ); + })}
    - ); - })} -
    -
    - - - {/* How It Works Section */} - -

    How It Works

    -

    - The core idea behind LinkBridger is to simplify the - management of social media links. Instead of sharing long, - hard-to-remember URLs, you create a single, personalized URL that - automatically redirects users to the correct platform. Here's a - step-by-step guide: -

    -
      -
    1. - Create an Account: Sign up using your email and create an - account on LinkBridger. -
    2. -
    3. - Choose a Username: Pick a username that's easy to remember - (e.g., dpkrn). Your link will follow this format: - `https://clickly.cv/your-username/instagram`. -
    4. -
    5. - Verify Your Account: Complete email verification to - activate your account. -
    6. -
    7. - Create a New Link: -
        -
      • - Enter the platform name (e.g., Instagram, LinkedIn) in - lowercase. -
      • -
      • Paste your profile URL in the "Destination URL" field.
      • -
      • Click "Create Link" to generate your personalized link.
      • -
      -
    8. -
    9. - Share the Link: Copy and share your smart link across - various platforms. -
    10. -
    -
    -

    Example:

    -

    - Instagram:{" "} - - https://clickly.cv/dpkrn/instagram - -

    -

    - LeetCode:{" "} - - https://clickly.cv/dpkrn/leetcode - -

    -
    -
    - - {/* Click Tracking Section */} - -

    Click Tracking

    -

    - With LinkBridger, you can track how many times each of your - links has been clicked. This allows you to monitor the engagement on - your social media profiles across different platforms. Access the - analytics section from your dashboard to see detailed statistics - about each link's performance. -

    -
    - - {/* Future Enhancements Section */} - -

    Future Enhancements

    -

    - Here are some potential features and enhancements we are planning to - add to LinkBridger: -

    -
      -
    • - Advanced Analytics: See detailed reports on clicks, traffic - sources, and engagement levels for each link. -
    • -
    • - Custom Link Themes: Add custom themes or styles to your - personalized links to match your branding or style preferences. -
    • -
    • - Link Expiration: Set expiration dates for temporary links, - ensuring they are only accessible for a certain period. -
    • -
    • - Link Password Protection: Add a layer of security by - allowing password protection on sensitive links. -
    • -
    -
    - - {/* FAQ Section */} - -

    Frequently Asked Questions (FAQ)

    -

    Here are some common questions users have about LinkBridger:

    -
    - {/* FAQ 1 */} -
    toggleFAQ(0)}> -

    - Q: Can I change my username after creating an account? - {openFAQ[0] ? "-" : "+"} + + + {/* Platform Support Section */} + + + Supported Platforms + + +

    + LinkBridger supports any platform you can think of! Just provide the destination URL and we'll create your personalized link.

    - {openFAQ[0] && ( - - A: Unfortunately, usernames cannot be changed once they are set. Choose your username carefully! - - )} +
    + {[ + "LinkedIn", "GitHub", "Instagram", "Facebook", "Twitter/X", "YouTube", + "TikTok", "Snapchat", "Pinterest", "Reddit", "Discord", "Telegram", + "WhatsApp", "Medium", "Dev.to", "Behance", "Dribbble", "Figma", + "Portfolio", "Blog", "Website", "Email", "Resume", "LeetCode", + "Codeforces", "HackerRank", "CodeChef", "Stack Overflow", "Quora", "Tumblr" + ].map((platform, idx) => ( + +

    + {platform} +

    +
    + ))} +
    + + And many more! If you can share a URL, we can create a personalized link for it. + + + + + {/* Security & Privacy Section */} + + + + + + + + Security & Privacy + + +
    + {[ + { + title: "Secure Authentication", + desc: "All user accounts are protected with secure password hashing. We use industry-standard encryption to keep your data safe.", + icon: FaShieldAlt, + gradient: "from-blue-500 to-cyan-500", + }, + { + title: "Privacy First", + desc: "We respect your privacy. Your personal information and link data are stored securely and never shared with third parties without your consent.", + icon: FaEye, + gradient: "from-purple-500 to-pink-500", + }, + { + title: "HTTPS Encryption", + desc: "All connections to LinkBridger are encrypted using HTTPS, ensuring your data is protected during transmission.", + icon: FaLock, + gradient: "from-green-500 to-emerald-500", + }, + { + title: "Secure Server Infrastructure", + desc: "Our servers are hosted on secure, reliable infrastructure with regular security updates and monitoring.", + icon: FaServer, + gradient: "from-orange-500 to-red-500", + }, + ].map((item, idx) => { + const IconComponent = item.icon; + return ( + + + + +
    +

    + {item.title} +

    +

    + {item.desc} +

    +
    +
    + ); + })} +
    +
    +
    + + {/* Troubleshooting Section */} + + + Troubleshooting + +
    + {[ + { + issue: "My link is not redirecting correctly", + solution: "Double-check that your destination URL is correct and includes the full protocol (https://). Make sure the URL is accessible and not behind a login wall.", + icon: FaExclamationTriangle, + color: "from-red-500 to-orange-500", + }, + { + issue: "I forgot my password", + solution: "Use the 'Forgot Password' link on the login page to reset your password. You'll receive an email with instructions to create a new password.", + icon: FaQuestionCircle, + color: "from-blue-500 to-cyan-500", + }, + { + issue: "My username is already taken", + solution: "Usernames must be unique. Try adding numbers or variations to your desired username. Remember, usernames cannot be changed once created.", + icon: FaTimes, + color: "from-yellow-500 to-orange-500", + }, + { + issue: "I'm not receiving verification emails", + solution: "Check your spam/junk folder. Make sure you entered the correct email address. If the issue persists, contact support for assistance.", + icon: FaExclamationTriangle, + color: "from-purple-500 to-pink-500", + }, + { + issue: "My click count seems incorrect", + solution: "Click tracking may take a few moments to update. Refresh your dashboard. Note that clicks from the same IP within a short time may be filtered to prevent spam.", + icon: FaChartLine, + color: "from-green-500 to-emerald-500", + }, + { + issue: "I want to delete my account", + solution: "Contact our support team to request account deletion. We'll process your request and ensure all your data is permanently removed from our systems.", + icon: FaUserTie, + color: "from-indigo-500 to-purple-500", + }, + ].map((item, idx) => { + const IconComponent = item.icon; + return ( + + +
    + + + +
    +

    + {item.issue} +

    +

    + Solution: + {item.solution} +

    +
    +
    +
    +
    + ); + })}
    - {/* FAQ 2 */} -
    toggleFAQ(1)}> -

    - Q: How do I track my link clicks? - {openFAQ[1] ? "-" : "+"} + +

    + Still need help?

    - {openFAQ[1] && ( - - A: Click tracking is available through your dashboard. You can view the number of clicks for each link, and advanced analytics will be added soon. - - )} -
    - {/* FAQ 3 */} -
    toggleFAQ(2)}> -

    - Q: Can I use custom platforms other than the popular ones (Instagram, LinkedIn, etc.)? - {openFAQ[2] ? "-" : "+"} +

    + Contact our support team at{" "} + + d.wizard.techno@gmail.com + + {" "}and we'll be happy to assist you!

    - {openFAQ[2] && ( - - A: Yes! You can add any platform as long as you provide the correct profile URL. - - )} -
    -
    - - - {/* Testimonials Section - now at bottom */} - -

    What Our Users Say

    -
    -
    -

    "LinkBridger made sharing my profiles so much easier. The personalized links look great and are super easy to remember!"

    -
    - User - Amit S. -
    + + + + {/* FAQ Section */} + + + Frequently Asked Questions + +
    + {[ + { + q: "Can I change my username after creating an account?", + a: "Unfortunately, usernames cannot be changed once they are set. Choose your username carefully!", + }, + { + q: "How do I track my link clicks?", + a: "Click tracking is available through your dashboard. You can view the number of clicks for each link, and advanced analytics will be added soon.", + }, + { + q: "Can I use custom platforms other than the popular ones (Instagram, LinkedIn, etc.)?", + a: "Yes! You can add any platform as long as you provide the correct profile URL.", + }, + ].map((faq, idx) => ( + toggleFAQ(idx)} + > + +

    + Q: {faq.q} +

    + + {openFAQ[idx] ? ( + + ) : ( + + )} + +
    + + {openFAQ[idx] && ( + + + A: {faq.a} + + + )} + +
    + ))}
    -
    -

    "I love the analytics and the ability to update all my links in one place. Highly recommended!"

    -
    - User - Priya K. -
    + + + {/* Testimonials Section */} + + + What Our Users Say + +
    + {[ + { + text: "LinkBridger made sharing my profiles so much easier. The personalized links look great and are super easy to remember!", + author: "Amit S.", + }, + { + text: "I love the analytics and the ability to update all my links in one place. Highly recommended!", + author: "Priya K.", + }, + ].map((testimonial, idx) => ( + + +
    +
    + {[...Array(5)].map((_, i) => ( + + ))} +
    +

    + "{testimonial.text}" +

    +
    + + + {testimonial.author} + +
    +
    +
    + ))}
    -
    -
    - {/* Footer Section */} -
    -
    - - © 2024 LinkBridger. All Rights Reserved. - -
    - - + + + {/* Footer */} +
    + + + © 2024 LinkBridger. All Rights Reserved. + + + +
    +
    ); }; diff --git a/frontend/src/components/pages/HomePage.jsx b/frontend/src/components/pages/HomePage.jsx index a082c29..7cf2d11 100644 --- a/frontend/src/components/pages/HomePage.jsx +++ b/frontend/src/components/pages/HomePage.jsx @@ -1,9 +1,683 @@ -import React from 'react' +import React, { useState, useEffect, useRef } from 'react'; +import { motion, useScroll, useTransform, useInView } from 'framer-motion'; +import { useNavigate, useLocation } from 'react-router-dom'; +import { useSelector } from 'react-redux'; +import { TypewriterEffect, TypewriterEffectSmooth } from '../ui/typewriter-effect'; +import { FlipWords } from '../ui/flip-words'; +import { BackgroundBeamsWithCollision } from '../ui/background-beams-with-collision'; +import { + FaRocket, + FaLink, + FaChartLine, + FaSyncAlt, + FaShieldAlt, + FaInfinity, + FaArrowRight, + FaCheckCircle, + FaUsers, + FaGlobe, + FaClock, + FaStar, + FaEnvelope, + FaHome +} from 'react-icons/fa'; +import { + SiLinkedin, + SiGithub, + SiInstagram, + SiFacebook, + SiLeetcode, + SiYoutube, + SiX +} from 'react-icons/si'; + +// AnimatedCounter component moved outside to prevent recreation on every parent re-render +const AnimatedCounter = ({ end, duration = 2000, suffix = "", statsInView }) => { + const [count, setCount] = useState(0); + const countRef = useRef(null); + const animationFrameRef = useRef(null); + const isMountedRef = useRef(true); + + useEffect(() => { + if (!statsInView) return; + isMountedRef.current = true; + + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting && isMountedRef.current) { + let startTime = null; + const animate = (currentTime) => { + if (!isMountedRef.current) { + // Component unmounted, stop animation + return; + } + + if (!startTime) startTime = currentTime; + const progress = Math.min((currentTime - startTime) / duration, 1); + const easeOutQuart = 1 - Math.pow(1 - progress, 4); + + if (isMountedRef.current) { + setCount(Math.floor(easeOutQuart * end)); + } + + if (progress < 1 && isMountedRef.current) { + animationFrameRef.current = requestAnimationFrame(animate); + } else { + animationFrameRef.current = null; + } + }; + animationFrameRef.current = requestAnimationFrame(animate); + observer.disconnect(); + } + }, + { threshold: 0.1 } + ); + + if (countRef.current) { + observer.observe(countRef.current); + } + + return () => { + isMountedRef.current = false; + observer.disconnect(); + // Cancel any pending animation frame + if (animationFrameRef.current !== null) { + cancelAnimationFrame(animationFrameRef.current); + animationFrameRef.current = null; + } + }; + }, [statsInView, end, duration]); + + return {count}{suffix}; +}; const HomePage = () => { + const navigate = useNavigate(); + const location = useLocation(); + const isAuthenticated = useSelector(store => store.admin.isAuthenticated); + const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); + const heroRef = useRef(null); + const statsRef = useRef(null); + const featuresRef = useRef(null); + const benefitsRef = useRef(null); + // Ref to store latest mouse position, avoiding stale closure values in RAF callback + const latestMousePositionRef = useRef({ x: 0, y: 0 }); + + const statsInView = useInView(statsRef, { once: true, margin: "-100px" }); + const featuresInView = useInView(featuresRef, { once: true, margin: "-100px" }); + const benefitsInView = useInView(benefitsRef, { once: true, margin: "-100px" }); + + const { scrollYProgress } = useScroll(); + const opacity = useTransform(scrollYProgress, [0, 0.5], [1, 0]); + const scale = useTransform(scrollYProgress, [0, 0.5], [1, 0.8]); + + useEffect(() => { + let rafId = null; + let lastUpdateTime = 0; + const throttleDelay = 16; // ~60fps + + const handleMouseMove = (e) => { + // Always update the latest position ref immediately with current event coordinates + // This ensures we always have the most recent position, even if RAF hasn't executed yet + // Convert to percentages (0-100%) for proper CSS gradient positioning + latestMousePositionRef.current = { + x: (e.clientX / window.innerWidth) * 100, + y: (e.clientY / window.innerHeight) * 100, + }; + + const now = Date.now(); + if (now - lastUpdateTime < throttleDelay) { + // Throttle updates to reduce re-renders + // If RAF is already scheduled, don't schedule another one + // The scheduled RAF will read the latest position from the ref at execution time + if (rafId === null) { + // Update lastUpdateTime immediately when scheduling RAF to prevent continuous scheduling + // This ensures subsequent mousemove events correctly see we're still throttling + lastUpdateTime = now; + rafId = requestAnimationFrame(() => { + // Read latest position from ref at execution time, not from closure-captured event + // This ensures we always use the most recent mouse position, not the position + // from when the RAF was first scheduled + setMousePosition({ + x: latestMousePositionRef.current.x, + y: latestMousePositionRef.current.y, + }); + rafId = null; + }); + } + return; + } + + // Update immediately if throttle delay has passed + lastUpdateTime = now; + setMousePosition({ + x: latestMousePositionRef.current.x, + y: latestMousePositionRef.current.y, + }); + }; + + window.addEventListener('mousemove', handleMouseMove, { passive: true }); + return () => { + window.removeEventListener('mousemove', handleMouseMove); + if (rafId !== null) { + cancelAnimationFrame(rafId); + rafId = null; + } + }; + }, []); + + const words = [ + { + text: "Transform", + className: "text-5xl md:text-7xl font-extrabold bg-clip-text text-transparent bg-gradient-to-r from-purple-600 via-pink-600 to-blue-600 dark:from-purple-400 dark:via-pink-400 dark:to-blue-400", + }, + { + text: "Your", + className: "text-5xl md:text-7xl font-extrabold text-gray-800 dark:text-gray-200", + }, + { + text: "Social", + className: "text-5xl md:text-7xl font-extrabold text-gray-800 dark:text-gray-200", + }, + { + text: "Presence", + className: "text-5xl md:text-7xl font-extrabold bg-clip-text text-transparent bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 dark:from-blue-400 dark:via-purple-400 dark:to-pink-400", + }, + ]; + + const flipWords = ["LinkedIn", "GitHub", "Instagram", "Portfolio", "YouTube", "Twitter"]; + + const features = [ + { + icon: , + title: "Personalized Links", + description: "Create memorable URLs like clickly.cv/yourname/linkedin that reflect your brand", + color: "from-blue-500 to-cyan-500", + delay: 0.1 + }, + { + icon: , + title: "All Links at One Place", + description: "Access all your profiles with a single hub link. Visit clickly.cv/yourname to see all your links in one beautiful page", + color: "from-violet-500 to-purple-500", + delay: 0.15 + }, + { + icon: , + title: "Centralized Management", + description: "Update once, reflect everywhere. Change your destination URL and all shared links update automatically", + color: "from-purple-500 to-pink-500", + delay: 0.2 + }, + { + icon: , + title: "Real-Time Email Notifications", + description: "Get instant email notifications for every visit to your links. Stay informed about who's checking out your profiles in real-time", + color: "from-cyan-500 to-blue-500", + delay: 0.25 + }, + { + icon: , + title: "Click Analytics", + description: "Track engagement with real-time analytics. Know which platforms drive the most traffic", + color: "from-green-500 to-emerald-500", + delay: 0.3 + }, + { + icon: , + title: "Privacy First", + description: "No tracking scripts, no third-party analytics. Your data stays yours", + color: "from-orange-500 to-red-500", + delay: 0.4 + }, + { + icon: , + title: "Never Expires", + description: "Your links work forever. No expiration dates, no premium plans, no limits", + color: "from-indigo-500 to-purple-500", + delay: 0.5 + }, + { + icon: , + title: "Easy Setup", + description: "Get started in minutes. Just choose a username and start creating your personalized links", + color: "from-pink-500 to-rose-500", + delay: 0.6 + }, + ]; + + const benefits = [ + { + title: "For Professionals", + points: [ + "Build your brand with consistent, memorable links", + "Professional appearance on resumes and business cards", + "Save time with centralized link management", + "Track engagement to optimize networking strategy" + ], + icon: , + gradient: "from-blue-600 to-cyan-600" + }, + { + title: "For Content Creators", + points: [ + "Easy sharing across all platforms", + "Audience insights through click tracking", + "Flexibility to add any platform", + "Cross-platform promotion made simple" + ], + icon: , + gradient: "from-purple-600 to-pink-600" + }, + { + title: "For Developers", + points: [ + "Open source and fully customizable", + "API access for integration", + "Self-hostable solution", + "Contribute to the community" + ], + icon: , + gradient: "from-green-600 to-emerald-600" + }, + ]; + + const platforms = [ + { name: "LinkedIn", icon: , color: "text-blue-600" }, + { name: "GitHub", icon: , color: "text-gray-800 dark:text-gray-200" }, + { name: "Instagram", icon: , color: "text-pink-600" }, + { name: "Facebook", icon: , color: "text-blue-700" }, + { name: "LeetCode", icon: , color: "text-orange-600" }, + { name: "YouTube", icon: , color: "text-red-600" }, + { name: "Twitter", icon: , color: "text-blue-400" }, + ]; + + const stats = [ + { value: 10000, suffix: "+", label: "Active Users", icon: }, + { value: 50000, suffix: "+", label: "Links Created", icon: }, + { value: 1000000, suffix: "+", label: "Clicks Tracked", icon: }, + { value: 99, suffix: "%", label: "Uptime", icon: }, + ]; + return ( -
    HomePage
    - ) -} +
    + {/* Navigation Header for Non-Authenticated Users */} + {!isAuthenticated && location.pathname === '/' && ( + +
    +
    + navigate('/')} + > + LinkBridger Logo { + e.target.src = 'https://tailwindui.com/plus/img/logos/mark.svg?color=indigo&shade=500'; + }} + /> + + LinkBridger + + + +
    + navigate('/doc')} + className="px-3 py-1.5 sm:px-4 sm:py-2 text-sm sm:text-base text-gray-700 dark:text-gray-300 font-medium hover:text-purple-600 dark:hover:text-purple-400 transition-colors" + > + Docs + + navigate('/login')} + className="px-4 py-1.5 sm:px-6 sm:py-2 text-sm sm:text-base bg-gradient-to-r from-purple-600 to-pink-600 text-white font-semibold rounded-full shadow-lg hover:shadow-xl transition-all" + > + Get Started + +
    +
    +
    +
    + )} + + {/* Hero Section */} + + {/* Animated Background */} +
    +
    + +
    + +
    +
    + {/* Main Heading with Typewriter */} + + + + + {/* Subheading with Flip Words */} + + Create personalized links for your{' '} + + + + + + {/* Description */} + + LinkBridger transforms your social media presence with memorable, personalized URLs. +
    + + One link to rule them all. Update once, reflect everywhere. + +
    + + {/* CTA Buttons */} + + navigate('/login')} + className="group relative px-6 py-3 sm:px-8 sm:py-4 bg-gradient-to-r from-purple-600 to-pink-600 text-white font-bold text-base sm:text-lg rounded-full shadow-2xl hover:shadow-purple-500/50 transition-all duration-300 overflow-hidden w-full sm:w-auto" + > + + Get Started Free + + + + + + navigate('/doc')} + className="px-6 py-3 sm:px-8 sm:py-4 bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200 font-bold text-base sm:text-lg rounded-full shadow-xl border-2 border-purple-600 dark:border-purple-400 hover:bg-purple-50 dark:hover:bg-purple-900/20 transition-all duration-300 w-full sm:w-auto" + > + Learn More + + + + {/* Platform Icons */} + +

    Works with all platforms

    +
    + {platforms.map((platform, index) => ( + + {platform.icon} + + ))} +
    +
    +
    +
    + + {/* Scroll Indicator */} + + + + + + + + {/* Statistics Section */} +
    +
    + +

    + Trusted by Thousands +

    +

    + Join the community of professionals, creators, and developers +

    +
    + +
    + {stats.map((stat, index) => ( + +
    + {stat.icon} +
    +
    + {statsInView ? ( + + ) : ( + `0${stat.suffix}` + )} +
    +
    + {stat.label} +
    +
    + ))} +
    +
    +
    + + {/* Features Section */} +
    +
    + +

    + Powerful Features +

    +

    + Everything you need to manage your social presence in one place +

    +
    + +
    + {features.map((feature, index) => ( + +
    +
    + {feature.icon} +
    +

    + {feature.title} +

    +

    + {feature.description} +

    + + + ))} +
    +
    +
    + + {/* Benefits Section */} +
    +
    + +

    + Perfect for Everyone +

    +

    + Whether you're a professional, creator, or developer +

    +
    + +
    + {benefits.map((benefit, index) => ( + +
    +
    +
    {benefit.icon}
    +

    {benefit.title}

    +
      + {benefit.points.map((point, idx) => ( + + + {point} + + ))} +
    +
    + + ))} +
    +
    +
    + + {/* Final CTA Section */} +
    +
    +
    + +

    + Ready to Transform Your Links? +

    +

    + Join thousands of professionals who trust LinkBridger to manage their social presence +

    + navigate('/login')} + className="px-6 py-3 sm:px-10 sm:py-5 bg-white text-purple-600 font-bold text-base sm:text-lg md:text-xl rounded-full shadow-2xl hover:shadow-white/50 transition-all duration-300 flex items-center justify-center gap-3 mx-auto" + > + Get Started Now + + +
    +
    +
    +
    + ); +}; -export default HomePage \ No newline at end of file +export default HomePage; From f91444ef7eeaf1547440b6190d9aae0f1fd5d030 Mon Sep 17 00:00:00 2001 From: DpkRn Date: Thu, 18 Dec 2025 02:43:57 +0530 Subject: [PATCH 004/166] Refactored AuthPage to enhance user experience with improved animations and layout. Updated gradient effects for better visual appeal and optimized performance across components. Adjusted routing to ensure seamless navigation. Enhanced Documentation with new features and layout improvements for clarity. --- frontend/src/utils/api.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index 4ee1b17..f3761bd 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -3,10 +3,10 @@ import axios from 'axios'; // Create an Axios instance const api = axios.create({ - // baseURL: 'http://localhost:8080', // Replace with your API base URL + baseURL: 'http://localhost:8080', // Replace with your API base URL // baseURL: 'https://linkb-one.vercel.app', - baseURL: import.meta.env.VITE_API_URL || 'https://clickly.cv', - withCredentials: true, + // baseURL: import.meta.env.VITE_API_URL || 'https://clickly.cv', + // withCredentials: true, }); export default api; \ No newline at end of file From b26e3044c30ec1f3f0d72ee31f7fc82423969f4e Mon Sep 17 00:00:00 2001 From: DpkRn Date: Thu, 18 Dec 2025 03:46:21 +0530 Subject: [PATCH 005/166] changed theme --- backend/public/css/styles.css | 461 +++++++++++++-- backend/views/linktree.ejs | 184 +++++- frontend/CHANGELOG.md | 226 +++++++ frontend/src/components/AuthPage.jsx | 34 +- frontend/src/components/DashBoard.jsx | 2 +- frontend/src/components/Documentation.jsx | 125 ++-- frontend/src/components/content/Content.jsx | 164 +++++- frontend/src/components/footer/Footer.jsx | 340 ++++++++--- frontend/src/components/linkcard/Linkcard.jsx | 234 +++++--- frontend/src/components/navbar/Nav.jsx | 554 ++++++++++-------- .../components/notification/Notification.jsx | 94 ++- .../src/components/pages/CreateBridge.jsx | 554 ++++++++++++------ frontend/src/components/pages/HomePage.jsx | 73 +-- frontend/src/components/pages/LinkPage.jsx | 353 +++++++++-- 14 files changed, 2562 insertions(+), 836 deletions(-) diff --git a/backend/public/css/styles.css b/backend/public/css/styles.css index 3373078..996fb89 100644 --- a/backend/public/css/styles.css +++ b/backend/public/css/styles.css @@ -1,83 +1,462 @@ -/* styles.css */ -body { +/* Modern Linktree Styles */ +* { margin: 0; padding: 0; - font-family: Arial, sans-serif; - background-color: #000; + box-sizing: border-box; +} + +body { + font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + background: linear-gradient(135deg, #0f172a 0%, #1e1b4b 50%, #0f172a 100%); color: #fff; - text-align: center; + min-height: 100vh; + overflow-x: hidden; + position: relative; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Animated Background */ +.background { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 0; + overflow: hidden; +} + +.gradient-orb { + position: absolute; + border-radius: 50%; + filter: blur(80px); + opacity: 0.3; + transition: transform 0.3s ease-out; + pointer-events: none; } +.orb-1 { + width: 500px; + height: 500px; + background: linear-gradient(135deg, #9333ea 0%, #ec4899 100%); + top: -250px; + left: -250px; +} + +.orb-2 { + width: 400px; + height: 400px; + background: linear-gradient(135deg, #3b82f6 0%, #06b6d4 100%); + bottom: -200px; + right: -200px; +} + +.orb-3 { + width: 350px; + height: 350px; + background: linear-gradient(135deg, #f59e0b 0%, #ef4444 100%); + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.grid-pattern { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: + linear-gradient(to right, rgba(128, 128, 128, 0.05) 1px, transparent 1px), + linear-gradient(to bottom, rgba(128, 128, 128, 0.05) 1px, transparent 1px); + background-size: 24px 24px; + mask-image: radial-gradient(ellipse 80% 50% at 50% 0%, #000 70%, transparent 110%); +} + +/* Container */ .container { display: flex; justify-content: center; align-items: center; - height: 100vh; + min-height: 100vh; + padding: 20px; + position: relative; + z-index: 1; } +/* Profile Card */ .profile { width: 100%; - max-width: 400px; - padding: 60px; - border:2px solid rgb(56, 37, 37); - border-radius: 10px; - background-color: #433c3c; + max-width: 450px; + padding: 40px 30px; + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 24px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + position: relative; + text-align: center; + animation: fadeInUp 0.8s ease-out; +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInScale { + from { + opacity: 0; + transform: scale(0.8); + } + to { + opacity: 1; + transform: scale(1); + } +} + +/* Profile Picture */ +.profile-pic-wrapper { + position: relative; + display: inline-block; + margin-bottom: 24px; } -.profile-pic img { +.profile-pic-glow { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 140px; + height: 140px; + background: linear-gradient(135deg, #9333ea 0%, #ec4899 50%, #3b82f6 100%); + border-radius: 50%; + filter: blur(20px); + opacity: 0.6; + animation: pulse 3s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { + transform: translate(-50%, -50%) scale(1); + opacity: 0.6; + } + 50% { + transform: translate(-50%, -50%) scale(1.1); + opacity: 0.8; + } +} + +.profile-pic { width: 120px; height: 120px; border-radius: 50%; object-fit: cover; - border: 2px solid white ; - + border: 3px solid rgba(255, 255, 255, 0.3); + position: relative; + z-index: 1; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); + transition: transform 0.3s ease; + background: linear-gradient(135deg, #9333ea 0%, #ec4899 50%, #3b82f6 100%); + display: block; } -.bio{ - color: #fff; +.profile-pic:hover { + transform: scale(1.05); } -.title h1 { - font-size: 24px; - margin-bottom: 20px; +.profile-pic[src=""] { + display: none; } +/* Username */ +.username-wrapper { + margin-bottom: 16px; + position: relative; +} +.username { + font-family: 'Montserrat', 'Poppins', sans-serif; + font-size: 32px; + font-weight: 700; + margin-bottom: 8px; + background: linear-gradient(135deg, #e0e7ff 0%, #c7d2fe 50%, #a5b4fc 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + letter-spacing: 2px; + text-align: center; +} -.social-icons a img { - width: 40px; - margin-bottom: 20px; +.username-underline { + width: 60px; + height: 3px; + background: linear-gradient(90deg, #9333ea 0%, #ec4899 50%, #3b82f6 100%); + margin: 0 auto; + border-radius: 2px; + animation: expandWidth 0.8s ease-out 0.3s both; } -.links .link-item { - margin-bottom: 15px; +@keyframes expandWidth { + from { + width: 0; + } + to { + width: 60px; + } } -.links .btn { - display: block; - padding: 15px; - background-color: #333; +/* Bio */ +.bio { + color: rgba(255, 255, 255, 0.8); + font-size: 16px; + line-height: 1.6; + text-align: center; + margin-bottom: 32px; + padding: 0 10px; +} + +/* Links Container */ +.links { + margin-bottom: 32px; + min-height: 50px; +} + +.link-item { + margin-bottom: 16px; + animation: fadeInUp 0.6s ease-out both; + position: relative; +} + +.no-links { + text-align: center; + padding: 40px 20px; + background: rgba(255, 255, 255, 0.05); + border-radius: 16px; + border: 1px dashed rgba(255, 255, 255, 0.2); +} + +.no-links-text { + color: rgba(255, 255, 255, 0.6); + font-size: 16px; + font-style: italic; +} + +.btn { + display: flex; + align-items: center; + justify-content: space-between; + padding: 18px 24px; + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); color: white; text-decoration: none; - border-radius: 25px; + border-radius: 16px; font-size: 18px; - transition: background-color 0.3s; - display: flex; + font-weight: 600; + border: 1px solid rgba(255, 255, 255, 0.2); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; + cursor: pointer; + user-select: none; + -webkit-tap-highlight-color: transparent; +} + +.btn::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent); + transition: left 0.5s ease; +} + +.btn:hover::before { + left: 100%; +} + +.btn:hover { + background: rgba(255, 255, 255, 0.15); + border-color: rgba(255, 255, 255, 0.3); + transform: translateY(-5px) scale(1.02); + box-shadow: 0 10px 30px rgba(147, 51, 234, 0.3); +} + +.btn:active { + transform: translateY(-2px) scale(1); +} + +.btn-text { + flex: 1; + text-align: left; + letter-spacing: 1px; +} + +.btn-arrow { + font-size: 20px; + opacity: 0.7; + transition: transform 0.3s ease, opacity 0.3s ease; +} + +.btn:hover .btn-arrow { + transform: translateX(5px); + opacity: 1; +} + +/* Footer */ +.footer { + text-align: center; + padding-top: 24px; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +.website { + color: rgba(255, 255, 255, 0.6); + text-decoration: none; + font-size: 14px; + transition: color 0.3s ease; + display: inline-flex; align-items: center; - justify-content: center; + gap: 6px; } -.links .btn:hover { - background-color: #555; +.website:hover { + color: rgba(255, 255, 255, 0.9); } -.links .btn img { - width: 35px; - height: 35px; - border-radius: 50%; - margin-right: 10px; +.heart { + color: #ec4899; + animation: heartbeat 1.5s ease-in-out infinite; + display: inline-block; +} + +@keyframes heartbeat { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.2); + } +} + +/* Touch device optimizations */ +@media (hover: none) and (pointer: coarse) { + .btn:hover { + transform: none; + } + + .btn:active { + transform: translateY(-2px) scale(0.98); + background: rgba(255, 255, 255, 0.2); + } +} + +/* Responsive Design */ +@media (max-width: 480px) { + .profile { + padding: 30px 20px; + max-width: 100%; + } + + .username { + font-size: 24px; + letter-spacing: 1px; + } + + .bio { + font-size: 14px; + padding: 0 5px; + } + + .btn { + padding: 16px 20px; + font-size: 16px; + } + + .btn-text { + letter-spacing: 0.5px; + } + + .profile-pic { + width: 100px; + height: 100px; + } + + .profile-pic-glow { + width: 120px; + height: 120px; + } + + .username-underline { + width: 50px; + } +} + +@media (max-width: 360px) { + .profile { + padding: 24px 16px; + } + + .username { + font-size: 20px; + letter-spacing: 0.5px; + } + + .btn { + padding: 14px 18px; + font-size: 15px; + } + + .bio { + font-size: 13px; + } + + .username-underline { + width: 40px; + } +} + +/* Loading state */ +.profile { + will-change: transform; +} + +.link-item { + will-change: transform, opacity; +} + +/* Accessibility improvements */ +.btn:focus { + outline: 2px solid rgba(147, 51, 234, 0.5); + outline-offset: 2px; +} + +.website:focus { + outline: 2px solid rgba(255, 255, 255, 0.5); + outline-offset: 2px; + border-radius: 4px; +} + +/* Smooth scrolling */ +html { + scroll-behavior: smooth; } -.website{ - color: rgb(15, 15, 71); +/* Dark mode support (if system prefers dark) */ +@media (prefers-color-scheme: dark) { + body { + background: linear-gradient(135deg, #030712 0%, #1e1b4b 50%, #030712 100%); + } } diff --git a/backend/views/linktree.ejs b/backend/views/linktree.ejs index d58d2f4..03c9276 100644 --- a/backend/views/linktree.ejs +++ b/backend/views/linktree.ejs @@ -1,37 +1,187 @@ - + + - <%=username.toUpperCase()%> + <%=username.toUpperCase()%> - LinkBridger + + + +
    +
    +
    +
    +
    +
    +
    -
    - Profile Picture +
    +
    + <%=username.toUpperCase()%>'s Profile Picture
    -

    <%=dp.bio||"bio not available"%>

    - -
    -

    <%= username.toUpperCase() %>

    + +
    +

    <%= username.toUpperCase() %>

    +
    - +

    <%=dp.bio||"Welcome to my link hub! Explore all my profiles below."%>

    + + + - Click here to make your own links.
    + + diff --git a/frontend/CHANGELOG.md b/frontend/CHANGELOG.md index efbf809..6fafb84 100644 --- a/frontend/CHANGELOG.md +++ b/frontend/CHANGELOG.md @@ -8,6 +8,232 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Fixed - 2024-12-19 ++- **Nav Component Complete Redesign**: Completely redesigned the navigation bar with modern interactive design + - Implemented glassmorphism design with backdrop blur and gradient borders + - Added interactive mouse-tracking gradient orb background + - Redesigned logo section with hover effects and gradient text + - Enhanced navigation links with active state indicators using Framer Motion `layoutId` + - Added icons to navigation links (FaHome, FaLink, FaBook) + - Redesigned dark mode toggle with smooth animations and hover effects + - Enhanced notification button with animated badge and modern dropdown + - Redesigned profile menu with glassmorphism, icons (FaUser, FaCog, FaSignOutAlt), and smooth animations + - Improved mobile menu with slide-in animation and modern styling + - Added AnimatePresence for smooth transitions on dropdowns and mobile menu + - Consistent gradient color scheme (purple, pink, blue) matching the rest of the application + - Improved accessibility with proper ARIA labels and focus states + ++- **Footer Component Complete Redesign**: Completely redesigned the footer with modern interactive design + - Implemented glassmorphism design with backdrop blur and gradient borders + - Added interactive mouse-tracking gradient orbs background + - Added animated grid pattern for visual depth + - Redesigned brand section with hover effects and gradient text + - Enhanced footer links with hover animations (translate-x effect) and rocket icons + - Redesigned social media icons with hover effects, glassmorphism cards, and smooth animations + - Added "Made with ❤️" section with animated heart icon + - Improved responsive design with better grid layout + - Added Framer Motion animations for scroll-triggered reveals + - Consistent gradient color scheme matching the application theme + - Improved visual hierarchy and spacing + ++- **Notification Component Complete Redesign**: Completely redesigned the notification component with improved text and styling + - Enhanced notification cards with glassmorphism design and gradient borders + - Added icons (FaChartLine, FaLink, FaMousePointer) for better visual communication + - Improved text: Changed from "X clicks on Y" to "X New Click(s)" with platform badge + - Added empty state with friendly message and icon + - Implemented staggered entry animations using Framer Motion + - Added hover effects and smooth transitions + - Enhanced visual hierarchy with gradient badges and progress indicators + - Better spacing and typography for improved readability + - Consistent design language with the rest of the application + ++- **LinkPage Light Mode Dark Link Containers**: Added darker containers only for link sections in light mode + - Kept main card backgrounds as `bg-white/10` (reverted from dark containers) + - Only link containers (inner divs showing actual links) use `bg-gray-800/90` in light mode + - Link text uses white color for better readability on dark link containers + - Maintained all dark mode styles unchanged + - Applied to Hub Link container and Personalized Link containers in Linkcard components + ++- **LinkPage Light Mode Text Visibility**: Improved text visibility in light mode for LinkPage and Linkcard components + - Changed text colors from `text-white` to `text-gray-900 dark:text-white` for headings and important text + - Changed `text-gray-300` to `text-gray-700` for body text in light mode + - Changed `text-gray-400` to `text-gray-700` for labels and secondary text in light mode + - Maintained all dark mode styles unchanged + - Improved contrast and readability across all text elements in light mode + ++- **CreateBridge and LinkPage Excessive Gap**: Fixed excessive spacing between CreateBridge and LinkPage components + - Removed `min-h-screen` from both components since they're sections within Dashboard, not standalone pages + - Removed duplicate background containers (Dashboard now provides the background) + - Changed padding from `min-h-screen p-4` to `py-8 md:py-12 px-4` for more reasonable section spacing + - Components now flow seamlessly as sections within the Dashboard without forced full-screen heights + - Maintains all interactive background effects while eliminating excessive gaps + ++- **Content.jsx Design Consistency**: Fixed visual inconsistency between Content, CreateBridge, and LinkPage components + - Updated Dashboard background to match dark theme (`from-slate-900 via-purple-900 to-slate-900`) + - Removed duplicate background container from Content.jsx to allow seamless flow with Dashboard + - Standardized gradient orbs pattern to match CreateBridge and LinkPage (purple, pink, blue with /20 opacity) + - Updated animated grid pattern to match other components + - Adjusted text colors and icon opacity to work consistently across all sections + - All three sections (Content, CreateBridge, LinkPage) now appear as one cohesive page with seamless transitions + - Maintains interactive mouse-tracking background effects across all sections + ++- **linktree.ejs Profile Picture Centering**: Fixed profile picture alignment + - Profile picture was appearing on the left instead of being horizontally centered + - Added `text-align: center` to `.profile` class in styles.css + - Profile picture wrapper (inline-block) now centers properly within the profile card + - Also centers username, bio, and other text content for better visual balance + ++- **linktree.ejs CSS Linter Error**: Fixed red line/linter error on EJS template syntax + - CSS linter was complaining about EJS syntax `<%= index * 0.1 %>` inside inline style attribute + - Changed from inline style with EJS to data attribute `data-delay` + - Updated JavaScript to read `data-delay` attribute and apply animation delay programmatically + - Eliminates CSS linter false positive while maintaining the same functionality + - Animation delay is now applied both via CSS `animationDelay` and JavaScript `setTimeout` timing + ++- **SVG Attribute Warnings (Nav & Footer)**: Fixed React DOM property warnings for SVG attributes + - Converted kebab-case SVG attributes to camelCase as required by React + - Fixed `stroke-width` → `strokeWidth` in Nav.jsx (3 occurrences) + - Fixed `stroke-linecap` → `strokeLinecap` in Nav.jsx (3 occurrences) + - Fixed `stroke-linejoin` → `strokeLinejoin` in Nav.jsx (3 occurrences) + - Fixed `fill-rule` → `fillRule` in Footer.jsx (4 occurrences) + - Eliminates all React warnings about invalid DOM properties in SVG elements + ++- **Content Component Icon Import Error**: Fixed `FaSparkles` import error + - `FaSparkles` is not exported from `react-icons/fa` + - Replaced with `HiSparkles` from `react-icons/hi2` to match usage in other components + - Resolves "The requested module does not provide an export named 'FaSparkles'" error + ++- **AnimatedCounter IntersectionObserver Conflict**: Fixed frozen counter display when props change + - Removed redundant internal `IntersectionObserver` that conflicted with parent's `useInView` hook + - The component was using both `statsInView` (from parent) and its own observer, causing issues when `end` or `duration` props changed + - When props changed, the effect would re-run, disconnect the observer, but the new observer wouldn't fire because `useInView` already triggered with `once: true` + - Now uses `statsInView` directly to trigger animation, eliminating the conflict + - Added `hasAnimatedRef` to track animation state and properly restart animation when props change + - Animation now correctly restarts when `end` or `duration` changes, even if element is already in view + - Prevents frozen counter display on subsequent prop updates + ++- **Content Component Complete Redesign**: Completely redesigned the Content component with modern interactive design + - Added interactive mouse-tracking gradient orbs that follow cursor movement + - Implemented animated grid pattern background with parallax effect + - Redesigned logo/title with gradient text effect and glowing backdrop + - Added floating decorative icons (FaLink, FaSparkles, FaRocket) with pulse animations + - Enhanced taglines with gradient text highlights and improved typography + - Added scroll-triggered animations using Framer Motion's `useInView` hook + - Implemented smooth spring animations for all elements + - Added decorative wave effect at the bottom + - Improved responsive design for all screen sizes + - Matches the modern glassmorphism and interactive style of other redesigned pages + - Maintains dark mode support with proper color transitions + ++- **State Update on Unmounted Component (AuthPage)**: Fixed React warnings about state updates on unmounted components ++ - Added `isMountedRef` to track component mount status ++ - Modified `handleSignUp` and `handleLogin` to check `isMountedRef.current` before calling `setLoading(false)` in finally block ++ - Set loading to false before navigation in success paths to prevent finally block from executing after unmount ++ - Added early returns after navigation to prevent finally block execution ++ - Properly handles cleanup in useEffect to set `isMountedRef.current = false` on unmount ++ - Eliminates React warnings about memory leaks and state updates on unmounted components + ++- **React Hooks Rules Violation (Documentation)**: Fixed hooks being called inside map loop ++ - `useRef()` and `useInView()` were being called inside `features.map()` loop, violating React's Rules of Hooks ++ - Created separate `FeatureCard` component that properly uses hooks at the top level ++ - Each feature card now has its own component instance with proper hook usage ++ - Maintains all animations and functionality while following React best practices ++ - Prevents potential errors when number of features changes between renders + ++- **CreateBridge Component Complete Redesign**: Completely redesigned the bridge creation/editing component with modern animations ++ - Added interactive mouse-tracking gradient orbs that follow cursor movement ++ - Implemented glassmorphism design with backdrop blur effects throughout ++ - Enhanced form with modern input fields featuring icons and better visual hierarchy ++ - Redesigned header with gradient icon and animated title ++ - Added edit mode indicator with animated badge ++ - Improved input fields with icon indicators (Link icon for platform, Globe icon for URL) ++ - Enhanced action buttons with gradient backgrounds and loading states ++ - Redesigned generated link display with animated entrance and gradient background ++ - Modernized warning modal with glassmorphism, gradient backgrounds, and better visual hierarchy ++ - Added smooth animations for form elements using Framer Motion ++ - Improved visual feedback for all interactions ++ - Fully responsive design optimized for all screen sizes ++ - Better spacing, typography, and color scheme ++ - Maintained all original functionality (create, edit, warning modal, etc.) + ++- **LinkPage Complete Redesign**: Completely redesigned the user's link management page with modern animations ++ - Added interactive mouse-tracking gradient orbs that follow cursor movement ++ - Implemented glassmorphism design with backdrop blur effects throughout ++ - Enhanced header section with gradient text and modern typography ++ - Redesigned hub link card with icon, better layout, and hover effects ++ - Added stats summary cards showing total links, total clicks, and hub page status ++ - Improved empty state with animated icon and call-to-action button ++ - Enhanced link cards with staggered animations on load ++ - Fully responsive design optimized for all screen sizes ++ - Smooth scroll animations using Framer Motion ++ - Modern gradient buttons with hover effects ++ - Animated grid background pattern for depth + ++- **Linkcard Component Redesign**: Completely redesigned individual link cards ++ - Modern glassmorphism card design with gradient borders ++ - Redesigned click counter with gradient icon and better visual hierarchy ++ - Enhanced action buttons with gradient backgrounds and hover animations ++ - Added "Open" button to directly visit the link ++ - Improved mobile responsiveness with separate mobile click counter ++ - Better spacing and typography throughout ++ - Smooth hover effects with scale and translate animations ++ - Gradient text for platform names ++ - Modern copy button with gradient background + ++- **Public Linktree Page Fixes and Enhancements**: Fixed issues and enhanced the public linktree page (linktree.ejs) ++ - **Fixed Mouse Tracking Performance**: Improved mouse tracking with requestAnimationFrame and percentage-based calculations ++ - Changed from direct pixel calculations to percentage-based for better responsiveness ++ - Implemented RAF loop for smooth animations instead of updating on every mousemove ++ - Added proper cleanup to prevent memory leaks ++ - Better performance on all devices ++ - **Fixed Image Loading**: Added fallback image with SVG placeholder if profile image fails to load + - Shows user's initial letter in a gradient circle if image is missing + - Prevents broken image icons ++ - **Fixed Empty State**: Added proper handling for when user has no links ++ - Shows friendly message instead of empty space ++ - Styled empty state with dashed border ++ - **Enhanced Script Execution**: Improved JavaScript with proper DOM ready checks ++ - Wrapped code in IIFE for better scope management ++ - Added proper event listener cleanup ++ - Prevents errors if elements don't exist ++ - **Added Ripple Effect**: Added click ripple animation on buttons for better feedback ++ - Creates visual feedback when links are clicked ++ - Smooth animation that fades out ++ - **Improved Accessibility**: Added better accessibility features ++ - Added focus states for keyboard navigation + - Added proper alt text for images ++ - Added rel="noopener noreferrer" for security ++ - Added meta description for SEO ++ - **Touch Device Optimizations**: Added specific styles for touch devices ++ - Removed hover effects on touch devices ++ - Better active states for mobile ++ - Improved tap targets ++ - **Performance Improvements**: Added will-change properties for better animation performance ++ - Smooth scrolling enabled ++ - Better browser optimization hints ++ - **Enhanced Responsive Design**: Improved mobile experience ++ - Better spacing on very small screens ++ - Adjusted font sizes and letter spacing ++ - Improved button sizes for touch ++ - **Better Error Handling**: Added null checks and error handling throughout ++ - Prevents JavaScript errors if elements are missing ++ - Graceful degradation + ++- **Public Linktree Page Redesign**: Completely redesigned the public linktree page (linktree.ejs) ++ - Modern glassmorphism design with backdrop blur effects ++ - Interactive mouse-tracking gradient orbs (purple, blue, orange) that follow cursor ++ - Animated grid background pattern for depth ++ - Enhanced profile picture with glowing effect and pulse animation ++ - Gradient username text with animated underline ++ - Modern link buttons with hover effects, scale animations, and shimmer effect +- - Smooth fade-in animations for all elements ++ - Smooth fade-in animations for all elements with staggered delays ++ - Arrow indicators on buttons that animate on hover ++ - Heart animation in footer ++ - Fully responsive design for mobile, tablet, and desktop ++ - Updated typography with Poppins and Montserrat fonts ++ - Modern CSS with smooth transitions and animations ++ - Improved accessibility and user experience + +- **Added Missing Features to HomePage and Documentation**: Added two important features that were missing + - **All Links at One Place**: Added feature highlighting the hub link functionality + - Users can access all their links by visiting https://clickly.cv/username (without platform name) diff --git a/frontend/src/components/AuthPage.jsx b/frontend/src/components/AuthPage.jsx index 609913d..d23e761 100644 --- a/frontend/src/components/AuthPage.jsx +++ b/frontend/src/components/AuthPage.jsx @@ -18,6 +18,7 @@ const AuthPage = () => { const [isShow, setShow] = useState(false); const [isShowSignup, setShowSignup] = useState(false); const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); + const isMountedRef = useRef(true); const [loginemail, setLoginEmail] = useState(""); const [loginpassword, setLoginPassword] = useState(""); @@ -25,6 +26,14 @@ const AuthPage = () => { const [signinpassword, setSigninPassword] = useState(""); const [username, setUsername] = useState(""); + // Track component mount status + useEffect(() => { + isMountedRef.current = true; + return () => { + isMountedRef.current = false; + }; + }, []); + // Mouse tracking for interactive background useEffect(() => { const handleMouseMove = (e) => { @@ -48,6 +57,10 @@ const AuthPage = () => { ); if (res.status === 201 && res.data.success) { toast.success(res.data.message); + // Set loading to false before navigation to prevent state update on unmounted component + if (isMountedRef.current) { + setLoading(false); + } navigate("/verify", { state: { username: username, @@ -55,16 +68,25 @@ const AuthPage = () => { password: signinpassword, }, }); + return; // Exit early to prevent finally block from executing } } catch (err) { console.log(err); const message = err.response?.data?.message || "Network Slow ! Try again"; toast.error(message); if (err.status === 409) { + // Set loading to false before navigation + if (isMountedRef.current) { + setLoading(false); + } navigate("/login"); + return; // Exit early to prevent finally block from executing } } finally { - setLoading(false); + // Only update loading state if component is still mounted + if (isMountedRef.current) { + setLoading(false); + } } }; @@ -83,14 +105,22 @@ const AuthPage = () => { setLoginEmail(""); setLoginPassword(""); toast.success(`Welcome ${res.data.user.username}!`); + // Set loading to false before navigation to prevent state update on unmounted component + if (isMountedRef.current) { + setLoading(false); + } navigate("/home", { replace: true }); + return; // Exit early to prevent finally block from executing } } catch (err) { console.log(err); const message = err.response?.data?.message || "Network Slow ! Try again"; toast.error(message); } finally { - setLoading(false); + // Only update loading state if component is still mounted + if (isMountedRef.current) { + setLoading(false); + } } }; diff --git a/frontend/src/components/DashBoard.jsx b/frontend/src/components/DashBoard.jsx index aca0a9b..3b3cfa1 100644 --- a/frontend/src/components/DashBoard.jsx +++ b/frontend/src/components/DashBoard.jsx @@ -17,7 +17,7 @@ const DashBoard = () => { return ( -
    +
    diff --git a/frontend/src/components/Documentation.jsx b/frontend/src/components/Documentation.jsx index e626766..cd6ae01 100644 --- a/frontend/src/components/Documentation.jsx +++ b/frontend/src/components/Documentation.jsx @@ -1,6 +1,65 @@ import React, { useState, useRef, useEffect } from "react"; import { useInView } from "framer-motion"; import { motion, AnimatePresence } from "framer-motion"; + +// Feature Card Component - Separate component to allow hooks usage +const FeatureCard = ({ feature, idx, hoveredFeature, setHoveredFeature }) => { + const ref = useRef(null); + const inView = useInView(ref, { once: true, margin: "-100px" }); + const IconComponent = feature.icon; + const isLeft = idx % 2 === 0; + + return ( + setHoveredFeature(idx)} + onHoverEnd={() => setHoveredFeature(null)} + className={`group relative bg-white/10 dark:bg-gray-900/50 backdrop-blur-xl rounded-3xl shadow-2xl border border-white/20 dark:border-gray-700/50 p-6 md:p-10 overflow-hidden ${ + isLeft ? "" : "md:flex-row-reverse" + } flex flex-col md:flex-row items-center gap-6 md:gap-8 cursor-pointer transition-all duration-300 ${ + hoveredFeature === idx ? "scale-105" : "scale-100" + }`} + > + {/* Gradient Background on Hover */} + + + {/* Icon */} + + + + + {/* Image */} + + + {/* Content */} +
    + + {feature.title} + + + {feature.desc} + +
    +
    + ); +}; import { useSelector } from "react-redux"; import { useLocation, useNavigate } from "react-router-dom"; import { TypewriterEffect } from "./ui/typewriter-effect"; @@ -487,63 +546,15 @@ const Documentation = () => { Key Features
    - {features.map((feature, idx) => { - const ref = useRef(null); - const inView = useInView(ref, { once: true, margin: "-100px" }); - const IconComponent = feature.icon; - const isLeft = idx % 2 === 0; - - return ( - setHoveredFeature(idx)} - onHoverEnd={() => setHoveredFeature(null)} - className={`group relative bg-white/10 dark:bg-gray-900/50 backdrop-blur-xl rounded-3xl shadow-2xl border border-white/20 dark:border-gray-700/50 p-6 md:p-10 overflow-hidden ${ - isLeft ? "" : "md:flex-row-reverse" - } flex flex-col md:flex-row items-center gap-6 md:gap-8 cursor-pointer transition-all duration-300 ${ - hoveredFeature === idx ? "scale-105" : "scale-100" - }`} - > - {/* Gradient Background on Hover */} - - - {/* Icon */} - - - - - {/* Image */} - - - {/* Content */} -
    - - {feature.title} - - - {feature.desc} - -
    -
    - ); - })} + {features.map((feature, idx) => ( + + ))}
    diff --git a/frontend/src/components/content/Content.jsx b/frontend/src/components/content/Content.jsx index d972b3a..59a995b 100644 --- a/frontend/src/components/content/Content.jsx +++ b/frontend/src/components/content/Content.jsx @@ -1,22 +1,156 @@ -import React from 'react' +import React, { useState, useEffect, useRef } from 'react'; +import { motion, useInView } from 'framer-motion'; +import { FaRocket, FaLink } from 'react-icons/fa'; +import { HiSparkles } from 'react-icons/hi2'; const Content = () => { - - return ( - <> -
    - {/* logo */} -
    - LinkBridger - Generate Links You'll Never Forget - Turn Your Username Into Smart Links - Quick and Simple! + const [mousePosition, setMousePosition] = useState({ x: 50, y: 50 }); + const containerRef = useRef(null); + const isInView = useInView(containerRef, { once: true, margin: "-100px" }); + + // Mouse tracking for interactive background + useEffect(() => { + const handleMouseMove = (e) => { + setMousePosition({ + x: (e.clientX / window.innerWidth) * 100, + y: (e.clientY / window.innerHeight) * 100, + }); + }; + + window.addEventListener('mousemove', handleMouseMove, { passive: true }); + return () => window.removeEventListener('mousemove', handleMouseMove); + }, []); + return ( +
    + {/* Interactive Background Gradient Orbs */} +
    + + +
    - + {/* Animated Grid Pattern */} +
    + + {/* Content Container */} +
    + {/* Floating Icons */} + + + + + + + + + + + {/* Main Content */} + + {/* Logo/Title */} + +

    + + LinkBridger + +

    + {/* Glowing effect behind text */} +
    + + + {/* Tagline 1 */} + + Generate Links You'll{' '} + + Never Forget + + + + {/* Tagline 2 */} + + Turn Your Username Into{' '} + Smart Links - Quick and Simple! + + + {/* Decorative Elements */} + +
    + +
    + + +
    - - ) -} + ); +}; -export default Content \ No newline at end of file +export default Content; diff --git a/frontend/src/components/footer/Footer.jsx b/frontend/src/components/footer/Footer.jsx index d2eb93a..d397149 100644 --- a/frontend/src/components/footer/Footer.jsx +++ b/frontend/src/components/footer/Footer.jsx @@ -1,104 +1,252 @@ -import React from 'react' -import { Link } from 'react-router-dom' +import React, { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; +import { motion } from 'framer-motion'; +import { FaGithub, FaLinkedin, FaInstagram, FaFacebook, FaTwitter, FaDribbble, FaHeart, FaRocket } from 'react-icons/fa'; const Footer = () => { + const [mousePosition, setMousePosition] = useState({ x: 50, y: 50 }); + + // Mouse tracking for interactive background + useEffect(() => { + const handleMouseMove = (e) => { + setMousePosition({ + x: (e.clientX / window.innerWidth) * 100, + y: (e.clientY / window.innerHeight) * 100, + }); + }; + window.addEventListener('mousemove', handleMouseMove, { passive: true }); + return () => window.removeEventListener('mousemove', handleMouseMove); + }, []); + + const footerLinks = { + Resources: [ + { href: 'https://deepak-aryan.vercel.app/', label: 'Portfolio', external: true }, + { href: 'https://www.instagram.com/uffh_rn/?hl=en', label: 'Instagram', external: true }, + ], + 'Follow Us': [ + { href: 'https://github.com/DpkRn', label: 'GitHub', external: true }, + { href: 'https://www.linkedin.com/in/deepak-kumar-b3181a236/', label: 'LinkedIn', external: true }, + ], + Legal: [ + { href: '#', label: 'Privacy Policy', external: false }, + { href: '#', label: 'Terms & Conditions', external: false }, + ], + About: [ + { to: '/about-developer', label: 'About Developer', external: false }, + ], + }; + + const socialLinks = [ + { href: '#', icon: FaFacebook, label: 'Facebook', color: 'hover:text-blue-500' }, + { href: '#', icon: FaTwitter, label: 'Twitter', color: 'hover:text-cyan-400' }, + { href: 'https://github.com/DpkRn', icon: FaGithub, label: 'GitHub', color: 'hover:text-gray-300' }, + { href: '#', icon: FaDribbble, label: 'Dribbble', color: 'hover:text-pink-500' }, + ]; + return ( -
    -
    + + ); +}; -export default Footer \ No newline at end of file +export default Footer; diff --git a/frontend/src/components/linkcard/Linkcard.jsx b/frontend/src/components/linkcard/Linkcard.jsx index 2155b83..8633ca2 100644 --- a/frontend/src/components/linkcard/Linkcard.jsx +++ b/frontend/src/components/linkcard/Linkcard.jsx @@ -1,9 +1,9 @@ import React, { useRef } from "react"; +import { motion } from "framer-motion"; import toast from "react-hot-toast"; import { MdContentCopy } from "react-icons/md"; import { useDispatch, useSelector } from "react-redux"; -import { FaEdit } from "react-icons/fa"; -import { MdDelete } from "react-icons/md"; +import { FaEdit, FaTrash, FaExternalLinkAlt, FaMousePointer } from "react-icons/fa"; import api from "../../utils/api"; import { setLinks } from "../../redux/userSlice"; import { setEditLinkData } from "../../redux/pageSlice"; @@ -12,117 +12,201 @@ import { FcImageFile } from "react-icons/fc"; const Linkcard = ({ sources }) => { const linkRef = useRef(null); const { username } = useSelector((store) => store.admin.user); - const links = useSelector((store) => store.admin.links); - const dispatch=useDispatch() + const links = useSelector((store) => store.admin.links); + const dispatch = useDispatch(); - const { source, destination, clicked,_id } = sources; - + const { source, destination, clicked, _id } = sources; - - const handleDeleteLink=async(id)=>{ - try{ - const res=await api.post('/source/deletelink',{id:id},{withCredentials:true}) - if(res.status===200&&res.data.success){ - const tempArr=links.filter(link=>link._id!=id) - dispatch(setLinks(tempArr)) - - toast.success("Bridge has been deleted successfully!") + const handleDeleteLink = async (id) => { + try { + const res = await api.post('/source/deletelink', { id: id }, { withCredentials: true }); + if (res.status === 200 && res.data.success) { + const tempArr = links.filter(link => link._id != id); + dispatch(setLinks(tempArr)); + toast.success("Bridge has been deleted successfully!"); } - }catch(err){ - const message=err.response?.data?.message || "Server Internal Error" - toast.error(message) + } catch (err) { + const message = err.response?.data?.message || "Server Internal Error"; + toast.error(message); } - } - const handleEditLink=(id)=>{ + }; + + const handleEditLink = (id) => { const linkToEdit = links.find(link => link._id === id); - if(!linkToEdit) { + if (!linkToEdit) { toast.error("Link not found"); return; } - // Set edit mode with link data - this will populate CreateBridge form dispatch(setEditLinkData({ id: linkToEdit._id, source: linkToEdit.source, destination: linkToEdit.destination })); - // Scroll to CreateBridge component setTimeout(() => { const createBridgeElement = document.querySelector('[data-create-bridge]'); if (createBridgeElement) { createBridgeElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }, 100); - } + }; const copyToClipboard = () => { - const linkText = linkRef.current.innerText; // Get the text from the ref + const linkText = linkRef.current.innerText; navigator.clipboard .writeText(linkText) .then(() => { - toast.success("Link copied to clipboard!"); // Show notification + toast.success("Link copied to clipboard!"); }) .catch((err) => { - toast.error("Failed to copy!"); // Show error message if failed + toast.error("Failed to copy!"); }); }; - return ( -
    - - {/* linkLogo */} -
    - {clicked} - Clicked -
    + + {/* Gradient Background on Hover */} + - {/* LinkContent */} -
    - {/* platform */} -
    - - {source} - -
    - {/* destination */} -
    - {destination} -
    - - {/* bridge */} -
    -
    - - {`https://clickly.cv/${username}/${source}`} - - - +
    +
    + {/* Click Counter Section */} + + + + + + {clicked || 0} + + + Clicks -
    -
    - {/* clicked section for small */} -
    - {clicked} - Clicked -
    + + + {/* Main Content */} +
    + {/* Platform Name */} +
    + + {source.toUpperCase()} + +

    + {destination} +

    +
    + + {/* Personalized Link */} +
    +

    + Your Personalized Link: +

    +
    + + {`https://clickly.cv/${username}/${source}`} + + + + +
    +
    - {/* Edit section */} -
    - - - - - - + {/* Mobile Click Counter */} +
    + + + +
    +
    {clicked || 0}
    +
    Total Clicks
    +
    +
    + {/* Action Buttons */} +
    + handleDeleteLink(_id)} + className="bg-gradient-to-r from-red-600 to-red-700 hover:from-red-700 hover:to-red-800 text-white p-3 rounded-xl transition-all duration-300 shadow-lg hover:shadow-xl flex items-center gap-2" + title="Delete link" + > + + Delete + + + handleEditLink(_id)} + className="bg-gradient-to-r from-blue-600 to-cyan-600 hover:from-blue-700 hover:to-cyan-700 text-white p-3 rounded-xl transition-all duration-300 shadow-lg hover:shadow-xl flex items-center gap-2" + title="Edit link" + > + + Edit + + + + + Open + + + handleEditLink(_id)} + className="bg-white/10 dark:bg-gray-800/50 hover:bg-white/20 dark:hover:bg-gray-700/50 text-gray-900 dark:text-white p-3 rounded-xl transition-all duration-300 border border-white/20 flex items-center gap-2" + title="Change image" + > + + Image + +
    +
    -
    + ); }; diff --git a/frontend/src/components/navbar/Nav.jsx b/frontend/src/components/navbar/Nav.jsx index 631b395..a9f96b1 100644 --- a/frontend/src/components/navbar/Nav.jsx +++ b/frontend/src/components/navbar/Nav.jsx @@ -1,4 +1,5 @@ import React, { useEffect, useRef, useState } from "react"; +import { motion, AnimatePresence } from "framer-motion"; import { useDispatch, useSelector } from "react-redux"; import { Link, useLocation, useNavigate } from "react-router-dom"; import { setSidebarMenu, toggleDarkMode } from "../../redux/pageSlice"; @@ -10,29 +11,40 @@ import { setNotifications, setUser, } from "../../redux/userSlice"; -import { MdOutlineArrowDropDownCircle } from "react-icons/md"; +import { MdOutlineArrowDropDownCircle, MdMenu, MdClose } from "react-icons/md"; import { MdDarkMode, MdLightMode } from "react-icons/md"; +import { FaBell, FaUser, FaCog, FaSignOutAlt, FaHome, FaLink, FaBook } from "react-icons/fa"; import Notification from "../notification/Notification"; - const Nav = () => { const navigate = useNavigate(); const dispatch = useDispatch(); - const notificationRef=useRef(null) - const profilePageRef=useRef(null) - const location=useLocation() - + const notificationRef = useRef(null); + const profilePageRef = useRef(null); + const location = useLocation(); const [profileMenu, setProfileMenu] = useState(false); const [notificationPage, setNotificationPage] = useState(false); + const [mousePosition, setMousePosition] = useState({ x: 50, y: 50 }); const { sidebarMenu, darkMode } = useSelector((store) => store.page); const username = useSelector((store) => store.admin.user.username); const links = useSelector((store) => store.admin.links); const notifications = useSelector((store) => store.admin.notifications); + // Mouse tracking for interactive background + useEffect(() => { + const handleMouseMove = (e) => { + setMousePosition({ + x: (e.clientX / window.innerWidth) * 100, + y: (e.clientY / window.innerHeight) * 100, + }); + }; + window.addEventListener("mousemove", handleMouseMove, { passive: true }); + return () => window.removeEventListener("mousemove", handleMouseMove); + }, []); + const handleSignOut = async (e) => { - // e.preventDefault(); try { const res = await api.get("/auth/signout", { withCredentials: true }); if (res.status === 200 && res.data.success) { @@ -66,7 +78,6 @@ const Nav = () => { }; const handleMarkRead = async () => { - console.log("clicked !"); try { const res = await api.post( "/source/notifications", @@ -77,41 +88,40 @@ const Nav = () => { await getAllLinks(); dispatch(setNotifications(0)); setNotificationPage(false); + toast.success("All notifications marked as read!"); } } catch (err) { console.log(err); } }; - useEffect(() => { dispatch( setNotifications(links.reduce((acc, link) => acc + link.notSeen, 0)) ); - - }, [links, notifications]); + }, [links, notifications, dispatch]); const onNotificationClick = async () => { if (notifications === 0) { - toast("You have no new clicks"); + toast("You have no new clicks", { icon: "📭" }); return; } setNotificationPage((state) => !state); }; -// handle out side click + // Handle outside click useEffect(() => { const handleClickOutside = (event) => { if ( - notificationRef.current && - !notificationRef.current.contains(event.target) && + notificationRef.current && + !notificationRef.current.contains(event.target) && !event.target.closest(".notification-button") ) { setNotificationPage(false); } if ( - profilePageRef.current && - !profilePageRef.current.contains(event.target) && + profilePageRef.current && + !profilePageRef.current.contains(event.target) && !event.target.closest(".profileMenu-button") ) { setProfileMenu(false); @@ -127,124 +137,138 @@ const Nav = () => { return () => { document.removeEventListener("mousedown", handleClickOutside); }; - }, [notificationPage,profileMenu]); - - const handleProfileClick=(e)=>{ - navigate('/profile') - setProfileMenu(false) - } + }, [notificationPage, profileMenu]); + const handleProfileClick = (e) => { + navigate("/profile"); + setProfileMenu(false); + }; + const navLinks = [ + { to: "/home", label: "Home", icon: FaHome }, + { to: "/links", label: "Links", icon: FaLink }, + { to: "/doc", label: "Docs", icon: FaBook }, + ]; return ( - ); }; diff --git a/frontend/src/components/notification/Notification.jsx b/frontend/src/components/notification/Notification.jsx index e8c41c0..a349255 100644 --- a/frontend/src/components/notification/Notification.jsx +++ b/frontend/src/components/notification/Notification.jsx @@ -1,17 +1,87 @@ -import React from 'react' -import { useSelector } from 'react-redux' +import React from 'react'; +import { useSelector } from 'react-redux'; +import { motion, AnimatePresence } from 'framer-motion'; +import { FaMousePointer, FaLink, FaChartLine } from 'react-icons/fa'; const Notification = () => { - const links=useSelector(store=>store.admin.links) + const links = useSelector(store => store.admin.links); + const notificationsWithClicks = links.filter(link => link.notSeen > 0); + + if (notificationsWithClicks.length === 0) { + return ( +
    + +
    + +
    +
    +

    + No New Activity +

    +

    + You're all caught up! Check back later for new clicks. +

    +
    +
    +
    + ); + } + return ( -
    -
    -
      - { links.map((link,ind)=>link.notSeen>0&&
      {`${link.notSeen} clicks on ${link.source}`}
      )} -
    -
    +
    + + {notificationsWithClicks.map((link, index) => ( + +
    + {/* Icon */} + + + + + {/* Content */} +
    +
    + + {link.notSeen} {link.notSeen === 1 ? 'New Click' : 'New Clicks'} + + + {link.source.toUpperCase()} + +
    +

    + + + {link.destination || 'Your personalized link'} + +

    + +
    +
    +
    + ))} +
    - ) -} + ); +}; -export default Notification \ No newline at end of file +export default Notification; diff --git a/frontend/src/components/pages/CreateBridge.jsx b/frontend/src/components/pages/CreateBridge.jsx index 5e9830a..90dc5e5 100644 --- a/frontend/src/components/pages/CreateBridge.jsx +++ b/frontend/src/components/pages/CreateBridge.jsx @@ -1,38 +1,50 @@ import { useRef, useState, useEffect } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; import toast from 'react-hot-toast'; import { useDispatch, useSelector } from 'react-redux'; import { MdContentCopy } from "react-icons/md"; +import { FaLink, FaGlobe, FaExclamationTriangle, FaCheckCircle, FaTimes, FaRocket, FaEdit } from 'react-icons/fa'; import api from '../../utils/api'; import { setLinks } from '../../redux/userSlice'; import { setEditLinkData, clearEditLinkData } from '../../redux/pageSlice'; const CreateBridge = () => { - const dispatch=useDispatch() - const [loading,setLoading]=useState(false) + const dispatch = useDispatch(); + const [loading, setLoading] = useState(false); const [platform, setPlatform] = useState(''); - const [source,setSource]=useState('') + const [source, setSource] = useState(''); const [profileLink, setProfileLink] = useState(''); - const [showBridge,setShowBridge]=useState(false) + const [showBridge, setShowBridge] = useState(false); const [showWarningModal, setShowWarningModal] = useState(false); const [pendingUpdate, setPendingUpdate] = useState(null); + const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); const linkRef = useRef(null); - const { username,_id } = useSelector((store) => store.admin.user); - let links = useSelector((store) => store.admin.links); + const { username, _id } = useSelector((store) => store.admin.user); + let links = useSelector((store) => store.admin.links); const editLinkData = useSelector((store) => store.page.editLinkData); const isEditMode = editLinkData !== null; + // Mouse tracking for interactive background + useEffect(() => { + const handleMouseMove = (e) => { + setMousePosition({ + x: (e.clientX / window.innerWidth) * 100, + y: (e.clientY / window.innerHeight) * 100, + }); + }; + window.addEventListener("mousemove", handleMouseMove, { passive: true }); + return () => window.removeEventListener("mousemove", handleMouseMove); + }, []); + // Populate form when edit mode is activated useEffect(() => { if (editLinkData) { - // Normalize platform to lowercase for consistency - // This ensures comparison works correctly even if database has mixed-case values const normalizedPlatform = editLinkData.source.toLowerCase().trim(); setPlatform(normalizedPlatform); setProfileLink(editLinkData.destination); setSource(normalizedPlatform); setShowBridge(false); } else { - // Reset form when not in edit mode setPlatform(''); setProfileLink(''); setSource(''); @@ -41,13 +53,11 @@ const CreateBridge = () => { }, [editLinkData]); const handleSubmit = async (e) => { - e.preventDefault() + e.preventDefault(); if (isEditMode && editLinkData) { - // Check if platform is being changed const platformChanged = platform.toLowerCase().trim() !== editLinkData.source.toLowerCase().trim(); - // Show warning modal if platform is being changed if (platformChanged) { setPendingUpdate({ id: editLinkData.id, @@ -55,39 +65,35 @@ const CreateBridge = () => { destination: profileLink.trim() }); setShowWarningModal(true); - return; // Wait for user confirmation + return; } - // Platform not changed, proceed with update directly (destination can still be updated) await performUpdate({ id: editLinkData.id, source: platform.toLowerCase().trim(), destination: profileLink.trim() }); } else { - // Create new link - try{ - setLoading(true) - const res=await api.post('/source/addnewsource',{userId:_id,username:username,source:platform,destination:profileLink},{withCredentials:true}) - if(res.status===201&&res.data.success){ - links=[...links,res.data.link] - dispatch(setLinks(links)) - setSource(res.data.link.source) - setShowBridge(true) - setPlatform('') - setProfileLink('') - toast.success("Bridge has been created successfully!") + try { + setLoading(true); + const res = await api.post('/source/addnewsource', { userId: _id, username: username, source: platform, destination: profileLink }, { withCredentials: true }); + if (res.status === 201 && res.data.success) { + links = [...links, res.data.link]; + dispatch(setLinks(links)); + setSource(res.data.link.source); + setShowBridge(true); + setPlatform(''); + setProfileLink(''); + toast.success("Bridge has been created successfully!"); } else if (res.status === 201 && !res.data.success) { - // Handle case where API returns 201 but success is false const message = res.data.message || "Creation failed"; toast.error(message); } - }catch(err){ - const message=err.response?.data?.message || "Server Internal Error" - toast.error(message) - }finally{ - // Always reset loading state, regardless of success or failure - setLoading(false) + } catch (err) { + const message = err.response?.data?.message || "Server Internal Error"; + toast.error(message); + } finally { + setLoading(false); } } }; @@ -100,17 +106,14 @@ const CreateBridge = () => { if (res.status === 200 && res.data.success) { const updatedLink = res.data.link || res.data; - // Check if the link exists in current state const linkExists = links.some(link => link._id === editLinkData.id); let updatedLinks; if (linkExists) { - // Update existing link in array updatedLinks = links.map(link => link._id === editLinkData.id ? { ...link, ...updatedLink } : link ); } else { - // Link was removed from state, add the updated link back updatedLinks = [...links, updatedLink]; toast.warning("Link was restored and updated. It may have been removed from the list."); } @@ -121,7 +124,6 @@ const CreateBridge = () => { dispatch(clearEditLinkData()); toast.success("Bridge has been updated successfully!"); } else if (res.status === 200 && !res.data.success) { - // Handle case where API returns 200 but success is false const message = res.data.message || "Update failed"; toast.error(message); } @@ -129,18 +131,13 @@ const CreateBridge = () => { const message = err.response?.data?.message || "Server Internal Error"; toast.error(message); } finally { - // Always reset loading state, regardless of success or failure setLoading(false); } }; const handleConfirmUpdate = async () => { - // Set loading state BEFORE closing modal to prevent form interaction - // This ensures form inputs remain disabled during the async update setLoading(true); setShowWarningModal(false); - // Use current form state instead of stale pendingUpdate - // This ensures any changes made while modal was open are included if (editLinkData) { await performUpdate({ id: editLinkData.id, @@ -165,158 +162,371 @@ const CreateBridge = () => { }; const copyToClipboard = () => { - const linkText = linkRef.current.innerText; // Get the text from the ref + const linkText = linkRef.current.innerText; navigator.clipboard.writeText(linkText) .then(() => { - toast.success("Link copied to clipboard!"); // Show notification + toast.success("Link copied to clipboard!"); }) .catch((err) => { - toast.error("Failed to copy!"); // Show error message if failed + toast.error("Failed to copy!"); }); }; - return ( -
    -
    -

    - {isEditMode ? "Edit Social Profile Bridge" : "Social Profile Bridge"} -

    - {isEditMode && ( -
    - Editing link ID: {editLinkData?.id} -
    - )} + const containerVariants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.1, + }, + }, + }; + + const itemVariants = { + hidden: { opacity: 0, y: 20 }, + visible: { + opacity: 1, + y: 0, + transition: { duration: 0.5 }, + }, + }; - { - const newPlatform = (e.target.value).toLowerCase(); - setPlatform(newPlatform); - // Update source state when platform changes in edit mode - if (isEditMode) { - setSource(newPlatform); - } + return ( +
    + {/* Animated Background */} +
    + {/* Gradient Orbs */} + - - setProfileLink(e.target.value)} - disabled={showWarningModal || loading} - className="p-3 w-full md:w-[80%] border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white rounded-lg focus:ring-2 focus:ring-indigo-400 dark:focus:ring-indigo-500 outline-none transition-all transform hover:scale-105 duration-300 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none" - required + + + + {/* Animated Grid */} +
    +
    -
    - - - {isEditMode && ( - - )} -
    - {/* bridge */} - {showBridge&&
    -
    - - {`https://clickly.cv/${username}/${source}`} - - - - -
    -
    } - + + {isEditMode ? ( + + ) : ( + + )} + +
    +

    + {isEditMode ? "Edit Bridge" : "Create Bridge"} +

    +

    + {isEditMode ? "Update your personalized link" : "Create a new personalized social media link"} +

    +
    +
    - {/* Warning Modal - Outside form to avoid form submission issues */} - {showWarningModal && editLinkData && ( -
    -
    -
    -
    - - - + {/* Edit Mode Indicator */} + {isEditMode && ( + + +
    +

    Edit Mode Active

    +

    Editing link ID: {editLinkData?.id}

    +
    +
    + )} + + {/* Platform Input */} + + +
    +
    + +
    + { + const newPlatform = (e.target.value).toLowerCase(); + setPlatform(newPlatform); + if (isEditMode) { + setSource(newPlatform); + } + }} + disabled={showWarningModal || loading} + className="w-full pl-12 pr-4 py-4 bg-white/10 dark:bg-gray-800/50 border border-white/20 dark:border-gray-700 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-300 backdrop-blur-sm disabled:opacity-50 disabled:cursor-not-allowed lowercase" + required + />
    -

    - Platform Change Warning -

    -
    - -
    -

    - Changing the platform from "{editLinkData.source}" to "{platform.toLowerCase()}" will make your old link invalid! +

    + Enter the platform name in lowercase (e.g., "linkedin" not "LinkedIn")

    + + + {/* Profile Link Input */} + + +
    +
    + +
    + setProfileLink(e.target.value)} + disabled={showWarningModal || loading} + className="w-full pl-12 pr-4 py-4 bg-white/10 dark:bg-gray-800/50 border border-white/20 dark:border-gray-700 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-300 backdrop-blur-sm disabled:opacity-50 disabled:cursor-not-allowed" + required + /> +
    +

    + Enter the full URL of your profile on this platform +

    +
    + + {/* Action Buttons */} + + + {loading ? ( + <> + + + + + {isEditMode ? "Updating..." : "Creating..."} + + ) : ( + <> + {isEditMode ? : } + {isEditMode ? "Update Bridge" : "Create Bridge"} + + )} + -
    -
    -

    Old Link (will become invalid):

    -

    - https://clickly.cv/{username}/{editLinkData.source} -

    + {isEditMode && ( + + + Cancel + + )} + + + {/* Generated Link Display */} + + {showBridge && ( + +
    + +

    Your Personalized Link:

    +
    +
    + + {`https://clickly.cv/${username}/${source}`} + + + + +
    +
    + )} +
    + + +
    + + {/* Warning Modal */} + + {showWarningModal && editLinkData && ( + + e.stopPropagation()} + className="bg-white/10 dark:bg-gray-900/90 backdrop-blur-xl rounded-3xl shadow-2xl max-w-md w-full p-6 md:p-8 border-2 border-yellow-400/50 dark:border-yellow-500/50 relative overflow-hidden" + > + {/* Gradient Background */} +
    + +
    +
    + + + +

    + Platform Change Warning +

    -
    -

    New Link:

    -

    - https://clickly.cv/{username}/{platform.toLowerCase()} + +

    +

    + Changing the platform from "{editLinkData.source}" to "{platform.toLowerCase()}" will make your old link invalid!

    + +
    +
    +

    + + Old Link (will become invalid): +

    +

    + https://clickly.cv/{username}/{editLinkData.source} +

    +
    +
    +

    + + New Link: +

    +

    + https://clickly.cv/{username}/{platform.toLowerCase()} +

    +
    +
    + +
    +

    + + Anyone who has bookmarked or shared the old link will need to use the new one. +

    +
    +
    + +
    + + Cancel + + + {loading ? ( + <> + + + + + Updating... + + ) : ( + <> + Continue Anyway + + )} +
    - -

    - ⚠️ Anyone who has bookmarked or shared the old link will need to use the new one. -

    -
    - -
    - - -
    -
    -
    - )} +
    + + )} +
    ); }; diff --git a/frontend/src/components/pages/HomePage.jsx b/frontend/src/components/pages/HomePage.jsx index 7cf2d11..b6de52d 100644 --- a/frontend/src/components/pages/HomePage.jsx +++ b/frontend/src/components/pages/HomePage.jsx @@ -37,49 +37,52 @@ const AnimatedCounter = ({ end, duration = 2000, suffix = "", statsInView }) => const countRef = useRef(null); const animationFrameRef = useRef(null); const isMountedRef = useRef(true); + const hasAnimatedRef = useRef(false); useEffect(() => { + // Reset animation state when end or duration changes + if (hasAnimatedRef.current) { + setCount(0); + hasAnimatedRef.current = false; + // Cancel any pending animation + if (animationFrameRef.current !== null) { + cancelAnimationFrame(animationFrameRef.current); + animationFrameRef.current = null; + } + } + + // Only start animation if element is in view if (!statsInView) return; + isMountedRef.current = true; + hasAnimatedRef.current = true; - const observer = new IntersectionObserver( - ([entry]) => { - if (entry.isIntersecting && isMountedRef.current) { - let startTime = null; - const animate = (currentTime) => { - if (!isMountedRef.current) { - // Component unmounted, stop animation - return; - } - - if (!startTime) startTime = currentTime; - const progress = Math.min((currentTime - startTime) / duration, 1); - const easeOutQuart = 1 - Math.pow(1 - progress, 4); - - if (isMountedRef.current) { - setCount(Math.floor(easeOutQuart * end)); - } - - if (progress < 1 && isMountedRef.current) { - animationFrameRef.current = requestAnimationFrame(animate); - } else { - animationFrameRef.current = null; - } - }; - animationFrameRef.current = requestAnimationFrame(animate); - observer.disconnect(); - } - }, - { threshold: 0.1 } - ); - - if (countRef.current) { - observer.observe(countRef.current); - } + let startTime = null; + const animate = (currentTime) => { + if (!isMountedRef.current) { + // Component unmounted, stop animation + return; + } + + if (!startTime) startTime = currentTime; + const progress = Math.min((currentTime - startTime) / duration, 1); + const easeOutQuart = 1 - Math.pow(1 - progress, 4); + + if (isMountedRef.current) { + setCount(Math.floor(easeOutQuart * end)); + } + + if (progress < 1 && isMountedRef.current) { + animationFrameRef.current = requestAnimationFrame(animate); + } else { + animationFrameRef.current = null; + } + }; + + animationFrameRef.current = requestAnimationFrame(animate); return () => { isMountedRef.current = false; - observer.disconnect(); // Cancel any pending animation frame if (animationFrameRef.current !== null) { cancelAnimationFrame(animationFrameRef.current); diff --git a/frontend/src/components/pages/LinkPage.jsx b/frontend/src/components/pages/LinkPage.jsx index b570688..b961f2f 100644 --- a/frontend/src/components/pages/LinkPage.jsx +++ b/frontend/src/components/pages/LinkPage.jsx @@ -1,95 +1,334 @@ -import React, { useEffect, useRef } from "react"; +import React, { useEffect, useRef, useState } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { useInView } from "framer-motion"; import Linkcard from "../linkcard/Linkcard"; import { useDispatch, useSelector } from "react-redux"; import { useLocation, useNavigate } from "react-router-dom"; -import { MdContentCopy } from "react-icons/md"; +import { MdContentCopy, MdAddCircle } from "react-icons/md"; +import { FaRocket, FaLink, FaChartLine, FaHome } from "react-icons/fa"; import { setLinks } from "../../redux/userSlice"; import toast from "react-hot-toast"; import api from "../../utils/api"; const LinkPage = () => { const navigate = useNavigate(); - const dispatch=useDispatch() + const dispatch = useDispatch(); const linkRef = useRef(null); const location = useLocation(); + const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); + const containerRef = useRef(null); const links = useSelector((store) => store.admin.links); const username = useSelector((store) => store.admin.user.username); + // Mouse tracking for interactive background + useEffect(() => { + const handleMouseMove = (e) => { + setMousePosition({ + x: (e.clientX / window.innerWidth) * 100, + y: (e.clientY / window.innerHeight) * 100, + }); + }; + window.addEventListener("mousemove", handleMouseMove, { passive: true }); + return () => window.removeEventListener("mousemove", handleMouseMove); + }, []); const handleCreateNewBridge = () => { navigate("/home"); }; - - - useEffect(()=>{ - const getAllLinks=async()=>{ - try{ - const res=await api.post('/source/getallsource',{username},{withCredentials:true}); - if(res.status===200&&res.data.success){ - - dispatch(setLinks(res.data.sources)) + useEffect(() => { + const getAllLinks = async () => { + try { + const res = await api.post('/source/getallsource', { username }, { withCredentials: true }); + if (res.status === 200 && res.data.success) { + dispatch(setLinks(res.data.sources)); + } + } catch (err) { + console.log(err); + const message = err.response?.data?.message || "Server Internal Error"; + toast.error(message); } - }catch(err){ - console.log(err) - const message=err.response?.data?.message||"Server Internal Error" - toast.error(message) - } - } - getAllLinks() - },[]) - - - + }; + getAllLinks(); + }, []); const copyToClipboard = () => { - const linkText = linkRef.current.innerText; // Get the text from the ref + const linkText = linkRef.current.innerText; navigator.clipboard .writeText(linkText) .then(() => { - toast.success("Link copied to clipboard!"); // Show notification + toast.success("Link copied to clipboard!"); }) .catch((err) => { - toast.error("Failed to copy!"); // Show error message if failed + toast.error("Failed to copy!"); }); }; + const containerVariants = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.1, + }, + }, + }; + + const itemVariants = { + hidden: { opacity: 0, y: 20 }, + visible: { + opacity: 1, + y: 0, + transition: { duration: 0.5 }, + }, + }; return ( -
    - {/* button */} - {location.pathname === "/links" && ( -
    - -
    - )} - Your linktree is live on: -
    - - {`https://clickly.cv/${username}`} - - - + {/* Animated Background */} +
    + {/* Gradient Orbs */} + - -
    - + + + + {/* Animated Grid */} +
    +
    + +
    + + {/* Header Section */} + +
    + +

    + Your Links +

    +

    + Manage and track all your personalized social media links +

    +
    + + {location.pathname === "/links" && ( + + + Create New Bridge + + )} +
    + + {/* Hub Link Card */} + + +
    +
    + + + +
    +

    + Your Linktree Hub +

    +

    + Share this link to let visitors see all your profiles in one place +

    +
    +
    +
    +
    +

    + Hub Link: +

    +
    + + {`https://clickly.cv/${username}`} + + + + +
    +
    +
    +
    +
    +
    + + {/* Links Section */} + + {links.length === 0 ? ( + + + + +

    + No Links Yet +

    +

    + Get started by creating your first personalized link. Click the button above to create a new bridge! +

    + + + Create Your First Link + +
    + ) : ( + + {links.map((link, index) => ( + + + + ))} + + )} +
    + + {/* Stats Summary */} + {links.length > 0 && ( + + + + + +

    + {links.length} +

    +

    Total Links

    +
    + + + + + +

    + {links.reduce((sum, link) => sum + (link.clicked || 0), 0)} +

    +

    Total Clicks

    +
    - {links.length === 0 ? ( -

    - No links found. Add a new link by clicking the Create New button. -

    - ) : ( - links.map((link) => ) - )} + + + + +

    + Live +

    +

    Hub Page

    +
    +
    + )} +
    +
    ); }; From 5ec146d9a9c24ad9ffccfe7373a5d97725e5c309 Mon Sep 17 00:00:00 2001 From: DpkRn Date: Thu, 18 Dec 2025 03:56:11 +0530 Subject: [PATCH 006/166] Refined UI components with improved color schemes and transparency for better visibility in light mode. Updated LinkPage and Linkcard backgrounds for enhanced readability. Adjusted Notification and Nav components for consistent styling and improved user experience across light and dark themes. --- frontend/CHANGELOG.md | 39 +++++++++++++++++-- frontend/src/components/linkcard/Linkcard.jsx | 2 +- frontend/src/components/navbar/Nav.jsx | 34 ++++++++-------- .../components/notification/Notification.jsx | 12 +++--- frontend/src/components/pages/LinkPage.jsx | 2 +- 5 files changed, 60 insertions(+), 29 deletions(-) diff --git a/frontend/CHANGELOG.md b/frontend/CHANGELOG.md index 6fafb84..da0b162 100644 --- a/frontend/CHANGELOG.md +++ b/frontend/CHANGELOG.md @@ -46,10 +46,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Better spacing and typography for improved readability - Consistent design language with the rest of the application -+- **LinkPage Light Mode Dark Link Containers**: Added darker containers only for link sections in light mode - - Kept main card backgrounds as `bg-white/10` (reverted from dark containers) - - Only link containers (inner divs showing actual links) use `bg-gray-800/90` in light mode - - Link text uses white color for better readability on dark link containers ++- **Nav Component Light Mode Visibility**: Improved navigation links and navbar visibility in light mode + - Changed navbar background from `bg-white/10` to `bg-gray-800/95` in light mode for better visibility + - Updated navbar border from `border-white/20` to `border-gray-700/50` in light mode + - Desktop nav links: Changed inactive links from `text-gray-300` to `text-gray-900 dark:text-gray-300` for better contrast + - Desktop nav links: Updated active link text to `text-white dark:text-white` for consistency + - Mobile sidebar: Changed background from `bg-white/10` to `bg-gray-800/95` in light mode + - Mobile sidebar links: Updated to `text-white dark:text-gray-300` for better visibility + - Maintained all dark mode styles unchanged + ++- **Notification and Profile Menu Z-Index and Light Mode Visibility**: Fixed overlapping issues and improved visibility + - Increased z-index from `z-10` to `z-[100]` for both notification dropdown and profile menu to appear above other elements + - Changed notification dropdown background from `bg-white/10` to `bg-gray-800/95` in light mode (95% opacity for better visibility) + - Changed profile menu background from `bg-white/10` to `bg-gray-800/95` in light mode + - Updated borders from `border-white/20` to `border-gray-700/50` in light mode for better definition + - Updated notification item backgrounds to `bg-gray-700/30` in light mode for better contrast + - Changed all text colors to white/light colors for readability on dark backgrounds in light mode + - Maintained all dark mode styles unchanged + - Fixed overlapping with Create Bridge button and other page elements + ++- **Notification and Profile Menu Light Mode Text Visibility**: Improved text visibility in light mode + - Notification empty state: Changed `text-gray-300` to `text-gray-900` and `text-gray-400` to `text-gray-700` + - Notification items: Changed count text from `text-white` to `text-gray-900 dark:text-white` + - Notification badge: Changed `text-purple-300` to `text-purple-600 dark:text-purple-300` for better contrast + - Notification destination: Changed `text-gray-400` to `text-gray-700` for better readability + - Profile menu dropdown: Changed username from `text-white` to `text-gray-900 dark:text-white` + - Profile menu welcome text: Changed `text-gray-400` to `text-gray-700 dark:text-gray-400` + - Profile menu items: Changed `text-gray-200` to `text-gray-900 dark:text-gray-200` for better visibility + - Profile menu sign out: Changed `text-red-300` to `text-red-600 dark:text-red-300` for better contrast + - Notification dropdown header: Changed title from `text-white` to `text-gray-900 dark:text-white` + - Maintained all dark mode styles unchanged + ++- **LinkPage Light Mode Transparent Link Containers**: Made link containers more transparent and less dark + - Reduced link container opacity from `bg-gray-800/90` to `bg-gray-800/40` in light mode for better transparency + - Reduced border opacity from `border-gray-700/50` to `border-gray-700/30` for lighter appearance + - Link text remains white for readability on semi-transparent dark containers - Maintained all dark mode styles unchanged - Applied to Hub Link container and Personalized Link containers in Linkcard components diff --git a/frontend/src/components/linkcard/Linkcard.jsx b/frontend/src/components/linkcard/Linkcard.jsx index 8633ca2..2559a71 100644 --- a/frontend/src/components/linkcard/Linkcard.jsx +++ b/frontend/src/components/linkcard/Linkcard.jsx @@ -118,7 +118,7 @@ const Linkcard = ({ sources }) => {
    {/* Personalized Link */} -
    +

    Your Personalized Link:

    diff --git a/frontend/src/components/navbar/Nav.jsx b/frontend/src/components/navbar/Nav.jsx index a9f96b1..1278481 100644 --- a/frontend/src/components/navbar/Nav.jsx +++ b/frontend/src/components/navbar/Nav.jsx @@ -151,7 +151,7 @@ const Nav = () => { ]; return ( -