feat: create a guild (#1)
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
import Image from 'next/image'
|
||||
import useTranslation from 'next-translate/useTranslation'
|
||||
import {
|
||||
CogIcon,
|
||||
PlusIcon,
|
||||
@ -19,6 +20,7 @@ import { Guilds } from './Guilds/Guilds'
|
||||
import { Divider } from '../design/Divider'
|
||||
import { Members } from './Members'
|
||||
import { useAuthentication } from 'utils/authentication'
|
||||
import { API_URL } from 'utils/api'
|
||||
|
||||
export interface GuildsChannelsPath {
|
||||
guildId: number
|
||||
@ -37,6 +39,7 @@ export interface ApplicationProps {
|
||||
export const Application: React.FC<ApplicationProps> = (props) => {
|
||||
const { children, path } = props
|
||||
|
||||
const { t } = useTranslation()
|
||||
const { user } = useAuthentication()
|
||||
|
||||
const [visibleSidebars, setVisibleSidebars] = useState({
|
||||
@ -138,10 +141,10 @@ export const Application: React.FC<ApplicationProps> = (props) => {
|
||||
return 'Join a Guild'
|
||||
}
|
||||
if (path === '/application/guilds/create') {
|
||||
return 'Create a Guild'
|
||||
return t('application:create-a-guild')
|
||||
}
|
||||
return 'Application'
|
||||
}, [path])
|
||||
}, [path, t])
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
@ -193,7 +196,11 @@ export const Application: React.FC<ApplicationProps> = (props) => {
|
||||
>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src='/images/data/divlo.png'
|
||||
src={
|
||||
user.logo == null
|
||||
? '/images/data/user-default.png'
|
||||
: API_URL + user.logo
|
||||
}
|
||||
alt='logo'
|
||||
width={48}
|
||||
height={48}
|
||||
|
15
components/Application/CreateGuild/CreateGuild.stories.tsx
Normal file
15
components/Application/CreateGuild/CreateGuild.stories.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { Meta, Story } from '@storybook/react'
|
||||
|
||||
import { CreateGuild as Component } from './CreateGuild'
|
||||
|
||||
const Stories: Meta = {
|
||||
title: 'CreateGuild',
|
||||
component: Component
|
||||
}
|
||||
|
||||
export default Stories
|
||||
|
||||
export const CreateGuild: Story = (arguments_) => {
|
||||
return <Component {...arguments_} />
|
||||
}
|
||||
CreateGuild.args = {}
|
82
components/Application/CreateGuild/CreateGuild.tsx
Normal file
82
components/Application/CreateGuild/CreateGuild.tsx
Normal file
@ -0,0 +1,82 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import useTranslation from 'next-translate/useTranslation'
|
||||
import { Form } from 'react-component-form'
|
||||
import TextareaAutosize from 'react-textarea-autosize'
|
||||
import { AxiosResponse } from 'axios'
|
||||
|
||||
import { useAuthentication } from '../../../utils/authentication'
|
||||
import { HandleSubmitCallback, useForm } from '../../../hooks/useForm'
|
||||
import { GuildComplete, guildSchema } from '../../../models/Guild'
|
||||
import { Input } from '../../design/Input'
|
||||
import { Main } from '../../design/Main'
|
||||
import { Button } from '../../design/Button'
|
||||
import { FormState } from '../../design/FormState'
|
||||
|
||||
export const CreateGuild: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
|
||||
const { formState, message, errors, getErrorTranslation, handleSubmit } =
|
||||
useForm({
|
||||
validateSchemaObject: {
|
||||
name: guildSchema.name,
|
||||
description: guildSchema.description
|
||||
}
|
||||
})
|
||||
|
||||
const { authentication } = useAuthentication()
|
||||
|
||||
const onSubmit: HandleSubmitCallback = async (formData) => {
|
||||
try {
|
||||
const { data } = await authentication.api.post<
|
||||
any,
|
||||
AxiosResponse<{ guild: GuildComplete }>
|
||||
>('/guilds', { name: formData.name, description: formData.description })
|
||||
const guildId = data.guild.id
|
||||
const channelId = data.guild.channels[0].id
|
||||
await router.push(`/application/${guildId}/${channelId}`)
|
||||
return null
|
||||
} catch (error) {
|
||||
return {
|
||||
type: 'error',
|
||||
value: 'errors:server-error'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Main>
|
||||
<Form className='w-4/6 max-w-xs' onSubmit={handleSubmit(onSubmit)}>
|
||||
<Input
|
||||
type='text'
|
||||
placeholder={t('common:name')}
|
||||
name='name'
|
||||
label={t('common:name')}
|
||||
error={getErrorTranslation(errors.name)}
|
||||
/>
|
||||
|
||||
<div className='flex flex-col'>
|
||||
<div className='flex justify-between mt-6 mb-2'>
|
||||
<label className='pl-1' htmlFor='description'>
|
||||
Description
|
||||
</label>
|
||||
</div>
|
||||
<div className='mt-0 relative'>
|
||||
<TextareaAutosize
|
||||
className='p-3 rounded-lg bg-[#f1f1f1] text-[#2a2a2a] caret-green-600 font-paragraph w-full focus:border focus:outline-none resize-none focus:shadow-green'
|
||||
placeholder='Description...'
|
||||
id='description'
|
||||
name='description'
|
||||
wrap='soft'
|
||||
></TextareaAutosize>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button className='w-full mt-6' type='submit' data-cy='submit'>
|
||||
{t('application:create')}
|
||||
</Button>
|
||||
</Form>
|
||||
<FormState id='message' state={formState} message={message} />
|
||||
</Main>
|
||||
)
|
||||
}
|
1
components/Application/CreateGuild/index.ts
Normal file
1
components/Application/CreateGuild/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './CreateGuild'
|
21
components/Application/UserProfile/UserProfile.stories.tsx
Normal file
21
components/Application/UserProfile/UserProfile.stories.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { Meta, Story } from '@storybook/react'
|
||||
import { user, userSettings } from '../../../cypress/fixtures/users/user'
|
||||
|
||||
import { UserProfile as Component, UserProfileProps } from './UserProfile'
|
||||
|
||||
const Stories: Meta = {
|
||||
title: 'UserProfile',
|
||||
component: Component
|
||||
}
|
||||
|
||||
export default Stories
|
||||
|
||||
export const UserProfile: Story<UserProfileProps> = (arguments_) => {
|
||||
return <Component {...arguments_} />
|
||||
}
|
||||
UserProfile.args = {
|
||||
user: {
|
||||
...user,
|
||||
settings: userSettings
|
||||
}
|
||||
}
|
14
components/Application/UserProfile/UserProfile.test.tsx
Normal file
14
components/Application/UserProfile/UserProfile.test.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { render } from '@testing-library/react'
|
||||
|
||||
import { user, userSettings } from '../../../cypress/fixtures/users/user'
|
||||
|
||||
import { UserProfile } from './UserProfile'
|
||||
|
||||
describe('<UserProfile />', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(
|
||||
<UserProfile user={{ ...user, settings: userSettings }} />
|
||||
)
|
||||
expect(baseElement).toBeTruthy()
|
||||
})
|
||||
})
|
189
components/Application/UserProfile/UserProfile.tsx
Normal file
189
components/Application/UserProfile/UserProfile.tsx
Normal file
@ -0,0 +1,189 @@
|
||||
import Image from 'next/image'
|
||||
import { PencilIcon, PhotographIcon } from '@heroicons/react/solid'
|
||||
import classNames from 'classnames'
|
||||
import useTranslation from 'next-translate/useTranslation'
|
||||
import date from 'date-and-time'
|
||||
|
||||
import { UserPublic } from '../../../models/User'
|
||||
|
||||
export interface UserProfileProps {
|
||||
className?: string
|
||||
isOwner?: boolean
|
||||
user: UserPublic
|
||||
}
|
||||
|
||||
export const UserProfile: React.FC<UserProfileProps> = (props) => {
|
||||
const { user, isOwner = false } = props
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleSubmitChanges = (
|
||||
event: React.FormEvent<HTMLFormElement>
|
||||
): void => {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='h-full flex flex-col items-center justify-center'>
|
||||
<div className='min-w-[1080px]'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='w-max flex items-center'>
|
||||
<div
|
||||
className={classNames(
|
||||
'relative flex justify-center items-center rounded-full overflow-hidden transition-all shadow-lg',
|
||||
{
|
||||
'after:absolute after:w-full after:h-full border-4 border-white cursor-pointer':
|
||||
isOwner
|
||||
}
|
||||
)}
|
||||
>
|
||||
{isOwner && (
|
||||
<div className='absolute w-full h-full z-50'>
|
||||
<button className='relative w-full h-full flex items-center justify-center transition hover:-translate-y-1'>
|
||||
<input
|
||||
type='file'
|
||||
className='absolute w-full h-full opacity-0 cursor-pointer'
|
||||
/>
|
||||
<PhotographIcon className='w-14 h-14' />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<Image
|
||||
className={classNames('rounded-full', {
|
||||
'opacity-30': isOwner
|
||||
})}
|
||||
src='/images/data/divlo.png'
|
||||
alt={'Profil Picture'}
|
||||
draggable='false'
|
||||
height={125}
|
||||
width={125}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-col ml-10'>
|
||||
<div className='flex items-center mb-2 border'>
|
||||
<form onSubmit={handleSubmitChanges}>
|
||||
<input
|
||||
type='text'
|
||||
value={user.name}
|
||||
className='min-w-[10px] border bg-transparent text-3xl font-bold space tracking-wide text-white'
|
||||
disabled
|
||||
/>
|
||||
</form>
|
||||
{isOwner && (
|
||||
<button className='ml-2 text-gray-500'>
|
||||
<PencilIcon className='w-6 h-6' />
|
||||
</button>
|
||||
)}
|
||||
<span className='h-5 w-5 bg-error shadow-error ml-4 rounded-full'>
|
||||
{''}
|
||||
</span>
|
||||
<p className='ml-8 text-sm tracking-widest text-white opacity-40 select-none'>
|
||||
{date.format(new Date(user.createdAt), 'DD/MM/YYYY')}
|
||||
</p>
|
||||
</div>
|
||||
<div className='text-left my-2'>
|
||||
{user.website != null && (
|
||||
<p className='font-bold'>
|
||||
{t('application:website')}:{' '}
|
||||
<a
|
||||
href={user.website}
|
||||
className='relative ml-2 opacity-80 hover:opacity-100 transition-all no-underline font-normal tracking-wide after:absolute after:left-0 after:bottom-[-1px] after:bg-black dark:after:bg-white after:h-[1px] after:w-0 after:transition-all hover:after:w-full'
|
||||
>
|
||||
{user.website}
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
{user.email != null && (
|
||||
<p className='font-bold'>
|
||||
Email:{' '}
|
||||
<a
|
||||
href={`mailto:${user.email}`}
|
||||
target='_blank'
|
||||
className='relative ml-2 opacity-80 hover:opacity-100 transition-all no-underline font-normal tracking-wide after:absolute after:left-0 after:bottom-[-1px] after:bg-black dark:after:bg-white after:h-[1px] after:w-0 after:transition-all hover:after:w-full'
|
||||
rel='noreferrer'
|
||||
>
|
||||
{user.email}
|
||||
</a>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex -space-x-7'>
|
||||
<div className='flex justify-center items-center rounded-full filter drop-shadow-lg'>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src='/images/guilds/Guild_1.svg'
|
||||
alt={'Profil Picture'}
|
||||
draggable='false'
|
||||
height={60}
|
||||
width={60}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-center items-center rounded-full filter drop-shadow-lg'>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src='/images/guilds/Guild_2.svg'
|
||||
alt={'Profil Picture'}
|
||||
draggable='false'
|
||||
height={60}
|
||||
width={60}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-center items-center rounded-full filter drop-shadow-lg'>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src='/images/guilds/Guild_3.svg'
|
||||
alt={'Profil Picture'}
|
||||
draggable='false'
|
||||
height={60}
|
||||
width={60}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-center items-center rounded-full filter drop-shadow-lg'>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src='/images/guilds/Guild_4.svg'
|
||||
alt={'Profil Picture'}
|
||||
draggable='false'
|
||||
height={60}
|
||||
width={60}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-center items-center rounded-full filter drop-shadow-lg'>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src='/images/guilds/Guild_5.svg'
|
||||
alt={'Profil Picture'}
|
||||
draggable='false'
|
||||
height={60}
|
||||
width={60}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-center items-center rounded-full filter drop-shadow-lg'>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src='/images/guilds/Guild_6.svg'
|
||||
alt={'Profil Picture'}
|
||||
draggable='false'
|
||||
height={60}
|
||||
width={60}
|
||||
/>
|
||||
</div>
|
||||
{/* End of the guilds list */}
|
||||
<div className='w-[60px] h-[60px] flex justify-center items-center rounded-full filter drop-shadow-lg bg-gray-300 dark:bg-gray-800 z-10'>
|
||||
<span className='font-bold text-black dark:text-white text-xl select-none'>
|
||||
+4
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-7'>
|
||||
{user.biography != null && (
|
||||
<p className='w-[45%]'>{user.biography}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
1
components/Application/UserProfile/index.ts
Normal file
1
components/Application/UserProfile/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './UserProfile'
|
Reference in New Issue
Block a user