Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions frontend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -48,6 +49,7 @@ function App() {
<Route path="/" element={<LandingPage />} />
<Route path="/sign-in/*" element={<SignInPage />} />
<Route path="/sign-up/*" element={<SignUpPage />} />
<Route path="/forgot-password" element={<ForgotPassword />} />
<Route path="/badge/:userId" element={<PublicBadge />} />
<Route path="/image-test" element={<ImageTest />} />
<Route path="/premium-test" element={<PremiumFeatureTest />} />
Expand All @@ -58,7 +60,7 @@ function App() {
<Route path="/refund-policy" element={<RefundPolicy />} />
<Route path="/contact" element={<ContactUs />} />
<Route path="/about" element={<AboutUs />} />

{/* Protected routes */}
<Route path="/dashboard" element={
<ProtectedRoute>
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/FeatureShowcase.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ 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 = [
{
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,
Expand All @@ -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,
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/ImageTest.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react'

const ImageTest = () => {
const ASSETS_VER = import.meta.env.VITE_ASSETS_VERSION || '1'
return (
<div className="p-8 space-y-8">
<h1 className="text-3xl font-bold">Image Test Page</h1>
Expand All @@ -9,7 +10,7 @@ const ImageTest = () => {
<h2 className="text-2xl font-semibold">Pattern Example Image</h2>
<div className="border rounded-lg overflow-hidden max-w-2xl">
<img
src="/patternEX.png"
src={`/patternEX.png?v=${ASSETS_VER}` }
alt="GitHub contribution pattern example"
className="w-full h-auto"
onError={(e) => {
Expand All @@ -26,7 +27,7 @@ const ImageTest = () => {
<h2 className="text-2xl font-semibold">README Example Image</h2>
<div className="border rounded-lg overflow-hidden max-w-2xl">
<img
src="/ReadmiEX.png"
src={`/ReadmiEX.png?v=${ASSETS_VER}` }
alt="GitHub README example"
className="w-full h-auto"
onError={(e) => {
Expand Down
18 changes: 10 additions & 8 deletions frontend/src/components/ui/ScrollytellingFeatureShowcase.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
247 changes: 247 additions & 0 deletions frontend/src/pages/ForgotPassword.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 flex flex-col justify-center py-12 sm:px-6 lg:px-8 relative overflow-hidden">
{/* Background decoration */}
<div className="absolute inset-0 overflow-hidden pointer-events-none">
<div className="absolute -top-40 -right-40 w-80 h-80 bg-indigo-400/10 dark:bg-indigo-400/5 rounded-full blur-3xl" />
<div className="absolute -bottom-40 -left-40 w-80 h-80 bg-purple-400/10 dark:bg-purple-400/5 rounded-full blur-3xl" />
</div>

<div className="relative z-10 w-full max-w-md mx-auto">
<div className={`overflow-hidden rounded-2xl border shadow-2xl backdrop-blur-xl ${
isDark ? 'border-white/20 bg-slate-900/40' : 'border-slate-200/50 bg-white/80 shadow-lg'
}`}>
<div className="px-6 pt-6">
<div className="flex justify-center mb-4">
<Logo variant="auth" size="lg" />
</div>
<h1 className={`text-center text-2xl font-bold mb-2 ${isDark ? 'text-white' : 'text-slate-900'}`}>
{step === 'request' && 'Forgot your password?'}
{step === 'verify' && 'Verify code'}
{step === 'reset' && 'Set a new password'}
</h1>
<p className={`text-center text-sm mb-6 ${isDark ? 'text-white/70' : 'text-slate-600'}`}>
{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.'}
</p>
</div>

<div className="px-6 pb-6">
{error && (
<div className="mb-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-3">
<p className="text-red-600 dark:text-red-400 text-sm">{error}</p>
</div>
)}

{step === 'request' && (
<form onSubmit={requestCode} className="space-y-3">
<label className="block">
<span className={`mb-1 block text-sm font-medium ${isDark ? 'text-white/90' : 'text-slate-900'}`}>Email</span>
<input
ref={firstFieldRef}
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="[email protected]"
autoComplete="email"
className={`w-full rounded-xl border px-3 py-2.5 outline-none bg-transparent ${
isDark
? 'text-white placeholder-white/50 border-white/20 focus:border-white/40'
: 'text-slate-900 placeholder-slate-400 border-slate-200/60 focus:border-slate-400/70'
}`}
required
/>
</label>
<button
type="submit"
disabled={loading || !email}
className={`mt-1 inline-flex w-full items-center justify-center gap-2 rounded-xl border px-4 py-2.5 font-semibold shadow-sm transition disabled:cursor-not-allowed disabled:opacity-60 ${
isDark ? 'border-white/20 bg-white text-black hover:bg-white/90' : 'border-slate-400/50 bg-slate-800 text-white hover:bg-slate-900'
}`}
>
{loading ? 'Sending…' : 'Send code'}
</button>
</form>
)}

{step === 'verify' && (
<form onSubmit={verifyCode} className="space-y-3">
<label className="block">
<span className={`mb-1 block text-sm font-medium ${isDark ? 'text-white/90' : 'text-slate-900'}`}>Verification code</span>
<input
ref={firstFieldRef}
type="text"
value={code}
onChange={(e) => setCode(e.target.value)}
placeholder="123456"
inputMode="numeric"
className={`w-full rounded-xl border px-3 py-2.5 outline-none bg-transparent tracking-widest text-center ${
isDark
? 'text-white placeholder-white/50 border-white/20 focus:border-white/40'
: 'text-slate-900 placeholder-slate-400 border-slate-200/60 focus:border-slate-400/70'
}`}
required
/>
</label>
<div className="flex gap-2">
<button
type="button"
onClick={() => setStep('request')}
className={`flex-1 rounded-xl border px-4 py-2.5 text-sm font-medium ${
isDark ? 'border-white/15 text-white/80 hover:bg-white/5' : 'border-slate-300 text-slate-700 hover:bg-slate-100'
}`}
>
Back
</button>
<button
type="submit"
disabled={loading || !code}
className={`flex-1 rounded-xl border px-4 py-2.5 font-semibold shadow-sm transition disabled:cursor-not-allowed disabled:opacity-60 ${
isDark ? 'border-white/20 bg-white text-black hover:bg-white/90' : 'border-slate-400/50 bg-slate-800 text-white hover:bg-slate-900'
}`}
>
{loading ? 'Verifying…' : 'Verify code'}
</button>
</div>
</form>
)}

{step === 'reset' && (
<form onSubmit={setNewPassword} className="space-y-3">
<label className="block">
<span className={`mb-1 block text-sm font-medium ${isDark ? 'text-white/90' : 'text-slate-900'}`}>New password</span>
<input
ref={firstFieldRef}
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
autoComplete="new-password"
className={`w-full rounded-xl border px-3 py-2.5 outline-none bg-transparent ${
isDark
? 'text-white placeholder-white/50 border-white/20 focus:border-white/40'
: 'text-slate-900 placeholder-slate-400 border-slate-200/60 focus:border-slate-400/70'
}`}
required
/>
</label>
<label className="block">
<span className={`mb-1 block text-sm font-medium ${isDark ? 'text-white/90' : 'text-slate-900'}`}>Confirm password</span>
<input
type="password"
value={confirm}
onChange={(e) => setConfirm(e.target.value)}
placeholder="••••••••"
autoComplete="new-password"
className={`w-full rounded-xl border px-3 py-2.5 outline-none bg-transparent ${
isDark
? 'text-white placeholder-white/50 border-white/20 focus:border-white/40'
: 'text-slate-900 placeholder-slate-400 border-slate-200/60 focus:border-slate-400/70'
}`}
required
/>
</label>
<button
type="submit"
disabled={loading || !password || !confirm}
className={`mt-1 inline-flex w-full items-center justify-center gap-2 rounded-xl border px-4 py-2.5 font-semibold shadow-sm transition disabled:cursor-not-allowed disabled:opacity-60 ${
isDark ? 'border-white/20 bg-white text-black hover:bg-white/90' : 'border-slate-400/50 bg-slate-800 text-white hover:bg-slate-900'
}`}
>
{loading ? 'Saving…' : 'Set new password'}
</button>
</form>
)}
</div>
</div>
</div>
</div>
)
}

Loading