Skip to content

Commit bde6c16

Browse files
committed
Improved landing page #235
1 parent 8af9dd6 commit bde6c16

9 files changed

Lines changed: 98 additions & 78 deletions

File tree

frontend/public/infographic.png

-1.49 MB
Loading

frontend/src/components/Footer.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,29 +27,28 @@ const Footer = () => {
2727
<div>
2828
<h3 className="text-lg font-semibold mb-4">Product</h3>
2929
<ul className="space-y-2">
30-
<li><a href="#" className="text-gray-400 hover:text-white">Features</a></li>
31-
<li><a href="#" className="text-gray-400 hover:text-white">Pricing</a></li>
32-
<li><a href="#" className="text-gray-400 hover:text-white">Languages</a></li>
30+
<li><a href="https://demo.mnemorai.com/card-gen" className="text-gray-400 hover:text-white">Card Generation</a></li>
31+
<li><a href="https://demo.mnemorai.com/library" className="text-gray-400 hover:text-white">Library</a></li>
3332
</ul>
3433
</div>
3534

3635
<div>
37-
<h3 className="text-lg font-semibold mb-4">Company</h3>
36+
<h3 className="text-lg font-semibold mb-4">Contact</h3>
3837
<ul className="space-y-2">
39-
<li><a href="#" className="text-gray-400 hover:text-white">About</a></li>
40-
<li><a href="#" className="text-gray-400 hover:text-white">Blog</a></li>
41-
<li><a href="#" className="text-gray-400 hover:text-white">Contact</a></li>
38+
<li><a href="https://github.com/StephanAkkerman/mnemorai" className="text-gray-400 hover:text-white">Github</a></li>
39+
<li><a href="https://discord.gg/VZMxzdnv" className="text-gray-400 hover:text-white">Discord</a></li>
40+
<li><a href="mailto:mnemorai@gmail.com" className="text-gray-400 hover:text-white">Mail</a></li>
4241
</ul>
4342
</div>
4443

45-
<div>
44+
{/* <div>
4645
<h3 className="text-lg font-semibold mb-4">Legal</h3>
4746
<ul className="space-y-2">
4847
<li><a href="#" className="text-gray-400 hover:text-white">Terms</a></li>
4948
<li><a href="#" className="text-gray-400 hover:text-white">Privacy</a></li>
5049
<li><a href="#" className="text-gray-400 hover:text-white">Cookies</a></li>
5150
</ul>
52-
</div>
51+
</div> */}
5352
</div>
5453
</div>
5554

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

33
import { useEffect, useRef } from "react";
4+
import { usePathname } from "next/navigation";
45
import { ANKI_CONFIG } from "@/config/constants";
56
import { useToast } from "@/contexts/ToastContext";
67

@@ -11,16 +12,19 @@ interface APIStatus {
1112
}
1213

1314
export default function StatusChecker() {
15+
const pathname = usePathname();
16+
// Moved toast context hook up as it's needed even when on homepage to hide toasts
1417
const { showToast, hideAllToasts } = useToast();
18+
1519
const statusesRef = useRef<APIStatus[]>([
1620
{ name: "AnkiConnect", description: "Anki synchronization", available: null },
1721
{ name: "Card Generator", description: "Card creation API", available: null },
1822
]);
1923

2024
const pollingIntervalRef = useRef<NodeJS.Timeout | null>(null);
21-
const isMounted = useRef(true);
25+
const isMounted = useRef(true); // To track if component is mounted for async operations
2226

23-
// Define the API status check function
27+
// Define the API status check function (no changes here)
2428
const checkAPIStatus = async (name: string): Promise<boolean> => {
2529
try {
2630
if (name === "AnkiConnect") {
@@ -47,50 +51,57 @@ export default function StatusChecker() {
4751
}
4852
};
4953

50-
// Cleanup when component unmounts
54+
// Effect for mount/unmount status
5155
useEffect(() => {
56+
isMounted.current = true;
5257
return () => {
5358
isMounted.current = false;
59+
// General cleanup for interval if component fully unmounts
5460
if (pollingIntervalRef.current) {
5561
clearInterval(pollingIntervalRef.current);
5662
pollingIntervalRef.current = null;
5763
}
5864
};
5965
}, []);
6066

61-
// Set up the polling mechanism
62-
const POLLING_INTERVAL = 5000; // 5 seconds between checks
63-
const SUCCESS_DISPLAY_TIME = 3000; // 3 seconds to display success
6467

68+
const POLLING_INTERVAL = 5000;
69+
const SUCCESS_DISPLAY_TIME = 3000;
70+
71+
// Main effect for polling and toast logic, now dependent on pathname
6572
useEffect(() => {
66-
// Function to check all statuses at once
73+
74+
if (pathname === "/") {
75+
if (pollingIntervalRef.current) {
76+
clearInterval(pollingIntervalRef.current);
77+
pollingIntervalRef.current = null;
78+
}
79+
hideAllToasts(); // Crucial: hide toasts when navigating to or on the homepage
80+
return;
81+
}
82+
83+
// --- Logic for non-homepage paths ---
84+
6785
const checkAllStatuses = async () => {
6886
if (!isMounted.current) return;
6987

70-
// Create a copy to work with
7188
const currentStatuses = [...statusesRef.current];
7289
const updatedStatuses = [...currentStatuses];
7390
const recoveredServices: string[] = [];
7491
let hasServiceDown = false;
7592
let statusChanged = false;
7693

77-
// Check each service
7894
for (let i = 0; i < updatedStatuses.length; i++) {
7995
const status = updatedStatuses[i];
8096
const isAvailable = await checkAPIStatus(status.name);
8197

82-
// If status changed from not available to available (true transition)
8398
if (status.available === false && isAvailable) {
8499
updatedStatuses[i] = { ...status, available: true };
85100
recoveredServices.push(status.name);
86101
statusChanged = true;
87-
}
88-
// If checking for the first time and it's available
89-
else if (status.available === null && isAvailable) {
102+
} else if (status.available === null && isAvailable) {
90103
updatedStatuses[i] = { ...status, available: true };
91-
}
92-
// If service is unavailable
93-
else if (!isAvailable) {
104+
} else if (!isAvailable) {
94105
if (status.available !== false) {
95106
statusChanged = true;
96107
}
@@ -99,76 +110,72 @@ export default function StatusChecker() {
99110
}
100111
}
101112

102-
// Update statuses ref
103113
statusesRef.current = updatedStatuses;
104114

105-
// Show appropriate toast notifications
106115
if (statusChanged) {
107-
// Clear existing toasts when status changes
108116
hideAllToasts();
109117

110-
// If any service was recovered, show a success toast for it
111118
if (recoveredServices.length > 0) {
112-
// Show success toast for specifically recovered services
113119
showToast({
114120
type: 'success',
115121
title: `Service${recoveredServices.length > 1 ? 's' : ''} Recovered`,
116122
message: `${recoveredServices.join(", ")} ${recoveredServices.length > 1 ? 'are' : 'is'} now available.`,
117-
duration: SUCCESS_DISPLAY_TIME // Show longer so users notice it
123+
duration: SUCCESS_DISPLAY_TIME
118124
});
119125
}
120126

121-
// If we still have services down, show an error toast after a brief delay
122-
// This prevents the toasts from appearing simultaneously
123127
if (hasServiceDown) {
124128
setTimeout(() => {
125129
if (!isMounted.current) return;
126-
127130
const unavailableServices = statusesRef.current
128131
.filter(s => s.available === false)
129132
.map(s => s.name)
130133
.join(", ");
131-
132-
showToast({
133-
type: 'error',
134-
title: 'Service Interruption',
135-
message: `${unavailableServices} ${statusesRef.current.filter(s => s.available === false).length > 1 ? 'are' : 'is'} unavailable. Please check your connections.`,
136-
duration: 0 // Keep until resolved
137-
});
138-
}, recoveredServices.length > 0 ? 300 : 0); // Small delay if we just showed a recovery toast
134+
if (unavailableServices) { // Only show if there are actually unavailable services
135+
showToast({
136+
type: 'error',
137+
title: 'Service Interruption',
138+
message: `${unavailableServices} ${statusesRef.current.filter(s => s.available === false).length > 1 ? 'are' : 'is'} unavailable. Please check your connections.`,
139+
duration: 0
140+
});
141+
}
142+
}, recoveredServices.length > 0 ? 300 : 0);
139143
} else if (recoveredServices.length > 0) {
140-
// If all services are up after some were down, show an additional "all clear" notification
141144
setTimeout(() => {
142145
if (!isMounted.current) return;
143-
144146
showToast({
145147
type: 'info',
146148
title: 'All Systems Operational',
147149
message: 'All services are now running properly.',
148150
duration: SUCCESS_DISPLAY_TIME
149151
});
150-
}, 300); // Small delay after the recovery toast
152+
}, 300);
151153
}
152154
}
153155
};
154156

155-
// Run initial check
157+
// Run initial check only if not on homepage (already handled by the if (pathname === "/") check)
156158
checkAllStatuses();
157159

158-
// Set up single polling interval (only if not already set)
160+
// Set up polling interval only if not already set and not on homepage
159161
if (!pollingIntervalRef.current) {
160162
pollingIntervalRef.current = setInterval(checkAllStatuses, POLLING_INTERVAL);
161163
}
162164

163-
// Cleanup on effect change
165+
// Cleanup for this effect:
166+
164167
return () => {
165168
if (pollingIntervalRef.current) {
166169
clearInterval(pollingIntervalRef.current);
167170
pollingIntervalRef.current = null;
168171
}
172+
169173
};
170-
}, [showToast, hideAllToasts]); // Include toast functions as dependencies
174+
}, [pathname, showToast, hideAllToasts]);
175+
176+
if (pathname === "/") {
177+
return null;
178+
}
171179

172-
// This component doesn't render anything visible on its own now
173180
return null;
174-
}
181+
}

frontend/src/components/landing-page/1-HeroSection.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ const HeroSection = () => {
124124
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: -15 }}
125125
>
126126
{/* This is the container that stays fixed in the viewport */}
127-
<div className="sticky top-[15%] h-screen w-full overflow-hidden flex justify-center">
127+
<div className="sticky top-[15%] h-screen w-full overflow-hidden flex justify-center min-h-[750px] sm:min-h-[900px]">
128128
{/* Content Container */}
129129
<div className="absolute inset-0 w-full h-full max-h-[400px] flex items-center justify-center z-40">
130130
{/* Text Content - positioned absolute */}
@@ -186,8 +186,8 @@ const HeroSection = () => {
186186
learning engaging and effective.
187187
</p>
188188
<div className="flex flex-wrap gap-4">
189-
<Button text="Get Started" onClick={() => { }} />
190-
<Button text="See Demo" onClick={() => { }} />
189+
<Button text="Get Started" link="https://demo.mnemorai.com/card-gen" />
190+
<Button text="View on Github" link="https://github.com/StephanAkkerman/mnemorai" />
191191
</div>
192192
</motion.div>
193193
</motion.div>

frontend/src/components/landing-page/3-WhySection.tsx

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
"use client"
22
import React, { useRef, useEffect, useState } from "react";
3-
import { motion, useScroll, useTransform } from "framer-motion";
3+
import {
4+
motion,
5+
useScroll,
6+
useTransform
7+
} from "framer-motion";
48
import { ThreeDMarquee } from "../ui/3d-marquee";
59

610
const WhySection = () => {
711
const sectionRef = useRef(null);
812
const titleRef = useRef(null);
9-
const [shouldShowContent, setShouldShowContent] = useState(false);
13+
1014
const [windowSize, setWindowSize] = useState({ width: 0, height: 0 });
1115

1216
useEffect(() => {
@@ -15,6 +19,7 @@ const WhySection = () => {
1519
setWindowSize({ width: window.innerWidth, height: window.innerHeight });
1620
}
1721
}
22+
1823
update();
1924
window.addEventListener("resize", update);
2025
return () => window.removeEventListener("resize", update);
@@ -44,25 +49,14 @@ const WhySection = () => {
4449
[0.8, 1.1]
4550
);
4651

47-
useEffect(() => {
48-
const unsubscribe = scrollYProgress.onChange(value => {
49-
setShouldShowContent(value >= 0.3);
50-
});
51-
return () => unsubscribe();
52-
}, [scrollYProgress, isMobileDevice]);
53-
5452
// Sample images array
55-
const images = Array(16).fill("/logo.png");
56-
57-
53+
const images = isMobileDevice ? Array(3).fill("/logo.png") : Array(16).fill("/logo.png")
5854

5955
const fadeInUp = {
6056
hidden: { opacity: 0, y: 30 },
6157
visible: { opacity: 1, y: 0 }
6258
};
6359

64-
65-
6660
return (
6761
<section
6862
ref={sectionRef}
@@ -226,7 +220,10 @@ const WhySection = () => {
226220

227221
>
228222
<div className="mt-20 sm:mt-10">
229-
<button className="px-6 py-2.5 sm:px-8 sm:py-3 md:px-10 md:py-4 bg-gradient-to-r from-blue-600 to-teal-500 text-white font-semibold text-base sm:text-lg rounded-xl hover:shadow-lg hover:shadow-blue-500/30 hover:scale-105 transition-all duration-300 relative group overflow-hidden">
223+
<button className="px-6 py-2.5 sm:px-8 sm:py-3 md:px-10 md:py-4 bg-gradient-to-r from-blue-600 to-teal-500 text-white font-semibold text-base sm:text-lg rounded-xl hover:shadow-lg hover:shadow-blue-500/30 hover:scale-105 transition-all duration-300 relative group overflow-hidden"
224+
onClick={() => {
225+
window.location.assign("https://demo.mnemorai.com/card-gen")
226+
}}>
230227
<span className="relative z-10">Start Learning Smarter</span>
231228
<span className="absolute inset-0 bg-gradient-to-r from-blue-500 to-teal-400 opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none"></span>
232229
</button>

frontend/src/components/landing-page/4-HowSection.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ const HowSection: React.FC = () => {
230230
<div className="sticky top-0 h-screen max-h-[1000px] w-full overflow-hidden flex flex-col items-center justify-center">
231231

232232
{/* Max width container for content, centered */}
233-
<div className="relative w-full max-w-6xl mx-auto px-4 sm:px-6 h-full flex flex-col items-center justify-center pt-16 sm:pt-20">
233+
<div className="relative w-full max-w-6xl mx-auto px-4 sm:px-6 h-full flex flex-col items-center justify-center pt-16 sm:pt-20 ">
234234

235235
{/* --- Title Area --- */}
236236
<motion.div
@@ -245,7 +245,7 @@ const HowSection: React.FC = () => {
245245
</motion.div>
246246

247247
{/* --- Main Content Area (Switches between steps and final card) --- */}
248-
<div className="relative w-full flex-grow flex flex-col items-center justify-center mt-16 md:mt-20">
248+
<div className="relative w-full flex-grow flex flex-col items-center justify-center mt-16 md:mt-20 min-h-[500px]">
249249
<AnimatePresence mode="wait">
250250
{!showCard ? (
251251
/* --- Scrolling Steps View --- */
@@ -329,7 +329,7 @@ const HowSection: React.FC = () => {
329329
initial={{ opacity: 0, scale: 0.95 }}
330330
animate={{ opacity: 1, scale: 1 }}
331331
transition={{ duration: 0.3, delay: 0.01 }} // Slight delay after steps fade
332-
className="w-full flex flex-col items-center justify-center text-center"
332+
className="w-full flex flex-col items-center justify-center text-center sm:mt-0 -mt-28"
333333
>
334334
{/* Et Voilà Title */}
335335
<motion.div
@@ -366,7 +366,11 @@ const HowSection: React.FC = () => {
366366
animate={{ opacity: 1 }}
367367
transition={{ duration: 0.5, delay: 0.6 }}
368368
>
369-
<button className="px-6 md:px-8 py-2.5 md:py-3 bg-gradient-to-r from-blue-600 to-teal-500 text-white font-medium rounded-full shadow-md hover:shadow-lg hover:shadow-blue-500/30 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 focus:ring-offset-gray-900 transform hover:scale-105 transition-all duration-300 text-sm md:text-base">
369+
<button className="px-6 md:px-8 py-2.5 md:py-3 bg-gradient-to-r from-blue-600 to-teal-500 text-white font-medium rounded-full shadow-md hover:shadow-lg hover:shadow-blue-500/30 focus:outline-none focus:ring-2 focus:ring-offset-2
370+
focus:ring-blue-500 focus:ring-offset-gray-900 transform hover:scale-105 transition-all duration-300 text-sm md:text-base"
371+
onClick={() => {
372+
window.location.assign("https://demo.mnemorai.com/card-gen")
373+
}}>
370374
Try It Now
371375
</button>
372376
</motion.div>

frontend/src/components/landing-page/5-AISection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ const AIPage: React.FC = () => {
177177
<Button
178178
text="Try Mnemorai Now"
179179
variant="primary"
180-
onClick={() => console.log("CTA Clicked!")}
180+
link="https://demo.mnemorai.com/card-gen"
181181
className="px-6 py-2.5 sm:px-8 sm:py-3 text-base sm:text-lg"
182182
/>
183183
</motion.div>

frontend/src/components/landing-page/7-CTASection.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ const CTASection = () => {
2929
initial={{ y: "50%", opacity: "0%" }}
3030
animate={isInView ? { y: "0%", opacity: "100%" } : { y: "50%", opacity: "0%" }}
3131
transition={{ duration: 1, ease: "easeOut" }}
32-
32+
viewport={{ once: true }}
3333
>
34-
<Button text="Sign Up Free" />
34+
<Button text="Create your card now" link="https://demo.mnemorai.com/card-gen" />
3535
</motion.div>
3636

3737
</div>

0 commit comments

Comments
 (0)