Introduction

This guide will help you to create a completely headless user profile including the following components:

  • Show the user’s profile based on whether they are logged in or not
  • Display the primary wallet address
  • Show the primary wallet icon
  • Display current and available networks
  • Show onramp button and open flow
  • Show balance of primary wallet
  • Show all connected wallets and allow unlinking
  • Allow user to link a new wallet
  • Switch the primary wallet to another
  • Show user profile information
  • Allow user to update their profile information
  • Show users linked social accounts, allow linking/unlinking

Prerequisites

Like with all this series of headless guides, headless is defined a way to use the Dynamic SDK without the need to use the Dynamic UI components (i.e. DynamicWidget, DynamicUserProfile).

You still need to add the SDK and set up the Dynamic Context Provider (complete the quickstart if you haven’t done so already, or refer to an example app)

Individual Components

Show profile if logged in

How: Check if the user is logged in or not

Hook/Component: useIsLoggedIn

Notes: We start here assuming you have signup/login implemented already

import { useIsLoggedIn } from "@dynamic-labs/sdk-react-core";

const isLoggedIn = useIsLoggedIn();

return <>{isLoggedIn ? <Profile /> : </Login>}</>

Display primary wallet address

How: Fetch the primary wallet info from context

Hook/Component: useDynamicContext

Notes: There can be a moment after logging in when it’s not populated yet

import { useDynamicContext } from "@dynamic-labs/sdk-react-core";

const { primaryWallet } = useDynamicContext();

const copyWalletAddress = () => {
  navigator.clipboard.writeText(primaryWallet?.address);
};

return (
  <button onCLick={() => copyWalletAddress()}>
    <p>...{primaryWallet?.address?.slice(-6)}</p>
  </button>
);

Display primary wallet icon

How: Utilize the Walletbook library

Hook/Component: WalletIcon (not publicly documented yet)

Notes: Requires the primaryWallet.connector object.

import { primaryWallet } from "@dynamic-labs/sdk-react-core";
import { WalletIcon } from "@dynamic-labs/wallet-book";

const WalletIconWrapped = ({ connector }) => {

  return (
    <div className="wallet-icon-container">
      <WalletIcon walletKey={primaryWallet?.connector?.key} />
    </div>
  );
};

Display current and available networks

How: Use evmNetworks and getNetwork to fetch, use supportsNetworkSwitching and switchNetwork to switch

Hook/Component: useDynamicContext & getNetwork

Notes: This example is for Evm networks only

import { useDynamicContext, getNetwork } from "@dynamic-labs/sdk-react-core";

const CustomNetworkPicker = () => {
  const [currentNetwork, setCurrentNetwork] = useState(null);
  const { primaryWallet } = useDynamicContext();

  const handleNetworkChange = async (event) => {
    const chainId = parseInt(event.target.value);

    if (primaryWallet?.connector?.supportsNetworkSwitching()) {
      try {
        return await primaryWallet?.connector?.switchNetwork({
          networkChainId: chainId,
        });
      } catch (error) {
        console.error("Error switching network", error);
      }
    }
  };

  useEffect(() => {
    if (!currentNetwork)
      getNetwork(primaryWallet?.connector).then((network) => {
        setCurrentNetwork(network);
      });
  }, [primaryWallet]);

  return (
    <>
      {currentNetwork && (
        <Select defaultValue={currentNetwork} onChange={handleNetworkChange}>
          {primaryWallet?.connector?.evmNetworks?.map((network) => (
            <option key={network.chainId} value={network.chainId}>
              {network.name}
            </option>
          ))}
        </Select>
      )}
    </>
  );
};

Show onramp button and open flow

How: Regular button attached to useFunding hook

Hook/Component: useFunding

Notes: Ensure Banxa is enabled in the Dynamic dashboard

import { useFunding } from "@dynamic-labs/sdk-react-core";

const Onramp = () => {
  const { enabled, openFunding } = useFunding();

  return (
    <div>
      {enabled && (
        <button className="onramp-button" onClick={openFunding}>
          Onramp
        </button>
      )}
    </div>
  );
};

Show balance of primary wallet

How: getBalance method from useDynamicContext wallet connector

Hook/Component: https://docs.dynamic.xyz/react-sdk/hooks/usedynamiccontext

Notes: None

import { useDynamicContext } from "@dynamic-labs/sdk-react-core";

const { primaryWallet } = useDynamicContext();

const [balance, setBalance] = useState(null);

useEffect(() => {
  const fetchBalance = async () => {
    if (primaryWallet) {
      const value = await primaryWallet.connector.getBalance();
      setBalance(value);
    }
  };
  fetchBalance();
}, [primaryWallet]);

return <p>{balance}</p>;

Show all connected wallets and allow unlinking

How: mapping return from useUserWallets hook and handleUnlinkWallet from useDynamicContext

Hook/Component: https://docs.dynamic.xyz/react-sdk/hooks/useuserwallets & https://docs.dynamic.xyz/react-sdk/hooks/usedynamiccontext

Notes: Doesn’t yet show if a wallet is the primary wallet. Unlinking is also handled here.

import {
  useDynamicContext,
  useUserWallets,
} from "@dynamic-labs/sdk-react-core";

import { Tooltip } from "react-tooltip";

const LinkedWallets = () => {
  const { handleUnlinkWallet } = useDynamicContext();

  const userWallets = useUserWallets();

  return (
    <>
      {userWallets.length > 0 && (
        <div className="profile-wallets-container">
          <h2>Connected Wallets</h2>
          {userWallets.length < 2 && <Tooltip id="unlink-tooltip" />}
          <div className="profile-wallets-inner-container">
            {userWallets.map((wallet) => (
              <Flex key={wallet.address}>
                <p>{wallet.address}</p>
                <Spacer />
                <a
                  data-tooltip-id="unlink-tooltip"
                  data-tooltip-content="Can't unlink your only wallet!"
                >
                  <Button
                    size="xs"
                    onClick={() => handleUnlinkWallet(wallet.id)}
                    disabled={userWallets.length < 2}
                  >
                    Unlink
                  </Button>
                </a>
              </Flex>
            ))}
          </div>
        </div>
      )}
    </>
  );
};

How: Pop up our linking modal using a hook (setShowLinkNewWalletModal)

Hook/Component: https://docs.dynamic.xyz/react-sdk/hooks/usedynamicmodals

Notes: You will need to also use the DynamicMultiWalletPromptsWidget component

import {
  useDynamicModals,
  DynamicMultiWalletPromptsWidget,
} from "@dynamic-labs/sdk-react-core";

const LinkWallet = ({ text }) => {
  const { setShowLinkNewWalletModal } = useDynamicModals();

  return (
    <>
      <div className="link-wallet-container">
        <Button
          className="profile-button"
          onClick={() => setShowLinkNewWalletModal(true)}
        >
          {text}
        </Button>
      </div>
      <DynamicMultiWalletPromptsWidget />
    </>
  );
};

Switch the primary wallet to another

How: setPrimaryWallet from useDynamicContext

Hook/Component: https://docs.dynamic.xyz/react-sdk/hooks/usedynamiccontext

Notes: None

import { useDynamicContext } from "@dynamic-labs/sdk-react-core";

const { setPrimaryWallet } = useDynamicContext();

const handleChangePrimaryWallet = (wallet) => setPrimaryWallet(wallet.id);

Show user profile information

How: Fetch info from user object on useDynamicContext

Hook/Component: useDynamicContext

Notes: The format of the user can be found here: userProfile

import { useDynamicContext } from "@dynamic-labs/sdk-react-core";

const { user } = useDynamicContext();

return (
  <div className="user-details">
    {user?.firstName && <p>First name: {user.firstName} </p>}
    {user?.email && <p>E-Mail: {user.email} </p>}
    {user?.alias && <p>Alias: {user.alias} </p>}
    {user?.lastName && <p>Last name: {user.lastName} </p>}
    {user?.jobTitle && <p>Job: {user.jobTitle} </p>}
    {user?.phoneNumber && <p>Phone: {user.phoneNumber} </p>}
    {user?.tShirtSize && <p>Tshirt size: {user.tShirtSize} </p>}
    {user?.team && <p>Team: {user.team} </p>}
    {user?.country && <p>Country: {user.country} </p>}
    {user?.username && <p>Username: {user.username} </p>}
  </div>
);

Allow user to update their profile information

How: useUserUpdateRequest hook

Hook/Component: useUserUpdateRequest

Notes: We include the validation for email updates here

import { useUserUpdateRequest, useOtpVerificationRequest } from "@dynamic-labs/sdk-react-core";

const { verifyOtp } = useOtpVerificationRequest();
const { updateUser } = useUserUpdateRequest();

const [showUpdateForm, setShowUpdateForm] = useState(false);
const [showVerifyForm, setShowVerifyEmailForm] = useState(false);

const updateUserInfoFormSubmit = async (e) => {
   e.preventDefault();
   try {
     setLoading(true);
     // Call the updateUser function with the new values entered by the user
     const { isEmailVerificationRequired } = await updateUser({
       firstName: e.target[0].value,
       email: e.target[1].value,
     });
     // If email verification is required, show the email verification form
     if (isEmailVerificationRequired) {
       setShowVerifyEmailForm(true);
     }
   } catch (e) {
     console.log("Error", e);
   } finally {
     setLoading(false);
     setShowUpdateForm(false);
   }
 };

   // Handler for the email verification form submission
const onVerifyEmailFormSubmit = async (e) => {
    e.preventDefault();
    try {
      setLoading(true);
      const verificationToken = e.target[0].value;
      // Call the verifyEmail function with the entered verification token
      await verifyOtp(verificationToken);
    } catch (e) {
      console.log("Error", e);
    } finally {
      setLoading(false);
      // Hide the email verification form after the process is completed
      setShowVerifyEmailForm(false);
    }
    return false;
};


return (
    <div>
          {/* Render the profile update form */}
          {showUpdateForm && (
            <div>
              <form onSubmit={onProfileFormSubmit} className="form">
                <div className="form__row">
                  <label className="label" htmlFor="firstName">
                    First-Name
                  </label>
                  <input
                    id="firstName"
                    className="form__input"
                    defaultValue={user.firstName}
                    disabled={loading || showVerifyEmailForm}
                  />
                </div>
                <div className="form__row">
                  <label className="label" htmlFor="email">
                    E-Mail
                  </label>
                  <input
                    type="email"
                    id="email"
                    className="form__input"
                    defaultValue={user.email}
                    disabled={loading || showVerifyEmailForm}
                  />
                </div>
                <button
                  disabled={loading || showVerifyEmailForm}
                  className="form__button"
                  type="submit"
                >
                  Save
                </button>
              </form>
            </div>
          )}

         {/* Render the email verification form if needed */}
          {showVerifyEmailForm && (
            <form onSubmit={onVerifyEmailFormSubmit} className="form">
              <h6>Verify Email</h6>
              <div className="form__row">
                <label htmlFor="verificationToken">Verification Token</label>
                <input
                  disabled={loading}
                  pattern="^\d{6}$"
                  name="verificationToken"
                />
              </div>
              <button disabled={loading} className="form__button" type="submit">
                Send
              </button>
            </form>
          )}
    <div>
)

Show users linked social accounts, allow linking/unlinking

How: useSocialAccounts hook from sdk-react-core

Hook/Component: useSocialAccounts

Notes: None

import { useSocialAccounts } from "@dynamic-labs/sdk-react-core";
import { SocialIcon } from '@dynamic-labs/iconic';

const Avatar = ({ avatarUrl }) => {
  return (
    <div className="avatar">
      <img src={avatarUrl} alt="avatar" />
    </div>
  );
};


const Icon = ({ provider }) => {
  return (
    <div className="icon-container">
    <SocialIcon name={provider} />
    </div>
  );
};

const UserProfileSocialAccount = ({ provider }) => {
  const {
    linkSocialAccount,
    unlinkSocialAccount,
    isProcessing,
    isLinked,
    getLinkedAccountInformation,
  } = useSocialAccounts();

  const isProviderLinked = isLinked(provider);
  const connectedAccountInfo = getLinkedAccountInformation(provider);

  return (
    <Flex>
      <div className="icon">
        {isProviderLinked ? (
          <Avatar avatarUrl={connectedAccountInfo?.avatar} />
        ) : (
          <Icon provider={provider} />
        )}
      </div>
      <div className="label">
        <p>{connectedAccountInfo?.publicIdentifier ?? provider}</p>
      </div>
      {isProviderLinked ? (
        <button
          onClick={() => unlinkSocialAccount(provider)}
          loading={isProcessing}
        >
          Disconnect
        </button>
      ) : (
        <button
          onClick={() => linkSocialAccount(provider)}
          loading={isProcessing}
        >
          Connect
        </button>
      )}
    </Flex>
  );
};

const Socials = () => {
  const providers = [
    "discord",
    "facebook",
    "github",
    "google",
    "instagram",
    "twitch",
    "twitter",
  ];

  return (
    <Flex direction="column" align="stretch">
      {providers.map((provider) => (
        <UserProfileSocialAccount provider={provider} />
      ))}
    </Flex>
  );
};