Skip to content

Commit e89c87a

Browse files
committed
Refactor: Replaced the header component with a new shared implementation, updated TTS service cancellation logic and language configuration, and added a linting fix plan.
1 parent d56199c commit e89c87a

File tree

18 files changed

+251
-1066
lines changed

18 files changed

+251
-1066
lines changed

netlify/functions/elevenlabs-tts-stream.mts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ export default async (req: Request, _context: Context) => {
5353
}
5454

5555
// Call ElevenLabs streaming endpoint
56-
const url = `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}/stream?optimize_streaming_latency=3&output_format=${outputFormat}`;
56+
const elUrl = `https://api.elevenlabs.io/v1/text-to-speech/${voiceId}/stream?optimize_streaming_latency=3&output_format=${outputFormat}`;
5757

58-
const elResponse = await fetch(url, {
58+
const elResponse = await fetch(elUrl, {
5959
method: "POST",
6060
headers: {
6161
Accept: "audio/mpeg",

src/components/DemoCall/UserPhoneInterface.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect, useRef, useCallback } from 'react';
1+
import { useState, useEffect, useRef, useCallback } from 'react';
22
import { motion, AnimatePresence } from 'framer-motion';
33
import { Phone, PhoneOff, Volume2, VolumeX, Minimize2, Maximize2, Globe, ArrowLeft } from 'lucide-react';
44
import { cn } from '../../utils/cn';
@@ -34,7 +34,7 @@ export default function UserPhoneInterface({
3434
isDark = true,
3535
onTranscript,
3636
// eslint-disable-next-line @typescript-eslint/no-unused-vars
37-
compact = false,
37+
3838
mode = 'standalone',
3939
autoStart = false,
4040
onBack
@@ -58,7 +58,7 @@ export default function UserPhoneInterface({
5858
const [language, setLanguage] = useState<TTSLanguage>('en');
5959
const [isMinimized, setIsMinimized] = useState(false);
6060
const [isEnding, setIsEnding] = useState(false);
61-
const [voiceProvider, setVoiceProvider] = useState<VoiceProviderType>(() => getGroqSettings().voiceProvider);
61+
const [, setVoiceProvider] = useState<VoiceProviderType>(() => getGroqSettings().voiceProvider);
6262

6363
const recognitionRef = useRef<SpeechRecognitionInstance | null>(null);
6464
const timerRef = useRef<NodeJS.Timeout | null>(null);

src/components/Landing/index.tsx

Lines changed: 4 additions & 241 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,10 @@ import SEO from '../SEO';
33
import { lazy, Suspense } from 'react';
44
import Footer from './Footer';
55
import AnnouncementBanner from './AnnouncementBanner';
6-
import { useNavigate } from 'react-router-dom';
7-
import { useState, useEffect, useCallback } from 'react';
8-
import { Menu, X } from 'lucide-react';
9-
import { motion, AnimatePresence } from 'framer-motion';
10-
import LanguageSwitcher from '../Layout/LanguageSwitcher';
116

12-
import { useLanguage } from '../../contexts/LanguageContext';
7+
import { useState, useCallback } from 'react';
8+
import SharedHeader from '../Layout/SharedHeader';
139

14-
import { useDeviceDetection } from '../../hooks/useDeviceDetection';
1510

1611
// Lazy-load below-the-fold sections to reduce initial JS bundle
1712
const AboutSection = lazy(() => import('./AboutSection'));
@@ -21,40 +16,13 @@ const Conversation = lazy(() => import('./Conversation'));
2116
const FAQ = lazy(() => import('./FAQ'));
2217

2318
export default function Landing() {
24-
const navigate = useNavigate();
25-
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
26-
const [isScrolled, setIsScrolled] = useState(false);
27-
const { t } = useLanguage();
28-
const { isMobile } = useDeviceDetection();
19+
2920
const [bannerVisible, setBannerVisible] = useState(true);
3021

3122
const handleBannerVisibility = useCallback((visible: boolean) => {
3223
setBannerVisible(visible);
3324
}, []);
3425

35-
// Track scroll position to change header background
36-
useEffect(() => {
37-
const handleScroll = () => {
38-
setIsScrolled(window.scrollY > 20);
39-
};
40-
window.addEventListener('scroll', handleScroll);
41-
handleScroll(); // Check initial state
42-
return () => window.removeEventListener('scroll', handleScroll);
43-
}, []);
44-
45-
const handleAboutClick = (e: React.MouseEvent) => {
46-
e.preventDefault();
47-
e.stopPropagation();
48-
setIsMobileMenuOpen(false);
49-
navigate('/about');
50-
};
51-
52-
53-
54-
const handleSolutionsClick = () => {
55-
setIsMobileMenuOpen(false);
56-
navigate('/solutions');
57-
};
5826

5927
return (
6028
<div className="min-h-screen overflow-hidden relative bg-[rgb(10,10,10)]">
@@ -66,212 +34,7 @@ export default function Landing() {
6634
{/* Announcement Banner */}
6735
<AnnouncementBanner onVisibilityChange={handleBannerVisibility} />
6836

69-
{/* Header - Dark background fading to transparent on right for corner glow, solid when scrolled */}
70-
{/* Floating Pill Header */}
71-
<header
72-
className={`fixed z-50 left-1/2 w-[95%] max-w-[1400px] transition-all duration-300 transform -translate-x-1/2 rounded-[50px] md:rounded-[70px] px-5 md:px-10 py-3 md:py-5 shadow-2xl backdrop-blur-xl bg-[#1a1a1a]/50 hover:bg-[#1a1a1a]/70 border border-white/10 ${isScrolled ? 'top-4 md:top-6' : 'top-4 md:top-6'}`}
73-
style={{ top: bannerVisible ? 'calc(44px + 1rem)' : '1rem' }}
74-
>
75-
<div className="w-full flex items-center justify-between relative">
76-
77-
{/* Left: Logo */}
78-
<div className="flex items-center gap-2 shrink-0">
79-
<button
80-
type="button"
81-
onClick={() => navigate('/')}
82-
className="flex items-center gap-2 md:gap-3 group"
83-
aria-label="Go to home"
84-
>
85-
<svg viewBox="0 0 464 468" className="w-8 h-8 md:w-12 md:h-12">
86-
<path fill="white" d="M275.9 63.5c37.7 5.3 76.6 24.1 103.7 50.2 30 28.8 41.8 57.6 35.8 87.1-6.1 30.1-33.6 52.9-70.6 58.3-6 0.9-18.3 1-44.9 0.6l-36.6-0.7-0.5 17.8c-0.3 9.7-0.4 17.8-0.4 17.9 0.1 0.1 19.1 0.3 42.2 0.4 23.2 0 42.7 0.5 43.5 1 1.2 0.7 1.1 2.2-0.8 9.4-6 23-20.5 42.1-41.8 55-7.3 4.3-26.7 11.9-36 14.1-9 2-34 2-44.5 0-41.3-7.9-74.2-38-82.9-75.7-8.1-35.7 2.2-71.5 27.5-94.7 16.1-14.9 35.5-22.4 63.7-24.7l7.7-0.7v-34.1l-11.7 0.7c-22.2 1.3-37 5.3-56.4 15.2-28.7 14.6-49.7 39.3-59.9 70.2-9.6 29.3-9.3 62.6 0.8 91.4 3.3 9.2 12.2 25.6 18.3 33.8 11.3 14.9 30.6 30.8 48.7 39.9 19.9 10 49.2 15.9 73.2 14.7 26.5-1.3 52.5-9.6 74.2-23.9 26.9-17.6 47.2-47.9 53.3-79.7 1-5.2 2.3-10.1 2.8-10.8 0.8-0.9 6.9-1.2 27.1-1l26.1 0.3 0.3 3.8c1.2 14.6-10.9 52.1-23.9 74-17.8 30-43.2 54-75.9 71.5-20.9 11.2-38.3 16.5-67.2 20.7-27.6 3.9-47.9 3.1-75.8-3.1-36.9-8.3-67.8-25.6-97.1-54.6-23.6-23.2-44.8-61.9-51.7-93.8-5.1-23.7-5.5-28.1-4.9-48.8 1.7-63.2 23.4-111.8 67.7-152 28-25.4 60.4-41.3 99-48.8 18.5-3.6 46.1-4 67.9-0.9zm16.4 92.6c-6.3 2.4-12.8 8.5-15.4 14.5-2.6 6.1-2.6 18.3 0 23.9 5 11 20.2 17.7 32.3 14.1 11.9-3.4 19.8-14.3 19.8-27.1-0.1-19.9-18.2-32.5-36.7-25.4z" />
87-
</svg>
88-
<span className="text-lg md:text-[28px] font-semibold text-white/90 group-hover:text-white transition-colors leading-none tracking-tight">
89-
ClerkTree
90-
</span>
91-
</button>
92-
</div>
93-
94-
{/* Center: Navigation Links - Hidden on Mobile. Centered absolutely to ensure it's precisely in the middle */}
95-
<nav className="hidden md:flex items-center justify-center gap-12 absolute left-1/2 transform -translate-x-1/2">
96-
<button
97-
type="button"
98-
onClick={() => navigate('/blog')}
99-
className="font-medium bg-transparent border-none outline-none transition-colors tracking-wide"
100-
style={{
101-
fontSize: '18px',
102-
fontFamily: 'Urbanist, sans-serif',
103-
color: 'oklch(0.9 0 0 / 0.7)',
104-
}}
105-
onMouseEnter={(e) => e.currentTarget.style.color = '#ffffff'}
106-
onMouseLeave={(e) => e.currentTarget.style.color = 'oklch(0.9 0 0 / 0.7)'}
107-
>
108-
{t('nav.blog')}
109-
</button>
110-
<button
111-
type="button"
112-
onClick={handleAboutClick}
113-
className="font-medium bg-transparent border-none outline-none transition-colors tracking-wide"
114-
style={{
115-
fontSize: '18px',
116-
fontFamily: 'Urbanist, sans-serif',
117-
color: 'oklch(0.9 0 0 / 0.7)',
118-
}}
119-
onMouseEnter={(e) => e.currentTarget.style.color = '#ffffff'}
120-
onMouseLeave={(e) => e.currentTarget.style.color = 'oklch(0.9 0 0 / 0.7)'}
121-
>
122-
{t('nav.about')}
123-
</button>
124-
<button
125-
onClick={() => navigate('/docs')}
126-
className="font-medium bg-transparent border-none outline-none transition-colors tracking-wide"
127-
style={{
128-
fontSize: '18px',
129-
fontFamily: 'Urbanist, sans-serif',
130-
color: 'oklch(0.9 0 0 / 0.7)',
131-
}}
132-
onMouseEnter={(e) => e.currentTarget.style.color = '#ffffff'}
133-
onMouseLeave={(e) => e.currentTarget.style.color = 'oklch(0.9 0 0 / 0.7)'}
134-
>
135-
{t('nav.docs')}
136-
</button>
137-
</nav>
138-
139-
{/* Right: Actions */}
140-
<div className="flex items-center justify-end gap-3 md:gap-5 shrink-0">
141-
{/* Language Switcher */}
142-
<div className="hidden md:block w-[75px] scale-100 origin-right">
143-
<LanguageSwitcher isExpanded={true} forceDark={true} />
144-
</div>
145-
146-
{/* CTA Book/Login Button matching the pill style */}
147-
<button
148-
onClick={() => navigate('/login')}
149-
className="hidden md:flex items-center justify-center whitespace-nowrap text-[18px] font-medium transition-all h-[54px] rounded-[30px] px-10 gap-2 group"
150-
style={{
151-
fontFamily: 'Urbanist, sans-serif',
152-
background: 'rgba(255, 255, 255, 1)',
153-
color: 'rgb(10, 10, 10)',
154-
}}
155-
>
156-
{t('nav.login')}
157-
<svg width="14" height="14" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" className="transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5 transition-transform">
158-
<path d="M1 11L11 1M11 1H3.5M11 1V8.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
159-
</svg>
160-
</button>
161-
162-
{/* Mobile Hamburger Menu Button */}
163-
<button
164-
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
165-
className="md:hidden p-2 text-white/80 hover:text-white transition-colors ml-1"
166-
aria-label="Toggle menu"
167-
>
168-
{isMobileMenuOpen ? (
169-
<X className="w-6 h-6" />
170-
) : (
171-
<Menu className="w-6 h-6" />
172-
)}
173-
</button>
174-
</div>
175-
</div>
176-
</header>
177-
178-
{/* Mobile Menu Overlay - offset top when banner is visible */}
179-
<AnimatePresence>
180-
{isMobileMenuOpen && (
181-
<>
182-
{/* Backdrop */}
183-
<motion.div
184-
initial={{ opacity: 0 }}
185-
animate={{ opacity: 1 }}
186-
exit={{ opacity: 0 }}
187-
transition={{ duration: 0.2 }}
188-
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-40 md:hidden"
189-
onClick={() => setIsMobileMenuOpen(false)}
190-
/>
191-
192-
{/* Glassy Popup Menu */}
193-
<motion.div
194-
initial={{ opacity: 0, y: -20, scale: 0.95 }}
195-
animate={{ opacity: 1, y: 0, scale: 1 }}
196-
exit={{ opacity: 0, y: -20, scale: 0.95 }}
197-
transition={{ duration: 0.2, ease: "easeOut" }}
198-
className="fixed left-1/2 transform -translate-x-1/2 w-[95%] z-50 md:hidden"
199-
style={{ top: bannerVisible ? 'calc(44px + 90px)' : '90px' }}
200-
>
201-
<div className="backdrop-blur-xl bg-[#1a1a1a]/50 border border-white/10 rounded-[30px] shadow-2xl overflow-hidden p-2">
202-
<nav className="flex flex-col p-2">
203-
<button
204-
onClick={handleAboutClick}
205-
className="flex items-center justify-between px-4 py-3.5 rounded-xl text-white/90 hover:text-white hover:bg-white/10 transition-all group"
206-
>
207-
<span className="text-base font-medium font-urbanist">{t('nav.about')}</span>
208-
<span className="text-white/20 group-hover:text-white/60 transition-colors"></span>
209-
</button>
210-
211-
<div className="h-px bg-white/5 mx-2" />
212-
213-
<button
214-
onClick={handleSolutionsClick}
215-
className="flex items-center justify-between px-4 py-3.5 rounded-xl text-white/90 hover:text-white hover:bg-white/10 transition-all group"
216-
>
217-
<span className="text-base font-medium font-urbanist">{t('nav.solutions')}</span>
218-
<span className="text-white/20 group-hover:text-white/60 transition-colors"></span>
219-
</button>
220-
221-
<div className="h-px bg-white/5 mx-2" />
222-
223-
<button
224-
onClick={() => {
225-
setIsMobileMenuOpen(false);
226-
navigate('/blog');
227-
}}
228-
className="flex items-center justify-between px-4 py-3.5 rounded-xl text-white/90 hover:text-white hover:bg-white/10 transition-all group"
229-
>
230-
<span className="text-base font-medium font-urbanist">{t('nav.blog')}</span>
231-
<span className="text-white/20 group-hover:text-white/60 transition-colors"></span>
232-
</button>
233-
234-
<div className="h-px bg-white/5 mx-2" />
235-
236-
<button
237-
onClick={() => {
238-
setIsMobileMenuOpen(false);
239-
navigate('/docs');
240-
}}
241-
className="flex items-center justify-between px-4 py-3.5 rounded-xl text-white/90 hover:text-white hover:bg-white/10 transition-all group"
242-
>
243-
<span className="text-base font-medium font-urbanist">{t('nav.docs')}</span>
244-
<span className="text-white/20 group-hover:text-white/60 transition-colors"></span>
245-
</button>
246-
247-
<div className="h-px bg-white/5 mx-2 my-1" />
248-
249-
<div className="p-2">
250-
<LanguageSwitcher isExpanded={true} forceDark={true} />
251-
</div>
252-
253-
<div className="h-px bg-white/5 mx-2" />
254-
255-
<button
256-
onClick={() => {
257-
setIsMobileMenuOpen(false);
258-
navigate('/login');
259-
}}
260-
className="flex items-center justify-center mx-2 my-2 px-4 py-3 rounded-xl text-sm font-medium transition-all"
261-
style={{
262-
fontFamily: 'Urbanist, sans-serif',
263-
background: 'rgba(255, 255, 255, 0.95)',
264-
color: 'rgb(10, 10, 10)',
265-
}}
266-
>
267-
{t('nav.login')}
268-
</button>
269-
</nav>
270-
</div>
271-
</motion.div>
272-
</>
273-
)}
274-
</AnimatePresence>
37+
<SharedHeader bannerVisible={bannerVisible} />
27538

27639
<div className="relative z-10">
27740
<Hero />

0 commit comments

Comments
 (0)