mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
basic avatar upload
This commit is contained in:
parent
29eedc3a37
commit
ae096d4b7e
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { TitleCard } from '../Templates/TitleCard'
|
import { TitleCard } from '../Templates/TitleCard'
|
||||||
import { TextInput } from '../Input/TextInput'
|
import { TextInput } from '../Input/TextInput'
|
||||||
import { TextAreaInput } from '../Input/TextAreaInput'
|
import { TextAreaInput } from '../Input/TextAreaInput'
|
||||||
@ -6,12 +6,14 @@ import { toast } from 'react-toastify';
|
|||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { useAuth } from '../Auth';
|
import { useAuth } from '../Auth';
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
import ReactCrop, { Crop, centerCrop, makeAspectCrop } from 'react-image-crop'
|
||||||
|
import 'react-image-crop/dist/ReactCrop.css'
|
||||||
import 'react-toastify/dist/ReactToastify.css';
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
import { UserItem } from '../../types';
|
import { UserItem } from '../../types';
|
||||||
|
import DialogModal from '../Templates/DialogModal';
|
||||||
|
import { useAssetApi } from '../AppShell/hooks/useAssets';
|
||||||
|
|
||||||
export function Settings() {
|
export function Settings() {
|
||||||
|
|
||||||
const { user, updateUser, loading, token } = useAuth();
|
const { user, updateUser, loading, token } = useAuth();
|
||||||
|
|
||||||
const [id, setId] = useState<string>("");
|
const [id, setId] = useState<string>("");
|
||||||
@ -19,11 +21,17 @@ export function Settings() {
|
|||||||
const [text, setText] = useState<string>("");
|
const [text, setText] = useState<string>("");
|
||||||
const [email, setEmail] = useState<string>("");
|
const [email, setEmail] = useState<string>("");
|
||||||
const [password, setPassword] = useState<string>("");
|
const [password, setPassword] = useState<string>("");
|
||||||
|
const [avatar, setAvatar] = useState<string>("");
|
||||||
|
|
||||||
const [passwordChanged, setPasswordChanged] = useState<boolean>(false);
|
const [passwordChanged, setPasswordChanged] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [crop, setCrop] = useState<Crop>();
|
||||||
|
const [image, setImage] = useState<string>("");
|
||||||
|
const [cropModalOpen, setCropModalOpen] = useState<boolean>(false);
|
||||||
|
const [cropping, setCropping] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const assetsApi = useAssetApi();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setId(user?.id ? user.id : "");
|
setId(user?.id ? user.id : "");
|
||||||
@ -31,19 +39,112 @@ export function Settings() {
|
|||||||
setText(user?.description ? user.description : "");
|
setText(user?.description ? user.description : "");
|
||||||
setEmail(user?.email ? user.email : "");
|
setEmail(user?.email ? user.email : "");
|
||||||
setPassword(user?.password ? user.password : "");
|
setPassword(user?.password ? user.password : "");
|
||||||
|
setAvatar(user?.avatar ? user?.avatar : "")
|
||||||
}, [user])
|
}, [user])
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const imgRef = useRef<HTMLImageElement>(null)
|
||||||
|
|
||||||
|
const onImageChange = (event) => {
|
||||||
|
if (event.target.files && event.target.files[0]) {
|
||||||
|
setImage(URL.createObjectURL(event.target.files[0]));
|
||||||
|
}
|
||||||
|
setCropModalOpen(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onImageLoad(e: React.SyntheticEvent<HTMLImageElement>) {
|
||||||
|
const { width, height } = e.currentTarget
|
||||||
|
console.log(width);
|
||||||
|
|
||||||
|
setCrop(centerAspectCrop(width, height, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This is to demonstate how to make and center a % aspect crop
|
||||||
|
// which is a bit trickier so we use some helper functions.
|
||||||
|
function centerAspectCrop(
|
||||||
|
mediaWidth: number,
|
||||||
|
mediaHeight: number,
|
||||||
|
aspect: number,
|
||||||
|
) {
|
||||||
|
return centerCrop(
|
||||||
|
makeAspectCrop(
|
||||||
|
{
|
||||||
|
unit: 'px',
|
||||||
|
width: mediaWidth / 2,
|
||||||
|
},
|
||||||
|
aspect,
|
||||||
|
mediaWidth,
|
||||||
|
mediaHeight,
|
||||||
|
),
|
||||||
|
mediaWidth,
|
||||||
|
mediaHeight,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderCrop() {
|
||||||
|
// get the image element
|
||||||
|
const image = imgRef.current;
|
||||||
|
if (crop && image) {
|
||||||
|
|
||||||
|
const scaleX = image.naturalWidth / image.width
|
||||||
|
const scaleY = image.naturalHeight / image.height
|
||||||
|
|
||||||
|
// create a canvas element to draw the cropped image
|
||||||
|
const canvas = new OffscreenCanvas(
|
||||||
|
crop.width * scaleX,
|
||||||
|
crop.height * scaleY,
|
||||||
|
)
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
const pixelRatio = window.devicePixelRatio;
|
||||||
|
canvas.width = crop.width * pixelRatio * scaleX;
|
||||||
|
canvas.height = crop.height * pixelRatio * scaleY;
|
||||||
|
|
||||||
|
if (ctx) {
|
||||||
|
ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
||||||
|
|
||||||
|
ctx.drawImage(
|
||||||
|
image,
|
||||||
|
crop.x * scaleX,
|
||||||
|
crop.y * scaleY,
|
||||||
|
crop.width * scaleX,
|
||||||
|
crop.height * scaleY,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
crop.width * scaleX,
|
||||||
|
crop.height * scaleY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const blob = await canvas.convertToBlob();
|
||||||
|
await resizeBlob(blob);
|
||||||
|
setCropping(false);
|
||||||
|
setImage("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resizeBlob(blob) {
|
||||||
|
var img = new Image();
|
||||||
|
img.src = URL.createObjectURL(blob);
|
||||||
|
await img.decode();
|
||||||
|
const canvas = new OffscreenCanvas(
|
||||||
|
400,
|
||||||
|
400
|
||||||
|
)
|
||||||
|
var ctx = canvas.getContext("2d");
|
||||||
|
ctx?.drawImage(img, 0, 0, 400, 400);
|
||||||
|
const resizedBlob = await canvas.convertToBlob()
|
||||||
|
const asset = await assetsApi.upload(resizedBlob, "test");
|
||||||
|
setAvatar(asset.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const onUpdateUser = () => {
|
const onUpdateUser = () => {
|
||||||
let changedUser = {} as UserItem;
|
let changedUser = {} as UserItem;
|
||||||
|
|
||||||
if (passwordChanged) {
|
if (passwordChanged) {
|
||||||
changedUser = { id: id, first_name: name, description: text, email: email, password: password };
|
changedUser = { id: id, first_name: name, description: text, email: email, avatar: avatar, password: password };
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
changedUser = { id: id, first_name: name, description: text, email: email };
|
changedUser = { id: id, first_name: name, description: text, email: email, avatar: avatar };
|
||||||
}
|
}
|
||||||
toast.promise(
|
toast.promise(
|
||||||
|
|
||||||
@ -57,19 +158,25 @@ export function Settings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<main className="tw-flex-1 tw-overflow-y-auto tw-overflow-x-hidden tw-pt-8 tw-px-6 tw-bg-base-200 tw-min-w-80 tw-flex tw-justify-center" >
|
<main className="tw-flex-1 tw-overflow-y-auto tw-overflow-x-hidden tw-pt-8 tw-px-6 tw-bg-base-200 tw-min-w-80 tw-flex tw-justify-center" >
|
||||||
<div className='tw-w-full xl:tw-max-w-6xl'>
|
<div className='tw-w-full xl:tw-max-w-6xl'>
|
||||||
<TitleCard title="Profile Settings" topMargin="tw-mt-2">
|
<TitleCard title="Profile Settings" topMargin="tw-mt-2" className='tw-mb-6'>
|
||||||
<div className="tw-flex">
|
<div className="tw-flex">
|
||||||
<img src={"https://api.utopia-lab.org/assets/" + user?.avatar + "?access_token=" + token} className='tw-w-20 tw-rounded-full' />
|
{!cropping && avatar ?
|
||||||
|
<label className="custom-file-upload">
|
||||||
|
<input type="file" accept="image/*" className="tw-file-input tw-w-full tw-max-w-xs" onChange={onImageChange} />
|
||||||
|
|
||||||
|
<img src={assetsApi.url + avatar + "?access_token=" + token} className='tw-w-20 tw-rounded-full' />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
:
|
||||||
|
<span className="tw-loading tw-loading-spinner"></span>
|
||||||
|
}
|
||||||
<TextInput placeholder="Name" defaultValue={user?.first_name ? user.first_name : ""} updateFormValue={(v) => setName(v)} containerStyle='tw-grow tw-ml-6 tw-my-auto ' />
|
<TextInput placeholder="Name" defaultValue={user?.first_name ? user.first_name : ""} updateFormValue={(v) => setName(v)} containerStyle='tw-grow tw-ml-6 tw-my-auto ' />
|
||||||
</div>
|
</div>
|
||||||
<div className="tw-flex tw-mt-2">
|
|
||||||
<button className='tw-btn tw-w-20'>Upload</button>
|
|
||||||
<TextInput placeholder="Color" defaultValue="#4f5532" containerStyle='tw-grow tw-ml-6'/>
|
|
||||||
</div>
|
|
||||||
<div className="tw-grid tw-grid-cols-1 tw-md:grid-cols-1 tw-gap-6 tw-pt-6 tw-pb-6">
|
<div className="tw-grid tw-grid-cols-1 tw-md:grid-cols-1 tw-gap-6 tw-pt-6 tw-pb-6">
|
||||||
<TextAreaInput placeholder="About me, Contact, #Tags, ..." defaultValue={user?.description ? user.description : ""} updateFormValue={(v) => setText(v)} inputStyle='tw-h-64' />
|
<TextAreaInput placeholder="About me, Contact, #Tags, ..." defaultValue={user?.description ? user.description : ""} updateFormValue={(v) => setText(v)} inputStyle='tw-h-64' />
|
||||||
</div>
|
</div>
|
||||||
@ -90,5 +197,22 @@ export function Settings() {
|
|||||||
</TitleCard>
|
</TitleCard>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
<DialogModal
|
||||||
|
title=""
|
||||||
|
isOpened={cropModalOpen}
|
||||||
|
onClose={() => {
|
||||||
|
setCropModalOpen(false);
|
||||||
|
setImage("");
|
||||||
|
}}>
|
||||||
|
<ReactCrop crop={crop} onChange={(c) => setCrop(c)} aspect={1} >
|
||||||
|
<img src={image} ref={imgRef} onLoad={onImageLoad} />
|
||||||
|
</ReactCrop>
|
||||||
|
<button className={`tw-btn `} onClick={() => {
|
||||||
|
setCropping(true);
|
||||||
|
setCropModalOpen(false);
|
||||||
|
renderCrop();
|
||||||
|
}}>Select</button>
|
||||||
|
</DialogModal>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,7 +43,7 @@ const DialogModal = ({
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dialog className='tw-card tw-shadow-xl tw-absolute tw-right-0 tw-top-0 tw-bottom-0 tw-left-0 tw-m-auto tw-transition-opacity tw-duration-300 tw-p-4 tw-max-w-2xl'
|
<dialog className='tw-card tw-shadow-xl tw-absolute tw-right-0 tw-top-0 tw-bottom-0 tw-left-0 tw-m-auto tw-transition-opacity tw-duration-300 tw-p-4 tw-max-w-xl'
|
||||||
|
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onCancel={onClose}
|
onCancel={onClose}
|
||||||
|
|||||||
@ -40,3 +40,11 @@
|
|||||||
--toastify-color-error: theme('colors.error');
|
--toastify-color-error: theme('colors.error');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-file-upload{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="file"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user