Skip to content

Commit 3efdfb4

Browse files
authored
chore: utilize new supabase-js relation typing. (vercel#193)
* chore: utilize new supabase-js relation typing. * chore: fully type props. * chore: only gen public schema types. * fix: types.
1 parent 8697790 commit 3efdfb4

File tree

13 files changed

+294
-182
lines changed

13 files changed

+294
-182
lines changed

app/account/page.tsx

+42-27
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
import { ReactNode } from 'react';
2-
import { cookies } from 'next/headers'
3-
import { redirect } from 'next/navigation';
4-
import { revalidatePath } from 'next/cache'
5-
import Link from 'next/link';
1+
import ManageSubscriptionButton from './ManageSubscriptionButton';
62
import {
73
getSession,
84
getUserDetails,
95
getSubscription
106
} from '@/app/supabase-server';
11-
import { createServerActionClient } from '@supabase/auth-helpers-nextjs'
12-
import ManageSubscriptionButton from './ManageSubscriptionButton';
137
import Button from '@/components/ui/Button';
148
import { Database } from '@/types_db';
9+
import { createServerActionClient } from '@supabase/auth-helpers-nextjs';
10+
import { revalidatePath } from 'next/cache';
11+
import { cookies } from 'next/headers';
12+
import Link from 'next/link';
13+
import { redirect } from 'next/navigation';
14+
import { ReactNode } from 'react';
1515

1616
export default async function Account() {
1717
const session = await getSession();
@@ -27,35 +27,38 @@ export default async function Account() {
2727
subscription &&
2828
new Intl.NumberFormat('en-US', {
2929
style: 'currency',
30-
currency: subscription?.prices?.currency,
30+
currency: subscription?.prices?.currency!,
3131
minimumFractionDigits: 0
3232
}).format((subscription?.prices?.unit_amount || 0) / 100);
3333

3434
const updateName = async (formData: FormData) => {
35-
'use server'
35+
'use server';
3636

37-
const newName = formData.get('name')
38-
const supabase = createServerActionClient<Database>({ cookies })
37+
const newName = formData.get('name') as string;
38+
const supabase = createServerActionClient<Database>({ cookies });
3939
const session = await getSession();
4040
const user = session?.user;
41-
const { error } = await supabase.from('users').update({ full_name: newName }).eq('id', user?.id)
41+
const { error } = await supabase
42+
.from('users')
43+
.update({ full_name: newName })
44+
.eq('id', user?.id);
4245
if (error) {
43-
console.log(error)
46+
console.log(error);
4447
}
45-
revalidatePath('/account')
46-
}
48+
revalidatePath('/account');
49+
};
4750

4851
const updateEmail = async (formData: FormData) => {
49-
'use server'
52+
'use server';
5053

51-
const newEmail = formData.get('email')
52-
const supabase = createServerActionClient<Database>({ cookies })
53-
const { error } = await supabase.auth.updateUser({ email: newEmail })
54+
const newEmail = formData.get('email') as string;
55+
const supabase = createServerActionClient<Database>({ cookies });
56+
const { error } = await supabase.auth.updateUser({ email: newEmail });
5457
if (error) {
55-
console.log(error)
58+
console.log(error);
5659
}
57-
revalidatePath('/account')
58-
}
60+
revalidatePath('/account');
61+
};
5962

6063
return (
6164
<section className="mb-32 bg-black">
@@ -93,7 +96,12 @@ export default async function Account() {
9396
footer={
9497
<div className="flex flex-col items-start justify-between sm:flex-row sm:items-center">
9598
<p className="pb-4 sm:pb-0">64 characters maximum</p>
96-
<Button variant="slim" type="submit" form="nameForm" disabled={true}>
99+
<Button
100+
variant="slim"
101+
type="submit"
102+
form="nameForm"
103+
disabled={true}
104+
>
97105
{/* WARNING - In Next.js 13.4.x server actions are in alpha and should not be used in production code! */}
98106
Update Name
99107
</Button>
@@ -106,7 +114,7 @@ export default async function Account() {
106114
type="text"
107115
name="name"
108116
className="w-1/2 p-3 rounded-md bg-zinc-800"
109-
defaultValue={userDetails?.full_name}
117+
defaultValue={userDetails?.full_name ?? ''}
110118
placeholder="Your name"
111119
maxLength={64}
112120
/>
@@ -118,8 +126,15 @@ export default async function Account() {
118126
description="Please enter the email address you want to use to login."
119127
footer={
120128
<div className="flex flex-col items-start justify-between sm:flex-row sm:items-center">
121-
<p className="pb-4 sm:pb-0">We will email you to verify the change.</p>
122-
<Button variant="slim" type="submit" form="emailForm" disabled={true}>
129+
<p className="pb-4 sm:pb-0">
130+
We will email you to verify the change.
131+
</p>
132+
<Button
133+
variant="slim"
134+
type="submit"
135+
form="emailForm"
136+
disabled={true}
137+
>
123138
{/* WARNING - In Next.js 13.4.x server actions are in alpha and should not be used in production code! */}
124139
Update Email
125140
</Button>
@@ -132,7 +147,7 @@ export default async function Account() {
132147
type="text"
133148
name="email"
134149
className="w-1/2 p-3 rounded-md bg-zinc-800"
135-
defaultValue={user ? user.email : ""}
150+
defaultValue={user ? user.email : ''}
136151
placeholder="Your email"
137152
maxLength={64}
138153
/>

app/api/create-checkout-session/route.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@ export async function POST(req: Request) {
1212

1313
try {
1414
// 2. Get the user from Supabase auth
15-
const supabase = createRouteHandlerClient<Database>({
16-
headers,
17-
cookies
18-
});
15+
const supabase = createRouteHandlerClient<Database>({cookies});
1916
const {
2017
data: { user }
2118
} = await supabase.auth.getUser();

app/api/create-portal-link/route.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ import { Database } from '@/types_db';
88
export async function POST(req: Request) {
99
if (req.method === 'POST') {
1010
try {
11-
const supabase = createRouteHandlerClient<Database>({
12-
headers,
13-
cookies
14-
});
11+
const supabase = createRouteHandlerClient<Database>({cookies});
1512
const {
1613
data: { user }
1714
} = await supabase.auth.getUser();

app/layout.tsx

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
import Navbar from '@/components/ui/Navbar';
2-
import Footer from '@/components/ui/Footer';
31
import SupabaseProvider from './supabase-provider';
4-
5-
import { PageMeta } from '../types';
6-
2+
import Footer from '@/components/ui/Footer';
3+
import Navbar from '@/components/ui/Navbar';
74
import 'styles/main.css';
85
import 'styles/chrome-bug.css';
96

10-
const meta: PageMeta = {
7+
const meta = {
118
title: 'Next.js Subscription Starter',
129
description: 'Brought to you by Vercel, Stripe, and Supabase.',
1310
cardImage: '/og.png',

app/supabase-server.ts

+8-13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
import { cookies } from 'next/headers';
2-
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
3-
4-
import { Subscription, UserDetails } from '@/types';
51
import { Database } from '@/types_db';
6-
import { ProductWithPrice } from 'types';
2+
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
3+
import { cookies } from 'next/headers';
74

85
export const createServerSupabaseClient = () =>
96
createServerComponentClient<Database>({ cookies });
@@ -28,7 +25,7 @@ export async function getUserDetails() {
2825
.from('users')
2926
.select('*')
3027
.single();
31-
return userDetails as unknown as UserDetails;
28+
return userDetails;
3229
} catch (error) {
3330
console.error('Error:', error);
3431
return null;
@@ -42,17 +39,16 @@ export async function getSubscription() {
4239
.from('subscriptions')
4340
.select('*, prices(*, products(*))')
4441
.in('status', ['trialing', 'active'])
45-
.single();
46-
return subscription as Subscription;
42+
.single()
43+
.throwOnError();
44+
return subscription;
4745
} catch (error) {
4846
console.error('Error:', error);
4947
return null;
5048
}
5149
}
5250

53-
export const getActiveProductsWithPrices = async (): Promise<
54-
ProductWithPrice[]
55-
> => {
51+
export const getActiveProductsWithPrices = async () => {
5652
const supabase = createServerSupabaseClient();
5753
const { data, error } = await supabase
5854
.from('products')
@@ -65,6 +61,5 @@ export const getActiveProductsWithPrices = async (): Promise<
6561
if (error) {
6662
console.log(error.message);
6763
}
68-
// TODO: improve the typing here.
69-
return (data as any) || [];
64+
return data ?? [];
7065
};

components/Pricing.tsx

+26-24
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,32 @@
11
'use client';
22

3-
import { useState } from 'react';
4-
import { useRouter } from 'next/navigation';
5-
import cn from 'classnames';
6-
73
import Button from '@/components/ui/Button';
4+
import { Database } from '@/types_db';
85
import { postData } from '@/utils/helpers';
96
import { getStripe } from '@/utils/stripe-client';
7+
import { Session, User } from '@supabase/supabase-js';
8+
import cn from 'classnames';
9+
import { useRouter } from 'next/navigation';
10+
import { useState } from 'react';
1011

11-
import { Price, ProductWithPrice } from 'types';
12+
type Subscription = Database['public']['Tables']['subscriptions']['Row'];
13+
type Product = Database['public']['Tables']['products']['Row'];
14+
type Price = Database['public']['Tables']['prices']['Row'];
15+
interface ProductWithPrices extends Product {
16+
prices: Price[];
17+
}
18+
interface PriceWithProduct extends Price {
19+
products: Product | null;
20+
}
21+
interface SubscriptionWithProduct extends Subscription {
22+
prices: PriceWithProduct | null;
23+
}
1224

1325
interface Props {
14-
session: any;
15-
user: any;
16-
products: ProductWithPrice[];
17-
subscription: any;
26+
session: Session | null;
27+
user: User | null | undefined;
28+
products: ProductWithPrices[];
29+
subscription: SubscriptionWithProduct | null;
1830
}
1931

2032
type BillingInterval = 'lifetime' | 'year' | 'month';
@@ -42,6 +54,9 @@ export default function Pricing({
4254
if (!user) {
4355
return router.push('/signin');
4456
}
57+
if (price.product_id === subscription?.prices?.products?.id) {
58+
return router.push('/account');
59+
}
4560
try {
4661
const { sessionId } = await postData({
4762
url: '/api/create-checkout-session',
@@ -104,7 +119,7 @@ export default function Pricing({
104119
price.unit_amount &&
105120
new Intl.NumberFormat('en-US', {
106121
style: 'currency',
107-
currency: price.currency,
122+
currency: price.currency!,
108123
minimumFractionDigits: 0
109124
}).format(price.unit_amount / 100);
110125

@@ -185,19 +200,6 @@ export default function Pricing({
185200
Yearly billing
186201
</button>
187202
)}
188-
{intervals.includes('lifetime') && (
189-
<button
190-
onClick={() => setBillingInterval('lifetime')}
191-
type="button"
192-
className={`${
193-
billingInterval === 'lifetime'
194-
? 'relative w-1/2 bg-zinc-700 border-zinc-800 shadow-sm text-white'
195-
: 'ml-0.5 relative w-1/2 border border-transparent text-zinc-400'
196-
} rounded-md m-1 py-2 text-sm font-medium whitespace-nowrap focus:outline-none focus:ring-2 focus:ring-pink-500 focus:ring-opacity-50 focus:z-10 sm:w-auto sm:px-8`}
197-
>
198-
One-time billing
199-
</button>
200-
)}
201203
</div>
202204
</div>
203205
<div className="mt-12 space-y-4 sm:mt-16 sm:space-y-0 sm:grid sm:grid-cols-2 sm:gap-6 lg:max-w-4xl lg:mx-auto xl:max-w-none xl:mx-0 xl:grid-cols-3">
@@ -208,7 +210,7 @@ export default function Pricing({
208210
if (!price) return null;
209211
const priceString = new Intl.NumberFormat('en-US', {
210212
style: 'currency',
211-
currency: price.currency,
213+
currency: price.currency!,
212214
minimumFractionDigits: 0
213215
}).format((price?.unit_amount || 0) / 100);
214216
return (

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"@supabase/auth-helpers-nextjs": "^0.7.0",
1616
"@supabase/auth-ui-react": "^0.4.2",
1717
"@supabase/auth-ui-shared": "^0.1.6",
18-
"@supabase/supabase-js": "^2.22.0",
18+
"@supabase/supabase-js": "^2.23.0",
1919
"classnames": "^2.3.2",
2020
"next": "13.4.3",
2121
"react": "^18.2.0",

supabase/config.toml

+12-11
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,22 @@ enable_confirmations = false
6161
# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
6262
# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin`, `notion`, `twitch`,
6363
# `twitter`, `slack`, `spotify`, `workos`, `zoom`.
64-
[auth.external.twitter]
64+
[auth.external.apple]
6565
enabled = false
6666
client_id = ""
6767
secret = ""
6868
# Overrides the default auth redirectUrl.
69-
redirect_uri = "http://localhost:54321/auth/v1/callback"
70-
# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
71-
# or any other third-party OIDC providers.
72-
url = ""
73-
[auth.external.github]
74-
enabled = true
75-
client_id = ""
76-
secret = ""
77-
# Overrides the default auth redirectUrl.
78-
redirect_uri = "http://localhost:54321/auth/v1/callback"
69+
redirect_uri = ""
7970
# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
8071
# or any other third-party OIDC providers.
8172
url = ""
73+
74+
[analytics]
75+
enabled = false
76+
port = 54327
77+
vector_port = 54328
78+
# Setup BigQuery project to enable log viewer on local development stack.
79+
# See: https://supabase.com/docs/guides/getting-started/local-development#enabling-local-logging
80+
gcp_project_id = ""
81+
gcp_project_number = ""
82+
gcp_jwt_path = "supabase/gcloud.json"

0 commit comments

Comments
 (0)