diff --git a/backend/native/backpack-api/src/db/users.ts b/backend/native/backpack-api/src/db/users.ts index e3b4ed627..0f8c2678d 100644 --- a/backend/native/backpack-api/src/db/users.ts +++ b/backend/native/backpack-api/src/db/users.ts @@ -468,6 +468,29 @@ export async function createUserPublicKey({ return response.insert_auth_public_keys_one; } +export const updateMetadata = async ( + uuid: string, + firstName: string, + lastName: string +) => { + const response = await chain("mutation")({ + update_auth_users: [ + { + where: { + id: { _eq: uuid }, + }, + _set: { + firstname: firstName, + lastname: lastName, + }, + }, + { + affected_rows: true, + }, + ], + }); +}; + /** * Update avatar_nft of a user. */ diff --git a/backend/native/backpack-api/src/routes/v1/users.ts b/backend/native/backpack-api/src/routes/v1/users.ts index 00dc7962e..75ef33996 100644 --- a/backend/native/backpack-api/src/routes/v1/users.ts +++ b/backend/native/backpack-api/src/routes/v1/users.ts @@ -30,6 +30,7 @@ import { getUsersByPrefix, getUsersByPublicKeys, getUsersMetadata, + updateMetadata, updateUserAvatar, } from "../../db/users"; import { getOrcreateXnftSecret } from "../../db/xnftSecrets"; @@ -110,6 +111,28 @@ router.get("/", extractUserId, async (req, res) => { }); }); +router.get("/metadata", extractUserId, async (req, res) => { + // @ts-ignore + const uuid = req.id as string; + const userMetadata = await getUser(uuid); + res.json({ + username: userMetadata.username, + firstName: userMetadata.firstname, + lastName: userMetadata.lastname, + }); +}); + +router.put("/metadata", extractUserId, async (req, res) => { + // @ts-ignore + const uuid = req.id as string; + const firstName = req.body.firstName; + const lastName = req.body.lastName; + + await updateMetadata(uuid, firstName, lastName); + + res.json({}); +}); + router.get("/jwt/xnft", extractUserId, async (req, res) => { // @ts-ignore const uuid = req.id as string; diff --git a/packages/app-extension/src/components/Unlocked/Settings/Preferences/index.tsx b/packages/app-extension/src/components/Unlocked/Settings/Preferences/index.tsx index cb51ed011..c9fc8c964 100644 --- a/packages/app-extension/src/components/Unlocked/Settings/Preferences/index.tsx +++ b/packages/app-extension/src/components/Unlocked/Settings/Preferences/index.tsx @@ -90,6 +90,9 @@ export function Preferences() { "Trusted Sites": { onClick: () => nav.push("preferences-trusted-sites"), }, + "Update Profile": { + onClick: () => nav.push("preferences-update-profile"), + }, }; if (BACKPACK_FEATURE_LIGHT_MODE) { diff --git a/packages/app-extension/src/components/Unlocked/Settings/SettingsNavStackDrawer.tsx b/packages/app-extension/src/components/Unlocked/Settings/SettingsNavStackDrawer.tsx index 7b2808ec1..e3e1dbeb8 100644 --- a/packages/app-extension/src/components/Unlocked/Settings/SettingsNavStackDrawer.tsx +++ b/packages/app-extension/src/components/Unlocked/Settings/SettingsNavStackDrawer.tsx @@ -13,7 +13,7 @@ import { ContactRequests, Contacts } from "../Messages/Contacts"; import { Requests } from "../Messages/Requests"; import { CreateMenu } from "./AddConnectWallet/CreateMenu"; -import { CreateMnemonic } from "./AddConnectWallet/CreateMnemonic" +import { CreateMnemonic } from "./AddConnectWallet/CreateMnemonic"; import { ImportMenu } from "./AddConnectWallet/ImportMenu"; import { ImportMnemonic } from "./AddConnectWallet/ImportMnemonic"; import { ImportSecretKey } from "./AddConnectWallet/ImportSecretKey"; @@ -43,6 +43,7 @@ import { import { AboutBackpack } from "./AboutBackpack"; import { AddConnectPreview, AddConnectWalletMenu } from "./AddConnectWallet"; import { Preferences } from "./Preferences"; +import { UpdateProfile } from "./UpdateProfile"; import { XnftSettings } from "./Xnfts"; import { YourAccount } from "./YourAccount"; import { SettingsMenu } from "."; @@ -106,6 +107,10 @@ export function SettingsNavStackDrawer({ name="preferences-trusted-sites" component={(props: any) => } /> + } + /> } diff --git a/packages/app-extension/src/components/Unlocked/Settings/UpdateProfile.tsx b/packages/app-extension/src/components/Unlocked/Settings/UpdateProfile.tsx new file mode 100644 index 000000000..bbf740d02 --- /dev/null +++ b/packages/app-extension/src/components/Unlocked/Settings/UpdateProfile.tsx @@ -0,0 +1,70 @@ +import { useState } from "react"; +import { BACKEND_API_URL } from "@coral-xyz/common"; +import { PrimaryButton, TextInput } from "@coral-xyz/react-common"; +import { userMetadata } from "@coral-xyz/recoil"; +import { useRecoilState, useRecoilValue } from "recoil"; + +import { useNavigation } from "../../common/Layout/NavStack"; + +export const UpdateProfile = () => { + const [metadata, setMetadata] = useRecoilState(userMetadata); + const [firstName, setFirstName] = useState(metadata?.firstName || ""); + const [lastName, setLastName] = useState(metadata?.lastName || ""); + const nav = useNavigation(); + + return ( +
+
+ { + setFirstName(e.target.value); + }} + /> + { + setLastName(e.target.value); + }} + /> +
+ { + const response = await fetch(`${BACKEND_API_URL}/users/metadata`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + firstName, + lastName, + }), + }); + setMetadata({ + firstName, + lastName, + }); + + nav.pop(); + }} + /> +
+ ); +}; diff --git a/packages/recoil/src/atoms/index.tsx b/packages/recoil/src/atoms/index.tsx index d477ca0d7..ebcb78c50 100644 --- a/packages/recoil/src/atoms/index.tsx +++ b/packages/recoil/src/atoms/index.tsx @@ -6,6 +6,7 @@ export * from "./ethereum"; export * from "./feature-gates"; export * from "./friendship"; export * from "./keyring"; +export * from "./metadata"; export * from "./nft"; export * from "./notifications"; export * from "./preferences"; diff --git a/packages/recoil/src/atoms/metadata.ts b/packages/recoil/src/atoms/metadata.ts new file mode 100644 index 000000000..290dde963 --- /dev/null +++ b/packages/recoil/src/atoms/metadata.ts @@ -0,0 +1,22 @@ +import { BACKEND_API_URL } from "@coral-xyz/common"; +import { atom, selector } from "recoil"; + +export const userMetadata = atom<{ + firstName: string; + lastName: string; +} | null>({ + key: "userMetadata", + default: selector({ + key: "userMetadataSelector", + get: async ({ get }) => { + const response = await fetch(`${BACKEND_API_URL}/users/metadata`, { + method: "GET", + }); + const json = await response.json(); + return { + firstName: json.firstName, + lastName: json.lastName, + }; + }, + }), +});