Add Auth + Layout&Components

This commit is contained in:
Michael
2021-06-14 22:15:54 +02:00
parent 61a5453ac8
commit dcdb384d2b
13 changed files with 370 additions and 78 deletions

110
components/Account.js Normal file
View File

@@ -0,0 +1,110 @@
import { useState, useEffect } from 'react'
import { supabase } from '../utils/supabaseClient'
export default function Account({ session }) {
const [loading, setLoading] = useState(true)
const [username, setUsername] = useState(null)
const [website, setWebsite] = useState(null)
const [avatar_url, setAvatarUrl] = useState(null)
useEffect(() => {
getProfile()
}, [session])
async function getProfile() {
try {
setLoading(true)
const user = supabase.auth.user()
let { data, error, status } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user.id)
.single()
if (error && status !== 406) {
throw error
}
if (data) {
setUsername(data.username)
setWebsite(data.website)
setAvatarUrl(data.avatar_url)
}
} catch (error) {
alert(error.message)
} finally {
setLoading(false)
}
}
async function updateProfile({ username, website, avatar_url }) {
try {
setLoading(true)
const user = supabase.auth.user()
const updates = {
id: user.id,
username,
website,
avatar_url,
updated_at: new Date(),
}
let { error } = await supabase.from('profiles').upsert(updates, {
returning: 'minimal', // Don't return the value after inserting
})
if (error) {
throw error
}
} catch (error) {
alert(error.message)
} finally {
setLoading(false)
}
}
return (
<div className="form-widget">
<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" value={session.user.email} disabled />
</div>
<div>
<label htmlFor="username">Name</label>
<input
id="username"
type="text"
value={username || ''}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div>
<label htmlFor="website">Website</label>
<input
id="website"
type="website"
value={website || ''}
onChange={(e) => setWebsite(e.target.value)}
/>
</div>
<div>
<button
className="button block primary"
onClick={() => updateProfile({ username, website, avatar_url })}
disabled={loading}
>
{loading ? 'Loading ...' : 'Update'}
</button>
</div>
<div>
<button className="button block" onClick={() => supabase.auth.signOut()}>
Sign Out
</button>
</div>
</div>
)
}

30
components/Auth.js Normal file
View File

@@ -0,0 +1,30 @@
import { Auth, Typography, Button } from "@supabase/ui";
import { supabase } from "utils/supabaseClient";
const { Text } = Typography;
// Create a single supabase client for interacting with your database
const Container = (props) => {
const { user } = Auth.useUser();
if (user)
return (
<>
<p>Hello {user.email}! 👋 You are already logged in</p>
<button className="btn btn-primary" onClick={() => props.supabaseClient.auth.signOut()}>
Sign out
</button>
</>
);
return props.children;
};
export default function AuthComponent() {
return (
<Auth.UserContextProvider supabaseClient={supabase}>
<Container supabaseClient={supabase}>
<Auth providers={["facebook", "github"]} supabaseClient={supabase} />
</Container>
</Auth.UserContextProvider>
);
}

9
components/Footer.js Normal file
View File

@@ -0,0 +1,9 @@
const Nav = () => {
return (
<footer className='flex items-center justify-center w-full mb-1'>
Supanextail
</footer>
);
};
export default Nav;

16
components/Layout.js Normal file
View File

@@ -0,0 +1,16 @@
import Nav from "./Nav";
import Footer from "./Footer";
const Layout = (props) => {
return (
<div className='flex flex-col items-center justify-center min-h-screen max-w-6xl m-auto'>
<Nav />
<main className='flex flex-col items-center justify-center w-full flex-1 px-2 text-center'>
{props.children}
</main>
<Footer />
</div>
);
};
export default Layout;

40
components/Nav.js Normal file
View File

@@ -0,0 +1,40 @@
import { Menu } from "react-feather";
import Link from "next/link";
const Nav = () => {
const NavMenu = (
<>
<Link href='/'>
<a className='btn btn-ghost btn-sm'>Home</a>
</Link>
<Link href='/dashboard'>
<a className='btn btn-ghost btn-sm'>Dashboard</a>
</Link>
<Link href='/'>
<a className='btn btn-ghost btn-sm'>About</a>
</Link>
<Link href='/'>
<a className='btn btn-ghost btn-sm'>Contact</a>
</Link>
</>
);
return (
<nav className='navbar mb-2 shadow-lg bg-neutral text-neutral-content w-full'>
<div className='flex-1 px-2 mx-2'>
<div className='items-stretch hidden lg:flex'>{NavMenu}</div>
</div>
<div className='flex-none'>
<div className='dropdown dropdown-end'>
<div tabindex='0' className='m-1 block cursor-pointer lg:hidden'>
<Menu />
</div>
<div className='shadow menu dropdown-content text-black w-52 btn-group mt-3'>
{NavMenu}
</div>
</div>
</div>
</nav>
);
};
export default Nav;

2
env.local.example Normal file
View File

@@ -0,0 +1,2 @@
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY

5
jsconfig.json Normal file
View File

@@ -0,0 +1,5 @@
{
"compilerOptions": {
"baseUrl": "."
}
}

62
package-lock.json generated
View File

@@ -9,10 +9,13 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@supabase/supabase-js": "^1.15.0", "@supabase/supabase-js": "^1.15.0",
"@supabase/ui": "^0.27.3",
"daisyui": "^1.3.4", "daisyui": "^1.3.4",
"next": "latest", "next": "latest",
"next-seo": "^4.24.0",
"react": "^17.0.1", "react": "^17.0.1",
"react-dom": "^17.0.1" "react-dom": "^17.0.1",
"react-feather": "^2.0.9"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^10.0.4", "autoprefixer": "^10.0.4",
@@ -306,6 +309,19 @@
"@supabase/storage-js": "^1.2.1" "@supabase/storage-js": "^1.2.1"
} }
}, },
"node_modules/@supabase/ui": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@supabase/ui/-/ui-0.27.3.tgz",
"integrity": "sha512-SO7TO/TIK6qziHUTmd2YpTmUtpBfYrN4Gx9tL7pYDHhfmmVApmYXmxEVexHAvHkSsxaK8yPER9KbMpwXx3ZwPQ==",
"dependencies": {
"lodash": "^4.17.20",
"prop-types": "^15.7.2"
},
"peerDependencies": {
"react": "^16.13.1 || ^17.0.1",
"react-dom": "^16.13.1 || ^17.0.1"
}
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "15.12.2", "version": "15.12.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz",
@@ -2197,6 +2213,16 @@
} }
} }
}, },
"node_modules/next-seo": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/next-seo/-/next-seo-4.24.0.tgz",
"integrity": "sha512-9VQXfXAelhE+hAWzJ4azigQaW3FPX0kU0eYKFQXKsQjgY7AWtukjRGXls0oSIk8khhDJwmCt46EwsO9n5DDW6Q==",
"peerDependencies": {
"next": "^8.1.1-canary.54 || ^9.0.0 || ^10.0.0",
"react": "^16.0.0 || ^17.0.0",
"react-dom": "^16.0.0 || ^17.0.0"
}
},
"node_modules/next-tick": { "node_modules/next-tick": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
@@ -2984,6 +3010,17 @@
"react": "17.0.2" "react": "17.0.2"
} }
}, },
"node_modules/react-feather": {
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/react-feather/-/react-feather-2.0.9.tgz",
"integrity": "sha512-yMfCGRkZdXwIs23Zw/zIWCJO3m3tlaUvtHiXlW+3FH7cIT6fiK1iJ7RJWugXq7Fso8ZaQyUm92/GOOHXvkiVUw==",
"dependencies": {
"prop-types": "^15.7.2"
},
"peerDependencies": {
"react": "^16.8.6 || ^17"
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -3995,6 +4032,15 @@
"@supabase/storage-js": "^1.2.1" "@supabase/storage-js": "^1.2.1"
} }
}, },
"@supabase/ui": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@supabase/ui/-/ui-0.27.3.tgz",
"integrity": "sha512-SO7TO/TIK6qziHUTmd2YpTmUtpBfYrN4Gx9tL7pYDHhfmmVApmYXmxEVexHAvHkSsxaK8yPER9KbMpwXx3ZwPQ==",
"requires": {
"lodash": "^4.17.20",
"prop-types": "^15.7.2"
}
},
"@types/node": { "@types/node": {
"version": "15.12.2", "version": "15.12.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz",
@@ -5484,6 +5530,12 @@
} }
} }
}, },
"next-seo": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/next-seo/-/next-seo-4.24.0.tgz",
"integrity": "sha512-9VQXfXAelhE+hAWzJ4azigQaW3FPX0kU0eYKFQXKsQjgY7AWtukjRGXls0oSIk8khhDJwmCt46EwsO9n5DDW6Q==",
"requires": {}
},
"next-tick": { "next-tick": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
@@ -6090,6 +6142,14 @@
"scheduler": "^0.20.2" "scheduler": "^0.20.2"
} }
}, },
"react-feather": {
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/react-feather/-/react-feather-2.0.9.tgz",
"integrity": "sha512-yMfCGRkZdXwIs23Zw/zIWCJO3m3tlaUvtHiXlW+3FH7cIT6fiK1iJ7RJWugXq7Fso8ZaQyUm92/GOOHXvkiVUw==",
"requires": {
"prop-types": "^15.7.2"
}
},
"react-is": { "react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",

View File

@@ -9,10 +9,13 @@
}, },
"dependencies": { "dependencies": {
"@supabase/supabase-js": "^1.15.0", "@supabase/supabase-js": "^1.15.0",
"@supabase/ui": "^0.27.3",
"daisyui": "^1.3.4", "daisyui": "^1.3.4",
"next": "latest", "next": "latest",
"next-seo": "^4.24.0",
"react": "^17.0.1", "react": "^17.0.1",
"react-dom": "^17.0.1" "react-dom": "^17.0.1",
"react-feather": "^2.0.9"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^10.0.4", "autoprefixer": "^10.0.4",

View File

@@ -1,7 +1,28 @@
import 'tailwindcss/tailwind.css' import "tailwindcss/tailwind.css";
import { Auth } from "@supabase/ui";
import { DefaultSeo } from "next-seo";
import { supabase } from "utils/supabaseClient";
function MyApp({ Component, pageProps }) { function MyApp({ Component, pageProps }) {
return <Component {...pageProps} /> return (
<>
<Auth.UserContextProvider supabaseClient={supabase}>
<DefaultSeo
openGraph={{
type: "website",
locale: "en_IE",
url: "",
site_name: "supanextail",
}}
twitter={{
handle: "@michael_webdev",
site: "@michael_webdev",
}}
/>
<Component {...pageProps} />
</Auth.UserContextProvider>
</>
);
} }
export default MyApp export default MyApp;

41
pages/dashboard.js Normal file
View File

@@ -0,0 +1,41 @@
import Head from "next/head";
import { useState, useEffect } from "react";
import { supabase } from "../utils/supabaseClient";
import Auth from "../components/Auth";
import Account from "../components/Account";
import Layout from "components/Layout";
export default function Home() {
const [session, setSession] = useState(null);
useEffect(() => {
setSession(supabase.auth.session());
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
});
}, []);
return (
<div>
<Head>
<title>Dashboard</title>
<link rel='icon' href='/favicon.ico' />
</Head>
<Layout>
<div>
<h1 className='text-6xl font-bold'>Dashboard</h1>
<div className='container'>
{!session ? (
<div className='max-w-md'>
<Auth />
</div>
) : (
<Account key={session.user.id} session={session} />
)}
</div>
</div>
</Layout>
</div>
);
}

View File

@@ -1,82 +1,31 @@
import Head from 'next/head' import Head from "next/head";
import { useState, useEffect } from "react";
import { supabase } from "../utils/supabaseClient";
import Auth from "../components/Auth";
import Account from "../components/Account";
import Layout from "components/Layout";
export default function Home() { export default function Home() {
const [session, setSession] = useState(null);
useEffect(() => {
setSession(supabase.auth.session());
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
});
}, []);
return ( return (
<div className="flex flex-col items-center justify-center min-h-screen py-2"> <div>
<Head> <Head>
<title>Create Next App</title> <title>Create Next App</title>
<link rel="icon" href="/favicon.ico" /> <link rel='icon' href='/favicon.ico' />
</Head> </Head>
<Layout>
<main className="flex flex-col items-center justify-center w-full flex-1 px-20 text-center"> <h1 className='text-6xl font-bold'>
<h1 className="text-6xl font-bold"> Supanextail <span className='text-blue-600'>Boilerplate</span>
Welcome to{' '}
<a className="text-blue-600" href="https://nextjs.org">
Next.js!
</a>
</h1> </h1>
</Layout>
<p className="mt-3 text-2xl">
Get started by editing{' '}
<code className="p-3 font-mono text-lg bg-gray-100 rounded-md">
pages/index.js
</code>
</p>
<div className="flex flex-wrap items-center justify-around max-w-4xl mt-6 sm:w-full">
<a
href="https://nextjs.org/docs"
className="p-6 mt-6 text-left border w-96 rounded-xl hover:text-blue-600 focus:text-blue-600"
>
<h3 className="text-2xl font-bold">Documentation &rarr;</h3>
<p className="mt-4 text-xl">
Find in-depth information about Next.js features and API.
</p>
</a>
<a
href="https://nextjs.org/learn"
className="p-6 mt-6 text-left border w-96 rounded-xl hover:text-blue-600 focus:text-blue-600"
>
<h3 className="text-2xl font-bold">Learn &rarr;</h3>
<p className="mt-4 text-xl">
Learn about Next.js in an interactive course with quizzes!
</p>
</a>
<a
href="https://github.com/vercel/next.js/tree/master/examples"
className="p-6 mt-6 text-left border w-96 rounded-xl hover:text-blue-600 focus:text-blue-600"
>
<h3 className="text-2xl font-bold">Examples &rarr;</h3>
<p className="mt-4 text-xl">
Discover and deploy boilerplate example Next.js projects.
</p>
</a>
<a
href="https://vercel.com/import?filter=next.js&utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
className="p-6 mt-6 text-left border w-96 rounded-xl hover:text-blue-600 focus:text-blue-600"
>
<h3 className="text-2xl font-bold">Deploy &rarr;</h3>
<p className="mt-4 text-xl">
Instantly deploy your Next.js site to a public URL with Vercel.
</p>
</a>
</div>
</main>
<footer className="flex items-center justify-center w-full h-24 border-t">
<a
className="flex items-center justify-center"
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<img src="/vercel.svg" alt="Vercel Logo" className="h-4 ml-2" />
</a>
</footer>
</div> </div>
) );
} }

6
utils/supabaseClient.js Normal file
View File

@@ -0,0 +1,6 @@
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)