Creating the edit profile, add links Page | LinkTree Project

• Updated May 5, 2023

This tutorial is a part of the Series ‘Complete FullStack LinkTree Project with MERN‘. 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.

Sharing Is Caring:
Soumya Mondal

An aspiring BTech Electronics and Communication student, obsessed with Coding, Gadgets, and Blogging.

Leave a Comment