mirror of
https://github.com/fergalmoran/supanextail.git
synced 2025-12-22 09:17:54 +00:00
Add Auth + Layout&Components
This commit is contained in:
110
components/Account.js
Normal file
110
components/Account.js
Normal 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
30
components/Auth.js
Normal 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
9
components/Footer.js
Normal 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
16
components/Layout.js
Normal 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
40
components/Nav.js
Normal 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
2
env.local.example
Normal 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
5
jsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "."
|
||||
}
|
||||
}
|
||||
62
package-lock.json
generated
62
package-lock.json
generated
@@ -9,10 +9,13 @@
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@supabase/supabase-js": "^1.15.0",
|
||||
"@supabase/ui": "^0.27.3",
|
||||
"daisyui": "^1.3.4",
|
||||
"next": "latest",
|
||||
"next-seo": "^4.24.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1"
|
||||
"react-dom": "^17.0.1",
|
||||
"react-feather": "^2.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.0.4",
|
||||
@@ -306,6 +309,19 @@
|
||||
"@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": {
|
||||
"version": "15.12.2",
|
||||
"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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
|
||||
@@ -2984,6 +3010,17 @@
|
||||
"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": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
@@ -3995,6 +4032,15 @@
|
||||
"@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": {
|
||||
"version": "15.12.2",
|
||||
"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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
|
||||
@@ -6090,6 +6142,14 @@
|
||||
"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": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
|
||||
@@ -9,10 +9,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@supabase/supabase-js": "^1.15.0",
|
||||
"@supabase/ui": "^0.27.3",
|
||||
"daisyui": "^1.3.4",
|
||||
"next": "latest",
|
||||
"next-seo": "^4.24.0",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1"
|
||||
"react-dom": "^17.0.1",
|
||||
"react-feather": "^2.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.0.4",
|
||||
|
||||
@@ -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 }) {
|
||||
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
41
pages/dashboard.js
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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() {
|
||||
const [session, setSession] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
setSession(supabase.auth.session());
|
||||
|
||||
supabase.auth.onAuthStateChange((_event, session) => {
|
||||
setSession(session);
|
||||
});
|
||||
}, []);
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-screen py-2">
|
||||
<div>
|
||||
<Head>
|
||||
<title>Create Next App</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel='icon' href='/favicon.ico' />
|
||||
</Head>
|
||||
|
||||
<main className="flex flex-col items-center justify-center w-full flex-1 px-20 text-center">
|
||||
<h1 className="text-6xl font-bold">
|
||||
Welcome to{' '}
|
||||
<a className="text-blue-600" href="https://nextjs.org">
|
||||
Next.js!
|
||||
</a>
|
||||
<Layout>
|
||||
<h1 className='text-6xl font-bold'>
|
||||
Supanextail <span className='text-blue-600'>Boilerplate</span>
|
||||
</h1>
|
||||
|
||||
<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 →</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 →</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 →</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 →</h3>
|
||||
<p className="mt-4 text-xl">
|
||||
Instantly deploy your Next.js site to a public URL with Vercel.
|
||||
</p>
|
||||
</a>
|
||||
</Layout>
|
||||
</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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
6
utils/supabaseClient.js
Normal file
6
utils/supabaseClient.js
Normal 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)
|
||||
Reference in New Issue
Block a user