mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-09-01 19:49:54 +02:00
web: edit profile directly
This commit is contained in:
committed by
Abdullah Atta
parent
7f62262eaf
commit
a4ddbe3f2f
@@ -470,8 +470,8 @@ export function showAttachmentsDialog() {
|
||||
));
|
||||
}
|
||||
|
||||
export function showEditProfileDialog(profile?: Profile) {
|
||||
return showDialog("EditProfileDialog", (Dialog, perform) => (
|
||||
export function showEditProfilePictureDialog(profile?: Profile) {
|
||||
return showDialog("EditProfilePictureDialog", (Dialog, perform) => (
|
||||
<Dialog onClose={(res: boolean) => perform(res)} profile={profile} />
|
||||
));
|
||||
}
|
||||
|
||||
@@ -20,9 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { Perform } from "../common/dialog-controller";
|
||||
import Dialog from "../components/dialog";
|
||||
import { Profile } from "@notesnook/core";
|
||||
import Field from "../components/field";
|
||||
import AvatarEditor from "react-avatar-editor";
|
||||
import { Avatar, Button, Flex, Slider } from "@theme-ui/components";
|
||||
import { Button, Flex, Slider } from "@theme-ui/components";
|
||||
import { User } from "../components/icons";
|
||||
import { useRef, useState } from "react";
|
||||
import { showFilePicker } from "../utils/file-picker";
|
||||
@@ -30,20 +29,19 @@ import { db } from "../common/db";
|
||||
import { useStore as useUserStore } from "../stores/user-store";
|
||||
import { showToast } from "../utils/toast";
|
||||
|
||||
export type EditProfileDialogProps = {
|
||||
export type EditProfilePictureDialogProps = {
|
||||
onClose: Perform;
|
||||
profile?: Profile;
|
||||
};
|
||||
|
||||
export default function EditProfileDialog(props: EditProfileDialogProps) {
|
||||
export default function EditProfilePictureDialog(
|
||||
props: EditProfilePictureDialogProps
|
||||
) {
|
||||
const { profile } = props;
|
||||
const [profilePicture, setProfilePicture] = useState<
|
||||
File | string | undefined
|
||||
>(profile?.profilePicture);
|
||||
const profileRef = useRef<AvatarEditor>(null);
|
||||
const [fullName, setFullName] = useState<string | undefined>(
|
||||
profile?.fullName
|
||||
);
|
||||
const [scale, setScale] = useState(1);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [clearProfilePicture, setClearProfilePicture] = useState(false);
|
||||
@@ -51,7 +49,7 @@ export default function EditProfileDialog(props: EditProfileDialogProps) {
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={true}
|
||||
title={"Edit profile"}
|
||||
title={"Edit profile picture"}
|
||||
description="Your profile data is stored 100% end-to-end encrypted."
|
||||
onClose={() => props.onClose(false)}
|
||||
positiveButton={{
|
||||
@@ -61,17 +59,19 @@ export default function EditProfileDialog(props: EditProfileDialogProps) {
|
||||
onClick: async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await db.user.setProfile({
|
||||
fullName,
|
||||
profilePicture: profileRef.current
|
||||
? profileRef.current
|
||||
.getImageScaledToCanvas()
|
||||
.toDataURL("image/jpeg", 1)
|
||||
: clearProfilePicture
|
||||
const pic = profileRef.current
|
||||
? profileRef.current
|
||||
.getImageScaledToCanvas()
|
||||
.toDataURL("image/jpeg", 0.8)
|
||||
: clearProfilePicture
|
||||
? undefined
|
||||
: profile?.profilePicture
|
||||
: profile?.profilePicture;
|
||||
await db.user.setProfile({
|
||||
profilePicture: pic
|
||||
});
|
||||
useUserStore.setState({
|
||||
profile: { ...profile, profilePicture: pic }
|
||||
});
|
||||
await useUserStore.getState().refreshUser();
|
||||
showToast("success", "Profile updated!");
|
||||
props.onClose(true);
|
||||
} catch (e) {
|
||||
@@ -82,84 +82,86 @@ export default function EditProfileDialog(props: EditProfileDialogProps) {
|
||||
}
|
||||
}
|
||||
}}
|
||||
width={400}
|
||||
negativeButton={{ text: "Cancel", onClick: () => props.onClose(false) }}
|
||||
>
|
||||
<Flex sx={{ gap: 2, mt: 2 }}>
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: "column"
|
||||
}}
|
||||
>
|
||||
{profilePicture ? (
|
||||
<AvatarEditor
|
||||
ref={profileRef}
|
||||
image={profilePicture}
|
||||
width={256}
|
||||
height={256}
|
||||
border={1}
|
||||
color={[255, 255, 255]}
|
||||
borderRadius={256}
|
||||
scale={scale}
|
||||
style={{
|
||||
width: 150,
|
||||
height: 150,
|
||||
alignSelf: "center"
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Flex
|
||||
variant="columnCenter"
|
||||
sx={{
|
||||
bg: "shade",
|
||||
mr: 2,
|
||||
size: 150,
|
||||
borderRadius: 200,
|
||||
alignSelf: "center"
|
||||
}}
|
||||
>
|
||||
<User size={60} />
|
||||
</Flex>
|
||||
)}
|
||||
<Flex sx={{ gap: 1, alignItems: "center", mt: 2 }}>
|
||||
<Button
|
||||
sx={{ flex: 1 }}
|
||||
variant="secondary"
|
||||
onClick={async () => {
|
||||
const file = await showFilePicker({
|
||||
acceptedFileTypes: "image/*"
|
||||
});
|
||||
if (!file) return;
|
||||
setProfilePicture(file);
|
||||
setScale(1);
|
||||
}}
|
||||
>
|
||||
{profilePicture
|
||||
? "Change profile picture"
|
||||
: "Select profile picture"}
|
||||
</Button>
|
||||
{profilePicture ? (
|
||||
<AvatarEditor
|
||||
ref={profileRef}
|
||||
image={profilePicture}
|
||||
width={150}
|
||||
height={150}
|
||||
border={0}
|
||||
color={[255, 255, 255]}
|
||||
borderRadius={100}
|
||||
scale={scale}
|
||||
style={{
|
||||
width: 150,
|
||||
height: 150
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Flex
|
||||
variant="columnCenter"
|
||||
sx={{
|
||||
bg: "shade",
|
||||
mr: 2,
|
||||
size: 150,
|
||||
borderRadius: 200,
|
||||
alignSelf: "center"
|
||||
}}
|
||||
>
|
||||
<User size={60} />
|
||||
</Flex>
|
||||
)}
|
||||
<Flex sx={{ gap: 1, alignItems: "center", mt: 2 }}>
|
||||
<Button
|
||||
sx={{ flex: 1 }}
|
||||
variant="secondary"
|
||||
onClick={async () =>
|
||||
setProfilePicture(
|
||||
await showFilePicker({ acceptedFileTypes: "image/*" })
|
||||
)
|
||||
}
|
||||
>
|
||||
{profilePicture ? "Change" : "Set picture"}
|
||||
</Button>
|
||||
{profilePicture ? (
|
||||
<Button
|
||||
sx={{ flex: 1 }}
|
||||
variant="secondary"
|
||||
onClick={async () => {
|
||||
if (profile?.profilePicture) setClearProfilePicture(true);
|
||||
onClick={async () => {
|
||||
if (typeof profilePicture === "string") {
|
||||
setClearProfilePicture(true);
|
||||
setProfilePicture(undefined);
|
||||
setScale(1);
|
||||
}}
|
||||
>
|
||||
{profile?.profilePicture ? "Clear" : "Reset"}
|
||||
</Button>
|
||||
) : null}
|
||||
</Flex>
|
||||
{profilePicture ? (
|
||||
<Slider
|
||||
max={5}
|
||||
min={1}
|
||||
step={0.1}
|
||||
value={scale}
|
||||
sx={{ color: "accent" }}
|
||||
onChange={(e) => setScale(e.target.valueAsNumber)}
|
||||
/>
|
||||
} else setProfilePicture(profile?.profilePicture);
|
||||
setScale(1);
|
||||
}}
|
||||
>
|
||||
{typeof profilePicture === "string" ? "Clear" : "Reset"}
|
||||
</Button>
|
||||
) : null}
|
||||
</Flex>
|
||||
<Field
|
||||
label="Full name"
|
||||
maxLength={200}
|
||||
value={fullName}
|
||||
autoFocus
|
||||
onChange={(e) => setFullName(e.target.value)}
|
||||
/>
|
||||
{profilePicture ? (
|
||||
<Slider
|
||||
max={5}
|
||||
min={1}
|
||||
step={0.1}
|
||||
value={scale}
|
||||
sx={{ color: "accent" }}
|
||||
onChange={(e) => setScale(e.target.valueAsNumber)}
|
||||
/>
|
||||
) : null}
|
||||
</Flex>
|
||||
</Dialog>
|
||||
);
|
||||
@@ -53,7 +53,7 @@ const EmailChangeDialog = React.lazy(() => import("./email-change-dialog"));
|
||||
const AddTagsDialog = React.lazy(() => import("./add-tags-dialog"));
|
||||
const ThemeDetailsDialog = React.lazy(() => import("./theme-details-dialog"));
|
||||
const CreateColorDialog = React.lazy(() => import("./create-color-dialog"));
|
||||
const EditProfileDialog = React.lazy(() => import("./edit-profile-dialog"));
|
||||
const EditProfilePictureDialog = React.lazy(() => import("./edit-profile-picture-dialog"));
|
||||
|
||||
export const Dialogs = {
|
||||
AddNotebookDialog,
|
||||
@@ -83,5 +83,5 @@ export const Dialogs = {
|
||||
SettingsDialog,
|
||||
ThemeDetailsDialog,
|
||||
CreateColorDialog,
|
||||
EditProfileDialog
|
||||
EditProfilePictureDialog
|
||||
};
|
||||
|
||||
@@ -44,7 +44,14 @@ export default function Prompt(props: PromptDialogProps) {
|
||||
}}
|
||||
negativeButton={{ text: "Cancel", onClick: () => props.onClose(false) }}
|
||||
>
|
||||
<Field inputRef={inputRef} defaultValue={props.defaultValue} autoFocus />
|
||||
<Field
|
||||
inputRef={inputRef}
|
||||
defaultValue={props.defaultValue}
|
||||
autoFocus
|
||||
onKeyUp={(e) => {
|
||||
if (e.key == "Enter") props.onSave(inputRef.current?.value || "");
|
||||
}}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,13 @@ import { getFormattedDate } from "@notesnook/common";
|
||||
import { SUBSCRIPTION_STATUS } from "../../../common/constants";
|
||||
import dayjs from "dayjs";
|
||||
import { useMemo } from "react";
|
||||
import { showEditProfileDialog } from "../../../common/dialog-controller";
|
||||
import {
|
||||
showEditProfileDialog,
|
||||
showEditProfilePictureDialog,
|
||||
showPromptDialog
|
||||
} from "../../../common/dialog-controller";
|
||||
import { db } from "../../../common/db";
|
||||
import { showToast } from "../../../utils/toast";
|
||||
|
||||
export function UserProfile() {
|
||||
const user = useUserStore((store) => store.user);
|
||||
@@ -104,7 +110,11 @@ export function UserProfile() {
|
||||
mr: 2,
|
||||
size: 60,
|
||||
borderRadius: 80,
|
||||
overflow: "hidden"
|
||||
overflow: "hidden",
|
||||
position: "relative",
|
||||
":hover #profile-picture-edit": {
|
||||
visibility: "visible"
|
||||
}
|
||||
}}
|
||||
>
|
||||
{profile?.profilePicture ? (
|
||||
@@ -115,6 +125,30 @@ export function UserProfile() {
|
||||
) : (
|
||||
<User size={30} />
|
||||
)}
|
||||
<Flex
|
||||
id="profile-picture-edit"
|
||||
sx={{
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
bg: "#000000aa",
|
||||
top: 0,
|
||||
left: 0,
|
||||
alignItems: "center",
|
||||
alignContent: "center",
|
||||
justifyContent: "center",
|
||||
visibility: "collapse",
|
||||
cursor: "pointer"
|
||||
}}
|
||||
title="Edit profile picture"
|
||||
onClick={async () => {
|
||||
await showEditProfilePictureDialog(profile);
|
||||
}}
|
||||
>
|
||||
<Text variant="body" sx={{ color: "white" }}>
|
||||
Edit
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
<Text
|
||||
@@ -126,32 +160,40 @@ export function UserProfile() {
|
||||
{remainingDays > 0 && (isPro || isProCancelled)
|
||||
? `PRO`
|
||||
: remainingDays > 0 && isTrial
|
||||
? "TRIAL"
|
||||
: isBeta
|
||||
? "BETA TESTER"
|
||||
: "BASIC"}
|
||||
? "TRIAL"
|
||||
: isBeta
|
||||
? "BETA TESTER"
|
||||
: "BASIC"}
|
||||
</Text>
|
||||
|
||||
{profile?.fullName ? (
|
||||
<>
|
||||
<Text variant={"title"}>{profile?.fullName}</Text>
|
||||
<Text variant={"subBody"}>
|
||||
{user.email} • Member since{" "}
|
||||
{getFormattedDate(getObjectIdTimestamp(user.id), "date")}
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Text variant={"title"}>{user.email}</Text>
|
||||
<Text variant={"subBody"}>
|
||||
Member since
|
||||
{getFormattedDate(getObjectIdTimestamp(user.id), "date")}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
<Text variant={"title"}>
|
||||
{profile?.fullName || "Your full name"}{" "}
|
||||
<Edit
|
||||
sx={{ display: "inline-block", cursor: "pointer" }}
|
||||
size={12}
|
||||
title="Edit full name"
|
||||
onClick={async () => {
|
||||
const fullName = await showPromptDialog({
|
||||
title: "Edit your full name",
|
||||
description:
|
||||
"Your profile data is 100% end-to-end encrypted.",
|
||||
defaultValue: profile?.fullName
|
||||
});
|
||||
useUserStore.setState({
|
||||
profile: { ...profile, fullName: fullName || undefined }
|
||||
});
|
||||
await db.user.setProfile({ fullName: fullName || undefined });
|
||||
showToast("success", "Full name updated!");
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
<Text variant={"subBody"}>
|
||||
{user.email} • Member since{" "}
|
||||
{getFormattedDate(getObjectIdTimestamp(user.id), "date")}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Button
|
||||
{/* <Button
|
||||
variant="icon"
|
||||
sx={{
|
||||
borderRadius: 50,
|
||||
@@ -165,7 +207,7 @@ export function UserProfile() {
|
||||
onClick={() => showEditProfileDialog(profile)}
|
||||
>
|
||||
<Edit size={18} />
|
||||
</Button>
|
||||
</Button> */}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user