WIP Custom auth handler / login

Forgot password is not working
This commit is contained in:
Michael
2021-08-05 21:12:12 +02:00
parent 56bc4f24a9
commit 67631d2ba8
11 changed files with 283 additions and 991 deletions

View File

@@ -6,18 +6,18 @@ CONFIGURE THE AUTH COMPONENT LINE 30
You can select your auth providers, or just keep the email/password. You can You can select your auth providers, or just keep the email/password. You can
check the providers available here: https://supabase.io/docs/guides/auth check the providers available here: https://supabase.io/docs/guides/auth
*/ */
import { Auth } from "@supabase/ui";
import Login from "./UI/Login";
import { supabase } from "utils/supabaseClient"; import { supabase } from "utils/supabaseClient";
import { useAuth } from "utils/Authcontext";
const Container = (props) => { const Container = (props) => {
const { user } = Auth.useUser(); const { user, signOut } = useAuth();
if (user) if (user)
return ( return (
<div className='w-80 md:w-96 order-first lg:order-last'> <div className='w-80 md:w-96 order-first lg:order-last'>
<p>Hello {user.email}! 👋 You are already logged in</p> <p>Hello {user.email}! 👋 You are already logged in</p>
<button <button className='btn btn-primary' onClick={() => signOut()}>
className='btn btn-primary'
onClick={() => props.supabaseClient.auth.signOut()}>
Sign out Sign out
</button> </button>
</div> </div>
@@ -26,19 +26,16 @@ const Container = (props) => {
}; };
const AuthComponent = () => { const AuthComponent = () => {
const { signUp, signIn, signOut, resetPassword } = useAuth();
return ( return (
<Auth.UserContextProvider supabaseClient={supabase}> <Container supabaseClient={supabase}>
<Container supabaseClient={supabase}> <Login
<Auth signUp={signUp}
supabaseClient={supabase} signIn={signIn}
providers={["google"]} signOut={signOut}
socialLayout='horizontal' resetPassword={resetPassword}
socialButtonSize='xlarge' />
socialColors={true} </Container>
className='p-5 bg-neutral-content rounded-md'
/>
</Container>
</Auth.UserContextProvider>
); );
}; };

View File

@@ -12,14 +12,36 @@ The images are in the public folder.
import "react-toastify/dist/ReactToastify.css"; import "react-toastify/dist/ReactToastify.css";
import { Auth } from "@supabase/ui";
import Footer from "./Footer"; import Footer from "./Footer";
import Head from "next/head"; import Head from "next/head";
import Nav from "./Nav"; import Nav from "./Nav";
import { ToastContainer } from "react-toastify"; import { ToastContainer } from "react-toastify";
import { supabase } from "utils/supabaseClient";
import { useAuth } from "utils/Authcontext";
import { useEffect } from "react";
const Layout = (props) => { const Layout = (props) => {
const { user } = Auth.useUser(); const { user, signOut } = useAuth();
useEffect(() => {
const { data: authListener } = supabase.auth.onAuthStateChange(
(event, session) => {
if ((event === "SIGNED_OUT") | (event === "SIGNED_IN")) {
fetch("/api/auth", {
method: "POST",
headers: new Headers({ "Content-Type": "application/json" }),
credentials: "same-origin",
body: JSON.stringify({ event, session }),
}).then((res) => res.json());
}
if (event === "USER_UPDATED") {
}
}
);
return () => {
authListener.unsubscribe();
};
}, []);
const toastStyle = { const toastStyle = {
//Style your toast elements here //Style your toast elements here
@@ -56,7 +78,7 @@ const Layout = (props) => {
<meta name='theme-color' content='#ffffff' /> <meta name='theme-color' content='#ffffff' />
</Head> </Head>
<div className='max-w-7xl flex flex-col min-h-screen mx-auto p-5'> <div className='max-w-7xl flex flex-col min-h-screen mx-auto p-5'>
<Nav user={user} /> <Nav user={user} signOut={signOut} />
<main className='flex-1'>{props.children}</main> <main className='flex-1'>{props.children}</main>
<ToastContainer <ToastContainer
position='bottom-center' position='bottom-center'

View File

@@ -6,30 +6,8 @@ import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import Logo from "public/logo.svg"; import Logo from "public/logo.svg";
import { Menu } from "react-feather"; import { Menu } from "react-feather";
import { supabase } from "utils/supabaseClient";
import { useEffect } from "react";
const Nav = (props) => { const Nav = (props) => {
useEffect(() => {
const { data: authListener } = supabase.auth.onAuthStateChange(
(event, session) => {
if ((event === "SIGNED_OUT") | (event === "SIGNED_IN")) {
fetch("/api/auth", {
method: "POST",
headers: new Headers({ "Content-Type": "application/json" }),
credentials: "same-origin",
body: JSON.stringify({ event, session }),
}).then((res) => res.json());
}
if (event === "USER_UPDATED") {
}
}
);
return () => {
authListener.unsubscribe();
};
}, []);
//Modify you menu directly here //Modify you menu directly here
const NavMenu = ( const NavMenu = (
<div className='flex flex-col lg:flex-row lg:space-x-10 lg:m-auto font-body text-sm'> <div className='flex flex-col lg:flex-row lg:space-x-10 lg:m-auto font-body text-sm'>
@@ -50,7 +28,7 @@ const Nav = (props) => {
{props.user ? ( {props.user ? (
<button <button
className='btn btn-ghost btn-sm' className='btn btn-ghost btn-sm'
onClick={() => supabase.auth.signOut()}> onClick={() => props.signOut()}>
Logout Logout
</button> </button>
) : ( ) : (

View File

@@ -9,15 +9,15 @@ https://dashboard.stripe.com/test/settings/billing/portal
import { getSub, supabase } from "utils/supabaseClient"; import { getSub, supabase } from "utils/supabaseClient";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Auth } from "@supabase/ui";
import { Prices } from "utils/priceList"; import { Prices } from "utils/priceList";
import { Switch } from "@headlessui/react"; import { Switch } from "@headlessui/react";
import axios from "axios"; import axios from "axios";
import router from "next/router"; import router from "next/router";
import { useAuth } from "utils/Authcontext";
const Pricing = () => { const Pricing = () => {
const [enabled, setEnabled] = useState(false); const [enabled, setEnabled] = useState(false);
const { user, session } = Auth.useUser(); const { user } = useAuth();
const [customerId, setCustomerId] = useState(null); const [customerId, setCustomerId] = useState(null);
const [sub, setSub] = useState(false); const [sub, setSub] = useState(false);
const flat = false; // Switch between subscription system or flat prices const flat = false; // Switch between subscription system or flat prices
@@ -143,19 +143,23 @@ const Pricing = () => {
<button <button
className='btn btn-primary w-full' className='btn btn-primary w-full'
onClick={ onClick={
sub user
? () => { ? sub
portal(); ? () => {
portal();
}
: (e) =>
handleSubmit(
e,
enabled
? Prices.personal.annually.id
: Prices.personal.monthly.id
)
: () => {
router.push("/auth");
} }
: (e) =>
handleSubmit(
e,
enabled
? Prices.personal.annually.id
: Prices.personal.monthly.id
)
}> }>
{sub ? "Upgrade" : "Buy Now"} {user ? (sub ? "Upgrade" : "Buy Now") : "Register"}
</button> </button>
</div> </div>
</div> </div>
@@ -192,19 +196,23 @@ const Pricing = () => {
<button <button
className='btn btn-primary w-full' className='btn btn-primary w-full'
onClick={ onClick={
sub user
? () => { ? sub
portal(); ? () => {
portal();
}
: (e) =>
handleSubmit(
e,
enabled
? Prices.team.annually.id
: Prices.team.monthly.id
)
: () => {
router.push("/auth");
} }
: (e) =>
handleSubmit(
e,
enabled
? Prices.team.annually.id
: Prices.team.monthly.id
)
}> }>
{sub ? "Upgrade" : "Buy Now"} {user ? (sub ? "Upgrade" : "Buy Now") : "Register"}
</button> </button>
</div> </div>
</div> </div>
@@ -240,17 +248,23 @@ const Pricing = () => {
<button <button
className='btn btn-primary w-full' className='btn btn-primary w-full'
onClick={ onClick={
sub user
? () => { ? sub
portal(); ? () => {
portal();
}
: (e) =>
handleSubmit(
e,
enabled
? Prices.pro.annually.id
: Prices.pro.monthly.id
)
: () => {
router.push("/auth");
} }
: (e) =>
handleSubmit(
e,
enabled ? Prices.pro.annually.id : Prices.pro.monthly.id
)
}> }>
{sub ? "Upgrade" : "Buy Now"} {user ? (sub ? "Upgrade" : "Buy Now") : "Register"}
</button> </button>
</div> </div>
</div> </div>

133
components/UI/Login.js Normal file
View File

@@ -0,0 +1,133 @@
import { IoLogoGoogle } from "react-icons/io";
import { useState } from "react";
const Login = (props) => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [forgot, setForgot] = useState(false);
return (
<div className='p-10 bg-base-100 md:flex-1 rounded-md text-base-content shadow-md'>
{!forgot && (
<>
<h3 className='my-4 text-2xl font-semibold'>Account Login</h3>
<form action='#' className='flex flex-col space-y-5'>
<div className='flex flex-col space-y-1'>
<label
htmlFor='email'
className='text-sm font-semibold text-gray-500'>
Email address
</label>
<input
type='email'
id='email'
autoFocus
className='input input-primary input-bordered input-sm'
value={email}
onChange={(event) => {
setEmail(event.target.value);
}}
/>
</div>
<div className='flex flex-col space-y-1'>
<div className='flex items-center justify-between'>
<label
htmlFor='password'
className='text-sm font-semibold text-gray-500'>
Password
</label>
<button
onClick={() => {
setForgot(true);
}}
className='text-sm text-blue-600 hover:underline focus:text-blue-800'>
Forgot Password?
</button>
</div>
<input
type='password'
id='password'
className='input input-primary input-bordered input-sm'
value={password}
onChange={(event) => {
setPassword(event.target.value);
}}
/>
</div>
<div>
<button
className='btn btn-primary w-full'
onClick={(event) => {
event.preventDefault();
props.signIn({ email: email, password: password });
}}>
Log in
</button>
</div>
<div className='flex flex-col space-y-5'>
<span className='flex items-center justify-center space-x-2'>
<span className='h-px bg-gray-400 w-14'></span>
<span className='font-normal text-gray-500'>or login with</span>
<span className='h-px bg-gray-400 w-14'></span>
</span>
<div className='flex flex-col space-y-4'>
<button
href='#'
className='flex items-center justify-center px-4 py-2 space-x-2 transition-colors duration-300 border border-gray-800 rounded-md group hover:bg-gray-800 focus:outline-none '
onClick={(event) => {
event.preventDefault();
props.signIn({ provider: "google" });
}}>
<div className='text-base-content group-hover:text-white'>
<IoLogoGoogle />
</div>
<span className='text-sm font-medium text-gray-800 group-hover:text-white'>
Gmail
</span>
</button>
</div>
</div>
</form>
</>
)}
{forgot && (
<>
<h3 className='my-4 text-2xl font-semibold'>Password recovery</h3>
<form action='#' className='flex flex-col space-y-5'>
<div className='flex flex-col space-y-1'>
<label
htmlFor='email'
className='text-sm font-semibold text-gray-500'>
Email address
</label>
<input
type='email'
id='email'
autoFocus
className='input input-primary input-bordered input-sm'
value={email}
onChange={(event) => {
setEmail(event.target.value);
}}
/>
</div>
<div>
<button
className='btn btn-primary w-full'
onClick={(event) => {
event.preventDefault();
props.resetPassword(email);
}}>
Recover my password
</button>
</div>
</form>
</>
)}
</div>
);
};
export default Login;

911
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,6 @@
"micro": "^9.3.4", "micro": "^9.3.4",
"next": "latest", "next": "latest",
"next-seo": "^4.24.0", "next-seo": "^4.24.0",
"octokit": "^1.1.0",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-feather": "^2.0.9", "react-feather": "^2.0.9",

View File

@@ -1,8 +1,7 @@
import "./global.css"; import "./global.css";
import { Auth } from "@supabase/ui"; import { AuthProvider } from "utils/Authcontext";
import { DefaultSeo } from "next-seo"; import { DefaultSeo } from "next-seo";
import { supabase } from "utils/supabaseClient";
/* /*
Next-seo is integrated by default, if you want more information and how to Next-seo is integrated by default, if you want more information and how to
@@ -12,7 +11,7 @@ setup more elements, visit their Github page https://github.com/garmeeh/next-seo
function MyApp({ Component, pageProps }) { function MyApp({ Component, pageProps }) {
return ( return (
<> <>
<Auth.UserContextProvider supabaseClient={supabase}> <AuthProvider>
<DefaultSeo <DefaultSeo
openGraph={{ openGraph={{
type: "website", type: "website",
@@ -26,7 +25,7 @@ function MyApp({ Component, pageProps }) {
}} }}
/> />
<Component {...pageProps} /> <Component {...pageProps} />
</Auth.UserContextProvider> </AuthProvider>
</> </>
); );
} }

View File

@@ -1,10 +1,10 @@
/** /**
* NOTE: this file is only needed if you're doing SSR (getServerSideProps)! * NOTE: this file is only needed if you're doing SSR (getServerSideProps)!
* With SupaNexTail, we use SSR with the Dashboard page (pages/dashboard.js) * With SupaNexTail, we use SSR with the Dashboard page (pages/dashboard.js)
*/ */
import { supabase } from 'utils/supabaseClient' import { supabase } from "utils/supabaseClient";
export default function handler(req, res) { export default function handler(req, res) {
supabase.auth.api.setAuthCookie(req, res) supabase.auth.api.setAuthCookie(req, res);
} }

View File

@@ -4,24 +4,12 @@ You have 2 components, the "AuthComponent" that handle the logic,
and the "AuthText" that will show the description on the left of the screen and the "AuthText" that will show the description on the left of the screen
*/ */
import { Auth } from "@supabase/ui"; import AuthComponent from "components/Auth";
import AuthComponent from "../components/Auth";
import AuthText from "components/AuthText"; import AuthText from "components/AuthText";
import Layout from "components/Layout"; import Layout from "components/Layout";
import { NextSeo } from "next-seo"; import { NextSeo } from "next-seo";
import { useEffect } from "react";
import { useRouter } from "next/router";
const AuthPage = () => { const AuthPage = () => {
const { user, session } = Auth.useUser();
const router = useRouter();
useEffect(() => {
// If a user is already logged in, return to the homepage
if (user) {
router.push("/");
}
}, [user]);
return ( return (
<> <>
<NextSeo <NextSeo
@@ -32,7 +20,9 @@ const AuthPage = () => {
<Layout> <Layout>
<div className='flex flex-wrap justify-evenly w-full'> <div className='flex flex-wrap justify-evenly w-full'>
<AuthText /> <AuthText />
<div>{!session && <AuthComponent />}</div> <div>
<AuthComponent />
</div>
</div> </div>
</Layout> </Layout>
</> </>

33
utils/AuthContext.js Normal file
View File

@@ -0,0 +1,33 @@
import React, { createContext, useContext, useState } from "react";
import { supabase } from "utils/supabaseClient";
// create a context for authentication
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
// create state values for user data and loading
const [user, setUser] = useState();
const [loading, setLoading] = useState(true);
// create signUp, signIn, signOut functions
const value = {
signUp: (data) => supabase.auth.signUp(data),
signIn: (data) => supabase.auth.signIn(data),
signOut: () => supabase.auth.signOut(),
resetPassword: (data) => supabase.auth.api.resetPasswordForEmail(data),
user,
};
// use a provider to pass down the value
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
};
// export the useAuth hook
export const useAuth = () => {
return useContext(AuthContext);
};