-
Notifications
You must be signed in to change notification settings - Fork 26.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
examples: improve Supabase example (#69308)
Co-authored-by: Terry Sutton <saltcod@gmail.com> Co-authored-by: Lee Robinson <me@leerob.io>
- Loading branch information
1 parent
c1a7463
commit 00f70f2
Showing
50 changed files
with
1,270 additions
and
676 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ yarn-error.log* | |
|
||
# local env files | ||
.env*.local | ||
.env | ||
|
||
# vercel | ||
.vercel | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
examples/with-supabase/app/(auth-pages)/forgot-password/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { forgotPasswordAction } from "@/app/actions"; | ||
import { FormMessage, Message } from "@/components/form-message"; | ||
import { SubmitButton } from "@/components/submit-button"; | ||
import { Input } from "@/components/ui/input"; | ||
import { Label } from "@/components/ui/label"; | ||
import Link from "next/link"; | ||
import { SmtpMessage } from "../smtp-message"; | ||
|
||
export default function ForgotPassword({ | ||
searchParams, | ||
}: { | ||
searchParams: Message; | ||
}) { | ||
return ( | ||
<> | ||
<form className="flex-1 flex flex-col w-full gap-2 text-foreground [&>input]:mb-6 min-w-64 max-w-64 mx-auto"> | ||
<div> | ||
<h1 className="text-2xl font-medium">Reset Password</h1> | ||
<p className="text-sm text-secondary-foreground"> | ||
Already have an account?{" "} | ||
<Link className="text-primary underline" href="/sign-in"> | ||
Sign in | ||
</Link> | ||
</p> | ||
</div> | ||
<div className="flex flex-col gap-2 [&>input]:mb-3 mt-8"> | ||
<Label htmlFor="email">Email</Label> | ||
<Input name="email" placeholder="you@example.com" required /> | ||
<SubmitButton formAction={forgotPasswordAction}> | ||
Reset Password | ||
</SubmitButton> | ||
<FormMessage message={searchParams} /> | ||
</div> | ||
</form> | ||
<SmtpMessage /> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export default async function Layout({ | ||
children, | ||
}: { | ||
children: React.ReactNode; | ||
}) { | ||
return ( | ||
<div className="max-w-7xl flex flex-col gap-12 items-start">{children}</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { signInAction } from "@/app/actions"; | ||
import { FormMessage, Message } from "@/components/form-message"; | ||
import { SubmitButton } from "@/components/submit-button"; | ||
import { Input } from "@/components/ui/input"; | ||
import { Label } from "@/components/ui/label"; | ||
import Link from "next/link"; | ||
|
||
export default function Login({ searchParams }: { searchParams: Message }) { | ||
return ( | ||
<form className="flex-1 flex flex-col min-w-64"> | ||
<h1 className="text-2xl font-medium">Sign in</h1> | ||
<p className="text-sm text-foreground"> | ||
Don't have an account?{" "} | ||
<Link className="text-foreground font-medium underline" href="/sign-up"> | ||
Sign up | ||
</Link> | ||
</p> | ||
<div className="flex flex-col gap-2 [&>input]:mb-3 mt-8"> | ||
<Label htmlFor="email">Email</Label> | ||
<Input name="email" placeholder="you@example.com" required /> | ||
<div className="flex justify-between items-center"> | ||
<Label htmlFor="password">Password</Label> | ||
<Link | ||
className="text-xs text-foreground underline" | ||
href="/forgot-password" | ||
> | ||
Forgot Password? | ||
</Link> | ||
</div> | ||
<Input | ||
type="password" | ||
name="password" | ||
placeholder="Your password" | ||
required | ||
/> | ||
<SubmitButton pendingText="Signing In..." formAction={signInAction}> | ||
Sign in | ||
</SubmitButton> | ||
<FormMessage message={searchParams} /> | ||
</div> | ||
</form> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { signUpAction } from "@/app/actions"; | ||
import { FormMessage, Message } from "@/components/form-message"; | ||
import { SubmitButton } from "@/components/submit-button"; | ||
import { Input } from "@/components/ui/input"; | ||
import { Label } from "@/components/ui/label"; | ||
import Link from "next/link"; | ||
import { SmtpMessage } from "../smtp-message"; | ||
|
||
export default function Signup({ searchParams }: { searchParams: Message }) { | ||
if ("message" in searchParams) { | ||
return ( | ||
<div className="w-full flex-1 flex items-center h-screen sm:max-w-md justify-center gap-2 p-4"> | ||
<FormMessage message={searchParams} /> | ||
</div> | ||
); | ||
} | ||
|
||
return ( | ||
<> | ||
<form className="flex flex-col min-w-64 max-w-64 mx-auto"> | ||
<h1 className="text-2xl font-medium">Sign up</h1> | ||
<p className="text-sm text text-foreground"> | ||
Already have an account?{" "} | ||
<Link className="text-primary font-medium underline" href="/sign-in"> | ||
Sign in | ||
</Link> | ||
</p> | ||
<div className="flex flex-col gap-2 [&>input]:mb-3 mt-8"> | ||
<Label htmlFor="email">Email</Label> | ||
<Input name="email" placeholder="you@example.com" required /> | ||
<Label htmlFor="password">Password</Label> | ||
<Input | ||
type="password" | ||
name="password" | ||
placeholder="Your password" | ||
minLength={6} | ||
required | ||
/> | ||
<SubmitButton formAction={signUpAction} pendingText="Signing up..."> | ||
Sign up | ||
</SubmitButton> | ||
<FormMessage message={searchParams} /> | ||
</div> | ||
</form> | ||
<SmtpMessage /> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { ArrowUpRight, InfoIcon } from "lucide-react"; | ||
import Link from "next/link"; | ||
|
||
export function SmtpMessage() { | ||
return ( | ||
<div className="bg-muted/50 px-5 py-3 border rounded-md flex gap-4"> | ||
<InfoIcon size={16} className="mt-0.5" /> | ||
<div className="flex flex-col gap-1"> | ||
<small className="text-sm text-secondary-foreground"> | ||
<strong> Note:</strong> Emails are limited to 4 per hour. Enable a | ||
custom SMTP endpoint to increase the rate limit. | ||
</small> | ||
<div> | ||
<Link | ||
href="https://supabase.com/docs/guides/auth/auth-smtp" | ||
target="_blank" | ||
className="text-primary/50 hover:text-primary flex items-center text-sm gap-1" | ||
> | ||
Learn more <ArrowUpRight size={14} /> | ||
</Link> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
"use server"; | ||
|
||
import { encodedRedirect } from "@/utils/utils"; | ||
import { createClient } from "@/utils/supabase/server"; | ||
import { headers } from "next/headers"; | ||
import { redirect } from "next/navigation"; | ||
|
||
export const signUpAction = async (formData: FormData) => { | ||
const email = formData.get("email")?.toString(); | ||
const password = formData.get("password")?.toString(); | ||
const supabase = createClient(); | ||
const origin = headers().get("origin"); | ||
|
||
if (!email || !password) { | ||
return { error: "Email and password are required" }; | ||
} | ||
|
||
const { error } = await supabase.auth.signUp({ | ||
email, | ||
password, | ||
options: { | ||
emailRedirectTo: `${origin}/auth/callback`, | ||
}, | ||
}); | ||
|
||
if (error) { | ||
console.error(error.code + " " + error.message); | ||
return encodedRedirect("error", "/sign-up", error.message); | ||
} else { | ||
return encodedRedirect( | ||
"success", | ||
"/sign-up", | ||
"Thanks for signing up! Please check your email for a verification link.", | ||
); | ||
} | ||
}; | ||
|
||
export const signInAction = async (formData: FormData) => { | ||
const email = formData.get("email") as string; | ||
const password = formData.get("password") as string; | ||
const supabase = createClient(); | ||
|
||
const { error } = await supabase.auth.signInWithPassword({ | ||
email, | ||
password, | ||
}); | ||
|
||
if (error) { | ||
return encodedRedirect("error", "/sign-in", error.message); | ||
} | ||
|
||
return redirect("/protected"); | ||
}; | ||
|
||
export const forgotPasswordAction = async (formData: FormData) => { | ||
const email = formData.get("email")?.toString(); | ||
const supabase = createClient(); | ||
const origin = headers().get("origin"); | ||
const callbackUrl = formData.get("callbackUrl")?.toString(); | ||
|
||
if (!email) { | ||
return encodedRedirect("error", "/forgot-password", "Email is required"); | ||
} | ||
|
||
const { error } = await supabase.auth.resetPasswordForEmail(email, { | ||
redirectTo: `${origin}/auth/callback?redirect_to=/protected/reset-password`, | ||
}); | ||
|
||
if (error) { | ||
console.error(error.message); | ||
return encodedRedirect( | ||
"error", | ||
"/forgot-password", | ||
"Could not reset password", | ||
); | ||
} | ||
|
||
if (callbackUrl) { | ||
return redirect(callbackUrl); | ||
} | ||
|
||
return encodedRedirect( | ||
"success", | ||
"/forgot-password", | ||
"Check your email for a link to reset your password.", | ||
); | ||
}; | ||
|
||
export const resetPasswordAction = async (formData: FormData) => { | ||
const supabase = createClient(); | ||
|
||
const password = formData.get("password") as string; | ||
const confirmPassword = formData.get("confirmPassword") as string; | ||
|
||
if (!password || !confirmPassword) { | ||
encodedRedirect( | ||
"error", | ||
"/protected/reset-password", | ||
"Password and confirm password are required", | ||
); | ||
} | ||
|
||
if (password !== confirmPassword) { | ||
encodedRedirect( | ||
"error", | ||
"/protected/reset-password", | ||
"Passwords do not match", | ||
); | ||
} | ||
|
||
const { error } = await supabase.auth.updateUser({ | ||
password: password, | ||
}); | ||
|
||
if (error) { | ||
encodedRedirect( | ||
"error", | ||
"/protected/reset-password", | ||
"Password update failed", | ||
); | ||
} | ||
|
||
encodedRedirect("success", "/protected/reset-password", "Password updated"); | ||
}; | ||
|
||
export const signOutAction = async () => { | ||
const supabase = createClient(); | ||
await supabase.auth.signOut(); | ||
return redirect("/sign-in"); | ||
}; |
Oops, something went wrong.