fix: file upload and OAuth2 (#26)
This commit is contained in:
parent
a64325f5b8
commit
313cfeeb36
@ -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