mirror of
https://github.com/fergalmoran/supanextail.git
synced 2025-12-22 09:17:54 +00:00
WIP Custom auth handler / login
Forgot password is not working
This commit is contained in:
@@ -6,18 +6,18 @@ CONFIGURE THE AUTH COMPONENT LINE 30
|
||||
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
|
||||
*/
|
||||
import { Auth } from "@supabase/ui";
|
||||
|
||||
import Login from "./UI/Login";
|
||||
import { supabase } from "utils/supabaseClient";
|
||||
import { useAuth } from "utils/Authcontext";
|
||||
|
||||
const Container = (props) => {
|
||||
const { user } = Auth.useUser();
|
||||
const { user, signOut } = useAuth();
|
||||
if (user)
|
||||
return (
|
||||
<div className='w-80 md:w-96 order-first lg:order-last'>
|
||||
<p>Hello {user.email}! 👋 You are already logged in</p>
|
||||
<button
|
||||
className='btn btn-primary'
|
||||
onClick={() => props.supabaseClient.auth.signOut()}>
|
||||
<button className='btn btn-primary' onClick={() => signOut()}>
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
@@ -26,19 +26,16 @@ const Container = (props) => {
|
||||
};
|
||||
|
||||
const AuthComponent = () => {
|
||||
const { signUp, signIn, signOut, resetPassword } = useAuth();
|
||||
return (
|
||||
<Auth.UserContextProvider supabaseClient={supabase}>
|
||||
<Container supabaseClient={supabase}>
|
||||
<Auth
|
||||
supabaseClient={supabase}
|
||||
providers={["google"]}
|
||||
socialLayout='horizontal'
|
||||
socialButtonSize='xlarge'
|
||||
socialColors={true}
|
||||
className='p-5 bg-neutral-content rounded-md'
|
||||
<Login
|
||||
signUp={signUp}
|
||||
signIn={signIn}
|
||||
signOut={signOut}
|
||||
resetPassword={resetPassword}
|
||||
/>
|
||||
</Container>
|
||||
</Auth.UserContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -12,14 +12,36 @@ The images are in the public folder.
|
||||
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
|
||||
import { Auth } from "@supabase/ui";
|
||||
import Footer from "./Footer";
|
||||
import Head from "next/head";
|
||||
import Nav from "./Nav";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import { supabase } from "utils/supabaseClient";
|
||||
import { useAuth } from "utils/Authcontext";
|
||||
import { useEffect } from "react";
|
||||
|
||||
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 = {
|
||||
//Style your toast elements here
|
||||
@@ -56,7 +78,7 @@ const Layout = (props) => {
|
||||
<meta name='theme-color' content='#ffffff' />
|
||||
</Head>
|
||||
<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>
|
||||
<ToastContainer
|
||||
position='bottom-center'
|
||||
|
||||
@@ -6,30 +6,8 @@ import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import Logo from "public/logo.svg";
|
||||
import { Menu } from "react-feather";
|
||||
import { supabase } from "utils/supabaseClient";
|
||||
import { useEffect } from "react";
|
||||
|
||||
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
|
||||
const NavMenu = (
|
||||
<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 ? (
|
||||
<button
|
||||
className='btn btn-ghost btn-sm'
|
||||
onClick={() => supabase.auth.signOut()}>
|
||||
onClick={() => props.signOut()}>
|
||||
Logout
|
||||
</button>
|
||||
) : (
|
||||
|
||||
@@ -9,15 +9,15 @@ https://dashboard.stripe.com/test/settings/billing/portal
|
||||
import { getSub, supabase } from "utils/supabaseClient";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { Auth } from "@supabase/ui";
|
||||
import { Prices } from "utils/priceList";
|
||||
import { Switch } from "@headlessui/react";
|
||||
import axios from "axios";
|
||||
import router from "next/router";
|
||||
import { useAuth } from "utils/Authcontext";
|
||||
|
||||
const Pricing = () => {
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const { user, session } = Auth.useUser();
|
||||
const { user } = useAuth();
|
||||
const [customerId, setCustomerId] = useState(null);
|
||||
const [sub, setSub] = useState(false);
|
||||
const flat = false; // Switch between subscription system or flat prices
|
||||
@@ -143,7 +143,8 @@ const Pricing = () => {
|
||||
<button
|
||||
className='btn btn-primary w-full'
|
||||
onClick={
|
||||
sub
|
||||
user
|
||||
? sub
|
||||
? () => {
|
||||
portal();
|
||||
}
|
||||
@@ -154,8 +155,11 @@ const Pricing = () => {
|
||||
? Prices.personal.annually.id
|
||||
: Prices.personal.monthly.id
|
||||
)
|
||||
: () => {
|
||||
router.push("/auth");
|
||||
}
|
||||
}>
|
||||
{sub ? "Upgrade" : "Buy Now"}
|
||||
{user ? (sub ? "Upgrade" : "Buy Now") : "Register"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -192,7 +196,8 @@ const Pricing = () => {
|
||||
<button
|
||||
className='btn btn-primary w-full'
|
||||
onClick={
|
||||
sub
|
||||
user
|
||||
? sub
|
||||
? () => {
|
||||
portal();
|
||||
}
|
||||
@@ -203,8 +208,11 @@ const Pricing = () => {
|
||||
? Prices.team.annually.id
|
||||
: Prices.team.monthly.id
|
||||
)
|
||||
: () => {
|
||||
router.push("/auth");
|
||||
}
|
||||
}>
|
||||
{sub ? "Upgrade" : "Buy Now"}
|
||||
{user ? (sub ? "Upgrade" : "Buy Now") : "Register"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -240,17 +248,23 @@ const Pricing = () => {
|
||||
<button
|
||||
className='btn btn-primary w-full'
|
||||
onClick={
|
||||
sub
|
||||
user
|
||||
? sub
|
||||
? () => {
|
||||
portal();
|
||||
}
|
||||
: (e) =>
|
||||
handleSubmit(
|
||||
e,
|
||||
enabled ? Prices.pro.annually.id : Prices.pro.monthly.id
|
||||
enabled
|
||||
? Prices.pro.annually.id
|
||||
: Prices.pro.monthly.id
|
||||
)
|
||||
: () => {
|
||||
router.push("/auth");
|
||||
}
|
||||
}>
|
||||
{sub ? "Upgrade" : "Buy Now"}
|
||||
{user ? (sub ? "Upgrade" : "Buy Now") : "Register"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
133
components/UI/Login.js
Normal file
133
components/UI/Login.js
Normal 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
911
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,6 @@
|
||||
"micro": "^9.3.4",
|
||||
"next": "latest",
|
||||
"next-seo": "^4.24.0",
|
||||
"octokit": "^1.1.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-feather": "^2.0.9",
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import "./global.css";
|
||||
|
||||
import { Auth } from "@supabase/ui";
|
||||
import { AuthProvider } from "utils/Authcontext";
|
||||
import { DefaultSeo } from "next-seo";
|
||||
import { supabase } from "utils/supabaseClient";
|
||||
|
||||
/*
|
||||
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 }) {
|
||||
return (
|
||||
<>
|
||||
<Auth.UserContextProvider supabaseClient={supabase}>
|
||||
<AuthProvider>
|
||||
<DefaultSeo
|
||||
openGraph={{
|
||||
type: "website",
|
||||
@@ -26,7 +25,7 @@ function MyApp({ Component, pageProps }) {
|
||||
}}
|
||||
/>
|
||||
<Component {...pageProps} />
|
||||
</Auth.UserContextProvider>
|
||||
</AuthProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* 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) {
|
||||
supabase.auth.api.setAuthCookie(req, res)
|
||||
}
|
||||
supabase.auth.api.setAuthCookie(req, res);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
import { Auth } from "@supabase/ui";
|
||||
import AuthComponent from "../components/Auth";
|
||||
import AuthComponent from "components/Auth";
|
||||
import AuthText from "components/AuthText";
|
||||
import Layout from "components/Layout";
|
||||
import { NextSeo } from "next-seo";
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
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 (
|
||||
<>
|
||||
<NextSeo
|
||||
@@ -32,7 +20,9 @@ const AuthPage = () => {
|
||||
<Layout>
|
||||
<div className='flex flex-wrap justify-evenly w-full'>
|
||||
<AuthText />
|
||||
<div>{!session && <AuthComponent />}</div>
|
||||
<div>
|
||||
<AuthComponent />
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
</>
|
||||
|
||||
33
utils/AuthContext.js
Normal file
33
utils/AuthContext.js
Normal 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);
|
||||
};
|
||||
Reference in New Issue
Block a user