init
This commit is contained in:
commit
5aa9b32c03
27 changed files with 4142 additions and 0 deletions
64
pages/_app.js
Normal file
64
pages/_app.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import '~/styles/style.scss'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import UserContext from 'lib/UserContext'
|
||||
import { supabase } from 'lib/Store'
|
||||
import { jwtDecode } from 'jwt-decode'
|
||||
|
||||
export default function SupabaseSlackClone({ Component, pageProps }) {
|
||||
const [userLoaded, setUserLoaded] = useState(false)
|
||||
const [user, setUser] = useState(null)
|
||||
const [session, setSession] = useState(null)
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
function saveSession(
|
||||
/** @type {Awaited<ReturnType<typeof supabase.auth.getSession>>['data']['session']} */
|
||||
session
|
||||
) {
|
||||
setSession(session)
|
||||
const currentUser = session?.user
|
||||
if (session) {
|
||||
const jwt = jwtDecode(session.access_token)
|
||||
currentUser.appRole = jwt.user_role
|
||||
}
|
||||
setUser(currentUser ?? null)
|
||||
setUserLoaded(!!currentUser)
|
||||
if (currentUser) {
|
||||
router.push('/channels/[id]', '/channels/1')
|
||||
}
|
||||
}
|
||||
|
||||
supabase.auth.getSession().then(({ data: { session } }) => saveSession(session))
|
||||
|
||||
const { data: { subscription: authListener } } = supabase.auth.onAuthStateChange(
|
||||
async (event, session) => {
|
||||
console.log(session)
|
||||
saveSession(session)
|
||||
}
|
||||
)
|
||||
|
||||
return () => {
|
||||
authListener.unsubscribe()
|
||||
}
|
||||
}, [])
|
||||
|
||||
const signOut = async () => {
|
||||
const { error } = await supabase.auth.signOut()
|
||||
if (!error) {
|
||||
router.push('/')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<UserContext.Provider
|
||||
value={{
|
||||
userLoaded,
|
||||
user,
|
||||
signOut,
|
||||
}}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</UserContext.Provider>
|
||||
)
|
||||
}
|
||||
52
pages/channels/[id].js
Normal file
52
pages/channels/[id].js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import Layout from '~/components/Layout'
|
||||
import Message from '~/components/Message'
|
||||
import MessageInput from '~/components/MessageInput'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useStore, addMessage } from '~/lib/Store'
|
||||
import { useContext, useEffect, useRef } from 'react'
|
||||
import UserContext from '~/lib/UserContext'
|
||||
|
||||
const ChannelsPage = (props) => {
|
||||
const router = useRouter()
|
||||
const { user, authLoaded, signOut } = useContext(UserContext)
|
||||
const messagesEndRef = useRef(null)
|
||||
|
||||
// Else load up the page
|
||||
const { id: channelId } = router.query
|
||||
const { messages, channels } = useStore({ channelId })
|
||||
|
||||
useEffect(() => {
|
||||
messagesEndRef.current.scrollIntoView({
|
||||
block: 'start',
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}, [messages])
|
||||
|
||||
// redirect to public channel when current channel is deleted
|
||||
useEffect(() => {
|
||||
if (!channels.some((channel) => channel.id === Number(channelId))) {
|
||||
router.push('/channels/1')
|
||||
}
|
||||
}, [channels, channelId])
|
||||
|
||||
// Render the channels and messages
|
||||
return (
|
||||
<Layout channels={channels} activeChannelId={channelId}>
|
||||
<div className="relative h-screen">
|
||||
<div className="Messages h-full pb-16">
|
||||
<div className="p-2 overflow-y-auto">
|
||||
{messages.map((x) => (
|
||||
<Message key={x.id} message={x} />
|
||||
))}
|
||||
<div ref={messagesEndRef} style={{ height: 0 }} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-2 absolute bottom-0 left-0 w-full">
|
||||
<MessageInput onSubmit={async (text) => addMessage(text, channelId, user.id)} />
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChannelsPage
|
||||
79
pages/index.js
Normal file
79
pages/index.js
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import { useState } from 'react'
|
||||
import { supabase } from 'lib/Store'
|
||||
|
||||
const Home = () => {
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
|
||||
const handleLogin = async (type, username, password) => {
|
||||
try {
|
||||
const { error, data: { user } } =
|
||||
type === 'LOGIN'
|
||||
? 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.
|
||||
// NOTE: Confirming your email address is required by default.
|
||||
if (error) {
|
||||
alert('Error with auth: ' + error.message)
|
||||
} else if (!user) alert('Signup successful, confirmation mail should be sent soon!')
|
||||
} catch (error) {
|
||||
console.log('error', error)
|
||||
alert(error.error_description || error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex justify-center items-center p-4 bg-gray-300">
|
||||
<div className="w-full sm:w-1/2 xl:w-1/3">
|
||||
<div className="border-teal p-8 border-t-12 bg-white mb-6 rounded-lg shadow-lg bg-white">
|
||||
<div className="mb-4">
|
||||
<label className="font-bold text-grey-darker block mb-2">Email</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"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="font-bold text-grey-darker block mb-2">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
className="block appearance-none w-full bg-white border border-grey-light hover:border-grey px-2 py-2 rounded shadow"
|
||||
placeholder="Your password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<a
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
handleLogin('SIGNUP', username, password)
|
||||
}}
|
||||
href={'/channels'}
|
||||
className="bg-indigo-700 hover:bg-teal text-white py-2 px-4 rounded text-center transition duration-150 hover:bg-indigo-600 hover:text-white"
|
||||
>
|
||||
Sign up
|
||||
</a>
|
||||
<a
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
handleLogin('LOGIN', username, password)
|
||||
}}
|
||||
href={'/channels'}
|
||||
className="border border-indigo-700 text-indigo-700 py-2 px-4 rounded w-full text-center transition duration-150 hover:bg-indigo-700 hover:text-white"
|
||||
>
|
||||
Login
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
Loading…
Add table
Add a link
Reference in a new issue