init commit,
This commit is contained in:
36
99_references/supabase-examples/realtime/nextjs-auth-presence/.gitignore
vendored
Normal file
36
99_references/supabase-examples/realtime/nextjs-auth-presence/.gitignore
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# 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*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
@@ -0,0 +1,79 @@
|
||||
# Supabase Realtime Presence API Sample Program
|
||||
|
||||
This is an example program for Supabase Realtime Presence APIs.
|
||||
User get authenticated using Supabase Auth API. Once Logged-in you can see which users are 'present' and viewing the page.
|
||||
|
||||
- Frontend:
|
||||
- Next.js.
|
||||
- [Supabase.js v2 (realtime presence support)](https://supabase.io/docs/library/getting-started)
|
||||
- Backend:
|
||||
- [app.supabase.io](https://app.supabase.io/): hosted postgres database with realtime support.
|
||||
|
||||
## Real time Presence APIs used.
|
||||
|
||||
This program shows usage of channel presence API calls such as , channel.on('presence', ...), channel.subscribe function usage.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### 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 "User Management Starter" Quickstart
|
||||
|
||||
This will create user tables and profile tables for user management.
|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
### 4. Pull this example git repository
|
||||
|
||||
`git clone <<this repository url>> `
|
||||
|
||||
### 5. Create a .env.local file
|
||||
|
||||
Create a .env.local file and add following environment variables.
|
||||
|
||||
```
|
||||
NEXT_PUBLIC_SUPABASE_URL=<<insert-your-db-url-here>>
|
||||
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=<<insert-your-anon-key-here>>
|
||||
```
|
||||
|
||||
### 5. Now run the development server!
|
||||
|
||||
Now run
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
## How to Test?
|
||||
|
||||
When you visit http://localhost:3000, you will be redirected to auth login/signup screen.
|
||||
|
||||
Signup if you haven't and then login.
|
||||
|
||||
You will see your email listed on the page.
|
||||
|
||||
Open another browser window. And sign-in as another user.
|
||||
|
||||
As you login from other browser window, you will see list of current users updated.
|
||||
|
||||
## Deployment
|
||||
|
||||
Since this is next.js application, simplest method to deploy this repository is on Vercel.
|
||||
|
||||
## Conclusion/Next Steps
|
||||
|
||||
- Need to implement Profile page
|
||||
- Need to implement ability to upload User Avatars
|
||||
- Need ability to read user avatar from social media.
|
@@ -0,0 +1,7 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: false,
|
||||
swcMinify: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
5837
99_references/supabase-examples/realtime/nextjs-auth-presence/package-lock.json
generated
Normal file
5837
99_references/supabase-examples/realtime/nextjs-auth-presence/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "nextjs-auth-presence",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"format": "prettier --write \"pages/**/*.{js,jsx,ts,tsx,css,md,json}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@supabase/auth-helpers-nextjs": "^0.6.0",
|
||||
"@supabase/auth-helpers-react": "^0.3.1",
|
||||
"@supabase/auth-ui-react": "^0.2.7",
|
||||
"@supabase/supabase-js": "^2.7.1",
|
||||
"next": "^13.1.6",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.11.0",
|
||||
"@types/react": "18.0.28",
|
||||
"@types/react-dom": "18.0.10",
|
||||
"eslint": "8.25.0",
|
||||
"eslint-config-next": "12.3.1",
|
||||
"typescript": "4.8.4"
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
import { createBrowserSupabaseClient, Session } from '@supabase/auth-helpers-nextjs'
|
||||
import { SessionContextProvider } from '@supabase/auth-helpers-react'
|
||||
import type { AppProps } from 'next/app'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
import '../styles/globals.css'
|
||||
|
||||
function MyApp({
|
||||
Component,
|
||||
pageProps,
|
||||
}: AppProps<{
|
||||
initialSession: Session
|
||||
}>) {
|
||||
const router = useRouter()
|
||||
const [supabaseClient] = useState(() => createBrowserSupabaseClient())
|
||||
|
||||
useEffect(() => {
|
||||
const {
|
||||
data: { subscription },
|
||||
} = supabaseClient.auth.onAuthStateChange((event, session) => {
|
||||
switch (event) {
|
||||
case 'SIGNED_IN':
|
||||
router.push('/')
|
||||
return
|
||||
case 'SIGNED_OUT':
|
||||
router.push('/login')
|
||||
return
|
||||
}
|
||||
})
|
||||
return subscription.unsubscribe
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<SessionContextProvider
|
||||
supabaseClient={supabaseClient}
|
||||
initialSession={pageProps.initialSession}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</SessionContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default MyApp
|
@@ -0,0 +1,80 @@
|
||||
import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { useSupabaseClient, useUser } from '@supabase/auth-helpers-react'
|
||||
import { RealtimePresenceState } from '@supabase/supabase-js'
|
||||
import type { GetServerSidePropsContext, NextPage } from 'next'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
const HomePage: NextPage = () => {
|
||||
const supabaseClient = useSupabaseClient()
|
||||
const this_user = useUser()
|
||||
const [userState, setUserState] = useState<RealtimePresenceState>({})
|
||||
|
||||
useEffect(() => {
|
||||
console.log('user: ', this_user)
|
||||
|
||||
const channel = supabaseClient.channel('online-users', {
|
||||
config: {
|
||||
presence: {
|
||||
key: this_user?.email ? this_user?.email : 'Unknown',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
channel.on('presence', { event: 'sync' }, () => {
|
||||
const presentState = channel.presenceState()
|
||||
|
||||
console.log('inside presence: ', presentState)
|
||||
|
||||
setUserState({ ...presentState })
|
||||
})
|
||||
|
||||
channel.on('presence', { event: 'join' }, ({ newPresences }) => {
|
||||
console.log('New users have joined: ', newPresences)
|
||||
})
|
||||
|
||||
channel.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
const status = await channel.track({
|
||||
user_name: this_user?.email ? this_user?.email : 'Unknown',
|
||||
})
|
||||
console.log('status: ', status)
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => supabaseClient.auth.signOut()}>Sign out</button>
|
||||
|
||||
<p> List of Currently Logged in Users: </p>
|
||||
{Object.keys(userState).map((key) => (
|
||||
<p key={key}>Hi {key}</p>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
// Create authenticated Supabase Client
|
||||
const supabase = createServerSupabaseClient(ctx)
|
||||
// Check if we have a session
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
if (!session)
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/login',
|
||||
permanent: false,
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
initialSession: session,
|
||||
user: session.user,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default HomePage
|
@@ -0,0 +1,31 @@
|
||||
import { useSupabaseClient, useUser } from '@supabase/auth-helpers-react'
|
||||
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
|
||||
import type { NextPage } from 'next'
|
||||
import styles from '../styles/Home.module.css'
|
||||
|
||||
const LoginPage: NextPage = () => {
|
||||
const supabaseClient = useSupabaseClient()
|
||||
const user = useUser()
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<Auth
|
||||
redirectTo="http://localhost:3000/"
|
||||
appearance={{ theme: ThemeSupa }}
|
||||
supabaseClient={supabaseClient}
|
||||
/>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => supabaseClient.auth.signOut()}>Sign out</button>
|
||||
<p>user:</p>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginPage
|
@@ -0,0 +1,39 @@
|
||||
import { createServerSupabaseClient, User } from '@supabase/auth-helpers-nextjs'
|
||||
import { GetServerSidePropsContext } from 'next'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function Profile({ user }: { user: User }) {
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
[<Link href="/">Home</Link>] | [<Link href="/protected-page">supabaseServerClient</Link>]
|
||||
</p>
|
||||
<div>Hello {user.email}</div>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
// Create authenticated Supabase Client
|
||||
const supabase = createServerSupabaseClient(ctx)
|
||||
// Check if we have a session
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
if (!session)
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/login',
|
||||
permanent: false,
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
initialSession: session,
|
||||
user: session.user,
|
||||
},
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,4 @@
|
||||
<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>
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,2 @@
|
||||
NEXT_PUBLIC_SUPABASE_URL="replace-this-with-your-supabase-instance"
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY="replace-this-with-your-supabasedb-anon-key"
|
@@ -0,0 +1,129 @@
|
||||
.container {
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.main {
|
||||
min-height: 100vh;
|
||||
padding: 4rem 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding: 2rem 0;
|
||||
border-top: 1px solid #eaeaea;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.title a {
|
||||
color: #0070f3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.title a:hover,
|
||||
.title a:focus,
|
||||
.title a:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
line-height: 1.15;
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.title,
|
||||
.description {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 4rem 0;
|
||||
line-height: 1.5;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.code {
|
||||
background: #fafafa;
|
||||
border-radius: 5px;
|
||||
padding: 0.75rem;
|
||||
font-size: 1.1rem;
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 1rem;
|
||||
padding: 1.5rem;
|
||||
text-align: left;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
border: 1px solid #eaeaea;
|
||||
border-radius: 10px;
|
||||
transition: color 0.15s ease, border-color 0.15s ease;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.card:hover,
|
||||
.card:focus,
|
||||
.card:active {
|
||||
color: #0070f3;
|
||||
border-color: #0070f3;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.card p {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 1em;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.grid {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.card,
|
||||
.footer {
|
||||
border-color: #222;
|
||||
}
|
||||
.code {
|
||||
background: #111;
|
||||
}
|
||||
.logo img {
|
||||
filter: invert(1);
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
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;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
body {
|
||||
color: white;
|
||||
background: black;
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 50 KiB |
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Reference in New Issue
Block a user