Skip to content

Commit f8d6af4

Browse files
feat(portal): add gmail signin (#1587)
Co-authored-by: chasprowebdev <chasgarciaprowebdev@gmail.com>
1 parent d00284a commit f8d6af4

File tree

4 files changed

+141
-43
lines changed

4 files changed

+141
-43
lines changed
Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import { LoginForm } from '@/app/components/login-form';
12
import { OtpSignIn } from '@/app/components/otp';
3+
import { env } from '@/env.mjs';
24
import { Button } from '@comp/ui/button';
5+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@comp/ui/card';
6+
import { Icons } from '@comp/ui/icons';
37
import { ArrowRight } from 'lucide-react';
48
import type { Metadata } from 'next';
59
import Link from 'next/link';
@@ -15,51 +19,50 @@ export default async function Page() {
1519
</div>
1620
);
1721

22+
const showGoogle = !!(env.AUTH_GOOGLE_ID && env.AUTH_GOOGLE_SECRET);
23+
1824
return (
19-
<>
20-
<div className="flex min-h-[calc(100vh-15rem)] items-center justify-center overflow-hidden p-6 md:p-0">
21-
<div className="relative z-20 m-auto flex w-full max-w-[380px] flex-col py-8">
22-
<div className="relative flex w-full flex-col">
23-
<div className="from-primary inline-block bg-clip-text pb-4">
24-
<div className="flex flex-row items-center gap-2">
25-
<Link href="/" className="flex flex-row items-center gap-2">
26-
<h1 className="font-mono text-xl font-semibold">Comp AI</h1>
25+
<div className="flex min-h-dvh flex-col text-foreground">
26+
<main className="flex flex-1 items-center justify-center p-6">
27+
<Card className="w-full max-w-lg">
28+
<CardHeader className="text-center space-y-3 pt-10">
29+
<Icons.Logo className="h-10 w-10 mx-auto" />
30+
<CardTitle className="text-2xl tracking-tight text-card-foreground">
31+
Employee Portal
32+
</CardTitle>
33+
<CardDescription className="text-base text-muted-foreground px-4">
34+
Enter your email address to receive a one time password.
35+
</CardDescription>
36+
</CardHeader>
37+
<CardContent className="space-y-6 pb-6">
38+
{defaultSignInOptions}
39+
<LoginForm showGoogle={showGoogle} />
40+
</CardContent>
41+
<CardFooter className="pb-10">
42+
<div className="from-primary/10 via-primary/5 to-primary/5 rounded-sm bg-gradient-to-r p-4">
43+
<h3 className="text-sm font-medium">
44+
Comp AI - AI that handles compliance for you in hours.
45+
</h3>
46+
<p className="text-muted-foreground mt-1 text-xs">
47+
Comp AI makes SOC 2, ISO 27001, HIPAA and GDPR effortless. Eliminate compliance
48+
busywork, win more deals and accelerate growth.
49+
</p>
50+
<Button variant="link" className="mt-2 p-0" asChild>
51+
<Link
52+
href="https://trycomp.ai"
53+
target="_blank"
54+
className="hover:underline hover:underline-offset-2"
55+
>
56+
<span className="text-primary mt-2 inline-flex items-center gap-2 text-xs font-medium">
57+
Learn More
58+
<ArrowRight className="h-3 w-3" />
59+
</span>
2760
</Link>
28-
</div>
29-
<h2 className="mt-4 text-lg font-medium">Employee Portal</h2>
30-
<div className="mt-2">
31-
<span className="text-muted-foreground text-xs">
32-
Enter your email address to receive a one time password.
33-
</span>
34-
</div>
61+
</Button>
3562
</div>
36-
37-
<div className="pointer-events-auto flex flex-col">{defaultSignInOptions}</div>
38-
</div>
39-
40-
<div className="from-primary/10 via-primary/5 to-primary/5 mt-8 rounded-sm bg-gradient-to-r p-4">
41-
<h3 className="text-sm font-medium">
42-
Comp AI - AI that handles compliance for you in hours.
43-
</h3>
44-
<p className="text-muted-foreground mt-1 text-xs">
45-
Comp AI makes SOC 2, ISO 27001, HIPAA and GDPR effortless. Eliminate compliance
46-
busywork, win more deals and accelerate growth.
47-
</p>
48-
<Button variant="link" className="mt-2 p-0" asChild>
49-
<Link
50-
href="https://trycomp.ai"
51-
target="_blank"
52-
className="hover:underline hover:underline-offset-2"
53-
>
54-
<span className="text-primary mt-2 inline-flex items-center gap-2 text-xs font-medium">
55-
Learn More
56-
<ArrowRight className="h-3 w-3" />
57-
</span>
58-
</Link>
59-
</Button>
60-
</div>
61-
</div>
62-
</div>
63-
</>
63+
</CardFooter>
64+
</Card>
65+
</main>
66+
</div>
6467
);
6568
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use client';
2+
3+
import { authClient } from '@/app/lib/auth-client';
4+
import { Button } from '@comp/ui/button';
5+
import { Icons } from '@comp/ui/icons';
6+
import { Loader2 } from 'lucide-react';
7+
import { useState } from 'react';
8+
9+
export function GoogleSignIn({
10+
inviteCode,
11+
searchParams,
12+
}: {
13+
inviteCode?: string;
14+
searchParams?: URLSearchParams;
15+
}) {
16+
const [isLoading, setLoading] = useState(false);
17+
18+
const handleSignIn = async () => {
19+
setLoading(true);
20+
21+
// Build the callback URL with search params
22+
const baseURL = window.location.origin;
23+
const path = inviteCode ? `/invite/${inviteCode}` : '/';
24+
const redirectTo = new URL(path, baseURL);
25+
26+
// Append all search params if they exist
27+
if (searchParams) {
28+
searchParams.forEach((value, key) => {
29+
redirectTo.searchParams.append(key, value);
30+
});
31+
}
32+
33+
console.log('******* redirectTo', redirectTo.toString());
34+
35+
await authClient.signIn.social({
36+
provider: 'google',
37+
callbackURL: redirectTo.toString(),
38+
});
39+
};
40+
41+
return (
42+
<Button
43+
onClick={handleSignIn}
44+
className="w-full h-11 font-medium"
45+
variant="outline"
46+
disabled={isLoading}
47+
>
48+
{isLoading ? (
49+
<Loader2 className="h-4 w-4 animate-spin" />
50+
) : (
51+
<>
52+
<Icons.Google className="h-4 w-4" />
53+
Continue with Google
54+
</>
55+
)}
56+
</Button>
57+
);
58+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use client';
2+
3+
import { GoogleSignIn } from './google-sign-in';
4+
import { useSearchParams } from 'next/navigation';
5+
6+
interface LoginFormProps {
7+
inviteCode?: string;
8+
showGoogle: boolean;
9+
}
10+
11+
export function LoginForm({ inviteCode, showGoogle }: LoginFormProps) {
12+
const searchParams = useSearchParams();
13+
14+
if (!showGoogle) {
15+
return;
16+
}
17+
18+
return (
19+
<div className="mt-4">
20+
<div className="relative flex items-center justify-center py-2">
21+
<div className="absolute inset-x-0 top-1/2 flex items-center">
22+
<span className="w-full border-t" />
23+
</div>
24+
<span className="relative z-10 bg-background px-3 text-xs text-muted-foreground font-medium">
25+
OR
26+
</span>
27+
</div>
28+
<div className="space-y-4 pt-4">
29+
<GoogleSignIn inviteCode={inviteCode} searchParams={searchParams as URLSearchParams} />
30+
</div>
31+
</div>
32+
);
33+
}

apps/portal/src/env.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ export const env = createEnv({
88
RESEND_API_KEY: z.string(),
99
UPSTASH_REDIS_REST_URL: z.string().optional(),
1010
UPSTASH_REDIS_REST_TOKEN: z.string().optional(),
11+
AUTH_GOOGLE_ID: z.string(),
12+
AUTH_GOOGLE_SECRET: z.string(),
1113
},
1214

1315
client: {
@@ -25,6 +27,8 @@ export const env = createEnv({
2527
RESEND_API_KEY: process.env.RESEND_API_KEY,
2628
UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL,
2729
UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN,
30+
AUTH_GOOGLE_ID: process.env.AUTH_GOOGLE_ID,
31+
AUTH_GOOGLE_SECRET: process.env.AUTH_GOOGLE_SECRET,
2832
},
2933

3034
skipValidation: !!process.env.CI || !!process.env.SKIP_ENV_VALIDATION,

0 commit comments

Comments
 (0)