From 142a4f86ee11910cae072ba1c8edad1140abe27e Mon Sep 17 00:00:00 2001 From: Anton Date: Tue, 12 Dec 2023 14:54:50 +0100 Subject: [PATCH] added color picker and avatar upload to profile --- package-lock.json | 10 +++++++ package.json | 1 + src/Components/Profile/ColorPicker.css | 20 +++++++++++++ src/Components/Profile/ColorPicker.tsx | 29 ++++++++++++++++++ src/Components/Profile/Settings.tsx | 13 +++++--- src/Components/Profile/useClickOutside.tsx | 35 ++++++++++++++++++++++ 6 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 src/Components/Profile/ColorPicker.css create mode 100644 src/Components/Profile/ColorPicker.tsx create mode 100644 src/Components/Profile/useClickOutside.tsx diff --git a/package-lock.json b/package-lock.json index fc34297f..67320acb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index fbc226f7..837292e8 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/Components/Profile/ColorPicker.css b/src/Components/Profile/ColorPicker.css new file mode 100644 index 00000000..8fb97fb2 --- /dev/null +++ b/src/Components/Profile/ColorPicker.css @@ -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); + } \ No newline at end of file diff --git a/src/Components/Profile/ColorPicker.tsx b/src/Components/Profile/ColorPicker.tsx new file mode 100644 index 00000000..49a941ab --- /dev/null +++ b/src/Components/Profile/ColorPicker.tsx @@ -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(null); + const [isOpen, toggle] = useState(false); + + const close = useCallback(() => toggle(false), []); + useClickOutside(popover, close); + + return ( +
+
toggle(true)} + /> + + {isOpen && ( +
+ +
+ )} +
+ ); +}; diff --git a/src/Components/Profile/Settings.tsx b/src/Components/Profile/Settings.tsx index dc368e2e..02960d12 100644 --- a/src/Components/Profile/Settings.tsx +++ b/src/Components/Profile/Settings.tsx @@ -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(""); const [password, setPassword] = useState(""); const [avatar, setAvatar] = useState(""); + const [color, setColor] = useState(""); + const [passwordChanged, setPasswordChanged] = useState(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(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() {
} + setName(v)} containerStyle='tw-grow tw-ml-6 tw-my-auto ' /> diff --git a/src/Components/Profile/useClickOutside.tsx b/src/Components/Profile/useClickOutside.tsx new file mode 100644 index 00000000..e5643c8a --- /dev/null +++ b/src/Components/Profile/useClickOutside.tsx @@ -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;