Skip to content

Commit ce69bfc

Browse files
committed
feat: Add Google and GitHub social login to the login page and centralize Supabase client configuration with authentication options.
1 parent 9b6acb4 commit ce69bfc

3 files changed

Lines changed: 136 additions & 33 deletions

File tree

src/app/login/page.tsx

Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,15 @@ import { Input } from "@/components/ui/input";
66
import { Label } from "@/components/ui/label";
77
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
88
import Link from "next/link";
9-
import { ArrowLeft, Mail, CheckCircle2 } from "lucide-react";
10-
import { createClient } from "@supabase/supabase-js";
11-
12-
const supabase = createClient(
13-
process.env.NEXT_PUBLIC_SUPABASE_URL!,
14-
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY!
15-
);
9+
import { ArrowLeft, Mail, CheckCircle2, Loader2 } from "lucide-react";
10+
import { supabase } from "@/lib/supabase";
1611

1712
export default function LoginPage() {
1813
const [email, setEmail] = useState("");
1914
const [isSubmitting, setIsSubmitting] = useState(false);
2015
const [isSubmitted, setIsSubmitted] = useState(false);
2116
const [error, setError] = useState<string | null>(null);
17+
const [socialLoading, setSocialLoading] = useState<string | null>(null);
2218

2319
const handleSubmit = async (e: React.FormEvent) => {
2420
e.preventDefault();
@@ -42,6 +38,23 @@ export default function LoginPage() {
4238
setIsSubmitted(true);
4339
};
4440

41+
const handleSocialLogin = async (provider: "google" | "github") => {
42+
setSocialLoading(provider);
43+
setError(null);
44+
45+
const { error: authError } = await supabase.auth.signInWithOAuth({
46+
provider,
47+
options: {
48+
redirectTo: `${window.location.origin}/auth/callback`,
49+
},
50+
});
51+
52+
if (authError) {
53+
setError(authError.message);
54+
setSocialLoading(null);
55+
}
56+
};
57+
4558
return (
4659
<div className="min-h-screen flex flex-col items-center justify-center px-6 dot-grid">
4760
<Link
@@ -100,7 +113,7 @@ export default function LoginPage() {
100113
) : (
101114
<form onSubmit={handleSubmit} className="space-y-4">
102115
<div className="space-y-2">
103-
<Label htmlFor="email">Email address</Label>
116+
<Label htmlFor="email">Work email</Label>
104117
<div className="relative">
105118
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
106119
<Input
@@ -113,8 +126,15 @@ export default function LoginPage() {
113126
required
114127
/>
115128
</div>
129+
<p className="text-[11px] text-muted-foreground">
130+
Work email preferred — helps us personalize your intel
131+
</p>
116132
</div>
117133

134+
{error && (
135+
<p className="text-sm text-red-500 text-center">{error}</p>
136+
)}
137+
118138
<Button
119139
type="submit"
120140
className="w-full"
@@ -123,6 +143,67 @@ export default function LoginPage() {
123143
{isSubmitting ? "Sending link..." : "Send Magic Link"}
124144
</Button>
125145

146+
{/* Divider */}
147+
<div className="relative my-6">
148+
<div className="absolute inset-0 flex items-center">
149+
<span className="w-full border-t border-border" />
150+
</div>
151+
<div className="relative flex justify-center text-xs uppercase">
152+
<span className="bg-card px-2 text-muted-foreground">Or continue with</span>
153+
</div>
154+
</div>
155+
156+
{/* Social Login Buttons */}
157+
<div className="grid grid-cols-2 gap-3">
158+
<Button
159+
type="button"
160+
variant="outline"
161+
onClick={() => handleSocialLogin("google")}
162+
disabled={socialLoading !== null}
163+
className="gap-2"
164+
>
165+
{socialLoading === "google" ? (
166+
<Loader2 className="w-4 h-4 animate-spin" />
167+
) : (
168+
<svg className="w-4 h-4" viewBox="0 0 24 24">
169+
<path
170+
fill="currentColor"
171+
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
172+
/>
173+
<path
174+
fill="currentColor"
175+
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
176+
/>
177+
<path
178+
fill="currentColor"
179+
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
180+
/>
181+
<path
182+
fill="currentColor"
183+
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
184+
/>
185+
</svg>
186+
)}
187+
Google
188+
</Button>
189+
<Button
190+
type="button"
191+
variant="outline"
192+
onClick={() => handleSocialLogin("github")}
193+
disabled={socialLoading !== null}
194+
className="gap-2"
195+
>
196+
{socialLoading === "github" ? (
197+
<Loader2 className="w-4 h-4 animate-spin" />
198+
) : (
199+
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
200+
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
201+
</svg>
202+
)}
203+
GitHub
204+
</Button>
205+
</div>
206+
126207
<p className="text-xs text-center text-muted-foreground">
127208
By signing in, you agree to our{" "}
128209
<Link href="/terms" className="text-emerald-400 hover:underline">

src/app/page.tsx

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ export default function Home() {
3737
{/* Headline */}
3838
<h1 className="font-display text-5xl sm:text-6xl lg:text-7xl text-foreground leading-[1.1] mb-6">
3939
Your competitors just changed pricing. <br />
40-
<span className="text-emerald-400 italic">You found out first.</span>
40+
<span className="text-emerald-400 italic">You knew 6 hours ago.</span>
4141
</h1>
4242

4343
{/* Subheadline */}
4444
<p className="text-lg sm:text-xl text-muted-foreground max-w-2xl mx-auto mb-10 leading-relaxed">
45-
RivalEye monitors competitor pricing pages from 8 global regions.
46-
When they move, you get an <span className="text-foreground font-medium">AI-powered tactical brief</span> — not just an alert.
45+
RivalEye monitors competitor pricing pages across 4 global regions.
46+
When they move, you get an <span className="text-foreground font-medium">AI tactical brief</span> — not just a notification.
4747
</p>
4848

4949
{/* CTAs */}
@@ -63,7 +63,7 @@ export default function Home() {
6363

6464
{/* Trust Signal */}
6565
<p className="text-sm text-muted-foreground mt-8">
66-
Free forever for 1 competitor • Setup in 30 seconds
66+
Free forever for 1 competitor • Setup in 30 seconds • No credit card
6767
</p>
6868
</div>
6969
</section>
@@ -73,15 +73,19 @@ export default function Home() {
7373
<div className="max-w-4xl mx-auto flex flex-wrap items-center justify-center gap-12 text-muted-foreground text-sm font-medium">
7474
<div className="flex items-center gap-2">
7575
<Check className="w-4 h-4 text-emerald-400" />
76-
<span>Detects changes in &lt;24 hours</span>
76+
<span>Daily automated scans</span>
7777
</div>
7878
<div className="flex items-center gap-2">
7979
<Globe2 className="w-4 h-4 text-emerald-400" />
80-
<span>4 key global regions</span>
80+
<span>4 global pricing regions</span>
8181
</div>
8282
<div className="flex items-center gap-2">
8383
<Sparkles className="w-4 h-4 text-emerald-400" />
84-
<span>AI explains the "Why"</span>
84+
<span>AI explains the strategy shift</span>
85+
</div>
86+
<div className="flex items-center gap-2">
87+
<Bell className="w-4 h-4 text-emerald-400" />
88+
<span>Slack + Email alerts</span>
8589
</div>
8690
</div>
8791
</section>
@@ -272,12 +276,12 @@ export default function Home() {
272276

273277
<p className="text-muted-foreground mb-8 leading-relaxed relative z-10">
274278
Competitors use IP-based pricing to hide localized strategies.
275-
RivalEye deployments monitor from 8 global regions simultaneously,
279+
RivalEye monitors from 4 key regions simultaneously,
276280
uncovering hidden discounts and regional test-pricing.
277281
</p>
278282

279-
<div className="grid grid-cols-4 sm:grid-cols-8 gap-3 relative z-10">
280-
{["🇺🇸 US", "🇬🇧 UK", "🇩🇪 DE", "🇫🇷 FR", "🇮🇳 IN", "🇦🇺 AU", "🇧🇷 BR", "🇯🇵 JP"].map(
283+
<div className="grid grid-cols-4 gap-3 relative z-10">
284+
{["🇺🇸 United States", "🇪🇺 Europe", "🇮🇳 India", "🌍 Global"].map(
281285
(region) => (
282286
<div
283287
key={region}
@@ -314,11 +318,11 @@ export default function Home() {
314318

315319
<ul className="space-y-4 text-sm text-muted-foreground mb-10">
316320
{[
317-
"1 competitor sensor",
318-
"Daily trajectory scans",
319-
"Standard email alerts",
320-
"Basic diff detection",
321-
"7-day history storage",
321+
"1 competitor tracking",
322+
"Daily automated scans",
323+
"Email alerts",
324+
"Basic change detection",
325+
"7-day history",
322326
].map((item, i) => (
323327
<li key={i} className="flex items-center gap-3">
324328
<Check className="w-4 h-4 text-emerald-500/50" />
@@ -348,13 +352,14 @@ export default function Home() {
348352

349353
<ul className="space-y-4 text-sm mb-10">
350354
{[
351-
{ text: "5 competitor sensors", highlight: false },
352-
{ text: "Unlimited page depth", highlight: false },
353-
{ text: "Frequent monitoring scans", highlight: false },
354-
{ text: "4 Regional sensors (US, EU, IN)", highlight: true },
355-
{ text: "AI Tactical Briefs", highlight: true },
356-
{ text: "Visual verification (SVG/Images)", highlight: true },
357-
{ text: "Unlimited delta history", highlight: false },
355+
{ text: "5 competitors", highlight: false },
356+
{ text: "Unlimited pages per competitor", highlight: false },
357+
{ text: "Daily + on-demand scans", highlight: false },
358+
{ text: "4 regional pricing sensors (US, EU, IN, Global)", highlight: true },
359+
{ text: "AI tactical briefs", highlight: true },
360+
{ text: "Visual snapshots for verification", highlight: true },
361+
{ text: "Slack + Email alerts", highlight: true },
362+
{ text: "Unlimited history", highlight: false },
358363
].map((item, i) => (
359364
<li
360365
key={i}
@@ -394,8 +399,8 @@ export default function Home() {
394399
<span className="text-emerald-400">Do you have a response?</span>
395400
</h2>
396401
<p className="text-lg text-muted-foreground mb-10 leading-relaxed max-w-xl mx-auto">
397-
Every day you&apos;re not monitoring, you&apos;re flying blind.
398-
Start free — it takes 30 seconds.
402+
Every day you&apos;re not monitoring, your sales team is flying blind.
403+
Start free — takes 30 seconds.
399404
</p>
400405
<Link href="/login">
401406
<Button size="lg" className="glow-emerald text-base px-10 py-8 gap-3">

src/lib/supabase.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,26 @@ if (!process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY) {
88
throw new Error("Missing NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY environment variable");
99
}
1010

11+
/**
12+
* Client-side Supabase client
13+
*
14+
* Configured with:
15+
* - autoRefreshToken: true (automatically refresh tokens before expiry)
16+
* - persistSession: true (store session in localStorage)
17+
* - detectSessionInUrl: true (handle OAuth callbacks)
18+
*/
1119
export const supabase = createClient(
1220
process.env.NEXT_PUBLIC_SUPABASE_URL,
13-
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY
21+
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY,
22+
{
23+
auth: {
24+
autoRefreshToken: true,
25+
persistSession: true,
26+
detectSessionInUrl: true,
27+
storageKey: "rivaleye-auth",
28+
flowType: "pkce",
29+
},
30+
}
1431
);
1532

1633
// Server-side client with service role key (for API routes/server actions)

0 commit comments

Comments
 (0)