Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Next.js 14 & Supabase SSR #278

Merged
merged 103 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from 97 commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
fd3e0f2
Update helpers.ts
chriscarrollsmith Nov 14, 2023
e679650
Update helpers.ts
chriscarrollsmith Nov 14, 2023
0b7597f
Update helpers.ts
chriscarrollsmith Nov 14, 2023
2468bf2
Support propagating product deletion to DB
chriscarrollsmith Nov 17, 2023
07130cd
initial
dalkommatt Nov 27, 2023
2f9aa32
Replace 'var' with 'const'
chriscarrollsmith Nov 27, 2023
135a1c5
add toast
dalkommatt Nov 28, 2023
7e7d633
add password recovery
dalkommatt Nov 28, 2023
29893ac
fixes
dalkommatt Nov 28, 2023
541c619
implement open PRs
dalkommatt Nov 28, 2023
c012ff0
implement more PRs
dalkommatt Nov 28, 2023
7520e3d
fix createOrRetrieveCustomer
dalkommatt Nov 28, 2023
a6644a3
Give button a displayName for logging purposes
chriscarrollsmith Nov 28, 2023
1a0cd38
Created a 'Card' ui component
chriscarrollsmith Nov 28, 2023
4b64f0b
Remove deprecated supabase auth helpers and update
chriscarrollsmith Nov 29, 2023
44b481a
use latest stripe api version, remove payment type
chriscarrollsmith Nov 29, 2023
d5e0c0c
Merge pull request #1 from dalkommatt/nextjs-14-supabase-ssr
chriscarrollsmith Nov 29, 2023
4446b80
Replaced trial_end with trial_period_days
chriscarrollsmith Nov 29, 2023
289bcea
Add signout as server action, move server calls
chriscarrollsmith Nov 29, 2023
41f6875
Added toasts
chriscarrollsmith Nov 29, 2023
c57bf26
'message' -> 'status' or 'error' in toast calls
chriscarrollsmith Nov 29, 2023
1b41ef8
Removed ring focus from toasts container and
chriscarrollsmith Nov 30, 2023
d6667d0
Remove obsolete NextJS config
chriscarrollsmith Nov 30, 2023
1506472
Update route.ts
chriscarrollsmith Nov 30, 2023
4f5b603
Merge pull request #2 from chriscarrollsmith/issue170
chriscarrollsmith Nov 30, 2023
4fad338
Merge pull request #3 from chriscarrollsmith/issue269
chriscarrollsmith Nov 30, 2023
ad95b6d
Merge pull request #5 from chriscarrollsmith/ui-refactor
chriscarrollsmith Nov 30, 2023
3fc4962
merged supabase-auth branch
chriscarrollsmith Nov 30, 2023
f06fd3c
Merged branch pr200 into dev
chriscarrollsmith Nov 30, 2023
1b6dca8
Merge branch 'toasts' into dev
chriscarrollsmith Nov 30, 2023
16a8e8a
Merge branch 'stripe-checkout' into dev
chriscarrollsmith Nov 30, 2023
e50fc43
Rolled back stripe API version change
chriscarrollsmith Nov 30, 2023
ac26a2e
Allow stripe-js to use user's default API version
chriscarrollsmith Nov 30, 2023
89fb774
Update dependencies to latest
chriscarrollsmith Nov 30, 2023
19dbc77
refactor supabase auth
dijonmusters Dec 1, 2023
ac5cc80
Merge pull request #1 from dijonmusters/nextjs-14-supabase-ssr
dalkommatt Dec 1, 2023
a4af3cc
add magic link/otp auth
dalkommatt Dec 1, 2023
a5aca7b
Update package.json
dalkommatt Dec 1, 2023
77f0724
Implement half-working password recovery
chriscarrollsmith Dec 1, 2023
61ea541
Merge pull request #7 from dalkommatt/nextjs-14-supabase-ssr
chriscarrollsmith Dec 3, 2023
cfb1137
Added signup, password signin, password reset
chriscarrollsmith Dec 3, 2023
ddadebc
Updated dependencies
chriscarrollsmith Dec 3, 2023
c1598bc
Squashed commit of the following:
chriscarrollsmith Dec 4, 2023
532efe8
API routes for the various auth pathways
chriscarrollsmith Dec 4, 2023
049d4d1
Updated dependencies
chriscarrollsmith Dec 4, 2023
b79a729
Eliminated API endpoints
chriscarrollsmith Dec 4, 2023
8b088c3
Added simple switches in auth-helpers.ts to
chriscarrollsmith Dec 4, 2023
0fefdac
Merge branch 'auth-helpers' into dev
chriscarrollsmith Dec 5, 2023
77fedf7
Refactor/streamline createOrRetrieveCustomer
chriscarrollsmith Dec 5, 2023
cc13e0f
getURL now takes a path argument and handles leading slashes
chriscarrollsmith Dec 6, 2023
23f5e24
Fixed bug that broke checkout session if trial period was not set
chriscarrollsmith Dec 6, 2023
72fc682
Cascade user deletion through database
chriscarrollsmith Dec 6, 2023
09bf861
Added full support for toast error handling when
chriscarrollsmith Dec 7, 2023
98e54c4
repaired/improved Stripe webhook error handling
chriscarrollsmith Dec 7, 2023
bb651b7
- Added Suspense around Toaster per NextJS docs on `useSearchParams`
chriscarrollsmith Dec 7, 2023
fdae906
Added package.json npm command for stripe fixtures
chriscarrollsmith Dec 7, 2023
9834e4b
Fixed mishandled magic link condition and handled default sign-in vie…
chriscarrollsmith Dec 7, 2023
1a8b046
Handled edge case where user's preferredSignInView
chriscarrollsmith Dec 7, 2023
55ddbe9
- gitignored some local dev files
chriscarrollsmith Dec 10, 2023
8994d0d
- gitignored some local dev files
chriscarrollsmith Dec 10, 2023
23c8515
Enhanced control of routing and redirects
chriscarrollsmith Dec 11, 2023
6c9b5cb
Deleted defunct Card components
chriscarrollsmith Dec 11, 2023
a94034a
Toaster passes through additional searchParams\nDisable button after …
chriscarrollsmith Dec 11, 2023
1788514
Separated server and client Navbar components
chriscarrollsmith Dec 12, 2023
aea6e6e
Simplified sign-in redirect
chriscarrollsmith Dec 12, 2023
ab11095
Sorted stripe helpers into client and server files
chriscarrollsmith Dec 13, 2023
e7273cf
gitignore vscode workspace settings
chriscarrollsmith Dec 13, 2023
2091adb
Merge branch 'pr278' into dev
chriscarrollsmith Dec 13, 2023
1dc1b60
Replaced stripe checkout API with server action
chriscarrollsmith Dec 14, 2023
784eb64
Replace Stripe portal API with server action
chriscarrollsmith Dec 14, 2023
3482f3e
Update middleware.ts
dalkommatt Dec 15, 2023
7d30272
Delete next.config.js
dalkommatt Dec 15, 2023
ca20608
Merge pull request #8 from dalkommatt/nextjs-14-supabase-ssr
chriscarrollsmith Dec 16, 2023
9743f1f
Disable buttons while submitting, fix password reset bug
chriscarrollsmith Dec 17, 2023
1fd8124
Merge branch 'pr278' of https://github.com/chriscarrollsmith/nextjs-s…
chriscarrollsmith Dec 17, 2023
22cc94e
- Separate client/server account functions
chriscarrollsmith Dec 26, 2023
b599f98
Fixed missing leading slash in error redirect
chriscarrollsmith Dec 27, 2023
5bec024
bump packages
dalkommatt Dec 27, 2023
8848e8f
Add display index to Stripe fixtures
chriscarrollsmith Dec 27, 2023
3fc8666
Redirect to home page on successful signup
chriscarrollsmith Dec 27, 2023
f2f553c
Set default trial period with a variable
chriscarrollsmith Dec 27, 2023
80d9a6b
Updated dependencies
chriscarrollsmith Dec 27, 2023
87a35ba
Universally implement loading dots
chriscarrollsmith Jan 5, 2024
edc045a
Handle database migrations appropriately
chriscarrollsmith Jan 5, 2024
8df1d3f
Added Supabase local development workflow
chriscarrollsmith Jan 5, 2024
50199e0
Script to link to supabase remote
chriscarrollsmith Jan 5, 2024
f358328
Fixed some bugs in link implementation
chriscarrollsmith Jan 5, 2024
b2461f0
Removed accidental redundant file
chriscarrollsmith Jan 5, 2024
a10edf6
Enable buttons when user is not logged in
chriscarrollsmith Jan 6, 2024
5acd31f
Fixed supabase:migrate command
chriscarrollsmith Jan 15, 2024
2a6756c
Fixed AuthApiError that arises in testing
chriscarrollsmith Feb 4, 2024
2bbb6ff
Documented how to develop locally with Supabase
chriscarrollsmith Feb 4, 2024
b8781e8
bump again
dalkommatt Feb 5, 2024
ac51cb1
Ignore svg routes in middleware pattern matcher
chriscarrollsmith Feb 5, 2024
d86e4c4
update dependencies, use turbo for local dev
chriscarrollsmith Feb 5, 2024
c523be3
Correctly export Next Metadata
chriscarrollsmith Feb 5, 2024
2976b13
Merge branch 'pr/2' into nextjs-14-supabase-ssr
dalkommatt Feb 8, 2024
8218435
fix regex on middleware matcher
dijonmusters Feb 8, 2024
91d75e5
fix case on Next.js
dijonmusters Feb 8, 2024
4298e43
refactor cookies to helpers
dijonmusters Feb 8, 2024
33e2794
add prettier script
dijonmusters Feb 8, 2024
f0af873
Merge pull request #4 from dijonmusters/other-fixes-from-pr-review
dalkommatt Feb 8, 2024
8879c19
cleanup and bump
dalkommatt Feb 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions .env.local.example
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
# Update these with your Supabase details from your project settings > API
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
# Update these with the details of your local test instance from the Supabase CLI
# ("API URL", "anon key", and "service_role key")
NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=

# Update these with the details of your remote database
SUPABASE_PROJECT_REF=
SUPABASE_DB_PASSWORD=

# Update these with your Stripe credentials from https://dashboard.stripe.com/apikeys
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_1234
STRIPE_SECRET_KEY=sk_test_1234
STRIPE_WEBHOOK_SECRET=whsec_1234

# Update this with your stable site URL only for the production environment.
NEXT_PUBLIC_SITE_URL=https://your-deployment-url.vercel.app
# Add NEXT_PUBLIC_SITE_URL to your Vercel environmental variables for the production environment following the example below. This defaults to http://localhost:3000 in development.
# NEXT_PUBLIC_SITE_URL=https://your-deployment-url.vercel.app
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ yarn-error.log*

# editors
.vscode

# certificates
certificates
61 changes: 49 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ When deploying this template, the sequence of steps is important. Follow the ste

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnextjs-subscription-payments&env=NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,STRIPE_SECRET_KEY&envDescription=Enter%20your%20Stripe%20API%20keys.&envLink=https%3A%2F%2Fdashboard.stripe.com%2Fapikeys&project-name=nextjs-subscription-payments&repository-name=nextjs-subscription-payments&integration-ids=oac_VqOgBHqhEoFTPzGkPd7L0iH6&external-id=https%3A%2F%2Fgithub.com%2Fvercel%2Fnextjs-subscription-payments%2Ftree%2Fmain)

The Vercel Deployment will create a new repository with this template on your GitHub account and guide your through a new Supabase project creation. The [Supabase Vercel Deploy Integration](https://vercel.com/integrations/supabase-v2) will set up the necessary Supabase environment variables and run the [SQL migrations](./supabase/migrations/20230530034630_init.sql) to set up the Database schema on your account. You can inspect the created tables in your project's [Table editor](https://app.supabase.com/project/_/editor).
The Vercel Deployment will create a new repository with this template on your GitHub account and guide you through a new Supabase project creation. The [Supabase Vercel Deploy Integration](https://vercel.com/integrations/supabase-v2) will set up the necessary Supabase environment variables and run the [SQL migrations](./supabase/migrations/20230530034630_init.sql) to set up the Database schema on your account. You can inspect the created tables in your project's [Table editor](https://app.supabase.com/project/_/editor).

Should the automatic setup fail, please [create a Supabase account](https://app.supabase.com/projects), and a new project if needed. In your project, navigate to the [SQL editor](https://app.supabase.com/project/_/sql) and select the "Stripe Subscriptions" starter template from the Quick start section.

### Configure Auth

Follow [this guide](https://supabase.com/docs/guides/auth/social-login/auth-github) to set up an OAuth app with GitHub and configure Supabase to use it as an auth provider.

In your Supabase project, navigate to [auth > URL configuration](https://app.supabase.com/project/_/auth/url-configuration) and set your main production URL (e.g. https://your-deployment-url.vercel.app) as the site url.

Next, in your Vercel deployment settings, add a new **Production** environment variable called `NEXT_PUBLIC_SITE_URL` and set it to the same URL. Make sure to deselect preview and development environments to make sure that preview branches and local development work correctly.
Expand All @@ -45,9 +47,9 @@ If you've deployed this template via the "Deploy to Vercel" button above, you ca

Otherwise, for auth redirects (email confirmations, magic links, OAuth providers) to work correctly in deploy previews, navigate to the [auth settings](https://app.supabase.com/project/_/auth/url-configuration) and add the following wildcard URL to "Redirect URLs": `https://*-username.vercel.app/**`. You can read more about redirect wildcard patterns in the [docs](https://supabase.com/docs/guides/auth#redirect-urls-and-wildcards).

#### [Optional] - Set up OAuth providers
If you've deployed this template via the "Deploy to Vercel" button above, you can skip this step. The Supabase Vercel Integration will have run database migrations for you. You can check this by going to [the Table Editor for your Supabase project](https://supabase.com/dashboard/project/_/editor), and confirming there are tables with seed data.

You can use third-party login providers like GitHub or Google. Refer to the [docs](https://supabase.io/docs/guides/auth#third-party-logins) to learn how to configure these. Once configured, you can add them to the `provider` array of the [`Auth` component](./app/signin/AuthUI.tsx) page.
Otherwise, navigate to the [SQL Editor](https://supabase.com/dashboard/project/_/sql/new), paste the contents of [the Supabase `schema.sql` file](./schema.sql), and click RUN to initialize the database.

#### [Maybe Optional] - Set up Supabase environment variables (not needed if you installed via the Deploy Button)

Expand Down Expand Up @@ -122,15 +124,51 @@ vercel login
vercel link
```

### Setting up the env vars locally

Use the Vercel CLI to download the development env vars:
If you don't intend to use a local Supabase instance for development and testing, you can use the Vercel CLI to download the development env vars:

```bash
vercel env pull .env.local
```

Running this command will create a new `.env.local` file in your project folder. For security purposes, you will need to set the `SUPABASE_SERVICE_ROLE_KEY` manually from your [Supabase dashboard](https://app.supabase.io/) (`Settings > API`).
Running this command will create a new `.env.local` file in your project folder. For security purposes, you will need to set the `SUPABASE_SERVICE_ROLE_KEY` manually from your [Supabase dashboard](https://app.supabase.io/) (`Settings > API`). If you are not using a local Supabase instance, you should also change the `--local` flag to `--remote` in the `supabase:generate-types` script in `package.json`.

### Local development with Supabase

It's highly recommended to use a local Supabase instance for development and testing. We have provided a set of custom commands for this in `package.json`.

First, you will need to install [Docker](https://www.docker.com/get-started/). You should also copy `.env.local.example` to `.env.local`.

Next, run the following command to start a local Supabase instance and run the migrations to set up the database schema:

```bash
# or `npm` or `yarn` instead of `pnpm`
pnpm run supabase:start
```

The terminal output will provide you with values for the environment variables `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, and `SUPABASE_SERVICE_ROLE_KEY`. Copy these into your `.env.local` file.

The terminal output will also provide you with a URL to access the local Supabase Studio, where you can make changes to your local database instance. (You can always find the Supabase Studio later by opening up a Docker window, navigating to `Containers` tab, and clicking the link in the `Ports` column for the corresponding container.)

To link your local Supabase instance to your project, you will need to set `SUPABASE_PROJECT_REF` and `SUPABASE_DB_PASSWORD` for your remote database in your `.env.local` file. You can find these values in the [Supabase dashboard](https://supabase.com/dashboard/projects) for your project. Then, run the following command to link your local Supabase instance to your project:

```bash
pnpm run supabase:link
```

Once you've linked your project, you can make changes to the database schema in your local Supabase Studio and run the following command to generate TypeScript types to match your schema:

```bash
pnpm run supabase:generate-types
```

You can also automatically generate a migration file with all the changes you've made to your local database schema and then push the migration to your remote database with the following commands:

```bash
pnpm run supabase:generate-migration
pnpm run supabase:migrate
```

Remember to test your changes thoroughly in your local environment before deploying them to production!

### Use the Stripe CLI to test webhooks

Expand All @@ -142,13 +180,16 @@ Next, start local webhook forwarding:
stripe listen --forward-to=localhost:3000/api/webhooks
```

Running this Stripe command will print a webhook secret (such as, `whsec_***`) to the console. Set `STRIPE_WEBHOOK_SECRET` to this value in your `.env.local` file.
Running this Stripe command will print a webhook secret (such as, `whsec_***`) to the console. Set `STRIPE_WEBHOOK_SECRET` to this value in your `.env.local` file. If you haven't already, you should also set `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` and `STRIPE_SECRET_KEY` in your `.env.local` file using the **test mode**(!) keys from your Stripe dashboard.

### Install dependencies and run the Next.js client

In a separate terminal, run the following commands to install dependencies and start the development server:

```bash
pnpm install
pnpm run dev
# or
npm install
npm run dev
# or
Expand All @@ -175,7 +216,3 @@ To run the project in live mode and process payments with Stripe, switch Stripe
Afterward, you will need to rebuild your production deployment for the changes to take effect. Within your project Dashboard, navigate to the "Deployments" tab, select the most recent deployment, click the overflow menu button (next to the "Visit" button) and select "Redeploy" (do NOT enable the "Use existing Build Cache" option).

To verify you are running in production mode, test checking out with the [Stripe test card](https://stripe.com/docs/testing). The test card should not work.

## A note on reliability

This template mirrors completed Stripe transactions to the Supabase database. This means that if the Supabase database is unavailable, the Stripe transaction will still succeed, but the Supabase database will not be updated, and the application will pass an error code back to Stripe. [By default](https://stripe.com/docs/webhooks/best-practices), Stripe will retry sending its response to the webhook for up to three days, or until the database update succeeds. This means that the Stripe transaction will eventually be reflected in the Supabase database as long as the database comes back online within three days. You may want to implement a process to automatically reconcile the Supabase database with Stripe in case of a prolonged outage.
38 changes: 0 additions & 38 deletions app/account/ManageSubscriptionButton.tsx

This file was deleted.

193 changes: 32 additions & 161 deletions app/account/page.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,39 @@
import ManageSubscriptionButton from './ManageSubscriptionButton';
import {
getSession,
getUserDetails,
getSubscription
} from '@/app/supabase-server';
import Button from '@/components/ui/Button';
import { Database } from '@/types_db';
import { createServerActionClient } from '@supabase/auth-helpers-nextjs';
import { revalidatePath } from 'next/cache';
'use server';

import CustomerPortalForm from '@/components/ui/AccountForms/CustomerPortalForm';
import EmailForm from '@/components/ui/AccountForms/EmailForm';
import NameForm from '@/components/ui/AccountForms/NameForm';
import { createClient } from '@/utils/supabase/server';
import { cookies } from 'next/headers';
import Link from 'next/link';
import { redirect } from 'next/navigation';
import { ReactNode } from 'react';

export default async function Account() {
const [session, userDetails, subscription] = await Promise.all([
getSession(),
getUserDetails(),
getSubscription()
]);

const user = session?.user;
const cookieStore = cookies();
const supabase = createClient(cookieStore);

const {
data: { user }
} = await supabase.auth.getUser();

const { data: userDetails } = await supabase
.from('users')
.select('*')
.single();

const { data: subscription, error } = await supabase
.from('subscriptions')
.select('*, prices(*, products(*))')
.in('status', ['trialing', 'active'])
.maybeSingle();

if (error) {
console.log(error);
}

if (!session) {
if (!user) {
return redirect('/signin');
}

const subscriptionPrice =
subscription &&
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: subscription?.prices?.currency!,
minimumFractionDigits: 0
}).format((subscription?.prices?.unit_amount || 0) / 100);

const updateName = async (formData: FormData) => {
'use server';

const newName = formData.get('name') as string;
const supabase = createServerActionClient<Database>({ cookies });
const session = await getSession();
const user = session?.user;
const { error } = await supabase
.from('users')
.update({ full_name: newName })
.eq('id', user?.id);
if (error) {
console.log(error);
}
revalidatePath('/account');
};

const updateEmail = async (formData: FormData) => {
'use server';

const newEmail = formData.get('email') as string;
const supabase = createServerActionClient<Database>({ cookies });
const { error } = await supabase.auth.updateUser({ email: newEmail });
if (error) {
console.log(error);
}
revalidatePath('/account');
};

return (
<section className="mb-32 bg-black">
<div className="max-w-6xl px-4 py-8 mx-auto sm:px-6 sm:pt-24 lg:px-8">
Expand All @@ -76,110 +47,10 @@ export default async function Account() {
</div>
</div>
<div className="p-4">
<Card
title="Your Plan"
description={
subscription
? `You are currently on the ${subscription?.prices?.products?.name} plan.`
: 'You are not currently subscribed to any plan.'
}
footer={<ManageSubscriptionButton session={session} />}
>
<div className="mt-8 mb-4 text-xl font-semibold">
{subscription ? (
`${subscriptionPrice}/${subscription?.prices?.interval}`
) : (
<Link href="/">Choose your plan</Link>
)}
</div>
</Card>
<Card
title="Your Name"
description="Please enter your full name, or a display name you are comfortable with."
footer={
<div className="flex flex-col items-start justify-between sm:flex-row sm:items-center">
<p className="pb-4 sm:pb-0">64 characters maximum</p>
<Button
variant="slim"
type="submit"
form="nameForm"
disabled={true}
>
{/* WARNING - In Next.js 13.4.x server actions are in alpha and should not be used in production code! */}
Update Name
</Button>
</div>
}
>
<div className="mt-8 mb-4 text-xl font-semibold">
<form id="nameForm" action={updateName}>
<input
type="text"
name="name"
className="w-1/2 p-3 rounded-md bg-zinc-800"
defaultValue={userDetails?.full_name ?? ''}
placeholder="Your name"
maxLength={64}
/>
</form>
</div>
</Card>
<Card
title="Your Email"
description="Please enter the email address you want to use to login."
footer={
<div className="flex flex-col items-start justify-between sm:flex-row sm:items-center">
<p className="pb-4 sm:pb-0">
We will email you to verify the change.
</p>
<Button
variant="slim"
type="submit"
form="emailForm"
disabled={true}
>
{/* WARNING - In Next.js 13.4.x server actions are in alpha and should not be used in production code! */}
Update Email
</Button>
</div>
}
>
<div className="mt-8 mb-4 text-xl font-semibold">
<form id="emailForm" action={updateEmail}>
<input
type="text"
name="email"
className="w-1/2 p-3 rounded-md bg-zinc-800"
defaultValue={user ? user.email : ''}
placeholder="Your email"
maxLength={64}
/>
</form>
</div>
</Card>
<CustomerPortalForm subscription={subscription} />
<NameForm userName={userDetails?.full_name ?? ''} />
<EmailForm userEmail={user.email} />
</div>
</section>
);
}

interface Props {
title: string;
description?: string;
footer?: ReactNode;
children: ReactNode;
}

function Card({ title, description, footer, children }: Props) {
return (
<div className="w-full max-w-3xl m-auto my-8 border rounded-md p border-zinc-700">
<div className="px-5 py-4">
<h3 className="mb-1 text-2xl font-medium">{title}</h3>
<p className="text-zinc-300">{description}</p>
{children}
</div>
<div className="p-4 border-t rounded-b-md border-zinc-700 bg-zinc-900 text-zinc-500">
{footer}
</div>
</div>
);
}
};
Loading