Skip to content

Commit cc358d8

Browse files
authored
fix: sso redirects + flow + ui (#5445)
1 parent 0c1a2cf commit cc358d8

File tree

7 files changed

+379
-288
lines changed

7 files changed

+379
-288
lines changed

web/components/shared/theme/themeContext.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ export default function ThemeProvider({
1717
<NextThemesProvider
1818
{...props}
1919
forcedTheme={
20-
pathname?.includes("/signup") || pathname?.includes("/signin")
20+
pathname?.includes("/signup") ||
21+
pathname?.includes("/signin") ||
22+
pathname?.includes("/sso") ||
23+
pathname?.includes("/reset")
2124
? "light"
2225
: undefined
2326
}
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
/* eslint-disable @next/next/no-img-element */
2+
import { useEffect, useState } from "react";
3+
import Link from "next/link";
4+
import Image from "next/image";
5+
6+
const centerImages = [
7+
"/static/onboarding-design-1.svg",
8+
"/static/onboarding-design-2.svg",
9+
];
10+
11+
const quotes = [
12+
{
13+
text: "The ability to test prompt variations on production traffic without touching a line of code is magical. It feels like we're cheating; it's just that good!",
14+
highlights: [
15+
{
16+
text: "It feels like we're cheating; it's just that good!",
17+
color: "text-slate-800",
18+
},
19+
],
20+
author: "Nishant Shukla",
21+
title: "Sr. Director of AI at QA Wolf",
22+
image: "/static/qawolf-logo.svg",
23+
},
24+
{
25+
text: "Thank you for an excellent observability platform! I pretty much use it for all my AI apps now.",
26+
highlights: [
27+
{
28+
text: "I pretty much use it for all my AI apps now.",
29+
color: "text-slate-800",
30+
},
31+
],
32+
author: "Hassan El Mghari",
33+
title: "DevRel Lead at Together AI",
34+
image: "/static/together-logo.svg",
35+
},
36+
];
37+
38+
const highlightText = (
39+
text: string,
40+
highlights: { text: string; color: string }[]
41+
) => {
42+
if (!highlights || highlights.length === 0) return <>{text}</>;
43+
44+
const result = [];
45+
let lastIndex = 0;
46+
47+
const sortedHighlights = [...highlights].sort((a, b) => {
48+
return text.indexOf(a.text) - text.indexOf(b.text);
49+
});
50+
51+
for (const highlight of sortedHighlights) {
52+
const index = text.indexOf(highlight.text, lastIndex);
53+
if (index === -1) continue;
54+
55+
if (index > lastIndex) {
56+
result.push(
57+
<span key={`text-${lastIndex}`}>{text.substring(lastIndex, index)}</span>
58+
);
59+
}
60+
61+
result.push(
62+
<span key={`highlight-${index}`} className="text-slate-800">
63+
{highlight.text}
64+
</span>
65+
);
66+
67+
lastIndex = index + highlight.text.length;
68+
}
69+
70+
if (lastIndex < text.length) {
71+
result.push(
72+
<span key={`text-${lastIndex}`}>{text.substring(lastIndex)}</span>
73+
);
74+
}
75+
76+
return <>{result}</>;
77+
};
78+
79+
export const AuthBrandingPanel = () => {
80+
const [selectedImage, setSelectedImage] = useState(centerImages[0]);
81+
const [selectedQuote, setSelectedQuote] = useState(quotes[0]);
82+
const [showQuote, setShowQuote] = useState(false);
83+
const [isContentLoaded, setIsContentLoaded] = useState(false);
84+
85+
useEffect(() => {
86+
const preloadImages = async () => {
87+
const imagePromises = centerImages.map((src) => {
88+
return new Promise((resolve) => {
89+
const img = new window.Image();
90+
img.src = src;
91+
img.onload = resolve;
92+
});
93+
});
94+
95+
const quoteImagePromises = quotes.map((quote) => {
96+
return new Promise((resolve) => {
97+
const img = new window.Image();
98+
img.src = quote.image;
99+
img.onload = resolve;
100+
});
101+
});
102+
103+
await Promise.all([...imagePromises, ...quoteImagePromises]);
104+
105+
const randomImgIndex = Math.floor(Math.random() * centerImages.length);
106+
setSelectedImage(centerImages[randomImgIndex]);
107+
108+
const randomQuoteIndex = Math.floor(Math.random() * quotes.length);
109+
setSelectedQuote(quotes[randomQuoteIndex]);
110+
111+
setShowQuote(Math.random() > 0.5);
112+
setIsContentLoaded(true);
113+
};
114+
115+
preloadImages();
116+
}, []);
117+
118+
return (
119+
<div className="relative hidden flex-col justify-between overflow-hidden bg-gradient-to-br from-slate-100 to-sky-100 p-10 md:m-4 md:flex md:w-1/2 md:rounded-3xl">
120+
<div className="relative z-20">
121+
<div className="flex items-center justify-between gap-4">
122+
<Link href="https://www.helicone.ai/" className="flex">
123+
<Image
124+
src="/static/logo-no-border.png"
125+
alt="Helicone - Open-source LLM observability and monitoring platform for developers."
126+
height={100}
127+
width={100}
128+
priority={true}
129+
/>
130+
</Link>
131+
<a
132+
href="https://www.producthunt.com/posts/helicone-ai"
133+
target="_blank"
134+
rel="noopener noreferrer"
135+
className="flex items-center"
136+
>
137+
<Image
138+
src="/static/product-of-the-day.svg"
139+
alt="#1 Product of the Day"
140+
width={120}
141+
height={26}
142+
/>
143+
</a>
144+
</div>
145+
</div>
146+
147+
{/* Center Image - Only shown when showQuote is false */}
148+
{!showQuote && isContentLoaded && (
149+
<div className="absolute inset-0 z-10 transition-opacity duration-300">
150+
<Image
151+
src={selectedImage}
152+
alt="Helicone Featured Image"
153+
fill
154+
style={{ objectFit: "cover" }}
155+
className="h-full w-full"
156+
priority={true}
157+
/>
158+
</div>
159+
)}
160+
161+
{/* Quote - Only shown when showQuote is true */}
162+
{showQuote && isContentLoaded ? (
163+
<>
164+
<div className="relative z-20 w-full space-y-3">
165+
<h1 className="text-4xl font-extrabold text-slate-300">&quot;</h1>
166+
<p className="w-full text-4xl font-medium text-slate-400">
167+
{highlightText(selectedQuote.text, selectedQuote.highlights)}
168+
</p>
169+
<h1 className="text-4xl font-bold text-slate-300">&quot;</h1>
170+
</div>
171+
172+
{/* Name and logo - Only shown with quote */}
173+
<div className="relative z-20 flex items-center gap-3 space-y-1">
174+
<Image
175+
src={selectedQuote.image}
176+
alt={selectedQuote.author}
177+
width={48}
178+
height={48}
179+
className="rounded-full"
180+
/>
181+
<div>
182+
<p className="text-md max-w-md text-slate-500">
183+
{selectedQuote.author}
184+
</p>
185+
<p className="max-w-md text-sm text-slate-400">
186+
{selectedQuote.title}
187+
</p>
188+
</div>
189+
</div>
190+
</>
191+
) : (
192+
<>
193+
{/* Empty middle section when showing image */}
194+
<div className="flex-grow"></div>
195+
196+
{/* Attribution at bottom when showing image */}
197+
<div className="relative z-20 flex items-center gap-3 space-y-1">
198+
<div>
199+
<p className="text-md max-w-md text-slate-500">
200+
Designed for the entire LLM lifecycle
201+
</p>
202+
<p className="max-w-md text-sm text-slate-400">
203+
The CI workflow to take your LLM application from MVP to
204+
production.
205+
</p>
206+
</div>
207+
</div>
208+
</>
209+
)}
210+
</div>
211+
);
212+
};

0 commit comments

Comments
 (0)