fix: file upload and OAuth2 (#26)
This commit is contained in:
		| @@ -12,7 +12,6 @@ import { Guilds } from './Guilds/Guilds' | ||||
| import { Divider } from '../design/Divider' | ||||
| import { Members } from './Members' | ||||
| import { useAuthentication } from '../../tools/authentication' | ||||
| import { API_URL } from '../../tools/api' | ||||
|  | ||||
| export interface ChannelsPath { | ||||
|   channelId: number | ||||
| @@ -189,7 +188,7 @@ export const Application: React.FC<ApplicationProps> = (props) => { | ||||
|                 src={ | ||||
|                   user.logo == null | ||||
|                     ? '/images/data/user-default.png' | ||||
|                     : API_URL + user.logo | ||||
|                     : user.logo | ||||
|                 } | ||||
|                 alt={"Users's profil picture"} | ||||
|                 draggable={false} | ||||
|   | ||||
| @@ -9,7 +9,6 @@ import useTranslation from 'next-translate/useTranslation' | ||||
| import { HandleSubmitCallback, useForm } from '../../../hooks/useForm' | ||||
| import { guildSchema } from '../../../models/Guild' | ||||
| import { FormState } from '../../design/FormState' | ||||
| import { API_URL } from '../../../tools/api' | ||||
| import { useGuildMember } from '../../../contexts/GuildMember' | ||||
| import { Textarea } from '../../design/Textarea' | ||||
| import { Input } from '../../design/Input' | ||||
| @@ -135,7 +134,7 @@ export const GuildSettings: React.FC = () => { | ||||
|                   src={ | ||||
|                     guild.icon == null | ||||
|                       ? '/images/data/guild-default.png' | ||||
|                       : API_URL + guild.icon | ||||
|                       : guild.icon | ||||
|                   } | ||||
|                   alt='Profil Picture' | ||||
|                   draggable='false' | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import Image from 'next/image' | ||||
|  | ||||
| import { API_URL } from '../../../../tools/api' | ||||
| import { GuildWithDefaultChannelId } from '../../../../models/Guild' | ||||
| import { IconLink } from '../../../design/IconLink' | ||||
|  | ||||
| @@ -24,9 +23,7 @@ export const Guild: React.FC<GuildProps> = (props) => { | ||||
|           quality={100} | ||||
|           className='rounded-full' | ||||
|           src={ | ||||
|             guild.icon != null | ||||
|               ? API_URL + guild.icon | ||||
|               : '/images/data/guild-default.png' | ||||
|             guild.icon != null ? guild.icon : '/images/data/guild-default.png' | ||||
|           } | ||||
|           alt='logo' | ||||
|           width={48} | ||||
|   | ||||
| @@ -7,7 +7,6 @@ import axios from 'axios' | ||||
|  | ||||
| import { Emoji } from '../../../Emoji' | ||||
| import { ConfirmGuildJoin } from '../../ConfirmGuildJoin' | ||||
| import { API_URL } from '../../../../tools/api' | ||||
| import { | ||||
|   GuildPublic as GuildPublicType, | ||||
|   GuildWithDefaultChannelId | ||||
| @@ -64,9 +63,7 @@ export const GuildPublic: React.FC<GuildPublicProps> = (props) => { | ||||
|           quality={100} | ||||
|           className='rounded-full' | ||||
|           src={ | ||||
|             guild.icon != null | ||||
|               ? API_URL + guild.icon | ||||
|               : '/images/data/guild-default.png' | ||||
|             guild.icon != null ? guild.icon : '/images/data/guild-default.png' | ||||
|           } | ||||
|           alt='logo' | ||||
|           width={80} | ||||
|   | ||||
| @@ -2,7 +2,6 @@ import Image from 'next/image' | ||||
| import Link from 'next/link' | ||||
|  | ||||
| import { MemberWithPublicUser } from '../../../../models/Member' | ||||
| import { API_URL } from '../../../../tools/api' | ||||
|  | ||||
| export interface MemberProps { | ||||
|   member: MemberWithPublicUser | ||||
| @@ -20,7 +19,7 @@ export const Member: React.FC<MemberProps> = (props) => { | ||||
|               src={ | ||||
|                 member.user.logo == null | ||||
|                   ? '/images/data/user-default.png' | ||||
|                   : API_URL + member.user.logo | ||||
|                   : member.user.logo | ||||
|               } | ||||
|               alt={"Users's profil picture"} | ||||
|               height={50} | ||||
|   | ||||
| @@ -3,7 +3,6 @@ import Link from 'next/link' | ||||
| import date from 'date-and-time' | ||||
|  | ||||
| import { MessageWithMember } from '../../../../models/Message' | ||||
| import { API_URL } from '../../../../tools/api' | ||||
| import { MessageText } from './MessageText' | ||||
| import { Loader } from '../../../design/Loader' | ||||
| import { MessageFile } from './MessageFile' | ||||
| @@ -30,7 +29,7 @@ export const Message: React.FC<MessageProps> = (props) => { | ||||
|                 src={ | ||||
|                   message.member.user.logo == null | ||||
|                     ? '/images/data/user-default.png' | ||||
|                     : API_URL + message.member.user.logo | ||||
|                     : message.member.user.logo | ||||
|                 } | ||||
|                 alt={"Users's profil picture"} | ||||
|                 width={50} | ||||
|   | ||||
| @@ -3,10 +3,10 @@ import axios from 'axios' | ||||
| import prettyBytes from 'pretty-bytes' | ||||
| import { DownloadIcon } from '@heroicons/react/solid' | ||||
|  | ||||
| import { useAuthentication } from '../../../../../tools/authentication' | ||||
| import { MessageWithMember } from '../../../../../models/Message' | ||||
| import { Loader } from '../../../../design/Loader' | ||||
| import { FileIcon } from './FileIcon' | ||||
| import { api } from '../../../../../tools/api' | ||||
|  | ||||
| const supportedImageMimetype = [ | ||||
|   'image/png', | ||||
| @@ -27,14 +27,13 @@ export interface MessageContentProps { | ||||
| export const MessageFile: React.FC<MessageContentProps> = (props) => { | ||||
|   const { message } = props | ||||
|  | ||||
|   const { authentication } = useAuthentication() | ||||
|   const [file, setFile] = useState<FileData | null>(null) | ||||
|  | ||||
|   useEffect(() => { | ||||
|     const ourRequest = axios.CancelToken.source() | ||||
|  | ||||
|     const fetchData = async (): Promise<void> => { | ||||
|       const { data } = await authentication.api.get(message.value, { | ||||
|       const { data } = await api.get(message.value, { | ||||
|         responseType: 'blob', | ||||
|         cancelToken: ourRequest.token | ||||
|       }) | ||||
| @@ -46,7 +45,7 @@ export const MessageFile: React.FC<MessageContentProps> = (props) => { | ||||
|     return () => { | ||||
|       ourRequest.cancel() | ||||
|     } | ||||
|   }, [message.value, authentication.api]) | ||||
|   }, [message.value]) | ||||
|  | ||||
|   if (file == null) { | ||||
|     return <Loader /> | ||||
|   | ||||
| @@ -2,7 +2,6 @@ import Image from 'next/image' | ||||
| import date from 'date-and-time' | ||||
| import useTranslation from 'next-translate/useTranslation' | ||||
|  | ||||
| import { API_URL } from '../../../tools/api' | ||||
| import { UserPublic } from '../../../models/User' | ||||
| import { Guild } from '../../../models/Guild' | ||||
|  | ||||
| @@ -28,7 +27,7 @@ export const UserProfile: React.FC<UserProfileProps> = (props) => { | ||||
|                   className='rounded-full' | ||||
|                   src={ | ||||
|                     user.logo != null | ||||
|                       ? API_URL + user.logo | ||||
|                       ? user.logo | ||||
|                       : '/images/data/user-default.png' | ||||
|                   } | ||||
|                   alt='Profil Picture' | ||||
| @@ -91,9 +90,7 @@ export const UserProfile: React.FC<UserProfileProps> = (props) => { | ||||
|             </div> | ||||
|           </div> | ||||
|           <div className='mt-7'> | ||||
|             {user.biography != null && ( | ||||
|               <p className='w-[45%]'>{user.biography}</p> | ||||
|             )} | ||||
|             {user.biography != null && <p>{user.biography}</p>} | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|   | ||||
| @@ -1,13 +1,12 @@ | ||||
| import Image from 'next/image' | ||||
| import useTranslation from 'next-translate/useTranslation' | ||||
| import { useState } from 'react' | ||||
| import { useState, useMemo } from 'react' | ||||
| import { Form } from 'react-component-form' | ||||
| import { EyeIcon, PhotographIcon } from '@heroicons/react/solid' | ||||
| import { Type } from '@sinclair/typebox' | ||||
| import axios from 'axios' | ||||
| import Link from 'next/link' | ||||
|  | ||||
| import { API_URL } from '../../../tools/api' | ||||
| import { Input } from '../../design/Input' | ||||
| import { Checkbox } from '../../design/Checkbox' | ||||
| import { Textarea } from '../../design/Textarea' | ||||
| @@ -20,6 +19,7 @@ import { FormState } from '../../design/FormState' | ||||
| import { useForm, HandleSubmitCallback } from '../../../hooks/useForm' | ||||
| import { userSchema } from '../../../models/User' | ||||
| import { userSettingsSchema } from '../../../models/UserSettings' | ||||
| import { ProviderOAuth, providers } from '../../../models/OAuth' | ||||
|  | ||||
| export const UserSettings: React.FC = () => { | ||||
|   const { user, setUser, authentication } = useAuthentication() | ||||
| @@ -56,6 +56,10 @@ export const UserSettings: React.FC = () => { | ||||
|     resetOnSuccess: false | ||||
|   }) | ||||
|  | ||||
|   const hasAllProviders = useMemo(() => { | ||||
|     return providers.every((provider) => user.strategies.includes(provider)) | ||||
|   }, [user.strategies]) | ||||
|  | ||||
|   const onSubmit: HandleSubmitCallback = async (formData) => { | ||||
|     try { | ||||
|       const { isPublicGuilds, isPublicEmail, ...userData } = formData | ||||
| @@ -173,6 +177,41 @@ export const UserSettings: React.FC = () => { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const handleDeletionProvider = ( | ||||
|     provider: ProviderOAuth | ||||
|   ): (() => Promise<void>) => { | ||||
|     return async () => { | ||||
|       try { | ||||
|         setFetchState('loading') | ||||
|         await authentication.api.delete(`/users/oauth2/${provider}`) | ||||
|         setUser((oldUser) => { | ||||
|           return { | ||||
|             ...oldUser, | ||||
|             strategies: oldUser.strategies.filter( | ||||
|               (strategy) => strategy !== provider | ||||
|             ) | ||||
|           } | ||||
|         }) | ||||
|         setMessageTranslationKey('application:success-deleted-provider') | ||||
|       } catch (error) { | ||||
|         setFetchState('error') | ||||
|         setMessageTranslationKey('errors:server-error') | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   const handleAddProvider = ( | ||||
|     provider: ProviderOAuth | ||||
|   ): (() => Promise<void>) => { | ||||
|     return async () => { | ||||
|       const redirect = window.location.href.replace(location.search, '') | ||||
|       const { data: url } = await authentication.api.get( | ||||
|         `/users/oauth2/${provider.toLowerCase()}/add-strategy?redirectURI=${redirect}` | ||||
|       ) | ||||
|       window.location.href = url | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <Form | ||||
|       onSubmit={handleSubmit(onSubmit)} | ||||
| @@ -197,7 +236,7 @@ export const UserSettings: React.FC = () => { | ||||
|                 className='rounded-full opacity-50' | ||||
|                 src={ | ||||
|                   user.logo != null | ||||
|                     ? API_URL + user.logo | ||||
|                     ? user.logo | ||||
|                     : '/images/data/user-default.png' | ||||
|                 } | ||||
|                 alt='Profil Picture' | ||||
| @@ -264,7 +303,7 @@ export const UserSettings: React.FC = () => { | ||||
|           /> | ||||
|         </div> | ||||
|         <div className='flex h-full w-4/5 flex-col items-center justify-between pr-0 sm:w-[415px] lg:pl-12'> | ||||
|           <div className='flex w-full items-center pt-14'> | ||||
|           <div className='flex w-full items-center pt-10 lg:pt-0'> | ||||
|             <Language className='!top-12' /> | ||||
|             <div className='ml-auto flex'> | ||||
|               <SwitchTheme /> | ||||
| @@ -283,18 +322,46 @@ export const UserSettings: React.FC = () => { | ||||
|           </div> | ||||
|  | ||||
|           <div className='mt-14 flex w-full flex-col gap-4'> | ||||
|             <SocialMediaButton | ||||
|               socialMedia='Google' | ||||
|               className='w-full justify-center' | ||||
|             /> | ||||
|             <SocialMediaButton | ||||
|               socialMedia='Discord' | ||||
|               className='w-full justify-center' | ||||
|             /> | ||||
|             <SocialMediaButton | ||||
|               socialMedia='GitHub' | ||||
|               className='w-full justify-center' | ||||
|             /> | ||||
|             {!hasAllProviders ? ( | ||||
|               <div className='flex w-full flex-col gap-4'> | ||||
|                 <h3 className='text-center'> | ||||
|                   {t('application:signin-with-an-account')} | ||||
|                 </h3> | ||||
|                 {providers.map((provider, index) => { | ||||
|                   if (!user.strategies.includes(provider)) { | ||||
|                     return ( | ||||
|                       <SocialMediaButton | ||||
|                         key={index} | ||||
|                         socialMedia={provider} | ||||
|                         className='w-full justify-center' | ||||
|                         onClick={handleAddProvider(provider)} | ||||
|                       /> | ||||
|                     ) | ||||
|                   } | ||||
|                   return null | ||||
|                 })} | ||||
|               </div> | ||||
|             ) : null} | ||||
|             {user.strategies.length !== 1 && ( | ||||
|               <div className='mt-4 flex w-full flex-col gap-4'> | ||||
|                 <h3 className='text-center'> | ||||
|                   {t('application:signout-with-an-account')} | ||||
|                 </h3> | ||||
|                 {providers.map((provider, index) => { | ||||
|                   if (user.strategies.includes(provider)) { | ||||
|                     return ( | ||||
|                       <SocialMediaButton | ||||
|                         key={index} | ||||
|                         socialMedia={provider} | ||||
|                         className='w-full justify-center' | ||||
|                         onClick={handleDeletionProvider(provider)} | ||||
|                       /> | ||||
|                     ) | ||||
|                   } | ||||
|                   return null | ||||
|                 })} | ||||
|               </div> | ||||
|             )} | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import { useRouter } from 'next/router' | ||||
| import { api } from '../../tools/api' | ||||
| import { Authentication, isTokens } from '../../tools/authentication' | ||||
| import { SocialMediaButton, SocialMedia } from '../design/SocialMediaButton' | ||||
| import { providers } from '../../models/OAuth' | ||||
|  | ||||
| export const AuthenticationSocialMedia: React.FC = () => { | ||||
|   const router = useRouter() | ||||
| @@ -12,7 +13,7 @@ export const AuthenticationSocialMedia: React.FC = () => { | ||||
|     socialMedia: SocialMedia | ||||
|   ): (() => Promise<void>) => { | ||||
|     return async () => { | ||||
|       const redirect = window.location.href | ||||
|       const redirect = window.location.href.replace(location.search, '') | ||||
|       const { data: url } = await api.get( | ||||
|         `/users/oauth2/${socialMedia.toLowerCase()}/signin?redirectURI=${redirect}` | ||||
|       ) | ||||
| @@ -32,18 +33,15 @@ export const AuthenticationSocialMedia: React.FC = () => { | ||||
|   return ( | ||||
|     <div className='flex flex-col sm:w-full sm:items-center'> | ||||
|       <div className='flex flex-col items-center justify-center space-y-6 sm:w-4/6 sm:flex-row sm:space-x-6 sm:space-y-0'> | ||||
|         <SocialMediaButton | ||||
|           socialMedia='Google' | ||||
|           onClick={handleAuthentication('Google')} | ||||
|         /> | ||||
|         <SocialMediaButton | ||||
|           socialMedia='GitHub' | ||||
|           onClick={handleAuthentication('GitHub')} | ||||
|         /> | ||||
|         <SocialMediaButton | ||||
|           socialMedia='Discord' | ||||
|           onClick={handleAuthentication('Discord')} | ||||
|         /> | ||||
|         {providers.map((provider, index) => { | ||||
|           return ( | ||||
|             <SocialMediaButton | ||||
|               key={index} | ||||
|               socialMedia={provider} | ||||
|               onClick={handleAuthentication(provider)} | ||||
|             /> | ||||
|           ) | ||||
|         })} | ||||
|       </div> | ||||
|     </div> | ||||
|   ) | ||||
|   | ||||
| @@ -19,6 +19,9 @@ | ||||
|   "success-email-changed": "Please check your emails to confirm your new email address. You are now signed out.", | ||||
|   "delete": "Delete", | ||||
|   "signout": "Sign out", | ||||
|   "signin-with-an-account": "Sign in with an account", | ||||
|   "signout-with-an-account": "Sign out with an account", | ||||
|   "success-deleted-provider": "The provider has been deleted.", | ||||
|   "leave": "Leave", | ||||
|   "start-chatting-kill-ghost": "Start chatting to kill this ghost!" | ||||
| } | ||||
|   | ||||
| @@ -19,6 +19,9 @@ | ||||
|   "success-email-changed": "Veuillez vérifier vos emails pour confirmer votre nouvelle adresse email. Vous êtes maintenant déconnecté.", | ||||
|   "delete": "Supprimer", | ||||
|   "signout": "Se déconnecter", | ||||
|   "signin-with-an-account": "Se connecter avec un compte", | ||||
|   "signout-with-an-account": "Se déconnecter avec un compte", | ||||
|   "success-deleted-provider": "Le moyen de connexion a été supprimé.", | ||||
|   "leave": "Quitter", | ||||
|   "start-chatting-kill-ghost": "Commencez à discuter pour tuer ce fantôme !" | ||||
| } | ||||
|   | ||||
| @@ -9,6 +9,7 @@ module.exports = nextTranslate( | ||||
|       domains: [ | ||||
|         'api.thream.divlo.fr', | ||||
|         'thream-api.herokuapp.com', | ||||
|         'file-uploads-api.thream.divlo.fr', | ||||
|         ...(process.env.NODE_ENV !== 'production' ? ['localhost'] : []) | ||||
|       ] | ||||
|     }, | ||||
|   | ||||
							
								
								
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -91,8 +91,8 @@ | ||||
|         "vercel": "24.0.1" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=16.0.0", | ||||
|         "npm": ">=8.0.0" | ||||
|         "node": ">=14.0.0", | ||||
|         "npm": ">=7.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@ampproject/remapping": { | ||||
|   | ||||
| @@ -7,8 +7,8 @@ | ||||
|     "url": "https://github.com/Thream/website" | ||||
|   }, | ||||
|   "engines": { | ||||
|     "node": ">=16.0.0", | ||||
|     "npm": ">=8.0.0" | ||||
|     "node": ">=14.0.0", | ||||
|     "npm": ">=7.0.0" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "dev": "next dev", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user