feat(pages): add /application/[guildId]/[channelId] (#4)
				
					
				
			This commit is contained in:
		@@ -27,6 +27,7 @@
 | 
				
			|||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    ]
 | 
					    ],
 | 
				
			||||||
 | 
					    "@typescript-eslint/no-namespace": "off"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,15 @@
 | 
				
			|||||||
FROM node:16.11.0 AS dependencies
 | 
					FROM node:16.13.1 AS dependencies
 | 
				
			||||||
WORKDIR /usr/src/app
 | 
					WORKDIR /usr/src/app
 | 
				
			||||||
COPY ./package*.json ./
 | 
					COPY ./package*.json ./
 | 
				
			||||||
RUN npm clean-install
 | 
					RUN npm clean-install
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FROM node:16.11.0 AS builder
 | 
					FROM node:16.13.1 AS builder
 | 
				
			||||||
WORKDIR /usr/src/app
 | 
					WORKDIR /usr/src/app
 | 
				
			||||||
COPY ./ ./
 | 
					COPY ./ ./
 | 
				
			||||||
COPY --from=dependencies /usr/src/app/node_modules ./node_modules
 | 
					COPY --from=dependencies /usr/src/app/node_modules ./node_modules
 | 
				
			||||||
RUN npm run build
 | 
					RUN npm run build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FROM node:16.11.0 AS runner
 | 
					FROM node:16.13.1 AS runner
 | 
				
			||||||
WORKDIR /usr/src/app
 | 
					WORKDIR /usr/src/app
 | 
				
			||||||
ENV NODE_ENV=production
 | 
					ENV NODE_ENV=production
 | 
				
			||||||
COPY --from=builder /usr/src/app/next.config.js ./next.config.js
 | 
					COPY --from=builder /usr/src/app/next.config.js ./next.config.js
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,6 @@
 | 
				
			|||||||
import { useState, useEffect, useMemo } from 'react'
 | 
					import { useState, useEffect } from 'react'
 | 
				
			||||||
import Image from 'next/image'
 | 
					import Image from 'next/image'
 | 
				
			||||||
import useTranslation from 'next-translate/useTranslation'
 | 
					import { PlusIcon, MenuIcon, UsersIcon, XIcon } from '@heroicons/react/solid'
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  CogIcon,
 | 
					 | 
				
			||||||
  PlusIcon,
 | 
					 | 
				
			||||||
  MenuIcon,
 | 
					 | 
				
			||||||
  UsersIcon,
 | 
					 | 
				
			||||||
  XIcon
 | 
					 | 
				
			||||||
} from '@heroicons/react/solid'
 | 
					 | 
				
			||||||
import classNames from 'classnames'
 | 
					import classNames from 'classnames'
 | 
				
			||||||
import { useMediaQuery } from 'react-responsive'
 | 
					import { useMediaQuery } from 'react-responsive'
 | 
				
			||||||
import { useSwipeable } from 'react-swipeable'
 | 
					import { useSwipeable } from 'react-swipeable'
 | 
				
			||||||
@@ -15,31 +8,33 @@ import { useSwipeable } from 'react-swipeable'
 | 
				
			|||||||
import { Sidebar, DirectionSidebar } from './Sidebar'
 | 
					import { Sidebar, DirectionSidebar } from './Sidebar'
 | 
				
			||||||
import { IconButton } from 'components/design/IconButton'
 | 
					import { IconButton } from 'components/design/IconButton'
 | 
				
			||||||
import { IconLink } from 'components/design/IconLink'
 | 
					import { IconLink } from 'components/design/IconLink'
 | 
				
			||||||
import { Channels } from './Channels'
 | 
					 | 
				
			||||||
import { Guilds } from './Guilds/Guilds'
 | 
					import { Guilds } from './Guilds/Guilds'
 | 
				
			||||||
import { Divider } from '../design/Divider'
 | 
					import { Divider } from '../design/Divider'
 | 
				
			||||||
import { Members } from './Members'
 | 
					import { Members } from './Members'
 | 
				
			||||||
import { useAuthentication } from 'utils/authentication'
 | 
					import { useAuthentication } from 'tools/authentication'
 | 
				
			||||||
import { API_URL } from 'utils/api'
 | 
					import { API_URL } from 'tools/api'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface GuildsChannelsPath {
 | 
					export interface GuildsChannelsPath {
 | 
				
			||||||
  guildId: number
 | 
					  guildId: number
 | 
				
			||||||
  channelId: number
 | 
					  channelId: number
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ApplicationPath =
 | 
				
			||||||
 | 
					  | '/application'
 | 
				
			||||||
 | 
					  | '/application/guilds/join'
 | 
				
			||||||
 | 
					  | '/application/guilds/create'
 | 
				
			||||||
 | 
					  | `/application/users/${number}`
 | 
				
			||||||
 | 
					  | GuildsChannelsPath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ApplicationProps {
 | 
					export interface ApplicationProps {
 | 
				
			||||||
  path:
 | 
					  path: ApplicationPath
 | 
				
			||||||
    | '/application'
 | 
					  guildLeftSidebar?: React.ReactNode
 | 
				
			||||||
    | '/application/guilds/join'
 | 
					  title: string
 | 
				
			||||||
    | '/application/guilds/create'
 | 
					 | 
				
			||||||
    | '/application/users/[userId]'
 | 
					 | 
				
			||||||
    | GuildsChannelsPath
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Application: React.FC<ApplicationProps> = (props) => {
 | 
					export const Application: React.FC<ApplicationProps> = (props) => {
 | 
				
			||||||
  const { children, path } = props
 | 
					  const { children, path, guildLeftSidebar, title } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const { t } = useTranslation()
 | 
					 | 
				
			||||||
  const { user } = useAuthentication()
 | 
					  const { user } = useAuthentication()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const [visibleSidebars, setVisibleSidebars] = useState({
 | 
					  const [visibleSidebars, setVisibleSidebars] = useState({
 | 
				
			||||||
@@ -129,23 +124,6 @@ export const Application: React.FC<ApplicationProps> = (props) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const title = useMemo(() => {
 | 
					 | 
				
			||||||
    if (typeof path !== 'string') {
 | 
					 | 
				
			||||||
      // TODO: Returns the real name of the channel when doing APIs calls
 | 
					 | 
				
			||||||
      return `# Channel ${path.channelId}`
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (path.startsWith('/application/users/')) {
 | 
					 | 
				
			||||||
      return 'Settings'
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (path === '/application/guilds/join') {
 | 
					 | 
				
			||||||
      return 'Join a Guild'
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (path === '/application/guilds/create') {
 | 
					 | 
				
			||||||
      return t('application:create-a-guild')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return 'Application'
 | 
					 | 
				
			||||||
  }, [path, t])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    setMounted(true)
 | 
					    setMounted(true)
 | 
				
			||||||
  }, [])
 | 
					  }, [])
 | 
				
			||||||
@@ -163,12 +141,16 @@ export const Application: React.FC<ApplicationProps> = (props) => {
 | 
				
			|||||||
        >
 | 
					        >
 | 
				
			||||||
          {!visibleSidebars.left ? <MenuIcon /> : <XIcon />}
 | 
					          {!visibleSidebars.left ? <MenuIcon /> : <XIcon />}
 | 
				
			||||||
        </IconButton>
 | 
					        </IconButton>
 | 
				
			||||||
        <div className='text-md text-green-800 dark:text-green-400 font-semibold'>
 | 
					        <div
 | 
				
			||||||
 | 
					          data-cy='application-title'
 | 
				
			||||||
 | 
					          className='text-md text-green-800 dark:text-green-400 font-semibold'
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
          {title}
 | 
					          {title}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div className='flex space-x-2'>
 | 
					        <div className='flex space-x-2'>
 | 
				
			||||||
          {title.startsWith('#') && (
 | 
					          {title.startsWith('#') && (
 | 
				
			||||||
            <IconButton
 | 
					            <IconButton
 | 
				
			||||||
 | 
					              data-cy='icon-button-right-sidebar-members'
 | 
				
			||||||
              className='p-2 h-10 w-10'
 | 
					              className='p-2 h-10 w-10'
 | 
				
			||||||
              onClick={() => handleToggleSidebars('right')}
 | 
					              onClick={() => handleToggleSidebars('right')}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
@@ -201,7 +183,8 @@ export const Application: React.FC<ApplicationProps> = (props) => {
 | 
				
			|||||||
                    ? '/images/data/user-default.png'
 | 
					                    ? '/images/data/user-default.png'
 | 
				
			||||||
                    : API_URL + user.logo
 | 
					                    : API_URL + user.logo
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                alt='logo'
 | 
					                alt={"Users's profil picture"}
 | 
				
			||||||
 | 
					                draggable={false}
 | 
				
			||||||
                width={48}
 | 
					                width={48}
 | 
				
			||||||
                height={48}
 | 
					                height={48}
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
@@ -217,26 +200,7 @@ export const Application: React.FC<ApplicationProps> = (props) => {
 | 
				
			|||||||
            <Guilds path={path} />
 | 
					            <Guilds path={path} />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          {typeof path !== 'string' && (
 | 
					          {guildLeftSidebar}
 | 
				
			||||||
            <div className='flex flex-col justify-between w-full mt-2'>
 | 
					 | 
				
			||||||
              <div className='text-center p-2 mx-8 mt-2'>
 | 
					 | 
				
			||||||
                <h2 className='text-xl'>Guild Name</h2>
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
              <Divider />
 | 
					 | 
				
			||||||
              <div className='scrollbar-firefox-support overflow-y-auto'>
 | 
					 | 
				
			||||||
                <Channels path={path} />
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
              <Divider />
 | 
					 | 
				
			||||||
              <div className='flex justify-center items-center p-2 mb-1 space-x-6'>
 | 
					 | 
				
			||||||
                <IconButton className='h-10 w-10' title='Add a Channel'>
 | 
					 | 
				
			||||||
                  <PlusIcon />
 | 
					 | 
				
			||||||
                </IconButton>
 | 
					 | 
				
			||||||
                <IconButton className='h-7 w-7' title='Settings'>
 | 
					 | 
				
			||||||
                  <CogIcon />
 | 
					 | 
				
			||||||
                </IconButton>
 | 
					 | 
				
			||||||
              </div>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
          )}
 | 
					 | 
				
			||||||
        </Sidebar>
 | 
					        </Sidebar>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
@@ -252,13 +216,15 @@ export const Application: React.FC<ApplicationProps> = (props) => {
 | 
				
			|||||||
          {children}
 | 
					          {children}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <Sidebar
 | 
					        {typeof path !== 'string' && (
 | 
				
			||||||
          direction='right'
 | 
					          <Sidebar
 | 
				
			||||||
          visible={visibleSidebars.right}
 | 
					            direction='right'
 | 
				
			||||||
          isMobile={isMobile}
 | 
					            visible={visibleSidebars.right}
 | 
				
			||||||
        >
 | 
					            isMobile={isMobile}
 | 
				
			||||||
          <Members />
 | 
					          >
 | 
				
			||||||
        </Sidebar>
 | 
					            <Members />
 | 
				
			||||||
 | 
					          </Sidebar>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
      </main>
 | 
					      </main>
 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								components/Application/Channels/Channel/Channel.stories.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								components/Application/Channels/Channel/Channel.stories.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					import { Meta, Story } from '@storybook/react'
 | 
				
			||||||
 | 
					import { channelExample } from '../../../../cypress/fixtures/channels/channel'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Channel as Component, ChannelProps } from './Channel'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Stories: Meta = {
 | 
				
			||||||
 | 
					  title: 'Channel',
 | 
				
			||||||
 | 
					  component: Component
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Stories
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Channel: Story<ChannelProps> = (arguments_) => {
 | 
				
			||||||
 | 
					  return <Component {...arguments_} />
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					Channel.args = { path: { channelId: 1, guildId: 1 }, channel: channelExample }
 | 
				
			||||||
							
								
								
									
										13
									
								
								components/Application/Channels/Channel/Channel.test.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								components/Application/Channels/Channel/Channel.test.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					import { render } from '@testing-library/react'
 | 
				
			||||||
 | 
					import { channelExample } from 'cypress/fixtures/channels/channel'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Channel } from './Channel'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('<Channel />', () => {
 | 
				
			||||||
 | 
					  it('should render successfully', () => {
 | 
				
			||||||
 | 
					    const { baseElement } = render(
 | 
				
			||||||
 | 
					      <Channel channel={channelExample} path={{ channelId: 1, guildId: 1 }} />
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    expect(baseElement).toBeTruthy()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										32
									
								
								components/Application/Channels/Channel/Channel.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								components/Application/Channels/Channel/Channel.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					import classNames from 'classnames'
 | 
				
			||||||
 | 
					import Link from 'next/link'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { GuildsChannelsPath } from '../../Application'
 | 
				
			||||||
 | 
					import { Channel as ChannelType } from '../../../../models/Channel'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ChannelProps {
 | 
				
			||||||
 | 
					  path: GuildsChannelsPath
 | 
				
			||||||
 | 
					  channel: ChannelType
 | 
				
			||||||
 | 
					  selected?: boolean
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Channel: React.FC<ChannelProps> = (props) => {
 | 
				
			||||||
 | 
					  const { channel, path, selected = false } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Link href={`/application/${path.guildId}/${channel.id}`}>
 | 
				
			||||||
 | 
					      <a
 | 
				
			||||||
 | 
					        className={classNames(
 | 
				
			||||||
 | 
					          'hover:bg-gray-100 group flex items-center justify-between text-sm py-2 my-3 mx-3 transition-colors dark:hover:bg-gray-600 duration-200 rounded-lg',
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            'text-green-800 dark:text-green-400 font-semibold': selected
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <span className='ml-2 mr-4' data-cy='channel-name'>
 | 
				
			||||||
 | 
					          # {channel.name}
 | 
				
			||||||
 | 
					        </span>
 | 
				
			||||||
 | 
					      </a>
 | 
				
			||||||
 | 
					    </Link>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								components/Application/Channels/Channel/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								components/Application/Channels/Channel/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export * from './Channel'
 | 
				
			||||||
@@ -1,15 +0,0 @@
 | 
				
			|||||||
import { Meta, Story } from '@storybook/react'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { Channels as Component, ChannelsProps } from './'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Stories: Meta = {
 | 
					 | 
				
			||||||
  title: 'Channels',
 | 
					 | 
				
			||||||
  component: Component
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Stories
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const Channels: Story<ChannelsProps> = (arguments_) => (
 | 
					 | 
				
			||||||
  <Component {...arguments_} />
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
Channels.args = { path: { channelId: 1, guildId: 2 } }
 | 
					 | 
				
			||||||
@@ -1,12 +0,0 @@
 | 
				
			|||||||
import { render } from '@testing-library/react'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { Channels } from './'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('<Channels />', () => {
 | 
					 | 
				
			||||||
  it('should render successfully', () => {
 | 
					 | 
				
			||||||
    const { baseElement } = render(
 | 
					 | 
				
			||||||
      <Channels path={{ channelId: 1, guildId: 2 }} />
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    expect(baseElement).toBeTruthy()
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
@@ -1,7 +1,9 @@
 | 
				
			|||||||
import Link from 'next/link'
 | 
					import InfiniteScroll from 'react-infinite-scroll-component'
 | 
				
			||||||
import classNames from 'classnames'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { GuildsChannelsPath } from '../Application'
 | 
					import { GuildsChannelsPath } from '../Application'
 | 
				
			||||||
 | 
					import { Loader } from 'components/design/Loader'
 | 
				
			||||||
 | 
					import { Channel } from './Channel'
 | 
				
			||||||
 | 
					import { useChannels } from 'contexts/Channels'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ChannelsProps {
 | 
					export interface ChannelsProps {
 | 
				
			||||||
  path: GuildsChannelsPath
 | 
					  path: GuildsChannelsPath
 | 
				
			||||||
@@ -10,27 +12,33 @@ export interface ChannelsProps {
 | 
				
			|||||||
export const Channels: React.FC<ChannelsProps> = (props) => {
 | 
					export const Channels: React.FC<ChannelsProps> = (props) => {
 | 
				
			||||||
  const { path } = props
 | 
					  const { path } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { channels, hasMore, nextPage } = useChannels()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <nav className='w-full'>
 | 
					    <div
 | 
				
			||||||
      {new Array(100).fill(null).map((_, index) => {
 | 
					      id='channels'
 | 
				
			||||||
        return (
 | 
					      className='scrollbar-firefox-support overflow-y-auto flex-1 flex flex-col'
 | 
				
			||||||
          <Link key={index} href={`/application/${path.guildId}/${index}`}>
 | 
					    >
 | 
				
			||||||
            <a
 | 
					      <InfiniteScroll
 | 
				
			||||||
              className={classNames(
 | 
					        className='w-full channels-list'
 | 
				
			||||||
                'hover:bg-gray-100 group flex items-center justify-between text-sm py-2 my-3 mx-3 transition-colors dark:hover:bg-gray-600 duration-200 rounded-lg',
 | 
					        scrollableTarget='channels'
 | 
				
			||||||
                {
 | 
					        dataLength={channels.length}
 | 
				
			||||||
                  'text-green-800 dark:text-green-400 font-semibold':
 | 
					        next={nextPage}
 | 
				
			||||||
                    typeof path !== 'string' && path.channelId === index,
 | 
					        hasMore={hasMore}
 | 
				
			||||||
                  'text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-white font-normal':
 | 
					        loader={<Loader />}
 | 
				
			||||||
                    typeof path === 'string'
 | 
					      >
 | 
				
			||||||
                }
 | 
					        {channels.map((channel) => {
 | 
				
			||||||
              )}
 | 
					          const selected = channel.id === path.channelId
 | 
				
			||||||
            >
 | 
					          return (
 | 
				
			||||||
              <span className='ml-2 mr-4'># Channel {index}</span>
 | 
					            <Channel
 | 
				
			||||||
            </a>
 | 
					              key={channel.id}
 | 
				
			||||||
          </Link>
 | 
					              channel={channel}
 | 
				
			||||||
        )
 | 
					              path={path}
 | 
				
			||||||
      })}
 | 
					              selected={selected}
 | 
				
			||||||
    </nav>
 | 
					            />
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        })}
 | 
				
			||||||
 | 
					      </InfiniteScroll>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ import { Form } from 'react-component-form'
 | 
				
			|||||||
import TextareaAutosize from 'react-textarea-autosize'
 | 
					import TextareaAutosize from 'react-textarea-autosize'
 | 
				
			||||||
import { AxiosResponse } from 'axios'
 | 
					import { AxiosResponse } from 'axios'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { useAuthentication } from '../../../utils/authentication'
 | 
					import { useAuthentication } from '../../../tools/authentication'
 | 
				
			||||||
import { HandleSubmitCallback, useForm } from '../../../hooks/useForm'
 | 
					import { HandleSubmitCallback, useForm } from '../../../hooks/useForm'
 | 
				
			||||||
import { GuildComplete, guildSchema } from '../../../models/Guild'
 | 
					import { GuildComplete, guildSchema } from '../../../models/Guild'
 | 
				
			||||||
import { Input } from '../../design/Input'
 | 
					import { Input } from '../../design/Input'
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										38
									
								
								components/Application/GuildLeftSidebar/GuildLeftSidebar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								components/Application/GuildLeftSidebar/GuildLeftSidebar.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					import { CogIcon, PlusIcon } from '@heroicons/react/solid'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { useGuildMember } from 'contexts/GuildMember'
 | 
				
			||||||
 | 
					import { Divider } from 'components/design/Divider'
 | 
				
			||||||
 | 
					import { Channels } from 'components/Application/Channels'
 | 
				
			||||||
 | 
					import { IconButton } from 'components/design/IconButton'
 | 
				
			||||||
 | 
					import { GuildsChannelsPath } from '..'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface GuildLeftSidebarProps {
 | 
				
			||||||
 | 
					  path: GuildsChannelsPath
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const GuildLeftSidebar: React.FC<GuildLeftSidebarProps> = (props) => {
 | 
				
			||||||
 | 
					  const { path } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { guild } = useGuildMember()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className='flex flex-col justify-between w-full mt-2'>
 | 
				
			||||||
 | 
					      <div className='text-center p-2 mx-8 mt-2'>
 | 
				
			||||||
 | 
					        <h2 data-cy='guild-left-sidebar-title' className='text-xl'>
 | 
				
			||||||
 | 
					          {guild.name}
 | 
				
			||||||
 | 
					        </h2>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <Divider />
 | 
				
			||||||
 | 
					      <Channels path={path} />
 | 
				
			||||||
 | 
					      <Divider />
 | 
				
			||||||
 | 
					      <div className='flex justify-center items-center p-2 mb-1 space-x-6'>
 | 
				
			||||||
 | 
					        <IconButton className='h-10 w-10' title='Add a Channel'>
 | 
				
			||||||
 | 
					          <PlusIcon />
 | 
				
			||||||
 | 
					        </IconButton>
 | 
				
			||||||
 | 
					        <IconButton className='h-7 w-7' title='Settings'>
 | 
				
			||||||
 | 
					          <CogIcon />
 | 
				
			||||||
 | 
					        </IconButton>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								components/Application/GuildLeftSidebar/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								components/Application/GuildLeftSidebar/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export * from './GuildLeftSidebar'
 | 
				
			||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import { Meta, Story } from '@storybook/react'
 | 
					import { Meta, Story } from '@storybook/react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Guild as Component, GuildProps } from './Guild'
 | 
					import { Guild as Component, GuildProps } from './Guild'
 | 
				
			||||||
 | 
					import { guildExample } from '../../../../cypress/fixtures/guilds/guild'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Stories: Meta = {
 | 
					const Stories: Meta = {
 | 
				
			||||||
  title: 'Guild',
 | 
					  title: 'Guild',
 | 
				
			||||||
@@ -14,12 +15,7 @@ export const Guild: Story<GuildProps> = (arguments_) => {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
Guild.args = {
 | 
					Guild.args = {
 | 
				
			||||||
  guild: {
 | 
					  guild: {
 | 
				
			||||||
    id: 1,
 | 
					    ...guildExample,
 | 
				
			||||||
    name: 'GuildExample',
 | 
					    defaultChannelId: 1
 | 
				
			||||||
    description: 'guild example.',
 | 
					 | 
				
			||||||
    icon: null,
 | 
					 | 
				
			||||||
    createdAt: new Date().toISOString(),
 | 
					 | 
				
			||||||
    updatedAt: new Date().toISOString(),
 | 
					 | 
				
			||||||
    membersCount: 1
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,19 +1,15 @@
 | 
				
			|||||||
import { render } from '@testing-library/react'
 | 
					import { render } from '@testing-library/react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Guild } from './Guild'
 | 
					import { Guild } from './Guild'
 | 
				
			||||||
 | 
					import { guildExample } from '../../../../cypress/fixtures/guilds/guild'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('<Guild />', () => {
 | 
					describe('<Guild />', () => {
 | 
				
			||||||
  it('should render successfully', () => {
 | 
					  it('should render successfully', () => {
 | 
				
			||||||
    const { baseElement } = render(
 | 
					    const { baseElement } = render(
 | 
				
			||||||
      <Guild
 | 
					      <Guild
 | 
				
			||||||
        guild={{
 | 
					        guild={{
 | 
				
			||||||
          id: 1,
 | 
					          ...guildExample,
 | 
				
			||||||
          name: 'GuildExample',
 | 
					          defaultChannelId: 1
 | 
				
			||||||
          description: 'guild example.',
 | 
					 | 
				
			||||||
          icon: null,
 | 
					 | 
				
			||||||
          createdAt: new Date().toISOString(),
 | 
					 | 
				
			||||||
          updatedAt: new Date().toISOString(),
 | 
					 | 
				
			||||||
          membersCount: 1
 | 
					 | 
				
			||||||
        }}
 | 
					        }}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
							
								
								
									
										35
									
								
								components/Application/Guilds/Guild/Guild.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								components/Application/Guilds/Guild/Guild.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					import Image from 'next/image'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { GuildWithDefaultChannelId } from '../../../../models/Guild'
 | 
				
			||||||
 | 
					import { IconLink } from '../../../design/IconLink'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface GuildProps {
 | 
				
			||||||
 | 
					  guild: GuildWithDefaultChannelId
 | 
				
			||||||
 | 
					  selected?: boolean
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Guild: React.FC<GuildProps> = (props) => {
 | 
				
			||||||
 | 
					  const { guild, selected } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <IconLink
 | 
				
			||||||
 | 
					      className='mt-2'
 | 
				
			||||||
 | 
					      href={`/application/${guild.id}/${guild.defaultChannelId}`}
 | 
				
			||||||
 | 
					      selected={selected}
 | 
				
			||||||
 | 
					      title={guild.name}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <div className='pl-[6px]'>
 | 
				
			||||||
 | 
					        <Image
 | 
				
			||||||
 | 
					          className='rounded-full'
 | 
				
			||||||
 | 
					          src={
 | 
				
			||||||
 | 
					            guild.icon != null ? guild.icon : '/images/data/guild-default.png'
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          alt='logo'
 | 
				
			||||||
 | 
					          width={48}
 | 
				
			||||||
 | 
					          height={48}
 | 
				
			||||||
 | 
					          draggable={false}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </IconLink>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,15 +0,0 @@
 | 
				
			|||||||
import { Meta, Story } from '@storybook/react'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { Guilds as Component, GuildsProps } from './'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Stories: Meta = {
 | 
					 | 
				
			||||||
  title: 'Guilds',
 | 
					 | 
				
			||||||
  component: Component
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Stories
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const Guilds: Story<GuildsProps> = (arguments_) => (
 | 
					 | 
				
			||||||
  <Component {...arguments_} />
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
Guilds.args = { path: { channelId: 1, guildId: 2 } }
 | 
					 | 
				
			||||||
@@ -1,12 +0,0 @@
 | 
				
			|||||||
import { render } from '@testing-library/react'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { Guilds } from './'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('<Guilds />', () => {
 | 
					 | 
				
			||||||
  it('should render successfully', () => {
 | 
					 | 
				
			||||||
    const { baseElement } = render(
 | 
					 | 
				
			||||||
      <Guilds path={{ channelId: 1, guildId: 2 }} />
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    expect(baseElement).toBeTruthy()
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
@@ -1,34 +1,37 @@
 | 
				
			|||||||
import Image from 'next/image'
 | 
					import InfiniteScroll from 'react-infinite-scroll-component'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { ApplicationProps } from '../Application'
 | 
					import { Loader } from 'components/design/Loader'
 | 
				
			||||||
import { IconLink } from '../../design/IconLink'
 | 
					import { Guild } from './Guild'
 | 
				
			||||||
 | 
					import { useGuilds } from 'contexts/Guilds'
 | 
				
			||||||
 | 
					import { GuildsChannelsPath } from '..'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface GuildsProps extends ApplicationProps {}
 | 
					export interface GuildsProps {
 | 
				
			||||||
 | 
					  path: GuildsChannelsPath | string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Guilds: React.FC<GuildsProps> = (props) => {
 | 
					export const Guilds: React.FC<GuildsProps> = (props) => {
 | 
				
			||||||
  const { path } = props
 | 
					  const { path } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { guilds, hasMore, nextPage } = useGuilds()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className='min-w-[92px] mt-[130px] pt-2 h-full border-r-2 border-gray-500 dark:border-white/20 space-y-2 scrollbar-firefox-support overflow-y-auto'>
 | 
					    <div
 | 
				
			||||||
      {new Array(100).fill(null).map((_, index) => {
 | 
					      id='guilds-list'
 | 
				
			||||||
        return (
 | 
					      className='min-w-[92px] mt-[130px] pt-2 h-full border-r-2 border-gray-500 dark:border-white/20 space-y-2 scrollbar-firefox-support overflow-y-auto'
 | 
				
			||||||
          <IconLink
 | 
					    >
 | 
				
			||||||
            key={index}
 | 
					      <InfiniteScroll
 | 
				
			||||||
            href={`/application/${index}/0`}
 | 
					        className='guilds-list'
 | 
				
			||||||
            selected={typeof path !== 'string' && path.guildId === index}
 | 
					        dataLength={guilds.length}
 | 
				
			||||||
            title='Guild Name'
 | 
					        next={nextPage}
 | 
				
			||||||
          >
 | 
					        hasMore={hasMore}
 | 
				
			||||||
            <div className='pl-[6px]'>
 | 
					        scrollableTarget='guilds-list'
 | 
				
			||||||
              <Image
 | 
					        loader={<Loader />}
 | 
				
			||||||
                src='/images/icons/Thream.png'
 | 
					      >
 | 
				
			||||||
                alt='logo'
 | 
					        {guilds.map((guild) => {
 | 
				
			||||||
                width={48}
 | 
					          const selected = typeof path !== 'string' && path.guildId === guild.id
 | 
				
			||||||
                height={48}
 | 
					          return <Guild key={guild.id} guild={guild} selected={selected} />
 | 
				
			||||||
              />
 | 
					        })}
 | 
				
			||||||
            </div>
 | 
					      </InfiniteScroll>
 | 
				
			||||||
          </IconLink>
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
      })}
 | 
					 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					import { Meta, Story } from '@storybook/react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { GuildPublic as Component, GuildPublicProps } from './GuildPublic'
 | 
				
			||||||
 | 
					import { guildExample } from '../../../../cypress/fixtures/guilds/guild'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Stories: Meta = {
 | 
				
			||||||
 | 
					  title: 'GuildPublic',
 | 
				
			||||||
 | 
					  component: Component
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Stories
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const GuildPublic: Story<GuildPublicProps> = (arguments_) => {
 | 
				
			||||||
 | 
					  return <Component {...arguments_} />
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					GuildPublic.args = {
 | 
				
			||||||
 | 
					  guild: {
 | 
				
			||||||
 | 
					    ...guildExample,
 | 
				
			||||||
 | 
					    membersCount: 1
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					import { render } from '@testing-library/react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { GuildPublic } from './GuildPublic'
 | 
				
			||||||
 | 
					import { guildExample } from '../../../../cypress/fixtures/guilds/guild'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('<GuildPublic />', () => {
 | 
				
			||||||
 | 
					  it('should render successfully', () => {
 | 
				
			||||||
 | 
					    const { baseElement } = render(
 | 
				
			||||||
 | 
					      <GuildPublic
 | 
				
			||||||
 | 
					        guild={{
 | 
				
			||||||
 | 
					          ...guildExample,
 | 
				
			||||||
 | 
					          membersCount: 1
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    expect(baseElement).toBeTruthy()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
@@ -1,19 +1,19 @@
 | 
				
			|||||||
import Image from 'next/image'
 | 
					import Image from 'next/image'
 | 
				
			||||||
 | 
					import useTranslation from 'next-translate/useTranslation'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { GuildPublic } from 'models/Guild'
 | 
					import { GuildPublic as GuildPublicType } from '../../../../models/Guild'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface GuildProps {
 | 
					export interface GuildPublicProps {
 | 
				
			||||||
  guild: GuildPublic
 | 
					  guild: GuildPublicType
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Guild: React.FC<GuildProps> = (props) => {
 | 
					export const GuildPublic: React.FC<GuildPublicProps> = (props) => {
 | 
				
			||||||
  const { guild } = props
 | 
					  const { guild } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { t } = useTranslation()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div
 | 
					    <div className='max-w-sm flex flex-col items-center justify-center border-gray-500 dark:border-gray-700 p-4 cursor-pointer rounded shadow-lg border transition duration-200 ease-in-out hover:-translate-y-2 hover:shadow-none'>
 | 
				
			||||||
      key={guild.id}
 | 
					 | 
				
			||||||
      className='max-w-sm flex flex-col items-center justify-center border-gray-500 dark:border-gray-700 p-4 cursor-pointer rounded shadow-lg border transition duration-200 ease-in-out hover:-translate-y-2 hover:shadow-none'
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <Image
 | 
					      <Image
 | 
				
			||||||
        className='rounded-full'
 | 
					        className='rounded-full'
 | 
				
			||||||
        src={guild.icon != null ? guild.icon : '/images/data/guild-default.png'}
 | 
					        src={guild.icon != null ? guild.icon : '/images/data/guild-default.png'}
 | 
				
			||||||
@@ -28,7 +28,7 @@ export const Guild: React.FC<GuildProps> = (props) => {
 | 
				
			|||||||
        <p className='text-base w-11/12 mx-auto'>{guild.description}</p>
 | 
					        <p className='text-base w-11/12 mx-auto'>{guild.description}</p>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <p className='flex flex-col text-green-800 dark:text-green-400 mt-4'>
 | 
					      <p className='flex flex-col text-green-800 dark:text-green-400 mt-4'>
 | 
				
			||||||
        {guild.membersCount} members
 | 
					        {guild.membersCount} {t('application:members')}
 | 
				
			||||||
      </p>
 | 
					      </p>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export * from './GuildPublic'
 | 
				
			||||||
@@ -1,49 +1,31 @@
 | 
				
			|||||||
import { useCallback, useEffect, useState, useRef } from 'react'
 | 
					import useTranslation from 'next-translate/useTranslation'
 | 
				
			||||||
 | 
					import { useEffect, useState } from 'react'
 | 
				
			||||||
import InfiniteScroll from 'react-infinite-scroll-component'
 | 
					import InfiniteScroll from 'react-infinite-scroll-component'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { useAuthentication } from 'utils/authentication'
 | 
					import { useAuthentication } from 'tools/authentication'
 | 
				
			||||||
import { GuildPublic } from 'models/Guild'
 | 
					import { GuildPublic as GuildPublicType } from 'models/Guild'
 | 
				
			||||||
import { Loader } from 'components/design/Loader'
 | 
					import { Loader } from 'components/design/Loader'
 | 
				
			||||||
import { useFetchState } from 'hooks/useFetchState'
 | 
					import { GuildPublic } from './GuildPublic'
 | 
				
			||||||
import { Guild } from './Guild'
 | 
					import { usePagination } from 'hooks/usePagination'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const JoinGuildsPublic: React.FC = () => {
 | 
					export const JoinGuildsPublic: React.FC = () => {
 | 
				
			||||||
  const [guilds, setGuilds] = useState<GuildPublic[]>([])
 | 
					  const [search, setSearch] = useState('')
 | 
				
			||||||
  const [hasMore, setHasMore] = useState(true)
 | 
					 | 
				
			||||||
  const [inputSearch, setInputSearch] = useState('')
 | 
					 | 
				
			||||||
  const [fetchState, setFetchState] = useFetchState('idle')
 | 
					 | 
				
			||||||
  const afterId = useRef<number | null>(null)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const { authentication } = useAuthentication()
 | 
					  const { authentication } = useAuthentication()
 | 
				
			||||||
 | 
					  const { t } = useTranslation()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const fetchGuilds = useCallback(async (): Promise<void> => {
 | 
					  const { items, hasMore, nextPage, resetPagination } =
 | 
				
			||||||
    if (fetchState !== 'idle') {
 | 
					    usePagination<GuildPublicType>({
 | 
				
			||||||
      return
 | 
					      api: authentication.api,
 | 
				
			||||||
    }
 | 
					      url: '/guilds/public'
 | 
				
			||||||
    setFetchState('loading')
 | 
					 | 
				
			||||||
    const { data } = await authentication.api.get<GuildPublic[]>(
 | 
					 | 
				
			||||||
      `/guilds/public?limit=20&search=${inputSearch}${
 | 
					 | 
				
			||||||
        afterId.current != null ? `&after=${afterId.current}` : ''
 | 
					 | 
				
			||||||
      }`
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    afterId.current = data.length > 0 ? data[data.length - 1].id : null
 | 
					 | 
				
			||||||
    setGuilds((oldGuilds) => {
 | 
					 | 
				
			||||||
      return [...oldGuilds, ...data]
 | 
					 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    setHasMore(data.length > 0)
 | 
					 | 
				
			||||||
    setFetchState('idle')
 | 
					 | 
				
			||||||
  }, [authentication, fetchState, setFetchState, inputSearch])
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    afterId.current = null
 | 
					    resetPagination()
 | 
				
			||||||
    setGuilds([])
 | 
					    nextPage({ search })
 | 
				
			||||||
    fetchGuilds().catch((error) => {
 | 
					  }, [resetPagination, nextPage, search])
 | 
				
			||||||
      console.error(error)
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
  }, [inputSearch]) // eslint-disable-line react-hooks/exhaustive-deps
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
 | 
					  const handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
 | 
				
			||||||
    setInputSearch(event.target.value)
 | 
					    setSearch(event.target.value)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
@@ -54,19 +36,19 @@ export const JoinGuildsPublic: React.FC = () => {
 | 
				
			|||||||
        className='w-10/12 sm:w-8/12 md:w-6/12 lg:w-5/12 bg-white dark:bg-[#3B3B3B] border-gray-500 dark:border-gray-700 p-3 my-6 mt-16 mx-auto rounded-md border'
 | 
					        className='w-10/12 sm:w-8/12 md:w-6/12 lg:w-5/12 bg-white dark:bg-[#3B3B3B] border-gray-500 dark:border-gray-700 p-3 my-6 mt-16 mx-auto rounded-md border'
 | 
				
			||||||
        type='search'
 | 
					        type='search'
 | 
				
			||||||
        name='search-guild'
 | 
					        name='search-guild'
 | 
				
			||||||
        placeholder='🔎  Search...'
 | 
					        placeholder={`🔎  ${t('application:search')}...`}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
      <div className='w-full flex items-center justify-center p-12'>
 | 
					      <div className='w-full flex items-center justify-center p-12'>
 | 
				
			||||||
        <InfiniteScroll
 | 
					        <InfiniteScroll
 | 
				
			||||||
          className='guilds-list max-w-[1600px] grid grid-cols-1 xl:grid-cols-3 md:grid-cols-2 sm:grid-cols-1 gap-8 !overflow-hidden'
 | 
					          className='guilds-public-list max-w-[1600px] grid grid-cols-1 xl:grid-cols-3 md:grid-cols-2 sm:grid-cols-1 gap-8 !overflow-hidden'
 | 
				
			||||||
          dataLength={guilds.length}
 | 
					          dataLength={items.length}
 | 
				
			||||||
          next={fetchGuilds}
 | 
					          next={nextPage}
 | 
				
			||||||
          scrollableTarget='application-page-content'
 | 
					          scrollableTarget='application-page-content'
 | 
				
			||||||
          hasMore={hasMore}
 | 
					          hasMore={hasMore}
 | 
				
			||||||
          loader={<Loader />}
 | 
					          loader={<Loader />}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          {guilds.map((guild) => {
 | 
					          {items.map((guild) => {
 | 
				
			||||||
            return <Guild guild={guild} key={guild.id} />
 | 
					            return <GuildPublic guild={guild} key={guild.id} />
 | 
				
			||||||
          })}
 | 
					          })}
 | 
				
			||||||
        </InfiniteScroll>
 | 
					        </InfiniteScroll>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								components/Application/Members/Member/Member.stories.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								components/Application/Members/Member/Member.stories.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					import { Meta, Story } from '@storybook/react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Member as Component, MemberProps } from './Member'
 | 
				
			||||||
 | 
					import { memberExampleComplete } from '../../../../cypress/fixtures/members/member'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Stories: Meta = {
 | 
				
			||||||
 | 
					  title: 'Member',
 | 
				
			||||||
 | 
					  component: Component
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Stories
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Member: Story<MemberProps> = (arguments_) => {
 | 
				
			||||||
 | 
					  return <Component {...arguments_} />
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					Member.args = { member: memberExampleComplete }
 | 
				
			||||||
							
								
								
									
										11
									
								
								components/Application/Members/Member/Member.test.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								components/Application/Members/Member/Member.test.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import { render } from '@testing-library/react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Member } from './Member'
 | 
				
			||||||
 | 
					import { memberExampleComplete } from '../../../../cypress/fixtures/members/member'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('<Member />', () => {
 | 
				
			||||||
 | 
					  it('should render successfully', () => {
 | 
				
			||||||
 | 
					    const { baseElement } = render(<Member member={memberExampleComplete} />)
 | 
				
			||||||
 | 
					    expect(baseElement).toBeTruthy()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										45
									
								
								components/Application/Members/Member/Member.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								components/Application/Members/Member/Member.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					import Image from 'next/image'
 | 
				
			||||||
 | 
					import Link from 'next/link'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { MemberWithPublicUser } from '../../../../models/Member'
 | 
				
			||||||
 | 
					import { API_URL } from '../../../../tools/api'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface MemberProps {
 | 
				
			||||||
 | 
					  member: MemberWithPublicUser
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Member: React.FC<MemberProps> = (props) => {
 | 
				
			||||||
 | 
					  const { member } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Link href={`/application/users/${member.user.id}`}>
 | 
				
			||||||
 | 
					      <a>
 | 
				
			||||||
 | 
					        <div className='flex items-center cursor-pointer py-2 px-4 pr-10 rounded hover:bg-gray-300 dark:hover:bg-gray-900'>
 | 
				
			||||||
 | 
					          <div className='min-w-[50px] flex rounded-full'>
 | 
				
			||||||
 | 
					            <Image
 | 
				
			||||||
 | 
					              src={
 | 
				
			||||||
 | 
					                member.user.logo == null
 | 
				
			||||||
 | 
					                  ? '/images/data/user-default.png'
 | 
				
			||||||
 | 
					                  : API_URL + member.user.logo
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              alt={"Users's profil picture"}
 | 
				
			||||||
 | 
					              height={50}
 | 
				
			||||||
 | 
					              width={50}
 | 
				
			||||||
 | 
					              draggable={false}
 | 
				
			||||||
 | 
					              className='rounded-full'
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          <div className='max-w-[145px] ml-4'>
 | 
				
			||||||
 | 
					            <p
 | 
				
			||||||
 | 
					              data-cy='member-user-name'
 | 
				
			||||||
 | 
					              className='overflow-hidden whitespace-nowrap overflow-ellipsis'
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              {member.user.name}
 | 
				
			||||||
 | 
					            </p>
 | 
				
			||||||
 | 
					            {member.user.status != null && <span>{member.user.status}</span>}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </a>
 | 
				
			||||||
 | 
					    </Link>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								components/Application/Members/Member/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								components/Application/Members/Member/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export * from './Member'
 | 
				
			||||||
@@ -1,10 +0,0 @@
 | 
				
			|||||||
import { render } from '@testing-library/react'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { Members } from './Members'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('<Members />', () => {
 | 
					 | 
				
			||||||
  it('should render successfully', () => {
 | 
					 | 
				
			||||||
    const { baseElement } = render(<Members />)
 | 
					 | 
				
			||||||
    expect(baseElement).toBeTruthy()
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
@@ -1,57 +1,36 @@
 | 
				
			|||||||
import Image from 'next/image'
 | 
					import useTranslation from 'next-translate/useTranslation'
 | 
				
			||||||
 | 
					import InfiniteScroll from 'react-infinite-scroll-component'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Divider } from '../../design/Divider'
 | 
					import { Divider } from '../../design/Divider'
 | 
				
			||||||
 | 
					import { Loader } from 'components/design/Loader'
 | 
				
			||||||
 | 
					import { useMembers } from 'contexts/Members'
 | 
				
			||||||
 | 
					import { Member } from './Member'
 | 
				
			||||||
 | 
					import { capitalize } from '../../../tools/utils/capitalize'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Members: React.FC = () => {
 | 
					export const Members: React.FC = () => {
 | 
				
			||||||
 | 
					  const { members, hasMore, nextPage } = useMembers()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { t } = useTranslation()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <div className='mb-2'>
 | 
					      <div className='mb-2'>
 | 
				
			||||||
        <h1 className='text-center pt-2 my-2 text-xl'>Members</h1>
 | 
					        <h1 data-cy='members-title' className='text-center pt-2 my-2 text-xl'>
 | 
				
			||||||
 | 
					          {capitalize(t('application:members'))}
 | 
				
			||||||
 | 
					        </h1>
 | 
				
			||||||
        <Divider />
 | 
					        <Divider />
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div className='flex items-center cursor-pointer py-2 px-4 pr-10 rounded hover:bg-gray-300 dark:hover:bg-gray-900'>
 | 
					      <InfiniteScroll
 | 
				
			||||||
        <div className='min-w-[50px] flex rounded-full border-2 border-green-500'>
 | 
					        className='members-list'
 | 
				
			||||||
          <Image
 | 
					        dataLength={members.length}
 | 
				
			||||||
            src='/images/data/divlo.png'
 | 
					        next={nextPage}
 | 
				
			||||||
            alt={"Users's profil picture"}
 | 
					        hasMore={hasMore}
 | 
				
			||||||
            height={50}
 | 
					        loader={<Loader />}
 | 
				
			||||||
            width={50}
 | 
					      >
 | 
				
			||||||
            draggable='false'
 | 
					        {members.map((member) => {
 | 
				
			||||||
            className='rounded-full'
 | 
					          return <Member key={member.id} member={member} />
 | 
				
			||||||
          />
 | 
					        })}
 | 
				
			||||||
        </div>
 | 
					      </InfiniteScroll>
 | 
				
			||||||
        <div className='max-w-[145px] ml-4'>
 | 
					 | 
				
			||||||
          <p className='overflow-hidden whitespace-nowrap overflow-ellipsis'>
 | 
					 | 
				
			||||||
            Walidouxssssssssssss
 | 
					 | 
				
			||||||
          </p>
 | 
					 | 
				
			||||||
          <span className='text-green-600 dark:text-green-400'>Online</span>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      {new Array(100).fill(null).map((_, index) => {
 | 
					 | 
				
			||||||
        return (
 | 
					 | 
				
			||||||
          <div
 | 
					 | 
				
			||||||
            key={index}
 | 
					 | 
				
			||||||
            className='flex items-center cursor-pointer py-2 px-4 pr-10 rounded opacity-40 hover:bg-gray-300 dark:hover:bg-gray-900'
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            <div className='min-w-[50px] flex rounded-full border-2 border-transparent drop-shadow-md'>
 | 
					 | 
				
			||||||
              <Image
 | 
					 | 
				
			||||||
                src='/images/data/divlo.png'
 | 
					 | 
				
			||||||
                alt={"Users's profil picture"}
 | 
					 | 
				
			||||||
                height={50}
 | 
					 | 
				
			||||||
                width={50}
 | 
					 | 
				
			||||||
                draggable='false'
 | 
					 | 
				
			||||||
                className='rounded-full'
 | 
					 | 
				
			||||||
              />
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
            <div className='max-w-[145px] ml-4'>
 | 
					 | 
				
			||||||
              <p className='overflow-hidden whitespace-nowrap overflow-ellipsis'>
 | 
					 | 
				
			||||||
                Walidouxssssssssssssssssssssssssssssss
 | 
					 | 
				
			||||||
              </p>
 | 
					 | 
				
			||||||
              <span className='text-red-800 dark:text-red-400'>Offline</span>
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
      })}
 | 
					 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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,82 +1,45 @@
 | 
				
			|||||||
import Image from 'next/image'
 | 
					import InfiniteScroll from 'react-infinite-scroll-component'
 | 
				
			||||||
import TextareaAutosize from 'react-textarea-autosize'
 | 
					
 | 
				
			||||||
 | 
					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 = () => {
 | 
					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 (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <div
 | 
				
			||||||
      <div className='w-full scrollbar-firefox-support overflow-y-auto transition-all'>
 | 
					      id='messages'
 | 
				
			||||||
        {new Array(20).fill(null).map((_, index) => {
 | 
					      className='w-full scrollbar-firefox-support overflow-y-auto transition-all flex-1 flex flex-col-reverse'
 | 
				
			||||||
          return (
 | 
					    >
 | 
				
			||||||
            <div
 | 
					      <InfiniteScroll
 | 
				
			||||||
              key={index}
 | 
					        scrollableTarget='messages'
 | 
				
			||||||
              className='p-4 flex transition hover:bg-gray-200 dark:hover:bg-gray-900'
 | 
					        className='messages-list'
 | 
				
			||||||
            >
 | 
					        dataLength={messages.length}
 | 
				
			||||||
              <div className='w-12 h-12 mr-4 flex flex-shrink-0 items-center justify-center'>
 | 
					        next={nextPage}
 | 
				
			||||||
                <div className='w-10 h-10 drop-shadow-md'>
 | 
					        inverse
 | 
				
			||||||
                  <Image
 | 
					        hasMore={hasMore}
 | 
				
			||||||
                    className='rounded-full'
 | 
					        loader={<Loader />}
 | 
				
			||||||
                    src='/images/data/user-default.png'
 | 
					      >
 | 
				
			||||||
                    alt='logo'
 | 
					        {messages.map((message) => {
 | 
				
			||||||
                    width={50}
 | 
					          return <Message key={message.id} message={message} />
 | 
				
			||||||
                    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>
 | 
					      </InfiniteScroll>
 | 
				
			||||||
      <div className='p-6 pb-4'>
 | 
					    </div>
 | 
				
			||||||
        <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>
 | 
					 | 
				
			||||||
    </>
 | 
					 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import useTranslation from 'next-translate/useTranslation'
 | 
				
			||||||
import { PlusSmIcon, ArrowDownIcon } from '@heroicons/react/solid'
 | 
					import { PlusSmIcon, ArrowDownIcon } from '@heroicons/react/solid'
 | 
				
			||||||
import classNames from 'classnames'
 | 
					import classNames from 'classnames'
 | 
				
			||||||
import Image from 'next/image'
 | 
					import Image from 'next/image'
 | 
				
			||||||
@@ -10,6 +11,8 @@ export interface PopupGuildProps {
 | 
				
			|||||||
export const PopupGuild: React.FC<PopupGuildProps> = (props) => {
 | 
					export const PopupGuild: React.FC<PopupGuildProps> = (props) => {
 | 
				
			||||||
  const { className } = props
 | 
					  const { className } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { t } = useTranslation()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div
 | 
					    <div
 | 
				
			||||||
      className={classNames(
 | 
					      className={classNames(
 | 
				
			||||||
@@ -21,16 +24,16 @@ export const PopupGuild: React.FC<PopupGuildProps> = (props) => {
 | 
				
			|||||||
        image={
 | 
					        image={
 | 
				
			||||||
          <Image
 | 
					          <Image
 | 
				
			||||||
            src='/images/svg/design/create-guild.svg'
 | 
					            src='/images/svg/design/create-guild.svg'
 | 
				
			||||||
            alt='Create a guild'
 | 
					            alt={t('application:create-a-guild')}
 | 
				
			||||||
            draggable='false'
 | 
					            draggable='false'
 | 
				
			||||||
            width={230}
 | 
					            width={230}
 | 
				
			||||||
            height={230}
 | 
					            height={230}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        description='Create your own guild and manage everything within a few clicks !'
 | 
					        description={t('application:create-a-guild-description')}
 | 
				
			||||||
        link={{
 | 
					        link={{
 | 
				
			||||||
          icon: <PlusSmIcon className='w-8 h-8 mr-2' />,
 | 
					          icon: <PlusSmIcon className='w-8 h-8 mr-2' />,
 | 
				
			||||||
          text: 'Create a Guild',
 | 
					          text: t('application:create-a-guild'),
 | 
				
			||||||
          href: '/application/guilds/create'
 | 
					          href: '/application/guilds/create'
 | 
				
			||||||
        }}
 | 
					        }}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
@@ -38,16 +41,16 @@ export const PopupGuild: React.FC<PopupGuildProps> = (props) => {
 | 
				
			|||||||
        image={
 | 
					        image={
 | 
				
			||||||
          <Image
 | 
					          <Image
 | 
				
			||||||
            src='/images/svg/design/join-guild.svg'
 | 
					            src='/images/svg/design/join-guild.svg'
 | 
				
			||||||
            alt='Join a Guild'
 | 
					            alt={t('application:join-a-guild')}
 | 
				
			||||||
            draggable='false'
 | 
					            draggable='false'
 | 
				
			||||||
            width={200}
 | 
					            width={200}
 | 
				
			||||||
            height={200}
 | 
					            height={200}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        description='Talk, meet and have fun with new friends by joining any interesting guild !'
 | 
					        description={t('application:join-a-guild-description')}
 | 
				
			||||||
        link={{
 | 
					        link={{
 | 
				
			||||||
          icon: <ArrowDownIcon className='w-6 h-6 mr-2' />,
 | 
					          icon: <ArrowDownIcon className='w-6 h-6 mr-2' />,
 | 
				
			||||||
          text: 'Join a Guild',
 | 
					          text: t('application:join-a-guild'),
 | 
				
			||||||
          href: '/application/guilds/join'
 | 
					          href: '/application/guilds/join'
 | 
				
			||||||
        }}
 | 
					        }}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,7 +19,9 @@ export const PopupGuildCard: React.FC<PopupGuildCardProps> = (props) => {
 | 
				
			|||||||
        {image}
 | 
					        {image}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div className='flex justify-between flex-col h-1/2 w-full bg-gray-700 rounded-b-2xl mt-2 shadow-sm'>
 | 
					      <div className='flex justify-between flex-col h-1/2 w-full bg-gray-700 rounded-b-2xl mt-2 shadow-sm'>
 | 
				
			||||||
        <p className='text-gray-200 mt-6 text-center px-8'>{description}</p>
 | 
					        <p className='text-gray-200 text-sm mt-6 text-center px-8'>
 | 
				
			||||||
 | 
					          {description}
 | 
				
			||||||
 | 
					        </p>
 | 
				
			||||||
        <Link href={link.href}>
 | 
					        <Link href={link.href}>
 | 
				
			||||||
          <a className='flex justify-center items-center w-4/5 h-10 rounded-2xl transition duration-200 ease-in-out text-white font-bold tracking-wide bg-green-400 self-center mb-6 hover:bg-green-600'>
 | 
					          <a className='flex justify-center items-center w-4/5 h-10 rounded-2xl transition duration-200 ease-in-out text-white font-bold tracking-wide bg-green-400 self-center mb-6 hover:bg-green-600'>
 | 
				
			||||||
            {link.icon}
 | 
					            {link.icon}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,15 @@
 | 
				
			|||||||
import { Meta, Story } from '@storybook/react'
 | 
					import { Meta, Story } from '@storybook/react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Members as Component } from './Members'
 | 
					import { SendMessage as Component } from './SendMessage'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Stories: Meta = {
 | 
					const Stories: Meta = {
 | 
				
			||||||
  title: 'Members',
 | 
					  title: 'SendMessage',
 | 
				
			||||||
  component: Component
 | 
					  component: Component
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Stories
 | 
					export default Stories
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Members: Story = (arguments_) => {
 | 
					export const SendMessage: Story = (arguments_) => {
 | 
				
			||||||
  return <Component {...arguments_} />
 | 
					  return <Component {...arguments_} />
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					SendMessage.args = {}
 | 
				
			||||||
@@ -1,10 +1,10 @@
 | 
				
			|||||||
import { render } from '@testing-library/react'
 | 
					import { render } from '@testing-library/react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Messages } from './'
 | 
					import { SendMessage } from './SendMessage'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('<Messages />', () => {
 | 
					describe('<SendMessage />', () => {
 | 
				
			||||||
  it('should render successfully', () => {
 | 
					  it('should render successfully', () => {
 | 
				
			||||||
    const { baseElement } = render(<Messages />)
 | 
					    const { baseElement } = render(<SendMessage />)
 | 
				
			||||||
    expect(baseElement).toBeTruthy()
 | 
					    expect(baseElement).toBeTruthy()
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
							
								
								
									
										38
									
								
								components/Application/SendMessage/SendMessage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								components/Application/SendMessage/SendMessage.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					import useTranslation from 'next-translate/useTranslation'
 | 
				
			||||||
 | 
					import TextareaAutosize from 'react-textarea-autosize'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const SendMessage: React.FC = () => {
 | 
				
			||||||
 | 
					  const { t } = useTranslation()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <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={t('application: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>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								components/Application/SendMessage/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								components/Application/SendMessage/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export * from './SendMessage'
 | 
				
			||||||
@@ -1,5 +1,8 @@
 | 
				
			|||||||
import { Meta, Story } from '@storybook/react'
 | 
					import { Meta, Story } from '@storybook/react'
 | 
				
			||||||
import { user, userSettings } from '../../../cypress/fixtures/users/user'
 | 
					import {
 | 
				
			||||||
 | 
					  userExample,
 | 
				
			||||||
 | 
					  userSettingsExample
 | 
				
			||||||
 | 
					} from '../../../cypress/fixtures/users/user'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { UserProfile as Component, UserProfileProps } from './UserProfile'
 | 
					import { UserProfile as Component, UserProfileProps } from './UserProfile'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -15,7 +18,7 @@ export const UserProfile: Story<UserProfileProps> = (arguments_) => {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
UserProfile.args = {
 | 
					UserProfile.args = {
 | 
				
			||||||
  user: {
 | 
					  user: {
 | 
				
			||||||
    ...user,
 | 
					    ...userExample,
 | 
				
			||||||
    settings: userSettings
 | 
					    settings: userSettingsExample
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,11 +11,11 @@ import { Button } from '../design/Button'
 | 
				
			|||||||
import { FormState } from '../design/FormState'
 | 
					import { FormState } from '../design/FormState'
 | 
				
			||||||
import { AuthenticationForm } from './'
 | 
					import { AuthenticationForm } from './'
 | 
				
			||||||
import { userSchema } from '../../models/User'
 | 
					import { userSchema } from '../../models/User'
 | 
				
			||||||
import { api } from 'utils/api'
 | 
					import { api } from 'tools/api'
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  Tokens,
 | 
					  Tokens,
 | 
				
			||||||
  Authentication as AuthenticationClass
 | 
					  Authentication as AuthenticationClass
 | 
				
			||||||
} from '../../utils/authentication'
 | 
					} from '../../tools/authentication'
 | 
				
			||||||
import { useForm, HandleSubmitCallback } from '../../hooks/useForm'
 | 
					import { useForm, HandleSubmitCallback } from '../../hooks/useForm'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface AuthenticationProps {
 | 
					export interface AuthenticationProps {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										15
									
								
								components/Emoji/Emoji.stories.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								components/Emoji/Emoji.stories.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					import { Meta, Story } from '@storybook/react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Emoji as Component, EmojiProps } from './Emoji'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Stories: Meta = {
 | 
				
			||||||
 | 
					  title: 'Emoji',
 | 
				
			||||||
 | 
					  component: Component
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Stories
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Emoji: Story<EmojiProps> = (arguments_) => {
 | 
				
			||||||
 | 
					  return <Component {...arguments_} />
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					Emoji.args = { value: ':wave:', size: 20 }
 | 
				
			||||||
							
								
								
									
										24
									
								
								components/Emoji/Emoji.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								components/Emoji/Emoji.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					import { Emoji as EmojiMart } from 'emoji-mart'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { EMOJI_SET } from './emojiPlugin'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface EmojiProps {
 | 
				
			||||||
 | 
					  value: string
 | 
				
			||||||
 | 
					  size: number
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const Emoji: React.FC<EmojiProps> = (props) => {
 | 
				
			||||||
 | 
					  const { value, size } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <EmojiMart
 | 
				
			||||||
 | 
					      set={EMOJI_SET}
 | 
				
			||||||
 | 
					      emoji={value}
 | 
				
			||||||
 | 
					      size={size}
 | 
				
			||||||
 | 
					      tooltip
 | 
				
			||||||
 | 
					      fallback={() => {
 | 
				
			||||||
 | 
					        return <>{value}</>
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								components/Emoji/EmojiPicker/EmojiPicker.stories.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								components/Emoji/EmojiPicker/EmojiPicker.stories.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					import { Meta, Story } from '@storybook/react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { EmojiPicker as Component, EmojiPickerProps } from './EmojiPicker'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Stories: Meta = {
 | 
				
			||||||
 | 
					  title: 'EmojiPicker',
 | 
				
			||||||
 | 
					  component: Component
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Stories
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const EmojiPicker: Story<EmojiPickerProps> = (arguments_) => {
 | 
				
			||||||
 | 
					  return <Component {...arguments_} />
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					EmojiPicker.args = { onClick: (emoji, event) => console.log(emoji, event) }
 | 
				
			||||||
							
								
								
									
										28
									
								
								components/Emoji/EmojiPicker/EmojiPicker.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								components/Emoji/EmojiPicker/EmojiPicker.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					import 'emoji-mart/css/emoji-mart.css'
 | 
				
			||||||
 | 
					import { EmojiData, Picker } from 'emoji-mart'
 | 
				
			||||||
 | 
					import { useTheme } from 'next-themes'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { EMOJI_SET } from '../emojiPlugin'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type EmojiPickerOnClick = (
 | 
				
			||||||
 | 
					  emoji: EmojiData,
 | 
				
			||||||
 | 
					  event: React.MouseEvent<HTMLElement, MouseEvent>
 | 
				
			||||||
 | 
					) => void
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface EmojiPickerProps {
 | 
				
			||||||
 | 
					  onClick?: EmojiPickerOnClick
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const EmojiPicker: React.FC<EmojiPickerProps> = (props) => {
 | 
				
			||||||
 | 
					  const { theme } = useTheme()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Picker
 | 
				
			||||||
 | 
					      set={EMOJI_SET}
 | 
				
			||||||
 | 
					      theme={theme as 'light' | 'dark' | 'auto'}
 | 
				
			||||||
 | 
					      onClick={props.onClick}
 | 
				
			||||||
 | 
					      showPreview={false}
 | 
				
			||||||
 | 
					      showSkinTones={false}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								components/Emoji/EmojiPicker/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								components/Emoji/EmojiPicker/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export * from './EmojiPicker'
 | 
				
			||||||
							
								
								
									
										71
									
								
								components/Emoji/emojiPlugin.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								components/Emoji/emojiPlugin.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					import { visit } from 'unist-util-visit'
 | 
				
			||||||
 | 
					import { Plugin, Transformer } from 'unified'
 | 
				
			||||||
 | 
					import { Literal, Parent } from 'unist'
 | 
				
			||||||
 | 
					import type { ElementContent } from 'hast'
 | 
				
			||||||
 | 
					import type { EmojiSet } from 'emoji-mart'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { emojiRegex } from './isStringWithOnlyOneEmoji'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const EMOJI_SET: EmojiSet = 'twitter'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const extractText = (
 | 
				
			||||||
 | 
					  string: string,
 | 
				
			||||||
 | 
					  start: number,
 | 
				
			||||||
 | 
					  end: number
 | 
				
			||||||
 | 
					): ElementContent => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    type: 'text',
 | 
				
			||||||
 | 
					    value: string.slice(start, end)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const emojiPlugin: Plugin<[], Literal<string>> = () => {
 | 
				
			||||||
 | 
					  const transformer: Transformer<Literal<string>> = (tree) => {
 | 
				
			||||||
 | 
					    visit<Literal<string>, string>(
 | 
				
			||||||
 | 
					      tree,
 | 
				
			||||||
 | 
					      'text',
 | 
				
			||||||
 | 
					      (node, position, parent: Parent<ElementContent> | null) => {
 | 
				
			||||||
 | 
					        if (typeof node.value !== 'string') {
 | 
				
			||||||
 | 
					          return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        position = position ?? 0
 | 
				
			||||||
 | 
					        const definition: ElementContent[] = []
 | 
				
			||||||
 | 
					        let lastIndex = 0
 | 
				
			||||||
 | 
					        const match = emojiRegex.exec(node.value)
 | 
				
			||||||
 | 
					        if (match != null) {
 | 
				
			||||||
 | 
					          const value = match[0]
 | 
				
			||||||
 | 
					          if (match.index !== lastIndex) {
 | 
				
			||||||
 | 
					            definition.push(extractText(node.value, lastIndex, match.index))
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          definition.push({
 | 
				
			||||||
 | 
					            type: 'element',
 | 
				
			||||||
 | 
					            tagName: 'emoji',
 | 
				
			||||||
 | 
					            properties: { value },
 | 
				
			||||||
 | 
					            children: []
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					          lastIndex = match.index + value.length
 | 
				
			||||||
 | 
					          if (lastIndex !== node.value.length) {
 | 
				
			||||||
 | 
					            definition.push(
 | 
				
			||||||
 | 
					              extractText(node.value, lastIndex, node.value.length)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          if (parent != null) {
 | 
				
			||||||
 | 
					            const last = parent.children.slice(position + 1)
 | 
				
			||||||
 | 
					            parent.children = parent.children.slice(0, position)
 | 
				
			||||||
 | 
					            parent.children = parent.children.concat(definition)
 | 
				
			||||||
 | 
					            parent.children = parent.children.concat(last)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return transformer
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare global {
 | 
				
			||||||
 | 
					  namespace JSX {
 | 
				
			||||||
 | 
					    interface IntrinsicElements {
 | 
				
			||||||
 | 
					      emoji: { value: string }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										4
									
								
								components/Emoji/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								components/Emoji/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					export * from './Emoji'
 | 
				
			||||||
 | 
					export * from './EmojiPicker'
 | 
				
			||||||
 | 
					export * from './emojiPlugin'
 | 
				
			||||||
 | 
					export * from './isStringWithOnlyOneEmoji'
 | 
				
			||||||
							
								
								
									
										18
									
								
								components/Emoji/isStringWithOnlyOneEmoji.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								components/Emoji/isStringWithOnlyOneEmoji.test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					import { isStringWithOnlyOneEmoji } from './isStringWithOnlyOneEmoji'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('components/Emoji/isStringWithOnlyOneEmoji', () => {
 | 
				
			||||||
 | 
					  it('returns true with a string with only one emoji', () => {
 | 
				
			||||||
 | 
					    expect(isStringWithOnlyOneEmoji(':wave:')).toBeTruthy()
 | 
				
			||||||
 | 
					    expect(isStringWithOnlyOneEmoji(':smile:')).toBeTruthy()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('returns false with a string with multiple emoji or with text', () => {
 | 
				
			||||||
 | 
					    expect(isStringWithOnlyOneEmoji(':wave: :smile:')).toBeFalsy()
 | 
				
			||||||
 | 
					    expect(isStringWithOnlyOneEmoji(':wave: some text')).toBeFalsy()
 | 
				
			||||||
 | 
					    expect(isStringWithOnlyOneEmoji('some text :wave:')).toBeFalsy()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('returns false with a string without emoji', () => {
 | 
				
			||||||
 | 
					    expect(isStringWithOnlyOneEmoji('some text')).toBeFalsy()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
							
								
								
									
										6
									
								
								components/Emoji/isStringWithOnlyOneEmoji.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								components/Emoji/isStringWithOnlyOneEmoji.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					export const emojiRegex = /:\+1:|:-1:|:[\w-]+:/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const isStringWithOnlyOneEmoji = (value: string): boolean => {
 | 
				
			||||||
 | 
					  const result = emojiRegex.exec(value)
 | 
				
			||||||
 | 
					  return result != null && result.input === result[0]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import Link from 'next/link'
 | 
					import Link from 'next/link'
 | 
				
			||||||
import useTranslation from 'next-translate/useTranslation'
 | 
					import useTranslation from 'next-translate/useTranslation'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { API_VERSION } from '../../utils/api'
 | 
					import { API_VERSION } from '../../tools/api'
 | 
				
			||||||
import { VersionLink } from './VersionLink'
 | 
					import { VersionLink } from './VersionLink'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface FooterProps {
 | 
					export interface FooterProps {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,15 +5,18 @@ export interface IconLinkProps {
 | 
				
			|||||||
  selected?: boolean
 | 
					  selected?: boolean
 | 
				
			||||||
  href: string
 | 
					  href: string
 | 
				
			||||||
  title?: string
 | 
					  title?: string
 | 
				
			||||||
 | 
					  className?: string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const IconLink: React.FC<IconLinkProps> = (props) => {
 | 
					export const IconLink: React.FC<IconLinkProps> = (props) => {
 | 
				
			||||||
  const { children, selected, href, title } = props
 | 
					  const { children, selected, href, title, className } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className='w-full flex justify-center group'>
 | 
					    <Link href={href}>
 | 
				
			||||||
      <Link href={href}>
 | 
					      <a className='w-full flex justify-center relative group' title={title}>
 | 
				
			||||||
        <a className='w-full flex justify-center relative group' title={title}>
 | 
					        <div
 | 
				
			||||||
 | 
					          className={classNames('w-full flex justify-center group', className)}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
          {children}
 | 
					          {children}
 | 
				
			||||||
          <div className='absolute flex items-center w-3 h-12 left-0'>
 | 
					          <div className='absolute flex items-center w-3 h-12 left-0'>
 | 
				
			||||||
            <span
 | 
					            <span
 | 
				
			||||||
@@ -25,8 +28,8 @@ export const IconLink: React.FC<IconLinkProps> = (props) => {
 | 
				
			|||||||
              )}
 | 
					              )}
 | 
				
			||||||
            ></span>
 | 
					            ></span>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </a>
 | 
					        </div>
 | 
				
			||||||
      </Link>
 | 
					      </a>
 | 
				
			||||||
    </div>
 | 
					    </Link>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,54 @@
 | 
				
			|||||||
export interface ChannelType {
 | 
					import { createContext, useContext, useEffect } from 'react'
 | 
				
			||||||
  id: number
 | 
					
 | 
				
			||||||
  name: string
 | 
					import { NextPage, usePagination } from 'hooks/usePagination'
 | 
				
			||||||
  description: string
 | 
					import { useAuthentication } from 'tools/authentication'
 | 
				
			||||||
  createdAt: string
 | 
					import { Channel } from 'models/Channel'
 | 
				
			||||||
  updatedAt: string
 | 
					import { GuildsChannelsPath } from 'components/Application'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface Channels {
 | 
				
			||||||
 | 
					  channels: Channel[]
 | 
				
			||||||
 | 
					  hasMore: boolean
 | 
				
			||||||
 | 
					  nextPage: NextPage
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const channelExample: ChannelType = {
 | 
					const defaultChannelsContext = {} as any
 | 
				
			||||||
  id: 4,
 | 
					const ChannelsContext = createContext<Channels>(defaultChannelsContext)
 | 
				
			||||||
  name: 'Channel 4',
 | 
					
 | 
				
			||||||
  description: '',
 | 
					export interface ChannelsProviderProps {
 | 
				
			||||||
  createdAt: '',
 | 
					  path: GuildsChannelsPath
 | 
				
			||||||
  updatedAt: ''
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ChannelsProvider: React.FC<ChannelsProviderProps> = (props) => {
 | 
				
			||||||
 | 
					  const { path, children } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { authentication } = useAuthentication()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    items: channels,
 | 
				
			||||||
 | 
					    hasMore,
 | 
				
			||||||
 | 
					    nextPage,
 | 
				
			||||||
 | 
					    resetPagination
 | 
				
			||||||
 | 
					  } = usePagination<Channel>({
 | 
				
			||||||
 | 
					    api: authentication.api,
 | 
				
			||||||
 | 
					    url: `/guilds/${path.guildId}/channels`
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    resetPagination()
 | 
				
			||||||
 | 
					    nextPage()
 | 
				
			||||||
 | 
					  }, [nextPage, resetPagination])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <ChannelsContext.Provider value={{ channels, hasMore, nextPage }}>
 | 
				
			||||||
 | 
					      {children}
 | 
				
			||||||
 | 
					    </ChannelsContext.Provider>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useChannels = (): Channels => {
 | 
				
			||||||
 | 
					  const channels = useContext(ChannelsContext)
 | 
				
			||||||
 | 
					  if (channels === defaultChannelsContext) {
 | 
				
			||||||
 | 
					    throw new Error('useChannels must be used within ChannelsProvider')
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return channels
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										51
									
								
								contexts/GuildMember.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								contexts/GuildMember.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					import { createContext, useContext, useEffect, useState } from 'react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Guild } from 'models/Guild'
 | 
				
			||||||
 | 
					import { Member } from 'models/Member'
 | 
				
			||||||
 | 
					import { GuildsChannelsPath } from 'components/Application'
 | 
				
			||||||
 | 
					import { useAuthentication } from 'tools/authentication'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface GuildMember {
 | 
				
			||||||
 | 
					  guild: Guild
 | 
				
			||||||
 | 
					  member: Member
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface GuildMemberProps {
 | 
				
			||||||
 | 
					  guildMember: GuildMember
 | 
				
			||||||
 | 
					  path: GuildsChannelsPath
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const defaultGuildMemberContext = {} as any
 | 
				
			||||||
 | 
					const GuildMemberContext = createContext<GuildMember>(defaultGuildMemberContext)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const GuildMemberProvider: React.FC<GuildMemberProps> = (props) => {
 | 
				
			||||||
 | 
					  const [guildMember, setGuildMember] = useState(props.guildMember)
 | 
				
			||||||
 | 
					  const { authentication } = useAuthentication()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const fetchGuildMember = async (): Promise<void> => {
 | 
				
			||||||
 | 
					      const { data } = await authentication.api.get(
 | 
				
			||||||
 | 
					        `/guilds/${props.path.guildId}`
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      setGuildMember(data)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fetchGuildMember().catch((error) => {
 | 
				
			||||||
 | 
					      console.error(error)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }, [props.path, authentication.api])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <GuildMemberContext.Provider value={guildMember}>
 | 
				
			||||||
 | 
					      {props.children}
 | 
				
			||||||
 | 
					    </GuildMemberContext.Provider>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useGuildMember = (): GuildMember => {
 | 
				
			||||||
 | 
					  const guildMember = useContext(GuildMemberContext)
 | 
				
			||||||
 | 
					  if (guildMember === defaultGuildMemberContext) {
 | 
				
			||||||
 | 
					    throw new Error('useGuildMember must be used within GuildMemberProvider')
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return guildMember
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										49
									
								
								contexts/Guilds.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								contexts/Guilds.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					import { createContext, useContext, useEffect } from 'react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { NextPage, usePagination } from 'hooks/usePagination'
 | 
				
			||||||
 | 
					import { useAuthentication } from 'tools/authentication'
 | 
				
			||||||
 | 
					import { GuildWithDefaultChannelId } from 'models/Guild'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface Guilds {
 | 
				
			||||||
 | 
					  guilds: GuildWithDefaultChannelId[]
 | 
				
			||||||
 | 
					  hasMore: boolean
 | 
				
			||||||
 | 
					  nextPage: NextPage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const defaultGuildsContext = {} as any
 | 
				
			||||||
 | 
					const GuildsContext = createContext<Guilds>(defaultGuildsContext)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const GuildsProvider: React.FC = (props) => {
 | 
				
			||||||
 | 
					  const { children } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { authentication } = useAuthentication()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    items: guilds,
 | 
				
			||||||
 | 
					    hasMore,
 | 
				
			||||||
 | 
					    nextPage,
 | 
				
			||||||
 | 
					    resetPagination
 | 
				
			||||||
 | 
					  } = usePagination<GuildWithDefaultChannelId>({
 | 
				
			||||||
 | 
					    api: authentication.api,
 | 
				
			||||||
 | 
					    url: '/guilds'
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    resetPagination()
 | 
				
			||||||
 | 
					    nextPage()
 | 
				
			||||||
 | 
					  }, [nextPage, resetPagination])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <GuildsContext.Provider value={{ guilds, hasMore, nextPage }}>
 | 
				
			||||||
 | 
					      {children}
 | 
				
			||||||
 | 
					    </GuildsContext.Provider>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useGuilds = (): Guilds => {
 | 
				
			||||||
 | 
					  const guilds = useContext(GuildsContext)
 | 
				
			||||||
 | 
					  if (guilds === defaultGuildsContext) {
 | 
				
			||||||
 | 
					    throw new Error('useGuilds must be used within GuildsProvider')
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return guilds
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										54
									
								
								contexts/Members.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								contexts/Members.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					import { createContext, useContext, useEffect } from 'react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { NextPage, usePagination } from 'hooks/usePagination'
 | 
				
			||||||
 | 
					import { useAuthentication } from 'tools/authentication'
 | 
				
			||||||
 | 
					import { MemberWithPublicUser } from 'models/Member'
 | 
				
			||||||
 | 
					import { GuildsChannelsPath } from 'components/Application'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface Members {
 | 
				
			||||||
 | 
					  members: MemberWithPublicUser[]
 | 
				
			||||||
 | 
					  hasMore: boolean
 | 
				
			||||||
 | 
					  nextPage: NextPage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const defaultMembersContext = {} as any
 | 
				
			||||||
 | 
					const MembersContext = createContext<Members>(defaultMembersContext)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface MembersProviderProps {
 | 
				
			||||||
 | 
					  path: GuildsChannelsPath
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const MembersProviders: React.FC<MembersProviderProps> = (props) => {
 | 
				
			||||||
 | 
					  const { children, path } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { authentication } = useAuthentication()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    items: members,
 | 
				
			||||||
 | 
					    hasMore,
 | 
				
			||||||
 | 
					    nextPage,
 | 
				
			||||||
 | 
					    resetPagination
 | 
				
			||||||
 | 
					  } = usePagination<MemberWithPublicUser>({
 | 
				
			||||||
 | 
					    api: authentication.api,
 | 
				
			||||||
 | 
					    url: `/guilds/${path.guildId}/members`
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    resetPagination()
 | 
				
			||||||
 | 
					    nextPage()
 | 
				
			||||||
 | 
					  }, [nextPage, resetPagination])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <MembersContext.Provider value={{ members, hasMore, nextPage }}>
 | 
				
			||||||
 | 
					      {children}
 | 
				
			||||||
 | 
					    </MembersContext.Provider>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useMembers = (): Members => {
 | 
				
			||||||
 | 
					  const members = useContext(MembersContext)
 | 
				
			||||||
 | 
					  if (members === defaultMembersContext) {
 | 
				
			||||||
 | 
					    throw new Error('useMembers must be used within MembersProvider')
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return members
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										60
									
								
								contexts/Messages.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								contexts/Messages.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					import { createContext, useContext, useEffect } from 'react'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { NextPage, usePagination } from 'hooks/usePagination'
 | 
				
			||||||
 | 
					import { useAuthentication } from 'tools/authentication'
 | 
				
			||||||
 | 
					import { MessageWithMember } from 'models/Message'
 | 
				
			||||||
 | 
					import { GuildsChannelsPath } from 'components/Application'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface Messages {
 | 
				
			||||||
 | 
					  messages: MessageWithMember[]
 | 
				
			||||||
 | 
					  hasMore: boolean
 | 
				
			||||||
 | 
					  nextPage: NextPage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const defaultMessagesContext = {} as any
 | 
				
			||||||
 | 
					const MessagesContext = createContext<Messages>(defaultMessagesContext)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface MessagesProviderProps {
 | 
				
			||||||
 | 
					  path: GuildsChannelsPath
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const MessagesProvider: React.FC<MessagesProviderProps> = (props) => {
 | 
				
			||||||
 | 
					  const { path, children } = props
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { authentication } = useAuthentication()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    items: messages,
 | 
				
			||||||
 | 
					    hasMore,
 | 
				
			||||||
 | 
					    nextPage,
 | 
				
			||||||
 | 
					    resetPagination
 | 
				
			||||||
 | 
					  } = usePagination<MessageWithMember>({
 | 
				
			||||||
 | 
					    api: authentication.api,
 | 
				
			||||||
 | 
					    url: `/channels/${path.channelId}/messages`,
 | 
				
			||||||
 | 
					    inverse: true
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    resetPagination()
 | 
				
			||||||
 | 
					    nextPage(undefined, () => {
 | 
				
			||||||
 | 
					      const messagesDiv = window.document.getElementById(
 | 
				
			||||||
 | 
					        'messages'
 | 
				
			||||||
 | 
					      ) as HTMLDivElement
 | 
				
			||||||
 | 
					      messagesDiv.scrollTo(0, messagesDiv.scrollHeight)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }, [nextPage, resetPagination])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <MessagesContext.Provider value={{ messages, hasMore, nextPage }}>
 | 
				
			||||||
 | 
					      {children}
 | 
				
			||||||
 | 
					    </MessagesContext.Provider>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useMessages = (): Messages => {
 | 
				
			||||||
 | 
					  const messages = useContext(MessagesContext)
 | 
				
			||||||
 | 
					  if (messages === defaultMessagesContext) {
 | 
				
			||||||
 | 
					    throw new Error('useMessages must be used within a MessagesProvider')
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return messages
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										14
									
								
								cypress/fixtures/channels/[channelId]/get.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								cypress/fixtures/channels/[channelId]/get.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					import { Handler } from '../../handler'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { channelExample } from '../channel'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getChannelWithChannelIdHandler: Handler = {
 | 
				
			||||||
 | 
					  method: 'GET',
 | 
				
			||||||
 | 
					  url: `/channels/${channelExample.id}`,
 | 
				
			||||||
 | 
					  response: {
 | 
				
			||||||
 | 
					    statusCode: 200,
 | 
				
			||||||
 | 
					    body: {
 | 
				
			||||||
 | 
					      channel: channelExample
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								cypress/fixtures/channels/[channelId]/messages/get.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								cypress/fixtures/channels/[channelId]/messages/get.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					import { Handler } from '../../../handler'
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  messageExampleComplete,
 | 
				
			||||||
 | 
					  messageExampleComplete2
 | 
				
			||||||
 | 
					} from '../../../messages/message'
 | 
				
			||||||
 | 
					import { channelExample } from '../../channel'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getMessagesWithChannelIdHandler: Handler = {
 | 
				
			||||||
 | 
					  method: 'GET',
 | 
				
			||||||
 | 
					  url: `/channels/${channelExample.id}/messages`,
 | 
				
			||||||
 | 
					  response: {
 | 
				
			||||||
 | 
					    statusCode: 200,
 | 
				
			||||||
 | 
					    body: [messageExampleComplete, messageExampleComplete2]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,9 +1,15 @@
 | 
				
			|||||||
import { guild } from '../guilds/guild'
 | 
					import { guildExample } from '../guilds/guild'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const channel = {
 | 
					export const channelExample = {
 | 
				
			||||||
  id: 1,
 | 
					  id: 1,
 | 
				
			||||||
  name: 'general',
 | 
					  name: 'general',
 | 
				
			||||||
  guildId: guild.id,
 | 
					  guildId: guildExample.id,
 | 
				
			||||||
  createdAt: new Date().toISOString(),
 | 
					  createdAt: new Date().toISOString(),
 | 
				
			||||||
  updatedAt: new Date().toISOString()
 | 
					  updatedAt: new Date().toISOString()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const channelExample2 = {
 | 
				
			||||||
 | 
					  ...channelExample,
 | 
				
			||||||
 | 
					  id: 2,
 | 
				
			||||||
 | 
					  name: 'general2'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										12
									
								
								cypress/fixtures/guilds/[guildId]/channels/get.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								cypress/fixtures/guilds/[guildId]/channels/get.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					import { guildExample } from '../../guild'
 | 
				
			||||||
 | 
					import { Handler } from '../../../handler'
 | 
				
			||||||
 | 
					import { channelExample, channelExample2 } from '../../../channels/channel'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getChannelsWithGuildIdHandler: Handler = {
 | 
				
			||||||
 | 
					  method: 'GET',
 | 
				
			||||||
 | 
					  url: `/guilds/${guildExample.id}/channels`,
 | 
				
			||||||
 | 
					  response: {
 | 
				
			||||||
 | 
					    statusCode: 200,
 | 
				
			||||||
 | 
					    body: [channelExample, channelExample2]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										16
									
								
								cypress/fixtures/guilds/[guildId]/get.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								cypress/fixtures/guilds/[guildId]/get.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					import { Handler } from '../../handler'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { guildExample } from '../guild'
 | 
				
			||||||
 | 
					import { memberExampleComplete } from '../../members/member'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getGuildMemberWithGuildIdHandler: Handler = {
 | 
				
			||||||
 | 
					  method: 'GET',
 | 
				
			||||||
 | 
					  url: `/guilds/${guildExample.id}`,
 | 
				
			||||||
 | 
					  response: {
 | 
				
			||||||
 | 
					    statusCode: 200,
 | 
				
			||||||
 | 
					    body: {
 | 
				
			||||||
 | 
					      guild: guildExample,
 | 
				
			||||||
 | 
					      member: memberExampleComplete
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										12
									
								
								cypress/fixtures/guilds/[guildId]/members/get.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								cypress/fixtures/guilds/[guildId]/members/get.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					import { guildExample } from '../../guild'
 | 
				
			||||||
 | 
					import { Handler } from '../../../handler'
 | 
				
			||||||
 | 
					import { memberExampleComplete } from '../../../members/member'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getMembersWithGuildIdHandler: Handler = {
 | 
				
			||||||
 | 
					  method: 'GET',
 | 
				
			||||||
 | 
					  url: `/guilds/${guildExample.id}/members`,
 | 
				
			||||||
 | 
					  response: {
 | 
				
			||||||
 | 
					    statusCode: 200,
 | 
				
			||||||
 | 
					    body: [memberExampleComplete]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										15
									
								
								cypress/fixtures/guilds/get.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								cypress/fixtures/guilds/get.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					import { Handler } from '../handler'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { guildExample, guildExample2 } from './guild'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getGuildsHandler: Handler = {
 | 
				
			||||||
 | 
					  method: 'GET',
 | 
				
			||||||
 | 
					  url: '/guilds',
 | 
				
			||||||
 | 
					  response: {
 | 
				
			||||||
 | 
					    statusCode: 200,
 | 
				
			||||||
 | 
					    body: [
 | 
				
			||||||
 | 
					      { ...guildExample, defaultChannelId: 1 },
 | 
				
			||||||
 | 
					      { ...guildExample2, defaultChannelId: 2 }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,4 +1,6 @@
 | 
				
			|||||||
export const guild = {
 | 
					import { Guild } from '../../../models/Guild'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const guildExample: Guild = {
 | 
				
			||||||
  id: 1,
 | 
					  id: 1,
 | 
				
			||||||
  name: 'GuildExample',
 | 
					  name: 'GuildExample',
 | 
				
			||||||
  description: 'guild example.',
 | 
					  description: 'guild example.',
 | 
				
			||||||
@@ -7,7 +9,8 @@ export const guild = {
 | 
				
			|||||||
  updatedAt: new Date().toISOString()
 | 
					  updatedAt: new Date().toISOString()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const guild2 = {
 | 
					export const guildExample2: Guild = {
 | 
				
			||||||
  ...guild,
 | 
					  ...guildExample,
 | 
				
			||||||
 | 
					  id: 2,
 | 
				
			||||||
  name: 'app'
 | 
					  name: 'app'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,8 @@
 | 
				
			|||||||
import { Handler } from '../handler'
 | 
					import { Handler } from '../handler'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { guild } from './guild'
 | 
					import { guildExample } from './guild'
 | 
				
			||||||
import { channel } from '../channels/channel'
 | 
					import { channelExample } from '../channels/channel'
 | 
				
			||||||
import { memberComplete } from '../members/member'
 | 
					import { memberExampleComplete } from '../members/member'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const postGuildsHandler: Handler = {
 | 
					export const postGuildsHandler: Handler = {
 | 
				
			||||||
  method: 'POST',
 | 
					  method: 'POST',
 | 
				
			||||||
@@ -11,9 +11,9 @@ export const postGuildsHandler: Handler = {
 | 
				
			|||||||
    statusCode: 201,
 | 
					    statusCode: 201,
 | 
				
			||||||
    body: {
 | 
					    body: {
 | 
				
			||||||
      guild: {
 | 
					      guild: {
 | 
				
			||||||
        ...guild,
 | 
					        ...guildExample,
 | 
				
			||||||
        channels: [channel],
 | 
					        channels: [channelExample],
 | 
				
			||||||
        members: [memberComplete]
 | 
					        members: [memberExampleComplete]
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { Handler } from '../../handler'
 | 
					import { Handler } from '../../handler'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { guild, guild2 } from '../guild'
 | 
					import { guildExample, guildExample2 } from '../guild'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getGuildsPublicEmptyHandler: Handler = {
 | 
					export const getGuildsPublicEmptyHandler: Handler = {
 | 
				
			||||||
  method: 'GET',
 | 
					  method: 'GET',
 | 
				
			||||||
@@ -17,8 +17,8 @@ export const getGuildsPublicHandler: Handler = {
 | 
				
			|||||||
  response: {
 | 
					  response: {
 | 
				
			||||||
    statusCode: 200,
 | 
					    statusCode: 200,
 | 
				
			||||||
    body: [
 | 
					    body: [
 | 
				
			||||||
      { ...guild, membersCount: 1 },
 | 
					      { ...guildExample, membersCount: 1 },
 | 
				
			||||||
      { ...guild2, membersCount: 1 }
 | 
					      { ...guildExample2, membersCount: 1 }
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -28,6 +28,6 @@ export const getGuildsPublicSearchHandler: Handler = {
 | 
				
			|||||||
  url: '/guilds/public',
 | 
					  url: '/guilds/public',
 | 
				
			||||||
  response: {
 | 
					  response: {
 | 
				
			||||||
    statusCode: 200,
 | 
					    statusCode: 200,
 | 
				
			||||||
    body: [{ ...guild2, membersCount: 1 }]
 | 
					    body: [{ ...guildExample2, membersCount: 1 }]
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@ import { postUsersRefreshTokenHandler } from './users/refresh-token/post'
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export interface Handler {
 | 
					export interface Handler {
 | 
				
			||||||
  method: 'GET' | 'POST' | 'PUT' | 'DELETE'
 | 
					  method: 'GET' | 'POST' | 'PUT' | 'DELETE'
 | 
				
			||||||
  url: string
 | 
					  url: `/${string}`
 | 
				
			||||||
  response: {
 | 
					  response: {
 | 
				
			||||||
    body: any
 | 
					    body: any
 | 
				
			||||||
    statusCode: number
 | 
					    statusCode: number
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,16 +1,16 @@
 | 
				
			|||||||
import { guild } from '../guilds/guild'
 | 
					import { guildExample } from '../guilds/guild'
 | 
				
			||||||
import { user } from '../users/user'
 | 
					import { userExample } from '../users/user'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const member = {
 | 
					export const memberExample = {
 | 
				
			||||||
  id: 1,
 | 
					  id: 1,
 | 
				
			||||||
  isOwner: true,
 | 
					  isOwner: true,
 | 
				
			||||||
  userId: user.id,
 | 
					  userId: userExample.id,
 | 
				
			||||||
  guildId: guild.id,
 | 
					  guildId: guildExample.id,
 | 
				
			||||||
  createdAt: new Date().toISOString(),
 | 
					  createdAt: new Date().toISOString(),
 | 
				
			||||||
  updatedAt: new Date().toISOString()
 | 
					  updatedAt: new Date().toISOString()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const memberComplete = {
 | 
					export const memberExampleComplete = {
 | 
				
			||||||
  ...member,
 | 
					  ...memberExample,
 | 
				
			||||||
  user
 | 
					  user: userExample
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										25
									
								
								cypress/fixtures/messages/message.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								cypress/fixtures/messages/message.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					import { channelExample } from '../channels/channel'
 | 
				
			||||||
 | 
					import { memberExampleComplete } from '../members/member'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const messageExample = {
 | 
				
			||||||
 | 
					  id: 1,
 | 
				
			||||||
 | 
					  value: 'Hello, world!',
 | 
				
			||||||
 | 
					  type: 'text' as 'text' | 'file',
 | 
				
			||||||
 | 
					  mimetype: 'text/plain',
 | 
				
			||||||
 | 
					  memberId: memberExampleComplete.id,
 | 
				
			||||||
 | 
					  channelId: channelExample.id,
 | 
				
			||||||
 | 
					  createdAt: new Date().toISOString(),
 | 
				
			||||||
 | 
					  updatedAt: new Date().toISOString()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const messageExampleComplete = {
 | 
				
			||||||
 | 
					  ...messageExample,
 | 
				
			||||||
 | 
					  member: memberExampleComplete
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const messageExampleComplete2 = {
 | 
				
			||||||
 | 
					  ...messageExample,
 | 
				
			||||||
 | 
					  id: 2,
 | 
				
			||||||
 | 
					  value: 'Second message',
 | 
				
			||||||
 | 
					  member: memberExampleComplete
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { Handler } from '../../handler'
 | 
					import { Handler } from '../../handler'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { user, userSettings } from '../user'
 | 
					import { userExample, userSettingsExample } from '../user'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getUsersCurrentHandler: Handler = {
 | 
					export const getUsersCurrentHandler: Handler = {
 | 
				
			||||||
  method: 'GET',
 | 
					  method: 'GET',
 | 
				
			||||||
@@ -9,8 +9,8 @@ export const getUsersCurrentHandler: Handler = {
 | 
				
			|||||||
    statusCode: 200,
 | 
					    statusCode: 200,
 | 
				
			||||||
    body: {
 | 
					    body: {
 | 
				
			||||||
      user: {
 | 
					      user: {
 | 
				
			||||||
        ...user,
 | 
					        ...userExample,
 | 
				
			||||||
        settings: userSettings,
 | 
					        settings: userSettingsExample,
 | 
				
			||||||
        currentStrategy: 'local',
 | 
					        currentStrategy: 'local',
 | 
				
			||||||
        strategies: ['local']
 | 
					        strategies: ['local']
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
import { Handler } from '../../handler'
 | 
					import { Handler } from '../../handler'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { user, userSettings } from '../user'
 | 
					import { userExample, userSettingsExample } from '../user'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const postUsersSignupHandler: Handler = {
 | 
					export const postUsersSignupHandler: Handler = {
 | 
				
			||||||
  method: 'POST',
 | 
					  method: 'POST',
 | 
				
			||||||
@@ -9,8 +9,8 @@ export const postUsersSignupHandler: Handler = {
 | 
				
			|||||||
    statusCode: 201,
 | 
					    statusCode: 201,
 | 
				
			||||||
    body: {
 | 
					    body: {
 | 
				
			||||||
      user: {
 | 
					      user: {
 | 
				
			||||||
        ...user,
 | 
					        ...userExample,
 | 
				
			||||||
        settings: userSettings
 | 
					        settings: userSettingsExample
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import { UserSettings } from '../../../models/UserSettings'
 | 
					import { UserSettings } from '../../../models/UserSettings'
 | 
				
			||||||
import { User } from '../../../models/User'
 | 
					import { User } from '../../../models/User'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const user: User = {
 | 
					export const userExample: User = {
 | 
				
			||||||
  id: 1,
 | 
					  id: 1,
 | 
				
			||||||
  name: 'Divlo',
 | 
					  name: 'Divlo',
 | 
				
			||||||
  email: 'contact@divlo.fr',
 | 
					  email: 'contact@divlo.fr',
 | 
				
			||||||
@@ -17,7 +17,7 @@ export const user: User = {
 | 
				
			|||||||
  updatedAt: '2021-10-20T20:59:08.485Z'
 | 
					  updatedAt: '2021-10-20T20:59:08.485Z'
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const userSettings: UserSettings = {
 | 
					export const userSettingsExample: UserSettings = {
 | 
				
			||||||
  id: 1,
 | 
					  id: 1,
 | 
				
			||||||
  language: 'en',
 | 
					  language: 'en',
 | 
				
			||||||
  theme: 'dark',
 | 
					  theme: 'dark',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					import { channelExample } from '../../../fixtures/channels/channel'
 | 
				
			||||||
 | 
					import { guildExample } from '../../../fixtures/guilds/guild'
 | 
				
			||||||
 | 
					import { userExample } from '../../../fixtures/users/user'
 | 
				
			||||||
 | 
					import { getGuildsHandler } from '../../../fixtures/guilds/get'
 | 
				
			||||||
 | 
					import { authenticationHandlers } from '../../../fixtures/handler'
 | 
				
			||||||
 | 
					import { getGuildMemberWithGuildIdHandler } from '../../../fixtures/guilds/[guildId]/get'
 | 
				
			||||||
 | 
					import { getChannelWithChannelIdHandler } from '../../../fixtures/channels/[channelId]/get'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const applicationPaths = [
 | 
				
			||||||
 | 
					  '/application',
 | 
				
			||||||
 | 
					  `/application/users/${userExample.id}`,
 | 
				
			||||||
 | 
					  '/application/guilds/create',
 | 
				
			||||||
 | 
					  '/application/guilds/join',
 | 
				
			||||||
 | 
					  `/application/${guildExample.id}/${channelExample.id}`
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Common > application/authentication', () => {
 | 
				
			||||||
 | 
					  beforeEach(() => {
 | 
				
			||||||
 | 
					    cy.task('stopMockServer')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should redirect the user to `/authentication/signin` if not signed in', () => {
 | 
				
			||||||
 | 
					    for (const applicationPath of applicationPaths) {
 | 
				
			||||||
 | 
					      cy.visit(applicationPath)
 | 
				
			||||||
 | 
					        .location('pathname')
 | 
				
			||||||
 | 
					        .should('eq', '/authentication/signin')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should not redirect the user if signed in', () => {
 | 
				
			||||||
 | 
					    cy.task('startMockServer', [
 | 
				
			||||||
 | 
					      ...authenticationHandlers,
 | 
				
			||||||
 | 
					      getGuildsHandler,
 | 
				
			||||||
 | 
					      getGuildMemberWithGuildIdHandler,
 | 
				
			||||||
 | 
					      getChannelWithChannelIdHandler
 | 
				
			||||||
 | 
					    ]).setCookie('refreshToken', 'refresh-token')
 | 
				
			||||||
 | 
					    for (const applicationPath of applicationPaths) {
 | 
				
			||||||
 | 
					      cy.visit(applicationPath)
 | 
				
			||||||
 | 
					        .location('pathname')
 | 
				
			||||||
 | 
					        .should('eq', applicationPath)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
@@ -1,10 +1,143 @@
 | 
				
			|||||||
 | 
					import date from 'date-and-time'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  channelExample,
 | 
				
			||||||
 | 
					  channelExample2
 | 
				
			||||||
 | 
					} from '../../../../fixtures/channels/channel'
 | 
				
			||||||
 | 
					import { guildExample } from '../../../../fixtures/guilds/guild'
 | 
				
			||||||
 | 
					import { getGuildMemberWithGuildIdHandler } from '../../../../fixtures/guilds/[guildId]/get'
 | 
				
			||||||
 | 
					import { getChannelWithChannelIdHandler } from '../../../../fixtures/channels/[channelId]/get'
 | 
				
			||||||
import { authenticationHandlers } from '../../../../fixtures/handler'
 | 
					import { authenticationHandlers } from '../../../../fixtures/handler'
 | 
				
			||||||
 | 
					import { getGuildsHandler } from '../../../../fixtures/guilds/get'
 | 
				
			||||||
 | 
					import { getChannelsWithGuildIdHandler } from '../../../../fixtures/guilds/[guildId]/channels/get'
 | 
				
			||||||
 | 
					import { getMessagesWithChannelIdHandler } from '../../../../fixtures/channels/[channelId]/messages/get'
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  messageExampleComplete,
 | 
				
			||||||
 | 
					  messageExampleComplete2
 | 
				
			||||||
 | 
					} from '../../../../fixtures/messages/message'
 | 
				
			||||||
 | 
					import { getMembersWithGuildIdHandler } from '../../../../fixtures/guilds/[guildId]/members/get'
 | 
				
			||||||
 | 
					import { memberExampleComplete } from '../../../../fixtures/members/member'
 | 
				
			||||||
 | 
					import { API_URL } from '../../../../../tools/api'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('Pages > /application/[guildId]/[channelId]', () => {
 | 
					describe('Pages > /application/[guildId]/[channelId]', () => {
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
    cy.task('stopMockServer')
 | 
					    cy.task('stopMockServer')
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should succeeds and display the guilds in left sidebar correctly', () => {
 | 
				
			||||||
 | 
					    cy.task('startMockServer', [
 | 
				
			||||||
 | 
					      ...authenticationHandlers,
 | 
				
			||||||
 | 
					      getGuildMemberWithGuildIdHandler,
 | 
				
			||||||
 | 
					      getChannelWithChannelIdHandler,
 | 
				
			||||||
 | 
					      getGuildsHandler
 | 
				
			||||||
 | 
					    ]).setCookie('refreshToken', 'refresh-token')
 | 
				
			||||||
 | 
					    cy.intercept(`${API_URL}${getGuildsHandler.url}*`).as('getGuildsHandler')
 | 
				
			||||||
 | 
					    cy.intercept(`/_next/*`).as('nextStaticAndImages')
 | 
				
			||||||
 | 
					    cy.visit(`/application/${guildExample.id}/${channelExample.id}`)
 | 
				
			||||||
 | 
					    cy.wait(['@getGuildsHandler', '@nextStaticAndImages']).then(() => {
 | 
				
			||||||
 | 
					      cy.get('[data-cy=application-title]').should(
 | 
				
			||||||
 | 
					        'have.text',
 | 
				
			||||||
 | 
					        `# ${channelExample.name}`
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      cy.get('[data-cy=guild-left-sidebar-title]').should(
 | 
				
			||||||
 | 
					        'have.text',
 | 
				
			||||||
 | 
					        guildExample.name
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      cy.get('.guilds-list').children().should('have.length', 2)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should succeeds and display the channels in left sidebar correctly', () => {
 | 
				
			||||||
 | 
					    cy.task('startMockServer', [
 | 
				
			||||||
 | 
					      ...authenticationHandlers,
 | 
				
			||||||
 | 
					      getGuildMemberWithGuildIdHandler,
 | 
				
			||||||
 | 
					      getChannelWithChannelIdHandler,
 | 
				
			||||||
 | 
					      getChannelsWithGuildIdHandler
 | 
				
			||||||
 | 
					    ]).setCookie('refreshToken', 'refresh-token')
 | 
				
			||||||
 | 
					    cy.intercept(`${API_URL}${getChannelsWithGuildIdHandler.url}*`).as(
 | 
				
			||||||
 | 
					      'getChannelsWithGuildIdHandler'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    cy.intercept(`/_next/*`).as('nextStaticAndImages')
 | 
				
			||||||
 | 
					    cy.visit(`/application/${guildExample.id}/${channelExample.id}`)
 | 
				
			||||||
 | 
					    cy.wait(['@getChannelsWithGuildIdHandler', '@nextStaticAndImages']).then(
 | 
				
			||||||
 | 
					      () => {
 | 
				
			||||||
 | 
					        cy.get('.channels-list').children().should('have.length', 2)
 | 
				
			||||||
 | 
					        cy.get('.channels-list [data-cy=channel-name]:first').should(
 | 
				
			||||||
 | 
					          'have.text',
 | 
				
			||||||
 | 
					          `# ${channelExample.name}`
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        cy.get('.channels-list [data-cy=channel-name]:last').should(
 | 
				
			||||||
 | 
					          'have.text',
 | 
				
			||||||
 | 
					          `# ${channelExample2.name}`
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should succeeds and display the messages correctly', () => {
 | 
				
			||||||
 | 
					    cy.task('startMockServer', [
 | 
				
			||||||
 | 
					      ...authenticationHandlers,
 | 
				
			||||||
 | 
					      getGuildMemberWithGuildIdHandler,
 | 
				
			||||||
 | 
					      getChannelWithChannelIdHandler,
 | 
				
			||||||
 | 
					      getMessagesWithChannelIdHandler
 | 
				
			||||||
 | 
					    ]).setCookie('refreshToken', 'refresh-token')
 | 
				
			||||||
 | 
					    cy.intercept(`${API_URL}${getMessagesWithChannelIdHandler.url}*`).as(
 | 
				
			||||||
 | 
					      'getMessagesWithChannelIdHandler'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    cy.intercept(`/_next/*`).as('nextStaticAndImages')
 | 
				
			||||||
 | 
					    cy.visit(`/application/${guildExample.id}/${channelExample.id}`)
 | 
				
			||||||
 | 
					    cy.wait(['@getMessagesWithChannelIdHandler', '@nextStaticAndImages']).then(
 | 
				
			||||||
 | 
					      () => {
 | 
				
			||||||
 | 
					        cy.get('.messages-list').children().should('have.length', 2)
 | 
				
			||||||
 | 
					        cy.get('.messages-list p:first').should(
 | 
				
			||||||
 | 
					          'have.text',
 | 
				
			||||||
 | 
					          messageExampleComplete.value
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        cy.get(
 | 
				
			||||||
 | 
					          '.messages-list [data-cy=message-member-user-name]:first'
 | 
				
			||||||
 | 
					        ).should('have.text', messageExampleComplete.member.user.name)
 | 
				
			||||||
 | 
					        cy.get('.messages-list [data-cy=message-date]:first').should(
 | 
				
			||||||
 | 
					          'have.text',
 | 
				
			||||||
 | 
					          date.format(
 | 
				
			||||||
 | 
					            new Date(messageExampleComplete.createdAt),
 | 
				
			||||||
 | 
					            'DD/MM/YYYY - HH:mm:ss'
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        cy.get('.messages-list p:last').should(
 | 
				
			||||||
 | 
					          'have.text',
 | 
				
			||||||
 | 
					          messageExampleComplete2.value
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should succeeds and display the members in right sidebar correctly', () => {
 | 
				
			||||||
 | 
					    cy.task('startMockServer', [
 | 
				
			||||||
 | 
					      ...authenticationHandlers,
 | 
				
			||||||
 | 
					      getGuildMemberWithGuildIdHandler,
 | 
				
			||||||
 | 
					      getChannelWithChannelIdHandler,
 | 
				
			||||||
 | 
					      getMembersWithGuildIdHandler
 | 
				
			||||||
 | 
					    ]).setCookie('refreshToken', 'refresh-token')
 | 
				
			||||||
 | 
					    cy.intercept(`${API_URL}${getMembersWithGuildIdHandler.url}*`).as(
 | 
				
			||||||
 | 
					      'getMembersWithGuildIdHandler'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    cy.intercept(`/_next/*`).as('nextStaticAndImages')
 | 
				
			||||||
 | 
					    cy.visit(`/application/${guildExample.id}/${channelExample.id}`)
 | 
				
			||||||
 | 
					    cy.wait(['@getMembersWithGuildIdHandler', '@nextStaticAndImages']).then(
 | 
				
			||||||
 | 
					      () => {
 | 
				
			||||||
 | 
					        cy.get('.members-list').should('not.be.visible')
 | 
				
			||||||
 | 
					        cy.get('[data-cy=icon-button-right-sidebar-members]').click()
 | 
				
			||||||
 | 
					        cy.get('.members-list').should('be.visible')
 | 
				
			||||||
 | 
					        cy.get('[data-cy=members-title]').should('have.text', 'Member(s)')
 | 
				
			||||||
 | 
					        cy.get('.members-list').children().should('have.length', 1)
 | 
				
			||||||
 | 
					        cy.get('.members-list [data-cy=member-user-name]:first').should(
 | 
				
			||||||
 | 
					          'have.text',
 | 
				
			||||||
 | 
					          memberExampleComplete.user.name
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should redirect the user to `/application` if `guildId` or `channelId` are not numbers', () => {
 | 
					  it('should redirect the user to `/application` if `guildId` or `channelId` are not numbers', () => {
 | 
				
			||||||
    cy.task('startMockServer', authenticationHandlers).setCookie(
 | 
					    cy.task('startMockServer', authenticationHandlers).setCookie(
 | 
				
			||||||
      'refreshToken',
 | 
					      'refreshToken',
 | 
				
			||||||
@@ -14,4 +147,26 @@ describe('Pages > /application/[guildId]/[channelId]', () => {
 | 
				
			|||||||
      .location('pathname')
 | 
					      .location('pathname')
 | 
				
			||||||
      .should('eq', '/application')
 | 
					      .should('eq', '/application')
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it("should redirect the user to `/404` if `guildId` doesn't exist", () => {
 | 
				
			||||||
 | 
					    cy.task('startMockServer', [
 | 
				
			||||||
 | 
					      ...authenticationHandlers,
 | 
				
			||||||
 | 
					      getChannelWithChannelIdHandler
 | 
				
			||||||
 | 
					    ]).setCookie('refreshToken', 'refresh-token')
 | 
				
			||||||
 | 
					    cy.visit(`/application/123/${channelExample.id}`, {
 | 
				
			||||||
 | 
					      failOnStatusCode: false
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					      .location('pathname')
 | 
				
			||||||
 | 
					      .should('eq', '/404')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it("should redirect the user to `/404` if `channelId` doesn't exist", () => {
 | 
				
			||||||
 | 
					    cy.task('startMockServer', [
 | 
				
			||||||
 | 
					      ...authenticationHandlers,
 | 
				
			||||||
 | 
					      getGuildMemberWithGuildIdHandler
 | 
				
			||||||
 | 
					    ]).setCookie('refreshToken', 'refresh-token')
 | 
				
			||||||
 | 
					    cy.visit(`/application/${guildExample.id}/123`, { failOnStatusCode: false })
 | 
				
			||||||
 | 
					      .location('pathname')
 | 
				
			||||||
 | 
					      .should('eq', '/404')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,8 @@
 | 
				
			|||||||
import { channel } from '../../../../fixtures/channels/channel'
 | 
					import { getChannelWithChannelIdHandler } from '../../../../fixtures/channels/[channelId]/get'
 | 
				
			||||||
import { guild } from '../../../../fixtures/guilds/guild'
 | 
					import { getGuildsHandler } from '../../../../fixtures/guilds/get'
 | 
				
			||||||
 | 
					import { getGuildMemberWithGuildIdHandler } from '../../../../fixtures/guilds/[guildId]/get'
 | 
				
			||||||
 | 
					import { channelExample } from '../../../../fixtures/channels/channel'
 | 
				
			||||||
 | 
					import { guildExample } from '../../../../fixtures/guilds/guild'
 | 
				
			||||||
import { postGuildsHandler } from '../../../../fixtures/guilds/post'
 | 
					import { postGuildsHandler } from '../../../../fixtures/guilds/post'
 | 
				
			||||||
import { authenticationHandlers } from '../../../../fixtures/handler'
 | 
					import { authenticationHandlers } from '../../../../fixtures/handler'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -11,15 +14,19 @@ describe('Pages > /application/guilds/create', () => {
 | 
				
			|||||||
  it('should succeeds and create the guild', () => {
 | 
					  it('should succeeds and create the guild', () => {
 | 
				
			||||||
    cy.task('startMockServer', [
 | 
					    cy.task('startMockServer', [
 | 
				
			||||||
      ...authenticationHandlers,
 | 
					      ...authenticationHandlers,
 | 
				
			||||||
      postGuildsHandler
 | 
					      postGuildsHandler,
 | 
				
			||||||
 | 
					      getGuildsHandler,
 | 
				
			||||||
 | 
					      getGuildMemberWithGuildIdHandler,
 | 
				
			||||||
 | 
					      getChannelWithChannelIdHandler
 | 
				
			||||||
    ]).setCookie('refreshToken', 'refresh-token')
 | 
					    ]).setCookie('refreshToken', 'refresh-token')
 | 
				
			||||||
    cy.visit('/application/guilds/create')
 | 
					    cy.visit('/application/guilds/create')
 | 
				
			||||||
 | 
					    cy.get('[data-cy=application-title]').should('have.text', 'Create a Guild')
 | 
				
			||||||
    cy.get('#error-name').should('not.exist')
 | 
					    cy.get('#error-name').should('not.exist')
 | 
				
			||||||
    cy.get('[data-cy=input-name]').type(guild.name)
 | 
					    cy.get('[data-cy=input-name]').type(guildExample.name)
 | 
				
			||||||
    cy.get('[data-cy=submit]').click()
 | 
					    cy.get('[data-cy=submit]').click()
 | 
				
			||||||
    cy.location('pathname').should(
 | 
					    cy.location('pathname').should(
 | 
				
			||||||
      'eq',
 | 
					      'eq',
 | 
				
			||||||
      `/application/${guild.id}/${channel.id}`
 | 
					      `/application/${guildExample.id}/${channelExample.id}`
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -30,7 +37,7 @@ describe('Pages > /application/guilds/create', () => {
 | 
				
			|||||||
    )
 | 
					    )
 | 
				
			||||||
    cy.visit('/application/guilds/create')
 | 
					    cy.visit('/application/guilds/create')
 | 
				
			||||||
    cy.get('#error-name').should('not.exist')
 | 
					    cy.get('#error-name').should('not.exist')
 | 
				
			||||||
    cy.get('[data-cy=input-name]').type(guild.name)
 | 
					    cy.get('[data-cy=input-name]').type(guildExample.name)
 | 
				
			||||||
    cy.get('[data-cy=submit]').click()
 | 
					    cy.get('[data-cy=submit]').click()
 | 
				
			||||||
    cy.get('#message').should('have.text', 'Error: Internal Server Error.')
 | 
					    cy.get('#message').should('have.text', 'Error: Internal Server Error.')
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,45 +1,39 @@
 | 
				
			|||||||
 | 
					import { guildExample, guildExample2 } from '../../../../fixtures/guilds/guild'
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  getGuildsPublicEmptyHandler,
 | 
					  getGuildsPublicEmptyHandler,
 | 
				
			||||||
  getGuildsPublicHandler,
 | 
					  getGuildsPublicHandler,
 | 
				
			||||||
  getGuildsPublicSearchHandler
 | 
					  getGuildsPublicSearchHandler
 | 
				
			||||||
} from '../../../../fixtures/guilds/public/get'
 | 
					} from '../../../../fixtures/guilds/public/get'
 | 
				
			||||||
import { authenticationHandlers } from '../../../../fixtures/handler'
 | 
					import { authenticationHandlers } from '../../../../fixtures/handler'
 | 
				
			||||||
 | 
					import { API_URL } from '../../../../../tools/api'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('Pages > /application/guilds/join', () => {
 | 
					describe('Pages > /application/guilds/join', () => {
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
    cy.task('stopMockServer')
 | 
					    cy.task('stopMockServer')
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should shows no guild if there are no public guilds', () => {
 | 
					 | 
				
			||||||
    cy.task('startMockServer', [
 | 
					 | 
				
			||||||
      ...authenticationHandlers,
 | 
					 | 
				
			||||||
      getGuildsPublicEmptyHandler
 | 
					 | 
				
			||||||
    ]).setCookie('refreshToken', 'refresh-token')
 | 
					 | 
				
			||||||
    cy.visit('/application/guilds/join')
 | 
					 | 
				
			||||||
    cy.get('.guilds-list').children().should('have.length', 0)
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should shows loader with internal api server error', () => {
 | 
					 | 
				
			||||||
    cy.task('startMockServer', [...authenticationHandlers]).setCookie(
 | 
					 | 
				
			||||||
      'refreshToken',
 | 
					 | 
				
			||||||
      'refresh-token'
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    cy.visit('/application/guilds/join')
 | 
					 | 
				
			||||||
    cy.get('.guilds-list').children().should('have.length', 1)
 | 
					 | 
				
			||||||
    cy.get('[data-testid=progress-spinner]').should('be.visible')
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should shows all the guilds', () => {
 | 
					  it('should shows all the guilds', () => {
 | 
				
			||||||
    cy.task('startMockServer', [
 | 
					    cy.task('startMockServer', [
 | 
				
			||||||
      ...authenticationHandlers,
 | 
					      ...authenticationHandlers,
 | 
				
			||||||
      getGuildsPublicHandler
 | 
					      getGuildsPublicHandler
 | 
				
			||||||
    ]).setCookie('refreshToken', 'refresh-token')
 | 
					    ]).setCookie('refreshToken', 'refresh-token')
 | 
				
			||||||
    cy.visit('/application/guilds/join')
 | 
					    cy.intercept(`${API_URL}${getGuildsPublicHandler.url}*`).as(
 | 
				
			||||||
    cy.get('.guilds-list').children().should('have.length', 2)
 | 
					      'getGuildsPublicHandler'
 | 
				
			||||||
    cy.get('.guilds-list [data-cy=guild-name]:first').should(
 | 
					 | 
				
			||||||
      'have.text',
 | 
					 | 
				
			||||||
      'GuildExample'
 | 
					 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					    cy.intercept(`/_next/*`).as('nextStaticAndImages')
 | 
				
			||||||
 | 
					    cy.visit('/application/guilds/join')
 | 
				
			||||||
 | 
					    cy.wait(['@getGuildsPublicHandler', '@nextStaticAndImages']).then(() => {
 | 
				
			||||||
 | 
					      cy.get('[data-cy=application-title]').should('have.text', 'Join a Guild')
 | 
				
			||||||
 | 
					      cy.get('.guilds-public-list').children().should('have.length', 2)
 | 
				
			||||||
 | 
					      cy.get('.guilds-public-list [data-cy=guild-name]:first').should(
 | 
				
			||||||
 | 
					        'have.text',
 | 
				
			||||||
 | 
					        guildExample.name
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      cy.get('.guilds-public-list [data-cy=guild-name]:last').should(
 | 
				
			||||||
 | 
					        'have.text',
 | 
				
			||||||
 | 
					        guildExample2.name
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should shows the searched guild', () => {
 | 
					  it('should shows the searched guild', () => {
 | 
				
			||||||
@@ -48,8 +42,41 @@ describe('Pages > /application/guilds/join', () => {
 | 
				
			|||||||
      getGuildsPublicSearchHandler
 | 
					      getGuildsPublicSearchHandler
 | 
				
			||||||
    ]).setCookie('refreshToken', 'refresh-token')
 | 
					    ]).setCookie('refreshToken', 'refresh-token')
 | 
				
			||||||
    cy.visit('/application/guilds/join')
 | 
					    cy.visit('/application/guilds/join')
 | 
				
			||||||
    cy.get('[data-cy=search-guild-input]').type('app')
 | 
					    cy.intercept(`${API_URL}${getGuildsPublicHandler.url}*`).as(
 | 
				
			||||||
    cy.get('.guilds-list').children().should('have.length', 1)
 | 
					      'getGuildsPublicHandler'
 | 
				
			||||||
    cy.get('.guilds-list [data-cy=guild-name]:first').should('have.text', 'app')
 | 
					    )
 | 
				
			||||||
 | 
					    cy.intercept(`/_next/*`).as('nextStaticAndImages')
 | 
				
			||||||
 | 
					    cy.wait(['@getGuildsPublicHandler', '@nextStaticAndImages']).then(() => {
 | 
				
			||||||
 | 
					      cy.get('[data-cy=search-guild-input]').type(guildExample2.name)
 | 
				
			||||||
 | 
					      cy.get('.guilds-public-list').children().should('have.length', 1)
 | 
				
			||||||
 | 
					      cy.get('.guilds-public-list [data-cy=guild-name]:first').should(
 | 
				
			||||||
 | 
					        'have.text',
 | 
				
			||||||
 | 
					        guildExample2.name
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should shows no guild if there are no public guilds', () => {
 | 
				
			||||||
 | 
					    cy.task('startMockServer', [
 | 
				
			||||||
 | 
					      ...authenticationHandlers,
 | 
				
			||||||
 | 
					      getGuildsPublicEmptyHandler
 | 
				
			||||||
 | 
					    ]).setCookie('refreshToken', 'refresh-token')
 | 
				
			||||||
 | 
					    cy.intercept(`${API_URL}${getGuildsPublicEmptyHandler.url}*`).as(
 | 
				
			||||||
 | 
					      'getGuildsPublicEmptyHandler'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    cy.visit('/application/guilds/join')
 | 
				
			||||||
 | 
					    cy.wait('@getGuildsPublicEmptyHandler').then(() => {
 | 
				
			||||||
 | 
					      cy.get('.guilds-public-list').children().should('have.length', 0)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should shows loader with internal api server error', () => {
 | 
				
			||||||
 | 
					    cy.task('startMockServer', [...authenticationHandlers]).setCookie(
 | 
				
			||||||
 | 
					      'refreshToken',
 | 
				
			||||||
 | 
					      'refresh-token'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    cy.visit('/application/guilds/join')
 | 
				
			||||||
 | 
					    cy.get('.guilds-public-list').children().should('have.length', 1)
 | 
				
			||||||
 | 
					    cy.get('[data-testid=progress-spinner]').should('be.visible')
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,35 +1,32 @@
 | 
				
			|||||||
import { authenticationHandlers } from '../../../fixtures/handler'
 | 
					import { authenticationHandlers } from '../../../fixtures/handler'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const applicationPaths = [
 | 
					 | 
				
			||||||
  '/application',
 | 
					 | 
				
			||||||
  '/application/users/0',
 | 
					 | 
				
			||||||
  '/application/guilds/create',
 | 
					 | 
				
			||||||
  '/application/guilds/join',
 | 
					 | 
				
			||||||
  '/application/0/0'
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('Pages > /application', () => {
 | 
					describe('Pages > /application', () => {
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
    cy.task('stopMockServer')
 | 
					    cy.task('stopMockServer')
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should redirect the user to `/authentication/signin` if not signed in', () => {
 | 
					  it('should redirect user to `/application/guilds/create` on click on "Create a Guild"', () => {
 | 
				
			||||||
    for (const applicationPath of applicationPaths) {
 | 
					    cy.task('startMockServer', [...authenticationHandlers]).setCookie(
 | 
				
			||||||
      cy.visit(applicationPath)
 | 
					 | 
				
			||||||
        .location('pathname')
 | 
					 | 
				
			||||||
        .should('eq', '/authentication/signin')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('should not redirect the user if signed in', () => {
 | 
					 | 
				
			||||||
    cy.task('startMockServer', authenticationHandlers).setCookie(
 | 
					 | 
				
			||||||
      'refreshToken',
 | 
					      'refreshToken',
 | 
				
			||||||
      'refresh-token'
 | 
					      'refresh-token'
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    for (const applicationPath of applicationPaths) {
 | 
					    cy.visit('/application')
 | 
				
			||||||
      cy.visit(applicationPath)
 | 
					    cy.get('[data-cy=application-title]').should('have.text', 'Application')
 | 
				
			||||||
        .location('pathname')
 | 
					    cy.get('a[href="/application/guilds/create"]')
 | 
				
			||||||
        .should('eq', applicationPath)
 | 
					      .click()
 | 
				
			||||||
    }
 | 
					      .location('pathname')
 | 
				
			||||||
 | 
					      .should('eq', '/application/guilds/create')
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should redirect user to `/application/guilds/join` on click on "Join a Guild"', () => {
 | 
				
			||||||
 | 
					    cy.task('startMockServer', [...authenticationHandlers]).setCookie(
 | 
				
			||||||
 | 
					      'refreshToken',
 | 
				
			||||||
 | 
					      'refresh-token'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    cy.visit('/application')
 | 
				
			||||||
 | 
					    cy.get('a[href="/application/guilds/join"]')
 | 
				
			||||||
 | 
					      .click()
 | 
				
			||||||
 | 
					      .location('pathname')
 | 
				
			||||||
 | 
					      .should('eq', '/application/guilds/join')
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { postUsersResetPasswordHandler } from '../../../fixtures/users/reset-password/post'
 | 
					import { postUsersResetPasswordHandler } from '../../../fixtures/users/reset-password/post'
 | 
				
			||||||
import { user } from '../../../fixtures/users/user'
 | 
					import { userExample } from '../../../fixtures/users/user'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('Pages > /authentication/forgot-password', () => {
 | 
					describe('Pages > /authentication/forgot-password', () => {
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
@@ -10,7 +10,7 @@ describe('Pages > /authentication/forgot-password', () => {
 | 
				
			|||||||
  it('should succeeds and sends a password-reset request', () => {
 | 
					  it('should succeeds and sends a password-reset request', () => {
 | 
				
			||||||
    cy.task('startMockServer', [postUsersResetPasswordHandler])
 | 
					    cy.task('startMockServer', [postUsersResetPasswordHandler])
 | 
				
			||||||
    cy.get('#message').should('not.exist')
 | 
					    cy.get('#message').should('not.exist')
 | 
				
			||||||
    cy.get('[data-cy=input-email]').type(user.email)
 | 
					    cy.get('[data-cy=input-email]').type(userExample.email)
 | 
				
			||||||
    cy.get('[data-cy=submit]').click()
 | 
					    cy.get('[data-cy=submit]').click()
 | 
				
			||||||
    cy.get('#message').should(
 | 
					    cy.get('#message').should(
 | 
				
			||||||
      'have.text',
 | 
					      'have.text',
 | 
				
			||||||
@@ -20,7 +20,7 @@ describe('Pages > /authentication/forgot-password', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  it('should fails with unreachable api server', () => {
 | 
					  it('should fails with unreachable api server', () => {
 | 
				
			||||||
    cy.get('#message').should('not.exist')
 | 
					    cy.get('#message').should('not.exist')
 | 
				
			||||||
    cy.get('[data-cy=input-email]').type(user.email)
 | 
					    cy.get('[data-cy=input-email]').type(userExample.email)
 | 
				
			||||||
    cy.get('[data-cy=submit]').click()
 | 
					    cy.get('[data-cy=submit]').click()
 | 
				
			||||||
    cy.get('#message').should('have.text', 'Error: Internal Server Error.')
 | 
					    cy.get('#message').should('have.text', 'Error: Internal Server Error.')
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@ import {
 | 
				
			|||||||
  postUsersSigninHandler,
 | 
					  postUsersSigninHandler,
 | 
				
			||||||
  postUsersSigninInvalidCredentialsHandler
 | 
					  postUsersSigninInvalidCredentialsHandler
 | 
				
			||||||
} from 'cypress/fixtures/users/signin/post'
 | 
					} from 'cypress/fixtures/users/signin/post'
 | 
				
			||||||
import { user } from '../../../fixtures/users/user'
 | 
					import { userExample } from '../../../fixtures/users/user'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('Pages > /authentication/signin', () => {
 | 
					describe('Pages > /authentication/signin', () => {
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
@@ -18,7 +18,7 @@ describe('Pages > /authentication/signin', () => {
 | 
				
			|||||||
    ])
 | 
					    ])
 | 
				
			||||||
    cy.get('#error-email').should('not.exist')
 | 
					    cy.get('#error-email').should('not.exist')
 | 
				
			||||||
    cy.get('#error-password').should('not.exist')
 | 
					    cy.get('#error-password').should('not.exist')
 | 
				
			||||||
    cy.get('[data-cy=input-email]').type(user.email)
 | 
					    cy.get('[data-cy=input-email]').type(userExample.email)
 | 
				
			||||||
    cy.get('[data-cy=input-password]').type('randompassword')
 | 
					    cy.get('[data-cy=input-password]').type('randompassword')
 | 
				
			||||||
    cy.get('[data-cy=submit]').click()
 | 
					    cy.get('[data-cy=submit]').click()
 | 
				
			||||||
    cy.location('pathname').should('eq', '/application')
 | 
					    cy.location('pathname').should('eq', '/application')
 | 
				
			||||||
@@ -27,7 +27,7 @@ describe('Pages > /authentication/signin', () => {
 | 
				
			|||||||
  it('should fails with unreachable api server', () => {
 | 
					  it('should fails with unreachable api server', () => {
 | 
				
			||||||
    cy.get('#error-email').should('not.exist')
 | 
					    cy.get('#error-email').should('not.exist')
 | 
				
			||||||
    cy.get('#error-password').should('not.exist')
 | 
					    cy.get('#error-password').should('not.exist')
 | 
				
			||||||
    cy.get('[data-cy=input-email]').type(user.email)
 | 
					    cy.get('[data-cy=input-email]').type(userExample.email)
 | 
				
			||||||
    cy.get('[data-cy=input-password]').type('randompassword')
 | 
					    cy.get('[data-cy=input-password]').type('randompassword')
 | 
				
			||||||
    cy.get('[data-cy=submit]').click()
 | 
					    cy.get('[data-cy=submit]').click()
 | 
				
			||||||
    cy.get('#message').should('have.text', 'Error: Internal Server Error.')
 | 
					    cy.get('#message').should('have.text', 'Error: Internal Server Error.')
 | 
				
			||||||
@@ -42,7 +42,7 @@ describe('Pages > /authentication/signin', () => {
 | 
				
			|||||||
    ])
 | 
					    ])
 | 
				
			||||||
    cy.get('#error-email').should('not.exist')
 | 
					    cy.get('#error-email').should('not.exist')
 | 
				
			||||||
    cy.get('#error-password').should('not.exist')
 | 
					    cy.get('#error-password').should('not.exist')
 | 
				
			||||||
    cy.get('[data-cy=input-email]').type(user.email)
 | 
					    cy.get('[data-cy=input-email]').type(userExample.email)
 | 
				
			||||||
    cy.get('[data-cy=input-password]').type('randompassword')
 | 
					    cy.get('[data-cy=input-password]').type('randompassword')
 | 
				
			||||||
    cy.get('[data-cy=submit]').click()
 | 
					    cy.get('[data-cy=submit]').click()
 | 
				
			||||||
    cy.get('#message').should(
 | 
					    cy.get('#message').should(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import { user } from '../../../fixtures/users/user'
 | 
					import { userExample } from '../../../fixtures/users/user'
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  postUsersSignupHandler,
 | 
					  postUsersSignupHandler,
 | 
				
			||||||
  postUsersSignupAlreadyUsedHandler
 | 
					  postUsersSignupAlreadyUsedHandler
 | 
				
			||||||
@@ -15,8 +15,8 @@ describe('Pages > /authentication/signup', () => {
 | 
				
			|||||||
    cy.get('#error-name').should('not.exist')
 | 
					    cy.get('#error-name').should('not.exist')
 | 
				
			||||||
    cy.get('#error-email').should('not.exist')
 | 
					    cy.get('#error-email').should('not.exist')
 | 
				
			||||||
    cy.get('#error-password').should('not.exist')
 | 
					    cy.get('#error-password').should('not.exist')
 | 
				
			||||||
    cy.get('[data-cy=input-name]').type(user.name)
 | 
					    cy.get('[data-cy=input-name]').type(userExample.name)
 | 
				
			||||||
    cy.get('[data-cy=input-email]').type(user.email)
 | 
					    cy.get('[data-cy=input-email]').type(userExample.email)
 | 
				
			||||||
    cy.get('[data-cy=input-password]').type('randompassword')
 | 
					    cy.get('[data-cy=input-password]').type('randompassword')
 | 
				
			||||||
    cy.get('[data-cy=submit]').click()
 | 
					    cy.get('[data-cy=submit]').click()
 | 
				
			||||||
    cy.get('#message').should(
 | 
					    cy.get('#message').should(
 | 
				
			||||||
@@ -30,8 +30,8 @@ describe('Pages > /authentication/signup', () => {
 | 
				
			|||||||
    cy.get('#error-name').should('not.exist')
 | 
					    cy.get('#error-name').should('not.exist')
 | 
				
			||||||
    cy.get('#error-email').should('not.exist')
 | 
					    cy.get('#error-email').should('not.exist')
 | 
				
			||||||
    cy.get('#error-password').should('not.exist')
 | 
					    cy.get('#error-password').should('not.exist')
 | 
				
			||||||
    cy.get('[data-cy=input-name]').type(user.name)
 | 
					    cy.get('[data-cy=input-name]').type(userExample.name)
 | 
				
			||||||
    cy.get('[data-cy=input-email]').type(user.email)
 | 
					    cy.get('[data-cy=input-email]').type(userExample.email)
 | 
				
			||||||
    cy.get('[data-cy=input-password]').type('randompassword')
 | 
					    cy.get('[data-cy=input-password]').type('randompassword')
 | 
				
			||||||
    cy.get('[data-cy=submit]').click()
 | 
					    cy.get('[data-cy=submit]').click()
 | 
				
			||||||
    cy.get('#message').should('have.text', 'Error: Name or Email already used.')
 | 
					    cy.get('#message').should('have.text', 'Error: Name or Email already used.')
 | 
				
			||||||
@@ -44,8 +44,8 @@ describe('Pages > /authentication/signup', () => {
 | 
				
			|||||||
    cy.get('#error-name').should('not.exist')
 | 
					    cy.get('#error-name').should('not.exist')
 | 
				
			||||||
    cy.get('#error-email').should('not.exist')
 | 
					    cy.get('#error-email').should('not.exist')
 | 
				
			||||||
    cy.get('#error-password').should('not.exist')
 | 
					    cy.get('#error-password').should('not.exist')
 | 
				
			||||||
    cy.get('[data-cy=input-name]').type(user.name)
 | 
					    cy.get('[data-cy=input-name]').type(userExample.name)
 | 
				
			||||||
    cy.get('[data-cy=input-email]').type(user.email)
 | 
					    cy.get('[data-cy=input-email]').type(userExample.email)
 | 
				
			||||||
    cy.get('[data-cy=input-password]').type('randompassword')
 | 
					    cy.get('[data-cy=input-password]').type('randompassword')
 | 
				
			||||||
    cy.get('[data-cy=submit]').click()
 | 
					    cy.get('[data-cy=submit]').click()
 | 
				
			||||||
    cy.get('#message').should('have.text', 'Error: Internal Server Error.')
 | 
					    cy.get('#message').should('have.text', 'Error: Internal Server Error.')
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,7 @@
 | 
				
			|||||||
import { getLocal } from 'mockttp'
 | 
					import { getLocal } from 'mockttp'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { API_DEFAULT_PORT } from '../../tools/api'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// <reference types="cypress" />
 | 
					/// <reference types="cypress" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** @type {import('mockttp').Mockttp | null}  */
 | 
					/** @type {import('mockttp').Mockttp | null}  */
 | 
				
			||||||
@@ -17,7 +19,7 @@ module.exports = (on, config) => {
 | 
				
			|||||||
      server = getLocal({
 | 
					      server = getLocal({
 | 
				
			||||||
        cors: true
 | 
					        cors: true
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      await server.start(8080)
 | 
					      await server.start(API_DEFAULT_PORT)
 | 
				
			||||||
      for (const handler of handlers) {
 | 
					      for (const handler of handlers) {
 | 
				
			||||||
        await server[handler.method.toLowerCase()](handler.url).thenJson(
 | 
					        await server[handler.method.toLowerCase()](handler.url).thenJson(
 | 
				
			||||||
          handler.response.statusCode,
 | 
					          handler.response.statusCode,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@ import type { FormDataObject, HandleForm } from 'react-component-form'
 | 
				
			|||||||
import type { ErrorObject } from 'ajv'
 | 
					import type { ErrorObject } from 'ajv'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { FetchState, useFetchState } from '../useFetchState'
 | 
					import { FetchState, useFetchState } from '../useFetchState'
 | 
				
			||||||
import { ajv } from '../../utils/ajv'
 | 
					import { ajv } from '../../tools/ajv'
 | 
				
			||||||
import { getErrorTranslationKey } from './getErrorTranslationKey'
 | 
					import { getErrorTranslationKey } from './getErrorTranslationKey'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface Errors {
 | 
					interface Errors {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										85
									
								
								hooks/usePagination.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								hooks/usePagination.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
				
			|||||||
 | 
					import { useState, useRef, useCallback } from 'react'
 | 
				
			||||||
 | 
					import { AxiosInstance } from 'axios'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { FetchState } from './useFetchState'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface Query {
 | 
				
			||||||
 | 
					  [key: string]: string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export type NextPageAsync = (query?: Query) => Promise<void>
 | 
				
			||||||
 | 
					export type NextPage = (query?: Query, callback?: () => void) => void
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface UsePaginationOptions {
 | 
				
			||||||
 | 
					  api: AxiosInstance
 | 
				
			||||||
 | 
					  url: string
 | 
				
			||||||
 | 
					  inverse?: boolean
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface UsePaginationResult<T> {
 | 
				
			||||||
 | 
					  items: T[]
 | 
				
			||||||
 | 
					  nextPage: NextPage
 | 
				
			||||||
 | 
					  resetPagination: () => void
 | 
				
			||||||
 | 
					  hasMore: boolean
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const usePagination = <T extends { id: number }>(
 | 
				
			||||||
 | 
					  options: UsePaginationOptions
 | 
				
			||||||
 | 
					): UsePaginationResult<T> => {
 | 
				
			||||||
 | 
					  const { api, url, inverse = false } = options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [items, setItems] = useState<T[]>([])
 | 
				
			||||||
 | 
					  const [hasMore, setHasMore] = useState(true)
 | 
				
			||||||
 | 
					  const fetchState = useRef<FetchState>('idle')
 | 
				
			||||||
 | 
					  const afterId = useRef<number | null>(null)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const nextPageAsync: NextPageAsync = useCallback(
 | 
				
			||||||
 | 
					    async (query) => {
 | 
				
			||||||
 | 
					      if (fetchState.current !== 'idle') {
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      fetchState.current = 'loading'
 | 
				
			||||||
 | 
					      const searchParameters = new URLSearchParams(query)
 | 
				
			||||||
 | 
					      searchParameters.append('limit', '20')
 | 
				
			||||||
 | 
					      if (afterId.current != null) {
 | 
				
			||||||
 | 
					        searchParameters.append('after', afterId.current.toString())
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const { data: newItems } = await api.get<T[]>(
 | 
				
			||||||
 | 
					        `${url}?${searchParameters.toString()}`
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      if (!inverse) {
 | 
				
			||||||
 | 
					        afterId.current =
 | 
				
			||||||
 | 
					          newItems.length > 0 ? newItems[newItems.length - 1].id : null
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        afterId.current = newItems.length > 0 ? newItems[0].id : null
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      setItems((oldItems) => {
 | 
				
			||||||
 | 
					        return inverse ? [...newItems, ...oldItems] : [...oldItems, ...newItems]
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      setHasMore(newItems.length > 0)
 | 
				
			||||||
 | 
					      fetchState.current = 'idle'
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    [api, url, inverse]
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const nextPage: NextPage = useCallback(
 | 
				
			||||||
 | 
					    (query, callback) => {
 | 
				
			||||||
 | 
					      nextPageAsync(query)
 | 
				
			||||||
 | 
					        .then(() => {
 | 
				
			||||||
 | 
					          if (callback != null) {
 | 
				
			||||||
 | 
					            callback()
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .catch((error) => {
 | 
				
			||||||
 | 
					          console.error(error)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    [nextPageAsync]
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const resetPagination = useCallback((): void => {
 | 
				
			||||||
 | 
					    afterId.current = null
 | 
				
			||||||
 | 
					    setItems([])
 | 
				
			||||||
 | 
					  }, [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { items, hasMore, nextPage, resetPagination }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -13,6 +13,7 @@
 | 
				
			|||||||
    "/application/users/[userId]": ["application", "errors"],
 | 
					    "/application/users/[userId]": ["application", "errors"],
 | 
				
			||||||
    "/application/guilds/create": ["application", "errors"],
 | 
					    "/application/guilds/create": ["application", "errors"],
 | 
				
			||||||
    "/application/guilds/join": ["application", "errors"],
 | 
					    "/application/guilds/join": ["application", "errors"],
 | 
				
			||||||
    "/application": ["application", "errors"]
 | 
					    "/application": ["application", "errors"],
 | 
				
			||||||
 | 
					    "/application/[guildId]/[channelId]": ["application", "errors"]
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,11 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "website": "Website",
 | 
					  "website": "Website",
 | 
				
			||||||
  "create": "Create",
 | 
					  "create": "Create",
 | 
				
			||||||
  "create-a-guild": "Create a Guild"
 | 
					  "create-a-guild": "Create a Guild",
 | 
				
			||||||
 | 
					  "create-a-guild-description": "Create your own guild and manage everything.",
 | 
				
			||||||
 | 
					  "join-a-guild": "Join a Guild",
 | 
				
			||||||
 | 
					  "join-a-guild-description": "Talk, collaborate, share and have fun with your friends by joining an already existing guild!",
 | 
				
			||||||
 | 
					  "members": "member(s)",
 | 
				
			||||||
 | 
					  "search": "Search",
 | 
				
			||||||
 | 
					  "write-a-message": "Write a message..."
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,11 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "website": "Site web",
 | 
					  "website": "Site web",
 | 
				
			||||||
  "create": "Crée",
 | 
					  "create": "Créer",
 | 
				
			||||||
  "create-a-guild": "Crée une Guilde"
 | 
					  "create-a-guild": "Créer une Guilde",
 | 
				
			||||||
 | 
					  "create-a-guild-description": "Créez votre propre guilde et gérez tout.",
 | 
				
			||||||
 | 
					  "join-a-guild": "Rejoindre une Guilde",
 | 
				
			||||||
 | 
					  "join-a-guild-description": "Discutez, collaborez, partagez et amusez-vous avec vos amis en rejoignant une guilde déjà existante!",
 | 
				
			||||||
 | 
					  "members": "membre(s)",
 | 
				
			||||||
 | 
					  "search": "Rechercher",
 | 
				
			||||||
 | 
					  "write-a-message": "Écrire un message..."
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,7 @@
 | 
				
			|||||||
import { Type } from '@sinclair/typebox'
 | 
					import { Type, Static } from '@sinclair/typebox'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { date, id } from './utils'
 | 
					import { date, id } from './utils'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const types = [Type.Literal('text')]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const channelSchema = {
 | 
					export const channelSchema = {
 | 
				
			||||||
  id,
 | 
					  id,
 | 
				
			||||||
  name: Type.String({ minLength: 1, maxLength: 20 }),
 | 
					  name: Type.String({ minLength: 1, maxLength: 20 }),
 | 
				
			||||||
@@ -11,3 +9,5 @@ export const channelSchema = {
 | 
				
			|||||||
  updatedAt: date.updatedAt,
 | 
					  updatedAt: date.updatedAt,
 | 
				
			||||||
  guildId: id
 | 
					  guildId: id
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					const channelObjectSchema = Type.Object(channelSchema)
 | 
				
			||||||
 | 
					export type Channel = Static<typeof channelObjectSchema>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,20 +12,30 @@ export const guildSchema = {
 | 
				
			|||||||
  createdAt: date.createdAt,
 | 
					  createdAt: date.createdAt,
 | 
				
			||||||
  updatedAt: date.updatedAt
 | 
					  updatedAt: date.updatedAt
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					export const guildObjectSchema = Type.Object(guildSchema)
 | 
				
			||||||
 | 
					export type Guild = Static<typeof guildObjectSchema>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const guildWithDefaultChannelIdSchema = {
 | 
				
			||||||
 | 
					  ...guildSchema,
 | 
				
			||||||
 | 
					  defaultChannelId: id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export const guildWithDefaultChannelObjectSchema = Type.Object(
 | 
				
			||||||
 | 
					  guildWithDefaultChannelIdSchema
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					export type GuildWithDefaultChannelId = Static<
 | 
				
			||||||
 | 
					  typeof guildWithDefaultChannelObjectSchema
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const guildCompleteSchema = {
 | 
					export const guildCompleteSchema = {
 | 
				
			||||||
  ...guildSchema,
 | 
					  ...guildSchema,
 | 
				
			||||||
  channels: Type.Array(Type.Object(channelSchema)),
 | 
					  channels: Type.Array(Type.Object(channelSchema)),
 | 
				
			||||||
  members: Type.Array(Type.Object(memberSchema))
 | 
					  members: Type.Array(Type.Object(memberSchema))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					export const guildCompleteObjectSchema = Type.Object(guildCompleteSchema)
 | 
				
			||||||
 | 
					export type GuildComplete = Static<typeof guildCompleteObjectSchema>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const guildPublicObjectSchema = Type.Object({
 | 
					export const guildPublicObjectSchema = Type.Object({
 | 
				
			||||||
  ...guildSchema,
 | 
					  ...guildSchema,
 | 
				
			||||||
  membersCount: Type.Integer({ min: 1 })
 | 
					  membersCount: Type.Integer({ min: 1 })
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					 | 
				
			||||||
export const guildCompleteObjectSchema = Type.Object(guildCompleteSchema)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type GuildComplete = Static<typeof guildCompleteObjectSchema>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type GuildPublic = Static<typeof guildPublicObjectSchema>
 | 
					export type GuildPublic = Static<typeof guildPublicObjectSchema>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import { Type } from '@sinclair/typebox'
 | 
					import { Type, Static } from '@sinclair/typebox'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { date, id } from './utils'
 | 
					import { date, id } from './utils'
 | 
				
			||||||
 | 
					import { UserPublicWithoutSettings } from './User'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const memberSchema = {
 | 
					export const memberSchema = {
 | 
				
			||||||
  id,
 | 
					  id,
 | 
				
			||||||
@@ -10,3 +11,9 @@ export const memberSchema = {
 | 
				
			|||||||
  userId: id,
 | 
					  userId: id,
 | 
				
			||||||
  guildId: id
 | 
					  guildId: id
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					const memberObjectSchema = Type.Object(memberSchema)
 | 
				
			||||||
 | 
					export type Member = Static<typeof memberObjectSchema>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface MemberWithPublicUser extends Member {
 | 
				
			||||||
 | 
					  user: UserPublicWithoutSettings
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import { Type } from '@sinclair/typebox'
 | 
					import { Type, Static } from '@sinclair/typebox'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { date, id } from './utils'
 | 
					import { date, id } from './utils'
 | 
				
			||||||
 | 
					import { MemberWithPublicUser } from './Member'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const types = [Type.Literal('text'), Type.Literal('file')]
 | 
					export const types = [Type.Literal('text'), Type.Literal('file')]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -21,3 +22,9 @@ export const messageSchema = {
 | 
				
			|||||||
  memberId: id,
 | 
					  memberId: id,
 | 
				
			||||||
  channelId: id
 | 
					  channelId: id
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					const messageObjectSchema = Type.Object(messageSchema)
 | 
				
			||||||
 | 
					export type Message = Static<typeof messageObjectSchema>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface MessageWithMember extends Message {
 | 
				
			||||||
 | 
					  member: MemberWithPublicUser
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,6 +50,9 @@ export const userPublicWithoutSettingsSchema = {
 | 
				
			|||||||
  createdAt: date.createdAt,
 | 
					  createdAt: date.createdAt,
 | 
				
			||||||
  updatedAt: date.updatedAt
 | 
					  updatedAt: date.updatedAt
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					export const userPublicWithoutSettingsObjectSchema = Type.Object(
 | 
				
			||||||
 | 
					  userPublicWithoutSettingsSchema
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const userPublicSchema = {
 | 
					export const userPublicSchema = {
 | 
				
			||||||
  ...userPublicWithoutSettingsSchema,
 | 
					  ...userPublicWithoutSettingsSchema,
 | 
				
			||||||
@@ -66,4 +69,7 @@ export const userCurrentSchema = Type.Object({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export type User = Static<typeof userObjectSchema>
 | 
					export type User = Static<typeof userObjectSchema>
 | 
				
			||||||
export type UserPublic = Static<typeof userPublicObjectSchema>
 | 
					export type UserPublic = Static<typeof userPublicObjectSchema>
 | 
				
			||||||
 | 
					export type UserPublicWithoutSettings = Static<
 | 
				
			||||||
 | 
					  typeof userPublicWithoutSettingsObjectSchema
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
export type UserCurrent = Static<typeof userCurrentSchema>
 | 
					export type UserCurrent = Static<typeof userCurrentSchema>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@ const nextPWA = require('next-pwa')
 | 
				
			|||||||
const nextTranslate = require('next-translate')
 | 
					const nextTranslate = require('next-translate')
 | 
				
			||||||
const { createSecureHeaders } = require('next-secure-headers')
 | 
					const { createSecureHeaders } = require('next-secure-headers')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** @type {import("next").NextConfig} */
 | 
				
			||||||
module.exports = nextTranslate(
 | 
					module.exports = nextTranslate(
 | 
				
			||||||
  nextPWA({
 | 
					  nextPWA({
 | 
				
			||||||
    images: {
 | 
					    images: {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										5658
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5658
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										21
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								package.json
									
									
									
									
									
								
							@@ -43,6 +43,7 @@
 | 
				
			|||||||
    "axios": "0.24.0",
 | 
					    "axios": "0.24.0",
 | 
				
			||||||
    "classnames": "2.3.1",
 | 
					    "classnames": "2.3.1",
 | 
				
			||||||
    "date-and-time": "2.0.1",
 | 
					    "date-and-time": "2.0.1",
 | 
				
			||||||
 | 
					    "emoji-mart": "3.0.1",
 | 
				
			||||||
    "next": "12.0.7",
 | 
					    "next": "12.0.7",
 | 
				
			||||||
    "next-pwa": "5.4.4",
 | 
					    "next-pwa": "5.4.4",
 | 
				
			||||||
    "next-themes": "0.0.15",
 | 
					    "next-themes": "0.0.15",
 | 
				
			||||||
@@ -51,12 +52,17 @@
 | 
				
			|||||||
    "react-component-form": "2.0.0",
 | 
					    "react-component-form": "2.0.0",
 | 
				
			||||||
    "react-dom": "17.0.2",
 | 
					    "react-dom": "17.0.2",
 | 
				
			||||||
    "react-infinite-scroll-component": "6.1.0",
 | 
					    "react-infinite-scroll-component": "6.1.0",
 | 
				
			||||||
 | 
					    "react-markdown": "7.1.1",
 | 
				
			||||||
    "react-responsive": "8.2.0",
 | 
					    "react-responsive": "8.2.0",
 | 
				
			||||||
    "react-swipeable": "6.2.0",
 | 
					    "react-swipeable": "6.2.0",
 | 
				
			||||||
    "react-textarea-autosize": "8.3.3",
 | 
					    "react-textarea-autosize": "8.3.3",
 | 
				
			||||||
    "read-pkg": "7.0.0",
 | 
					    "read-pkg": "7.0.0",
 | 
				
			||||||
 | 
					    "remark-breaks": "3.0.2",
 | 
				
			||||||
 | 
					    "remark-gfm": "3.0.1",
 | 
				
			||||||
    "sharp": "0.29.3",
 | 
					    "sharp": "0.29.3",
 | 
				
			||||||
    "socket.io-client": "4.4.0",
 | 
					    "socket.io-client": "4.4.0",
 | 
				
			||||||
 | 
					    "unified": "10.1.1",
 | 
				
			||||||
 | 
					    "unist-util-visit": "4.1.0",
 | 
				
			||||||
    "universal-cookie": "4.0.4"
 | 
					    "universal-cookie": "4.0.4"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
@@ -73,17 +79,20 @@
 | 
				
			|||||||
    "@testing-library/jest-dom": "5.16.1",
 | 
					    "@testing-library/jest-dom": "5.16.1",
 | 
				
			||||||
    "@testing-library/react": "12.1.2",
 | 
					    "@testing-library/react": "12.1.2",
 | 
				
			||||||
    "@types/date-and-time": "0.13.0",
 | 
					    "@types/date-and-time": "0.13.0",
 | 
				
			||||||
    "@types/jest": "27.0.3",
 | 
					    "@types/emoji-mart": "3.0.9",
 | 
				
			||||||
 | 
					    "@types/hast": "2.3.4",
 | 
				
			||||||
 | 
					    "@types/jest": "27.4.0",
 | 
				
			||||||
    "@types/node": "17.0.5",
 | 
					    "@types/node": "17.0.5",
 | 
				
			||||||
    "@types/react": "17.0.38",
 | 
					    "@types/react": "17.0.38",
 | 
				
			||||||
    "@types/react-responsive": "8.0.5",
 | 
					    "@types/react-responsive": "8.0.5",
 | 
				
			||||||
 | 
					    "@types/unist": "2.0.6",
 | 
				
			||||||
    "@typescript-eslint/eslint-plugin": "4.33.0",
 | 
					    "@typescript-eslint/eslint-plugin": "4.33.0",
 | 
				
			||||||
    "autoprefixer": "10.4.0",
 | 
					    "autoprefixer": "10.4.1",
 | 
				
			||||||
    "cypress": "9.2.0",
 | 
					    "cypress": "9.2.0",
 | 
				
			||||||
    "dockerfilelint": "1.8.0",
 | 
					    "dockerfilelint": "1.8.0",
 | 
				
			||||||
    "editorconfig-checker": "4.0.2",
 | 
					    "editorconfig-checker": "4.0.2",
 | 
				
			||||||
    "eslint": "7.32.0",
 | 
					    "eslint": "7.32.0",
 | 
				
			||||||
    "eslint-config-next": "11.1.2",
 | 
					    "eslint-config-next": "12.0.7",
 | 
				
			||||||
    "eslint-config-prettier": "8.3.0",
 | 
					    "eslint-config-prettier": "8.3.0",
 | 
				
			||||||
    "eslint-config-standard-with-typescript": "21.0.1",
 | 
					    "eslint-config-standard-with-typescript": "21.0.1",
 | 
				
			||||||
    "eslint-plugin-import": "2.25.3",
 | 
					    "eslint-plugin-import": "2.25.3",
 | 
				
			||||||
@@ -91,7 +100,7 @@
 | 
				
			|||||||
    "eslint-plugin-prettier": "4.0.0",
 | 
					    "eslint-plugin-prettier": "4.0.0",
 | 
				
			||||||
    "eslint-plugin-promise": "5.1.1",
 | 
					    "eslint-plugin-promise": "5.1.1",
 | 
				
			||||||
    "eslint-plugin-storybook": "0.5.5",
 | 
					    "eslint-plugin-storybook": "0.5.5",
 | 
				
			||||||
    "eslint-plugin-unicorn": "39.0.0",
 | 
					    "eslint-plugin-unicorn": "40.0.0",
 | 
				
			||||||
    "husky": "7.0.4",
 | 
					    "husky": "7.0.4",
 | 
				
			||||||
    "jest": "27.4.5",
 | 
					    "jest": "27.4.5",
 | 
				
			||||||
    "lint-staged": "12.1.4",
 | 
					    "lint-staged": "12.1.4",
 | 
				
			||||||
@@ -105,8 +114,8 @@
 | 
				
			|||||||
    "serve": "13.0.2",
 | 
					    "serve": "13.0.2",
 | 
				
			||||||
    "start-server-and-test": "1.14.0",
 | 
					    "start-server-and-test": "1.14.0",
 | 
				
			||||||
    "storybook-tailwind-dark-mode": "1.0.11",
 | 
					    "storybook-tailwind-dark-mode": "1.0.11",
 | 
				
			||||||
    "tailwindcss": "3.0.7",
 | 
					    "tailwindcss": "3.0.8",
 | 
				
			||||||
    "typescript": "4.5.4",
 | 
					    "typescript": "4.4.4",
 | 
				
			||||||
    "vercel": "23.1.2",
 | 
					    "vercel": "23.1.2",
 | 
				
			||||||
    "webpack": "5.65.0"
 | 
					    "webpack": "5.65.0"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user