diff --git a/components/Layout.tsx b/components/Layout.tsx new file mode 100644 index 0000000..2bd6e5d --- /dev/null +++ b/components/Layout.tsx @@ -0,0 +1,76 @@ +import Link from 'next/link' +import React, { ReactNode } from 'react' +import A from '../ui/A' +import { useAuth } from '../hooks/useAuth' + +type Props = { + children: ReactNode +} + +const Layout = ({ children }: Props) => { + const { user } = useAuth() + + return ( +
+
+
+ + + Cremona + + +
{user ? : }
+
+
+
{children}
+ +
+ ) +} + +export default Layout + +function LoggedIn() { + return ( + <> + + Sign in + +
+ + Sign up + +
+ + ) +} + +function LoggedOut() { + const { signout } = useAuth() + + return ( + <> + + + ) +} diff --git a/components/Login.tsx b/components/Login.tsx new file mode 100644 index 0000000..c5e24a9 --- /dev/null +++ b/components/Login.tsx @@ -0,0 +1,105 @@ +import { useRouter } from 'next/router' +import React, { FormEvent, useState } from 'react' +import { useAuth } from '../hooks/useAuth' + +type LoginMode = 'signin' | 'signup' + +type Props = { + mode: LoginMode +} + +const Login = ({ mode }: Props) => { + const router = useRouter() + const [formData, setFormData] = useState({ + email: '', + password: '', + }) + const [inProgress, setInProgress] = useState(false) + const [message, setMessage] = useState('') + const { signin, signup } = useAuth() + + const onSubmit = (event: FormEvent) => { + event.preventDefault() + + setInProgress(true) + setMessage('Processing...') + + const method = mode === 'signin' ? signin : signup + + method(formData.email, formData.password) + .then(() => { + setInProgress(false) + setMessage('') + + router.push('/') + }) + .catch((error: Error) => { + console.error(error) + + setInProgress(false) + setMessage(error.message) + }) + } + + return ( + <> +
+

{mode === 'signin' ? 'Sign In' : 'Sign Up'}

+
+
+
+
+ { + setFormData({ + ...formData, + email: target.value, + }) + }} + placeholder="Email" + autoComplete="email" + className="block px-4 py-2 shadow w-full" + /> +
+
+ { + setFormData({ + ...formData, + password: target.value, + }) + }} + placeholder="Password" + autoComplete="current-password" + className="block px-4 py-2 shadow w-full" + /> +
+
+ +
+ {message && ( +
+

{message}

+
+ )} +
+
+ + ) +} + +export default Login diff --git a/hooks/useAuth.tsx b/hooks/useAuth.tsx new file mode 100644 index 0000000..21e8dc3 --- /dev/null +++ b/hooks/useAuth.tsx @@ -0,0 +1,86 @@ +import { fuego } from '@nandorojo/swr-firestore' +import React, { + createContext, + ReactNode, + useContext, + useEffect, + useState, +} from 'react' + +type ContextData = { + user: firebase.User | null + signin: (email: string, password: string) => Promise + signup: (email: string, password: string) => Promise + signout: () => Promise +} + +const defaultContextData = { + user: null, + signin: () => Promise.resolve(null), + signup: () => Promise.resolve(null), + signout: () => Promise.resolve(void 0), +} + +const AuthContext = createContext(defaultContextData) + +type Props = { + children: ReactNode +} + +export function ProvideAuth({ children }: Props) { + const auth = useProvideAuth() + + return {children} +} + +export const useAuth = () => { + return useContext(AuthContext) +} + +function useProvideAuth() { + const [user, setUser] = useState(null) + + const signin = (email: string, password: string) => { + return fuego + .auth() + .signInWithEmailAndPassword(email, password) + .then((response) => { + setUser(response.user) + return response.user + }) + } + + const signup = (email: string, password: string) => { + return fuego + .auth() + .createUserWithEmailAndPassword(email, password) + .then((response) => { + setUser(response.user) + return response.user + }) + } + + const signout = () => { + return fuego + .auth() + .signOut() + .then(() => { + setUser(null) + }) + } + + useEffect(() => { + const unsubscribe = fuego.auth().onAuthStateChanged((user) => { + setUser(user) + }) + + return () => unsubscribe() + }, []) + + return { + user, + signin, + signup, + signout, + } +} diff --git a/pages/_app.tsx b/pages/_app.tsx index 73fce5d..1117b29 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,9 +1,35 @@ +import { Fuego, FuegoProvider } from '@nandorojo/swr-firestore' +import 'firebase/auth' +import 'firebase/firestore' import { AppProps } from 'next/app' import React from 'react' +import Layout from '../components/Layout' +import { ProvideAuth } from '../hooks/useAuth' import '../styles/globals.css' +const firebaseConfig = { + apiKey: process.env.NEXT_PUBLIC_API_KEY, + authDomain: process.env.NEXT_PUBLIC_AUTH_DOMAIN, + databaseURL: process.env.NEXT_PUBLIC_DATABASE_URL, + projectId: process.env.NEXT_PUBLIC_PROJECT_ID, + storageBucket: process.env.NEXT_PUBLIC_STORAGE_BUCKET, + messagingSenderId: process.env.NEXT_PUBLIC_MESSAGING_SENDER_ID, + appId: process.env.NEXT_PUBLIC_APP_ID, + measurementId: process.env.NEXT_PUBLIC_MEASUREMENT_ID, +} + +const fuego = new Fuego(firebaseConfig) + const App = ({ Component, pageProps }: AppProps) => { - return + return ( + + + + + + + + ) } export default App diff --git a/pages/_document.tsx b/pages/_document.tsx index e7535c5..496e2b5 100644 --- a/pages/_document.tsx +++ b/pages/_document.tsx @@ -8,7 +8,7 @@ class MyDocument extends Document { - +
diff --git a/pages/index.tsx b/pages/index.tsx index 97e513d..b6c2cc0 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,10 +1,8 @@ import React from 'react' +import { useAuth } from '../hooks/useAuth' export default function Index() { - return ( - <> - Cremona -

Cremona

- - ) + const { user } = useAuth() + + return
{user ? 'Hello' : null}
} diff --git a/pages/signin.tsx b/pages/signin.tsx new file mode 100644 index 0000000..357aae8 --- /dev/null +++ b/pages/signin.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import Login from '../components/Login' + +const SignIn = () => { + return ( + <> + + + ) +} + +export default SignIn diff --git a/pages/signup.tsx b/pages/signup.tsx new file mode 100644 index 0000000..99d3175 --- /dev/null +++ b/pages/signup.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import Login from '../components/Login' + +const SignUp = () => { + return ( + <> + + + ) +} + +export default SignUp diff --git a/ui/A.tsx b/ui/A.tsx new file mode 100644 index 0000000..c4944bb --- /dev/null +++ b/ui/A.tsx @@ -0,0 +1,19 @@ +import React, { AnchorHTMLAttributes, forwardRef, ReactNode, Ref } from 'react' + +type Props = AnchorHTMLAttributes & { + children: ReactNode +} + +const A = forwardRef( + ({ children, ...rest }: Props, ref: Ref) => { + return ( + + {children} + + ) + }, +) + +A.displayName = 'A' + +export default A