Merge branch 'master' into feature/simple_auth

This commit is contained in:
awalias committed 2020-08-31 21:56:09 +08:00
commit 958a407136
205 files changed
+19460 -72016

No files matched your search

-3
View File
@@ -1,3 +0,0 @@
# Example Contributing Guidelines
This is an example of GitHub's contributing guidelines file. Check out GitHub's [CONTRIBUTING.md help center article](https://help.github.com/articles/setting-guidelines-for-repository-contributors/) for more information.
-11
View File
@@ -1,11 +0,0 @@
* **I'm submitting a ...**
[ ] bug report
[ ] feature request
[ ] question about the decisions made in the repository
[ ] question about how to use this project
* **Summary**
* **Other information** (e.g. detailed explanation, stacktraces, related issues, suggestions how to fix, links for us to have context, eg. StackOverflow, personal fork, etc.)
-13
View File
@@ -1,13 +0,0 @@
* **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...)
* **What is the current behavior?** (You can also link to an open issue here)
* **What is the new behavior (if this is a feature change)?**
* **Other information**:
+8 -1
View File
@@ -32,5 +32,12 @@ We are currently in Public Alpha. Watch "releases" of this repo to get notified
- [Supabase Realtime](https://github.com/supabase/realtime): Listen to your to PostgreSQL database in realtime via websockets. Built with Elixir.
- [Supabase Postgres](https://github.com/supabase/postgres): Unmodified Postgres with some useful plugins.
- [@supabase/postgrest-js](https://github.com/supabase/postgrest-js): Isomorphic JavaScript client for PostgREST.
- [@supabase/doctest-js](https://github.com/supabase/realtime): Run JSDoc style doc examples as tests within your test suite.
- [@supabase/doctest-js](https://github.com/supabase/doctest-js): Run JSDoc style doc examples as tests within your test suite.
## Sponsors
We are building the features of Firebase using enterprise-grade, open source products. We support existing communities wherever possible, and if the products don’t exist we build them and open source them ourselves. Thanks to these sponsors who are making the OSS ecosystem better for everyone.
[![Worklife VC](https://user-images.githubusercontent.com/10214025/90451355-34d71200-e11e-11ea-81f9-1592fd1e9146.png)](https://www.worklife.vc)
[![New Sponsor](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)](https://github.com/sponsors/supabase)
+2 -2
View File
@@ -1,5 +1,5 @@
# Supabase Connectors
We are considering building connectors that will automatically forward your database changes to varios systems. At this stage we are simply gauging demand.
We are considering building connectors that will automatically forward your database changes to various systems. At this stage we are simply gauging demand.
If you would like us to start building a connector that you need, feel free to show your interest in this [issue on GitHub](https://github.com/supabase/supabase/issues/4).
If you would like us to start building a connector that you need, feel free to show your interest in this [issue on GitHub](https://github.com/supabase/supabase/issues/4).
+1
View File
@@ -0,0 +1 @@
dist/*
+2
View File
@@ -2,6 +2,8 @@
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)
+14 -21
View File
@@ -1,23 +1,16 @@
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);
}
/* h1 {
margin-bottom: 0;
}
.container {
margin: 1em;
}
.section {
margin: 1em;
} */
/* 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;
}
+3
View File
@@ -9,6 +9,9 @@
<body>
<div class='container'>
<div class='section'>
<h1>Supabase Auth Example</h1>
</div>
<div class='section'>
<form id='sign-up'>
<h3>Sign Up</h3>
+2 -2
View File
@@ -6,7 +6,7 @@ var SUPABASE_KEY =
var supabase = Supabase.createClient(SUPABASE_URL, SUPABASE_KEY)
window.userToken = null
window.onload = function () {
document.addEventListener('DOMContentLoaded', function (event) {
var signUpForm = document.querySelector('#sign-up')
signUpForm.onsubmit = signUpSubmitted.bind(signUpForm)
@@ -18,7 +18,7 @@ window.onload = function () {
var logoutButton = document.querySelector('#logout-button')
logoutButton.onclick = logoutSubmitted.bind(logoutButton)
}
})
const signUpSubmitted = (event) => {
event.preventDefault()
File diff suppressed because it is too large. Load diff
+47
View File
@@ -0,0 +1,47 @@
# Live Tracker Map
Build with supabase realtime and leafletjs. [[Demo App]](https://realtime-map.vercel.app/)
<p align="center">
<kbd>
<img src="https://media.giphy.com/media/iDU80ngpsSddc0ObGI/giphy.gif" alt="Demo"/>
</kbd>
</p>
## How to use
#### Clone this folder
```bash
git clone https://github.com/supabase/supabase
cd supabase/examples/live-tracker-map
```
#### Install dependencies
```bash
npm install
```
#### Start the app
```bash
# Open a terminal and run:
npm run dev
```
Visit http://localhost:4000 and start testing!
## Test with your own Supabase Project
#### Create locations table
Go to [app.supabase.io](https://app.supabase.io/), create a new organisation and project if you haven't had one.
Run this sql query to create `locations` table.
```sql
CREATE TABLE locations (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
latitude numeric NOT NULL,
longitude numeric NOT NULL,
ref VARCHAR(40)
);
```
#### Setup env vars
Make a .env.local in this folder with the following, you can get these values from your project dashboard at [app.supabase.io](https://app.supabase.io/):
```bash
NEXT_PUBLIC_SUPABASE_ENDPOINT=<your-supabase-url>
NEXT_PUBLIC_SUPABASE_APIKEY=<your-supabase-key>
```
@@ -0,0 +1,86 @@
import { useEffect, useState, useRef, useMemo, useCallback } from 'react'
import { MapContainer, TileLayer, Marker, Popup, Circle } from 'react-leaflet'
import { RoundToFixDecimals } from "lib/utils"
import TextLog from "components/TextLog"
function DraggableMarker({ initialPos, addCircle }) {
const [position, setPosition] = useState(initialPos)
const markerRef = useRef(null)
const eventHandlers = useMemo(
() => ({
dragend() {
const marker = markerRef.current
const pos = marker.getLatLng()
if (addCircle) addCircle(pos)
if (marker) setPosition(pos)
},
}),
[addCircle],
)
return (
<Marker
draggable={true}
eventHandlers={eventHandlers}
position={position}
ref={markerRef}>
<Popup minWidth={90}>
{`lat: ${position.lat}`}<br />{`long: ${position.lng}`}
</Popup>
</Marker>
)
}
function MapInput({ supabase, clientRef, center, zoom }) {
const [log, setLog] = useState(undefined)
const [circles, setCircles] = useState([])
useEffect(() => {
let newLog = `Ref: ${clientRef}\nReady to send location...`
newLog += circles.map(item => { return `\nsent lat=${RoundToFixDecimals(item.lat)} long=${RoundToFixDecimals(item.lng)}` })
setLog(newLog)
}, [circles, clientRef])
const onAddCircle = useCallback(
async (pos) => {
setCircles([...circles, pos])
// insert new location
await supabase
.from('locations')
.insert([
{ latitude: pos.lat, longitude: pos.lng, ref: clientRef },
])
},
[setCircles, circles, supabase, clientRef]
);
function renderCircles() {
return circles.map((item, index) => <Circle key={`${index}`} center={item} pathOptions={{ fillColor: 'blue' }} radius={20} />)
}
return (
<div className="map-input">
<MapContainer center={center} zoom={zoom || 15} scrollWheelZoom={false}>
<TileLayer
attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<DraggableMarker initialPos={center} addCircle={onAddCircle} />
{renderCircles()}
</MapContainer>
<TextLog log={log} />
<style jsx>{`
.map-input {
}
.map-input textarea {
width: 100%;
height: 7rem;
}
`}</style>
</div>
)
}
export default MapInput
@@ -0,0 +1,64 @@
import { useState, useEffect, useRef } from 'react'
import { MapContainer, TileLayer, Polyline } from 'react-leaflet'
import { RoundToFixDecimals } from "lib/utils"
import TextLog from "components/TextLog"
function MapView({ supabase, clientRef, center, zoom }) {
const [log, setLog] = useState(undefined)
const [positions, setPositions] = useState([])
const mySubscription = useRef(false)
useEffect(() => {
let newLog = `Ref: ${clientRef}\nStart listerning...`
newLog += positions.map(item => { return `\nid=${item.id} lat=${RoundToFixDecimals(item.lat)} long=${RoundToFixDecimals(item.lng)}` })
setLog(newLog)
}, [positions, clientRef])
useEffect(() => {
// Listen to INSERT event on locations table
mySubscription.current = supabase
.from('locations')
.on('INSERT', payload => {
const { new: newItem } = payload
const { id, ref, latitude, longitude } = newItem
if (ref === clientRef) {
console.log('Change received!', payload)
setPositions([...positions, { id, ref, lat: latitude, lng: longitude }])
}
})
.subscribe()
return () => {
if (mySubscription.current) supabase.removeSubscription(mySubscription.current)
}
}, [supabase, clientRef, positions, setPositions])
function drawPolyline() {
const temp = positions.map(item => {
if (!item) return
const { lat, lng } = item
return [lat, lng]
})
const polyline = [center, ...temp]
return <Polyline pathOptions={{ color: 'black' }} positions={polyline} />
}
return (
<div className="map-view">
<MapContainer center={center} zoom={zoom || 15} scrollWheelZoom={false}>
<TileLayer
attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{drawPolyline()}
</MapContainer>
<TextLog log={log} />
<style jsx>{`
.map-view {
}
`}</style>
</div>
)
}
export default MapView
@@ -0,0 +1,21 @@
import { useEffect, useRef } from 'react'
export default function TextLog({ log }) {
const textLog = useRef(null)
useEffect(() => {
textLog.current.scrollTop = textLog.current.scrollHeight;
}, [log])
return (
<>
<textarea ref={textLog} readOnly value={log} />
<style jsx>{`
textarea {
width: 100%;
height: 7rem;
}
`}</style>
</>
)
}
File renamed without changes.
+14
View File
@@ -0,0 +1,14 @@
function RoundToFixDecimals(value, numDecimals = 5) {
function addZero(s, size) {
while (s.length <= (size || 2)) { s = s + "0"; }
return s;
}
const temp = addZero("1", numDecimals)
const tempNum = parseInt(temp, 10)
return Math.round((value + Number.EPSILON) * tempNum) / tempNum
}
export {
RoundToFixDecimals
}
File diff suppressed because it is too large. Load diff
+19
View File
@@ -0,0 +1,19 @@
{
"name": "supabase-realtime-map",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev -p 4000",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@supabase/supabase-js": "^0.34.0",
"leaflet": "^1.6.0",
"nanoid": "^3.1.10",
"next": "9.4.4",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-leaflet": "^3.0.0-beta.2"
}
}
+195
View File
@@ -0,0 +1,195 @@
import { useState, useEffect } from 'react'
import Head from 'next/head'
import dynamic from 'next/dynamic'
import { createClient } from '@supabase/supabase-js'
import { nanoid } from 'nanoid'
const MapInput = dynamic(
() => import('components/MapInput'),
{ ssr: false }
)
const MapView = dynamic(
() => import('components/MapView'),
{ ssr: false }
)
export default function Home() {
const [clientRef, setClientRef] = useState(null)
const apiEndpoint = process.env.NEXT_PUBLIC_SUPABASE_ENDPOINT || "https://BeQiprVORewGZImzsKAS.supabase.co"
const apiKey = process.env.NEXT_PUBLIC_SUPABASE_APIKEY || "IHDL7hnmTSlqQ1fm7kYw5SBQPY11Rp"
// Create a single supabase client for interacting with your database
const supabase = createClient(apiEndpoint, apiKey);
const center = {
lat: 1.3489728457596013,
lng: 103.77043978311998
}
const zoomLevel = 14
useEffect(() => {
let ref = localStorage.getItem('_client-ref')
if (!ref) {
ref = nanoid()
localStorage.setItem('_client-ref', ref)
}
setClientRef(ref)
}, [])
return (
<div className="container">
<Head>
<title>Live Tracker</title>
<link rel="icon" href="/favicon.ico" />
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
crossOrigin=""
/>
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"
integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
crossOrigin=""></script>
</Head>
<main>
<h1 className="title">
Live Tracker
</h1>
<p className="description">
Get started by drag-drop the marker around to simulate location update
</p>
<div className="grid">
<div className="card">
<MapInput supabase={supabase} clientRef={clientRef} center={center} zoom={zoomLevel} />
</div>
<div className="card">
<MapView supabase={supabase} clientRef={clientRef} center={center} zoom={zoomLevel - 1} />
</div>
</div>
</main>
<footer>
<a
href="https://supabase.io"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<img src="/supabase.svg" alt="Supabase Logo" className="logo" />
</a>
</footer>
<style jsx>{`
.container {
min-height: 100vh;
padding: 0 0.5rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
main {
padding: 5rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
footer {
width: 100%;
height: 100px;
border-top: 1px solid #eaeaea;
display: flex;
justify-content: center;
align-items: center;
}
footer img {
margin-left: 0.5rem;
}
footer a {
display: flex;
justify-content: center;
align-items: center;
}
a {
color: inherit;
text-decoration: none;
}
.title {
margin: 0;
line-height: 1.15;
font-size: 4rem;
}
.title,
.description {
text-align: center;
}
.description {
line-height: 1.5;
font-size: 1.5rem;
}
.grid {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
max-width: 800px;
margin-top: 3rem;
}
.card {
margin: 1rem;
flex-grow: 1;
text-align: left;
color: inherit;
text-decoration: none;
}
.logo {
height: 1em;
}
@media (max-width: 600px) {
.grid {
width: 100%;
flex-direction: column;
}
.card {
width: 100%;
}
}
`}</style>
<style jsx global>{`
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue,
sans-serif;
}
* {
box-sizing: border-box;
}
.leaflet-container {
min-height: 20rem;
}
`}</style>
</div>
)
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@@ -0,0 +1,13 @@
<svg width="375" height="375" viewBox="0 0 375 375" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M218.909 28.4831C218.909 21.5889 213.322 16 206.431 16C203.004 16 199.9 17.382 197.645 19.6192L195.912 21.548L99.675 151.658H195.18L218.429 32.0055L218.853 29.6682C218.89 29.2781 218.909 28.8828 218.909 28.4831Z" fill="#3ECF8E"/>
<path d="M65 205.336C65 216.366 73.9385 225.308 84.9647 225.308L85.053 225.308H180.87L195.18 151.658H99.675L70.736 190.783L68.8725 193.512C66.4382 196.822 65 200.911 65 205.336Z" fill="#3ECF8E"/>
<path d="M310 171.631C310 160.6 301.061 151.658 290.035 151.658H289.245H195.18L180.87 225.308H275.202L305.604 184.205L306.817 182.455C308.831 179.337 310 175.62 310 171.631Z" fill="#3ECF8E"/>
<path d="M157.533 346.517C157.533 353.411 163.119 359 170.011 359C173.706 359 177.027 357.393 179.311 354.839L179.803 354.285L275.202 225.308H180.87L157.985 343.089L157.924 343.405C157.668 344.4 157.533 345.443 157.533 346.517Z" fill="#3ECF8E"/>
<path d="M157.533 346.517C157.533 353.411 163.119 359 170.011 359C173.706 359 177.027 357.393 179.311 354.839L179.803 354.285L275.202 225.308H180.87L157.985 343.089L157.924 343.405C157.668 344.4 157.533 345.443 157.533 346.517Z" fill="url(#paint0_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="236" y1="237" x2="167" y2="359" gradientUnits="userSpaceOnUse">
<stop stop-color="#23915F"/>
<stop offset="1" stop-color="#3ECF8E" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
@@ -1,2 +0,0 @@
const withSass = require('@zeit/next-sass')
module.exports = withSass()
@@ -1,16 +0,0 @@
{
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@supabase/supabase-js": "^0.1.18",
"@zeit/next-sass": "^1.0.0",
"next": "latest",
"node-sass": "4.7.2",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"tailwindcss": "^1.2.0"
}
}
Whitespace-only changes.
@@ -1,3 +0,0 @@
import '../styles/style.scss'
export default () => <div className="example">Hello World!</div>
@@ -1,4 +0,0 @@
$color: #2ecc71;
.example {
background-color: $color;
}
Whitespace-only changes.
Whitespace-only changes.
@@ -1,35 +0,0 @@
-- USERS
CREATE TABLE public.users (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
username text
);
-- CHANNELS
CREATE TABLE public.channels (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
slug text NOT NULL UNIQUE
);
-- MESSAGES
CREATE TABLE public.messages (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
message text,
user_id bigint REFERENCES users NOT NULL,
channel_id bigint REFERENCES channels NOT NULL
);
-- Create the Replication publication for Supabase
CREATE PUBLICATION supabase_realtime FOR ALL TABLES;
-- SOME DUMMY DATA
INSERT INTO
public.users (username)
VALUES
('jarvis'),
('tonystark');
INSERT INTO
public.channels (slug)
VALUES
('public'),
('random');
INSERT INTO
public.messages (message, channel_id, user_id)
VALUES
('Hello World 👋', 1, 1),
('Goodbye cruel world.', 2, 1);
@@ -1,30 +0,0 @@
version: '3'
services:
supabase:
image: supabase/supabase-dev:latest
ports:
- "8000:8000"
environment:
DB_HOST: db
DB_NAME: postgres
DB_USER: postgres
DB_PASSWORD: postgres
DB_PORT: 5432
DB_SCHEMA: public
depends_on:
- db
db:
image: postgres:12
ports:
- "5432:5432"
volumes:
- ./db:/docker-entrypoint-initdb.d/
command:
- "postgres"
- "-c"
- "wal_level=logical"
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_PORT: 5432
Whitespace-only changes.
Whitespace-only changes.
@@ -1,7 +0,0 @@
const withSass = require('@zeit/next-sass')
module.exports = withSass({
env: {
SUPABASE_URL: 'http://localhost:8000',
SUPABASE_KEY: 'examplekey',
},
})
File diff suppressed because it is too large. Load diff
@@ -1,16 +0,0 @@
{
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@supabase/supabase-js": "^0.1.18",
"@zeit/next-sass": "^1.0.0",
"next": "latest",
"node-sass": "4.7.2",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"tailwindcss": "^1.2.0"
}
}
Whitespace-only changes.
@@ -1,3 +0,0 @@
import '../styles/style.scss'
export default () => <div className="example">Hello World!</div>
@@ -1,4 +0,0 @@
$color: #2ecc71;
.example {
background-color: $color;
}
Whitespace-only changes.
@@ -1,35 +0,0 @@
-- USERS
CREATE TABLE public.users (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
username text
);
-- CHANNELS
CREATE TABLE public.channels (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
slug text NOT NULL UNIQUE
);
-- MESSAGES
CREATE TABLE public.messages (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
message text,
user_id bigint REFERENCES users NOT NULL,
channel_id bigint REFERENCES channels NOT NULL
);
-- Create the Replication publication for Supabase
CREATE PUBLICATION supabase_realtime FOR ALL TABLES;
-- SOME DUMMY DATA
INSERT INTO
public.users (username)
VALUES
('jarvis'),
('tonystark');
INSERT INTO
public.channels (slug)
VALUES
('public'),
('random');
INSERT INTO
public.messages (message, channel_id, user_id)
VALUES
('Hello World 👋', 1, 1),
('Goodbye cruel world.', 2, 1);
@@ -1,30 +0,0 @@
version: '3'
services:
supabase:
image: supabase/supabase-dev:latest
ports:
- "8000:8000"
environment:
DB_HOST: db
DB_NAME: postgres
DB_USER: postgres
DB_PASSWORD: postgres
DB_PORT: 5432
DB_SCHEMA: public
depends_on:
- db
db:
image: postgres:12
ports:
- "5432:5432"
volumes:
- ./db:/docker-entrypoint-initdb.d/
command:
- "postgres"
- "-c"
- "wal_level=logical"
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_PORT: 5432
@@ -1,53 +0,0 @@
import { useState, useEffect } from 'react'
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY)
export const useStore = ({ channelId }) => {
const [channels, setChannels] = useState(new Array())
const [messages, setMessages] = useState(new Array())
const [users] = useState(new Map())
// Fetches messages for the channel and starts the channel listeners
const reloadStore = () => {
if (channelId) {
console.log('channelId', channelId)
fetchChannels(setChannels)
fetchMessages(channelId, setMessages)
}
}
// Update the store when the user changes the "channel"
useEffect(reloadStore, [channelId])
// Export computed properties to use in our app
return {
channels: channels.sort((a, b) => a.slug.localeCompare(b.slug)),
messages,
users,
}
}
export const fetchChannels = async callback => {
try {
let { body } = await supabase.from('channels').select('*')
if (callback) callback(body)
return body
} catch (error) {
console.log('error', error)
}
}
export const fetchMessages = async (channelId, callback) => {
try {
let { body } = await supabase
.from('messages')
.eq('channel_id', channelId)
.select(`*, author:user_id(*)`)
.order('id', true)
if (callback) callback(body)
return body
} catch (error) {
console.log('error', error)
}
}
Whitespace-only changes.
@@ -1,7 +0,0 @@
const withSass = require('@zeit/next-sass')
module.exports = withSass({
env: {
SUPABASE_URL: 'http://localhost:8000',
SUPABASE_KEY: 'examplekey',
},
})
File diff suppressed because it is too large. Load diff
@@ -1,16 +0,0 @@
{
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@supabase/supabase-js": "^0.1.18",
"@zeit/next-sass": "^1.0.0",
"next": "latest",
"node-sass": "4.7.2",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"tailwindcss": "^1.2.0"
}
}
@@ -1,45 +0,0 @@
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useStore } from '../lib/Store'
const ChannelsPage = props => {
const router = useRouter()
const { channelId } = router.query
const { messages, channels } = useStore({ channelId })
return (
<>
<nav>
<div>
<button>New Channel</button>
</div>
<div>
<h3>Channels</h3>
</div>
<ul>
{channels.map(channel => (
<li>
<Link href="/[channelId]" as={`/${channel.id}`}>
<a style={{ fontWeight: channel.id == channelId ? 'bold' : 'normal' }}>
{channel.slug}
</a>
</Link>
</li>
))}
</ul>
</nav>
<main>
{messages.map(message => (
<div>
<p>
<strong>{message.author.username}</strong>
</p>
<p>{message.message}</p>
</div>
))}
</main>
</>
)
}
export default ChannelsPage
@@ -1,3 +0,0 @@
import '../styles/style.scss'
export default () => <div className="example">Hello World!</div>
@@ -1,4 +0,0 @@
$color: #2ecc71;
.example {
background-color: $color;
}
Whitespace-only changes.
@@ -1,35 +0,0 @@
-- USERS
CREATE TABLE public.users (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
username text
);
-- CHANNELS
CREATE TABLE public.channels (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
slug text NOT NULL UNIQUE
);
-- MESSAGES
CREATE TABLE public.messages (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
message text,
user_id bigint REFERENCES users NOT NULL,
channel_id bigint REFERENCES channels NOT NULL
);
-- Create the Replication publication for Supabase
CREATE PUBLICATION supabase_realtime FOR ALL TABLES;
-- SOME DUMMY DATA
INSERT INTO
public.users (username)
VALUES
('jarvis'),
('tonystark');
INSERT INTO
public.channels (slug)
VALUES
('public'),
('random');
INSERT INTO
public.messages (message, channel_id, user_id)
VALUES
('Hello World 👋', 1, 1),
('Goodbye cruel world.', 2, 1);
@@ -1,30 +0,0 @@
version: '3'
services:
supabase:
image: supabase/supabase-dev:latest
ports:
- "8000:8000"
environment:
DB_HOST: db
DB_NAME: postgres
DB_USER: postgres
DB_PASSWORD: postgres
DB_PORT: 5432
DB_SCHEMA: public
depends_on:
- db
db:
image: postgres:12
ports:
- "5432:5432"
volumes:
- ./db:/docker-entrypoint-initdb.d/
command:
- "postgres"
- "-c"
- "wal_level=logical"
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_PORT: 5432
@@ -1,53 +0,0 @@
import { useState, useEffect } from 'react'
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY)
export const useStore = ({ channelId }) => {
const [channels, setChannels] = useState(new Array())
const [messages, setMessages] = useState(new Array())
const [users] = useState(new Map())
// Fetches messages for the channel and starts the channel listeners
const reloadStore = () => {
if (channelId) {
console.log('channelId', channelId)
fetchChannels(setChannels)
fetchMessages(channelId, setMessages)
}
}
// Update the store when the user changes the "channel"
useEffect(reloadStore, [channelId])
// Export computed properties to use in our app
return {
channels: channels.sort((a, b) => a.slug.localeCompare(b.slug)),
messages,
users,
}
}
export const fetchChannels = async callback => {
try {
let { body } = await supabase.from('channels').select('*')
if (callback) callback(body)
return body
} catch (error) {
console.log('error', error)
}
}
export const fetchMessages = async (channelId, callback) => {
try {
let { body } = await supabase
.from('messages')
.eq('channel_id', channelId)
.select(`*, author:user_id(*)`)
.order('id', true)
if (callback) callback(body)
return body
} catch (error) {
console.log('error', error)
}
}
Whitespace-only changes.
@@ -1,7 +0,0 @@
const withSass = require('@zeit/next-sass')
module.exports = withSass({
env: {
SUPABASE_URL: 'http://localhost:8000',
SUPABASE_KEY: 'examplekey',
},
})
File diff suppressed because it is too large. Load diff
@@ -1,16 +0,0 @@
{
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@supabase/supabase-js": "^0.1.18",
"@zeit/next-sass": "^1.0.0",
"next": "latest",
"node-sass": "4.7.2",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"tailwindcss": "^1.2.0"
}
}
@@ -1,45 +0,0 @@
import '../styles/style.scss'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useStore } from '../lib/Store'
const ChannelsPage = props => {
const router = useRouter()
const { channelId } = router.query
const { messages, channels } = useStore({ channelId })
return (
<div className="main flex h-screen w-screen absolute">
<nav className="w-1/4 bg-gray-900 text-gray-100 h-screen p-2">
<button className="bg-blue-900 hover:bg-blue-700 text-white font-bold p-2 rounded w-full">
New Channel
</button>
<h3 className="py-4">Channels</h3>
<ul>
{channels.map(channel => (
<li key={channel.id}>
<Link href="/[channelId]" as={`/${channel.id}`}>
<a
className="hover:text-blue-400"
style={{ fontWeight: channel.id == channelId ? 'bold' : 'normal' }}
>
{channel.slug}
</a>
</Link>
</li>
))}
</ul>
</nav>
<main className="w-3/4 bg-gray-800 h-screen p-2">
{messages.map(message => (
<div>
<p className="text-blue-700 font-bold">{message.author.username}</p>
<p className="text-white">{message.message}</p>
</div>
))}
</main>
</div>
)
}
export default ChannelsPage
@@ -1,3 +0,0 @@
import '../styles/style.scss'
export default () => <div className="example">Hello World!</div>
@@ -1,3 +0,0 @@
@import '~tailwindcss/dist/base.css';
@import '~tailwindcss/dist/components.css';
@import '~tailwindcss/dist/utilities.css';
Whitespace-only changes.
@@ -1,35 +0,0 @@
-- USERS
CREATE TABLE public.users (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
username text
);
-- CHANNELS
CREATE TABLE public.channels (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
slug text NOT NULL UNIQUE
);
-- MESSAGES
CREATE TABLE public.messages (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
message text,
user_id bigint REFERENCES users NOT NULL,
channel_id bigint REFERENCES channels NOT NULL
);
-- Create the Replication publication for Supabase
CREATE PUBLICATION supabase_realtime FOR ALL TABLES;
-- SOME DUMMY DATA
INSERT INTO
public.users (username)
VALUES
('jarvis'),
('tonystark');
INSERT INTO
public.channels (slug)
VALUES
('public'),
('random');
INSERT INTO
public.messages (message, channel_id, user_id)
VALUES
('Hello World 👋', 1, 1),
('Goodbye cruel world.', 2, 1);
@@ -1,30 +0,0 @@
version: '3'
services:
supabase:
image: supabase/supabase-dev:latest
ports:
- "8000:8000"
environment:
DB_HOST: db
DB_NAME: postgres
DB_USER: postgres
DB_PASSWORD: postgres
DB_PORT: 5432
DB_SCHEMA: public
depends_on:
- db
db:
image: postgres:12
ports:
- "5432:5432"
volumes:
- ./db:/docker-entrypoint-initdb.d/
command:
- "postgres"
- "-c"
- "wal_level=logical"
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_PORT: 5432
@@ -1,53 +0,0 @@
import { useState, useEffect } from 'react'
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY)
export const useStore = ({ channelId }) => {
const [channels, setChannels] = useState(new Array())
const [messages, setMessages] = useState(new Array())
const [users] = useState(new Map())
// Fetches messages for the channel and starts the channel listeners
const reloadStore = () => {
if (channelId) {
console.log('channelId', channelId)
fetchChannels(setChannels)
fetchMessages(channelId, setMessages)
}
}
// Update the store when the user changes the "channel"
useEffect(reloadStore, [channelId])
// Export computed properties to use in our app
return {
channels: channels.sort((a, b) => a.slug.localeCompare(b.slug)),
messages,
users,
}
}
export const fetchChannels = async callback => {
try {
let { body } = await supabase.from('channels').select('*')
if (callback) callback(body)
return body
} catch (error) {
console.log('error', error)
}
}
export const fetchMessages = async (channelId, callback) => {
try {
let { body } = await supabase
.from('messages')
.eq('channel_id', channelId)
.select(`*, author:user_id(*)`)
.order('id', true)
if (callback) callback(body)
return body
} catch (error) {
console.log('error', error)
}
}
Whitespace-only changes.
@@ -1,7 +0,0 @@
const withSass = require('@zeit/next-sass')
module.exports = withSass({
env: {
SUPABASE_URL: 'http://localhost:8000',
SUPABASE_KEY: 'examplekey',
},
})
File diff suppressed because it is too large. Load diff
@@ -1,16 +0,0 @@
{
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@supabase/supabase-js": "^0.1.18",
"@zeit/next-sass": "^1.0.0",
"next": "latest",
"node-sass": "4.7.2",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"tailwindcss": "^1.2.0"
}
}
@@ -1,45 +0,0 @@
import '../styles/style.scss'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useStore } from '../lib/Store'
const ChannelsPage = props => {
const router = useRouter()
const { channelId } = router.query
const { messages, channels } = useStore({ channelId })
return (
<div className="main flex h-screen w-screen absolute">
<nav className="w-1/4 bg-gray-900 text-gray-100 h-screen p-2">
<button className="bg-blue-900 hover:bg-blue-700 text-white font-bold p-2 rounded w-full">
New Channel
</button>
<h3 className="py-4">Channels</h3>
<ul>
{channels.map(channel => (
<li key={channel.id}>
<Link href="/[channelId]" as={`/${channel.id}`}>
<a
className="hover:text-blue-400"
style={{ fontWeight: channel.id == channelId ? 'bold' : 'normal' }}
>
{channel.slug}
</a>
</Link>
</li>
))}
</ul>
</nav>
<main className="w-3/4 bg-gray-800 h-screen p-2">
{messages.map(message => (
<div>
<p className="text-blue-700 font-bold">{message.author.username}</p>
<p className="text-white">{message.message}</p>
</div>
))}
</main>
</div>
)
}
export default ChannelsPage
@@ -1,3 +0,0 @@
import '../styles/style.scss'
export default () => <div className="example">Hello World!</div>
@@ -1,3 +0,0 @@
@import '~tailwindcss/dist/base.css';
@import '~tailwindcss/dist/components.css';
@import '~tailwindcss/dist/utilities.css';
-49
View File
@@ -1,49 +0,0 @@
# Basic Slack Clone
Build a simple slack clone using Supabase, Postgres and React hooks.
<p align="center">
<kbd>
<img src="https://media.giphy.com/media/J07U8iblJhlKDqZOxV/giphy.gif" alt="Demo"/>
</kbd>
<br />
<a href="https://media.giphy.com/media/J07U8iblJhlKDqZOxV/source.gif">View full image</a>
</p>
## How to use
**Clone the this folder**
```sh
# Copy the repo to your machine
git clone --no-checkout https://github.com/supabase/supabase
cd supabase
# Checkout this
git sparse-checkout init --cone
git sparse-checkout set examples/slack-clone-basic
cd examples/slack-clone-basic
```
**Install dependencies**
```sh
npm install
```
**Start the backend**
```sh
# Open a terminal and run:
docker-compose up
```
**Start the frontent**
```sh
# Open a terminal and run:
npm run dev
```
Visit http://localhost:3000 and start slacking! Open in two tabs to see everything getting updated in realtime
@@ -1,27 +0,0 @@
import { useState } from 'react'
const MessageInput = ({onSubmit}) => {
const [messageText, setMessageText] = useState('')
const submitOnEnter = event => {
// Watch for enter key
if (event.keyCode === 13) {
onSubmit(messageText)
setMessageText('')
}
}
return (
<>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
type="text"
placeholder="Send a message"
value={messageText}
onChange={e => setMessageText(e.target.value)}
onKeyDown={e => submitOnEnter(e)}
/>
</>
)}
export default MessageInput
@@ -1,41 +0,0 @@
-- Create the Replication publication
CREATE PUBLICATION supabase_realtime FOR ALL TABLES;
-- USERS
CREATE TYPE public.user_status AS ENUM ('ONLINE', 'OFFLINE');
CREATE TABLE public.users (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
inserted_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
updated_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
data jsonb DEFAULT null,
username text,
status user_status DEFAULT 'ONLINE'::public.user_status
);
ALTER TABLE public.users REPLICA IDENTITY FULL; -- Send "previous data" to supabase
COMMENT ON COLUMN public.users.data IS 'For unstructured data and prototyping.';
-- CHANNELS
CREATE TABLE public.channels (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
inserted_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
updated_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
data jsonb DEFAULT null,
slug text NOT NULL UNIQUE
);
ALTER TABLE public.users REPLICA IDENTITY FULL; -- Send "previous data" to supabase
COMMENT ON COLUMN public.channels.data IS 'For unstructured data and prototyping.';
-- MESSAGES
CREATE TABLE public.messages (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
inserted_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
updated_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
data jsonb DEFAULT null,
message text,
user_id bigint REFERENCES users NOT NULL,
channel_id bigint REFERENCES channels NOT NULL
);
ALTER TABLE public.messages REPLICA IDENTITY FULL; -- Send "previous data" to supabase
COMMENT ON COLUMN public.messages.data IS 'For unstructured data and prototyping.';
@@ -1,19 +0,0 @@
INSERT INTO
public.users (username)
VALUES
('supabot'),
('kiwicopple'),
('awailas'),
('dragarcia');
INSERT INTO
public.channels (slug)
VALUES
('public'),
('random');
INSERT INTO
public.messages (message, channel_id, user_id)
VALUES
('Hello World 👋', 1, 1),
('Perfection is attained, not when there is nothing more to add, but when there is nothing left to take away.', 2, 1);
@@ -1,31 +0,0 @@
version: '3'
services:
supabase:
image: supabase/supabase-dev:0.1.9
ports:
- "8000:8000"
environment:
DB_HOST: postgres
DB_NAME: postgres
DB_USER: postgres
DB_PASSWORD: postgres
DB_PORT: 5432
DB_SCHEMA: public
depends_on:
- postgres
postgres:
image: postgres:12
ports:
- "5432:5432"
volumes:
- ./db:/docker-entrypoint-initdb.d/
command:
- "postgres"
- "-c"
- "wal_level=logical"
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_PORT: 5432
-12
View File
@@ -1,12 +0,0 @@
const path = require('path')
module.exports = {
env: {
SUPABASE_URL: process.env.SUPABASE_URL || 'http://localhost:8000',
SUPABASE_KEY: process.env.SUPABASE_KEY || 'examplekey',
},
webpack(config) {
config.resolve.alias['~'] = path.join(__dirname, '')
return config
},
}
-42
View File
@@ -1,42 +0,0 @@
import { useState, useContext } from 'react'
import UserContext from '../lib/UserContext'
const Home = () => {
const { signIn } = useContext(UserContext)
const [username, setUsername] = useState('')
const handleLogin = e => {
e.preventDefault()
if (username != '') signIn(username)
}
return (
<div className="container mx-auto h-full flex justify-center items-center">
<div className="w-1/3 mt-8">
<div className="border-teal p-8 border-t-12 bg-white mb-6 rounded-lg shadow-lg">
<div className="mb-4">
<label className="font-bold text-grey-darker block mb-2">Username</label>
<input
type="text"
className="block appearance-none w-full bg-white border border-grey-light hover:border-grey px-2 py-2 rounded shadow"
placeholder="Your Username"
onChange={e => setUsername(e.target.value)}
/>
</div>
<div className="flex items-center justify-between">
<a
onClick={handleLogin}
href={'/channels'}
className="bg-gray-900 hover:bg-teal text-white font-bold py-2 px-4 rounded"
>
Login
</a>
</div>
</div>
</div>
</div>
)
}
export default Home
+2
View File
@@ -0,0 +1,2 @@
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_KEY=your-anon-key
@@ -18,6 +18,7 @@
# misc
.DS_Store
.env*
!.env.local.example
# debug
npm-debug.log*
+148
View File
@@ -0,0 +1,148 @@
# Realtime chat example using Supabase
This is a full-stack Slack clone example using:
- Frontend:
- Next.js.
- [Supabase.js](https://supabase.io/docs/library/getting-started) for user management and realtime data syncing.
- Backend:
- [app.supabase.io/](https://app.supabase.io/): hosted Postgres database with restful API for usage with Supabase.js.
## Demo
- Live demo: http://supabase-slack-clone.supabase.vercel.app/
- CodeSandbox: https://codesandbox.io/s/github/supabase/supabase/tree/master/examples/slack-clone
- Video tutorial: https://www.loom.com/share/31eec9b656e44b8d87c88bde8a465676
![Demo animation gif](./public/slack-clone-demo.gif)
## Deploy your own
### 1. Create new project
Sign up to Supabase - [https://app.supabase.io](https://app.supabase.io) and create a new project. Wait for your database to start.
### 2. Run "Slack Clone" Quickstart
Once your database has started, run the "Slack Clone" quickstart.
![Slack Clone Quick Start](https://user-images.githubusercontent.com/10214025/88916135-1b1d7a00-d298-11ea-82e7-e2c18314e805.png)
### 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. Deploy the Next.js client
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/git?s=https%3A%2F%2Fgithub.com%2Fsupabase%2Fsupabase%2Ftree%2Fmaster%2Fexamples%2Fslack-clone&env=NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_KEY&envDescription=Find%20the%20Supabase%20URL%20and%20key%20in%20the%20your%20auto-generated%20docs%20at%20app.supabase.io&project-name=supabase-slack-clone&repo-name=supabase-slack-clone)
You will be asked for a `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_KEY`. Use the API URL and `anon` key from [step 3](#3.-get-the-url-and-key).
## How to use
### Using `create-next-app`
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example locally:
```bash
npx create-next-app --example with-supabase-auth-realtime-db realtime-chat-app
# or
yarn create next-app --example with-supabase-auth-realtime-db realtime-chat-app
```
### Download manually
Download the example:
```bash
curl https://codeload.github.com/vercel/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-supabase-auth-realtime-db
cd with-supabase-auth-realtime-db
```
### Required configuration
Copy the `.env.local.example` file into a file named `.env.local` in the root directory of the example:
```bash
cp .env.local.example .env.local
```
Set your Supabase details from from [step 3](#3.-get-the-url-and-key) above:
```bash
NEXT_PUBLIC_SUPABASE_URL=<replace-with-your-API-url>
NEXT_PUBLIC_SUPABASE_KEY=<replace-with-your-anon-key>
```
### Run the development server
Now install the dependencies and start the development server.
```bash
npm install
npm run dev
# or
yarn
yarn dev
```
Visit http://localhost:3000 and start chatting! Open a channel across two browser tabs to see everything getting updated in realtime 🥳
## 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 thier 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
-- USER PROFILES
CREATE TYPE public.user_status AS ENUM ('ONLINE', 'OFFLINE');
CREATE TABLE public.users (
id uuid NOT NULL PRIMARY KEY, -- UUID from auth.users (Supabase)
username text,
status user_status DEFAULT 'OFFLINE'::public.user_status
);
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow logged-in read access" on public.users FOR SELECT USING ( auth.role() = 'authenticated' );
CREATE POLICY "Allow individual insert access" on public.users FOR INSERT WITH CHECK ( auth.uid() = id );
CREATE POLICY "Allow individual update access" on public.users FOR UPDATE USING ( auth.uid() = id );
-- CHANNELS
CREATE TABLE public.channels (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
inserted_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
slug text NOT NULL UNIQUE
);
ALTER TABLE public.channels ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow logged-in full access" on public.channels FOR ALL USING ( auth.role() = 'authenticated' );
-- MESSAGES
CREATE TABLE public.messages (
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
inserted_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
message text,
user_id uuid REFERENCES public.users NOT NULL,
channel_id bigint REFERENCES public.channels NOT NULL
);
ALTER TABLE public.messages ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow logged-in read access" on public.messages USING ( auth.role() = 'authenticated' );
CREATE POLICY "Allow individual insert access" on public.messages FOR INSERT WITH CHECK ( auth.uid() = user_id );
CREATE POLICY "Allow individual update access" on public.messages FOR UPDATE USING ( auth.uid() = user_id );
```
## Authors
- [Supabase](https://supabase.io)
Supabase is open source, we'd love for you to follow along and get involved at https://github.com/supabase/supabase
@@ -3,16 +3,16 @@ import { useContext } from 'react'
import UserContext from '~/lib/UserContext'
import { addChannel } from '~/lib/Store'
export default props => {
export default function Layout(props) {
const { signOut } = useContext(UserContext)
const slugify = text => {
const slugify = (text) => {
return text
.toString()
.toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
.replace(/\-\-+/g, '-') // Replace multiple - with single -
.replace(/[^\w-]+/g, '') // Remove all non-word chars
.replace(/--+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, '') // Trim - from end of text
}
@@ -20,28 +20,33 @@ export default props => {
const newChannel = async () => {
const slug = prompt('Please enter your name')
if (slug) {
// let { body } = await
addChannel(slugify(slug))
// console.log('body', body)
}
}
return (
<main className="main flex h-screen w-screen absolute">
<main className="main flex h-screen w-screen absolute overflow-hidden">
{/* Sidebar */}
<nav className="w-1/4 bg-gray-900 text-gray-100 h-screen">
<nav
className="w-64 bg-gray-900 text-gray-100 h-screen"
style={{ maxWidth: '20%', minWidth: 150 }}
>
<div className="p-2">
<h4 className="font-bold">Channels</h4>
<ul className="channel-list">
{props.channels.map(x => (
<SidebarItem channel={x} key={x.id} isActiveChannel={x.id == props.activeChannelId} />
{props.channels.map((x) => (
<SidebarItem
channel={x}
key={x.id}
isActiveChannel={x.id === props.activeChannelId}
/>
))}
</ul>
</div>
<hr className="m-2" />
<div className="p-2">
<button
className="bg-blue-900 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded w-full"
className="bg-blue-900 hover:bg-blue-800 text-white py-2 px-4 rounded w-full transition duration-150"
onClick={() => newChannel()}
>
New Channel
@@ -50,7 +55,7 @@ export default props => {
<hr className="m-2" />
<div className="p-2">
<button
className="bg-blue-900 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded w-full"
className="bg-blue-900 hover:bg-blue-800 text-white py-2 px-4 rounded w-full transition duration-150"
onClick={() => signOut()}
>
Log out
@@ -59,7 +64,7 @@ export default props => {
</nav>
{/* Messages */}
<div className="w-3/4 bg-gray-800 h-screen">{props.children}</div>
<div className="flex-1 bg-gray-800 h-screen">{props.children}</div>
</main>
)
}
@@ -0,0 +1,28 @@
import { useState } from 'react'
const MessageInput = ({ onSubmit }) => {
const [messageText, setMessageText] = useState('')
const submitOnEnter = (event) => {
// Watch for enter key
if (event.keyCode === 13) {
onSubmit(messageText)
setMessageText('')
}
}
return (
<>
<input
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
type="text"
placeholder="Send a message"
value={messageText}
onChange={(e) => setMessageText(e.target.value)}
onKeyDown={(e) => submitOnEnter(e)}
/>
</>
)
}
export default MessageInput
Loaded 100 of 205 files, more files were not shown because too many files have changed in this diff. Show more