feat(pages): add /application/guilds/join (#2)

This commit is contained in:
Divlo
2021-11-13 21:50:34 +01:00
committed by GitHub
parent d8fab08585
commit accd36d1fc
23 changed files with 4767 additions and 5393 deletions

View File

@ -240,6 +240,7 @@ export const Application: React.FC<ApplicationProps> = (props) => {
</Sidebar>
<div
id='application-page-content'
className={classNames(
'top-0 h-full-without-header flex flex-col flex-1 z-0 overflow-y-auto transition',
{

View File

@ -16,7 +16,7 @@ export const CreateGuild: React.FC = () => {
const { t } = useTranslation()
const router = useRouter()
const { formState, message, errors, getErrorTranslation, handleSubmit } =
const { fetchState, message, errors, getErrorTranslation, handleSubmit } =
useForm({
validateSchemaObject: {
name: guildSchema.name,
@ -76,7 +76,7 @@ export const CreateGuild: React.FC = () => {
{t('application:create')}
</Button>
</Form>
<FormState id='message' state={formState} message={message} />
<FormState id='message' state={fetchState} message={message} />
</Main>
)
}

View File

@ -0,0 +1,25 @@
import { Meta, Story } from '@storybook/react'
import { Guild as Component, GuildProps } from './Guild'
const Stories: Meta = {
title: 'Guild',
component: Component
}
export default Stories
export const Guild: Story<GuildProps> = (arguments_) => {
return <Component {...arguments_} />
}
Guild.args = {
guild: {
id: 1,
name: 'GuildExample',
description: 'guild example.',
icon: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
membersCount: 1
}
}

View File

@ -0,0 +1,22 @@
import { render } from '@testing-library/react'
import { Guild } from './Guild'
describe('<Guild />', () => {
it('should render successfully', () => {
const { baseElement } = render(
<Guild
guild={{
id: 1,
name: 'GuildExample',
description: 'guild example.',
icon: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
membersCount: 1
}}
/>
)
expect(baseElement).toBeTruthy()
})
})

View File

@ -0,0 +1,35 @@
import Image from 'next/image'
import { GuildPublic } from 'models/Guild'
export interface GuildProps {
guild: GuildPublic
}
export const Guild: React.FC<GuildProps> = (props) => {
const { guild } = props
return (
<div
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
className='rounded-full'
src={guild.icon != null ? guild.icon : '/images/data/guild-default.png'}
alt='logo'
width={80}
height={80}
/>
<div className='m-2 text-center mt-3'>
<h3 data-cy='guild-name' className='font-bold text-xl mb-2'>
{guild.name}
</h3>
<p className='text-base w-11/12 mx-auto'>{guild.description}</p>
</div>
<p className='flex flex-col text-green-800 dark:text-green-400 mt-4'>
{guild.membersCount} members
</p>
</div>
)
}

View File

@ -0,0 +1 @@
export * from './Guild'

View File

@ -0,0 +1,75 @@
import { useCallback, useEffect, useState, useRef } from 'react'
import InfiniteScroll from 'react-infinite-scroll-component'
import { useAuthentication } from 'utils/authentication'
import { GuildPublic } from 'models/Guild'
import { Loader } from 'components/design/Loader'
import { useFetchState } from 'hooks/useFetchState'
import { Guild } from './Guild'
export const JoinGuildsPublic: React.FC = () => {
const [guilds, setGuilds] = useState<GuildPublic[]>([])
const [hasMore, setHasMore] = useState(true)
const [inputSearch, setInputSearch] = useState('')
const [fetchState, setFetchState] = useFetchState('idle')
const afterId = useRef<number | null>(null)
const { authentication } = useAuthentication()
const fetchGuilds = useCallback(async (): Promise<void> => {
if (fetchState !== 'idle') {
return
}
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(() => {
afterId.current = null
setGuilds([])
fetchGuilds().catch((error) => {
console.error(error)
})
}, [inputSearch]) // eslint-disable-line react-hooks/exhaustive-deps
const handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
setInputSearch(event.target.value)
}
return (
<>
<input
data-cy='search-guild-input'
onChange={handleChange}
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'
name='search-guild'
placeholder='🔎 Search...'
/>
<div className='w-full flex items-center justify-center p-12'>
<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'
dataLength={guilds.length}
next={fetchGuilds}
scrollableTarget='application-page-content'
hasMore={hasMore}
loader={<Loader />}
>
{guilds.map((guild) => {
return <Guild guild={guild} key={guild.id} />
})}
</InfiniteScroll>
</div>
</>
)
}

View File

@ -0,0 +1 @@
export * from './JoinGuildsPublic'

View File

@ -29,7 +29,7 @@ export const Authentication: React.FC<AuthenticationProps> = (props) => {
const { lang, t } = useTranslation()
const { theme } = useTheme()
const { errors, formState, message, getErrorTranslation, handleSubmit } =
const { errors, fetchState, message, getErrorTranslation, handleSubmit } =
useForm({
validateSchemaObject: {
...(mode === 'signup' && { name: userSchema.name }),
@ -139,7 +139,7 @@ export const Authentication: React.FC<AuthenticationProps> = (props) => {
</Link>
</p>
</AuthenticationForm>
<FormState id='message' state={formState} message={message} />
<FormState id='message' state={fetchState} message={message} />
</Main>
)
}

View File

@ -1,7 +1,7 @@
import classNames from 'classnames'
import useTranslation from 'next-translate/useTranslation'
import { FormState as FormStateType } from 'hooks/useFormState'
import { FetchState as FormStateType } from 'hooks/useFetchState'
import { Loader } from '../Loader'
export interface FormStateProps {