8000 initial example · homebaseio/homebase-react@a3d663d · GitHub
[go: up one dir, main page]

Skip to content

Commit a3d663d

Browse files
committed
initial example
1 parent b09b9fc commit a3d663d

File tree

9 files changed

+637
-0
lines changed

9 files changed

+637
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Supabase todo example
2+
3+
Heavily adopted from `create-react-app` bootstrapping, here's a our Todo example with a Firebase backend bundled in a Typescript React application.
4+
5+
## Live demo
6+
- https://homebase-example-ts-firebase-todo.vercel.app
7+
8+
## Env vars
9+
REACT_APP_SUPABASE_URL
10+
supabaseAnonKey
11+
12+
## Installation
13+
```
14+
yarn install
15+
```
16+
17+
## Run it
18+
```
19+
yarn start
20+
```
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"name": "typescript-supabase-todo",
3+
"version": "0.1.0",
4+
"private": true,
5+
"dependencies": {
6+
"@supabase/supabase-js": "^1.15.1",
7+
"@testing-library/jest-dom": "^5.11.4",
8+
"@testing-library/react": "^11.1.0",
9+
"@testing-library/user-event": "^12.1.10",
10+
"@types/jest": "^26.0.15",
11+
"@types/node": "^12.0.0",
12+
"@types/react": "^16.9.53",
13+
"@types/react-dom": "^16.9.8",
14+
"firebase": "^8.1.1",
15+
"firebaseui": "^4.7.1",
16+
"homebase-react": "^0.7.0",
17+
"react": "^17.0.1",
18+
"react-dom": "^17.0.1",
19+
"react-scripts": "4.0.0",
20+
"typescript": "^4.0.3",
21+
"web-vitals": "^0.2.4"
22+
},
23+
"scripts": {
24+
"start": "react-scripts start",
25+
"build": "react-scripts build",
26+
"test": "react-scripts test",
27+
"eject": "react-scripts eject"
28+
},
29+
"eslintConfig": {
30+
"extends": [
31+
"react-app",
32+
"react-app/jest"
33+
]
34+
},
35+
"browserslist": {
36+
"production": [
37+
">0.2%",
38+
"not dead",
39+
"not op_mini all"
40+
],
41+
"development": [
42+
"last 1 chrome version",
43+
"last 1 firefox version",
44+
"last 1 safari version"
45+
]
46+
}
47+
}
Binary file not shown.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1" />
7+
<meta name="theme-color" content="#000000" />
8+
<meta
9+
name="description"
10+
content="Web site created using create-react-app"
11+
/>
12+
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
13+
<!--
14+
Notice the use of %PUBLIC_URL% in the tags above.
15+
It will be replaced with the URL of the `public` folder during the build.
16+
Only files inside the `public` folder can be referenced from the HTML.
17+
18+
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
19+
work correctly both with client-side routing and a non-root public URL.
20+
Learn how to configure a non-root public URL by running `npm run build`.
21+
-->
22+
<title>React App</title>
23+
</head>
24+
<body>
25+
<noscript>You need to enable JavaScript to run this app.</noscript>
26+
<div id="root"></div>
27+
<!--
28+
This HTML file is a template.
29+
If you open it directly in the browser, you will see an empty page.
30+
31+
You can add webfonts, meta tags, or analytics to this file.
32+
The build step will place the bundled scripts into the <body> tag.
33+
34+
To begin the development, run `npm start` or `yarn start`.
35+
To create a production bundle, use `npm run build` or `yarn build`.
36+
-->
37+
</body>
38+
</html>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { useState, useEffect } from 'react'
2+
import { supabase } from './supabaseClient'
3+
import Auth from './Auth'
4+
import { HomebaseProvider, useClient, useTransact, useQuery, useEntity, Transaction, Entity} from 'homebase-react'
5+
import Todos from './Todos'
6+
export default function App() {
7+
8+
const config = {
9+
// Lookup helpers are used to enforce
10+
// unique constraints and relationships.
11+
lookupHelpers: {
12+
user: { uid: { unique: 'identity' } },
13+
todo: {
14+
// refs are relationships
15+
project: { type: 'ref' },
16+
owner: { type: 'ref' }
17+
}
18+
},
19+
// Initial data let's you conveniently transact some
20+
// starting data on DB creation to hydrate your components.
21+
initialData: [
22+
23+
]
24+
}
25+
26+
let [session, setSession] = useState(null)
27+
28+
const AuthChanged = (session) => {
29+
const [transact] = useTransact()
30+
const [currentUser] = useEntity({ identity: 'currentUser' })
31+
const [client] = useClient()
32+
setSession(session)
33+
if (session){
34+
const user = supabase.auth.user();
35+
console.log(user)
36+
transact([{ user: { uid: user.id, name: user.email} }])
37+
client.transactSilently([{ currentUser: { identity: 'currentUser', uid: user.id }}])
38+
}
39+
}
40+
41+
useEffect(() => {
42+
setSession(supabase.auth.session())
43+
supabase.auth.onAuthStateChange((_event, session) => AuthChanged)
44+
}, [])
45+
46+
return (
47+
<HomebaseProvider config={config}>
48+
<div className="w-full h-full bg-gray-900">
49+
{!session ? (
50+
<div className="w-full h-full flex justify-center items-center p-4">
51+
<Auth />
52+
</div>
53+
) : (
54+
<div
55+
className="w-full h-full flex flex-col justify-center items-center p-4"
56+
style={{ minWidth: 250, maxWidth: 600, margin: 'auto' }}
57+
>
58+
<Todos user={supabase.auth.user()} />
59+
<button
60+
className="btn-black w-full mt-12"
61+
onClick={async () => {
62+
const { error } = await supabase.auth.signOut()
63+
if (error) console.log('Error logging out:', error.message)
64+
}}
65+
>
66+
Logout
67+
</button>
68+
</div>
69+
)}
70+
71+
</div>
72+
</HomebaseProvider>
73+
)
74+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import {useState} from 'react'
2+
import { supabase } from './supabaseClient'
3+
4+
export default function Auth({}) {
5+
const [email, setEmail] = useState('')
6+
const [password, setPassword] = useState('')
7+
8+
const handleLogin = async (type, email, password) => {
9+
try {
10+
const { error , user} =
11+
type === 'LOGIN'
12+
? await supabase.auth.signIn({ email, password })
13+
: await supabase.auth.signUp({ email, password })
14+
if (!error && !user) alert('Check your email for the login link!')
15+
if (error) console.log('Error returned:', error.message)
16+
} catch (error) {
17+
console.log('Error thrown:', error.message)
18+
alert(error.error_description || error)
19+
}
20+
}
21+
22+
async function handleOAuthLogin(provider) {
23+
let { error } = await supabase.auth.signIn({ provider })
24+
if (error) console.log('Error: ', error.message)
25+
}
26+
27+
async function forgotPassword(e) {
28+
e.preventDefault()
29+
var email = prompt('Please enter your email:')
30+
if (email === null || email === '') {
31+
window.alert('You must enter your email.')
32+
} else {
33+
let { error } = await supabase.auth.api.resetPasswordForEmail(email)
34+
if (error) {
35+
console.log('Error: ', error.message)
36+
} else {
37+
alert('Password recovery email has been sent.')
38+
}
39+
}
40+
}
41+
42+
return (
43+
<div className="w-full sm:w-1/2 xl:w-1/3">
44+
<div className="border-teal p-8 border-t-12 bg-white mb-6 rounded-lg shadow-lg bg-white">
45+
<div className="mb-4">
46+
<label className="font-bold text-grey-darker block mb-2">Email</label>
47+
<input
48+
type="text"
49+
className="block appearance-none w-full bg-white border border-grey-light hover:border-grey px-2 py-2 rounded shadow"
50+
placeholder="Your email"
51+
value={email}
52+
onChange={(e) => setEmail(e.target.value)}
53+
/>
54+
</div>
55+
<div className="mb-4">
56+
<label className="font-bold text-grey-darker block mb-2">Password</label>
57+
<input
58+
type="password"
59+
className="block appearance-none w-full bg-white border border-grey-light hover:border-grey px-2 py-2 rounded shadow"
60+
placeholder="Your password"
61+
value={password}
62+
onChange={(e) => setPassword(e.target.value)}
63+
/>
64+
</div>
65+
66+
<div className="flex flex-col gap-2">
67+
<a
68+
onClick={(e) => {
69+
e.preventDefault()
70+
handleLogin('SIGNUP', email, password)
71+
}}
72+
href={'/channels'}
73+
className="btn-black"
74+
>
75+
Sign up
76+
</a>
77+
<a
78+
onClick={(e) => {
79+
e.preventDefault()
80+
handleLogin('LOGIN', email, password)
81+
}}
82+
href={'/channels'}
83+
className="btn-black-outline"
84+
>
85+
{password.length ? 'Sign in' : 'Send magic link'}
86+
</a>
87+
</div>
88+
89+
<div className="mt-2 text-sm leading-5">
90+
{/* eslint-disable-next-line */}
91+
<a
92+
onClick={forgotPassword}
93+
href="/"
94+
className="font-medium text-indigo-600 hover:text-indigo-500 focus:outline-none focus:underline transition ease-in-out duration-150"
95+
>
96+
Forgot your password?
97+
</a>
98+
</div>
99+
100+
<div className="mt-4">
101+
<div className="relative">
102+
<div className="absolute inset-0 flex items-center">
103+
<div className="w-full border-t border-gray-300"></div>
104+
</div>
105+
<div className="relative flex justify-center text-sm leading-5">
106+
<span className="px-2 bg-white text-gray-500">Or continue with</span>
107+
</div>
108+
</div>
109+
110+
<div className="mt-6">
111+
<div className="mt-6">
112+
<span className="block w-full rounded-md shadow-sm">
113+
<button
114+
onClick={() => handleOAuthLogin('github')}
115+
type="button"
116+
className="w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition duration-150 ease-in-out"
117+
>
118+
GitHub
119+
</button>
120+
</span>
121+
</div>
122+
<div className="mt-6">
123+
<span className="block w-full rounded-md shadow-sm">
124+
<button
125+
onClick={() => handleOAuthLogin('google')}
126+
type="button"
127+
className="w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition duration-150 ease-in-out"
128+
>
129+
Google
130+
</button>
131+
</span>
132+
</div>
133+
</div>
134+
</div>
135+
</div>
136+
</div>
137+
)
138+
}

0 commit comments

Comments
 (0)
0