feat(pages): add /application/[guildId]/[channelId]
(#4)
This commit is contained in:
16
components/Application/Messages/Message/Message.stories.tsx
Normal file
16
components/Application/Messages/Message/Message.stories.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { Meta, Story } from '@storybook/react'
|
||||
|
||||
import { Message as Component, MessageProps } from './Message'
|
||||
import { messageExampleComplete } from '../../../../cypress/fixtures/messages/message'
|
||||
|
||||
const Stories: Meta = {
|
||||
title: 'Message',
|
||||
component: Component
|
||||
}
|
||||
|
||||
export default Stories
|
||||
|
||||
export const Message: Story<MessageProps> = (arguments_) => {
|
||||
return <Component {...arguments_} />
|
||||
}
|
||||
Message.args = { message: messageExampleComplete }
|
61
components/Application/Messages/Message/Message.tsx
Normal file
61
components/Application/Messages/Message/Message.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import date from 'date-and-time'
|
||||
|
||||
import { MessageWithMember } from '../../../../models/Message'
|
||||
import { API_URL } from '../../../../tools/api'
|
||||
import { MessageContent } from './MessageContent'
|
||||
|
||||
export interface MessageProps {
|
||||
message: MessageWithMember
|
||||
}
|
||||
|
||||
export const Message: React.FC<MessageProps> = (props) => {
|
||||
const { message } = props
|
||||
|
||||
return (
|
||||
<div className='p-4 flex transition hover:bg-gray-200 dark:hover:bg-gray-900'>
|
||||
<Link href={`/application/users/${message.member.user.id}`}>
|
||||
<a>
|
||||
<div className='w-12 h-12 mr-4 flex flex-shrink-0 items-center justify-center'>
|
||||
<div className='w-10 h-10 drop-shadow-md'>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src={
|
||||
message.member.user.logo == null
|
||||
? '/images/data/user-default.png'
|
||||
: API_URL + message.member.user.logo
|
||||
}
|
||||
alt={"Users's profil picture"}
|
||||
width={50}
|
||||
height={50}
|
||||
draggable={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
<div className='w-full'>
|
||||
<div className='w-max flex items-center'>
|
||||
<Link href={`/application/users/${message.member.user.id}`}>
|
||||
<a>
|
||||
<span
|
||||
data-cy='message-member-user-name'
|
||||
className='font-bold text-gray-900 dark:text-gray-200'
|
||||
>
|
||||
{message.member.user.name}
|
||||
</span>
|
||||
</a>
|
||||
</Link>
|
||||
<span
|
||||
data-cy='message-date'
|
||||
className='text-gray-500 dark:text-gray-200 text-xs ml-4 select-none'
|
||||
>
|
||||
{date.format(new Date(message.createdAt), 'DD/MM/YYYY - HH:mm:ss')}
|
||||
</span>
|
||||
</div>
|
||||
<MessageContent message={message} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
import { useMemo } from 'react'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import gfm from 'remark-gfm'
|
||||
import remarkBreaks from 'remark-breaks'
|
||||
|
||||
import { Emoji, emojiPlugin, isStringWithOnlyOneEmoji } from '../../../../Emoji'
|
||||
import { MessageWithMember } from '../../../../../models/Message'
|
||||
|
||||
export interface MessageContentProps {
|
||||
message: MessageWithMember
|
||||
}
|
||||
|
||||
export const MessageContent: React.FC<MessageContentProps> = (props) => {
|
||||
const { message } = props
|
||||
|
||||
const isMessageWithOnlyOneEmoji = useMemo(() => {
|
||||
return isStringWithOnlyOneEmoji(message.value)
|
||||
}, [message.value])
|
||||
|
||||
if (isMessageWithOnlyOneEmoji) {
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
<Emoji value={message.value} size={40} />
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ReactMarkdown
|
||||
disallowedElements={['table']}
|
||||
unwrapDisallowed
|
||||
remarkPlugins={[[gfm], [remarkBreaks]]}
|
||||
rehypePlugins={[emojiPlugin]}
|
||||
linkTarget='_blank'
|
||||
components={{
|
||||
emoji: (props) => {
|
||||
return <Emoji value={props.value} size={20} />
|
||||
}
|
||||
}}
|
||||
>
|
||||
{message.value}
|
||||
</ReactMarkdown>
|
||||
)
|
||||
}
|
@ -0,0 +1 @@
|
||||
export * from './MessageContent'
|
1
components/Application/Messages/Message/index.ts
Normal file
1
components/Application/Messages/Message/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Message'
|
@ -1,12 +0,0 @@
|
||||
import { Meta, Story } from '@storybook/react'
|
||||
|
||||
import { Messages as Component } from './'
|
||||
|
||||
const Stories: Meta = {
|
||||
title: 'Messages',
|
||||
component: Component
|
||||
}
|
||||
|
||||
export default Stories
|
||||
|
||||
export const Messages: Story = (arguments_) => <Component {...arguments_} />
|
@ -1,10 +0,0 @@
|
||||
import { render } from '@testing-library/react'
|
||||
|
||||
import { Messages } from './'
|
||||
|
||||
describe('<Messages />', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(<Messages />)
|
||||
expect(baseElement).toBeTruthy()
|
||||
})
|
||||
})
|
@ -1,82 +1,45 @@
|
||||
import Image from 'next/image'
|
||||
import TextareaAutosize from 'react-textarea-autosize'
|
||||
import InfiniteScroll from 'react-infinite-scroll-component'
|
||||
|
||||
import { Loader } from 'components/design/Loader'
|
||||
import { Message } from './Message'
|
||||
import { useMessages } from 'contexts/Messages'
|
||||
import { Emoji } from 'components/Emoji'
|
||||
|
||||
export const Messages: React.FC = () => {
|
||||
const { messages, hasMore, nextPage } = useMessages()
|
||||
|
||||
if (messages.length === 0) {
|
||||
return (
|
||||
<div
|
||||
id='messages'
|
||||
className='w-full scrollbar-firefox-support overflow-y-auto transition-all flex-1 flex flex-col text-center mt-8 text-lg'
|
||||
>
|
||||
<p>
|
||||
Nothing to show here! <Emoji value=':ghost:' size={20} />
|
||||
</p>
|
||||
<p>Start chatting to kill this Ghost!</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='w-full scrollbar-firefox-support overflow-y-auto transition-all'>
|
||||
{new Array(20).fill(null).map((_, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='p-4 flex transition hover:bg-gray-200 dark:hover:bg-gray-900'
|
||||
>
|
||||
<div className='w-12 h-12 mr-4 flex flex-shrink-0 items-center justify-center'>
|
||||
<div className='w-10 h-10 drop-shadow-md'>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src='/images/data/user-default.png'
|
||||
alt='logo'
|
||||
width={50}
|
||||
height={50}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='w-full'>
|
||||
<div className='w-max flex items-center'>
|
||||
<span className='font-bold text-gray-900 dark:text-gray-200'>
|
||||
Divlo
|
||||
</span>
|
||||
<span className='text-gray-500 dark:text-gray-200 text-xs ml-4 select-none'>
|
||||
06/04/2021 - 22:28:40
|
||||
</span>
|
||||
</div>
|
||||
<div className='text-gray-800 dark:text-gray-300 font-paragraph mt-1 break-words'>
|
||||
<p>Message {index}</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit.
|
||||
Eum debitis voluptatum itaque quaerat. Nemo optio voluptas
|
||||
quas mollitia rerum commodi laboriosam voluptates et sit
|
||||
quo. Repudiandae eius at inventore magnam. Voluptas nisi
|
||||
maxime laborum architecto fuga a consequuntur reiciendis
|
||||
rerum beatae hic possimus, omnis dolorum libero, illo
|
||||
dolorem assumenda. Repellat, ad!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
<div
|
||||
id='messages'
|
||||
className='w-full scrollbar-firefox-support overflow-y-auto transition-all flex-1 flex flex-col-reverse'
|
||||
>
|
||||
<InfiniteScroll
|
||||
scrollableTarget='messages'
|
||||
className='messages-list'
|
||||
dataLength={messages.length}
|
||||
next={nextPage}
|
||||
inverse
|
||||
hasMore={hasMore}
|
||||
loader={<Loader />}
|
||||
>
|
||||
{messages.map((message) => {
|
||||
return <Message key={message.id} message={message} />
|
||||
})}
|
||||
</div>
|
||||
<div className='p-6 pb-4'>
|
||||
<div className='w-full h-full py-1 flex rounded-lg bg-gray-200 dark:bg-gray-800 text-gray-600 dark:text-gray-200'>
|
||||
<form className='w-full h-full flex items-center'>
|
||||
<TextareaAutosize
|
||||
className='w-full scrollbar-firefox-support p-2 px-6 my-2 bg-transparent outline-none font-paragraph tracking-wide resize-none'
|
||||
placeholder='Write a message...'
|
||||
wrap='soft'
|
||||
maxRows={6}
|
||||
/>
|
||||
</form>
|
||||
<div className='h-full flex items-center justify-around pr-6'>
|
||||
<button className='w-full h-full flex items-center justify-center p-1 text-2xl transition hover:-translate-y-1'>
|
||||
🙂
|
||||
</button>
|
||||
<button className='relative w-full h-full flex items-center justify-center p-1 text-green-800 dark:text-green-400 transition hover:-translate-y-1'>
|
||||
<input
|
||||
type='file'
|
||||
className='absolute w-full h-full opacity-0 cursor-pointer'
|
||||
/>
|
||||
<svg width='25' height='25' viewBox='0 0 22 22'>
|
||||
<path
|
||||
d='M11 0C4.925 0 0 4.925 0 11C0 17.075 4.925 22 11 22C17.075 22 22 17.075 22 11C22 4.925 17.075 0 11 0ZM12 15C12 15.2652 11.8946 15.5196 11.7071 15.7071C11.5196 15.8946 11.2652 16 11 16C10.7348 16 10.4804 15.8946 10.2929 15.7071C10.1054 15.5196 10 15.2652 10 15V12H7C6.73478 12 6.48043 11.8946 6.29289 11.7071C6.10536 11.5196 6 11.2652 6 11C6 10.7348 6.10536 10.4804 6.29289 10.2929C6.48043 10.1054 6.73478 10 7 10H10V7C10 6.73478 10.1054 6.48043 10.2929 6.29289C10.4804 6.10536 10.7348 6 11 6C11.2652 6 11.5196 6.10536 11.7071 6.29289C11.8946 6.48043 12 6.73478 12 7V10H15C15.2652 10 15.5196 10.1054 15.7071 10.2929C15.8946 10.4804 16 10.7348 16 11C16 11.2652 15.8946 11.5196 15.7071 11.7071C15.5196 11.8946 15.2652 12 15 12H12V15Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
Reference in New Issue
Block a user