added color picker and avatar upload to profile

This commit is contained in:
Anton 2023-12-12 14:54:50 +01:00
parent 771c8f06bc
commit 142a4f86ee
6 changed files with 104 additions and 4 deletions

10
package-lock.json generated
View File

@ -13,6 +13,7 @@
"@types/offscreencanvas": "^2019.7.1",
"leaflet": "^1.9.4",
"prop-types": "^15.8.1",
"react-colorful": "^5.6.1",
"react-image-crop": "^10.1.8",
"react-leaflet": "^4.2.1",
"react-leaflet-cluster": "^2.1.0",
@ -3787,6 +3788,15 @@
"resolved": "../react18-app/node_modules/react",
"link": true
},
"node_modules/react-colorful": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz",
"integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==",
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",

View File

@ -47,6 +47,7 @@
"@types/offscreencanvas": "^2019.7.1",
"leaflet": "^1.9.4",
"prop-types": "^15.8.1",
"react-colorful": "^5.6.1",
"react-image-crop": "^10.1.8",
"react-leaflet": "^4.2.1",
"react-leaflet-cluster": "^2.1.0",

View File

@ -0,0 +1,20 @@
.picker {
position: relative;
}
.swatch {
width: 28px;
height: 28px;
border-radius: 8px;
border: 3px solid #fff;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), inset 0 0 0 1px rgba(0, 0, 0, 0.1);
cursor: pointer;
}
.popover {
position: absolute;
top: 0;
left: 36px;
border-radius: 9px;
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
}

View File

@ -0,0 +1,29 @@
import { useCallback, useRef, useState } from "react";
import * as React from "react";
import { HexColorPicker } from "react-colorful";
import "./ColorPicker.css"
import useClickOutside from "./useClickOutside";
export const ColorPicker = ({ color, onChange, className }) => {
const popover = useRef<HTMLDivElement>(null);
const [isOpen, toggle] = useState(false);
const close = useCallback(() => toggle(false), []);
useClickOutside(popover, close);
return (
<div className={`picker ${className}`}>
<div
className="swatch"
style={{ backgroundColor: color }}
onClick={() => toggle(true)}
/>
{isOpen && (
<div className="popover" ref={popover}>
<HexColorPicker color={color} onChange={onChange} />
</div>
)}
</div>
);
};

View File

@ -12,6 +12,7 @@ import 'react-toastify/dist/ReactToastify.css';
import { UserItem } from '../../types';
import DialogModal from '../Templates/DialogModal';
import { useAssetApi } from '../AppShell/hooks/useAssets';
import { ColorPicker } from './ColorPicker';
export function Settings() {
const { user, updateUser, loading, token } = useAuth();
@ -22,6 +23,8 @@ export function Settings() {
const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const [avatar, setAvatar] = useState<string>("");
const [color, setColor] = useState<string>("");
const [passwordChanged, setPasswordChanged] = useState<boolean>(false);
@ -39,7 +42,8 @@ export function Settings() {
setText(user?.description ? user.description : "");
setEmail(user?.email ? user.email : "");
setPassword(user?.password ? user.password : "");
setAvatar(user?.avatar ? user?.avatar : "")
setAvatar(user?.avatar ? user?.avatar : ""),
setColor(user?.color? user.color : "#aabbcc")
}, [user])
const imgRef = useRef<HTMLImageElement>(null)
@ -140,7 +144,7 @@ export function Settings() {
const onUpdateUser = () => {
let changedUser = {} as UserItem;
changedUser = { id: id, first_name: name, description: text, email: email, ...passwordChanged && { password: password }, ...avatar.length > 10 && { avatar: avatar } };
changedUser = { id: id, first_name: name, description: text, email: email, color: color, ...passwordChanged && { password: password }, ...avatar.length > 10 && { avatar: avatar } };
toast.promise(
@ -169,8 +173,8 @@ export function Settings() {
<label className="custom-file-upload">
<input type="file" accept="image/*" className="tw-file-input tw-w-full tw-max-w-xs" onChange={onImageChange} />
<div className='button tw-btn tw-btn-lg tw-btn-circle tw-animate-none'>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" className="tw-w-6 tw-h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" />
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="tw-w-6 tw-h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" />
</svg>
</div>
{avatar ?
@ -192,6 +196,7 @@ export function Settings() {
</div>
}
<ColorPicker color={color} onChange={setColor} className={"-tw-left-6 tw-top-14 -tw-mr-6"} />
<TextInput placeholder="Name" defaultValue={user?.first_name ? user.first_name : ""} updateFormValue={(v) => setName(v)} containerStyle='tw-grow tw-ml-6 tw-my-auto ' />
</div>

View File

@ -0,0 +1,35 @@
import { useEffect } from "react";
// Improved version of https://usehooks.com/useOnClickOutside/
const useClickOutside = (ref, handler) => {
useEffect(() => {
let startedInside = false;
let startedWhenMounted = false;
const listener = (event) => {
// Do nothing if `mousedown` or `touchstart` started inside ref element
if (startedInside || !startedWhenMounted) return;
// Do nothing if clicking ref's element or descendent elements
if (!ref.current || ref.current.contains(event.target)) return;
handler(event);
};
const validateEventStart = (event) => {
startedWhenMounted = ref.current;
startedInside = ref.current && ref.current.contains(event.target);
};
document.addEventListener("mousedown", validateEventStart);
document.addEventListener("touchstart", validateEventStart);
document.addEventListener("click", listener);
return () => {
document.removeEventListener("mousedown", validateEventStart);
document.removeEventListener("touchstart", validateEventStart);
document.removeEventListener("click", listener);
};
}, [ref, handler]);
};
export default useClickOutside;