feat: add user profile page (#6)
This commit is contained in:
parent
9229131c1a
commit
ee73885fe9
@ -24,6 +24,7 @@ export type ApplicationPath =
|
|||||||
| '/application/guilds/join'
|
| '/application/guilds/join'
|
||||||
| '/application/guilds/create'
|
| '/application/guilds/create'
|
||||||
| `/application/users/${number}`
|
| `/application/users/${number}`
|
||||||
|
| `/application/users/${number}/settings`
|
||||||
| GuildsChannelsPath
|
| GuildsChannelsPath
|
||||||
|
|
||||||
export interface ApplicationProps {
|
export interface ApplicationProps {
|
||||||
@ -172,8 +173,8 @@ export const Application: React.FC<ApplicationProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<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'>
|
<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
|
<IconLink
|
||||||
href={`/application/users/${user.id}`}
|
href={`/application/users/${user.id}/settings`}
|
||||||
selected={path === `/application/users/${user.id}`}
|
selected={path === `/application/users/${user.id}/settings`}
|
||||||
title='Settings'
|
title='Settings'
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
@ -199,14 +200,13 @@ export const Application: React.FC<ApplicationProps> = (props) => {
|
|||||||
<Divider />
|
<Divider />
|
||||||
<Guilds path={path} />
|
<Guilds path={path} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{guildLeftSidebar}
|
{guildLeftSidebar}
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
id='application-page-content'
|
id='application-page-content'
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'top-0 h-full-without-header flex flex-col flex-1 z-0 overflow-y-auto transition',
|
'top-0 h-full-without-header w-full flex flex-col flex-1 z-0 overflow-y-auto transition',
|
||||||
{
|
{
|
||||||
'absolute opacity-20':
|
'absolute opacity-20':
|
||||||
isMobile && (visibleSidebars.left || visibleSidebars.right)
|
isMobile && (visibleSidebars.left || visibleSidebars.right)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import useTranslation from 'next-translate/useTranslation'
|
import useTranslation from 'next-translate/useTranslation'
|
||||||
import { Form } from 'react-component-form'
|
import { Form } from 'react-component-form'
|
||||||
import TextareaAutosize from 'react-textarea-autosize'
|
|
||||||
import { AxiosResponse } from 'axios'
|
import { AxiosResponse } from 'axios'
|
||||||
|
|
||||||
import { useAuthentication } from '../../../tools/authentication'
|
import { useAuthentication } from '../../../tools/authentication'
|
||||||
@ -11,6 +10,7 @@ import { Input } from '../../design/Input'
|
|||||||
import { Main } from '../../design/Main'
|
import { Main } from '../../design/Main'
|
||||||
import { Button } from '../../design/Button'
|
import { Button } from '../../design/Button'
|
||||||
import { FormState } from '../../design/FormState'
|
import { FormState } from '../../design/FormState'
|
||||||
|
import { Textarea } from '../../design/Textarea'
|
||||||
|
|
||||||
export const CreateGuild: React.FC = () => {
|
export const CreateGuild: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -54,24 +54,11 @@ export const CreateGuild: React.FC = () => {
|
|||||||
label={t('common:name')}
|
label={t('common:name')}
|
||||||
error={getErrorTranslation(errors.name)}
|
error={getErrorTranslation(errors.name)}
|
||||||
/>
|
/>
|
||||||
|
<Textarea
|
||||||
<div className='flex flex-col'>
|
label='Description'
|
||||||
<div className='flex justify-between mt-6 mb-2'>
|
placeholder='Description'
|
||||||
<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'
|
id='description'
|
||||||
name='description'
|
/>
|
||||||
wrap='soft'
|
|
||||||
></TextareaAutosize>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button className='w-full mt-6' type='submit' data-cy='submit'>
|
<Button className='w-full mt-6' type='submit' data-cy='submit'>
|
||||||
{t('application:create')}
|
{t('application:create')}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
|
|
||||||
|
import { API_URL } from '../../../../tools/api'
|
||||||
import { GuildWithDefaultChannelId } from '../../../../models/Guild'
|
import { GuildWithDefaultChannelId } from '../../../../models/Guild'
|
||||||
import { IconLink } from '../../../design/IconLink'
|
import { IconLink } from '../../../design/IconLink'
|
||||||
|
|
||||||
@ -22,7 +23,9 @@ export const Guild: React.FC<GuildProps> = (props) => {
|
|||||||
<Image
|
<Image
|
||||||
className='rounded-full'
|
className='rounded-full'
|
||||||
src={
|
src={
|
||||||
guild.icon != null ? guild.icon : '/images/data/guild-default.png'
|
guild.icon != null
|
||||||
|
? API_URL + guild.icon
|
||||||
|
: '/images/data/guild-default.png'
|
||||||
}
|
}
|
||||||
alt='logo'
|
alt='logo'
|
||||||
width={48}
|
width={48}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import InfiniteScroll from 'react-infinite-scroll-component'
|
import InfiniteScroll from 'react-infinite-scroll-component'
|
||||||
|
|
||||||
import { Loader } from '../../design/Loader'
|
import { Loader } from '../../design/Loader'
|
||||||
import { Guild } from './Guild'
|
|
||||||
import { useGuilds } from '../../../contexts/Guilds'
|
import { useGuilds } from '../../../contexts/Guilds'
|
||||||
import { GuildsChannelsPath } from '..'
|
import { GuildsChannelsPath } from '..'
|
||||||
|
import { Guild } from './Guild'
|
||||||
|
|
||||||
export interface GuildsProps {
|
export interface GuildsProps {
|
||||||
path: GuildsChannelsPath | string
|
path: GuildsChannelsPath | string
|
||||||
|
@ -1,98 +1,69 @@
|
|||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { PencilIcon, PhotographIcon } from '@heroicons/react/solid'
|
import { useState } from 'react'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import useTranslation from 'next-translate/useTranslation'
|
|
||||||
import date from 'date-and-time'
|
import date from 'date-and-time'
|
||||||
|
import useTranslation from 'next-translate/useTranslation'
|
||||||
|
import { XIcon } from '@heroicons/react/solid'
|
||||||
|
|
||||||
|
import { API_URL } from '../../../tools/api'
|
||||||
import { UserPublic } from '../../../models/User'
|
import { UserPublic } from '../../../models/User'
|
||||||
|
import { UserProfileGuilds } from './UserProfileGuilds'
|
||||||
|
import { UserProfileGuild } from './UserProfileGuilds/UserProfileGuild'
|
||||||
|
|
||||||
export interface UserProfileProps {
|
export interface UserProfileProps {
|
||||||
className?: string
|
className?: string
|
||||||
isOwner?: boolean
|
|
||||||
user: UserPublic
|
user: UserPublic
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserProfile: React.FC<UserProfileProps> = (props) => {
|
export const UserProfile: React.FC<UserProfileProps> = (props) => {
|
||||||
const { user, isOwner = false } = props
|
const { user } = props
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const handleSubmitChanges = (
|
const [showPopup, setShowPopup] = useState<boolean>(false)
|
||||||
event: React.FormEvent<HTMLFormElement>
|
const [confirmation, setConfirmation] = useState<boolean>(false)
|
||||||
): void => {
|
|
||||||
event.preventDefault()
|
const handleConfirmationState = (): void => {
|
||||||
|
setConfirmation((confirmation) => !confirmation)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePopupVisibility = (): void => {
|
||||||
|
setShowPopup((showPopup) => !showPopup)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='h-full flex flex-col items-center justify-center'>
|
<div className='relative h-full flex flex-col items-center justify-center'>
|
||||||
<div className='min-w-[1080px]'>
|
<div
|
||||||
|
className={classNames('transition', {
|
||||||
|
'blur-3xl select-none': showPopup
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className='max-w-[1000px] px-12'>
|
||||||
<div className='flex justify-between items-center'>
|
<div className='flex justify-between items-center'>
|
||||||
<div className='w-max flex items-center'>
|
<div className='w-max flex items-center'>
|
||||||
<div
|
<div className='relative flex justify-center items-center rounded-full overflow-hidden transition-all shadow-lg'>
|
||||||
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
|
<Image
|
||||||
className={classNames('rounded-full', {
|
className='rounded-full'
|
||||||
'opacity-30': isOwner
|
src={
|
||||||
})}
|
user.logo != null
|
||||||
src='/images/data/divlo.png'
|
? API_URL + user.logo
|
||||||
alt={'Profil Picture'}
|
: '/images/data/user-default.png'
|
||||||
|
}
|
||||||
|
alt='Profil Picture'
|
||||||
draggable='false'
|
draggable='false'
|
||||||
height={125}
|
height={125}
|
||||||
width={125}
|
width={125}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col ml-10'>
|
<div className='flex flex-col ml-10'>
|
||||||
<div className='flex items-center mb-2 border'>
|
<div className='flex items-center mb-2'>
|
||||||
<form onSubmit={handleSubmitChanges}>
|
<p className='text-3xl font-bold space tracking-wide text-white'>
|
||||||
<input
|
{user.name}
|
||||||
type='text'
|
</p>
|
||||||
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'>
|
<p className='ml-8 text-sm tracking-widest text-white opacity-40 select-none'>
|
||||||
{date.format(new Date(user.createdAt), 'DD/MM/YYYY')}
|
{date.format(new Date(user.createdAt), 'DD/MM/YYYY')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className='text-left my-2'>
|
<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 && (
|
{user.email != null && (
|
||||||
<p className='font-bold'>
|
<p className='font-bold'>
|
||||||
Email:{' '}
|
Email:{' '}
|
||||||
@ -106,78 +77,35 @@ export const UserProfile: React.FC<UserProfileProps> = (props) => {
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
{user.website != null && (
|
||||||
</div>
|
<p className='font-bold'>
|
||||||
</div>
|
{t('application:website')}:{' '}
|
||||||
<div className='flex -space-x-7'>
|
<a
|
||||||
<div className='flex justify-center items-center rounded-full filter drop-shadow-lg'>
|
href={user.website}
|
||||||
<Image
|
className='relative ml-2 opacity-80 hover:opacity-100 transition-all no-underline font-normal tracking-wide after:absolute after:left-0 after:bottom-[-2px] after:bg-black dark:after:bg-white after:h-[1px] after:w-0 after:transition-all hover:after:w-full'
|
||||||
className='rounded-full'
|
>
|
||||||
src='/images/guilds/Guild_1.svg'
|
{user.website}
|
||||||
alt={'Profil Picture'}
|
</a>
|
||||||
draggable='false'
|
</p>
|
||||||
height={60}
|
)}
|
||||||
width={60}
|
{user.status != null && (
|
||||||
/>
|
<p className='flex font-bold'>
|
||||||
</div>
|
{t('application:status')}:{' '}
|
||||||
<div className='flex justify-center items-center rounded-full filter drop-shadow-lg'>
|
<span className='ml-2 font-normal tracking-wide'>
|
||||||
<Image
|
{user.status}
|
||||||
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>
|
</span>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className='py-8 px-4' onClick={handlePopupVisibility}>
|
||||||
|
<UserProfileGuilds
|
||||||
|
isPublicGuilds={user.settings.isPublicGuilds}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className='mt-7'>
|
<div className='mt-7'>
|
||||||
{user.biography != null && (
|
{user.biography != null && (
|
||||||
<p className='w-[45%]'>{user.biography}</p>
|
<p className='w-[45%]'>{user.biography}</p>
|
||||||
@ -185,5 +113,67 @@ export const UserProfile: React.FC<UserProfileProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'absolute flex justify-center items-center top-0 h-full w-full bg-zinc-900/75 transition opacity-0 pointer-events-none',
|
||||||
|
{
|
||||||
|
'opacity-100 visible pointer-events-auto': showPopup
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'relative h-[400px] w-[400px] py-2 rounded-2xl shadow-xl bg-gray-200 dark:bg-gray-800 scale-0 transition overflow-y-auto overflow-x-hidden',
|
||||||
|
{ 'scale-100': showPopup }
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames('relative transition h-full', {
|
||||||
|
'-translate-x-[150%]': confirmation
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<UserProfileGuild
|
||||||
|
handleConfirmationState={handleConfirmationState}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'absolute w-full h-full flex flex-col justify-center items-center transition-all top-0 left-[150%]',
|
||||||
|
{ 'left-[0%]': confirmation }
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src='/images/svg/design/join-guild.svg'
|
||||||
|
alt='Joing Guild Illustration'
|
||||||
|
height={150}
|
||||||
|
width={150}
|
||||||
|
/>
|
||||||
|
<div className='flex flex-col mt-8'>
|
||||||
|
<h1 className='text-xl mb-6 text-center'>Rejoindre la guild ?</h1>
|
||||||
|
<div className='flex gap-7'>
|
||||||
|
<button
|
||||||
|
className='px-8 py-2 rounded-3xl bg-success hover:opacity-50 transition'
|
||||||
|
onClick={handleConfirmationState}
|
||||||
|
>
|
||||||
|
Oui
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className='px-8 py-2 rounded-3xl bg-error hover:opacity-50 transition'
|
||||||
|
onClick={handleConfirmationState}
|
||||||
|
>
|
||||||
|
Non
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<XIcon
|
||||||
|
height={40}
|
||||||
|
onClick={() => setShowPopup(false)}
|
||||||
|
className='absolute top-8 right-8 cursor-pointer hover:rotate-180 transition'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import { Meta, Story } from '@storybook/react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
UserProfileGuild as Component,
|
||||||
|
UserProfileGuildProps
|
||||||
|
} from './UserProfileGuild'
|
||||||
|
|
||||||
|
const Stories: Meta = {
|
||||||
|
title: 'UserProfileGuild',
|
||||||
|
component: Component
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Stories
|
||||||
|
|
||||||
|
export const UserProfileGuild: Story<UserProfileGuildProps> = (arguments_) => {
|
||||||
|
return <Component {...arguments_} />
|
||||||
|
}
|
||||||
|
UserProfileGuild.args = { className: 'text-center' }
|
@ -0,0 +1,12 @@
|
|||||||
|
import { render } from '@testing-library/react'
|
||||||
|
|
||||||
|
import { UserProfileGuild } from './UserProfileGuild'
|
||||||
|
|
||||||
|
describe('<UserProfileGuild />', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
const { baseElement } = render(
|
||||||
|
<UserProfileGuild handleConfirmationState={() => {}} />
|
||||||
|
)
|
||||||
|
expect(baseElement).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,46 @@
|
|||||||
|
import Image from 'next/image'
|
||||||
|
import { LoginIcon } from '@heroicons/react/solid'
|
||||||
|
|
||||||
|
export interface UserProfileGuildProps {
|
||||||
|
className?: string
|
||||||
|
handleConfirmationState: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UserProfileGuild: React.FC<UserProfileGuildProps> = (props) => {
|
||||||
|
const { handleConfirmationState } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className='relative flex w-full cursor-pointer transition group'
|
||||||
|
onClick={handleConfirmationState}
|
||||||
|
>
|
||||||
|
<div className='relative group-hover:-translate-x-20 px-8 py-5 w-full transition'>
|
||||||
|
<div className='flex group-hover:opacity-40 transition'>
|
||||||
|
<div className='flex justify-center rounded-full filter drop-shadow-lg mr-8 min-w-[60px] min-h-[60px] select-none'>
|
||||||
|
<Image
|
||||||
|
className='rounded-full'
|
||||||
|
src='/images/guilds/Guild_1.svg'
|
||||||
|
alt={'Profil Picture'}
|
||||||
|
draggable='false'
|
||||||
|
height={60}
|
||||||
|
width={60}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col'>
|
||||||
|
<h1 className='text-xl font-bold'>Guild Name</h1>
|
||||||
|
<p className='text-gray-300 mt-2'>
|
||||||
|
Lorem ipsum dolor sit amet consectetur adipisicing elit. Debitis,
|
||||||
|
nam.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='absolute top-0 right-[-80px] flex justify-center items-center w-[80px] h-full'>
|
||||||
|
<LoginIcon
|
||||||
|
height={40}
|
||||||
|
className='fill-green-600 drop-shadow-[0_0_15px_rgba(22,163,74,0.50)]'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export * from './UserProfileGuild'
|
@ -0,0 +1,20 @@
|
|||||||
|
import { Meta, Story } from '@storybook/react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
UserProfileGuilds as Component,
|
||||||
|
UserProfileGuildsProps
|
||||||
|
} from './UserProfileGuilds'
|
||||||
|
|
||||||
|
const Stories: Meta = {
|
||||||
|
title: 'UserProfileGuilds',
|
||||||
|
component: Component
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Stories
|
||||||
|
|
||||||
|
export const UserProfileGuilds: Story<UserProfileGuildsProps> = (
|
||||||
|
arguments_
|
||||||
|
) => {
|
||||||
|
return <Component {...arguments_} />
|
||||||
|
}
|
||||||
|
UserProfileGuilds.args = {}
|
@ -0,0 +1,10 @@
|
|||||||
|
import { render } from '@testing-library/react'
|
||||||
|
|
||||||
|
import { UserProfileGuilds } from './UserProfileGuilds'
|
||||||
|
|
||||||
|
describe('<UserProfileGuilds />', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
const { baseElement } = render(<UserProfileGuilds />)
|
||||||
|
expect(baseElement).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
@ -0,0 +1,104 @@
|
|||||||
|
import Image from 'next/image'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
import { EyeOffIcon } from '@heroicons/react/solid'
|
||||||
|
import useTranslation from 'next-translate/useTranslation'
|
||||||
|
|
||||||
|
export interface UserProfileGuildsProps {
|
||||||
|
isPublicGuilds?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UserProfileGuilds: React.FC<UserProfileGuildsProps> = (props) => {
|
||||||
|
const { isPublicGuilds = false } = props
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames('relative cursor-pointer', {
|
||||||
|
'cursor-auto': !isPublicGuilds
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames('flex -space-x-7', {
|
||||||
|
'blur-lg select-none': !isPublicGuilds
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
<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
|
||||||
|
className={classNames(
|
||||||
|
'absolute flex items-center top-1/2 -translate-y-1/2',
|
||||||
|
{ hidden: isPublicGuilds }
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<EyeOffIcon height={25} />
|
||||||
|
<p className='drop-shadow-2xl ml-4'>
|
||||||
|
{t('application:private-user-guilds-list')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export * from './UserProfileGuilds'
|
19
components/Application/UserSettings/UserSettings.stories.tsx
Normal file
19
components/Application/UserSettings/UserSettings.stories.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Meta, Story } from '@storybook/react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
userExample,
|
||||||
|
userSettingsExample
|
||||||
|
} from '../../../cypress/fixtures/users/user'
|
||||||
|
import { UserSettings as Component, UserSettingsProps } from './UserSettings'
|
||||||
|
|
||||||
|
const Stories: Meta = {
|
||||||
|
title: 'UserSettings',
|
||||||
|
component: Component
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Stories
|
||||||
|
|
||||||
|
export const UserSettings: Story<UserSettingsProps> = (arguments_) => {
|
||||||
|
return <Component {...arguments_} />
|
||||||
|
}
|
||||||
|
UserSettings.args = { user: { ...userExample, settings: userSettingsExample } }
|
16
components/Application/UserSettings/UserSettings.test.tsx
Normal file
16
components/Application/UserSettings/UserSettings.test.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { render } from '@testing-library/react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
userExample,
|
||||||
|
userSettingsExample
|
||||||
|
} from '../../../cypress/fixtures/users/user'
|
||||||
|
import { UserSettings } from './UserSettings'
|
||||||
|
|
||||||
|
describe('<UserSettings />', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
const { baseElement } = render(
|
||||||
|
<UserSettings user={{ ...userExample, settings: userSettingsExample }} />
|
||||||
|
)
|
||||||
|
expect(baseElement).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
120
components/Application/UserSettings/UserSettings.tsx
Normal file
120
components/Application/UserSettings/UserSettings.tsx
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import Image from 'next/image'
|
||||||
|
import { PhotographIcon } from '@heroicons/react/solid'
|
||||||
|
import useTranslation from 'next-translate/useTranslation'
|
||||||
|
|
||||||
|
import { API_URL } from '../../../tools/api'
|
||||||
|
import { UserPublic } from '../../../models/User'
|
||||||
|
import { UserProfileGuilds } from '../UserProfile/UserProfileGuilds'
|
||||||
|
import { Input } from '../../design/Input'
|
||||||
|
import { Checkbox } from '../../design/Checkbox'
|
||||||
|
import { Textarea } from '../../design/Textarea'
|
||||||
|
import { SocialMediaButton } from '../../design/SocialMediaButton'
|
||||||
|
import { SwitchTheme } from '../../Header/SwitchTheme'
|
||||||
|
import { Language } from '../../Header/Language'
|
||||||
|
|
||||||
|
export interface UserSettingsProps {
|
||||||
|
user: UserPublic
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UserSettings: React.FC<UserSettingsProps> = (props) => {
|
||||||
|
const { user } = props
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='my-auto lg:min-w-[875px] py-12 justify-center items-center flex flex-col'>
|
||||||
|
<div className='flex flex-col w-full justify-center items-center lg:flex-row sm:w-fit'>
|
||||||
|
<div className=' flex justify-center items-center flex-wrap px-6 w-full sm:w-max'>
|
||||||
|
<div className='relative'>
|
||||||
|
<div className='absolute w-full h-full z-50'>
|
||||||
|
<button className='relative w-full h-full flex items-center justify-center transition hover:scale-110'>
|
||||||
|
<input
|
||||||
|
type='file'
|
||||||
|
className='absolute w-full h-full opacity-0 cursor-pointer'
|
||||||
|
/>
|
||||||
|
<PhotographIcon color='white' className='w-8 h-8' />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className='flex justify-center items-center rounded-full shadow-xl bg-black'>
|
||||||
|
<Image
|
||||||
|
className='opacity-50 rounded-full'
|
||||||
|
src={
|
||||||
|
user.logo != null
|
||||||
|
? API_URL + user.logo
|
||||||
|
: '/images/data/user-default.png'
|
||||||
|
}
|
||||||
|
alt='Profil Picture'
|
||||||
|
draggable='false'
|
||||||
|
height={125}
|
||||||
|
width={125}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col mx-12'>
|
||||||
|
<Input
|
||||||
|
label={t('common:name')}
|
||||||
|
placeholder={t('common:name')}
|
||||||
|
className='!mt-0'
|
||||||
|
defaultValue={user.name}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label={t('application:status')}
|
||||||
|
placeholder={t('application:status')}
|
||||||
|
className='!mt-4'
|
||||||
|
defaultValue={user.status ?? ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex mt-10 flex-col items-center ml-0 lg:ml-24 lg:mt-0'>
|
||||||
|
<UserProfileGuilds isPublicGuilds={user.settings.isPublicGuilds} />
|
||||||
|
<Checkbox
|
||||||
|
label={t('application:label-checkbox-guilds')}
|
||||||
|
defaultChecked={user.settings.isPublicGuilds}
|
||||||
|
id='checkbox-public-guilds'
|
||||||
|
className='px-8'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col w-full justify-between items-center mt-12 lg:flex-row sm:w-fit'>
|
||||||
|
<div className='w-4/5 sm:w-[450px] pr-0 lg:pr-12 lg:border-r-[1px] lg:border-neutral-700'>
|
||||||
|
<Input label='Email' defaultValue={user.email ?? ''} />
|
||||||
|
<Checkbox
|
||||||
|
label={t('application:label-checkbox-email')}
|
||||||
|
id='checkbox-email-visibility'
|
||||||
|
defaultChecked={user.settings.isPublicEmail}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label={t('application:website')}
|
||||||
|
placeholder={t('application:website')}
|
||||||
|
defaultValue={user.website ?? ''}
|
||||||
|
/>
|
||||||
|
<Textarea
|
||||||
|
label={t('application:biography')}
|
||||||
|
placeholder={t('application:biography')}
|
||||||
|
id='textarea-biography'
|
||||||
|
defaultValue={user.biography ?? ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col justify-between items-center w-4/5 sm:w-[415px] h-full pr-0 lg:pl-12'>
|
||||||
|
<div className='flex w-full flex-col gap-4 mt-14'>
|
||||||
|
<SocialMediaButton
|
||||||
|
socialMedia='Google'
|
||||||
|
className='w-full justify-center'
|
||||||
|
/>
|
||||||
|
<SocialMediaButton
|
||||||
|
socialMedia='Discord'
|
||||||
|
className='w-full justify-center'
|
||||||
|
/>
|
||||||
|
<SocialMediaButton
|
||||||
|
socialMedia='GitHub'
|
||||||
|
className='w-full justify-center'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='flex justify-between w-full pt-14'>
|
||||||
|
<Language />
|
||||||
|
<SwitchTheme />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
1
components/Application/UserSettings/index.ts
Normal file
1
components/Application/UserSettings/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './UserSettings'
|
@ -33,7 +33,7 @@ export const Language: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col justify-center items-center cursor-pointer'>
|
<div className='relative flex flex-col justify-center items-center cursor-pointer'>
|
||||||
<div
|
<div
|
||||||
data-cy='language-click'
|
data-cy='language-click'
|
||||||
className='flex items-center mr-5'
|
className='flex items-center mr-5'
|
||||||
@ -46,7 +46,7 @@ export const Language: React.FC = () => {
|
|||||||
<ul
|
<ul
|
||||||
data-cy='languages-list'
|
data-cy='languages-list'
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'flex flex-col justify-center items-center absolute p-0 top-14 z-10 w-24 mt-3 mr-4 rounded-lg list-none shadow-light dark:shadow-dark bg-white dark:bg-black',
|
'flex flex-col justify-center items-center absolute p-0 -bottom-16 z-10 w-24 mt-3 mr-4 rounded-lg list-none shadow-light dark:shadow-dark bg-white dark:bg-black',
|
||||||
{ hidden: hiddenMenu }
|
{ hidden: hiddenMenu }
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
15
components/design/Checkbox/Checkbox.stories.tsx
Normal file
15
components/design/Checkbox/Checkbox.stories.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Meta, Story } from '@storybook/react'
|
||||||
|
|
||||||
|
import { Checkbox as Component, CheckboxProps } from './Checkbox'
|
||||||
|
|
||||||
|
const Stories: Meta = {
|
||||||
|
title: 'Checkbox',
|
||||||
|
component: Component
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Stories
|
||||||
|
|
||||||
|
export const Checkbox: Story<CheckboxProps> = (arguments_) => {
|
||||||
|
return <Component {...arguments_} />
|
||||||
|
}
|
||||||
|
Checkbox.args = { label: 'Checkbox' }
|
10
components/design/Checkbox/Checkbox.test.tsx
Normal file
10
components/design/Checkbox/Checkbox.test.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { render } from '@testing-library/react'
|
||||||
|
|
||||||
|
import { Checkbox } from './Checkbox'
|
||||||
|
|
||||||
|
describe('<Checkbox />', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
const { baseElement } = render(<Checkbox label='Checkbox' />)
|
||||||
|
expect(baseElement).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
26
components/design/Checkbox/Checkbox.tsx
Normal file
26
components/design/Checkbox/Checkbox.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
export interface CheckboxProps extends React.ComponentPropsWithRef<'input'> {
|
||||||
|
className?: string
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Checkbox: React.FC<CheckboxProps> = (props) => {
|
||||||
|
const { label, id, className } = props
|
||||||
|
return (
|
||||||
|
<div className={classNames('flex items-center mt-4', className)}>
|
||||||
|
<input
|
||||||
|
{...props}
|
||||||
|
type='checkbox'
|
||||||
|
id={id}
|
||||||
|
className='relative appearance-none min-h-[25px] min-w-[25px] bg-gradient-to-t from-[#bcc7d4] to-[#d3dfed] dark:from-[#1f2937] dark:to-[#273547] mr-3 cursor-pointer rounded-md after:absolute before:absolute after:w-[2px] before:w-[2px] after:bg-black before:bg-black dark:after:bg-white dark:before:bg-white transition-all after:transition-all before:transition-all after:top-[62.5%] after:left-[36%] after:h-[7px] after:translate-x-[-35%] after:translate-y-[-62.5%] after:rotate-[-50deg] after:scale-0 after:duration-200 before:top-[50%] before:left-[59%] before:h-[12px] before:translate-x-[-59%] before:translate-y-[-50%] before:rotate-[40deg] before:scale-0 checked:after:scale-100 checked:before:scale-100'
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
className='cursor-pointer opacity-80 hover:opacity-100 transition duration-400 select-none '
|
||||||
|
htmlFor={id}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
1
components/design/Checkbox/index.ts
Normal file
1
components/design/Checkbox/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './Checkbox'
|
@ -1,6 +1,7 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import useTranslation from 'next-translate/useTranslation'
|
import useTranslation from 'next-translate/useTranslation'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
import { FormState } from '../FormState'
|
import { FormState } from '../FormState'
|
||||||
|
|
||||||
@ -8,6 +9,7 @@ export interface InputProps extends React.ComponentPropsWithRef<'input'> {
|
|||||||
label: string
|
label: string
|
||||||
error?: string | null
|
error?: string | null
|
||||||
showForgotPassword?: boolean
|
showForgotPassword?: boolean
|
||||||
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getInputType = (inputType: string): string => {
|
export const getInputType = (inputType: string): string => {
|
||||||
@ -18,6 +20,7 @@ export const Input: React.FC<InputProps> = (props) => {
|
|||||||
const {
|
const {
|
||||||
label,
|
label,
|
||||||
name,
|
name,
|
||||||
|
className,
|
||||||
type = 'text',
|
type = 'text',
|
||||||
showForgotPassword = false,
|
showForgotPassword = false,
|
||||||
error,
|
error,
|
||||||
@ -34,7 +37,9 @@ export const Input: React.FC<InputProps> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='flex flex-col'>
|
<div className='flex flex-col'>
|
||||||
<div className='flex justify-between mt-6 mb-2'>
|
<div
|
||||||
|
className={classNames('flex justify-between mt-6 mb-2', className)}
|
||||||
|
>
|
||||||
<label className='pl-1' htmlFor={name}>
|
<label className='pl-1' htmlFor={name}>
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
@ -53,7 +58,7 @@ export const Input: React.FC<InputProps> = (props) => {
|
|||||||
<input
|
<input
|
||||||
data-testid='input'
|
data-testid='input'
|
||||||
data-cy={`input-${name ?? 'name'}`}
|
data-cy={`input-${name ?? 'name'}`}
|
||||||
className='h-11 leading-10 px-3 rounded-lg bg-[#f1f1f1] text-[#2a2a2a] caret-green-600 font-paragraph w-full focus:border focus:outline-none focus:shadow-green border-0'
|
className='h-11 leading-10 px-3 rounded-lg bg-[#f1f1f1] text-[#2a2a2a] border border-transparent caret-green-600 font-paragraph w-full focus:border focus:outline-none focus:shadow-green'
|
||||||
{...rest}
|
{...rest}
|
||||||
id={name}
|
id={name}
|
||||||
name={name}
|
name={name}
|
||||||
|
15
components/design/Textarea/Textarea.stories.tsx
Normal file
15
components/design/Textarea/Textarea.stories.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Meta, Story } from '@storybook/react'
|
||||||
|
|
||||||
|
import { Textarea as Component, TextareaProps } from './Textarea'
|
||||||
|
|
||||||
|
const Stories: Meta = {
|
||||||
|
title: 'Textarea',
|
||||||
|
component: Component
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Stories
|
||||||
|
|
||||||
|
export const Textarea: Story<TextareaProps> = (arguments_) => {
|
||||||
|
return <Component {...arguments_} />
|
||||||
|
}
|
||||||
|
Textarea.args = { label: 'Textarea' }
|
10
components/design/Textarea/Textarea.test.tsx
Normal file
10
components/design/Textarea/Textarea.test.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { render } from '@testing-library/react'
|
||||||
|
|
||||||
|
import { Textarea } from './Textarea'
|
||||||
|
|
||||||
|
describe('<Textarea />', () => {
|
||||||
|
it('should render successfully', () => {
|
||||||
|
const { baseElement } = render(<Textarea label='Textarea' />)
|
||||||
|
expect(baseElement).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
30
components/design/Textarea/Textarea.tsx
Normal file
30
components/design/Textarea/Textarea.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import TextareaAutosize, {
|
||||||
|
TextareaAutosizeProps
|
||||||
|
} from 'react-textarea-autosize'
|
||||||
|
|
||||||
|
export interface TextareaProps extends TextareaAutosizeProps {
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Textarea: React.FC<TextareaProps> = (props) => {
|
||||||
|
const { label, id, ...rest } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col'>
|
||||||
|
<div className='flex justify-between mt-6 mb-2'>
|
||||||
|
<label className='pl-1' htmlFor={id}>
|
||||||
|
{label}
|
||||||
|
</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 overflow-hidden'
|
||||||
|
wrap='soft'
|
||||||
|
id={id}
|
||||||
|
name={id}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
1
components/design/Textarea/index.ts
Normal file
1
components/design/Textarea/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './Textarea'
|
@ -11,6 +11,7 @@
|
|||||||
"/authentication/signup": ["authentication", "errors"],
|
"/authentication/signup": ["authentication", "errors"],
|
||||||
"/authentication/signin": ["authentication", "errors"],
|
"/authentication/signin": ["authentication", "errors"],
|
||||||
"/application/users/[userId]": ["application", "errors"],
|
"/application/users/[userId]": ["application", "errors"],
|
||||||
|
"/application/users/[userId]/settings": ["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"],
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
{
|
{
|
||||||
"website": "Website",
|
"website": "Website",
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
|
"status": "Status",
|
||||||
"create-a-guild": "Create a Guild",
|
"create-a-guild": "Create a Guild",
|
||||||
"create-a-guild-description": "Create your own guild and manage everything.",
|
"create-a-guild-description": "Create your own guild and manage everything.",
|
||||||
"join-a-guild": "Join a Guild",
|
"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!",
|
"join-a-guild-description": "Talk, collaborate, share and have fun with your friends by joining an already existing guild!",
|
||||||
"members": "member(s)",
|
"members": "member(s)",
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
"write-a-message": "Write a message..."
|
"write-a-message": "Write a message...",
|
||||||
|
"biography": "Biography",
|
||||||
|
"label-checkbox-email": "Show your email to everyone.",
|
||||||
|
"label-checkbox-guilds": "Show the list of guilds to everyone.",
|
||||||
|
"private-user-guilds-list": "List of private guilds"
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
{
|
{
|
||||||
"website": "Site web",
|
"website": "Site web",
|
||||||
"create": "Créer",
|
"create": "Créer",
|
||||||
|
"status": "Statut",
|
||||||
"create-a-guild": "Créer une Guilde",
|
"create-a-guild": "Créer une Guilde",
|
||||||
"create-a-guild-description": "Créez votre propre guilde et gérez tout.",
|
"create-a-guild-description": "Créez votre propre guilde et gérez tout.",
|
||||||
"join-a-guild": "Rejoindre une Guilde",
|
"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!",
|
"join-a-guild-description": "Discutez, collaborez, partagez et amusez-vous avec vos amis en rejoignant une guilde déjà existante!",
|
||||||
"members": "membre(s)",
|
"members": "membre(s)",
|
||||||
"search": "Rechercher",
|
"search": "Rechercher",
|
||||||
"write-a-message": "Écrire un message..."
|
"write-a-message": "Écrire un message...",
|
||||||
|
"biography": "Biographie",
|
||||||
|
"label-checkbox-email": "Afficher votre email au public.",
|
||||||
|
"label-checkbox-guilds": "Afficher la liste des guilds au public.",
|
||||||
|
"private-user-guilds-list": "List of private guilds"
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,10 @@ const UserProfilePage: NextPage<PagePropsWithAuthentication> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<AuthenticationProvider authentication={props.authentication}>
|
<AuthenticationProvider authentication={props.authentication}>
|
||||||
<GuildsProvider>
|
<GuildsProvider>
|
||||||
<Head title='Thream | Settings' />
|
<Head title={`Thream | ${props.authentication.user.name}`} />
|
||||||
<Application
|
<Application
|
||||||
path={`/application/users/${props.authentication.user.id}`}
|
path={`/application/users/${props.authentication.user.id}`}
|
||||||
title='Settings'
|
title={props.authentication.user.name}
|
||||||
>
|
>
|
||||||
<UserProfile user={props.authentication.user} />
|
<UserProfile user={props.authentication.user} />
|
||||||
</Application>
|
</Application>
|
33
pages/application/users/[userId]/settings.tsx
Normal file
33
pages/application/users/[userId]/settings.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { NextPage } from 'next'
|
||||||
|
|
||||||
|
import { Head } from 'components/Head'
|
||||||
|
import { Application } from 'components/Application'
|
||||||
|
import {
|
||||||
|
authenticationFromServerSide,
|
||||||
|
AuthenticationProvider,
|
||||||
|
PagePropsWithAuthentication
|
||||||
|
} from 'tools/authentication'
|
||||||
|
import { UserSettings } from 'components/Application/UserSettings'
|
||||||
|
import { GuildsProvider } from 'contexts/Guilds'
|
||||||
|
|
||||||
|
const UserSettingsPage: NextPage<PagePropsWithAuthentication> = (props) => {
|
||||||
|
return (
|
||||||
|
<AuthenticationProvider authentication={props.authentication}>
|
||||||
|
<GuildsProvider>
|
||||||
|
<Head title='Thream | Settings' />
|
||||||
|
<Application
|
||||||
|
path={`/application/users/${props.authentication.user.id}/settings`}
|
||||||
|
title='Settings'
|
||||||
|
>
|
||||||
|
<UserSettings user={props.authentication.user} />
|
||||||
|
</Application>
|
||||||
|
</GuildsProvider>
|
||||||
|
</AuthenticationProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getServerSideProps = authenticationFromServerSide({
|
||||||
|
shouldBeAuthenticated: true
|
||||||
|
})
|
||||||
|
|
||||||
|
export default UserSettingsPage
|
Reference in New Issue
Block a user