Skip to content

Commit

Permalink
Added login with email, google and log out
Browse files Browse the repository at this point in the history
  • Loading branch information
Dosbodoke committed Sep 2, 2023
1 parent 36dd1b3 commit 18ac960
Show file tree
Hide file tree
Showing 26 changed files with 971 additions and 89 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
DEV_GOOGLE_CLIENT_ID= # Get it from Google Cloud APIs & Services pages on the Credentials tab
DEV_GOOGLE_CLIENT_SECRET= # Get it from Google Cloud APIs & Services pages on the Credentials tab
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ yarn-debug.log*
yarn-error.log*

# local env files
.env
.env*.local

# vercel
Expand Down
34 changes: 15 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,30 @@
# CHOOSELIFE

This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started
## Local development

First, setup the `.env` file. See [How to setup Google oAuth](#how-to-setup-google-oauth).

First, run the development server:
Then, run the development server:

```bash
npm run dev
# or
yarn install
npx supabase start
yarn dev
# or
pnpm dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:
### How to setup Google oAuth

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can follow [this tutorial](https://docs.retool.com/data-sources/guides/authentication/google-oauth) to get your Google oAuth credentials.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
For local development:

## Deploy on Vercel
- Add `http://localhost:3000` to **Authorized JavaScript origins**
- Add `http://localhost:54321/auth/v1/callback` to **Authorized redirect URIs**

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
For prod:

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
- Add `https://${SUPABASE_PROJECT_ID}.supabase.co/auth/v1/callback` to **Authorized redirect URIs**
5 changes: 3 additions & 2 deletions app/[locale]/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

import type { ReactNode } from "react";
import { ThemeProvider } from "next-themes";
import { type AbstractIntlMessages, NextIntlClientProvider } from "next-intl";
import { NextIntlClientProvider, type AbstractIntlMessages } from "next-intl";

import { ReactQueryProvider } from "@/components/ReactQueryProvider";

interface Props {
locale: "en" | "pt";
messages: AbstractIntlMessages;
messages: AbstractIntlMessages | undefined;
children: ReactNode;
}

function Providers({ locale, messages, children }: Props) {
return (
<ReactQueryProvider>
Expand Down
27 changes: 27 additions & 0 deletions app/[locale]/auth/callback/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Database } from "@/utils/database.types";
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

export const dynamic = "force-dynamic";

export async function GET(request: Request) {
// The `/auth/callback` route is required for the server-side auth flow implemented
// by the Auth Helpers package. It exchanges an auth code for the user's session.
// https://supabase.com/docs/guides/auth/auth-helpers/nextjs#managing-sign-in-with-code-exchange
const requestUrl = new URL(request.url);
const code = requestUrl.searchParams.get("code");

if (code) {
const supabase = createRouteHandlerClient<Database>({ cookies });
const { data, error } = await supabase.auth.exchangeCodeForSession(code);

// Redirect to profile page so the user can update it
if (!data.user?.user_metadata.age) {
return NextResponse.redirect(requestUrl.origin + "/profile");
}
}

// URL to redirect to after sign in process completes
return NextResponse.redirect(requestUrl.origin);
}
23 changes: 11 additions & 12 deletions app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import "./globals.css";
import { Inter } from "next/font/google";
import { Analytics } from "@vercel/analytics/react";
import { notFound } from "next/navigation";
import { useLocale, useMessages } from "next-intl";

import Providers from "./Providers";
import Footer from "@/components/Footer";
Expand All @@ -14,24 +15,21 @@ export const metadata = {
description: "Site oficial do festival Chooselife",
};

export function generateStaticParams() {
return [{ locale: "en" }, { locale: "pt" }];
}

export default async function RootLayout({
export default function RootLayout({
children,
params: { locale },
params,
}: {
children: React.ReactNode;
params: { locale: "en" | "pt" };
}) {
let messages;
try {
messages = (await import(`../../messages/${locale}.json`)).default;
} catch (error) {
const locale = useLocale();
// Show a 404 error if the user requests an unknown locale
if (params.locale !== locale) {
notFound();
}

const messages = useMessages();

return (
// suppressHydrationWarning because of `next-themes`
// refer to https://github.com/pacocoursey/next-themes#with-app
Expand All @@ -41,11 +39,12 @@ export default async function RootLayout({
style={inter.style}
>
<Providers locale={locale} messages={messages}>
<>
<main className="flex flex-col">
{/* @ts-expect-error Server Component */}
<NavBar />
{children}
<Footer />
</>
</main>
</Providers>
<Analytics />
</body>
Expand Down
31 changes: 31 additions & 0 deletions app/[locale]/login/_components/google-oauth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"use client";

import { createClientComponentClient } from "@supabase/auth-helpers-nextjs";

import type { Database } from "@/utils/database.types";

import { GoogleIcon } from "@/assets";
import Button from "@/components/ui/Button";

export default function GoogleAuthLogin() {
const supabase = createClientComponentClient<Database>();

async function handleSignInWithGoogle() {
await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
});
}

return (
<Button
label="Sign in with Google"
icon={<GoogleIcon className="-ml-1 mr-2 h-5 w-5" />}
variant="outlined"
color="secondary"
onClick={handleSignInWithGoogle}
/>
);
}
103 changes: 103 additions & 0 deletions app/[locale]/login/_components/login-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"use client";

import { createClientComponentClient } from "@supabase/auth-helpers-nextjs";
import { useRouter } from "next/navigation";

import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
} from "@/components/ui/Form";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Input } from "@/components/ui/Input";
import Button from "@/components/ui/Button";
import Checkbox from "@/components/ui/Checkbox";

const loginFormSchema = z.object({
email: z.string().email(),
password: z.string(),
});

type LoginFormSchema = z.infer<typeof loginFormSchema>;

export default function LoginForm() {
const router = useRouter();
const supabase = createClientComponentClient();

const form = useForm<LoginFormSchema>({
resolver: zodResolver(loginFormSchema),
defaultValues: {
email: "",
password: "",
},
});

async function onSubmit(data: LoginFormSchema) {
const { error } = await supabase.auth.signInWithPassword({
email: data.email,
password: data.password,
});
if (!error) router.refresh();
// TODO: Check if there is a profile with this email and tell the user to Log In with Google
}

return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-4 md:space-y-6"
>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder={"Your email"} {...field} />
</FormControl>
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
type="password"
placeholder={"Your password"}
{...field}
/>
</FormControl>
</FormItem>
)}
/>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Checkbox id="remember" />
<label
htmlFor="remember"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Remember me
</label>
</div>
<a
href="#"
className="text-sm font-medium text-blue-600 hover:underline dark:text-blue-500"
>
Forgot passoword?
</a>
</div>
<Button type="submit" label="Log In" />
</form>
</Form>
);
}
57 changes: 57 additions & 0 deletions app/[locale]/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Link from "next/link";
import { Metadata } from "next";
import { createServerComponentClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";

import type { Database } from "@/utils/database.types";
import LoginForm from "./_components/login-form";
import GoogleAuthLogin from "./_components/google-oauth";

export const metadata: Metadata = {
title: "Login",
description: "Login to your account",
};

export default async function Login() {
const cookieStore = cookies();
const supabase = createServerComponentClient<Database>({
cookies: () => cookieStore,
});

const {
data: { session },
} = await supabase.auth.getSession();

if (session) {
redirect("/");
}

return (
<section className="mx-auto flex h-auto w-full max-w-xl flex-col items-center justify-center px-6 py-8 lg:py-0">
<div className="w-full rounded-lg bg-white shadow-xl dark:border dark:border-gray-700 dark:bg-gray-800 sm:max-w-md md:mt-0 xl:p-0">
<div className="space-y-4 p-6 sm:p-8 md:space-y-6">
<h1 className="text-xl font-bold leading-tight tracking-tight text-gray-900 dark:text-white md:text-2xl">
Sign in to your account
</h1>
<GoogleAuthLogin />
<div className="my-4 flex items-center before:mt-0.5 before:flex-1 before:border-t before:border-gray-600 after:mt-0.5 after:flex-1 after:border-t after:border-gray-600">
<p className="mx-4 mb-0 text-center font-semibold dark:text-gray-500">
or
</p>
</div>
<LoginForm />
<p className="text-sm font-light text-gray-500 dark:text-gray-400">
Don’t have an account yet?{" "}
<Link
href={"/sign-up"}
className="font-medium text-blue-600 hover:underline dark:text-blue-500"
>
Sign up
</Link>
</p>
</div>
</div>
</section>
);
}
14 changes: 14 additions & 0 deletions app/[locale]/profile/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Metadata } from "next";

export const metadata: Metadata = {
title: "Profile",
description: "Your profile",
};

export default function Profile() {
return (
<div className="container mx-auto mt-4 space-y-12">
<h1>Profile</h1>
</div>
);
}
Loading

0 comments on commit 18ac960

Please sign in to comment.