feat: design applications and first api calls
Co-authored-by: Walid <87608619+WalidKorchi@users.noreply.github.com>
This commit is contained in:
257
components/Application/Application.tsx
Normal file
257
components/Application/Application.tsx
Normal file
@ -0,0 +1,257 @@
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
import Image from 'next/image'
|
||||
import {
|
||||
CogIcon,
|
||||
PlusIcon,
|
||||
MenuIcon,
|
||||
UsersIcon,
|
||||
XIcon
|
||||
} from '@heroicons/react/solid'
|
||||
import classNames from 'classnames'
|
||||
import { useMediaQuery } from 'react-responsive'
|
||||
import { useSwipeable } from 'react-swipeable'
|
||||
|
||||
import { Sidebar, DirectionSidebar } from './Sidebar'
|
||||
import { IconButton } from 'components/design/IconButton'
|
||||
import { IconLink } from 'components/design/IconLink'
|
||||
import { Channels } from './Channels'
|
||||
import { Guilds } from './Guilds/Guilds'
|
||||
import { Divider } from '../design/Divider'
|
||||
import { Members } from './Members'
|
||||
import { useAuthentication } from 'utils/authentication'
|
||||
|
||||
export interface GuildsChannelsPath {
|
||||
guildId: number
|
||||
channelId: number
|
||||
}
|
||||
|
||||
export interface ApplicationProps {
|
||||
path:
|
||||
| '/application'
|
||||
| '/application/guilds/join'
|
||||
| '/application/guilds/create'
|
||||
| '/application/users/[userId]'
|
||||
| GuildsChannelsPath
|
||||
}
|
||||
|
||||
export const Application: React.FC<ApplicationProps> = (props) => {
|
||||
const { children, path } = props
|
||||
|
||||
const { user } = useAuthentication()
|
||||
|
||||
const [visibleSidebars, setVisibleSidebars] = useState({
|
||||
left: true,
|
||||
right: false
|
||||
})
|
||||
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
const isMobile = useMediaQuery({
|
||||
query: '(max-width: 900px)'
|
||||
})
|
||||
|
||||
const handleToggleSidebars = (direction: DirectionSidebar): void => {
|
||||
if (!isMobile) {
|
||||
if (direction === 'left') {
|
||||
return setVisibleSidebars({
|
||||
...visibleSidebars,
|
||||
left: !visibleSidebars.left
|
||||
})
|
||||
}
|
||||
if (direction === 'right') {
|
||||
return setVisibleSidebars({
|
||||
...visibleSidebars,
|
||||
right: !visibleSidebars.right
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (direction === 'right' && visibleSidebars.left) {
|
||||
return setVisibleSidebars({
|
||||
left: false,
|
||||
right: true
|
||||
})
|
||||
}
|
||||
if (direction === 'left' && visibleSidebars.right) {
|
||||
return setVisibleSidebars({
|
||||
left: true,
|
||||
right: false
|
||||
})
|
||||
}
|
||||
if (direction === 'left' && !visibleSidebars.right) {
|
||||
return setVisibleSidebars({
|
||||
...visibleSidebars,
|
||||
left: !visibleSidebars.left
|
||||
})
|
||||
}
|
||||
if (direction === 'right' && !visibleSidebars.left) {
|
||||
return setVisibleSidebars({
|
||||
...visibleSidebars,
|
||||
right: !visibleSidebars.right
|
||||
})
|
||||
}
|
||||
}
|
||||
handleCloseSidebars()
|
||||
}
|
||||
|
||||
const handleCloseSidebars = (): void => {
|
||||
if (isMobile && (visibleSidebars.left || visibleSidebars.right)) {
|
||||
setVisibleSidebars({
|
||||
left: false,
|
||||
right: false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const swipeableHandlers = useSwipeable({
|
||||
trackMouse: false,
|
||||
trackTouch: true,
|
||||
preventDefaultTouchmoveEvent: true,
|
||||
onSwipedRight: () => {
|
||||
if (visibleSidebars.right) {
|
||||
return setVisibleSidebars({ ...visibleSidebars, right: false })
|
||||
}
|
||||
setVisibleSidebars({
|
||||
...visibleSidebars,
|
||||
left: true
|
||||
})
|
||||
},
|
||||
onSwipedLeft: () => {
|
||||
if (visibleSidebars.left) {
|
||||
return setVisibleSidebars({ ...visibleSidebars, left: false })
|
||||
}
|
||||
setVisibleSidebars({
|
||||
...visibleSidebars,
|
||||
right: true
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
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 'Create a Guild'
|
||||
}
|
||||
return 'Application'
|
||||
}, [path])
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
if (!mounted) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className='flex bg-gray-200 dark:bg-gray-800 h-16 px-2 py-3 justify-between items-center shadow-lg z-50'>
|
||||
<IconButton
|
||||
className='p-2 h-10 w-10'
|
||||
onClick={() => handleToggleSidebars('left')}
|
||||
>
|
||||
{!visibleSidebars.left ? <MenuIcon /> : <XIcon />}
|
||||
</IconButton>
|
||||
<div className='text-md text-green-800 dark:text-green-400 font-semibold'>
|
||||
{title}
|
||||
</div>
|
||||
<div className='flex space-x-2'>
|
||||
{title.startsWith('#') && (
|
||||
<IconButton
|
||||
className='p-2 h-10 w-10'
|
||||
onClick={() => handleToggleSidebars('right')}
|
||||
>
|
||||
{!visibleSidebars.right ? <UsersIcon /> : <XIcon />}
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main
|
||||
className='relative flex h-full-without-header overflow-hidden'
|
||||
onClick={handleCloseSidebars}
|
||||
{...swipeableHandlers}
|
||||
>
|
||||
<Sidebar
|
||||
direction='left'
|
||||
visible={visibleSidebars.left}
|
||||
isMobile={isMobile}
|
||||
>
|
||||
<div className='flex flex-col min-w-[92px] top-0 left-0 z-50 bg-gray-200 dark:bg-gray-800 border-r-2 border-gray-500 dark:border-white/20 py-2 space-y-2'>
|
||||
<IconLink
|
||||
href={`/application/users/${user.id}`}
|
||||
selected={path === `/application/users/${user.id}`}
|
||||
title='Settings'
|
||||
>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src='/images/data/divlo.png'
|
||||
alt='logo'
|
||||
width={48}
|
||||
height={48}
|
||||
/>
|
||||
</IconLink>
|
||||
<IconLink
|
||||
href='/application'
|
||||
selected={path === '/application'}
|
||||
title='Join or create a Guild'
|
||||
>
|
||||
<PlusIcon className='w-12 h-12 text-green-800 dark:text-green-400' />
|
||||
</IconLink>
|
||||
<Divider />
|
||||
<Guilds path={path} />
|
||||
</div>
|
||||
|
||||
{typeof path !== 'string' && (
|
||||
<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>
|
||||
|
||||
<div
|
||||
className={classNames(
|
||||
'top-0 h-full-without-header flex flex-col flex-1 z-0 overflow-y-auto transition',
|
||||
{
|
||||
'absolute opacity-20':
|
||||
isMobile && (visibleSidebars.left || visibleSidebars.right)
|
||||
}
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
<Sidebar
|
||||
direction='right'
|
||||
visible={visibleSidebars.right}
|
||||
isMobile={isMobile}
|
||||
>
|
||||
<Members />
|
||||
</Sidebar>
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
}
|
15
components/Application/Channels/Channels.stories.tsx
Normal file
15
components/Application/Channels/Channels.stories.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
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 } }
|
12
components/Application/Channels/Channels.test.tsx
Normal file
12
components/Application/Channels/Channels.test.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
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()
|
||||
})
|
||||
})
|
36
components/Application/Channels/Channels.tsx
Normal file
36
components/Application/Channels/Channels.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import Link from 'next/link'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { GuildsChannelsPath } from '../Application'
|
||||
|
||||
export interface ChannelsProps {
|
||||
path: GuildsChannelsPath
|
||||
}
|
||||
|
||||
export const Channels: React.FC<ChannelsProps> = (props) => {
|
||||
const { path } = props
|
||||
|
||||
return (
|
||||
<nav className='w-full'>
|
||||
{new Array(100).fill(null).map((_, index) => {
|
||||
return (
|
||||
<Link key={index} href={`/application/${path.guildId}/${index}`}>
|
||||
<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':
|
||||
typeof path !== 'string' && path.channelId === index,
|
||||
'text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-white font-normal':
|
||||
typeof path === 'string'
|
||||
}
|
||||
)}
|
||||
>
|
||||
<span className='ml-2 mr-4'># Channel {index}</span>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
)
|
||||
}
|
1
components/Application/Channels/index.ts
Normal file
1
components/Application/Channels/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Channels'
|
15
components/Application/Guilds/Guilds.stories.tsx
Normal file
15
components/Application/Guilds/Guilds.stories.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
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 } }
|
12
components/Application/Guilds/Guilds.test.tsx
Normal file
12
components/Application/Guilds/Guilds.test.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
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()
|
||||
})
|
||||
})
|
34
components/Application/Guilds/Guilds.tsx
Normal file
34
components/Application/Guilds/Guilds.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import Image from 'next/image'
|
||||
|
||||
import { ApplicationProps } from '../Application'
|
||||
import { IconLink } from '../../design/IconLink'
|
||||
|
||||
export interface GuildsProps extends ApplicationProps {}
|
||||
|
||||
export const Guilds: React.FC<GuildsProps> = (props) => {
|
||||
const { path } = props
|
||||
|
||||
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'>
|
||||
{new Array(100).fill(null).map((_, index) => {
|
||||
return (
|
||||
<IconLink
|
||||
key={index}
|
||||
href={`/application/${index}/0`}
|
||||
selected={typeof path !== 'string' && path.guildId === index}
|
||||
title='Guild Name'
|
||||
>
|
||||
<div className='pl-[6px]'>
|
||||
<Image
|
||||
src='/images/icons/Thream.png'
|
||||
alt='logo'
|
||||
width={48}
|
||||
height={48}
|
||||
/>
|
||||
</div>
|
||||
</IconLink>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
1
components/Application/Guilds/index.ts
Normal file
1
components/Application/Guilds/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Guilds'
|
14
components/Application/Members/Members.stories.tsx
Normal file
14
components/Application/Members/Members.stories.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { Meta, Story } from '@storybook/react'
|
||||
|
||||
import { Members as Component } from './Members'
|
||||
|
||||
const Stories: Meta = {
|
||||
title: 'Members',
|
||||
component: Component
|
||||
}
|
||||
|
||||
export default Stories
|
||||
|
||||
export const Members: Story = (arguments_) => {
|
||||
return <Component {...arguments_} />
|
||||
}
|
10
components/Application/Members/Members.test.tsx
Normal file
10
components/Application/Members/Members.test.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { render } from '@testing-library/react'
|
||||
|
||||
import { Members } from './Members'
|
||||
|
||||
describe('<Members />', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(<Members />)
|
||||
expect(baseElement).toBeTruthy()
|
||||
})
|
||||
})
|
57
components/Application/Members/Members.tsx
Normal file
57
components/Application/Members/Members.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import Image from 'next/image'
|
||||
|
||||
import { Divider } from '../../design/Divider'
|
||||
|
||||
export const Members: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<div className='mb-2'>
|
||||
<h1 className='text-center pt-2 my-2 text-xl'>Members</h1>
|
||||
<Divider />
|
||||
</div>
|
||||
<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 border-2 border-green-500'>
|
||||
<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'>
|
||||
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>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
1
components/Application/Members/index.ts
Normal file
1
components/Application/Members/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Members'
|
12
components/Application/Messages/Messages.stories.tsx
Normal file
12
components/Application/Messages/Messages.stories.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
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_} />
|
10
components/Application/Messages/Messages.test.tsx
Normal file
10
components/Application/Messages/Messages.test.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { render } from '@testing-library/react'
|
||||
|
||||
import { Messages } from './'
|
||||
|
||||
describe('<Messages />', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(<Messages />)
|
||||
expect(baseElement).toBeTruthy()
|
||||
})
|
||||
})
|
82
components/Application/Messages/Messages.tsx
Normal file
82
components/Application/Messages/Messages.tsx
Normal file
@ -0,0 +1,82 @@
|
||||
import Image from 'next/image'
|
||||
import TextareaAutosize from 'react-textarea-autosize'
|
||||
|
||||
export const Messages: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<div className='w-full scrollbar-firefox-support overflow-y-auto transition-all'>
|
||||
{new Array(20).fill(null).map((_, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className='p-4 flex transition hover:bg-gray-200 dark:hover:bg-gray-900'
|
||||
>
|
||||
<div className='w-12 h-12 mr-4 flex flex-shrink-0 items-center justify-center'>
|
||||
<div className='w-10 h-10 drop-shadow-md'>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src='/images/data/divlo.png'
|
||||
alt='logo'
|
||||
width={50}
|
||||
height={50}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='w-full'>
|
||||
<div className='w-max flex items-center'>
|
||||
<span className='font-bold text-gray-900 dark:text-gray-200'>
|
||||
Divlo
|
||||
</span>
|
||||
<span className='text-gray-500 dark:text-gray-200 text-xs ml-4 select-none'>
|
||||
06/04/2021 - 22:28:40
|
||||
</span>
|
||||
</div>
|
||||
<div className='text-gray-800 dark:text-gray-300 font-paragraph mt-1 break-words'>
|
||||
<p>Message {index}</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit, amet consectetur adipisicing elit.
|
||||
Eum debitis voluptatum itaque quaerat. Nemo optio voluptas
|
||||
quas mollitia rerum commodi laboriosam voluptates et sit
|
||||
quo. Repudiandae eius at inventore magnam. Voluptas nisi
|
||||
maxime laborum architecto fuga a consequuntur reiciendis
|
||||
rerum beatae hic possimus, omnis dolorum libero, illo
|
||||
dolorem assumenda. Repellat, ad!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div className='p-6 pb-4'>
|
||||
<div className='w-full h-full py-1 flex rounded-lg bg-gray-200 dark:bg-gray-800 text-gray-600 dark:text-gray-200'>
|
||||
<form className='w-full h-full flex items-center'>
|
||||
<TextareaAutosize
|
||||
className='w-full scrollbar-firefox-support p-2 px-6 my-2 bg-transparent outline-none font-paragraph tracking-wide resize-none'
|
||||
placeholder='Write a message...'
|
||||
wrap='soft'
|
||||
maxRows={6}
|
||||
/>
|
||||
</form>
|
||||
<div className='h-full flex items-center justify-around pr-6'>
|
||||
<button className='w-full h-full flex items-center justify-center p-1 text-2xl transition hover:-translate-y-1'>
|
||||
🙂
|
||||
</button>
|
||||
<button className='relative w-full h-full flex items-center justify-center p-1 text-green-800 dark:text-green-400 transition hover:-translate-y-1'>
|
||||
<input
|
||||
type='file'
|
||||
className='absolute w-full h-full opacity-0 cursor-pointer'
|
||||
/>
|
||||
<svg width='25' height='25' viewBox='0 0 22 22'>
|
||||
<path
|
||||
d='M11 0C4.925 0 0 4.925 0 11C0 17.075 4.925 22 11 22C17.075 22 22 17.075 22 11C22 4.925 17.075 0 11 0ZM12 15C12 15.2652 11.8946 15.5196 11.7071 15.7071C11.5196 15.8946 11.2652 16 11 16C10.7348 16 10.4804 15.8946 10.2929 15.7071C10.1054 15.5196 10 15.2652 10 15V12H7C6.73478 12 6.48043 11.8946 6.29289 11.7071C6.10536 11.5196 6 11.2652 6 11C6 10.7348 6.10536 10.4804 6.29289 10.2929C6.48043 10.1054 6.73478 10 7 10H10V7C10 6.73478 10.1054 6.48043 10.2929 6.29289C10.4804 6.10536 10.7348 6 11 6C11.2652 6 11.5196 6.10536 11.7071 6.29289C11.8946 6.48043 12 6.73478 12 7V10H15C15.2652 10 15.5196 10.1054 15.7071 10.2929C15.8946 10.4804 16 10.7348 16 11C16 11.2652 15.8946 11.5196 15.7071 11.7071C15.5196 11.8946 15.2652 12 15 12H12V15Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
1
components/Application/Messages/index.ts
Normal file
1
components/Application/Messages/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Messages'
|
15
components/Application/PopupGuild/PopupGuild.stories.tsx
Normal file
15
components/Application/PopupGuild/PopupGuild.stories.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { Meta, Story } from '@storybook/react'
|
||||
|
||||
import { PopupGuild as Component, PopupGuildProps } from './PopupGuild'
|
||||
|
||||
const Stories: Meta = {
|
||||
title: 'PopupGuild',
|
||||
component: Component
|
||||
}
|
||||
|
||||
export default Stories
|
||||
|
||||
export const PopupGuild: Story<PopupGuildProps> = (arguments_) => {
|
||||
return <Component {...arguments_} />
|
||||
}
|
||||
PopupGuild.args = {}
|
10
components/Application/PopupGuild/PopupGuild.test.tsx
Normal file
10
components/Application/PopupGuild/PopupGuild.test.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { render } from '@testing-library/react'
|
||||
|
||||
import { PopupGuild } from './PopupGuild'
|
||||
|
||||
describe('<PopupGuild />', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(<PopupGuild />)
|
||||
expect(baseElement).toBeTruthy()
|
||||
})
|
||||
})
|
56
components/Application/PopupGuild/PopupGuild.tsx
Normal file
56
components/Application/PopupGuild/PopupGuild.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import { PlusSmIcon, ArrowDownIcon } from '@heroicons/react/solid'
|
||||
import classNames from 'classnames'
|
||||
import Image from 'next/image'
|
||||
|
||||
import { PopupGuildCard } from './PopupGuildCard/PopupGuildCard'
|
||||
export interface PopupGuildProps {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const PopupGuild: React.FC<PopupGuildProps> = (props) => {
|
||||
const { className } = props
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
className,
|
||||
'flex p-8 flex-wrap justify-center items-center overflow-y-auto h-full-without-header min-w-full'
|
||||
)}
|
||||
>
|
||||
<PopupGuildCard
|
||||
image={
|
||||
<Image
|
||||
src='/images/svg/design/create-guild.svg'
|
||||
alt='Create a guild'
|
||||
draggable='false'
|
||||
width={230}
|
||||
height={230}
|
||||
/>
|
||||
}
|
||||
description='Create your own guild and manage everything within a few clicks !'
|
||||
link={{
|
||||
icon: <PlusSmIcon className='w-8 h-8 mr-2' />,
|
||||
text: 'Create a Guild',
|
||||
href: '/application/guilds/create'
|
||||
}}
|
||||
/>
|
||||
<PopupGuildCard
|
||||
image={
|
||||
<Image
|
||||
src='/images/svg/design/join-guild.svg'
|
||||
alt='Join a Guild'
|
||||
draggable='false'
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
}
|
||||
description='Talk, meet and have fun with new friends by joining any interesting guild !'
|
||||
link={{
|
||||
icon: <ArrowDownIcon className='w-6 h-6 mr-2' />,
|
||||
text: 'Join a Guild',
|
||||
href: '/application/guilds/join'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import { Meta, Story } from '@storybook/react'
|
||||
import { PlusSmIcon } from '@heroicons/react/solid'
|
||||
import Image from 'next/image'
|
||||
|
||||
import {
|
||||
PopupGuildCard as Component,
|
||||
PopupGuildCardProps
|
||||
} from './PopupGuildCard'
|
||||
|
||||
const Stories: Meta = {
|
||||
title: 'PopupGuildCard',
|
||||
component: Component
|
||||
}
|
||||
|
||||
export default Stories
|
||||
|
||||
export const PopupGuildCard: Story<PopupGuildCardProps> = (arguments_) => {
|
||||
return <Component {...arguments_} />
|
||||
}
|
||||
PopupGuildCard.args = {
|
||||
image: (
|
||||
<Image
|
||||
src='/images/svg/design/create-server.svg'
|
||||
alt=''
|
||||
width={230}
|
||||
height={230}
|
||||
/>
|
||||
),
|
||||
description:
|
||||
'Create your own guild and manage everything within a few clicks !',
|
||||
link: {
|
||||
icon: <PlusSmIcon className='w-8 h-8 mr-2' />,
|
||||
text: 'Create a server',
|
||||
href: '/application/guilds/create'
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import { render } from '@testing-library/react'
|
||||
import { PlusSmIcon } from '@heroicons/react/solid'
|
||||
import Image from 'next/image'
|
||||
|
||||
import { PopupGuildCard } from './PopupGuildCard'
|
||||
|
||||
describe('<PopupGuildCard />', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(
|
||||
<PopupGuildCard
|
||||
image={
|
||||
<Image
|
||||
src='/images/svg/design/create-server.svg'
|
||||
alt=''
|
||||
width={230}
|
||||
height={230}
|
||||
/>
|
||||
}
|
||||
description='Create your own guild and manage everything within a few clicks !'
|
||||
link={{
|
||||
icon: <PlusSmIcon className='w-8 h-8 mr-2' />,
|
||||
text: 'Create a server',
|
||||
href: '/application/guilds/create'
|
||||
}}
|
||||
/>
|
||||
)
|
||||
expect(baseElement).toBeTruthy()
|
||||
})
|
||||
})
|
@ -0,0 +1,32 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
export interface PopupGuildCardProps {
|
||||
image: JSX.Element
|
||||
description: string
|
||||
link: {
|
||||
href: string
|
||||
text: string
|
||||
icon: JSX.Element
|
||||
}
|
||||
}
|
||||
|
||||
export const PopupGuildCard: React.FC<PopupGuildCardProps> = (props) => {
|
||||
const { image, description, link } = props
|
||||
|
||||
return (
|
||||
<div className='w-80 h-96 m-8 rounded-2xl bg-gray-800'>
|
||||
<div className='flex justify-center items-center h-1/2 w-full'>
|
||||
{image}
|
||||
</div>
|
||||
<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>
|
||||
<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'>
|
||||
{link.icon}
|
||||
{link.text}
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -0,0 +1 @@
|
||||
export * from './PopupGuildCard'
|
1
components/Application/PopupGuild/index.ts
Normal file
1
components/Application/PopupGuild/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './PopupGuild'
|
14
components/Application/Sidebar/Sidebar.stories.tsx
Normal file
14
components/Application/Sidebar/Sidebar.stories.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { Meta, Story } from '@storybook/react'
|
||||
|
||||
import { Sidebar as Component, SidebarProps } from './Sidebar'
|
||||
|
||||
const Stories: Meta = {
|
||||
title: 'Sidebar',
|
||||
component: Component
|
||||
}
|
||||
|
||||
export default Stories
|
||||
|
||||
export const Sidebar: Story<SidebarProps> = (arguments_) => {
|
||||
return <Component {...arguments_} />
|
||||
}
|
12
components/Application/Sidebar/Sidebar.test.tsx
Normal file
12
components/Application/Sidebar/Sidebar.test.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { render } from '@testing-library/react'
|
||||
|
||||
import { Sidebar } from './Sidebar'
|
||||
|
||||
describe('<Sidebar />', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(
|
||||
<Sidebar direction='left' visible={true} isMobile={false} />
|
||||
)
|
||||
expect(baseElement).toBeTruthy()
|
||||
})
|
||||
})
|
36
components/Application/Sidebar/Sidebar.tsx
Normal file
36
components/Application/Sidebar/Sidebar.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { ApplicationProps } from '..'
|
||||
|
||||
export type DirectionSidebar = 'left' | 'right'
|
||||
|
||||
export interface SidebarProps {
|
||||
direction: DirectionSidebar
|
||||
visible: boolean
|
||||
path?: ApplicationProps
|
||||
isMobile: boolean
|
||||
}
|
||||
|
||||
export const Sidebar: React.FC<SidebarProps> = (props) => {
|
||||
const { direction, visible, children, path, isMobile } = props
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={classNames(
|
||||
'h-full-without-header flex z-50 drop-shadow-2xl bg-gray-200 dark:bg-gray-800 transition-all',
|
||||
{
|
||||
'top-0 right-0 scrollbar-firefox-support overflow-y-auto flex-col space-y-1':
|
||||
direction === 'right',
|
||||
'w-64': direction === 'right' && visible,
|
||||
'w-0 opacity-0': !visible,
|
||||
'w-80': direction === 'left' && visible,
|
||||
'max-w-max': typeof path !== 'string' && direction === 'left',
|
||||
'top-0 right-0': direction === 'right' && isMobile,
|
||||
absolute: isMobile
|
||||
}
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</nav>
|
||||
)
|
||||
}
|
1
components/Application/Sidebar/index.ts
Normal file
1
components/Application/Sidebar/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Sidebar'
|
1
components/Application/index.ts
Normal file
1
components/Application/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Application'
|
Reference in New Issue
Block a user