diff --git a/frontend/.env.example b/frontend/.env.example index f6d2e48..5ba416a 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -17,6 +17,9 @@ VITE_APP_NAME=AutoMaxLib VITE_APP_VERSION=1.0.0 VITE_APP_DESCRIPTION=Automate your GitHub commits and maintain your contribution streak +# Static assets cache-busting: bump this when you update images in public/ +VITE_ASSETS_VERSION=1 + # Feature Flags VITE_ENABLE_PAYMENTS=true VITE_ENABLE_AI_FEATURES=true diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 2ab3c22..5cde7a2 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -17,6 +17,7 @@ import Upgrade from './pages/Upgrade' import PublicBadge from './pages/PublicBadge' import SignInPage from './pages/SignInPage' import SignUpPage from './pages/SignUpPage' +import ForgotPassword from './pages/ForgotPassword' import PatternGenerator from './components/PatternGenerator' import ReadmeGeneratorPage from './pages/ReadmeGeneratorPage' import RepositoryReadmeGeneratorPage from './pages/RepositoryReadmeGeneratorPage' @@ -48,6 +49,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> @@ -58,7 +60,7 @@ function App() { } /> } /> } /> - + {/* Protected routes */} diff --git a/frontend/src/components/FeatureShowcase.jsx b/frontend/src/components/FeatureShowcase.jsx index 0a02ac8..23feea0 100644 --- a/frontend/src/components/FeatureShowcase.jsx +++ b/frontend/src/components/FeatureShowcase.jsx @@ -6,6 +6,7 @@ import { Crown, GitBranch, FileText, ArrowRight, Sparkles } from 'lucide-react' import PremiumFeaturePreview from './PremiumFeaturePreview' const FeatureShowcase = ({ onUpgrade }) => { + const ASSETS_VER = import.meta.env.VITE_ASSETS_VERSION || '1' const [previewModal, setPreviewModal] = useState({ isOpen: false, featureType: null }) const features = [ @@ -13,7 +14,7 @@ const FeatureShowcase = ({ onUpgrade }) => { id: 'pattern', title: 'GitHub Contribution Patterns', description: 'Create stunning visual patterns in your GitHub contribution graph that spell out words, create designs, and showcase your coding dedication.', - image: '/patternEX.png', + image: `/patternEX.png?v=${ASSETS_VER}`, imageAlt: 'GitHub contribution pattern example showing AUTOMAX text pattern', icon: GitBranch, isPremium: true, @@ -29,7 +30,7 @@ const FeatureShowcase = ({ onUpgrade }) => { id: 'readme', title: 'AI-Powered README Generation', description: 'Generate professional GitHub profile READMEs with AI assistance, featuring modern templates, statistics, and personalized content.', - image: '/ReadmiEX.png', + image: `/ReadmiEX.png?v=${ASSETS_VER}`, imageAlt: 'Professional GitHub README example with modern design and statistics', icon: FileText, isPremium: true, diff --git a/frontend/src/components/ImageTest.jsx b/frontend/src/components/ImageTest.jsx index 3394f8b..a13d6a3 100644 --- a/frontend/src/components/ImageTest.jsx +++ b/frontend/src/components/ImageTest.jsx @@ -1,6 +1,7 @@ import React from 'react' const ImageTest = () => { + const ASSETS_VER = import.meta.env.VITE_ASSETS_VERSION || '1' return (

Image Test Page

@@ -9,7 +10,7 @@ const ImageTest = () => {

Pattern Example Image

GitHub contribution pattern example { @@ -26,7 +27,7 @@ const ImageTest = () => {

README Example Image

GitHub README example { diff --git a/frontend/src/components/ui/ScrollytellingFeatureShowcase.jsx b/frontend/src/components/ui/ScrollytellingFeatureShowcase.jsx index 2a1913c..247909b 100644 --- a/frontend/src/components/ui/ScrollytellingFeatureShowcase.jsx +++ b/frontend/src/components/ui/ScrollytellingFeatureShowcase.jsx @@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef, forwardRef } from 'react' import { motion, AnimatePresence } from 'framer-motion' const ScrollytellingFeatureShowcase = forwardRef(({ features, title, subtitle }, ref) => { + const ASSETS_VER = import.meta.env.VITE_ASSETS_VERSION || '1' const [activeFeature, setActiveFeature] = useState(0) const [imageLoaded, setImageLoaded] = useState({}) const containerRef = useRef(null) @@ -13,16 +14,17 @@ const ScrollytellingFeatureShowcase = forwardRef(({ features, title, subtitle }, // Map features to their corresponding images (exact order from LandingPage.jsx) const getFeatureImage = (index) => { + const q = `?v=${ASSETS_VER}` const imageMap = { - 0: '/Feature_images/Auto_Commit_AI_Scheduling.png', // Auto Commit AI Scheduling ✅ - 1: '/Feature_images/Multi-Repository_Github_Tools.png', // Multi-Repository GitHub Tools ✅ - 2: '/Feature_images/Github_README_AI.png', // GitHub README AI ✅ - 3: '/Feature_images/Contribution_Analytics.png', // Contribution Analytics ✅ - 4: '/Feature_images/Enterprise_Security.png', // Enterprise Security ✅ - 5: '/Feature_images/Goal_Tracking.png', // Goal Tracking ✅ - 6: '/Feature_images/README_Generation.png', // README Generation ✅ + 0: `/Feature_images/Auto_Commit_AI_Scheduling.png${q}`, + 1: `/Feature_images/Multi-Repository_Github_Tools.png${q}`, + 2: `/Feature_images/Github_README_AI.png${q}`, + 3: `/Feature_images/Contribution_Analytics.png${q}`, + 4: `/Feature_images/Enterprise_Security.png${q}`, + 5: `/Feature_images/Goal_Tracking.png${q}`, + 6: `/Feature_images/README_Generation.png${q}`, } - return imageMap[index] || '/Feature_images/Auto_Commit_AI_Scheduling.png' // Default fallback + return imageMap[index] || `/Feature_images/Auto_Commit_AI_Scheduling.png${q}` } // Preload images diff --git a/frontend/src/pages/ForgotPassword.jsx b/frontend/src/pages/ForgotPassword.jsx new file mode 100644 index 0000000..a3169bb --- /dev/null +++ b/frontend/src/pages/ForgotPassword.jsx @@ -0,0 +1,247 @@ +import React, { useEffect, useRef, useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { useSignIn } from '@clerk/clerk-react' +import Logo from '../components/ui/Logo' +import { useTheme } from '../contexts/ThemeContext' + +// A lightweight, Clerk-native reset password flow with 3 simple steps +// 1) Request email code 2) Verify code 3) Set new password +export default function ForgotPassword() { + const navigate = useNavigate() + const { isDark } = useTheme() + const { signIn, isLoaded } = useSignIn() + + const [step, setStep] = useState('request') // request | verify | reset + const [email, setEmail] = useState('') + const [code, setCode] = useState('') + const [password, setPassword] = useState('') + const [confirm, setConfirm] = useState('') + const [loading, setLoading] = useState(false) + const [error, setError] = useState('') + const firstFieldRef = useRef(null) + + useEffect(() => { + firstFieldRef.current?.focus({ preventScroll: true }) + }, [step]) + + const requestCode = async (e) => { + e.preventDefault() + if (!isLoaded || loading) return + setLoading(true) + setError('') + try { + await signIn.create({ strategy: 'reset_password_email_code', identifier: email }) + setStep('verify') + } catch (err) { + console.error('Request reset error:', err) + setError(err?.errors?.[0]?.message || err?.message || 'Failed to send reset code') + } finally { + setLoading(false) + } + } + + const verifyCode = async (e) => { + e.preventDefault() + if (!isLoaded || loading) return + setLoading(true) + setError('') + try { + const res = await signIn.attemptFirstFactor({ strategy: 'reset_password_email_code', code }) + if (res?.status === 'needs_new_password') { + setStep('reset') + } else { + setError('Invalid or expired code. Please try again.') + } + } catch (err) { + console.error('Verify code error:', err) + setError(err?.errors?.[0]?.message || err?.message || 'Failed to verify code') + } finally { + setLoading(false) + } + } + + const setNewPassword = async (e) => { + e.preventDefault() + if (!isLoaded || loading) return + if (!password || password !== confirm) { + setError('Passwords do not match') + return + } + setLoading(true) + setError('') + try { + const res = await signIn.resetPassword({ password, signOutOfOtherSessions: false }) + if (res?.status === 'complete') { + navigate('/sign-in', { replace: true }) + } else { + setError('Could not set new password. Please try again.') + } + } catch (err) { + console.error('Set new password error:', err) + setError(err?.errors?.[0]?.message || err?.message || 'Failed to set new password') + } finally { + setLoading(false) + } + } + + return ( +
+ {/* Background decoration */} +
+
+
+
+ +
+
+
+
+ +
+

+ {step === 'request' && 'Forgot your password?'} + {step === 'verify' && 'Verify code'} + {step === 'reset' && 'Set a new password'} +

+

+ {step === 'request' && 'Enter your email and we will send you a verification code.'} + {step === 'verify' && 'Enter the 6-digit code sent to your email.'} + {step === 'reset' && 'Create a strong password for your account.'} +

+
+ +
+ {error && ( +
+

{error}

+
+ )} + + {step === 'request' && ( +
+ + +
+ )} + + {step === 'verify' && ( +
+ +
+ + +
+
+ )} + + {step === 'reset' && ( +
+ + + +
+ )} +
+
+
+
+ ) +} +