feat: interact with user settings/profile (#9)
This commit is contained in:
@ -24,7 +24,7 @@ export type ApplicationPath =
|
||||
| '/application/guilds/join'
|
||||
| '/application/guilds/create'
|
||||
| `/application/users/${number}`
|
||||
| `/application/users/${number}/settings`
|
||||
| `/application/users/settings`
|
||||
| GuildsChannelsPath
|
||||
|
||||
export interface ApplicationProps {
|
||||
@ -135,16 +135,16 @@ export const Application: React.FC<ApplicationProps> = (props) => {
|
||||
|
||||
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'>
|
||||
<header className='z-50 flex h-16 items-center justify-between bg-gray-200 px-2 py-3 shadow-lg dark:bg-gray-800'>
|
||||
<IconButton
|
||||
className='p-2 h-10 w-10'
|
||||
className='h-10 w-10 p-2'
|
||||
onClick={() => handleToggleSidebars('left')}
|
||||
>
|
||||
{!visibleSidebars.left ? <MenuIcon /> : <XIcon />}
|
||||
</IconButton>
|
||||
<div
|
||||
data-cy='application-title'
|
||||
className='text-md text-green-800 dark:text-green-400 font-semibold'
|
||||
className='text-md font-semibold text-green-800 dark:text-green-400'
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
@ -152,7 +152,7 @@ export const Application: React.FC<ApplicationProps> = (props) => {
|
||||
{title.startsWith('#') && (
|
||||
<IconButton
|
||||
data-cy='icon-button-right-sidebar-members'
|
||||
className='p-2 h-10 w-10'
|
||||
className='h-10 w-10 p-2'
|
||||
onClick={() => handleToggleSidebars('right')}
|
||||
>
|
||||
{!visibleSidebars.right ? <UsersIcon /> : <XIcon />}
|
||||
@ -162,7 +162,7 @@ export const Application: React.FC<ApplicationProps> = (props) => {
|
||||
</header>
|
||||
|
||||
<main
|
||||
className='relative flex h-full-without-header overflow-hidden'
|
||||
className='h-full-without-header relative flex overflow-hidden'
|
||||
onClick={handleCloseSidebars}
|
||||
{...swipeableHandlers}
|
||||
>
|
||||
@ -171,10 +171,10 @@ export const Application: React.FC<ApplicationProps> = (props) => {
|
||||
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-4'>
|
||||
<div className='top-0 left-0 z-50 flex min-w-[92px] flex-col space-y-4 border-r-2 border-gray-500 bg-gray-200 py-2 dark:border-white/20 dark:bg-gray-800'>
|
||||
<IconLink
|
||||
href={`/application/users/${user.id}/settings`}
|
||||
selected={path === `/application/users/${user.id}/settings`}
|
||||
href={`/application/users/settings`}
|
||||
selected={path === `/application/users/settings`}
|
||||
title='Settings'
|
||||
>
|
||||
<Image
|
||||
@ -195,7 +195,7 @@ export const Application: React.FC<ApplicationProps> = (props) => {
|
||||
selected={path === '/application'}
|
||||
title='Join or create a Guild'
|
||||
>
|
||||
<PlusIcon className='w-12 h-12 text-green-800 dark:text-green-400' />
|
||||
<PlusIcon className='h-12 w-12 text-green-800 dark:text-green-400' />
|
||||
</IconLink>
|
||||
<Divider />
|
||||
<Guilds path={path} />
|
||||
@ -206,7 +206,7 @@ export const Application: React.FC<ApplicationProps> = (props) => {
|
||||
<div
|
||||
id='application-page-content'
|
||||
className={classNames(
|
||||
'top-0 h-full-without-header w-full flex flex-col flex-1 z-0 overflow-y-auto transition',
|
||||
'h-full-without-header top-0 z-0 flex w-full flex-1 flex-col overflow-y-auto transition',
|
||||
{
|
||||
'absolute opacity-20':
|
||||
isMobile && (visibleSidebars.left || visibleSidebars.right)
|
||||
|
@ -17,9 +17,9 @@ export const Channel: React.FC<ChannelProps> = (props) => {
|
||||
<Link href={`/application/${path.guildId}/${channel.id}`}>
|
||||
<a
|
||||
className={classNames(
|
||||
'hover:bg-gray-100 group flex items-center justify-between text-sm py-2 my-3 mx-3 transition-colors dark:hover:bg-gray-600 duration-200 rounded-lg',
|
||||
'group my-3 mx-3 flex items-center justify-between rounded-lg py-2 text-sm transition-colors duration-200 hover:bg-gray-100 dark:hover:bg-gray-600',
|
||||
{
|
||||
'text-green-800 dark:text-green-400 font-semibold': selected
|
||||
'font-semibold text-green-800 dark:text-green-400': selected
|
||||
}
|
||||
)}
|
||||
>
|
||||
|
@ -17,10 +17,10 @@ export const Channels: React.FC<ChannelsProps> = (props) => {
|
||||
return (
|
||||
<div
|
||||
id='channels'
|
||||
className='scrollbar-firefox-support overflow-y-auto flex-1 flex flex-col'
|
||||
className='scrollbar-firefox-support flex flex-1 flex-col overflow-y-auto'
|
||||
>
|
||||
<InfiniteScroll
|
||||
className='w-full channels-list'
|
||||
className='channels-list w-full'
|
||||
scrollableTarget='channels'
|
||||
dataLength={channels.length}
|
||||
next={nextPage}
|
||||
|
@ -16,17 +16,17 @@ export const ConfirmGuildJoin: React.FC<ConfirmGuildJoinProps> = (props) => {
|
||||
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='mt-8 flex flex-col'>
|
||||
<h1 className='mb-6 text-center text-xl'>Rejoindre la guild ?</h1>
|
||||
<div className='flex gap-7'>
|
||||
<button
|
||||
className='px-8 py-2 rounded-3xl bg-success hover:opacity-50 transition'
|
||||
className='rounded-3xl bg-success px-8 py-2 transition hover:opacity-50'
|
||||
onClick={handleJoinGuild}
|
||||
>
|
||||
Oui
|
||||
</button>
|
||||
<button
|
||||
className='px-8 py-2 rounded-3xl bg-error hover:opacity-50 transition'
|
||||
className='rounded-3xl bg-error px-8 py-2 transition hover:opacity-50'
|
||||
onClick={handleJoinGuild}
|
||||
>
|
||||
Non
|
||||
|
@ -18,10 +18,11 @@ export const CreateGuild: React.FC = () => {
|
||||
|
||||
const { fetchState, message, errors, getErrorTranslation, handleSubmit } =
|
||||
useForm({
|
||||
validateSchemaObject: {
|
||||
validateSchema: {
|
||||
name: guildSchema.name,
|
||||
description: guildSchema.description
|
||||
}
|
||||
},
|
||||
resetOnSuccess: true
|
||||
})
|
||||
|
||||
const { authentication } = useAuthentication()
|
||||
@ -59,7 +60,7 @@ export const CreateGuild: React.FC = () => {
|
||||
placeholder='Description'
|
||||
id='description'
|
||||
/>
|
||||
<Button className='w-full mt-6' type='submit' data-cy='submit'>
|
||||
<Button className='mt-6 w-full' type='submit' data-cy='submit'>
|
||||
{t('application:create')}
|
||||
</Button>
|
||||
</Form>
|
||||
|
@ -2,7 +2,7 @@ import { CogIcon, PlusIcon } from '@heroicons/react/solid'
|
||||
|
||||
import { useGuildMember } from '../../../contexts/GuildMember'
|
||||
import { Divider } from '../../design/Divider'
|
||||
import { Channels } from '../../Application/Channels'
|
||||
import { Channels } from '../Channels'
|
||||
import { IconButton } from '../../design/IconButton'
|
||||
import { GuildsChannelsPath } from '..'
|
||||
|
||||
@ -16,8 +16,8 @@ export const GuildLeftSidebar: React.FC<GuildLeftSidebarProps> = (props) => {
|
||||
const { guild } = useGuildMember()
|
||||
|
||||
return (
|
||||
<div className='flex flex-col justify-between w-full mt-2'>
|
||||
<div className='text-center p-2 mx-8 mt-2'>
|
||||
<div className='mt-2 flex w-full flex-col justify-between'>
|
||||
<div className='mx-8 mt-2 p-2 text-center'>
|
||||
<h2 data-cy='guild-left-sidebar-title' className='text-xl'>
|
||||
{guild.name}
|
||||
</h2>
|
||||
@ -25,7 +25,7 @@ export const GuildLeftSidebar: React.FC<GuildLeftSidebarProps> = (props) => {
|
||||
<Divider />
|
||||
<Channels path={path} />
|
||||
<Divider />
|
||||
<div className='flex justify-center items-center p-2 mb-1 space-x-6'>
|
||||
<div className='mb-1 flex items-center justify-center space-x-6 p-2'>
|
||||
<IconButton className='h-10 w-10' title='Add a Channel'>
|
||||
<PlusIcon />
|
||||
</IconButton>
|
||||
|
@ -17,7 +17,7 @@ export const Guilds: React.FC<GuildsProps> = (props) => {
|
||||
return (
|
||||
<div
|
||||
id='guilds-list'
|
||||
className='min-w-[92px] mt-[130px] pt-2 h-full border-r-1 border-gray-500 dark:border-white/20 space-y-2 scrollbar-firefox-support overflow-y-auto'
|
||||
className='border-r-1 scrollbar-firefox-support mt-[130px] h-full min-w-[92px] space-y-2 overflow-y-auto border-gray-500 pt-2 dark:border-white/20'
|
||||
>
|
||||
<InfiniteScroll
|
||||
className='guilds-list'
|
||||
|
@ -19,10 +19,10 @@ export const GuildPublic: React.FC<GuildPublicProps> = (props) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='relative overflow-hidden rounded border border-gray-500 dark:border-gray-700 hover:-translate-y-2 hover:shadow-none shadow-lg transition duration-200 ease-in-out'>
|
||||
<div className='relative overflow-hidden rounded border border-gray-500 shadow-lg transition duration-200 ease-in-out hover:-translate-y-2 hover:shadow-none dark:border-gray-700'>
|
||||
<div
|
||||
className={classNames(
|
||||
'flex flex-col items-center h-full justify-center cursor-pointer p-4 pt-8 transition duration-200 ease-in-out',
|
||||
'flex h-full cursor-pointer flex-col items-center justify-center p-4 pt-8 transition duration-200 ease-in-out',
|
||||
{ '-translate-x-full': isConfirmed }
|
||||
)}
|
||||
onClick={() => setIsConfirmed(!isConfirmed)}
|
||||
@ -36,10 +36,10 @@ export const GuildPublic: React.FC<GuildPublicProps> = (props) => {
|
||||
width={80}
|
||||
height={80}
|
||||
/>
|
||||
<div className='w-full px-4 m-2 text-center mt-6'>
|
||||
<div className='m-2 mt-6 w-full px-4 text-center'>
|
||||
<h3
|
||||
data-cy='guild-name'
|
||||
className='w-full center font-bold text-xl mb-2 truncate'
|
||||
className='center mb-2 w-full truncate text-xl font-bold'
|
||||
>
|
||||
{guild.name}
|
||||
</h3>
|
||||
@ -47,20 +47,20 @@ export const GuildPublic: React.FC<GuildPublicProps> = (props) => {
|
||||
{guild.description != null ? (
|
||||
guild.description
|
||||
) : (
|
||||
<span className='flex h-full opacity-25 justify-center items-center'>
|
||||
<span className='flex h-full items-center justify-center opacity-25'>
|
||||
<Emoji value=':eyes:' size={25} />
|
||||
<span className='ml-2'>Nothing's here...</span>
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<p className='flex flex-col text-green-800 dark:text-green-400 mt-auto'>
|
||||
<p className='mt-auto flex flex-col text-green-800 dark:text-green-400'>
|
||||
{guild.membersCount} {t('application:members')}
|
||||
</p>
|
||||
</div>
|
||||
<ConfirmGuildJoin
|
||||
className={classNames(
|
||||
'absolute w-full h-full flex flex-col w-ful h-ful top-1/2 -translate-y-1/2 left-full translate-x- rounded-2xl justify-center items-center transition-all',
|
||||
'w-ful h-ful translate-x- absolute top-1/2 left-full flex h-full w-full -translate-y-1/2 flex-col items-center justify-center rounded-2xl transition-all',
|
||||
{
|
||||
'!left-0': isConfirmed
|
||||
}
|
||||
|
@ -30,18 +30,18 @@ export const JoinGuildsPublic: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-col w-full h-full transition-all'>
|
||||
<div className='flex h-full w-full flex-col transition-all'>
|
||||
<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'
|
||||
className='my-6 mx-auto mt-16 w-10/12 rounded-md border border-gray-500 bg-white p-3 dark:border-gray-700 dark:bg-[#3B3B3B] sm:w-8/12 md:w-6/12 lg:w-5/12'
|
||||
type='search'
|
||||
name='search-guild'
|
||||
placeholder={`🔎 ${t('application:search')}...`}
|
||||
/>
|
||||
<div className='w-full p-12'>
|
||||
<InfiniteScroll
|
||||
className='guilds-public-list max-w-[1400px] mx-auto grid gap-8 grid-cols-[repeat(auto-fill,_minmax(20em,_1fr))] !overflow-visible'
|
||||
className='guilds-public-list mx-auto grid max-w-[1400px] grid-cols-[repeat(auto-fill,_minmax(20em,_1fr))] gap-8 !overflow-visible'
|
||||
dataLength={items.length}
|
||||
next={nextPage}
|
||||
scrollableTarget='application-page-content'
|
||||
|
@ -14,8 +14,8 @@ export const Member: React.FC<MemberProps> = (props) => {
|
||||
return (
|
||||
<Link href={`/application/users/${member.user.id}`}>
|
||||
<a>
|
||||
<div className='flex items-center cursor-pointer py-2 px-6 pr-10 overflow-hidden hover:bg-gray-300 dark:hover:bg-gray-900'>
|
||||
<div className='min-w-[50px] flex rounded-full'>
|
||||
<div className='flex cursor-pointer items-center overflow-hidden py-2 px-6 pr-10 hover:bg-gray-300 dark:hover:bg-gray-900'>
|
||||
<div className='flex min-w-[50px] rounded-full'>
|
||||
<Image
|
||||
src={
|
||||
member.user.logo == null
|
||||
@ -34,7 +34,7 @@ export const Member: React.FC<MemberProps> = (props) => {
|
||||
{member.user.name}
|
||||
</p>
|
||||
{member.user.status != null && (
|
||||
<span className='block truncate w-44'>{member.user.status}</span>
|
||||
<span className='block w-44 truncate'>{member.user.status}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -15,7 +15,7 @@ export const Members: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<div className='mb-2'>
|
||||
<h1 data-cy='members-title' className='text-center pt-2 my-2 text-xl'>
|
||||
<h1 data-cy='members-title' className='my-2 pt-2 text-center text-xl'>
|
||||
{capitalize(t('application:members'))}
|
||||
</h1>
|
||||
<Divider />
|
||||
|
@ -17,13 +17,13 @@ export const Message: React.FC<MessageProps> = (props) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className='p-4 flex transition hover:bg-gray-200 dark:hover:bg-gray-900'
|
||||
className='flex p-4 transition hover:bg-gray-200 dark:hover:bg-gray-900'
|
||||
data-cy={`message-${message.id}`}
|
||||
>
|
||||
<Link href={`/application/users/${message.member.user.id}`}>
|
||||
<a>
|
||||
<div className='w-12 h-12 mr-4 flex flex-shrink-0 items-center justify-center'>
|
||||
<div className='w-10 h-10 drop-shadow-md'>
|
||||
<div className='mr-4 flex h-12 w-12 flex-shrink-0 items-center justify-center'>
|
||||
<div className='h-10 w-10 drop-shadow-md'>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src={
|
||||
@ -41,7 +41,7 @@ export const Message: React.FC<MessageProps> = (props) => {
|
||||
</a>
|
||||
</Link>
|
||||
<div className='w-full'>
|
||||
<div className='w-max flex items-center'>
|
||||
<div className='flex w-max items-center'>
|
||||
<Link href={`/application/users/${message.member.user.id}`}>
|
||||
<a>
|
||||
<span
|
||||
@ -54,7 +54,7 @@ export const Message: React.FC<MessageProps> = (props) => {
|
||||
</Link>
|
||||
<span
|
||||
data-cy='message-date'
|
||||
className='text-gray-500 dark:text-gray-200 text-xs ml-4 select-none'
|
||||
className='ml-4 select-none text-xs text-gray-500 dark:text-gray-200'
|
||||
>
|
||||
{date.format(new Date(message.createdAt), 'DD/MM/YYYY - HH:mm:ss')}
|
||||
</span>
|
||||
|
@ -1,7 +1,7 @@
|
||||
export const FileIcon: React.FC = () => {
|
||||
return (
|
||||
<svg
|
||||
className='dark:text-white text-black fill-current'
|
||||
className='fill-current text-black dark:text-white'
|
||||
width='21'
|
||||
height='26'
|
||||
viewBox='0 0 21 26'
|
||||
|
@ -49,7 +49,7 @@ export const MessageFile: React.FC<MessageContentProps> = (props) => {
|
||||
<a href={file.url} target='_blank' rel='noreferrer'>
|
||||
<img
|
||||
data-cy={`message-file-image-${message.id}`}
|
||||
className='sm:max-w-xs max-h-80'
|
||||
className='max-h-80 sm:max-w-xs'
|
||||
src={file.url}
|
||||
alt={message.value}
|
||||
/>
|
||||
@ -66,7 +66,7 @@ export const MessageFile: React.FC<MessageContentProps> = (props) => {
|
||||
if (message.mimetype.startsWith('video/')) {
|
||||
return (
|
||||
<video
|
||||
className='max-w-xs max-h-80'
|
||||
className='max-h-80 max-w-xs'
|
||||
controls
|
||||
data-cy={`message-file-video-${message.id}`}
|
||||
>
|
||||
@ -86,7 +86,7 @@ export const MessageFile: React.FC<MessageContentProps> = (props) => {
|
||||
<p className='mt-1'>{prettyBytes(file.blob.size)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<DownloadIcon className='ml-4 w-8 h-8' />
|
||||
<DownloadIcon className='ml-4 h-8 w-8' />
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
|
@ -12,7 +12,7 @@ export const Messages: React.FC = () => {
|
||||
return (
|
||||
<div
|
||||
id='messages'
|
||||
className='w-full scrollbar-firefox-support overflow-y-auto transition-all flex-1 flex flex-col text-center mt-8 text-lg'
|
||||
className='scrollbar-firefox-support mt-8 flex w-full flex-1 flex-col overflow-y-auto text-center text-lg transition-all'
|
||||
>
|
||||
<p>
|
||||
Nothing to show here! <Emoji value=':ghost:' size={20} />
|
||||
@ -25,7 +25,7 @@ export const Messages: React.FC = () => {
|
||||
return (
|
||||
<div
|
||||
id='messages'
|
||||
className='w-full scrollbar-firefox-support overflow-y-auto transition-all flex-1 flex flex-col-reverse'
|
||||
className='scrollbar-firefox-support flex w-full flex-1 flex-col-reverse overflow-y-auto transition-all'
|
||||
>
|
||||
<InfiniteScroll
|
||||
scrollableTarget='messages'
|
||||
|
@ -17,7 +17,7 @@ export const PopupGuild: React.FC<PopupGuildProps> = (props) => {
|
||||
<div
|
||||
className={classNames(
|
||||
className,
|
||||
'flex p-8 flex-wrap justify-center items-center overflow-y-auto h-full-without-header min-w-full'
|
||||
'h-full-without-header flex min-w-full flex-wrap items-center justify-center overflow-y-auto p-8'
|
||||
)}
|
||||
>
|
||||
<PopupGuildCard
|
||||
@ -32,7 +32,7 @@ export const PopupGuild: React.FC<PopupGuildProps> = (props) => {
|
||||
}
|
||||
description={t('application:create-a-guild-description')}
|
||||
link={{
|
||||
icon: <PlusSmIcon className='w-8 h-8 mr-2' />,
|
||||
icon: <PlusSmIcon className='mr-2 h-8 w-8' />,
|
||||
text: t('application:create-a-guild'),
|
||||
href: '/application/guilds/create'
|
||||
}}
|
||||
@ -49,7 +49,7 @@ export const PopupGuild: React.FC<PopupGuildProps> = (props) => {
|
||||
}
|
||||
description={t('application:join-a-guild-description')}
|
||||
link={{
|
||||
icon: <ArrowDownIcon className='w-6 h-6 mr-2' />,
|
||||
icon: <ArrowDownIcon className='mr-2 h-6 w-6' />,
|
||||
text: t('application:join-a-guild'),
|
||||
href: '/application/guilds/join'
|
||||
}}
|
||||
|
@ -29,7 +29,7 @@ PopupGuildCard.args = {
|
||||
description:
|
||||
'Create your own guild and manage everything within a few clicks !',
|
||||
link: {
|
||||
icon: <PlusSmIcon className='w-8 h-8 mr-2' />,
|
||||
icon: <PlusSmIcon className='mr-2 h-8 w-8' />,
|
||||
text: 'Create a server',
|
||||
href: '/application/guilds/create'
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ describe('<PopupGuildCard />', () => {
|
||||
}
|
||||
description='Create your own guild and manage everything within a few clicks !'
|
||||
link={{
|
||||
icon: <PlusSmIcon className='w-8 h-8 mr-2' />,
|
||||
icon: <PlusSmIcon className='mr-2 h-8 w-8' />,
|
||||
text: 'Create a server',
|
||||
href: '/application/guilds/create'
|
||||
}}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
export interface PopupGuildCardProps {
|
||||
@ -14,16 +15,16 @@ 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'>
|
||||
<div className='m-8 h-96 w-80 rounded-2xl bg-gray-800'>
|
||||
<div className='flex h-1/2 w-full items-center justify-center'>
|
||||
{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 text-sm mt-6 text-center px-8'>
|
||||
<div className='mt-2 flex h-1/2 w-full flex-col justify-between rounded-b-2xl bg-gray-700 shadow-sm'>
|
||||
<p className='mt-6 px-8 text-center text-sm text-gray-200'>
|
||||
{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'>
|
||||
<a className='mb-6 flex h-10 w-4/5 items-center justify-center self-center rounded-2xl bg-green-400 font-bold tracking-wide text-white transition duration-200 ease-in-out hover:bg-green-600'>
|
||||
{link.icon}
|
||||
{link.text}
|
||||
</a>
|
||||
|
@ -80,14 +80,14 @@ export const SendMessage: React.FC<SendMessageProps> = (props) => {
|
||||
<>
|
||||
{isVisibleEmojiPicker && <EmojiPicker onClick={handleEmojiPicker} />}
|
||||
<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'>
|
||||
<div className='flex h-full w-full rounded-lg bg-gray-200 py-1 text-gray-600 dark:bg-gray-800 dark:text-gray-200'>
|
||||
<form
|
||||
className='w-full h-full flex items-center'
|
||||
className='flex h-full w-full items-center'
|
||||
onSubmit={handleSubmit}
|
||||
onKeyDown={handleTextareaKeyDown}
|
||||
>
|
||||
<TextareaAutosize
|
||||
className='w-full scrollbar-firefox-support p-2 px-6 my-2 bg-transparent outline-none font-paragraph tracking-wide resize-none'
|
||||
className='scrollbar-firefox-support my-2 w-full resize-none bg-transparent p-2 px-6 font-paragraph tracking-wide outline-none'
|
||||
placeholder={t('application:write-a-message')}
|
||||
wrap='soft'
|
||||
maxRows={6}
|
||||
@ -98,17 +98,17 @@ export const SendMessage: React.FC<SendMessageProps> = (props) => {
|
||||
autoFocus
|
||||
/>
|
||||
</form>
|
||||
<div className='h-full flex items-center justify-around pr-6'>
|
||||
<div className='flex h-full 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'
|
||||
className='flex h-full w-full items-center justify-center p-1 text-2xl transition hover:-translate-y-1'
|
||||
onClick={handleVisibleEmojiPicker}
|
||||
>
|
||||
🙂
|
||||
</button>
|
||||
<button className='cursor-pointer relative w-full h-full flex items-center justify-center p-1 text-green-800 dark:text-green-400 transition hover:-translate-y-1'>
|
||||
<button className='relative flex h-full w-full cursor-pointer items-center justify-center p-1 text-green-800 transition hover:-translate-y-1 dark:text-green-400'>
|
||||
<input
|
||||
type='file'
|
||||
className='absolute w-full h-full opacity-0 cursor-pointer'
|
||||
className='absolute h-full w-full cursor-pointer opacity-0'
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
<svg width='25' height='25' viewBox='0 0 22 22'>
|
||||
|
@ -17,9 +17,9 @@ export const Sidebar: React.FC<SidebarProps> = (props) => {
|
||||
return (
|
||||
<nav
|
||||
className={classNames(
|
||||
'h-full-without-header flex z-50 drop-shadow-2xl bg-gray-200 dark:bg-gray-800 transition-all',
|
||||
'h-full-without-header z-50 flex bg-gray-200 drop-shadow-2xl transition-all dark:bg-gray-800',
|
||||
{
|
||||
'top-0 right-0 scrollbar-firefox-support overflow-y-auto flex-col space-y-1':
|
||||
'scrollbar-firefox-support top-0 right-0 flex-col space-y-1 overflow-y-auto':
|
||||
direction === 'right',
|
||||
'w-72': direction === 'right' && visible,
|
||||
'w-0 opacity-0': !visible,
|
||||
|
@ -1,5 +1,9 @@
|
||||
import { Meta, Story } from '@storybook/react'
|
||||
|
||||
import {
|
||||
guildExample,
|
||||
guildExample2
|
||||
} from '../../../cypress/fixtures/guilds/guild'
|
||||
import {
|
||||
userExample,
|
||||
userSettingsExample
|
||||
@ -20,5 +24,6 @@ UserProfile.args = {
|
||||
user: {
|
||||
...userExample,
|
||||
settings: userSettingsExample
|
||||
}
|
||||
},
|
||||
guilds: [guildExample, guildExample2]
|
||||
}
|
||||
|
@ -1,5 +1,9 @@
|
||||
import { render } from '@testing-library/react'
|
||||
|
||||
import {
|
||||
guildExample,
|
||||
guildExample2
|
||||
} from '../../../cypress/fixtures/guilds/guild'
|
||||
import {
|
||||
userExample,
|
||||
userSettingsExample
|
||||
@ -9,7 +13,10 @@ import { UserProfile } from './UserProfile'
|
||||
describe('<UserProfile />', () => {
|
||||
it('should render successfully', () => {
|
||||
const { baseElement } = render(
|
||||
<UserProfile user={{ ...userExample, settings: userSettingsExample }} />
|
||||
<UserProfile
|
||||
user={{ ...userExample, settings: userSettingsExample }}
|
||||
guilds={[guildExample, guildExample2]}
|
||||
/>
|
||||
)
|
||||
expect(baseElement).toBeTruthy()
|
||||
})
|
||||
|
@ -9,15 +9,17 @@ import { API_URL } from '../../../tools/api'
|
||||
import { UserPublic } from '../../../models/User'
|
||||
import { UserProfileGuilds } from './UserProfileGuilds'
|
||||
import { UserProfileGuild } from './UserProfileGuilds/UserProfileGuild'
|
||||
import { Guild } from '../../../models/Guild'
|
||||
import { ConfirmGuildJoin } from '../ConfirmGuildJoin'
|
||||
|
||||
export interface UserProfileProps {
|
||||
className?: string
|
||||
user: UserPublic
|
||||
guilds: Guild[]
|
||||
}
|
||||
|
||||
export const UserProfile: React.FC<UserProfileProps> = (props) => {
|
||||
const { user } = props
|
||||
const { user, guilds } = props
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [showPopup, setShowPopup] = useState<boolean>(false)
|
||||
@ -32,16 +34,16 @@ export const UserProfile: React.FC<UserProfileProps> = (props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='relative h-full flex flex-col items-center justify-center'>
|
||||
<div className='relative flex h-full flex-col items-center justify-center'>
|
||||
<div
|
||||
className={classNames('transition', {
|
||||
'blur-3xl select-none': showPopup
|
||||
'select-none blur-3xl': showPopup
|
||||
})}
|
||||
>
|
||||
<div className='max-w-[1000px] px-12'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='w-max flex items-center'>
|
||||
<div className='relative flex justify-center items-center rounded-full overflow-hidden transition-all shadow-lg'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex w-max items-center'>
|
||||
<div className='relative flex items-center justify-center overflow-hidden rounded-full shadow-lg transition-all'>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src={
|
||||
@ -55,24 +57,31 @@ export const UserProfile: React.FC<UserProfileProps> = (props) => {
|
||||
width={125}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-col ml-10'>
|
||||
<div className='flex items-center mb-2'>
|
||||
<p className='text-3xl font-bold space tracking-wide text-white'>
|
||||
<div className='ml-10 flex flex-col'>
|
||||
<div className='mb-2 flex items-center'>
|
||||
<p
|
||||
className='space text-3xl font-bold tracking-wide text-white'
|
||||
data-cy='user-name'
|
||||
>
|
||||
{user.name}
|
||||
</p>
|
||||
<p className='ml-8 text-sm tracking-widest text-white opacity-40 select-none'>
|
||||
<p
|
||||
className='ml-8 select-none text-sm tracking-widest text-white opacity-40'
|
||||
data-cy='user-createdAt'
|
||||
>
|
||||
{date.format(new Date(user.createdAt), 'DD/MM/YYYY')}
|
||||
</p>
|
||||
</div>
|
||||
<div className='text-left my-2'>
|
||||
<div className='my-2 text-left'>
|
||||
{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'
|
||||
className='relative ml-2 font-normal tracking-wide no-underline opacity-80 transition-all after:absolute after:left-0 after:bottom-[-1px] after:h-[1px] after:w-0 after:bg-black after:transition-all hover:opacity-100 hover:after:w-full dark:after:bg-white'
|
||||
rel='noreferrer'
|
||||
data-cy='user-email'
|
||||
>
|
||||
{user.email}
|
||||
</a>
|
||||
@ -83,7 +92,7 @@ export const UserProfile: React.FC<UserProfileProps> = (props) => {
|
||||
{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-[-2px] after:bg-black dark:after:bg-white after:h-[1px] after:w-0 after:transition-all hover:after:w-full'
|
||||
className='relative ml-2 font-normal tracking-wide no-underline opacity-80 transition-all after:absolute after:left-0 after:bottom-[-2px] after:h-[1px] after:w-0 after:bg-black after:transition-all hover:opacity-100 hover:after:w-full dark:after:bg-white'
|
||||
>
|
||||
{user.website}
|
||||
</a>
|
||||
@ -104,6 +113,7 @@ export const UserProfile: React.FC<UserProfileProps> = (props) => {
|
||||
<div className='py-8 px-4' onClick={handlePopupVisibility}>
|
||||
<UserProfileGuilds
|
||||
isPublicGuilds={user.settings.isPublicGuilds}
|
||||
guilds={guilds}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -114,22 +124,24 @@ export const UserProfile: React.FC<UserProfileProps> = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* TODO: We might want to remove this code */}
|
||||
<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',
|
||||
'pointer-events-none absolute top-0 flex h-full w-full items-center justify-center bg-zinc-900/75 opacity-0 transition',
|
||||
{
|
||||
'opacity-100 visible pointer-events-auto': showPopup
|
||||
'pointer-events-auto visible opacity-100': 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',
|
||||
'relative h-[400px] w-[400px] scale-0 overflow-y-auto overflow-x-hidden rounded-2xl bg-gray-200 py-2 shadow-xl transition dark:bg-gray-800',
|
||||
{ 'scale-100': showPopup }
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={classNames('relative transition h-full', {
|
||||
className={classNames('relative h-full transition', {
|
||||
'-translate-x-[150%]': confirmation
|
||||
})}
|
||||
>
|
||||
@ -140,7 +152,7 @@ export const UserProfile: React.FC<UserProfileProps> = (props) => {
|
||||
|
||||
<ConfirmGuildJoin
|
||||
className={classNames(
|
||||
'absolute w-full h-full flex flex-col justify-center items-center transition-all top-0 left-[150%]',
|
||||
'absolute top-0 left-[150%] flex h-full w-full flex-col items-center justify-center transition-all',
|
||||
{ 'left-[0%]': confirmation }
|
||||
)}
|
||||
handleJoinGuild={handleConfirmationState}
|
||||
@ -149,7 +161,7 @@ export const UserProfile: React.FC<UserProfileProps> = (props) => {
|
||||
<XIcon
|
||||
height={40}
|
||||
onClick={() => setShowPopup(false)}
|
||||
className='absolute top-8 right-8 cursor-pointer hover:rotate-180 transition'
|
||||
className='absolute top-8 right-8 cursor-pointer transition hover:rotate-180'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -11,12 +11,12 @@ export const UserProfileGuild: React.FC<UserProfileGuildProps> = (props) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className='relative flex w-full cursor-pointer transition group'
|
||||
className='group relative flex w-full cursor-pointer transition'
|
||||
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'>
|
||||
<div className='relative w-full px-8 py-5 transition group-hover:-translate-x-20'>
|
||||
<div className='flex transition group-hover:opacity-40'>
|
||||
<div className='mr-8 flex min-h-[60px] min-w-[60px] select-none justify-center rounded-full drop-shadow-lg filter'>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src='/images/guilds/Guild_1.svg'
|
||||
@ -28,13 +28,13 @@ export const UserProfileGuild: React.FC<UserProfileGuildProps> = (props) => {
|
||||
</div>
|
||||
<div className='flex flex-col'>
|
||||
<h1 className='text-xl font-bold'>Guild Name</h1>
|
||||
<p className='text-gray-300 mt-2'>
|
||||
<p className='mt-2 text-gray-300'>
|
||||
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'>
|
||||
<div className='absolute top-0 right-[-80px] flex h-full w-[80px] items-center justify-center'>
|
||||
<LoginIcon
|
||||
height={40}
|
||||
className='fill-green-600 drop-shadow-[0_0_15px_rgba(22,163,74,0.50)]'
|
||||
|
@ -3,8 +3,11 @@ import classNames from 'classnames'
|
||||
import { EyeOffIcon } from '@heroicons/react/solid'
|
||||
import useTranslation from 'next-translate/useTranslation'
|
||||
|
||||
import { Guild } from '../../../../models/Guild'
|
||||
|
||||
export interface UserProfileGuildsProps {
|
||||
isPublicGuilds?: boolean
|
||||
guilds: Guild[]
|
||||
}
|
||||
|
||||
export const UserProfileGuilds: React.FC<UserProfileGuildsProps> = (props) => {
|
||||
@ -19,10 +22,10 @@ export const UserProfileGuilds: React.FC<UserProfileGuildsProps> = (props) => {
|
||||
>
|
||||
<div
|
||||
className={classNames('flex -space-x-7', {
|
||||
'blur-lg select-none': !isPublicGuilds
|
||||
'select-none blur-lg': !isPublicGuilds
|
||||
})}
|
||||
>
|
||||
<div className='flex justify-center items-center rounded-full filter drop-shadow-lg'>
|
||||
<div className='flex items-center justify-center rounded-full drop-shadow-lg filter'>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src='/images/guilds/Guild_1.svg'
|
||||
@ -32,7 +35,7 @@ export const UserProfileGuilds: React.FC<UserProfileGuildsProps> = (props) => {
|
||||
width={60}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-center items-center rounded-full filter drop-shadow-lg'>
|
||||
<div className='flex items-center justify-center rounded-full drop-shadow-lg filter'>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src='/images/guilds/Guild_2.svg'
|
||||
@ -42,7 +45,7 @@ export const UserProfileGuilds: React.FC<UserProfileGuildsProps> = (props) => {
|
||||
width={60}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-center items-center rounded-full filter drop-shadow-lg'>
|
||||
<div className='flex items-center justify-center rounded-full drop-shadow-lg filter'>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src='/images/guilds/Guild_3.svg'
|
||||
@ -52,7 +55,7 @@ export const UserProfileGuilds: React.FC<UserProfileGuildsProps> = (props) => {
|
||||
width={60}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-center items-center rounded-full filter drop-shadow-lg'>
|
||||
<div className='flex items-center justify-center rounded-full drop-shadow-lg filter'>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src='/images/guilds/Guild_4.svg'
|
||||
@ -62,7 +65,7 @@ export const UserProfileGuilds: React.FC<UserProfileGuildsProps> = (props) => {
|
||||
width={60}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-center items-center rounded-full filter drop-shadow-lg'>
|
||||
<div className='flex items-center justify-center rounded-full drop-shadow-lg filter'>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src='/images/guilds/Guild_5.svg'
|
||||
@ -72,7 +75,7 @@ export const UserProfileGuilds: React.FC<UserProfileGuildsProps> = (props) => {
|
||||
width={60}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-center items-center rounded-full filter drop-shadow-lg'>
|
||||
<div className='flex items-center justify-center rounded-full drop-shadow-lg filter'>
|
||||
<Image
|
||||
className='rounded-full'
|
||||
src='/images/guilds/Guild_6.svg'
|
||||
@ -82,20 +85,20 @@ export const UserProfileGuilds: React.FC<UserProfileGuildsProps> = (props) => {
|
||||
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'>
|
||||
<div className='z-10 flex h-[60px] w-[60px] items-center justify-center rounded-full bg-gray-300 drop-shadow-lg filter dark:bg-gray-800'>
|
||||
<span className='select-none text-xl font-bold text-black dark:text-white'>
|
||||
+4
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute flex items-center top-1/2 -translate-y-1/2',
|
||||
'absolute top-1/2 flex -translate-y-1/2 items-center',
|
||||
{ hidden: isPublicGuilds }
|
||||
)}
|
||||
>
|
||||
<EyeOffIcon height={25} />
|
||||
<p className='drop-shadow-2xl ml-4'>
|
||||
<p className='ml-4 drop-shadow-2xl'>
|
||||
{t('application:private-user-guilds-list')}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -1,19 +0,0 @@
|
||||
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 } }
|
@ -1,16 +0,0 @@
|
||||
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()
|
||||
})
|
||||
})
|
@ -1,9 +1,12 @@
|
||||
import Image from 'next/image'
|
||||
import { PhotographIcon } from '@heroicons/react/solid'
|
||||
import useTranslation from 'next-translate/useTranslation'
|
||||
import { useState } from 'react'
|
||||
import { Form } from 'react-component-form'
|
||||
import { PhotographIcon } from '@heroicons/react/solid'
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import axios from 'axios'
|
||||
|
||||
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'
|
||||
@ -11,32 +14,169 @@ import { Textarea } from '../../design/Textarea'
|
||||
import { SocialMediaButton } from '../../design/SocialMediaButton'
|
||||
import { SwitchTheme } from '../../Header/SwitchTheme'
|
||||
import { Language } from '../../Header/Language'
|
||||
import { useAuthentication } from '../../../tools/authentication'
|
||||
import { Button } from '../../design/Button'
|
||||
import { FormState } from '../../design/FormState'
|
||||
import { useForm, HandleSubmitCallback } from '../../../hooks/useForm'
|
||||
import { userCurrentSchema, userSchema } from '../../../models/User'
|
||||
import { userSettingsSchema } from '../../../models/UserSettings'
|
||||
import { useGuilds } from '../../../contexts/Guilds'
|
||||
|
||||
export interface UserSettingsProps {
|
||||
user: UserPublic
|
||||
}
|
||||
|
||||
export const UserSettings: React.FC<UserSettingsProps> = (props) => {
|
||||
const { user } = props
|
||||
export const UserSettings: React.FC = () => {
|
||||
const { user, setUser, authentication } = useAuthentication()
|
||||
const { guilds } = useGuilds()
|
||||
const { t } = useTranslation()
|
||||
const [inputValues, setInputValues] = useState({
|
||||
name: user.name,
|
||||
status: user.status,
|
||||
email: user.email,
|
||||
website: user.website,
|
||||
biography: user.biography,
|
||||
isPublicGuilds: user.settings.isPublicGuilds,
|
||||
isPublicEmail: user.settings.isPublicEmail
|
||||
})
|
||||
|
||||
const {
|
||||
fetchState,
|
||||
setFetchState,
|
||||
message,
|
||||
setMessageTranslationKey,
|
||||
errors,
|
||||
getErrorTranslation,
|
||||
handleSubmit
|
||||
} = useForm({
|
||||
validateSchema: {
|
||||
name: userSchema.name,
|
||||
status: Type.Optional(userSchema.status),
|
||||
email: Type.Optional(userCurrentSchema.email),
|
||||
website: Type.Optional(userSchema.website),
|
||||
biography: Type.Optional(userSchema.biography),
|
||||
isPublicGuilds: userSettingsSchema.isPublicGuilds,
|
||||
isPublicEmail: userSettingsSchema.isPublicEmail
|
||||
},
|
||||
replaceEmptyStringToNull: true,
|
||||
resetOnSuccess: false
|
||||
})
|
||||
|
||||
const onSubmit: HandleSubmitCallback = async (formData) => {
|
||||
try {
|
||||
const { isPublicGuilds, isPublicEmail, ...userData } = formData
|
||||
const userSettings = { isPublicEmail, isPublicGuilds }
|
||||
const { data: userCurrentData } = await authentication.api.put(
|
||||
`/users/current?redirectURI=${window.location.origin}/authentication/signin`,
|
||||
userData
|
||||
)
|
||||
const { data: userCurrentSettings } = await authentication.api.put(
|
||||
'/users/current/settings',
|
||||
userSettings
|
||||
)
|
||||
setUser((oldUser) => {
|
||||
return {
|
||||
...oldUser,
|
||||
...userCurrentData,
|
||||
settings: userCurrentSettings.settings
|
||||
}
|
||||
})
|
||||
setInputValues(formData as any)
|
||||
return {
|
||||
type: 'success',
|
||||
value: 'common:name'
|
||||
}
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error) && error.response?.status === 400) {
|
||||
const message = error.response.data.message as string
|
||||
if (message.endsWith('already taken.')) {
|
||||
return {
|
||||
type: 'error',
|
||||
value: 'authentication:already-used'
|
||||
}
|
||||
} else if (message.endsWith('email to sign in.')) {
|
||||
return {
|
||||
type: 'error',
|
||||
value: 'authentication:email-required-to-sign-in'
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'error',
|
||||
value: 'errors:server-error'
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'error',
|
||||
value: 'errors:server-error'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onChange: React.ChangeEventHandler<
|
||||
HTMLInputElement | HTMLTextAreaElement
|
||||
> = (event) => {
|
||||
setInputValues((oldInputValues) => {
|
||||
return {
|
||||
...oldInputValues,
|
||||
[event.target.name]: event.target.value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onChangeCheckbox: React.ChangeEventHandler<HTMLInputElement> = (
|
||||
event
|
||||
) => {
|
||||
setInputValues((oldInputValues) => {
|
||||
return {
|
||||
...oldInputValues,
|
||||
[event.target.name]: event.target.checked
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleFileChange: React.ChangeEventHandler<HTMLInputElement> = async (
|
||||
event
|
||||
) => {
|
||||
const files = event?.target?.files
|
||||
if (files != null && files.length === 1) {
|
||||
const file = files[0]
|
||||
const formData = new FormData()
|
||||
formData.append('logo', file)
|
||||
try {
|
||||
const { data } = await authentication.api.put(
|
||||
`/users/current/logo`,
|
||||
formData
|
||||
)
|
||||
setUser((oldUser) => {
|
||||
return {
|
||||
...oldUser,
|
||||
logo: data.user.logo
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
setFetchState('error')
|
||||
setMessageTranslationKey('errors:server-error')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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'>
|
||||
<Form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className='my-auto flex flex-col items-center justify-center py-12 lg:min-w-[875px]'
|
||||
>
|
||||
<div className='flex w-full flex-col items-center justify-center sm:w-fit lg:flex-row'>
|
||||
<div className=' flex w-full flex-wrap items-center justify-center px-6 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'>
|
||||
<div className='absolute z-50 h-full w-full'>
|
||||
<button className='relative flex h-full w-full items-center justify-center transition hover:scale-110'>
|
||||
<input
|
||||
type='file'
|
||||
className='absolute w-full h-full opacity-0 cursor-pointer'
|
||||
className='absolute h-full w-full cursor-pointer opacity-0'
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
<PhotographIcon color='white' className='w-8 h-8' />
|
||||
<PhotographIcon color='white' className='h-8 w-8' />
|
||||
</button>
|
||||
</div>
|
||||
<div className='flex justify-center items-center rounded-full shadow-xl bg-black'>
|
||||
<div className='flex items-center justify-center rounded-full bg-black shadow-xl'>
|
||||
<Image
|
||||
className='opacity-50 rounded-full'
|
||||
className='rounded-full opacity-50'
|
||||
src={
|
||||
user.logo != null
|
||||
? API_URL + user.logo
|
||||
@ -49,53 +189,78 @@ export const UserSettings: React.FC<UserSettingsProps> = (props) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col mx-12'>
|
||||
<div className='mx-12 flex flex-col'>
|
||||
<Input
|
||||
name='name'
|
||||
label={t('common:name')}
|
||||
placeholder={t('common:name')}
|
||||
className='!mt-0'
|
||||
defaultValue={user.name}
|
||||
onChange={onChange}
|
||||
value={inputValues.name ?? ''}
|
||||
error={getErrorTranslation(errors.name)}
|
||||
/>
|
||||
<Input
|
||||
name='status'
|
||||
label={t('application:status')}
|
||||
placeholder={t('application:status')}
|
||||
className='!mt-4'
|
||||
defaultValue={user.status ?? ''}
|
||||
onChange={onChange}
|
||||
value={inputValues.status ?? ''}
|
||||
error={getErrorTranslation(errors.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} />
|
||||
<div className='mt-10 ml-0 flex flex-col items-center lg:ml-24 lg:mt-0'>
|
||||
<UserProfileGuilds
|
||||
isPublicGuilds={inputValues.isPublicGuilds}
|
||||
guilds={guilds}
|
||||
/>
|
||||
<Checkbox
|
||||
name='isPublicGuilds'
|
||||
label={t('application:label-checkbox-guilds')}
|
||||
defaultChecked={user.settings.isPublicGuilds}
|
||||
onChange={onChangeCheckbox}
|
||||
checked={inputValues.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 ?? ''} />
|
||||
<div className='mt-12 flex w-full flex-col items-center justify-between sm:w-fit lg:flex-row'>
|
||||
<div className='w-4/5 pr-0 sm:w-[450px] lg:border-r-[1px] lg:border-neutral-700 lg:pr-12'>
|
||||
<Input
|
||||
name='email'
|
||||
label='Email'
|
||||
placeholder='Email'
|
||||
onChange={onChange}
|
||||
value={inputValues.email ?? ''}
|
||||
error={getErrorTranslation(errors.email)}
|
||||
/>
|
||||
<Checkbox
|
||||
name='isPublicEmail'
|
||||
label={t('application:label-checkbox-email')}
|
||||
id='checkbox-email-visibility'
|
||||
defaultChecked={user.settings.isPublicEmail}
|
||||
onChange={onChangeCheckbox}
|
||||
checked={inputValues.isPublicEmail}
|
||||
/>
|
||||
<Input
|
||||
name='website'
|
||||
label={t('application:website')}
|
||||
placeholder={t('application:website')}
|
||||
defaultValue={user.website ?? ''}
|
||||
onChange={onChange}
|
||||
value={inputValues.website ?? ''}
|
||||
error={getErrorTranslation(errors.website)}
|
||||
/>
|
||||
<Textarea
|
||||
name='biography'
|
||||
label={t('application:biography')}
|
||||
placeholder={t('application:biography')}
|
||||
id='textarea-biography'
|
||||
defaultValue={user.biography ?? ''}
|
||||
onChange={onChange}
|
||||
value={inputValues.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'>
|
||||
<div className='flex h-full w-4/5 flex-col items-center justify-between pr-0 sm:w-[415px] lg:pl-12'>
|
||||
<div className='mt-14 flex w-full flex-col gap-4'>
|
||||
<SocialMediaButton
|
||||
socialMedia='Google'
|
||||
className='w-full justify-center'
|
||||
@ -109,12 +274,17 @@ export const UserSettings: React.FC<UserSettingsProps> = (props) => {
|
||||
className='w-full justify-center'
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-between w-full pt-14'>
|
||||
<div className='flex w-full justify-between pt-14'>
|
||||
<Language />
|
||||
<SwitchTheme />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-12 flex flex-col items-center justify-center sm:w-fit'>
|
||||
<Button type='submit'>Sauvegarder</Button>
|
||||
<FormState state={fetchState} message={message} />
|
||||
</div>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
Reference in New Issue
Block a user