Skip to content

Commit 00eac44

Browse files
committed
configure supabase to use cookies
1 parent d716e5b commit 00eac44

25 files changed

+315
-144
lines changed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,16 @@ There are many auth providers you can choose from. [See the Supabase docs](https
156156

157157
The file structure uses the group by `feature` concept. This is where you will colocate code related to a specific feature, with the exception of UI code. Typically you want to keep your UI code in the `app` dir, with the exception of reusable components. Most of the time reusable components will be agnostic to a feature and should live in the `components` dir. The `components/ui` dir is where `shadcn/ui` components are generated to.
158158

159+
### Going live
160+
161+
Follow these steps when you're ready to go live:
162+
163+
- [ ] Activate your Stripe account and set the dashboard to live mode
164+
- [ ] Repeat the steps above to create a Stripe webhook in live mode, this time using your live url
165+
- [ ] Update Vercel env variables with your live Stripe pk, sk, and whsec
166+
- [ ] After Vercel has redeployed with your new env variables, run the fixture command using your Stripe sk
167+
- [ ] [Configure Supabase SMTP](https://supabase.com/docs/guides/auth/auth-smtp). I recommend using Resend, it's super easy to add with [the Resend integration](https://supabase.com/partners/integrations/resend)
168+
159169
---
160170

161171
## Support

package-lock.json

+30-37
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"@radix-ui/react-tabs": "1.0.4",
2424
"@radix-ui/react-toast": "1.1.5",
2525
"@stripe/stripe-js": "2.2.0",
26-
"@supabase/auth-helpers-nextjs": "0.8.7",
26+
"@supabase/ssr": "0.1.0",
2727
"@supabase/supabase-js": "2.39.0",
2828
"@vercel/analytics": "1.1.1",
2929
"class-variance-authority": "0.7.0",

src/app/(auth)/auth-actions.ts

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use server';
2+
3+
import { redirect } from 'next/navigation';
4+
5+
import { createSupabaseServerClient } from '@/libs/supabase/supabase-server-client';
6+
import { ActionResponse } from '@/types/action-response';
7+
import { getURL } from '@/utils/get-url';
8+
9+
export async function signInWithOAuth(provider: 'github' | 'google'): Promise<ActionResponse> {
10+
const supabase = createSupabaseServerClient();
11+
12+
const { data, error } = await supabase.auth.signInWithOAuth({
13+
provider,
14+
options: {
15+
redirectTo: getURL('/auth/callback'),
16+
},
17+
});
18+
19+
if (error) {
20+
console.error(error);
21+
return { data: null, error: error };
22+
}
23+
24+
return redirect(data.url);
25+
}
26+
27+
export async function signInWithEmail(email: string): Promise<ActionResponse> {
28+
const supabase = createSupabaseServerClient();
29+
30+
const { error } = await supabase.auth.signInWithOtp({
31+
email,
32+
options: {
33+
emailRedirectTo: getURL('/auth/callback'),
34+
},
35+
});
36+
37+
if (error) {
38+
console.error(error);
39+
return { data: null, error: error };
40+
}
41+
42+
return { data: null, error: null };
43+
}
44+
45+
export async function signOut(): Promise<ActionResponse> {
46+
const supabase = createSupabaseServerClient();
47+
const { error } = await supabase.auth.signOut();
48+
49+
if (error) {
50+
console.error(error);
51+
return { data: null, error: error };
52+
}
53+
54+
return { data: null, error: null };
55+
}

src/app/(auth)/auth-ui.tsx

+26-40
Original file line numberDiff line numberDiff line change
@@ -9,72 +9,58 @@ import { Button } from '@/components/ui/button';
99
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
1010
import { Input } from '@/components/ui/input';
1111
import { toast } from '@/components/ui/use-toast';
12-
import { useSupabase } from '@/libs/supabase/supabase-provider';
13-
import { getURL } from '@/utils/get-url';
12+
import { ActionResponse } from '@/types/action-response';
1413

1514
const titleMap = {
1615
login: 'Login to UPDATE_THIS_WITH_YOUR_APP_DISPLAY_NAME',
1716
signup: 'Join UPDATE_THIS_WITH_YOUR_APP_DISPLAY_NAME and start generating banners for free',
1817
} as const;
1918

20-
const siteUrl = getURL();
21-
22-
export function AuthUI({ mode }: { mode: 'login' | 'signup' }) {
23-
const { supabase } = useSupabase();
19+
export function AuthUI({
20+
mode,
21+
signInWithOAuth,
22+
signInWithEmail,
23+
}: {
24+
mode: 'login' | 'signup';
25+
signInWithOAuth: (provider: 'github' | 'google') => Promise<ActionResponse>;
26+
signInWithEmail: (email: string) => Promise<ActionResponse>;
27+
}) {
2428
const [pending, setPending] = useState(false);
2529
const [emailFormOpen, setEmailFormOpen] = useState(false);
2630

27-
async function signInWithOAuth(provider: 'github' | 'google') {
31+
async function handleEmailSubmit(event: FormEvent<HTMLFormElement>) {
32+
event.preventDefault();
2833
setPending(true);
29-
const { error } = await supabase.auth.signInWithOAuth({
30-
provider,
31-
options: {
32-
redirectTo: `${siteUrl}/auth/callback`,
33-
},
34-
});
35-
if (error) {
36-
toast({
37-
variant: 'destructive',
38-
description: 'An error occurred while authenticating. Please try again.',
39-
});
40-
console.error(error);
41-
}
42-
}
34+
const form = event.target as HTMLFormElement;
35+
const email = form['email'].value;
36+
const response = await signInWithEmail(email);
4337

44-
async function signInWithEmail(email: string) {
45-
setPending(true);
46-
const { error } = await supabase.auth.signInWithOtp({
47-
email,
48-
options: {
49-
emailRedirectTo: `${siteUrl}/auth/callback`,
50-
},
51-
});
52-
if (error) {
38+
if (response?.error) {
5339
toast({
5440
variant: 'destructive',
5541
description: 'An error occurred while authenticating. Please try again.',
5642
});
57-
console.error(error);
5843
} else {
5944
toast({
6045
description: `To continue, click the link in the email sent to: ${email}`,
6146
});
6247
}
63-
}
6448

65-
function handleEmailSubmit(event: FormEvent<HTMLFormElement>) {
66-
event.preventDefault();
67-
setPending(true);
68-
const form = event.target as HTMLFormElement;
69-
const email = form['email'].value;
70-
signInWithEmail(email);
7149
form.reset();
7250
setPending(false);
7351
}
7452

75-
function handleOAuthClick(provider: 'google' | 'github') {
53+
async function handleOAuthClick(provider: 'google' | 'github') {
7654
setPending(true);
77-
signInWithOAuth(provider);
55+
const response = await signInWithOAuth(provider);
56+
57+
if (response?.error) {
58+
toast({
59+
variant: 'destructive',
60+
description: 'An error occurred while authenticating. Please try again.',
61+
});
62+
setPending(false);
63+
}
7864
}
7965

8066
return (

src/app/(auth)/login/page.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { redirect } from 'next/navigation';
33
import { getSession } from '@/features/account/controllers/get-session';
44
import { getSubscription } from '@/features/account/controllers/get-subscription';
55

6+
import { signInWithEmail, signInWithOAuth } from '../auth-actions';
67
import { AuthUI } from '../auth-ui';
78

89
export default async function LoginPage() {
@@ -19,7 +20,7 @@ export default async function LoginPage() {
1920

2021
return (
2122
<section className='py-xl m-auto flex h-full max-w-lg items-center'>
22-
<AuthUI mode='login' />
23+
<AuthUI mode='login' signInWithOAuth={signInWithOAuth} signInWithEmail={signInWithEmail} />
2324
</section>
2425
);
2526
}

src/app/(auth)/signup/page.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { redirect } from 'next/navigation';
33
import { getSession } from '@/features/account/controllers/get-session';
44
import { getSubscription } from '@/features/account/controllers/get-subscription';
55

6+
import { signInWithEmail, signInWithOAuth } from '../auth-actions';
67
import { AuthUI } from '../auth-ui';
78

89
export default async function SignUp() {
@@ -19,7 +20,7 @@ export default async function SignUp() {
1920

2021
return (
2122
<section className='py-xl m-auto flex h-full max-w-lg items-center'>
22-
<AuthUI mode='signup' />
23+
<AuthUI mode='signup' signInWithOAuth={signInWithOAuth} signInWithEmail={signInWithEmail} />
2324
</section>
2425
);
2526
}

src/app/layout.tsx

+10-11
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { IoLogoFacebook, IoLogoInstagram, IoLogoTwitter } from 'react-icons/io5'
66

77
import { Logo } from '@/components/logo';
88
import { Toaster } from '@/components/ui/toaster';
9-
import { SupabaseProvider } from '@/libs/supabase/supabase-provider';
109
import { cn } from '@/utils/cn';
1110
import { Analytics } from '@vercel/analytics/react';
1211

@@ -36,15 +35,13 @@ export default function RootLayout({ children }: PropsWithChildren) {
3635
return (
3736
<html lang='en'>
3837
<body className={cn('font-sans antialiased', montserrat.variable, montserratAlternates.variable)}>
39-
<SupabaseProvider>
40-
<div className='m-auto flex h-full max-w-[1440px] flex-col px-4'>
41-
<AppBar />
42-
<main className='relative flex-1'>
43-
<div className='relative h-full'>{children}</div>
44-
</main>
45-
<Footer />
46-
</div>
47-
</SupabaseProvider>
38+
<div className='m-auto flex h-full max-w-[1440px] flex-col px-4'>
39+
<AppBar />
40+
<main className='relative flex-1'>
41+
<div className='relative h-full'>{children}</div>
42+
</main>
43+
<Footer />
44+
</div>
4845
<Toaster />
4946
<Analytics />
5047
</body>
@@ -111,7 +108,9 @@ function Footer() {
111108
</div>
112109
</div>
113110
<div className='border-t border-zinc-800 py-6 text-center'>
114-
<span className='text-neutral4 text-xs'>Copyright {new Date().getFullYear()} © UPDATE_THIS_WITH_YOUR_APP_DISPLAY_NAME</span>
111+
<span className='text-neutral4 text-xs'>
112+
Copyright {new Date().getFullYear()} © UPDATE_THIS_WITH_YOUR_APP_DISPLAY_NAME
113+
</span>
115114
</div>
116115
</footer>
117116
);

src/app/navigation.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ import { Button } from '@/components/ui/button';
77
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTrigger } from '@/components/ui/sheet';
88
import { getSession } from '@/features/account/controllers/get-session';
99

10+
import { signOut } from './(auth)/auth-actions';
11+
1012
export async function Navigation() {
1113
const session = await getSession();
1214

1315
return (
1416
<div className='relative flex items-center gap-6'>
1517
{session ? (
16-
<AccountMenu />
18+
<AccountMenu signOut={signOut} />
1719
) : (
1820
<>
1921
<Button variant='sexy' className='hidden flex-shrink-0 lg:flex' asChild>

0 commit comments

Comments
 (0)