init
This commit is contained in:
commit
5aa9b32c03
27 changed files with 4142 additions and 0 deletions
88
components/Layout.js
Normal file
88
components/Layout.js
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import Link from 'next/link'
|
||||
import { useContext } from 'react'
|
||||
import UserContext from '~/lib/UserContext'
|
||||
import { addChannel, deleteChannel } from '~/lib/Store'
|
||||
import TrashIcon from '~/components/TrashIcon'
|
||||
|
||||
export default function Layout(props) {
|
||||
const { signOut, user } = useContext(UserContext)
|
||||
|
||||
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(/^-+/, '') // Trim - from start of text
|
||||
.replace(/-+$/, '') // Trim - from end of text
|
||||
}
|
||||
|
||||
const newChannel = async () => {
|
||||
const slug = prompt('Please enter your name')
|
||||
if (slug) {
|
||||
addChannel(slugify(slug), user.id)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="main flex h-screen w-screen overflow-hidden">
|
||||
{/* Sidebar */}
|
||||
<nav
|
||||
className="w-64 bg-gray-900 text-gray-100 overflow-scroll "
|
||||
style={{ maxWidth: '20%', minWidth: 150, maxHeight: '100vh' }}
|
||||
>
|
||||
<div className="p-2 ">
|
||||
<div className="p-2">
|
||||
<button
|
||||
className="bg-blue-900 hover:bg-blue-800 text-white py-2 px-4 rounded w-full transition duration-150"
|
||||
onClick={() => newChannel()}
|
||||
>
|
||||
New Channel
|
||||
</button>
|
||||
</div>
|
||||
<hr className="m-2" />
|
||||
<div className="p-2 flex flex-col space-y-2">
|
||||
<h6 className="text-xs">{user?.email}</h6>
|
||||
<button
|
||||
className="bg-blue-900 hover:bg-blue-800 text-white py-2 px-4 rounded w-full transition duration-150"
|
||||
onClick={() => signOut()}
|
||||
>
|
||||
Log out
|
||||
</button>
|
||||
</div>
|
||||
<hr className="m-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}
|
||||
user={user}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* Messages */}
|
||||
<div className="flex-1 bg-gray-800 h-screen">{props.children}</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
const SidebarItem = ({ channel, isActiveChannel, user }) => (
|
||||
<>
|
||||
<li className="flex items-center justify-between">
|
||||
<Link href="/channels/[id]" as={`/channels/${channel.id}`}>
|
||||
<a className={isActiveChannel ? 'font-bold' : ''}>{channel.slug}</a>
|
||||
</Link>
|
||||
{channel.id !== 1 && (channel.created_by === user?.id || user?.appRole === 'admin') && (
|
||||
<button onClick={() => deleteChannel(channel.id)}>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
)}
|
||||
</li>
|
||||
</>
|
||||
)
|
||||
26
components/Message.js
Normal file
26
components/Message.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { useContext } from 'react'
|
||||
import UserContext from '~/lib/UserContext'
|
||||
import { deleteMessage } from '~/lib/Store'
|
||||
import TrashIcon from '~/components/TrashIcon'
|
||||
|
||||
const Message = ({ message }) => {
|
||||
const { user } = useContext(UserContext)
|
||||
|
||||
return (
|
||||
<div className="py-1 flex items-center space-x-2">
|
||||
<div className="text-gray-100 w-4">
|
||||
{(user?.id === message.user_id || ['admin', 'moderator'].includes(user?.appRole)) && (
|
||||
<button onClick={() => deleteMessage(message.id)}>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-blue-700 font-bold">{message?.author?.username}</p>
|
||||
<p className="text-white">{message.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Message
|
||||
28
components/MessageInput.js
Normal file
28
components/MessageInput.js
Normal file
|
|
@ -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
|
||||
25
components/TrashIcon.js
Normal file
25
components/TrashIcon.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
const TrashIcon = (props) => {
|
||||
const { size = 16 } = props
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="feather feather-trash-2"
|
||||
>
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
||||
<line x1="10" y1="11" x2="10" y2="17"></line>
|
||||
<line x1="14" y1="11" x2="14" y2="17"></line>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default TrashIcon
|
||||
Loading…
Add table
Add a link
Reference in a new issue