fix: removing file upload, avatar by robohash
This commit is contained in:
parent
2221b22285
commit
8e7620201e
@ -1,80 +0,0 @@
|
|||||||
import axios, { AxiosError, AxiosInstance } from "axios"
|
|
||||||
import { PostMediaResponse } from "../types/media"
|
|
||||||
import sha256 from 'crypto-js/sha256'
|
|
||||||
import Hex from 'crypto-js/enc-hex'
|
|
||||||
import { NostrController } from "."
|
|
||||||
|
|
||||||
export class ApiController {
|
|
||||||
private mediaUrl: string = 'https://media.nquiz.io'
|
|
||||||
private api: AxiosInstance
|
|
||||||
private baseUrl: string = ''
|
|
||||||
|
|
||||||
private nostrController = NostrController.getInstance()
|
|
||||||
|
|
||||||
constructor(baseUrl?: string) {
|
|
||||||
this.baseUrl = baseUrl ?? this.baseUrl
|
|
||||||
|
|
||||||
this.api = axios.create({
|
|
||||||
withCredentials: true,
|
|
||||||
baseURL: this.baseUrl,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadMediaImage = async (
|
|
||||||
file: Buffer,
|
|
||||||
): Promise<PostMediaResponse> => {
|
|
||||||
const mediaUrl = this.mediaUrl
|
|
||||||
|
|
||||||
const formData = new FormData()
|
|
||||||
const imageBlob = new Blob([file])
|
|
||||||
formData.append('mediafile', imageBlob)
|
|
||||||
|
|
||||||
const endpointUrl = 'v1/media'
|
|
||||||
const tags = [
|
|
||||||
['u', `${mediaUrl}/${endpointUrl}`],
|
|
||||||
['method', 'POST'],
|
|
||||||
['payload', this.hashString('{}')]
|
|
||||||
]
|
|
||||||
|
|
||||||
const authToken = await this.nostrController.createNostrHttpAuthToken(tags)
|
|
||||||
|
|
||||||
console.info('Auth token created for media server.', authToken)
|
|
||||||
|
|
||||||
return axios
|
|
||||||
.post<PostMediaResponse>(`${mediaUrl}/api/v1/media`, formData, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'multipart/form-data',
|
|
||||||
Authorization: `Nostr ${authToken}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
console.info(
|
|
||||||
`POST ${mediaUrl}/api/v1/media | res.data:`,
|
|
||||||
res.data
|
|
||||||
)
|
|
||||||
// Create correct api url, because res.url is not including 'api/v1/'
|
|
||||||
if (!res || !res.data) {
|
|
||||||
console.warn('Image upload: no data returned after upload')
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = res.data
|
|
||||||
data.directLink = `${data.url.replace(mediaUrl, `${mediaUrl}/api/v1`)}`
|
|
||||||
|
|
||||||
return data
|
|
||||||
})
|
|
||||||
.catch((err: AxiosError) => {
|
|
||||||
console.info(`POST ${mediaUrl}/api/v1/media | err:`, err)
|
|
||||||
throw {
|
|
||||||
code: 500,
|
|
||||||
message: err.response?.data || err.response || err.message || err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
hashString = (payload: any): string => {
|
|
||||||
return sha256(payload).toString(Hex)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +1,13 @@
|
|||||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
import ContentCopyIcon from '@mui/icons-material/ContentCopy'
|
||||||
import {
|
import {
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
|
IconButton,
|
||||||
|
InputProps,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
ListSubheader,
|
ListSubheader,
|
||||||
TextField,
|
TextField,
|
||||||
|
Tooltip,
|
||||||
Typography,
|
Typography,
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
@ -23,8 +26,7 @@ import { Dispatch } from '../../store/store'
|
|||||||
import { setMetadataEvent } from '../../store/actions'
|
import { setMetadataEvent } from '../../store/actions'
|
||||||
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
import { LoadingSpinner } from '../../components/LoadingSpinner'
|
||||||
import { LoginMethods } from '../../store/auth/types'
|
import { LoginMethods } from '../../store/auth/types'
|
||||||
import { ApiController } from '../../controllers/ApiController'
|
import { SmartToy } from '@mui/icons-material'
|
||||||
import { PostMediaResponse } from '../../types/media'
|
|
||||||
|
|
||||||
export const ProfilePage = () => {
|
export const ProfilePage = () => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
@ -45,7 +47,6 @@ export const ProfilePage = () => {
|
|||||||
const metadataState = useSelector((state: State) => state.metadata)
|
const metadataState = useSelector((state: State) => state.metadata)
|
||||||
const keys = useSelector((state: State) => state.auth?.keyPair)
|
const keys = useSelector((state: State) => state.auth?.keyPair)
|
||||||
const { usersPubkey, loginMethod } = useSelector((state: State) => state.auth)
|
const { usersPubkey, loginMethod } = useSelector((state: State) => state.auth)
|
||||||
const [uploadingImage, setUploadingImage] = useState<boolean>()
|
|
||||||
|
|
||||||
const [isUsersOwnProfile, setIsUsersOwnProfile] = useState(false)
|
const [isUsersOwnProfile, setIsUsersOwnProfile] = useState(false)
|
||||||
|
|
||||||
@ -118,7 +119,8 @@ export const ProfilePage = () => {
|
|||||||
key: keyof ProfileMetadata,
|
key: keyof ProfileMetadata,
|
||||||
label: string,
|
label: string,
|
||||||
multiline = false,
|
multiline = false,
|
||||||
rows = 1
|
rows = 1,
|
||||||
|
inputProps?: InputProps
|
||||||
) => (
|
) => (
|
||||||
<ListItem sx={{ marginTop: 1 }}>
|
<ListItem sx={{ marginTop: 1 }}>
|
||||||
<TextField
|
<TextField
|
||||||
@ -130,6 +132,7 @@ export const ProfilePage = () => {
|
|||||||
rows={rows}
|
rows={rows}
|
||||||
className={styles.textField}
|
className={styles.textField}
|
||||||
disabled={!isUsersOwnProfile}
|
disabled={!isUsersOwnProfile}
|
||||||
|
InputProps={inputProps}
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const { value } = event.target
|
const { value } = event.target
|
||||||
|
|
||||||
@ -208,41 +211,6 @@ export const ProfilePage = () => {
|
|||||||
setSavingProfileMetadata(false)
|
setSavingProfileMetadata(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onProfileImageChange = (event: any) => {
|
|
||||||
const file = event?.target?.files[0]
|
|
||||||
|
|
||||||
if (file) {
|
|
||||||
setUploadingImage(true)
|
|
||||||
|
|
||||||
const apiController = new ApiController()
|
|
||||||
|
|
||||||
apiController.uploadMediaImage(file).then((imageResponse: PostMediaResponse) => {
|
|
||||||
if (imageResponse && imageResponse.directLink) {
|
|
||||||
setTimeout(() => {
|
|
||||||
setProfileMetadata((prev) => ({
|
|
||||||
...prev,
|
|
||||||
picture: imageResponse.directLink
|
|
||||||
}))
|
|
||||||
}, 200)
|
|
||||||
} else {
|
|
||||||
toast.error(`Uploading image failed. Please try again.`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error('Error uploading image to media server.', err)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setUploadingImage(false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const progressSpinner = (show = false) => {
|
|
||||||
if (!show) return undefined
|
|
||||||
|
|
||||||
return <CircularProgress size={20} />
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateRobotAvatar = () => {
|
const generateRobotAvatar = () => {
|
||||||
setAvatarLoading(true)
|
setAvatarLoading(true)
|
||||||
|
|
||||||
@ -261,6 +229,21 @@ export const ProfilePage = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns robohash generate button, loading spinner or no button
|
||||||
|
*/
|
||||||
|
const robohashButton = () => {
|
||||||
|
if (profileMetadata?.picture?.includes('robohash')) return null
|
||||||
|
|
||||||
|
return <Tooltip title="Generate a robohash avatar">
|
||||||
|
{avatarLoading ? <CircularProgress style={{padding: 8}} size={22}/>
|
||||||
|
: <IconButton onClick={generateRobotAvatar}>
|
||||||
|
<SmartToy/>
|
||||||
|
</IconButton>}
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
||||||
@ -304,20 +287,6 @@ export const ProfilePage = () => {
|
|||||||
alt='Profile Image'
|
alt='Profile Image'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LoadingButton onClick={generateRobotAvatar} loading={avatarLoading}>Generate Robot Avatar</LoadingButton>
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
onChange={onProfileImageChange}
|
|
||||||
type="file"
|
|
||||||
size="small"
|
|
||||||
label="Profile Image"
|
|
||||||
className={styles.textField}
|
|
||||||
InputProps={{
|
|
||||||
endAdornment: progressSpinner(uploadingImage)
|
|
||||||
}}
|
|
||||||
focused
|
|
||||||
/>
|
|
||||||
|
|
||||||
{nostrJoiningBlock && (
|
{nostrJoiningBlock && (
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
@ -334,6 +303,10 @@ export const ProfilePage = () => {
|
|||||||
)}
|
)}
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
|
{editItem('picture', 'Picture URL', undefined, undefined, {
|
||||||
|
endAdornment: robohashButton()
|
||||||
|
})}
|
||||||
|
|
||||||
{editItem('name', 'Username')}
|
{editItem('name', 'Username')}
|
||||||
{editItem('display_name', 'Display Name')}
|
{editItem('display_name', 'Display Name')}
|
||||||
{editItem('nip05', 'Nostr Address (nip05)')}
|
{editItem('nip05', 'Nostr Address (nip05)')}
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
export interface PostMediaResponse {
|
|
||||||
result: boolean
|
|
||||||
description: string
|
|
||||||
status: string
|
|
||||||
id: number
|
|
||||||
pubkey: string
|
|
||||||
url: string
|
|
||||||
directLink: string
|
|
||||||
hash: string
|
|
||||||
magnet: string
|
|
||||||
tags: any[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetMediaResponse extends PostMediaResponse {}
|
|
||||||
|
|
||||||
export interface MediaErrorResponse {
|
|
||||||
description: string
|
|
||||||
result: boolean
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user