chore: update examples supabase-js v2

This commit is contained in:
thorwebdev
2022-10-19 23:18:59 +08:00
parent 4b41d456d4
commit d4ee3473c8
842 changed files with 118055 additions and 215160 deletions

View File

@@ -0,0 +1,5 @@
# Supabase Examples Archive
## supabase-js v1
You can find the supabase-js v1 examples at [github.com/supabase/examples-archive](https://github.com/supabase/examples-archive).

View File

@@ -1,3 +0,0 @@
dist/*
.vercel

View File

@@ -1,14 +0,0 @@
# Vanilla-js Auth Example
Live Example: [https://auth-vanilla-js.vercel.app/](https://auth-vanilla-js.vercel.app/)
How to sign up and login using supabase and supabase-js using HTML and JavaScript only
<img width="558" alt="image" src="https://user-images.githubusercontent.com/458736/88377414-b6fb4180-cdd1-11ea-8061-103ec4577b7b.png">
### running
`npm run dev` (requires npx to be installed)
if you want to make changes without restarting the server run this in a different terminal window:
`npm run watch`

View File

@@ -1,16 +0,0 @@
body {
/* margin: 0; */
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: rgb(250, 217, 246);
}
label {
margin: 0.5em;
}
.section {
margin: 1em;
}

View File

@@ -1,53 +0,0 @@
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@1"></script>
<script src="./index.js"></script>
<link rel="stylesheet" type="text/css" href="./index.css">
</head>
<body>
<div class='container'>
<div class='section'>
<h1>Supabase Auth Example</h1>
</div>
<div class='section'>
<a href="https://github.com/supabase/supabase/tree/master/examples/auth/javascript-auth">View the code on GitHub</a>
</div>
<div class='section'>
<form id='sign-up'>
<h3>Sign Up</h3>
<label>Email</label><input type='email' name='email' />
<label>Password</label><input type='password' name='password' />
<input type='submit'>
</form>
</div>
<div class='section'>
<form id='log-in'>
<h3>Log In</h3>
<label>Email</label><input type='email' name='email' />
<label>Password</label><input type='password' name='password' />
<input type='submit'>
</form>
</div>
<div class='section'>
<form id='validate'>
<h3>Access Token</h3>
<input readonly=readonly type='text' id='access-token' /> <small>Default expiry is 60 minutes</small>
<h3>Refresh Token</h3>
<input readonly=readonly type='text' id='refresh-token' /> <small>Supabase-js will use this to automatically fetch a new accessToken for you every 60 mins whilst the client is running</small>
</form>
</div>
<div class='section'>
<h3>Fetch User Details</h3>
<button id='user-button'>Fetch</button>
</div>
<div class='section'>
<h3>Logout</h3>
<button id='logout-button'>Logout</button>
</div>
</div>
</body>
</html>

View File

@@ -1,79 +0,0 @@
var SUPABASE_URL = 'https://ernhobnpmmupjnmxpfbt.supabase.co'
var SUPABASE_KEY =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxMzA5ODU0NCwiZXhwIjoxOTI4Njc0NTQ0fQ.Z9bRrfaL2oGhSuyBckFcdcnRIJDelWJ1II98OnEtLO0'
var supabase = supabase.createClient(SUPABASE_URL, SUPABASE_KEY)
window.userToken = null
document.addEventListener('DOMContentLoaded', function (event) {
var signUpForm = document.querySelector('#sign-up')
signUpForm.onsubmit = signUpSubmitted.bind(signUpForm)
var logInForm = document.querySelector('#log-in')
logInForm.onsubmit = logInSubmitted.bind(logInForm)
var userDetailsButton = document.querySelector('#user-button')
userDetailsButton.onclick = fetchUserDetails.bind(userDetailsButton)
var logoutButton = document.querySelector('#logout-button')
logoutButton.onclick = logoutSubmitted.bind(logoutButton)
})
const signUpSubmitted = (event) => {
event.preventDefault()
const email = event.target[0].value
const password = event.target[1].value
supabase.auth
.signUp({ email, password })
.then((response) => {
response.error ? alert(response.error.message) : setToken(response)
})
.catch((err) => {
alert(err)
})
}
const logInSubmitted = (event) => {
event.preventDefault()
const email = event.target[0].value
const password = event.target[1].value
supabase.auth
.signIn({ email, password })
.then((response) => {
response.error ? alert(response.error.message) : setToken(response)
})
.catch((err) => {
alert(err.response.text)
})
}
const fetchUserDetails = () => {
alert(JSON.stringify(supabase.auth.user()))
}
const logoutSubmitted = (event) => {
event.preventDefault()
supabase.auth
.signOut()
.then((_response) => {
document.querySelector('#access-token').value = ''
document.querySelector('#refresh-token').value = ''
alert('Logout successful')
})
.catch((err) => {
alert(err.response.text)
})
}
function setToken(response) {
if (response.user.confirmation_sent_at && !response?.session?.access_token) {
alert('Confirmation Email Sent')
} else {
document.querySelector('#access-token').value = response.session.access_token
document.querySelector('#refresh-token').value = response.session.refresh_token
alert('Logged in as ' + response.user.email)
}
}

View File

@@ -1,5 +0,0 @@
{
"name": "supabase-auth-vanilla-js",
"version": "0.0.1",
"lockfileVersion": 1
}

View File

@@ -1,11 +0,0 @@
{
"name": "supabase-auth-vanilla-js",
"version": "0.0.1",
"description": "basic auth example for supabase with js",
"main": "index.js",
"scripts": {
"dev": "npx light-server -s ."
},
"author": "ant@supabase.io",
"license": "MIT"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -1,3 +0,0 @@
# Update these with your Supabase details from your project settings > API
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=

View File

@@ -1,35 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/
.env
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel

View File

@@ -1,7 +0,0 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 100
}

View File

@@ -1,36 +0,0 @@
<img src="./.assets/Screen.png">
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

View File

@@ -1,12 +0,0 @@
import { default as HeadContainer } from 'next/head'
const Head = () => {
return (
<HeadContainer>
<title>Supabase Example</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</HeadContainer>
)
}
export default Head

View File

@@ -1,87 +0,0 @@
import React from 'react'
import AuthUser from '../../hooks/authUser'
import { Disclosure } from '@headlessui/react'
import { MenuIcon, XIcon } from '@heroicons/react/outline'
import MenuLogado from './menuLogado'
import MenuNotLogado from './menuNotLogado'
import Navigation from './navigation'
import classNames from '../../utils/classsesNames'
export default function Header() {
return (
<Disclosure as="nav" className="bg-gray-800">
{({ open }) => (
<>
<div className="max-w-7xl mx-auto px-2 sm:px-6 lg:px-8">
<div className="relative flex items-center justify-between h-16">
<div className="absolute inset-y-0 left-0 flex items-center sm:hidden">
{/* Mobile menu button*/}
<Disclosure.Button className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
<span className="sr-only">Open main menu</span>
{open ? (
<XIcon className="block h-6 w-6" aria-hidden="true" />
) : (
<MenuIcon className="block h-6 w-6" aria-hidden="true" />
)}
</Disclosure.Button>
</div>
<div className="flex-1 flex items-center justify-center sm:items-stretch sm:justify-start">
<div className="flex-shrink-0 flex items-center text-white">
<img
className="h-8 w-auto"
src="https://supabase.com/images/logo-dark.png"
alt="supabase"
/>
</div>
<div className="hidden sm:block sm:ml-6">
<div className="flex space-x-4">
{Navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={classNames(
item.current
? 'bg-gray-900 text-white'
: 'text-gray-300 hover:bg-gray-700 hover:text-white',
'px-3 py-2 rounded-md text-sm font-medium'
)}
aria-current={item.current ? 'page' : undefined}
>
{item.name}
</a>
))}
</div>
</div>
</div>
<div className="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
{/** notifications */}
{AuthUser() ? <MenuLogado /> : <MenuNotLogado />}
</div>
</div>
</div>
<Disclosure.Panel className="sm:hidden">
<div className="px-2 pt-2 pb-3 space-y-1">
{Navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={classNames(
item.current
? 'bg-gray-900 text-white'
: 'text-gray-300 hover:bg-gray-700 hover:text-white',
'block px-3 py-2 rounded-md text-base font-medium'
)}
aria-current={item.current ? 'page' : undefined}
>
{item.name}
</a>
))}
</div>
</Disclosure.Panel>
</>
)}
</Disclosure>
)
}

View File

@@ -1,70 +0,0 @@
import { Fragment } from 'react'
import { Menu, Transition } from '@headlessui/react'
import { UserCircleIcon } from '@heroicons/react/outline'
import classNames from '../../utils/classsesNames'
import { SignOut } from '../../hooks/authUser'
const MenuLogado = () => (
<Menu as="div" className="ml-3 relative">
{({ open }) => (
<>
<div>
<Menu.Button className="bg-gray-800 flex text-sm rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white">
<span className="sr-only">Open user menu</span>
<UserCircleIcon className="h-8 w-8 text-white" />
</Menu.Button>
</div>
<Transition
show={open}
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
static
className="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"
>
<Menu.Item>
{({ active }) => (
<a
href="/profile"
className={classNames(
active ? 'bg-gray-100' : '',
'block px-4 py-2 text-sm text-gray-700'
)}
>
Your Profile
</a>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<a
href="/#"
className={classNames(
active ? 'bg-gray-100' : '',
'block px-4 py-2 text-sm text-gray-700'
)}
>
Settings
</a>
)}
</Menu.Item>
<Menu.Item>
<button onClick={() => SignOut()} className="block px-4 py-2 text-sm text-gray-700">
Sign out
</button>
</Menu.Item>
</Menu.Items>
</Transition>
</>
)}
</Menu>
)
export default MenuLogado

View File

@@ -1,11 +0,0 @@
import React from 'react'
import Link from 'next/link'
const MenuNotLogado = () => (
<div className="flex space-x-4">
<Link href="/auth">
<a className="text-white hover:bg-gray-700 px-3 py-2 rounded-md text-sm font-medium">LOGIN</a>
</Link>
</div>
)
export default MenuNotLogado

View File

@@ -1,7 +0,0 @@
const Navigation = [
{ name: 'Home', href: '/', current: true },
{ name: 'Jobs', href: '#', current: false },
{ name: 'Developers', href: '#', current: false },
]
export default Navigation

View File

@@ -1,71 +0,0 @@
import { useEffect, useState, createContext, useContext } from 'react'
import { supabase } from '../utils/initSupabase'
import { useRouter } from 'next/router'
export const SignOut = async () => {
await supabase.auth.signOut()
}
export const RequireAuth = () => {
const { user } = useUser()
const router = useRouter()
useEffect(() => {
if (!user) {
router.push('/auth')
}
}, [user, router])
}
export const AuthRedirect = () => {
const { user } = useUser()
const router = useRouter()
useEffect(() => {
if (user) {
router.push('/profile')
}
}, [user, router])
}
export const UserContext = createContext()
export const UserContextProvider = (props) => {
const [session, setSession] = useState(false)
const [user, setUser] = useState(false)
useEffect(() => {
const session = supabase.auth.session()
setSession(session)
setUser(session?.user ?? false)
const { data: authListener } = supabase.auth.onAuthStateChange(async (event, session) => {
setSession(session)
setUser(session?.user ?? false)
})
return () => {
authListener.unsubscribe()
}
}, [])
const value = {
session,
user,
}
return <UserContext.Provider value={value} {...props} />
}
export const useUser = () => {
const context = useContext(UserContext)
if (context === undefined) {
throw new Error(`useUser must be used within a UserContextProvider.`)
}
return context
}
const AuthUser = () => {
const { user } = useUser()
return user
}
export default AuthUser

View File

@@ -1,27 +0,0 @@
{
"name": "nextjs-auth-tailwind",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"format": "prettier --write './**/*.{js,jsx,ts,tsx,css,md,json}' --config ./.prettierrc"
},
"dependencies": {
"@headlessui/react": "^1.2.0",
"@heroicons/react": "^1.0.1",
"@supabase/supabase-js": "^1.11.15",
"@supabase/ui": "^0.26.1",
"@tailwindcss/typography": "^0.4.0",
"next": "12.0.9",
"react": "17.0.2",
"react-dom": "17.0.2"
},
"devDependencies": {
"autoprefixer": "^10.2.5",
"postcss": "^8.3.0",
"prettier": "^2.3.0",
"tailwindcss": "^2.1.2"
}
}

View File

@@ -1,15 +0,0 @@
import React from 'react'
import { UserContextProvider } from '../hooks/authUser'
import 'tailwindcss/tailwind.css'
import '../styles/globals.css'
export default function MyApp({ Component, pageProps }) {
return (
<main className={'dark'}>
<UserContextProvider>
<Component {...pageProps} />
</UserContextProvider>
</main>
)
}

View File

@@ -1,36 +0,0 @@
import React from 'react'
import { AuthRedirect } from '../hooks/authUser'
import { supabase } from '../utils/initSupabase'
import { Auth, Card, Typography, Space } from '@supabase/ui'
import Head from '../components/Head'
import Header from '../components/Header'
const AuthPage = () => {
AuthRedirect()
return (
<>
<Head />
<Header />
<div className="authcontainer">
<Card>
<Space direction="vertical" size={8}>
<div>
<Typography.Title level={3}>Welcome</Typography.Title>
</div>
<Auth
supabaseClient={supabase}
providers={['google', 'github']}
view={'sign_in'}
socialLayout="horizontal"
socialButtonSize="xlarge"
/>
</Space>
</Card>
</div>
</>
)
}
export default AuthPage

View File

@@ -1,44 +0,0 @@
import Header from '../components/Header'
import Head from '../components/Head'
const Index = () => {
return (
<>
<Head />
<Header />
<main className="mt-10 mx-auto max-w-7xl px-4 sm:mt-12 sm:px-6 md:mt-16 lg:mt-20 lg:px-8 xl:mt-28">
<div className="sm:text-center lg:text-left">
<h1 className="text-4xl tracking-tight font-extrabold text-white sm:text-5xl md:text-6xl">
<span className="block xl:inline">The Open Source</span>{' '}
<span className="block text-green-400 xl:inline">Firebase Alternative</span>
</h1>
<p className="mt-3 text-base text-white sm:mt-5 sm:text-lg sm:max-w-xl sm:mx-auto md:mt-5 md:text-xl lg:mx-0">
Create a backend in less than 2 minutes. Start your project with a Postgres Database,
Authentication, instant APIs, Realtime subscriptions and Storage.
</p>
<div className="mt-5 sm:mt-8 sm:flex sm:justify-center lg:justify-start">
<div className="rounded-md shadow">
<a
href="#"
className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-green-400 hover:bg-green-600 md:py-4 md:text-lg md:px-10"
>
Get started
</a>
</div>
<div className="mt-3 sm:mt-0 sm:ml-3">
<a
href="#"
className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-green-400 bg-indigo-100 hover:bg-indigo-200 md:py-4 md:text-lg md:px-10"
>
Live demo
</a>
</div>
</div>
</div>
</main>
</>
)
}
export default Index

View File

@@ -1,32 +0,0 @@
import React from 'react'
import { useUser, RequireAuth } from '../hooks/authUser'
import { Card, Typography, Space } from '@supabase/ui'
import Header from '../components/Header'
export default function Profile() {
RequireAuth()
const { user } = useUser()
return (
<>
<Header />
{user && (
<div style={{ maxWidth: '620px', margin: '96px auto' }}>
<Card>
<Space direction="vertical" size={6}>
<Typography.Text>you're signed in</Typography.Text>
<Typography.Text strong>Email: {user.email}</Typography.Text>
<Typography.Text type="success">User data:</Typography.Text>
<Typography.Text>
<pre>{JSON.stringify(user, null, 2)}</pre>
</Typography.Text>
</Space>
</Card>
</div>
)}
</>
)
}

View File

@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,4 +0,0 @@
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,29 +0,0 @@
body {
background: #181818;
}
/*
* Auth
*/
.authcontainer {
max-width: 420px;
margin: 96px auto;
}
.authcontainer a {
color: #8b5cf6 !important;
}
.authcontainer button[type='submit'] {
background: #8b5cf6 !important;
}
.authcontainer input:focus {
border-color: #8b5cf6 !important;
}
.authcontainer .sbui-typography-text-danger {
top: 3px !important;
position: absolute !important;
}

View File

@@ -1,11 +0,0 @@
module.exports = {
purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [require('@tailwindcss/typography')],
}

View File

@@ -1,5 +0,0 @@
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default classNames

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +0,0 @@
# Update these with your Supabase details from your project settings > API
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key

View File

@@ -1,34 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel

View File

@@ -1,5 +0,0 @@
# Example: Supabase authentication client- and server-side (API routes), and SSR with auth cookie.
This example shows how to use Supabase auth both on the client and server in both [API routes](https://nextjs.org/docs/api-routes/introduction) and when using [server side rendering (SSR)](https://nextjs.org/docs/basic-features/pages#server-side-rendering).
This example is the same as [nextjs-with-supabase-auth](https://github.com/supabase/supabase/tree/master/examples/nextjs-with-supabase-auth) however it does not use the @supabase/ui Auth component.

View File

@@ -1,209 +0,0 @@
import React, { useEffect, useState } from 'react'
function Auth(props) {
const { supabaseClient, authView, setAuthView } = props
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [message, setMessage] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
const handleSignIn = async (e) => {
e.preventDefault()
setError('')
setLoading(true)
const { error: signInError } = await supabaseClient.auth.signIn({
email,
password,
})
if (signInError) setError(signInError.message)
setLoading(false)
}
const handleSignUp = async (e) => {
e.preventDefault()
setError('')
setLoading(true)
const { error: signUpError } = await supabaseClient.auth.signUp({
email,
password,
})
if (signUpError) setError(signUpError.message)
setLoading(false)
}
const handlePasswordReset = async (e) => {
e.preventDefault()
setError('')
setMessage('')
setLoading(true)
const { error } = await supabaseClient.auth.api.resetPasswordForEmail(email)
if (error) setError(error.message)
else setMessage('Check your email for the password reset link')
setLoading(false)
}
const handleMagicLinkSignIn = async (e) => {
e.preventDefault()
setError('')
setMessage('')
setLoading(true)
const { error } = await supabaseClient.auth.signIn({ email })
if (error) setError(error.message)
else setMessage('Check your email for the magic link')
setLoading(false)
}
return (
<>
{loading && <h3>Loading..</h3>}
{error && <div style={{ color: 'red' }}>{error}</div>}
{message && <div style={{ color: 'green' }}>{message}</div>}
{authView === 'sign_in' ? (
<>
<h4>Sign in</h4>
<form onSubmit={(e) => handleSignIn(e)}>
<label htmlFor="sign-in__email">Email</label>
<input
id="sign-in__email"
label="Email address"
autoComplete="email"
placeholder="Type in your email address"
defaultValue={email}
onChange={(e) => setEmail(e.target.value)}
/>
<label htmlFor="sign-in__password">Password</label>
<input
id="sign-in__password"
label="Password"
type="password"
defaultValue={password}
autoComplete="current-password"
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">SignIn</button>
</form>
<hr />
<a onClick={() => setAuthView('sign_up')}>Don't have an account? Sign up</a>
<a onClick={() => setAuthView('forgotten_password')}>Forgot my password</a>
<hr />
<a onClick={() => setAuthView('magic_link')}>Send magic link email</a>
</>
) : authView === 'sign_up' ? (
<>
<h4>Sign up</h4>
<form onSubmit={(e) => handleSignUp(e)}>
<label htmlFor="sign-up__email">Email</label>
<input
id="sign-up__email"
label="Email address"
autoComplete="email"
placeholder="Type in your email address"
defaultValue={email}
onChange={(e) => setEmail(e.target.value)}
/>
<label htmlFor="sign-up__password">Password</label>
<input
id="sign-up__password"
label="Password"
type="password"
defaultValue={password}
autoComplete="current-password"
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">SignUp</button>
</form>
<hr />
<a onClick={() => setAuthView('sign_in')}>Already have an account, Sign in</a>
<a onClick={() => setAuthView('forgotten_password')}>Forgot my password</a>
<hr />
<a onClick={() => setAuthView('magic_link')}>Send magic link email</a>
</>
) : authView === 'forgotten_password' ? (
<>
<h4>Forgotten password</h4>
<form onSubmit={handlePasswordReset}>
<label htmlFor="forgotten_password__email">Email</label>
<input
id="forgotten_password__email"
label="Email address"
autoComplete="email"
placeholder="Type in your email address"
defaultValue={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button type="submit">Send reset password instructions</button>
</form>
<hr />
<a onClick={() => setAuthView('sign_up')}>Don't have an account? Sign up</a>
<a onClick={() => setAuthView('sign_in')}>Already have an account, Sign in</a>
<hr />
<a onClick={() => setAuthView('magic_link')}>Send magic link email</a>
</>
) : authView === 'magic_link' ? (
<>
<h4>Magic link</h4>
<form onSubmit={handleMagicLinkSignIn}>
<input
label="Email address"
autoComplete="email"
placeholder="Type in your email address"
defaultValue={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button type="submit">Send magic link</button>
</form>
<hr />
<a onClick={() => setAuthView('sign_up')}>Don't have an account? Sign up</a>
<a onClick={() => setAuthView('sign_in')}>Already have an account, Sign in</a>
</>
) : null}
</>
)
}
function UpdatePassword({ supabaseClient }) {
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const [message, setMessage] = useState('')
const [loading, setLoading] = useState(false)
const handlePasswordReset = async (e) => {
e.preventDefault()
setError('')
setMessage('')
setLoading(true)
const { error } = await supabaseClient.auth.update({ password })
if (error) setError(error.message)
else setMessage('Your password has been updated')
setLoading(false)
}
return (
<>
{loading && <h3>Loading..</h3>}
{error && <div style={{ color: 'red' }}>{error}</div>}
{message && <div style={{ color: 'green' }}>{message}</div>}
<h4>Set a new password</h4>
<form onSubmit={handlePasswordReset}>
<input
label="New password"
placeholder="Enter your new password"
type="password"
onChange={(e) => setPassword(e.target.value)}
/>
<button block size="large" htmlType="submit">
Update password
</button>
</form>
</>
)
}
Auth.UpdatePassword = UpdatePassword
export default Auth

View File

@@ -1,38 +0,0 @@
import React, { useEffect, useState, createContext, useContext } from 'react'
const UserContext = createContext({ user: null, session: null })
export const UserContextProvider = (props) => {
const { supabaseClient } = props
const [session, setSession] = useState(null)
const [user, setUser] = useState(null)
useEffect(() => {
const session = supabaseClient.auth.session()
setSession(session)
setUser(session?.user ?? null)
const { data: authListener } = supabaseClient.auth.onAuthStateChange(async (event, session) => {
setSession(session)
setUser(session?.user ?? null)
})
return () => {
authListener.unsubscribe()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const value = {
session,
user,
}
return <UserContext.Provider value={value} {...props} />
}
export const useUser = () => {
const context = useContext(UserContext)
if (context === undefined) {
throw new Error(`useUser must be used within a UserContextProvider.`)
}
return context
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +0,0 @@
{
"name": "with-supabase-auth",
"version": "1.0.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@supabase/supabase-js": "^1.2.1",
"next": "12.0.9",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"swr": "0.2.3"
},
"license": "MIT"
}

View File

@@ -1,13 +0,0 @@
import { UserContextProvider } from '../lib/UserContext'
import { supabase } from '../utils/initSupabase'
import './../style.css'
export default function MyApp({ Component, pageProps }) {
return (
<main>
<UserContextProvider supabaseClient={supabase}>
<Component {...pageProps} />
</UserContextProvider>
</main>
)
}

View File

@@ -1,8 +0,0 @@
/**
* NOTE: this file is only needed if you're doing SSR (getServerSideProps)!
*/
import { supabase } from '../../utils/initSupabase'
export default function handler(req, res) {
supabase.auth.api.setAuthCookie(req, res)
}

View File

@@ -1,13 +0,0 @@
import { supabase } from '../../utils/initSupabase'
// Example of how to verify and get user data server-side.
const getUser = async (req, res) => {
const token = req.headers.token
const { data: user, error } = await supabase.auth.api.getUser(token)
if (error) return res.status(401).json({ error: error.message })
return res.status(200).json(user)
}
export default getUser

View File

@@ -1,95 +0,0 @@
import Link from 'next/link'
import useSWR from 'swr'
import { supabase } from '../utils/initSupabase'
import { useEffect, useState } from 'react'
import Auth from './../components/Auth'
import { useUser } from '../lib/UserContext'
const fetcher = (url, token) =>
fetch(url, {
method: 'GET',
headers: new Headers({ 'Content-Type': 'application/json', token }),
credentials: 'same-origin',
}).then((res) => res.json())
const Index = () => {
const { user, session } = useUser()
const { data, error } = useSWR(session ? ['/api/getUser', session.access_token] : null, fetcher)
const [authView, setAuthView] = useState('sign_in')
useEffect(() => {
const { data: authListener } = supabase.auth.onAuthStateChange((event, session) => {
if (event === 'PASSWORD_RECOVERY') setAuthView('update_password')
if (event === 'USER_UPDATED') setTimeout(() => setAuthView('sign_in'), 1000)
// Send session to /api/auth route to set the auth cookie.
// NOTE: this is only needed if you're doing SSR (getServerSideProps)!
fetch('/api/auth', {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
credentials: 'same-origin',
body: JSON.stringify({ event, session }),
}).then((res) => res.json())
})
return () => {
authListener.unsubscribe()
}
}, [])
const View = () => {
if (!user)
return (
<>
<div>
<img src="https://app.supabase.com/img/supabase-dark.svg" width="96" />
<h2>
Supabase Auth <br />
with NextJS SSR
</h2>
</div>
<Auth supabaseClient={supabase} authView={authView} setAuthView={setAuthView} />
</>
)
return (
<>
{authView === 'update_password' && <Auth.UpdatePassword supabaseClient={supabase} />}
{user && (
<>
<h4>You're signed in</h4>
<h5>Email: {user.email}</h5>
<button type="outline" onClick={() => supabase.auth.signOut()}>
Log out
</button>
<hr />
{error && <div style={{ color: 'red' }}>Failed to fetch user!</div>}
{data && !error ? (
<>
<div style={{ color: 'green' }}>
User data retrieved server-side (in API route):
</div>
<pre>{JSON.stringify(data, null, 2)}</pre>
</>
) : (
<div>Loading...</div>
)}
<Link href="/profile">
<a>SSR example with getServerSideProps</a>
</Link>
</>
)}
</>
)
}
return (
<div style={{ maxWidth: '520px', margin: '96px auto' }}>
<View />
</div>
)
}
export default Index

View File

@@ -1,37 +0,0 @@
import Link from 'next/link'
import { supabase } from '../utils/initSupabase'
export default function Profile({ user }) {
return (
<div style={{ maxWidth: '520px', margin: '96px auto' }}>
<h4>You're signed in</h4>
<h5>Email: {user.email}</h5>
<hr />
<div style={{ color: 'green' }}>
User data retrieved server-side (from Cookie in getServerSideProps):
</div>
<div>
<pre>{JSON.stringify(user, null, 2)}</pre>
</div>
<div>
<Link href="/">
<a>Static example with useSWR</a>
</Link>
</div>
</div>
)
}
export async function getServerSideProps({ req }) {
const { user } = await supabase.auth.api.getUserByCookie(req)
if (!user) {
// If no user, redirect to index.
return { props: {}, redirect: { destination: '/', permanent: false } }
}
// If there is a user, return it.
return { props: { user } }
}

View File

@@ -1,29 +0,0 @@
body {
font-family: Helvetica, Arial, Sans-Serif;
}
a {
display: block;
margin-bottom: 8px;
cursor: pointer;
text-decoration: underline;
}
input {
display: block;
width: 320px;
font-size: 16px;
padding: 8px;
margin-bottom: 16px;
}
button {
margin-bottom: 32px;
cursor: pointer;
}
pre {
background: black;
color: white;
padding: 16px;
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
SUPABASE_SERVICE_KEY="{SERVICE_KEY}"
SUPABASE_URL="https://{YOUR_INSTANCE_NAME}.supabase.co"

View File

@@ -1,6 +0,0 @@
node_modules
/.cache
/build
/public/build
.env

View File

@@ -1,53 +0,0 @@
# Remix Auth - Supabase Strategy with redirectTo
Authentication using `signInWithEmail` handling redirectTo.
## Setup
1. Copy `.env.example` to create a new file `.env`:
```sh
cp .env.example .env
```
2. Go to https://app.supabase.com/project/{PROJECT}/api?page=auth to find your secrets
3. Add your `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE` in `.env`
```env
SUPABASE_SERVICE_KEY="{SERVICE_KEY}"
SUPABASE_URL="https://{YOUR_INSTANCE_NAME}.supabase.co"
```
## Using the Remix Auth & SupabaseStrategy 🚀
SupabaseStrategy provides `checkSession` working like Remix Auth `isAuthenticated` but handles token refresh
You must use `checkSession` instead of `isAuthenticated`
## Example
This is using Remix Auth, `remix-auth-supabase` and `supabase-js` packages.
> Thanks to Remix, we can securely use server only authentication with `supabase.auth.api.signInWithEmail`
>
> This function should only be called on a server (`loader` or `action` functions).
>
> **⚠️ Never expose your `service_role` key in the browser**
The `/login` route renders a form with a email and password input. After a submit it runs some validations and store `user` object, `access_token` and `refresh_token` in the session.
The `/private` routes redirects the user to `/login` if it's not logged-in, or shows the user email and a logout form if it's logged-in.
The `/private/profile` routes redirects the user to `/login?redirectTo=/private/profile` if it's not logged-in, or shows the user email and a logout form if it's logged-in.
If the user go to `/private/profile` and is not logged-in, it'll be redirected here after login success.
**Handle refreshing of tokens** (if expired) or redirects to `/login` if it fails
More use cases can be found on [Remix Auth Supabase - Use cases](https://github.com/mitchelvanbever/remix-auth-supabase#using-the-authenticator--strategy-)
## Related Links
- [Remix Auth](https://github.com/sergiodxa/remix-auth)
- [Remix Auth Supabase](https://github.com/mitchelvanbever/remix-auth-supabase)
- [supabase-js](https://github.com/supabase/supabase-js)

View File

@@ -1,53 +0,0 @@
import { createCookieSessionStorage } from 'remix'
import { Authenticator, AuthorizationError } from 'remix-auth'
import { SupabaseStrategy } from 'remix-auth-supabase'
import { supabaseClient } from '~/supabase'
import type { Session } from '~/supabase'
export const sessionStorage = createCookieSessionStorage({
cookie: {
name: 'sb',
httpOnly: true,
path: '/',
sameSite: 'lax',
secrets: ['s3cr3t'], // This should be an env variable
secure: process.env.NODE_ENV === 'production',
},
})
export const supabaseStrategy = new SupabaseStrategy(
{
supabaseClient,
sessionStorage,
sessionKey: 'sb:session',
sessionErrorKey: 'sb:error',
},
async ({ req, supabaseClient }) => {
const form = await req.formData()
const email = form?.get('email')
const password = form?.get('password')
if (!email) throw new AuthorizationError('Email is required')
if (typeof email !== 'string') throw new AuthorizationError('Email must be a string')
if (!password) throw new AuthorizationError('Password is required')
if (typeof password !== 'string') throw new AuthorizationError('Password must be a string')
return supabaseClient.auth.api
.signInWithEmail(email, password)
.then(({ data, error }): Session => {
if (error || !data) {
throw new AuthorizationError(error?.message ?? 'No user session found')
}
return data
})
}
)
export const authenticator = new Authenticator<Session>(sessionStorage, {
sessionKey: supabaseStrategy.sessionKey,
sessionErrorKey: supabaseStrategy.sessionErrorKey,
})
authenticator.use(supabaseStrategy)

View File

@@ -1,4 +0,0 @@
import { hydrate } from 'react-dom'
import { RemixBrowser } from 'remix'
hydrate(<RemixBrowser />, document)

View File

@@ -1,19 +0,0 @@
import { renderToString } from 'react-dom/server'
import { RemixServer } from 'remix'
import type { EntryContext } from 'remix'
export default function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext
) {
const markup = renderToString(<RemixServer context={remixContext} url={request.url} />)
responseHeaders.set('Content-Type', 'text/html')
return new Response('<!DOCTYPE html>' + markup, {
status: responseStatusCode,
headers: responseHeaders,
})
}

View File

@@ -1,20 +0,0 @@
import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from 'remix'
export default function App() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
{process.env.NODE_ENV === 'development' && <LiveReload />}
</body>
</html>
)
}

View File

@@ -1,20 +0,0 @@
import { Link } from 'remix'
export default function Index() {
return (
<>
<h1>Index page</h1>
<ul>
<li>
<Link to="/private">Go to private page</Link>
</li>
<li>
<Link to="/private/profile">Go to private/profile page</Link>
</li>
<li>
<Link to="/login">Go to login page</Link>
</li>
</ul>
</>
)
}

View File

@@ -1,59 +0,0 @@
import type { ActionFunction, LoaderFunction } from 'remix'
import { Form, json, useLoaderData, useSearchParams } from 'remix'
import { authenticator, sessionStorage, supabaseStrategy } from '~/auth.server'
type LoaderData = {
error: { message: string } | null
}
export const action: ActionFunction = async ({ request }) => {
const data = await request.clone().formData()
const redirectTo = (data.get('redirectTo') ?? '/private') as string
await authenticator.authenticate('sb', request, {
successRedirect: redirectTo,
failureRedirect: '/login',
})
}
export const loader: LoaderFunction = async ({ request }) => {
const redirectTo = new URL(request.url).searchParams.get('redirectTo') ?? '/private'
await supabaseStrategy.checkSession(request, {
successRedirect: redirectTo,
})
const session = await sessionStorage.getSession(request.headers.get('Cookie'))
const error = session.get(authenticator.sessionErrorKey) as LoaderData['error']
return json<LoaderData>({ error })
}
export default function Screen() {
const [searchParams] = useSearchParams()
const { error } = useLoaderData<LoaderData>()
return (
<Form method="post">
{error && <div>{error.message}</div>}
<input
name="redirectTo"
value={searchParams.get('redirectTo') ?? undefined}
hidden
readOnly
/>
<div>
<label htmlFor="email">Email</label>
<input type="email" name="email" id="email" />
</div>
<div>
<label htmlFor="password">Password</label>
<input type="password" name="password" id="password" />
</div>
<button>Log In</button>
</Form>
)
}

View File

@@ -1,32 +0,0 @@
import type { ActionFunction, LoaderFunction } from 'remix'
import { Form, json, useLoaderData } from 'remix'
import { authenticator, supabaseStrategy } from '~/auth.server'
type LoaderData = { email?: string; id?: string }
export const action: ActionFunction = async ({ request }) => {
await authenticator.logout(request, { redirectTo: '/login' })
}
export const loader: LoaderFunction = async ({ request }) => {
const session = await supabaseStrategy.checkSession(request, {
failureRedirect: '/login?redirectTo=/private/profile',
})
return json<LoaderData>({ email: session.user?.email, id: session.user?.id })
}
export default function Screen() {
const { email, id } = useLoaderData<LoaderData>()
return (
<>
<h1>Hello {email}</h1>
<h2>Welcome in Private Profile</h2>
<h3>Your user id is {id}</h3>
<Form method="post">
<button>Log Out</button>
</Form>
</>
)
}

View File

@@ -1,31 +0,0 @@
import type { ActionFunction, LoaderFunction } from 'remix'
import { Form, json, useLoaderData } from 'remix'
import { authenticator, supabaseStrategy } from '~/auth.server'
type LoaderData = { email?: string }
export const action: ActionFunction = async ({ request }) => {
await authenticator.logout(request, { redirectTo: '/login' })
}
export const loader: LoaderFunction = async ({ request }) => {
const session = await supabaseStrategy.checkSession(request, {
failureRedirect: '/login',
})
return json<LoaderData>({ email: session.user?.email })
}
export default function Screen() {
const { email } = useLoaderData<LoaderData>()
return (
<>
<h1>Hello {email}</h1>
<h2>Welcome in Private index</h2>
<Form method="post">
<button>Log Out</button>
</Form>
</>
)
}

View File

@@ -1,42 +0,0 @@
import { createClient } from '@supabase/supabase-js'
import type { Session } from '@supabase/supabase-js'
declare global {
namespace NodeJS {
interface ProcessEnv {
SUPABASE_URL: string
SUPABASE_SERVICE_KEY: string
}
}
}
if (!process.env.SUPABASE_URL) {
throw new Error('SUPABASE_URL is required')
}
if (!process.env.SUPABASE_SERVICE_KEY) {
throw new Error('SUPABASE_SERVICE_KEY is required')
}
// Supabase options example (build your own :))
// https://supabase.com/docs/reference/javascript/initializing#with-additional-parameters
// const supabaseOptions = {
// fetch, // see ⚠️ cloudflare
// schema: "public",
// persistSession: true,
// autoRefreshToken: true,
// detectSessionInUrl: true,
// headers: { "x-application-name": "{my-site-name}" }
// };
// ⚠️ cloudflare needs you define fetch option : https://github.com/supabase/supabase-js#custom-fetch-implementation
// Use Remix fetch polyfill for node (See https://remix.run/docs/en/v1/other-api/node)
export const supabaseClient = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_SERVICE_KEY
// supabaseOptions
)
export { Session }

File diff suppressed because it is too large Load Diff

View File

@@ -1,34 +0,0 @@
{
"private": true,
"name": "remix-app",
"description": "",
"license": "",
"scripts": {
"build": "remix build",
"dev": "node -r dotenv/config node_modules/.bin/remix dev",
"postinstall": "remix setup node",
"start": "remix-serve build"
},
"dependencies": {
"@remix-run/react": "^1.1.1",
"@remix-run/serve": "^1.5.0",
"@supabase/supabase-js": "^1.29.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"remix": "^1.1.1",
"remix-auth": "^3.2.1",
"remix-auth-supabase": "^3.0.1"
},
"devDependencies": {
"@remix-run/dev": "^1.1.1",
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
"dotenv": "^10.0.0",
"prettier": "^2.5.1",
"typescript": "^4.5.4"
},
"engines": {
"node": ">=14"
},
"sideEffects": false
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,10 +0,0 @@
/**
* @type {import('@remix-run/dev/config').AppConfig}
*/
module.exports = {
appDirectory: 'app',
browserBuildDirectory: 'public/build',
publicPath: '/build/',
serverBuildDirectory: 'build',
devServerPort: 8002,
}

View File

@@ -1,2 +0,0 @@
/// <reference types="@remix-run/dev" />
/// <reference types="@remix-run/node/globals" />

View File

@@ -1,19 +0,0 @@
{
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2021"],
"esModuleInterop": true,
"jsx": "react-jsx",
"moduleResolution": "node",
"resolveJsonModule": true,
"target": "ES2019",
"strict": true,
"baseUrl": ".",
"paths": {
"~/*": ["./app/*"]
},
// Remix takes care of building everything in `remix build`.
"noEmit": true
}
}

View File

@@ -0,0 +1 @@
uvqnpvxfahfkegscgmjl

View File

@@ -4,7 +4,7 @@
"private": true,
"dependencies": {
"@supabase/auth-ui-react": "^0.2.1",
"@supabase/supabase-js": "^2.0.0-rc.12",
"@supabase/supabase-js": "^2.0.0",
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",

View File

@@ -3,7 +3,7 @@
// This enables autocomplete, go to definition, etc.
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.0.0-rc.12'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.0.0'
import { corsHeaders } from '../_shared/cors.ts'
console.log(`Function "get-tshirt-competition" up and running!`)

View File

@@ -3,7 +3,7 @@
// This enables autocomplete, go to definition, etc.
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.0.0-rc.12'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.0.0'
import { corsHeaders } from '../_shared/cors.ts'
console.log(`Function "select-from-table-with-auth-rls" up and running!`)

View File

@@ -2,10 +2,10 @@
// https://deno.land/manual/getting_started/setup_your_environment
// This enables autocomplete, go to definition, etc.
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
import { serve } from 'https://deno.land/std@0.132.0/http/server.ts'
// esm.sh is used to compile stripe-node to be compatible with ES modules.
import Stripe from 'https://esm.sh/stripe@9.6.0?target=deno&no-check'
import Stripe from 'https://esm.sh/stripe@10.13.0?target=deno&deno-std=0.132.0'
const stripe = Stripe(Deno.env.get('STRIPE_API_KEY'), {
// This is needed to use the Fetch API rather than relying on the Node http

View File

@@ -1,3 +0,0 @@
# Update these with your Supabase details from your project settings > API
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key

View File

@@ -1,34 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel

View File

@@ -1,9 +0,0 @@
# Example: Supabase authentication client- and server-side (API routes), and SSR with auth cookie.
This example shows how to use Supabase auth both on the client and server in both [API routes](https://nextjs.org/docs/api-routes/introduction) and when using [server side rendering (SSR)](https://nextjs.org/docs/basic-features/pages#server-side-rendering). This example uses helper functions that can be viewed at [supabase auth helpers repository](https://github.com/supabase-community/supabase-auth-helpers).
## Deploy with Vercel
The Vercel deployment will guide you through creating a Supabase account and project. After installation of the Supabase integration, all relevant environment variables will be set up so that the project is usable immediately after deployment 🚀
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fsupabase%2Fsupabase%2Ftree%2Fmaster%2Fexamples%2Fnextjs-with-supabase-auth&project-name=nextjs-with-supabase-auth&repository-name=nextjs-with-supabase-auth&integration-ids=oac_jUduyjQgOyzev1fjrW83NYOv)

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +0,0 @@
{
"name": "with-supabase-auth",
"version": "1.0.0",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@supabase/supabase-auth-helpers": "^1.0.6",
"next": "^12.0.9",
"react": "17.0.2",
"react-dom": "17.0.2",
"swr": "^0.5.5"
},
"license": "MIT"
}

View File

@@ -1,13 +0,0 @@
import { UserProvider } from '@supabase/supabase-auth-helpers/react'
import { supabaseClient } from '@supabase/supabase-auth-helpers/nextjs'
import './../style.css'
export default function MyApp({ Component, pageProps }) {
return (
<main className={'dark'}>
<UserProvider supabaseClient={supabaseClient}>
<Component {...pageProps} />
</UserProvider>
</main>
)
}

View File

@@ -1,3 +0,0 @@
import { handleAuth } from '@supabase/supabase-auth-helpers/nextjs'
export default handleAuth()

View File

@@ -1,43 +0,0 @@
import { useUser, Auth } from '@supabase/supabase-auth-helpers/react'
import { supabaseClient } from '@supabase/supabase-auth-helpers/nextjs'
import { useEffect, useState } from 'react'
const LoginPage = () => {
const { user, error } = useUser()
const [data, setData] = useState({})
useEffect(() => {
async function loadData() {
const { data } = await supabaseClient.from('test').select('*')
setData(data)
}
// Only run query once user is logged in.
if (user) loadData()
}, [user])
if (!user)
return (
<>
{error && <p>{error.message}</p>}
<Auth
// view="update_password"
supabaseClient={supabaseClient}
providers={['google', 'github']}
socialLayout="horizontal"
socialButtonSize="xlarge"
/>
</>
)
return (
<>
<button onClick={() => supabaseClient.auth.signOut()}>Sign out</button>
<p>user:</p>
<pre>{JSON.stringify(user, null, 2)}</pre>
<p>client-side data fetching with RLS</p>
<pre>{JSON.stringify(data, null, 2)}</pre>
</>
)
}
export default LoginPage

View File

@@ -1,13 +0,0 @@
// pages/profile.js
import { withAuthRequired } from '@supabase/supabase-auth-helpers/nextjs'
export default function Profile({ user }) {
return (
<>
<div>Hello {user.email}</div>
<pre>{JSON.stringify(user, null, 2)}</pre>
</>
)
}
export const getServerSideProps = withAuthRequired()

View File

@@ -1,4 +0,0 @@
body {
background: #3d3d3d;
font-family: Helvetica, Arial, Sans-Serif;
}

File diff suppressed because it is too large Load Diff

View File

@@ -25,26 +25,46 @@ export const useStore = (props) => {
fetchChannels(setChannels)
// Listen for new and deleted messages
const messageListener = supabase
.from('messages')
.on('INSERT', (payload) => handleNewMessage(payload.new))
.on('DELETE', (payload) => handleDeletedMessage(payload.old))
.channel('public:messages')
.on(
'postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'messages' },
(payload) => handleNewMessage(payload.new)
)
.on(
'postgres_changes',
{ event: 'DELETE', schema: 'public', table: 'messages' },
(payload) => handleDeletedMessage(payload.old)
)
.subscribe()
// Listen for changes to our users
const userListener = supabase
.from('users')
.on('*', (payload) => handleNewOrUpdatedUser(payload.new))
.channel('public:users')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: 'users' },
(payload) => handleNewOrUpdatedUser(payload.new)
)
.subscribe()
// Listen for new and deleted channels
const channelListener = supabase
.from('channels')
.on('INSERT', (payload) => handleNewChannel(payload.new))
.on('DELETE', (payload) => handleDeletedChannel(payload.old))
.channel('public:channels')
.on(
'postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'channels' },
(payload) => handleNewChannel(payload.new)
)
.on(
'postgres_changes',
{ event: 'DELETE', schema: 'public', table: 'channels' },
(payload) => handleDeletedChannel(payload.old)
)
.subscribe()
// Cleanup on unmount
return () => {
messageListener.unsubscribe()
userListener.unsubscribe()
channelListener.unsubscribe()
supabase.removeChannel('public:messages')
supabase.removeChannel('public:users')
supabase.removeChannel('public:channels')
}
}, [])
@@ -110,9 +130,9 @@ export const useStore = (props) => {
*/
export const fetchChannels = async (setState) => {
try {
let { body } = await supabase.from('channels').select('*')
if (setState) setState(body)
return body
let { data } = await supabase.from('channels').select('*')
if (setState) setState(data)
return data
} catch (error) {
console.log('error', error)
}
@@ -125,8 +145,8 @@ export const fetchChannels = async (setState) => {
*/
export const fetchUser = async (userId, setState) => {
try {
let { body } = await supabase.from('users').select(`*`).eq('id', userId)
let user = body[0]
let { data } = await supabase.from('users').select(`*`).eq('id', userId)
let user = data[0]
if (setState) setState(user)
return user
} catch (error) {
@@ -140,9 +160,9 @@ export const fetchUser = async (userId, setState) => {
*/
export const fetchUserRoles = async (setState) => {
try {
let { body } = await supabase.from('user_roles').select(`*`)
if (setState) setState(body)
return body
let { data } = await supabase.from('user_roles').select(`*`)
if (setState) setState(data)
return data
} catch (error) {
console.log('error', error)
}
@@ -155,13 +175,13 @@ export const fetchUserRoles = async (setState) => {
*/
export const fetchMessages = async (channelId, setState) => {
try {
let { body } = await supabase
let { data } = await supabase
.from('messages')
.select(`*, author:user_id(*)`)
.eq('channel_id', channelId)
.order('inserted_at', true)
if (setState) setState(body)
return body
if (setState) setState(data)
return data
} catch (error) {
console.log('error', error)
}
@@ -174,8 +194,8 @@ export const fetchMessages = async (channelId, setState) => {
*/
export const addChannel = async (slug, user_id) => {
try {
let { body } = await supabase.from('channels').insert([{ slug, created_by: user_id }])
return body
let { data } = await supabase.from('channels').insert([{ slug, created_by: user_id }]).select()
return data
} catch (error) {
console.log('error', error)
}
@@ -189,8 +209,8 @@ export const addChannel = async (slug, user_id) => {
*/
export const addMessage = async (message, channel_id, user_id) => {
try {
let { body } = await supabase.from('messages').insert([{ message, channel_id, user_id }])
return body
let { data } = await supabase.from('messages').insert([{ message, channel_id, user_id }]).select()
return data
} catch (error) {
console.log('error', error)
}
@@ -202,8 +222,8 @@ export const addMessage = async (message, channel_id, user_id) => {
*/
export const deleteChannel = async (channel_id) => {
try {
let { body } = await supabase.from('channels').delete().match({ id: channel_id })
return body
let { data } = await supabase.from('channels').delete().match({ id: channel_id })
return data
} catch (error) {
console.log('error', error)
}
@@ -215,8 +235,8 @@ export const deleteChannel = async (channel_id) => {
*/
export const deleteMessage = async (message_id) => {
try {
let { body } = await supabase.from('messages').delete().match({ id: message_id })
return body
let { data } = await supabase.from('messages').delete().match({ id: message_id })
return data
} catch (error) {
console.log('error', error)
}

View File

@@ -1,20 +1,20 @@
{
"name": "supabase-slack-clone-basic",
"version": "0.1.1",
"version": "0.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "supabase-slack-clone-basic",
"version": "0.1.1",
"version": "0.2.0",
"license": "MIT",
"dependencies": {
"@supabase/supabase-js": "^1.35.3",
"next": "latest",
"@supabase/supabase-js": "^2.0.0-rc.9",
"next": "*",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"sass": "^1.26.2",
"tailwindcss": "^1.1.4"
"tailwindcss": "^1.9.6"
}
},
"node_modules/@fullhuman/postcss-purgecss": {
@@ -296,56 +296,57 @@
}
},
"node_modules/@supabase/functions-js": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-1.3.3.tgz",
"integrity": "sha512-35vO9niHRtzGe1QSvXKdOfvGPiX2KC44dGpWU6y0/gZCfTIgog/soU9HqABzQC/maVowO3hGLWfez5aN0MKfow==",
"version": "2.0.0-rc.1",
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.0.0-rc.1.tgz",
"integrity": "sha512-cYFaSA28NoTREM4ZrHWaxXjElfY0S8umrjoffTbmI4Rgkg8+4raBnQIi52usfr5MuFpy566vhbLNOcP9kNHjOw==",
"dependencies": {
"cross-fetch": "^3.1.5"
}
},
"node_modules/@supabase/gotrue-js": {
"version": "1.22.15",
"resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-1.22.15.tgz",
"integrity": "sha512-7/mwnd1hR/bpkCmbDvjnwPfWyRcE2B1ZnfxthqgVaZ5oJHS/CQibyuLBL8DA75fxmgY9nIfednDZSydSm6zK0w==",
"version": "2.0.0-rc.4",
"resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.0.0-rc.4.tgz",
"integrity": "sha512-jHWyF1/Rl/1MXybFnHnHVjXYrOuvLzSUACow2Ii2KFD7zURPjPzy5KvOzq6tk+wL92LDgCl88nU1QVvO7dvRog==",
"dependencies": {
"cross-fetch": "^3.0.6"
"cross-fetch": "^3.1.5"
}
},
"node_modules/@supabase/postgrest-js": {
"version": "0.37.2",
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-0.37.2.tgz",
"integrity": "sha512-3Dgx5k3RvtKqc8DvR2BEyh2fVyjZe5P4e0zD1r8dyuVmpaYDaASZ2YeNVgyWXMCWH7xzrj4vepTYlKwfj78QLg==",
"version": "1.0.0-rc.5",
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.0.0-rc.5.tgz",
"integrity": "sha512-rBOyhK+qLd2fdX6WbCW3e9gY9qbwC5Zeso6QWM+00RlRz3k156lxnreVpe4KKGXNT3JMqJwAHXjQ2lbxmtBJqg==",
"dependencies": {
"cross-fetch": "^3.0.6"
"cross-fetch": "^3.1.5"
}
},
"node_modules/@supabase/realtime-js": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-1.7.2.tgz",
"integrity": "sha512-DMUaFIKj7KszGtWTTQbhMmUzZf7UnwYqySsmY+G8HgYxvY3ZaVa+DZD0I6ofgr4OLNr0po/ODM2a4lf5m5GNBg==",
"version": "2.0.0-rc.5",
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.0.0-rc.5.tgz",
"integrity": "sha512-0JiSJjoTmwleufGzBQwEVamI047jnAiemBk8qOkgwyoLOlfGTQk8XSA7aT023BsGVgRNKZqp/jJJAVBht/Rd2w==",
"dependencies": {
"@types/phoenix": "^1.5.4",
"websocket": "^1.0.34"
}
},
"node_modules/@supabase/storage-js": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-1.7.0.tgz",
"integrity": "sha512-f5EBw0wM96hKmnrXhgiqq2Reh9O0NgjKE+jkaKY4jQmfutefqaCAWn+cBzlmHs9h135H2ldaGmhWRFHUSkLt2g==",
"version": "2.0.0-rc.1",
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.0.0-rc.1.tgz",
"integrity": "sha512-OjPtPuKzhzQW6nO/UdwVJVFrzfFnIWt1u+gTQ4+8t1+IBdn7xDHssDteBPkMCd+fYcbMZrqpggbagoFH3FylXw==",
"dependencies": {
"cross-fetch": "^3.1.0"
"cross-fetch": "^3.1.5"
}
},
"node_modules/@supabase/supabase-js": {
"version": "1.35.3",
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-1.35.3.tgz",
"integrity": "sha512-uwO8OVdMFsGZNZ1xQhFz22+PSW0EWYZ5xVq+jQeGz8nhabEu+Q9Uyep/bcNzOpyPJRzbGfxSPRzgAdAxfJgFhw==",
"version": "2.0.0-rc.9",
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.0.0-rc.9.tgz",
"integrity": "sha512-WTb60mEjzkI7FcuQA7KW9IkLltl2I7ojlpBIORwbekuCz5nKDSlo4g6/Wyo4u6Qe97weh3mhFMLGZRorhRq7lg==",
"dependencies": {
"@supabase/functions-js": "^1.3.3",
"@supabase/gotrue-js": "^1.22.14",
"@supabase/postgrest-js": "^0.37.2",
"@supabase/realtime-js": "^1.7.2",
"@supabase/storage-js": "^1.7.0"
"@supabase/functions-js": "^2.0.0-rc.1",
"@supabase/gotrue-js": "^2.0.0-rc.4",
"@supabase/postgrest-js": "^1.0.0-rc.5",
"@supabase/realtime-js": "^2.0.0-rc.5",
"@supabase/storage-js": "^2.0.0-rc.1",
"cross-fetch": "^3.1.5"
}
},
"node_modules/@types/phoenix": {
@@ -723,9 +724,9 @@
"integrity": "sha512-/s3+fwhKf1YK4k7btOImOzCQLpUjS6MaPf0ODTNuT4eTM1Bg4itBpLkydhOzJmpmH6Z9eXFyuuK5czsmzRzwtw=="
},
"node_modules/es5-ext": {
"version": "0.10.61",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz",
"integrity": "sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==",
"version": "0.10.62",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
"integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
"hasInstallScript": true,
"dependencies": {
"es6-iterator": "^2.0.3",
@@ -739,7 +740,7 @@
"node_modules/es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
"integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
"dependencies": {
"d": "1",
"es5-ext": "^0.10.35",
@@ -772,17 +773,17 @@
}
},
"node_modules/ext": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz",
"integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==",
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
"dependencies": {
"type": "^2.5.0"
"type": "^2.7.2"
}
},
"node_modules/ext/node_modules/type": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz",
"integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ=="
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
"integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
},
"node_modules/fill-range": {
"version": "7.0.1",
@@ -972,7 +973,7 @@
"node_modules/is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
"node_modules/js-tokens": {
"version": "4.0.0",
@@ -1022,7 +1023,7 @@
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/nanoid": {
"version": "3.3.2",
@@ -1117,9 +1118,9 @@
}
},
"node_modules/node-gyp-build": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz",
"integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==",
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz",
"integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
@@ -1719,7 +1720,7 @@
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/type": {
"version": "1.2.0",
@@ -1762,7 +1763,7 @@
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/websocket": {
"version": "1.0.34",
@@ -1783,7 +1784,7 @@
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
@@ -1805,7 +1806,7 @@
"node_modules/yaeti": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
"integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=",
"integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==",
"engines": {
"node": ">=0.10.32"
}
@@ -1965,56 +1966,57 @@
"optional": true
},
"@supabase/functions-js": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-1.3.3.tgz",
"integrity": "sha512-35vO9niHRtzGe1QSvXKdOfvGPiX2KC44dGpWU6y0/gZCfTIgog/soU9HqABzQC/maVowO3hGLWfez5aN0MKfow==",
"version": "2.0.0-rc.1",
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.0.0-rc.1.tgz",
"integrity": "sha512-cYFaSA28NoTREM4ZrHWaxXjElfY0S8umrjoffTbmI4Rgkg8+4raBnQIi52usfr5MuFpy566vhbLNOcP9kNHjOw==",
"requires": {
"cross-fetch": "^3.1.5"
}
},
"@supabase/gotrue-js": {
"version": "1.22.15",
"resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-1.22.15.tgz",
"integrity": "sha512-7/mwnd1hR/bpkCmbDvjnwPfWyRcE2B1ZnfxthqgVaZ5oJHS/CQibyuLBL8DA75fxmgY9nIfednDZSydSm6zK0w==",
"version": "2.0.0-rc.4",
"resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.0.0-rc.4.tgz",
"integrity": "sha512-jHWyF1/Rl/1MXybFnHnHVjXYrOuvLzSUACow2Ii2KFD7zURPjPzy5KvOzq6tk+wL92LDgCl88nU1QVvO7dvRog==",
"requires": {
"cross-fetch": "^3.0.6"
"cross-fetch": "^3.1.5"
}
},
"@supabase/postgrest-js": {
"version": "0.37.2",
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-0.37.2.tgz",
"integrity": "sha512-3Dgx5k3RvtKqc8DvR2BEyh2fVyjZe5P4e0zD1r8dyuVmpaYDaASZ2YeNVgyWXMCWH7xzrj4vepTYlKwfj78QLg==",
"version": "1.0.0-rc.5",
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.0.0-rc.5.tgz",
"integrity": "sha512-rBOyhK+qLd2fdX6WbCW3e9gY9qbwC5Zeso6QWM+00RlRz3k156lxnreVpe4KKGXNT3JMqJwAHXjQ2lbxmtBJqg==",
"requires": {
"cross-fetch": "^3.0.6"
"cross-fetch": "^3.1.5"
}
},
"@supabase/realtime-js": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-1.7.2.tgz",
"integrity": "sha512-DMUaFIKj7KszGtWTTQbhMmUzZf7UnwYqySsmY+G8HgYxvY3ZaVa+DZD0I6ofgr4OLNr0po/ODM2a4lf5m5GNBg==",
"version": "2.0.0-rc.5",
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.0.0-rc.5.tgz",
"integrity": "sha512-0JiSJjoTmwleufGzBQwEVamI047jnAiemBk8qOkgwyoLOlfGTQk8XSA7aT023BsGVgRNKZqp/jJJAVBht/Rd2w==",
"requires": {
"@types/phoenix": "^1.5.4",
"websocket": "^1.0.34"
}
},
"@supabase/storage-js": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-1.7.0.tgz",
"integrity": "sha512-f5EBw0wM96hKmnrXhgiqq2Reh9O0NgjKE+jkaKY4jQmfutefqaCAWn+cBzlmHs9h135H2ldaGmhWRFHUSkLt2g==",
"version": "2.0.0-rc.1",
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.0.0-rc.1.tgz",
"integrity": "sha512-OjPtPuKzhzQW6nO/UdwVJVFrzfFnIWt1u+gTQ4+8t1+IBdn7xDHssDteBPkMCd+fYcbMZrqpggbagoFH3FylXw==",
"requires": {
"cross-fetch": "^3.1.0"
"cross-fetch": "^3.1.5"
}
},
"@supabase/supabase-js": {
"version": "1.35.3",
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-1.35.3.tgz",
"integrity": "sha512-uwO8OVdMFsGZNZ1xQhFz22+PSW0EWYZ5xVq+jQeGz8nhabEu+Q9Uyep/bcNzOpyPJRzbGfxSPRzgAdAxfJgFhw==",
"version": "2.0.0-rc.9",
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.0.0-rc.9.tgz",
"integrity": "sha512-WTb60mEjzkI7FcuQA7KW9IkLltl2I7ojlpBIORwbekuCz5nKDSlo4g6/Wyo4u6Qe97weh3mhFMLGZRorhRq7lg==",
"requires": {
"@supabase/functions-js": "^1.3.3",
"@supabase/gotrue-js": "^1.22.14",
"@supabase/postgrest-js": "^0.37.2",
"@supabase/realtime-js": "^1.7.2",
"@supabase/storage-js": "^1.7.0"
"@supabase/functions-js": "^2.0.0-rc.1",
"@supabase/gotrue-js": "^2.0.0-rc.4",
"@supabase/postgrest-js": "^1.0.0-rc.5",
"@supabase/realtime-js": "^2.0.0-rc.5",
"@supabase/storage-js": "^2.0.0-rc.1",
"cross-fetch": "^3.1.5"
}
},
"@types/phoenix": {
@@ -2287,9 +2289,9 @@
"integrity": "sha512-/s3+fwhKf1YK4k7btOImOzCQLpUjS6MaPf0ODTNuT4eTM1Bg4itBpLkydhOzJmpmH6Z9eXFyuuK5czsmzRzwtw=="
},
"es5-ext": {
"version": "0.10.61",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz",
"integrity": "sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==",
"version": "0.10.62",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
"integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
"requires": {
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.3",
@@ -2299,7 +2301,7 @@
"es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
"integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
"requires": {
"d": "1",
"es5-ext": "^0.10.35",
@@ -2326,17 +2328,17 @@
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"ext": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz",
"integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==",
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
"requires": {
"type": "^2.5.0"
"type": "^2.7.2"
},
"dependencies": {
"type": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz",
"integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ=="
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
"integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
}
}
},
@@ -2479,7 +2481,7 @@
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
"js-tokens": {
"version": "4.0.0",
@@ -2523,7 +2525,7 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"nanoid": {
"version": "3.3.2",
@@ -2575,9 +2577,9 @@
}
},
"node-gyp-build": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz",
"integrity": "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ=="
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz",
"integrity": "sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg=="
},
"node-releases": {
"version": "2.0.3",
@@ -3032,7 +3034,7 @@
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"type": {
"version": "1.2.0",
@@ -3068,7 +3070,7 @@
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"websocket": {
"version": "1.0.34",
@@ -3086,7 +3088,7 @@
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
@@ -3105,7 +3107,7 @@
"yaeti": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
"integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc="
"integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug=="
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "supabase-slack-clone-basic",
"version": "0.1.1",
"version": "0.2.0",
"license": "MIT",
"scripts": {
"dev": "next",
@@ -9,11 +9,11 @@
"start": "next start"
},
"dependencies": {
"@supabase/supabase-js": "^1.35.3",
"@supabase/supabase-js": "^2.0.0-rc.9",
"next": "latest",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"sass": "^1.26.2",
"tailwindcss": "^1.1.4"
"tailwindcss": "^1.9.6"
}
}

View File

@@ -1,6 +1,6 @@
import '~/styles/style.scss'
import React, { useState, useEffect } from 'react'
import Router from 'next/router'
import { useRouter } from 'next/router'
import UserContext from 'lib/UserContext'
import { supabase, fetchUserRoles } from 'lib/Store'
@@ -9,40 +9,44 @@ export default function SupabaseSlackClone({ Component, pageProps }) {
const [user, setUser] = useState(null)
const [session, setSession] = useState(null)
const [userRoles, setUserRoles] = useState([])
const router = useRouter()
useEffect(() => {
const session = supabase.auth.session()
setSession(session)
setUser(session?.user ?? null)
setUserLoaded(session ? true : false)
if (user) {
signIn()
Router.push('/channels/[id]', '/channels/1')
}
supabase.auth.getSession().then(({ data: { session }}) => {
setSession(session)
setUserLoaded(session ? true : false)
if (session?.user) {
signIn()
router.push('/channels/[id]', '/channels/1')
}
})
const { data: authListener } = supabase.auth.onAuthStateChange(async (event, session) => {
const { subscription: authListener } = supabase.auth.onAuthStateChange(async (event, session) => {
setSession(session)
const currentUser = session?.user
setUser(currentUser ?? null)
setUserLoaded(!!currentUser)
if (currentUser) {
signIn(currentUser.id, currentUser.email)
Router.push('/channels/[id]', '/channels/1')
router.push('/channels/[id]', '/channels/1')
}
})
return () => {
authListener.unsubscribe()
}
}, [user])
}, [])
const signIn = async () => {
await fetchUserRoles((userRoles) => setUserRoles(userRoles.map((userRole) => userRole.role)))
}
const signOut = async () => {
const result = await supabase.auth.signOut()
Router.push('/')
const { error } = await supabase.auth.signOut()
if (!error) {
router.push('/')
}
}
return (

View File

@@ -7,9 +7,9 @@ const Home = () => {
const handleLogin = async (type, username, password) => {
try {
const { error, user } =
const { error, data: { user } } =
type === 'LOGIN'
? await supabase.auth.signIn({ email: username, password })
? await supabase.auth.signInWithPassword({ email: username, password })
: await supabase.auth.signUp({ email: username, password })
// If the user doesn't exist here and an error hasn't been raised yet,
// that must mean that a confirmation email has been sent.

View File

@@ -1,120 +0,0 @@
# Todo example using Supabase & Angular
- Frontend:
- [Angular](https://angular.io)
- [Supabase.js](https://supabase.com/docs/library/getting-started) for user management and realtime data syncing.
- Backend:
- [app.supabase.com](https://app.supabase.com/): hosted Postgres database with restful API for usage with Supabase.js.
## Deploy your own
### 1. Create new project
Sign up to Supabase - [https://app.supabase.com](https://app.supabase.com) and create a new project. Wait for your database to start.
### 2. Run "Todo List" Quickstart
Once your database has started, run the "Todo List" quickstart. Inside of your project, enter the `SQL editor` tab and scroll down until you see `TODO LIST: Build a basic todo list with Row Level Security`.
### 3. Get the URL and Key
Go to the Project Settings (the cog icon), open the API tab, and find your API URL and `anon` key, you'll need these in the next step.
The `anon` key is your client-side API key. It allows "anonymous access" to your database, until the user has logged in. Once they have logged in, the keys will switch to the user's own login token. This enables row level security for your data. Read more about this [below](#postgres-row-level-security).
![image](https://user-images.githubusercontent.com/10214025/88916245-528c2680-d298-11ea-8a71-708f93e1ce4f.png)
**_NOTE_**: The `service_role` key has full access to your data, bypassing any security policies. These keys have to be kept secret and are meant to be used in server environments and never on a client or browser.
### 4. Run the Angular client locally
Copy the url and the key retrieved into the previous step into the `environment.ts` file :
```ts
export const environment = {
production: false,
supabaseUrl: 'YOUR_SUPABASE_URL',
supabaseKey: 'YOUR_SUPABASE_KEY',
}
```
run `ng serve`
### 5. Deploy the Angular client
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fsupabase%2Fsupabase%2Ftree%2Fmaster%2Fexamples%2todo-list%2Fangular-todo-list&env=ANGULAR_APP_SUPABASE_URL,ANGULAR_APP_SUPABASE_KEY&envDescription=Find%20the%20Supabase%20URL%20and%20key%20in%20the%20your%20auto-generated%20docs%20at%20app.supabase.com&project-name=supabase-todo-list&repo-name=supabase-todo-list)
Here, we recommend forking this repo so you can deploy through Vercel by clicking the button above. When you click the button, replace the repo URL with your fork's URL.
You will be asked for a `ANGULAR_APP_SUPABASE_URL` and `ANGULAR_APP_SUPABASE_KEY`. Use the API URL and `anon` key from [step 3](#3.-get-the-url-and-key).
### 6. Change authentication settings if necessary
![Change auth settings](https://user-images.githubusercontent.com/1811651/101840012-39be3800-3af8-11eb-8c32-73f2fae6299e.png)
On [app.supabase.com](https://app.supabase.com), you can go to Authentication -> Settings to change your auth settings for your project if necessary. Here, you can change the site URL, which is used for determining where to redirect users after they confirm their email addresses or attempt to use a magic link to log in.
Here, you can also enable external oauth providers, such as Google and GitHub.
Instructions to activate Google provider : https://supabase.com/docs/guides/auth/auth-google
Instructions to activate GitHub provider : https://supabase.com/docs/guides/auth/auth-github
## Supabase details
### Postgres Row level security
This project uses very high-level Authorization using Postgres' Role Level Security.
When you start a Postgres database on Supabase, we populate it with an `auth` schema, and some helper functions.
When a user logs in, they are issued a JWT with the role `authenticated` and their UUID.
We can use these details to provide fine-grained control over what each user can and cannot do.
This is a trimmed-down schema, with the policies:
```sql
create table todos (
id bigint generated by default as identity primary key,
user_id uuid references auth.users not null,
task text check (char_length(task) > 3),
is_complete boolean default false,
inserted_at timestamp with time zone default timezone('utc'::text, now()) not null
);
alter table todos enable row level security;
create policy "Individuals can create todos." on todos for
insert with check (auth.uid() = user_id);
create policy "Individuals can view their own todos. " on todos for
select using (auth.uid() = user_id);
create policy "Individuals can update their own todos." on todos for
update using (auth.uid() = user_id);
create policy "Individuals can delete their own todos." on todos for
delete using (auth.uid() = user_id);
```
## Authors
- [Supabase](https://supabase.com)
- [Gerome Grignon](https://github.com/geromegrignon)
Supabase is open source, we'd love for you to follow along and get involved at https://github.com/supabase/supabase
## Local development
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```

View File

@@ -1,7 +0,0 @@
module.exports = {
moduleNameMapper: {
'@core/(.*)': '<rootDir>/src/app/core/$1',
},
preset: 'jest-preset-angular',
setupFilesAfterEnv: ['<rootDir>/setup-jest.ts'],
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,49 +0,0 @@
{
"name": "angular-todo-list",
"version": "1.0.0",
"author": {
"name": "Gerome Grignon",
"url": "https://github.com/geromegrignon"
},
"scripts": {
"ng": "ng",
"start": "ng serve",
"config": "ts-node set-env.ts",
"build": "npm run config && ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "~12.1.1",
"@angular/common": "~12.1.1",
"@angular/compiler": "~12.1.1",
"@angular/core": "~12.1.1",
"@angular/forms": "~12.1.1",
"@angular/platform-browser": "~12.1.1",
"@angular/platform-browser-dynamic": "~12.1.1",
"@angular/router": "~12.1.1",
"@briebug/jest-schematic": "^3.1.0",
"@ngneat/tailwind": "^7.0.3",
"@supabase/supabase-js": "^1.18.1",
"dotenv": "^10.0.0",
"rxjs": "~6.6.0",
"tslib": "^2.2.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-builders/jest": "12.1.0",
"@angular-devkit/build-angular": "~12.1.1",
"@angular/cli": "~12.1.1",
"@angular/compiler-cli": "~12.1.1",
"@types/jasmine": "~3.6.0",
"@types/jest": "26.0.24",
"@types/node": "^12.11.1",
"jasmine-core": "~3.7.0",
"jest": "27.0.6",
"karma-coverage": "~2.0.3",
"tailwindcss": "2.2.4",
"ts-node": "^10.1.0",
"typescript": "~4.3.2"
}
}

View File

@@ -1,23 +0,0 @@
import ErrnoException = NodeJS.ErrnoException
const { writeFile } = require('fs')
// Configure Angular `environment.ts` file path
const targetPath = './src/environments/environment.prod.ts'
// read environment variables from .env file
require('dotenv').config()
// `environment.ts` file structure
const envConfigFile = `export const environment = {
production: true,
supabaseUrl: '${process.env.ANGULAR_APP_SUPABASE_URL}',
supabaseKey: '${process.env.ANGULAR_APP_SUPABASE_KEY}'
};
`
writeFile(targetPath, envConfigFile, function (err: ErrnoException | null) {
if (err) {
throw console.error(err)
}
})

View File

@@ -1,30 +0,0 @@
import 'jest-preset-angular'
/* global mocks for jsdom */
const mock = () => {
let storage: { [key: string]: string } = {}
return {
getItem: (key: string) => (key in storage ? storage[key] : null),
setItem: (key: string, value: string) => (storage[key] = value || ''),
removeItem: (key: string) => delete storage[key],
clear: () => (storage = {}),
}
}
Object.defineProperty(window, 'localStorage', { value: mock() })
Object.defineProperty(window, 'sessionStorage', { value: mock() })
Object.defineProperty(window, 'getComputedStyle', {
value: () => ['-webkit-appearance'],
})
Object.defineProperty(document.body.style, 'transform', {
value: () => {
return {
enumerable: true,
configurable: true,
}
},
})
/* output shorter and more meaningful Zone error stack traces */
// Error.stackTraceLimit = 2;

View File

@@ -1,29 +0,0 @@
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
import { AuthComponent } from './components/auth/auth.component'
import { HomeComponent } from './components/home/home.component'
import { AuthGuard } from './guards/auth.guard'
import { RecoveryPasswordGuard } from './guards/recovery-password.guard'
import { RecoveryPasswordComponent } from './components/recovery-password/recovery-password.component'
const routes: Routes = [
{
path: '',
component: HomeComponent,
canActivate: [AuthGuard, RecoveryPasswordGuard],
},
{
path: 'auth',
component: AuthComponent,
},
{
path: 'recovery-password',
component: RecoveryPasswordComponent,
},
]
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}

View File

@@ -1 +0,0 @@
<router-outlet></router-outlet>

View File

@@ -1,18 +0,0 @@
import { TestBed } from '@angular/core/testing'
import { AppComponent } from './app.component'
import { RouterTestingModule } from '@angular/router/testing'
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AppComponent],
imports: [RouterTestingModule],
}).compileComponents()
})
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent)
const app = fixture.componentInstance
expect(app).toBeTruthy()
})
})

View File

@@ -1,7 +0,0 @@
import { Component } from '@angular/core'
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {}

View File

@@ -1,23 +0,0 @@
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { AppComponent } from './app.component'
import { ReactiveFormsModule } from '@angular/forms'
import { AuthComponent } from './components/auth/auth.component'
import { HomeComponent } from './components/home/home.component'
import { AppRoutingModule } from './app-routing.module'
import { TodoItemComponent } from './components/todo-item/todo-item.component'
import { RecoveryPasswordComponent } from './components/recovery-password/recovery-password.component'
@NgModule({
declarations: [
AppComponent,
AuthComponent,
HomeComponent,
TodoItemComponent,
RecoveryPasswordComponent,
],
imports: [BrowserModule, ReactiveFormsModule, AppRoutingModule],
bootstrap: [AppComponent],
})
export class AppModule {}

View File

@@ -1,179 +0,0 @@
<div class="min-w-full min-h-screen flex items-center justify-center bg-gray-200">
<form
[formGroup]="authForm"
class="w-full h-full sm:h-auto sm:w-2/5 max-w-sm p-5 bg-white shadow flex flex-col text-base"
>
<span class="font-sans text-4xl text-center pb-2 mb-1 border-b mx-4 align-center"> Login </span>
<label class="mt-3 mb-2 font-medium text-lg" for="email">
<span class="font-mono mr-1 text-red-400">*</span>
Email:
</label>
<input
id="email"
class="bg-gray-100 border py-1 px-3"
type="email"
name="email"
formControlName="email"
required
/>
<label class="mt-3 mb-2 font-medium text-lg" for="password">
<span class="font-mono mr-1 text-red-400">*</span>
Password:
</label>
<input
id="password"
class="bg-gray-100 border py-1 px-3"
type="password"
name="password"
formControlName="password"
required
/>
<span
class="text-blue-600 mt-2 cursor-pointer self-end text-sm font-medium"
(click)="forgotPassword()"
>
Forgot Password?
</span>
<div
*ngIf="helperText?.text && helperText?.error"
class="border px-1 py-2 my-2 text-center text-sm bg-red-100 border-red-300 text-red-400"
>
{{ helperText?.text }}
</div>
<div
*ngIf="helperText?.text && !helperText?.error"
class="border px-1 py-2 my-2 text-center text-sm bg-green-100 border-green-300 text-green-500"
>
{{ helperText?.text }}
</div>
<div class="mt-2 flex">
<span class="block mx-1.5 w-full rounded-md shadow-sm">
<button
type="button"
(click)="handleLogin('REGISTER')"
class="
border
w-full
border-blue-600
text-blue-600
flex
justify-center
py-2
px-4
text-sm
font-medium
rounded-md
hover:bg-blue-200
focus:outline-none focus:border-blue-700 focus:shadow-outline-blue
active:bg-blue-700
transition
duration-150
ease-in-out
"
>
Sign Up
</button>
</span>
<span class="block w-full mx-1.5 rounded-md shadow-sm">
<button
(click)="handleLogin('LOGIN')"
type="button"
class="
flex
w-full
justify-center
py-2
px-4
border border-transparent
text-sm
font-medium
rounded-md
text-white
bg-blue-600
hover:bg-blue-500
focus:outline-none focus:border-blue-700 focus:shadow-outline-blue
active:bg-blue-700
transition
duration-150
ease-in-out
"
>
Sign In
</button>
</span>
</div>
<div class="mt-3">
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div class="w-full mx-1.5 border-t border-gray-300"></div>
</div>
<div class="relative flex justify-center text-sm leading-5">
<span class="px-2 bg-white text-gray-500"> Or continue with </span>
</div>
</div>
<div>
<div class="mt-3">
<span class="block rounded-md shadow-sm">
<button
(click)="handleOAuthLogin('github')"
type="button"
class="
w-3/4
mx-auto
flex
justify-center
py-2
px-4
border border-transparent
text-sm
font-medium
rounded-md
text-white
bg-blue-600
hover:bg-blue-500
focus:outline-none focus:border-blue-700 focus:shadow-outline-blue
active:bg-blue-700
transition
duration-150
ease-in-out
"
>
GitHub
</button>
</span>
</div>
<div class="mt-3">
<span class="block rounded-md shadow-sm">
<button
(click)="handleOAuthLogin('google')"
type="button"
class="
w-3/4
mx-auto
flex
justify-center
py-2
px-4
border border-transparent
text-sm
font-medium
rounded-md
text-white
bg-blue-600
hover:bg-blue-500
focus:outline-none focus:border-blue-700 focus:shadow-outline-blue
active:bg-blue-700
transition
duration-150
ease-in-out
"
>
Google
</button>
</span>
</div>
</div>
</div>
</form>
</div>

View File

@@ -1,27 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { AuthComponent } from './auth.component'
import { RouterTestingModule } from '@angular/router/testing'
import { ReactiveFormsModule } from '@angular/forms'
describe('AuthComponent', () => {
let component: AuthComponent
let fixture: ComponentFixture<AuthComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AuthComponent],
imports: [RouterTestingModule, ReactiveFormsModule],
}).compileComponents()
})
beforeEach(() => {
fixture = TestBed.createComponent(AuthComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})

View File

@@ -1,66 +0,0 @@
import { Component } from '@angular/core'
import { FormControl, FormGroup, Validators } from '@angular/forms'
import { SupabaseService } from '../../services/supabase.service'
import { Provider } from '@supabase/supabase-js'
import { Router } from '@angular/router'
interface HelperText {
text: string
error: boolean
}
@Component({
selector: 'app-auth',
templateUrl: './auth.component.html',
})
export class AuthComponent {
authForm: FormGroup = new FormGroup({
email: new FormControl('', Validators.required),
password: new FormControl('', Validators.required),
})
helperText: HelperText | undefined
constructor(private readonly supabase: SupabaseService, private readonly router: Router) {}
async forgotPassword(): Promise<void> {
const email = prompt('Please enter your email:')
let { error } = await this.supabase.resetPassword(email as string)
if (error) {
console.error('Error: ', error.message)
} else {
this.helperText = {
error: false,
text: 'Password recovery email has been sent.',
}
}
}
async handleLogin(type: string): Promise<void> {
const { email, password } = this.authForm.value
const { user, error, session } =
type === 'LOGIN'
? await this.supabase.signIn(email, password)
: await this.supabase.signUp(email, password)
if (user && type === 'LOGIN') {
await this.router.navigate(['/'])
} else if (error) {
this.helperText = { error: true, text: error.message }
} else if (user && !session && !error) {
this.helperText = {
error: false,
text: 'An email has been sent to you for verification!',
}
}
}
async handleOAuthLogin(provider: Provider): Promise<void> {
// You need to enable the third party auth you want in Authentication > Settings
// Read more on: https://supabase.com/docs/guides/auth#third-party-logins
let { error } = await this.supabase.signInWithProvider(provider)
if (error) console.error('Error: ', error.message)
}
}

Some files were not shown because too many files have changed in this diff Show More