8 Commits

16 changed files with 132 additions and 96 deletions

View File

@ -18,7 +18,7 @@
Thream's website to stay close with your friends and communities.
It uses [Thream/api](https://github.com/Thream/api) [v1.0.0](https://github.com/Thream/api/releases/tag/v1.0.0).
It uses [Thream/api](https://github.com/Thream/api) [v1.0.1](https://github.com/Thream/api/releases/tag/v1.0.1).
## ⚙️ Getting Started

View File

@ -121,13 +121,15 @@ export const Application: React.FC<ApplicationProps> = (props) => {
})
},
onSwipedLeft: () => {
if (visibleSidebars.left) {
return setVisibleSidebars({ ...visibleSidebars, left: false })
if (isGuildsChannelsPath(path)) {
if (visibleSidebars.left) {
return setVisibleSidebars({ ...visibleSidebars, left: false })
}
setVisibleSidebars({
...visibleSidebars,
right: true
})
}
setVisibleSidebars({
...visibleSidebars,
right: true
})
}
})

View File

@ -2,6 +2,7 @@ import { useRouter } from 'next/router'
import { useState } from 'react'
import { Form } from 'react-component-form'
import useTranslation from 'next-translate/useTranslation'
import axios from 'axios'
import { HandleSubmitCallback, useForm } from '../../../hooks/useForm'
import { FormState } from '../../design/FormState'
@ -81,7 +82,11 @@ export const ChannelSettings: React.FC<ChannelSettingsProps> = (props) => {
await router.push(`/application/${guild.id}/${data.defaultChannelId}`)
} catch (error) {
setFetchState('error')
setMessageTranslationKey('errors:server-error')
if (axios.isAxiosError(error) && error.response?.status === 400) {
setMessageTranslationKey('application:delete-channel-only-one')
} else {
setMessageTranslationKey('errors:server-error')
}
}
}

View File

@ -16,84 +16,94 @@ export const UserProfile: React.FC<UserProfileProps> = (props) => {
const { t } = useTranslation()
return (
<div className='relative flex h-full flex-col items-center justify-center'>
<div className='transition'>
<div className='max-w-[1000px] px-12'>
<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
quality={100}
className='rounded-full'
src={
user.logo != null
? user.logo
: '/images/data/user-default.png'
}
alt='Profil Picture'
draggable='false'
height={125}
width={125}
/>
</div>
<div className='ml-10 flex flex-col'>
<div className='mb-2 flex items-center'>
<p
className='space text-dark text-3xl font-bold tracking-wide dark:text-white'
data-cy='user-name'
>
{user.name}
</p>
<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 className='relative flex h-full flex-col items-center justify-center'>
<div className='transition'>
<div className='max-w-[1000px] px-12'>
<div className='flex items-center justify-between'>
<div className='flex w-max flex-col items-center gap-7 md:flex-row'>
<div className='relative flex items-center justify-center overflow-hidden rounded-full shadow-lg transition-all'>
<Image
quality={100}
className='rounded-full'
src={
user.logo != null
? user.logo
: '/images/data/user-default.png'
}
alt='Profil Picture'
draggable='false'
height={125}
width={125}
/>
</div>
<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 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>
<div className='ml-10 flex flex-col'>
<div className='mb-2 flex items-center'>
<p
className='space text-dark text-3xl font-bold tracking-wide dark:text-white'
data-cy='user-name'
>
{user.name}
</p>
)}
{user.website != null && (
<p className='font-bold'>
{t('application:website')}:{' '}
<a
href={user.website}
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>
<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>
)}
{user.status != null && (
<p className='flex font-bold'>
{t('application:status')}:{' '}
<span className='ml-2 font-normal tracking-wide'>
{user.status}
</span>
</p>
)}
</div>
<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 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>
</p>
)}
{user.website != null && (
<p className='font-bold'>
{t('application:website')}:{' '}
<a
href={user.website}
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>
</p>
)}
{user.status != null && (
<p className='flex font-bold'>
{t('application:status')}:{' '}
<span className='ml-2 font-normal tracking-wide'>
{user.status}
</span>
</p>
)}
</div>
</div>
</div>
</div>
</div>
<div className='mt-7'>
{user.biography != null && <p>{user.biography}</p>}
{user.biography != null && (
<div className='mt-7 text-center'>
<p>{user.biography}</p>
</div>
)}
</div>
</div>
</div>
</div>
<style jsx global>{`
#application-page-content {
overflow-x: hidden;
}
`}</style>
</>
)
}

View File

@ -84,7 +84,10 @@ export const UserSettings: React.FC = () => {
return {
...oldUser,
...userCurrentData,
settings: userCurrentSettings.settings
settings: {
...oldUser.settings,
...userCurrentSettings.settings
}
}
})
return {

View File

@ -2,8 +2,12 @@
"website": "Website",
"create": "Create",
"status": "Status",
"create-a-channel": "Create a channel",
"delete-channel-only-one": "The guild should have at least one channel. You can't delete the only channel of the guild.",
"create-a-guild": "Create a Guild",
"create-a-guild-description": "Create your own guild and manage everything.",
"user-settings": "User settings",
"guild-settings": "Guild settings",
"join-a-guild": "Join a Guild",
"join-the-guild": "Join the guild",
"join-a-guild-description": "Talk, collaborate, share and have fun with your friends by joining an already existing guild!",

View File

@ -2,8 +2,12 @@
"website": "Site web",
"create": "Créer",
"status": "Statut",
"create-a-channel": "Créer un channel",
"delete-channel-only-one": "La guilde doit avoir au moins un channel. Vous ne pouvez pas supprimer le seul channel de la guilde.",
"create-a-guild": "Créer une Guilde",
"create-a-guild-description": "Créez votre propre guilde et gérez tout.",
"user-settings": "Paramètres utilisateur",
"guild-settings": "Paramètres de la guilde",
"join-a-guild": "Rejoindre une Guilde",
"join-the-guild": "Rejoindre la guilde",
"join-a-guild-description": "Discutez, collaborez, partagez et amusez-vous avec vos amis en rejoignant une guilde déjà existante!",

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "@thream/website",
"version": "1.0.0",
"version": "1.0.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@thream/website",
"version": "1.0.0",
"version": "1.0.2",
"hasInstallScript": true,
"dependencies": {
"@fontsource/montserrat": "4.5.7",

View File

@ -1,6 +1,6 @@
{
"name": "@thream/website",
"version": "1.0.0",
"version": "1.0.2",
"private": true,
"repository": {
"type": "git",

View File

@ -43,7 +43,7 @@ const ChannelPage: NextPage<ChannelPageProps> = (props) => {
<MembersProviders path={path}>
<ChannelsProvider path={path}>
<MessagesProvider path={path}>
<Head title='Thream | Application' />
<Head title={`Thream | ${selectedChannel.name}`} />
<Application
path={path}
guildLeftSidebar={<GuildLeftSidebar path={path} />}

View File

@ -40,7 +40,7 @@ const ChannelSettingsPage: NextPage<ChannelSettingsPageProps> = (props) => {
<GuildMemberProvider guildMember={guildMember} path={path}>
<MembersProviders path={path}>
<ChannelsProvider path={path}>
<Head title='Thream | Application' />
<Head title={`Thream | ${selectedChannel.name}`} />
<Application
path={path}
guildLeftSidebar={<GuildLeftSidebar path={path} />}

View File

@ -1,4 +1,5 @@
import { NextPage } from 'next'
import useTranslation from 'next-translate/useTranslation'
import { Head } from '../../../../components/Head'
import { Application } from '../../../../components/Application'
@ -21,6 +22,7 @@ export interface CreateChannelPageProps extends PagePropsWithAuthentication {
const CreateChannelPage: NextPage<CreateChannelPageProps> = (props) => {
const { guildId, authentication, guildMember } = props
const { t } = useTranslation()
const path = { guildId }
@ -29,10 +31,10 @@ const CreateChannelPage: NextPage<CreateChannelPageProps> = (props) => {
<GuildsProvider>
<GuildMemberProvider guildMember={guildMember} path={path}>
<Head
title={`Thream | Crée un channel`}
description={'Crée un nouveau channel'}
title={`Thream | ${t('application:create-a-channel')}`}
description={t('application:create-a-channel')}
/>
<Application path={path} title={'Crée un channel'}>
<Application path={path} title={t('application:create-a-channel')}>
<CreateChannel />
</Application>
</GuildMemberProvider>

View File

@ -1,4 +1,5 @@
import { NextPage } from 'next'
import useTranslation from 'next-translate/useTranslation'
import { Head } from '../../../components/Head'
import { Application } from '../../../components/Application'
@ -18,6 +19,7 @@ export interface GuildSettingsPageProps extends PagePropsWithAuthentication {
const GuildSettingsPage: NextPage<GuildSettingsPageProps> = (props) => {
const { guildId, authentication, guildMember } = props
const { t } = useTranslation()
const path = { guildId }
@ -25,8 +27,8 @@ const GuildSettingsPage: NextPage<GuildSettingsPageProps> = (props) => {
<AuthenticationProvider authentication={authentication}>
<GuildsProvider>
<GuildMemberProvider guildMember={guildMember} path={path}>
<Head title='Thream | Guild settings' />
<Application path={path} title='Guild settings'>
<Head title={`Thream | ${t('application:guild-settings')}`} />
<Application path={path} title={t('application:guild-settings')}>
<GuildSettings />
</Application>
</GuildMemberProvider>

View File

@ -1,4 +1,5 @@
import { NextPage } from 'next'
import useTranslation from 'next-translate/useTranslation'
import { Head } from '../../../components/Head'
import { Application } from '../../../components/Application'
@ -11,11 +12,16 @@ import { UserSettings } from '../../../components/Application/UserSettings'
import { GuildsProvider } from '../../../contexts/Guilds'
const UserSettingsPage: NextPage<PagePropsWithAuthentication> = (props) => {
const { t } = useTranslation()
return (
<AuthenticationProvider authentication={props.authentication}>
<GuildsProvider>
<Head title='Thream | Settings' />
<Application path={`/application/users/settings`} title='Settings'>
<Head title={`Thream | ${t('application:user-settings')}`} />
<Application
path='/application/users/settings'
title={t('application:user-settings')}
>
<UserSettings />
</Application>
</GuildsProvider>

View File

@ -1,6 +1,6 @@
import axios from 'axios'
export const API_VERSION = '1.0.0'
export const API_VERSION = '1.0.1'
export const API_DEFAULT_PORT = 8080

View File

@ -5,7 +5,6 @@ import useTranslation from 'next-translate/useTranslation'
import { Authentication, PagePropsWithAuthentication } from '.'
import { UserCurrent } from '../../models/User'
import { Language, Theme } from '../../models/UserSettings'
export interface AuthenticationValue {
authentication: Authentication
@ -41,14 +40,13 @@ export const AuthenticationProvider: React.FC<PagePropsWithAuthentication> = (
useEffect(() => {
authentication.api
.put('/users/current/settings', { theme, language: lang })
.then(() => {
.then(({ data: userCurrentSettings }) => {
setUser((oldUser) => {
return {
...oldUser,
settings: {
...oldUser.settings,
theme: theme as Theme,
language: lang as Language
...userCurrentSettings.settings
}
}
})