mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
added color picker and avatar upload to profile
This commit is contained in:
parent
771c8f06bc
commit
142a4f86ee
10
package-lock.json
generated
10
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
20
src/Components/Profile/ColorPicker.css
Normal file
20
src/Components/Profile/ColorPicker.css
Normal 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);
|
||||
}
|
||||
29
src/Components/Profile/ColorPicker.tsx
Normal file
29
src/Components/Profile/ColorPicker.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
|
||||
|
||||
35
src/Components/Profile/useClickOutside.tsx
Normal file
35
src/Components/Profile/useClickOutside.tsx
Normal 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;
|
||||
Loading…
x
Reference in New Issue
Block a user