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",
|
"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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
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() {
|
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 →</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>
|
|
||||||
</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
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