{
@@ -26,7 +27,7 @@ const ImageTest = () => {
{
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 (
+ + {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}
+