Skip to content

Instantly share code, notes, and snippets.

@mastoj
Created May 5, 2024 20:20
Show Gist options
  • Save mastoj/037d1f4831c0ee31d777edac09f8d8d8 to your computer and use it in GitHub Desktop.
Save mastoj/037d1f4831c0ee31d777edac09f8d8d8 to your computer and use it in GitHub Desktop.
"use server";
import { createClient } from "@/lib/supabase/server";
import { revalidatePath } from "next/cache";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
export async function login(formData: FormData) {
const supabase = createClient();
// type-casting here for convenience
// in practice, you should validate your inputs
const data = {
email: formData.get("email") as string,
password: formData.get("password") as string,
};
const { error } = await supabase.auth.signInWithPassword(data);
if (error) {
redirect("/error");
}
revalidatePath("/", "layout");
redirect("/");
}
export async function signInWithGoogle() {
console.log("==> Signing in with Google");
const supabase = createClient();
const origin = headers().get("origin");
const { data, error } = await supabase.auth.signInWithOAuth({
provider: "google",
options: {
queryParams: {
access_type: "offline",
prompt: "consent",
},
redirectTo: new URL("/auth/callback", origin!).toString(),
},
});
console.log("==> Data: ", data, error, new URL("/auth/callback", origin!));
if (error) {
redirect("/error");
}
redirect(data.url);
}
const getURL = () => {
let url =
process?.env?.NEXT_PUBLIC_SITE_URL ?? // Set this to your site URL in production env.
process?.env?.NEXT_PUBLIC_VERCEL_URL ?? // Automatically set by Vercel.
"http://localhost:3000/";
// Make sure to include `https://` when not localhost.
url = url.includes("http") ? url : `https://${url}`;
// Make sure to include a trailing `/`.
url = url.charAt(url.length - 1) === "/" ? url : `${url}/`;
return url;
};
export async function signup(formData: FormData) {
const supabase = createClient();
// type-casting here for convenience
// in practice, you should validate your inputs
const data = {
email: formData.get("email") as string,
password: formData.get("password") as string,
};
const firstName = formData.get("firstName") as string;
const lastName = formData.get("lastName") as string;
const emailRedirectTo = getURL();
console.log(
"==> Signing up with data: ",
firstName,
lastName,
emailRedirectTo
);
const { error, data: signUpResponse } = await supabase.auth.signUp({
...data,
options: {
data: {
first_name: firstName,
last_name: lastName,
},
emailRedirectTo: emailRedirectTo,
},
});
console.log("==> Sign up response: ", error, signUpResponse);
if (error) {
console.error("=> Error signing up", error.message);
redirect("/error");
}
revalidatePath("/", "layout");
redirect("/confirmation");
}
import { createClient } from "@/lib/supabase/server";
import { type EmailOtpType } from "@supabase/supabase-js";
import { NextResponse, type NextRequest } from "next/server";
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const token_hash = searchParams.get("token_hash");
const type = searchParams.get("type") as EmailOtpType | null;
const next = searchParams.get("next") ?? "/";
const redirectTo = request.nextUrl.clone();
redirectTo.pathname = next;
redirectTo.searchParams.delete("token_hash");
redirectTo.searchParams.delete("type");
if (token_hash && type) {
const supabase = createClient();
const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
});
if (!error) {
redirectTo.searchParams.delete("next");
return NextResponse.redirect(redirectTo);
}
}
// return the user to an error page with some instructions
redirectTo.pathname = "/error";
return NextResponse.redirect(redirectTo);
}
import { login, signInWithGoogle } from "@/domains/auth/actions";
import Link from "next/link";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import { Label } from "./ui/label";
type Props = {};
const LoginForm = async () => {
return (
<form action={login}>
<div className="grid gap-2 text-center">
<h1 className="text-3xl font-bold">Login</h1>
<p className="text-balance text-muted-foreground">
Enter your email below to login to your account
</p>
</div>
<div className="grid gap-4">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
name="email"
placeholder="m@example.com"
required
type="email"
/>
</div>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password">Password</Label>
<Link className="ml-auto inline-block text-sm underline" href="#">
Forgot your password?
</Link>
</div>
<Input id="password" name="password" required type="password" />
</div>
<Button className="w-full" type="submit">
Login
</Button>
</div>
</form>
);
};
const Login = async (props: Props) => {
return (
<div className="flex items-center justify-center py-12 h-screen overflow-y-auto">
<div className="mx-auto grid w-[350px] gap-6">
<LoginForm />
<form>
<Button
className="w-full"
variant="outline"
formAction={signInWithGoogle}
>
Log in with Google
</Button>
</form>
<div className="mt-4 text-center text-sm">
Don't have an account?
<Link className="underline" href="/signup">
Sign up
</Link>
</div>
</div>
</div>
);
};
export default Login;
import Login from "@/components/login";
type Props = {};
const Page = (props: Props) => {
return <Login />;
};
export default Page;
import { NextResponse, type NextRequest } from "next/server";
import { updateSession } from "./lib/supabase/middleware";
export async function middleware(request: NextRequest) {
const response =
request.nextUrl.pathname === "/"
? NextResponse.rewrite(new URL("/dashboard", request.url))
: NextResponse.next({
request: {
headers: request.headers,
},
});
const user = await updateSession(request, response);
if (request.nextUrl.pathname === "/" && !user) {
return NextResponse.rewrite(new URL("/home", request.url));
}
return response;
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
"/((?!api|_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
],
};
import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { cookies } from "next/headers";
export function createClient() {
const cookieStore = cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value;
},
set(name: string, value: string, options: CookieOptions) {
try {
cookieStore.set({ name, value, ...options });
} catch (error) {
// The `set` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
remove(name: string, options: CookieOptions) {
try {
cookieStore.set({ name, value: "", ...options });
} catch (error) {
// The `delete` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
},
}
);
}
import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { NextResponse, type NextRequest } from "next/server";
export const updateSession = async (
request: NextRequest,
response: NextResponse
) => {
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return request.cookies.get(name)?.value;
},
set(name: string, value: string, options: CookieOptions) {
// If the cookie is updated, update the cookies for the request and response
request.cookies.set({
name,
value,
...options,
});
response = NextResponse.next({
request: {
headers: request.headers,
},
});
response.cookies.set({
name,
value,
...options,
});
},
remove(name: string, options: CookieOptions) {
// If the cookie is removed, update the cookies for the request and response
request.cookies.set({
name,
value: "",
...options,
});
response = NextResponse.next({
request: {
headers: request.headers,
},
});
response.cookies.set({
name,
value: "",
...options,
});
},
},
}
);
// This will refresh session if expired - required for Server Components
// https://supabase.com/docs/guides/auth/server-side/nextjs
const userResponse = await supabase.auth.getUser();
return userResponse.data.user;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment