From aa334dfb09274c6eb3092f7153d51ad5ed6cb453 Mon Sep 17 00:00:00 2001 From: an-lee Date: Sun, 7 Apr 2024 14:38:43 +0800 Subject: [PATCH] Feat: update user profile (#491) * add update profile api * may update email * may update username --- enjoy/src/api/client.ts | 11 ++ enjoy/src/renderer/components/login-form.tsx | 18 ++- .../components/preferences/email-settings.tsx | 128 +++++++++++++++ .../renderer/components/preferences/index.ts | 1 + .../components/preferences/preferences.tsx | 8 +- .../components/preferences/user-settings.tsx | 146 ++++++++++++------ enjoy/src/types/user.d.ts | 1 + 7 files changed, 263 insertions(+), 50 deletions(-) create mode 100644 enjoy/src/renderer/components/preferences/email-settings.tsx diff --git a/enjoy/src/api/client.ts b/enjoy/src/api/client.ts index ea2e3bee..fc5e7079 100644 --- a/enjoy/src/api/client.ts +++ b/enjoy/src/api/client.ts @@ -82,6 +82,17 @@ export class Client { return this.api.get("/api/me"); } + updateProfile( + id: string, + params: { + name?: string; + email?: string; + code?: string; + } + ): Promise { + return this.api.put(`/api/users/${id}`, decamelizeKeys(params)); + } + loginCode(params: { phoneNumber?: string; email?: string }): Promise { return this.api.post("/api/sessions/login_code", decamelizeKeys(params)); } diff --git a/enjoy/src/renderer/components/login-form.tsx b/enjoy/src/renderer/components/login-form.tsx index 395beb81..8af53baa 100644 --- a/enjoy/src/renderer/components/login-form.tsx +++ b/enjoy/src/renderer/components/login-form.tsx @@ -214,11 +214,17 @@ const EmailLoginForm = () => { const { login, webApi } = useContext(AppSettingsProviderContext); useEffect(() => { + let timeout: NodeJS.Timeout; + if (countdown > 0) { - setTimeout(() => { + timeout = setTimeout(() => { setCountdown(countdown - 1); }, 1000); } + + return () => { + if (timeout) clearTimeout(timeout); + } }, [countdown]); return ( @@ -340,11 +346,17 @@ const PandoLoginForm = () => { }, [iti]); useEffect(() => { + let timeout: NodeJS.Timeout; + if (countdown > 0) { - setTimeout(() => { + timeout = setTimeout(() => { setCountdown(countdown - 1); }, 1000); } + + return () => { + if (timeout) clearTimeout(timeout); + } }, [countdown]); return ( @@ -393,7 +405,7 @@ const PandoLoginForm = () => { .then(() => { toast.success(t("codeSent")); setCodeSent(true); - setCountdown(60); + setCountdown(120); }) .catch((err) => { toast.error(err.message); diff --git a/enjoy/src/renderer/components/preferences/email-settings.tsx b/enjoy/src/renderer/components/preferences/email-settings.tsx new file mode 100644 index 00000000..74b4bc31 --- /dev/null +++ b/enjoy/src/renderer/components/preferences/email-settings.tsx @@ -0,0 +1,128 @@ +import { t } from "i18next"; +import { + Button, + Dialog, + DialogTrigger, + DialogContent, + DialogHeader, + DialogTitle, + Label, + Input, + toast, +} from "@renderer/components/ui"; +import { AppSettingsProviderContext } from "@renderer/context"; +import { useContext, useEffect, useState } from "react"; + +export const EmailSettings = () => { + const { user, login, webApi } = useContext(AppSettingsProviderContext); + const [editing, setEditing] = useState(false); + const [email, setEmail] = useState(user.email); + const [code, setCode] = useState(''); + const [codeSent, setCodeSent] = useState(false); + const [countdown, setCountdown] = useState(0); + + const refreshProfile = () => { + webApi.me().then((profile: UserType) => { + login(Object.assign({}, user, profile)); + }); + }; + + useEffect(() => { + let timeout: NodeJS.Timeout; + + if (countdown > 0) { + timeout = setTimeout(() => { + setCountdown(countdown - 1); + }, 1000); + } + + return () => { + if (timeout) clearTimeout(timeout); + } + }, [countdown]); + + if (!user) return null; + + return ( +
+
+
{t("email")}
+
{user.email || '-'}
+
+ + setEditing(value)}> + + + + + + + + {t('editEmail')} + + + +
+
+
+ + setEmail(e.target.value)} + placeholder="m@example.com" /> +
+ +
+ + setCode(e.target.value)} + placeholder={t('verificationCode')} + /> +
+
+ +
+ + + +
+
+
+
+
+ ); +}; diff --git a/enjoy/src/renderer/components/preferences/index.ts b/enjoy/src/renderer/components/preferences/index.ts index 3545934a..6ba224ee 100644 --- a/enjoy/src/renderer/components/preferences/index.ts +++ b/enjoy/src/renderer/components/preferences/index.ts @@ -10,6 +10,7 @@ export * from "./whisper-settings"; export * from "./google-generative-ai-settings"; export * from "./user-settings"; +export * from "./email-settings"; export * from "./balance-settings"; export * from "./reset-settings"; diff --git a/enjoy/src/renderer/components/preferences/preferences.tsx b/enjoy/src/renderer/components/preferences/preferences.tsx index 2e470cc8..010e08f0 100644 --- a/enjoy/src/renderer/components/preferences/preferences.tsx +++ b/enjoy/src/renderer/components/preferences/preferences.tsx @@ -17,6 +17,7 @@ import { } from "@renderer/components"; import { useState } from "react"; import { Tooltip } from "react-tooltip"; +import { EmailSettings } from "./email-settings"; export const Preferences = () => { const TABS = [ @@ -68,6 +69,8 @@ export const Preferences = () => { + + @@ -102,9 +105,8 @@ export const Preferences = () => { key={tab.value} variant={activeTab === tab.value ? "default" : "ghost"} size="sm" - className={`capitilized w-full justify-start mb-2 ${ - activeTab === tab.value ? "" : "hover:bg-muted" - }`} + className={`capitilized w-full justify-start mb-2 ${activeTab === tab.value ? "" : "hover:bg-muted" + }`} onClick={() => setActiveTab(tab.value)} > {tab.label} diff --git a/enjoy/src/renderer/components/preferences/user-settings.tsx b/enjoy/src/renderer/components/preferences/user-settings.tsx index 997c316c..8aa9ba7d 100644 --- a/enjoy/src/renderer/components/preferences/user-settings.tsx +++ b/enjoy/src/renderer/components/preferences/user-settings.tsx @@ -13,60 +13,118 @@ import { AvatarImage, AvatarFallback, Button, + Dialog, + DialogTrigger, + DialogContent, + DialogHeader, + DialogTitle, + Label, + Input, + toast, } from "@renderer/components/ui"; import { AppSettingsProviderContext } from "@renderer/context"; -import { useContext } from "react"; +import { useContext, useState } from "react"; import { redirect } from "react-router-dom"; export const UserSettings = () => { - const { user, logout } = useContext(AppSettingsProviderContext); + const { user, login, logout, webApi } = useContext(AppSettingsProviderContext); + const [name, setName] = useState(user.name); + const [editing, setEditing] = useState(false); + + const refreshProfile = () => { + webApi.me().then((profile: UserType) => { + login(Object.assign({}, user, profile)); + }); + }; if (!user) return null; return ( -
-
-
- - - - {user.name[0].toUpperCase()} - - -
-
{user.name}
-
{user.id}
+ <> +
+
+
+ + + + {user.name[0].toUpperCase()} + + +
+
{user.name}
+
{user.id}
+
+ +
+ setEditing(value)}> + + + + + + + {t('updateUserName')} + + +
+
+ + setName(e.target.value)} + /> +
+
+ +
+
+
+
+ + + + + + + + + {t("logout")} + + + {t("logoutConfirmation")} + + + {t("cancel")} + { + logout(); + redirect("/"); + }} + > + {t("logout")} + + + + +
- - - - - - - - - {t("logout")} - - - {t("logoutConfirmation")} - - - {t("cancel")} - { - logout(); - redirect("/"); - }} - > - {t("logout")} - - - - -
+ ); }; diff --git a/enjoy/src/types/user.d.ts b/enjoy/src/types/user.d.ts index 5c24cfd2..e1f1164f 100644 --- a/enjoy/src/types/user.d.ts +++ b/enjoy/src/types/user.d.ts @@ -1,6 +1,7 @@ type UserType = { id: string; name: string; + email?: string; balance?: number; avatarUrl?: string; accessToken?: string;