Creating the Edit Profile Add Links Page Linktree Project

Creating the edit profile, add links Page | LinkTree Project

This tutorial is a part of the Series ‘Complete FullStack LinkTree Project with MERN (opens in a new tab)‘. This Project covers beginner to Intermediate level implementation of MongoDB, Express, ReactJS, NodeJS, and NextJS.

Project Walkthrough

Users can signup on the LinkTree website and start creating their LinkTree immediately. They get a user profile editing page, an edit or customize links page, a Dashboard to edit and track the links, and many more.

I’ve created a YouTube series on this Project in 6 parts. This Code snippet is from the 6th and final part where we create an edit profile, and add Links page for a user’s LinkTree.

Frontend

import { createContext } from "react";
const UserContext = createContext(null);
 
export default UserContext;
// using the context in the whole app, this changes are added to previous code
 
import UserContext from "../context/userContext";
 
const [userData, setUserData] = useState({
  name: "",
  role: "",
  bio: "",
  avatar: "",
  handle: ""
});
 
<UserContext.Provider value={{ userData, setUserData }}>
  <Component {...pageProps} />
</UserContext.Provider>;
import React, { useContext, useEffect, useState } from "react";
import { useRouter } from "next/router";
import UserContext from "../../context/userContext";
import UserHeader from "../../components/UserHeader";
import { toast } from "react-toastify";
 
const profile = () => {
  const router = useRouter();
  const { userData, setUserData } = useContext(UserContext);
  const [social, setSocial] = useState({
    facebook: "",
    twitter: "",
    instagram: "",
    youtube: "",
    linkedin: "",
    github: ""
  });
  const [name, setName] = useState("");
  const [bio, setBio] = useState("");
  const [avatar, setAvatar] = useState(
    "https://cdn-icons-png.flaticon.com/64/4140/4140048.png"
  );
 
  const handleSocial = (e) => {
    setSocial({
      ...social,
      [e.target.id]: e.target.value
    });
  };
  useEffect(() => {
    if (userData) {
      setName(userData.name);
      setBio(userData.bio);
      setAvatar(userData.avatar);
    }
  }, [userData]);
  const saveProfile = (e) => {
    e.preventDefault();
    fetch(`http://localhost:8080/save/profile`, {
      method: "POST",
      headers: {
        "Content-type": "application/json"
      },
      body: JSON.stringify({
        tokenMail: localStorage.getItem("LinkTreeToken"),
        name: name,
        bio: bio,
        avatar: avatar
      })
    })
      .then((res) => res.json())
      .then((data) => {
        if (data.status === "error") return toast.error(data.error);
        toast.success("Profile saved successfully");
      });
  };
  const saveSocials = (e) => {
    e.preventDefault();
    fetch(`http://localhost:8080/save/socials`, {
      method: "POST",
      headers: {
        "Content-type": "application/json"
      },
      body: JSON.stringify({
        tokenMail: localStorage.getItem("LinkTreeToken"),
        socials: social
      })
    })
      .then((res) => res.json())
      .then((data) => {
        if (data.status === "error") return toast.error(data.error);
        toast.success("Socials saved successfully");
      });
  };
 
  useEffect(() => {
    if (!localStorage.getItem("LinkTreeToken")) return router.push("/login");
    fetch(`http://localhost:8080/load/socials`, {
      method: "POST",
      headers: {
        "Content-type": "application/json"
      },
      body: JSON.stringify({
        tokenMail: localStorage.getItem("LinkTreeToken")
      })
    })
      .then((res) => res.json())
      .then((data) => {
        if (data.status === "error") return toast.error(data.error);
        setSocial(data.socials);
      });
  }, []);
 
  return (
    <>
      <div>
        <UserHeader />
        <main>
          <section>
            <div>
              <h4 className="font-bold text-center mb-5">Edit profile</h4>
              <div>
                <form
                  onSubmit={saveProfile}
                  className="flex flex-col justify-center items-center"
                >
                  <span className="flex flex-row mb-3 w-11/12 m-auto shadow-md border-2 px-3 py-2 rounded-md focus:outline-none">
                    <img className="w-6 mr-2" src="/svg/user.svg" alt="" />
                    <input
                      value={name}
                      onChange={(e) => setName(e.target.value)}
                      className="focus:outline-none w-full"
                      type="text"
                      placeholder="Set a Name"
                      required
                    />
                  </span>
                  <span className="flex flex-row mb-3 w-11/12 m-auto shadow-md border-2 px-3 py-2 rounded-md focus:outline-none">
                    <img className="w-6 mr-2" src="/svg/bio.svg" alt="" />
                    <input
                      value={bio}
                      onChange={(e) => setBio(e.target.value)}
                      className="focus:outline-none w-full"
                      type="text"
                      placeholder="Enter a bio"
                      required
                    />
                  </span>
                  <span className="flex flex-row mb-3 w-11/12 m-auto shadow-md border-2 px-3 py-2 rounded-md focus:outline-none">
                    <img className="w-6 mr-2" src="/svg/user.svg" alt="" />
                    <input
                      value={avatar}
                      onChange={(e) => setAvatar(e.target.value)}
                      className="focus:outline-none w-full"
                      type="text"
                      placeholder="Enter Image link"
                      required
                    />
                    <img
                      className="w-10 rounded-full border-2 border-white shadow-md"
                      src={avatar}
                    />
                  </span>
                  <input
                    className="bg-green-500 w-32 px-4 py-2 rounded-md border-2 border-green-800 shadow-md cursor-pointer text-white"
                    type="submit"
                    value="save profile"
                  />
                </form>
              </div>
            </div>
            <div className="mt-14">
              <h4 className="font-bold text-center mb-5">Edit Socials</h4>
              <div>
                <form
                  onSubmit={saveSocials}
                  className="flex flex-col justify-center items-center"
                >
                  <span className="flex flex-row mb-3 w-11/12 m-auto shadow-md border-2 px-3 py-2 rounded-md focus:outline-none">
                    <img className="w-6 mr-2" src="/svg/facebook.svg" alt="" />
                    <input
                      id="facebook"
                      value={social.facebook}
                      onChange={handleSocial}
                      className="focus:outline-none w-full"
                      type="text"
                      placeholder="Enter Facebook ID"
                      required
                    />
                  </span>
                  <span className="flex flex-row mb-3 w-11/12 m-auto shadow-md border-2 px-3 py-2 rounded-md focus:outline-none">
                    <img className="w-6 mr-2" src="/svg/instagram.svg" alt="" />
                    <input
                      id="instagram"
                      value={social.instagram}
                      onChange={handleSocial}
                      className="focus:outline-none w-full"
                      type="text"
                      placeholder="Enter Instagram ID"
                      required
                    />
                  </span>
                  <span className="flex flex-row mb-3 w-11/12 m-auto shadow-md border-2 px-3 py-2 rounded-md focus:outline-none">
                    <img className="w-6 mr-2" src="/svg/twt.svg" alt="" />
                    <input
                      id="twitter"
                      value={social.twitter}
                      onChange={handleSocial}
                      className="focus:outline-none w-full"
                      type="text"
                      placeholder="Enter Twitter ID"
                      required
                    />
                  </span>
                  <span className="flex flex-row mb-3 w-11/12 m-auto shadow-md border-2 px-3 py-2 rounded-md focus:outline-none">
                    <img className="w-6 mr-2" src="/svg/lnkdn.svg" alt="" />
                    <input
                      id="linkedin"
                      value={social.linkedin}
                      onChange={handleSocial}
                      className="focus:outline-none w-full"
                      type="text"
                      placeholder="Enter Linkedin ID"
                      required
                    />
                  </span>
                  <span className="flex flex-row mb-3 w-11/12 m-auto shadow-md border-2 px-3 py-2 rounded-md focus:outline-none">
                    <img className="w-6 mr-2" src="/svg/github.svg" alt="" />
                    <input
                      id="github"
                      value={social.github}
                      onChange={handleSocial}
                      className="focus:outline-none w-full"
                      type="text"
                      placeholder="Enter Github ID"
                      required
                    />
                  </span>
                  <span className="flex flex-row mb-3 w-11/12 m-auto shadow-md border-2 px-3 py-2 rounded-md focus:outline-none">
                    <img className="w-6 mr-2" src="/svg/yt.svg" alt="" />
                    <input
                      id="youtube"
                      value={social.youtube}
                      onChange={handleSocial}
                      className="focus:outline-none w-full"
                      type="text"
                      placeholder="Enter YouTube ID @"
                      required
                    />
                  </span>
                  <input
                    className="bg-green-500 w-32 px-4 py-2 rounded-md border-2 border-green-800 shadow-md cursor-pointer mb-10 text-white"
                    type="submit"
                    value="save Socials"
                  />
                </form>
              </div>
            </div>
          </section>
        </main>
      </div>
    </>
  );
};
 
export default profile;
import UserHeader from "../../components/UserHeader";
import React, { useState, useEffect, useContext } from "react";
import { toast } from "react-toastify";
 
const links = () => {
  const [links, setLinks] = useState([{ url: "", title: "" }]);
  const [title, setTitle] = useState("");
 
  const handleLinkChange = (index, field, value) => {
    const updatedLinks = [...links];
    const linkToUpdate = { ...updatedLinks[index], [field]: value };
    updatedLinks[index] = linkToUpdate;
    setLinks(updatedLinks);
  };
 
  const handleAddLink = () => {
    setLinks([...links, { url: "", title: "" }]);
  };
 
  const handleRemoveLink = (index) => {
    const updatedLinks = [...links];
    updatedLinks.splice(index, 1);
    setLinks(updatedLinks);
  };
 
  const saveLinks = (e) => {
    e.preventDefault();
    const linksArray = Object.values(links);
    const titlesArray = Object.values(title);
    const linksData = linksArray.map((link, index) => ({
      link,
      title: titlesArray[index]
    }));
 
    fetch(`http://localhost:8080/save/links`, {
      method: "POST",
      headers: {
        "Content-type": "application/json"
      },
      body: JSON.stringify({
        tokenMail: localStorage.getItem("LinkTreeToken"),
        links: linksData
      })
    })
      .then((res) => res.json())
      .then((data) => {
        if (data.status === "error") return toast.error(data.error);
        toast.success("Links saved successfully");
      })
      .catch((err) => {
        toast.error(err.message);
      });
  };
 
  useEffect(() => {
    if (!localStorage.getItem("LinkTreeToken")) return router.push("/login");
    fetch(`http://localhost:8080/load/links`, {
      method: "POST",
      headers: {
        "Content-type": "application/json"
      },
      body: JSON.stringify({
        tokenMail: localStorage.getItem("LinkTreeToken")
      })
    })
      .then((res) => res.json())
      .then((data) => {
        if (data.status === "error") return toast.error(data.error);
        setLinks(data.links);
      });
  }, []);
 
  return (
    <>
      <div>
        <UserHeader />
        <main>
          <section>
            <h1 className="text-center font-bold text-xl text-gray-600">
              Add or Customize your Links
            </h1>
            <div>
              <form onSubmit={saveLinks}>
                {links.map((link, index) => (
                  <div
                    className="flex flex-row justify-evenly my-2"
                    key={index}
                  >
                    <label>
                      URL:
                      <input
                        className="outline-none border-2 border-gray-200 shadow-md rounded-md px-2 p-1 ml-2"
                        type="text"
                        value={link.url}
                        onChange={(e) =>
                          handleLinkChange(index, "url", e.target.value)
                        }
                      />
                    </label>
                    <label>
                      Title:
                      <input
                        className="outline-none border-2 border-gray-200 shadow-md rounded-md px-2 p-1 ml-2"
                        type="text"
                        value={link.title}
                        onChange={(e) =>
                          handleLinkChange(index, "title", e.target.value)
                        }
                      />
                    </label>
                    <button
                      className="bg-indigo-500 text-white px-4 py-2 rounded-md shadow-sm ml-3"
                      type="button"
                      onClick={() => {
                        handleRemoveLink(index);
                      }}
                    >
                      Remove Link
                    </button>
                  </div>
                ))}
                <div className="buttons flex flex-row gap-5 my-1">
                  <button
                    className="bg-indigo-500 text-white px-4 py-2 rounded-md shadow-sm w-full"
                    type="button"
                    onClick={handleAddLink}
                  >
                    Add link
                  </button>
                  <button
                    className="bg-green-500 text-white px-4 py-2 rounded-md shadow-sm w-full"
                    type="submit"
                  >
                    Save
                  </button>
                </div>
              </form>
            </div>
          </section>
        </main>
      </div>
    </>
  );
};
 
export default links;

Backend

// Added logic for loading saved data, and add new data
 
app.post("/save/socials", saveSocials);
app.post("/save/profile", saveProfile);
app.post("/save/links", saveLinks);
app.post("/load/socials", loadSocials);
app.post("/load/links", loadLinks);
const User = require("../models/user");
const jwt_decode = require("jwt-decode");
 
const saveSocials = async (req, res) => {
  const { tokenMail, socials } = req.body;
  console.log(req.body);
  try {
    const decodedTokenMail = jwt_decode(tokenMail, process.env.SECRET_JWT);
    const email = decodedTokenMail.email;
    console.log(email);
    const user = await User.findOne({ email: email });
    console.log(user);
    user.socialMedia = socials;
    user.save();
    return res.json({ message: "saved", status: "success" });
  } catch (err) {
    return res.json({ status: "error", error: err.message });
  }
};
 
const saveProfile = async (req, res) => {
  const { tokenMail, name, bio, avatar } = req.body;
  console.log(req.body);
  try {
    const decodedTokenMail = jwt_decode(tokenMail, process.env.SECRET_JWT);
    const email = decodedTokenMail.email;
    console.log(email);
    const user = await User.findOne({ email: email });
    console.log(user);
    user.name = name;
    user.bio = bio;
    user.avatar = avatar;
    user.save();
    return res.json({ message: "saved", status: "success" });
  } catch (err) {
    return res.json({ status: "error", error: err.message });
  }
};
 
const saveLinks = async (req, res) => {
  const { tokenMail, links } = req.body;
  try {
    const decodedTokenMail = jwt_decode(tokenMail, process.env.SECRET_JWT);
    const email = decodedTokenMail.email;
    console.log(email);
    const user = await User.findOne({ email: email });
    console.log(user);
    const newLinks = links.map((link) => ({
      url: link.link.url,
      title: link.link.title,
      icon: ""
    }));
    user.links = newLinks;
    await user.save();
    return res.json({ message: "saved", status: "success" });
  } catch (err) {
    return res.json({ status: "error", error: err.message });
  }
};
 
module.exports = { saveSocials, saveProfile, saveLinks };
const User = require("../models/user");
const jwt_decode = require("jwt-decode");
 
const loadSocials = async (req, res) => {
  const { tokenMail } = req.body;
  try {
    const decodedTokenMail = jwt_decode(tokenMail, process.env.SECRET_JWT);
    const email = decodedTokenMail.email;
    console.log(email);
    const user = await User.findOne({ email: email });
    const socials = user.socialMedia;
    return res.json({ message: "found", socials, status: "success" });
  } catch (error) {
    return res.json({ status: "error", error: error.message });
  }
};
 
const loadLinks = async (req, res) => {
  const { tokenMail } = req.body;
  try {
    const decodedTokenMail = jwt_decode(tokenMail, process.env.SECRET_JWT);
    const email = decodedTokenMail.email;
    console.log(email);
    const user = await User.findOne({ email: email });
    const links = user.links;
    return res.json({ message: "found", links, status: "success" });
  } catch (error) {
    return res.json({ status: "error", error: error.message });
  }
};
 
module.exports = { loadSocials, loadLinks };

Summing up

I hope this part of the tutorial on the Fullstack LinkTree Project was helpful to you. If you faced any problems while following the video or the source code snippets, comment below and I’ll reply as soon as possible. See you soon.

IndGeek provides solutions in the software field, and is a hub for ultimate Tech Knowledge.