fix: file upload and OAuth2 (#26)

This commit is contained in:
Divlo 2022-04-08 20:59:04 +02:00 committed by GitHub
parent a64325f5b8
commit 313cfeeb36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 116 additions and 58 deletions

View File

@ -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}

View File

@ -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'

View File

@ -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}

View File

@ -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}

View File

@ -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}

View File

@ -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}

View File

@ -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 />

View File

@ -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>

View File

@ -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>

View File

@ -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>
)

View File

@ -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!"
}

View File

@ -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 !"
}

View File

@ -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
View File

@ -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": {

View File

@ -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",