Open In App

Social Media Dashboard With NextJS

Last Updated : 03 Jul, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

Social media management plays a very major role in the success of businesses, influencers, and organizations alike. With the rapidly growing number of platforms and the need for real-time engagement, having a centralized hub to monitor and manage social media activities is important. In this article, you will learn to build a Social Media Dashboard With Next.js.

Project Preview:

Screenshot-2024-02-15-223824
Social Media Dashboard With Next.js

Prerequisites:

Approach

  1. Setting up Next.js Environment: Install Node.js and set up a Next.js project using create-next-app.
  2. Implement Authentication: Integrate authentication using NextAuth.js and OAuth for social media platforms.(Optional for now).
  3. Connect Social Media APIs: Utilize APIs of social media platforms for fetching data, posting content, and managing accounts.(Further Enhancement work)
  4. Develop Dashboard Features: Building components for overview of the growth from different social media platform comparing all together in a single place.

Steps to Create Social Media Dashboard With Next.js

Step 1. Set up Next.js project using the command:

npx create-next-app social-media-dashboard 

The create-next-app command will sets up everything automatically for you, and on the terminal you will see this,

What is your project named? my-app
Would you like to use TypeScript? No / Yes
Would you like to use ESLint? No / Yes
Would you like to use Tailwind CSS? No / Yes
Would you like to use `src/` directory? No / Yes
Would you like to use App Router? (recommended) No / Yes
Would you like to customize the default import alias (@/*)? No / Yes
What import alias would you like configured? @/*

Just go for yes on every option otherwise keep it default and hit enter.

Step 2. Navigate to the project folder using:

cd social-media-dashboard  

Project Structure

Screenshot-2024-02-12-171804
Project Structure
You can put any logo you need in the public\assets directory

Dependencies

After following the installation steps the dependencies in package.json file will be somewhat like this:

 "dependencies": {
"@next/font": "13.1.6",
"eslint": "8.33.0",
"eslint-config-next": "13.1.6",
"next": "13.1.6",
"react": "18.2.0",
"react-dom": "18.2.0",
"standard": "^17.0.0"
}

Example: Here is the example code to create a dashboard using Next.js.

JavaScript
// src\components\Card.jsx

import Image from 'next/image'
import React, { useEffect, useState } from 'react'
import CountFollowers from './CountFollowers'

const Card = ({ icon, mediaName, userName,
    followers, type, isUp, recentNotify, arrow }) => {
    return (
        <div className='relative flex flex-col items-center
         dark:bg-card-light dark:text-text-light 
         dark:hover:bg-[#dadeeb] hover:bg-[#45455c]
          bg-card-dark text-text-dark h-auto rounded 
          overflow-hidden transition-colors ease-linear 
          duration-300  w-full cursor-pointer  '>
            <span className={`${mediaName === 'facebook' ?
                'bg-fb-color' : ''} ${mediaName === 'twitter' ?
                    'bg-tw-color' : ''} ${mediaName === 'instagram' ?
                        'bg-gradient-to-r from-ig-left-gradient to-ig-right-gradient'
                        : ''} ${mediaName === 'youtube' ? 'bg-yt-color' : ''}
                   h-[4px] w-full block absolute top-0`} />
            <div className='flex mt-8 mb-7'>
                <Image src={icon} height={20} width={20}
                    alt={`icon_${mediaName}`} />
                <p className='ml-2 font-semibold'>@{userName}</p>
            </div>
            <div className='text-center'>
                <CountFollowers followers={followers} card={true} />
                <p className='uppercase space tracking-[.25em] mt-2'>{type}</p>
            </div>
            {/* <span className='mb-6 mt-7' > */}
            <p className={`${isUp ? 'text-text-green-arrow'
                : 'text-text-red-arrow'} mb-6 mt-7`}>
                <Image src={arrow} width={12} height={15}
                    className='inline-block' alt='arrow_stat' />
                {recentNotify}Today</p>
            {/* </span> */}
        </div>
    )
}

export default Card
JavaScript
// src\components\CountFollowers.jsx

import React, { useEffect, useLayoutEffect, useState } from 'react'

const CountFollowers = ({ followers, card }) => {
    const [counter, setCounter] = useState(0)

    let animInterval;

    function startAnim() {
        animInterval = setInterval(animNumValue, 15, counter, followers);
    }

    function animNumValue(num, newValue) {
        let valor = num
        let diferencia = newValue - valor;

        if (counter >= followers) return
        if (diferencia > 1500) {
            valor += 519;
        } else if (diferencia > 400) {
            valor += 200;
        } else if (diferencia > 200) {
            valor += 100;
        } else if (diferencia > 100) {
            valor += 20;
        }
        else {
            valor++;
        }
        setCounter(valor)
    }

    useEffect(() => {
        if (!(counter >= followers)) startAnim()
        return (() => {
            clearInterval(animInterval)
        })
    }, [counter])
    return (
        <p className={`${card ? 'text-[4rem] tracking-[3px]'
            : 'text-4xl md:text-4xl '} font-bold dark:text-titles-light
        dark:group-hover:text-white text-titles-dark `}>
            {`${counter.toString().replace('000', "k")}`}</p>
    )
}

export default CountFollowers
JavaScript
// src\components\DetailsCard.jsx

import Image from "next/image";
import React from "react";
import CountFollowers from "./CountFollowers";

const DetailsCard = ({
    mediaName,
    icon,
    arrow,
    isUp,
    sectionName,
    state,
    percentage,
}) => {
    return (
        <div className="flex flex-col items-center justify-center
    dark:bg-card-light dark:text-text-light bg-card-dark text-text-dark
    dark:hover:bg-[#dadeeb] hover:bg-[#45455c] cursor-pointer h-auto 
    rounded overflow-hidden transition-colors ease-linear duration-300
        gap-10 w-full px-8 py-6">
            <div className="flex justify-between w-full items-center">
                <p className="font-semibold">{sectionName}</p>
                <Image src={icon} width={22} height={22}
                    alt={`${mediaName}_icon`} />
            </div>
            <div className="flex justify-between w-full items-center">
                <CountFollowers followers={state} card={false} />
                <div className={`${isUp ?
                    "text-text-green-arrow" : "text-text-red-arrow"}`}>
                    <Image src={arrow} width={12} height={15}
                        className="inline-block" alt="arrow-stat" />
                    {percentage}
                </div>
            </div>
        </div>
    );
};

export default DetailsCard;
JavaScript
// src\components\Header.jsx

import React from 'react'
import Layout from './Layout'
import ToogleTheme from './ToogleTheme'

const Header = () => {
    return (
        <header className='dark:bg-header-light dark:text-text-light
         bg-header-dark text-text-dark w-full h-64 md:h-[280px] 
         rounded-b-3xl pt-10'>
            <Layout>
                <div className='flex flex-col md:flex-row 
                justify-between md:items-center'>
                    <div>
                        <h1 className='dark:text-titles-light text-titles-dark 
                        text-2xl md:text-4xl mb-1 font-bold'>
                            Social Media Dashboard</h1>
                        <p className='font-bold'> Total Followers: 111,101</p>
                    </div>
                    <hr className='dark:bg-slate-600 bg-slate-200 
                    h-[2px] md:hidden my-5' />
                    <ToogleTheme />
                </div>
            </Layout>
        </header>
    )
}

export default Header
JavaScript
// src\components\Layout.jsx

import React from 'react'

const Layout = ({ children }) => {
    return (
        <div className='w-[85%] m-auto 
            max-w-[1440px]'>{children}</div>
    )
}

export default Layout
JavaScript
// src\components\ToogleTheme.jsx

import Head from 'next/head'
import React, { useEffect, useState } from 'react'

const ToogleTheme = () => {
    const [darkTheme, setDarkTheme] = useState(false)
    useEffect(() => {
        if (localStorage.getItem("theme") === 'true') {
            setDarkTheme(true)
            document.documentElement.classList.add('dark')
        } else {
            setDarkTheme(false)
            document.documentElement.classList.remove('dark')
        }
    }, [])

    const handleTheme = () => {
        if (darkTheme) {
            setDarkTheme(false)
            document.documentElement.classList.remove('dark')
            localStorage.setItem('theme', false)
        } else {
            setDarkTheme(true)
            localStorage.setItem('theme', true)
            document.documentElement.classList.add('dark')
        }
    }
    return (
        <>
            <Head>
                <meta name="theme-color" content={darkTheme ?
                    '#F8F8FA' : '#171723'} />
            </Head>
            <div className='flex justify-between 
            md:justify-start items-center md:gap-3'>
                <h2 className='font-bold'>Dark Mode</h2>
                <div className={`w-16 h-8 rounded-full 
                    ${darkTheme ? 'bg-toggle-light hover:bg-gradient-to-r 
                     from-toogle-gradient-left to-toogle-gradient-right ' : ' 
                     bg-gradient-to-r from-toogle-gradient-left to-toogle-gradient-right'} 
                      transition-all ease-in-out duration-500 flex flex-col justify-center`}>
                <span onClick={handleTheme} className={`w-6 h-6 cursor-pointer
                     rounded-full dark:bg-white bg-toggle-dark transition-all
                      ease-in-out duration-500 
                       ${darkTheme ? 'ml-9' : 'ml-1'}`}></span>
            </div>
        </div >
        </>
    )
}

export default ToogleTheme
JavaScript
// src\pages\_app.js

import '@/styles/globals.css'

export default function App({ Component, pageProps }) {
    return <Component {...pageProps} />
}
JavaScript
// src\pages\_document.js

import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
    return (
        <Html lang="en">
            <Head />
            <body className='dark:bg-light bg-dark 
          dark:text-light-dark text-titles-dark min-h-screen'>
                <Main />
                <NextScript />
            </body>
        </Html>
    )
}
JavaScript
// src\pages\index.js

import Head from "next/head";
import { Inter } from "@next/font/google";
import Header from "@/components/Header";
import Layout from "@/components/Layout";
import Card from "@/components/Card";
import DetailsCard from "@/components/DetailsCard";

const inter = Inter({
    subsets: ["latin"],
    weight: ["400", "700"],
    preload: true,
});

export default function Home({ cards, details }) {
    return (
        <>
            <Head>
                <title>Social Media Dashboard</title>
                <meta
                    name="description"
                    content="Social Media Dashboard Design with Next.js"
                />
                <meta name="viewport" content="width=device-width, initial-scale=1" />
                <link rel="icon" href="/favicon.ico" />
            </Head>
            <main className={`${inter.className} m-auto bg-pink-100`}>
                <Header />
                <Layout>
                    <div className="grid xl:grid-cols-2 place-content-between
           gap-8 sm:grid-cols-2 -mt-10 md:-mt-[120px]
            ml-auto mr-auto text-center">
                        {cards.map(renderCard)}
                    </div>

                    <h2 className="dark:text-text-light text-titles-dark
           font-bold text-2xl my-6 md:mt-10">
                        Overview - Today
                    </h2>
                    <div className="grid grid-cols-1 sm:grid-cols-2
           xl:grid-cols-4 md:flex-row md:justify-between 
           items-center flex-wrap gap-8 mb-12">
                        {details.map(renderDetailsCard)}
                    </div>
                </Layout>
            </main>
        </>
    );
}

function renderCard(card, index) {
    return (
        <Card
            key={card.mediaName + index}
            icon={card.icon}
            mediaName={card.mediaName}
            isUp={card.isUp}
            userName={card.username}
            type={card.type}
            followers={card.followers}
            recentNotify={card.recentNotify}
            arrow={card.arrow}
        />
    );
}

function renderDetailsCard(detail, index) {
    return (
        <DetailsCard
            key={detail.mediaName + index}
            mediaName={detail.mediaName}
            icon={detail.icon}
            arrow={detail.arrow}
            isUp={detail.isUp}
            sectionName={detail.sectionName}
            state={detail.state}
            percentage={detail.percentage}
        />
    );
}

export async function getStaticProps() {
    const cards = [
        {
            icon: "./../assets/facebook-svgrepo-com.svg",
            mediaName: "facebook",
            username: "Geek",
            type: "followers",
            followers: 1234,
            recentNotify: 12,
            arrow: "./../assets/icon-up.svg",
            isUp: true,
        },
        {
            icon: "./../assets/twitter-svgrepo-com.svg",
            mediaName: "twitter",
            username: "Geek",
            type: "followers",
            followers: 8294,
            recentNotify: 99,
            arrow: "./../assets/icon-up.svg",
            isUp: true,
        },
        {
            icon: "./../assets/instagram-svgrepo-com.svg",
            mediaName: "instagram",
            username: "realGeek",
            type: "followers",
            followers: 86000,
            recentNotify: 1099,
            arrow: "./../assets/icon-up.svg",
            isUp: true,
        },
        {
            icon: "./../assets/youtube-svgrepo-com.svg",
            mediaName: "youtube",
            username: "Geek.",
            type: "subscribers",
            followers: 5432,
            recentNotify: 144,
            arrow: "./../assets/icon-down.svg",
            isUp: false,
        },
        // Add more card objects here if needed
    ];

    const details = [
        {
            mediaName: "facebook-views",
            icon: "./../assets/facebook-svgrepo-com.svg",
            arrow: "./../assets/icon-up.svg",
            isUp: true,
            sectionName: "Page Views",
            state: 202,
            percentage: "3%",
        },
        {
            mediaName: "facebook-likes",
            icon: "./../assets/facebook-svgrepo-com.svg",
            arrow: "./../assets/icon-down.svg",
            isUp: false,
            sectionName: "Likes",
            state: 40,
            percentage: "2%",
        },
        {
            mediaName: "instagram-likes",
            icon: "./../assets/instagram-svgrepo-com.svg",
            arrow: "./../assets/icon-up.svg",
            isUp: true,
            sectionName: "Likes",
            state: 50101,
            percentage: "2257%",
        },
        {
            mediaName: "instagram-views",
            icon: "./../assets/instagram-svgrepo-com.svg",
            arrow: "./../assets/icon-up.svg",
            isUp: true,
            sectionName: "Profile Views",
            state: 202000,
            percentage: "1375%",
        },
        {
            mediaName: "retweets",
            icon: "./../assets/twitter-svgrepo-com.svg",
            arrow: "./../assets/icon-up.svg",
            isUp: true,
            sectionName: "Retweets",
            state: 513,
            percentage: "303%",
        },
        {
            mediaName: "twitter-likes",
            icon: "./../assets/twitter-svgrepo-com.svg",
            arrow: "./../assets/icon-up.svg",
            isUp: true,
            sectionName: "Likes",
            state: 1013,
            percentage: "553%",
        },
        {
            mediaName: "youtube-likes",
            icon: "./../assets/youtube-svgrepo-com.svg",
            arrow: "./../assets/icon-down.svg",
            isUp: false,
            sectionName: "Likes",
            state: 214,
            percentage: "19%",
        },
        {
            mediaName: "youtube-views",
            icon: "./../assets/youtube-svgrepo-com.svg",
            arrow: "./../assets/icon-down.svg",
            isUp: false,
            sectionName: "Total Views",
            state: 3658,
            percentage: "12%",
        },
        // Add more details objects here if needed
    ];

    return {
        props: {
            cards,
            details,
        },
    };
}
XML
<!-- public\assets\facebook-svgrepo-com.svg -->

<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" 
      width="100" height="100" viewBox="0 0 40 40">
<path fill="#8bb7f0" d="M2.5 2.5H37.5V37.5H2.5z">
     </path><path fill="#4e7ab5" d="M37,3v34H3V3H37 M38,2H2v36h36V2L38,2z">
     </path><path fill="#fff" d="M27,37V24h4.93l0.698-5H27v-3.384c0-1.568,0.702-2.636,2.95-2.636L33,12.979V8.225	c-0.496-0.066-2.381-0.213-4.361-0.213c-4.134,0-6.639,2.523-6.639,7.157V19h-5v5h5v13H27z"></path>
</svg>
XML
<!-- public\assets\icon-down.svg -->

<svg xmlns="http://www.w3.org/2000/svg" width="8" height="4">
  <path fill="#DC414C" fill-rule="evenodd" d="M0 0l4 4 4-4z"/></svg>
XML
<!-- public\assets\icon-up.svg -->

<svg xmlns="http://www.w3.org/2000/svg" width="8" height="4">
  <path fill="#1EB589" fill-rule="evenodd" d="M0 4l4-4 4 4z"/></svg>
XML
<!-- public\assets\instagram-svgrepo-com.svg -->

<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100"
     viewBox="0 0 48 48">
<radialGradient id="yOrnnhliCrdS2gy~4tD8ma_Xy10Jcu1L2Su_gr1" cx="19.38"
                cy="42.035" r="44.899" gradientUnits="userSpaceOnUse">
  <stop offset="0" stop-color="#fd5"></stop>
  <stop offset=".328" stop-color="#ff543f">
  </stop><stop offset=".348" stop-color="#fc5245"></stop>
  <stop offset=".504" stop-color="#e64771"></stop>
  <stop offset=".643" stop-color="#d53e91"></stop>
  <stop offset=".761" stop-color="#cc39a4"></stop>
  <stop offset=".841" stop-color="#c837ab"></stop>
  </radialGradient><path fill="url(#yOrnnhliCrdS2gy~4tD8ma_Xy10Jcu1L2Su_gr1)" 
                         d="M34.017,41.99l-20,0.019c-4.4,0.004-8.003-3.592-8.008-7.992l-0.019-20	
    c-0.004-4.4,3.592-8.003,7.992-8.008l20-0.019c4.4-0.004,8.003,3.592,8.008,7.992l0.019,20	C42.014,38.383,38.417,41.986,34.017,41.99z"></path>
  <radialGradient id="yOrnnhliCrdS2gy~4tD8mb_Xy10Jcu1L2Su_gr2" cx="11.786"
                  cy="5.54" r="29.813" gradientTransform="matrix(1 0 0 .6663 0 1.849)"
                  gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#4168c9">
    </stop><stop offset=".999" stop-color="#4168c9" stop-opacity="0"></stop>
  </radialGradient><path fill="url(#yOrnnhliCrdS2gy~4tD8mb_Xy10Jcu1L2Su_gr2)"
                         d="M34.017,41.99l-20,0.019c-4.4,0.004-8.003-3.592-8.008-7.992l-0.019-20
    c-0.004-4.4,3.592-8.003,7.992-8.008l20-0.019c4.4-0.004,8.003,3.592,8.008,7.992l0.019,20	
    C42.014,38.383,38.417,41.986,34.017,41.99z"></path><path fill="#fff" d="M24,31c-3.859,
    0-7-3.14-7-7s3.141-7,7-7s7,3.14,7,7S27.859,31,24,31z M24,19c-2.757,0-5,2.243-5,5	
    s2.243,5,5,5s5-2.243,5-5S26.757,19,24,19z"></path><circle cx="31.5" cy="16.5"
                                                              r="1.5" fill="#fff"></circle>
  <path fill="#fff" d="M30,37H18c-3.859,0-7-3.14-7-7V18c0-3.86,3.141-7,7-7h12c3.859,0,7,3.14,7,7v12	C37,33.86,33.859,37,30,37z M18,13c-2.757,0-5,2.243-5,5v12c0,2.757,2.243,5,5,5h12c2.757,0,5-2.243,5-5V18c0-2.757-2.243-5-5-5H18z"></path>
</svg>
XML
<!-- public\assets\twitter-svgrepo-com.svg -->

<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100"
     viewBox="0 0 48 48">
<path fill="#03A9F4" d="M42,12.429c-1.323,0.586-2.746,0.977-4.247,1.162c1.526-0.906,2.7-2.351,3.251-4.058c-1.428,0.837-3.01,1.452-4.693,1.776C34.967,9.884,33.05,9,30.926,9c-4.08,0-7.387,3.278-7.387,7.32c0,0.572,0.067,1.129,0.193,1.67c-6.138-0.308-11.582-3.226-15.224-7.654c-0.64,1.082-1,2.349-1,3.686c0,2.541,1.301,4.778,3.285,6.096c-1.211-0.037-2.351-0.374-3.349-0.914c0,0.022,0,0.055,0,0.086c0,3.551,2.547,6.508,5.923,7.181c-0.617,0.169-1.269,0.263-1.941,0.263c-0.477,0-0.942-0.054-1.392-0.135c0.94,2.902,3.667,5.023,6.898,5.086c-2.528,1.96-5.712,3.134-9.174,3.134c-0.598,0-1.183-0.034-1.761-0.104C9.268,36.786,13.152,38,17.321,38c13.585,0,21.017-11.156,21.017-20.834c0-0.317-0.01-0.633-0.025-0.945C39.763,15.197,41.013,13.905,42,12.429"></path>
</svg>
XML
<!-- public\assets\youtube-svgrepo-com.svg -->

<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100"
     viewBox="0 0 48 48">
<path fill="#FF3D00" d="M43.2,33.9c-0.4,2.1-2.1,3.7-4.2,4c-3.3,0.5-8.8,1.1-15,1.1c-6.1,0-11.6-0.6-15-1.1c-2.1-0.3-3.8-1.9-4.2-4C4.4,31.6,4,28.2,4,24c0-4.2,0.4-7.6,0.8-9.9c0.4-2.1,2.1-3.7,4.2-4C12.3,9.6,17.8,9,24,9c6.2,0,11.6,0.6,15,1.1c2.1,0.3,3.8,1.9,4.2,4c0.4,2.3,0.9,5.7,0.9,9.9C44,28.2,43.6,31.6,43.2,33.9z"></path><path fill="#FFF" d="M20 31L20 17 32 24z"></path>
</svg>

Steps to run the application:

Type the command:

npm run dev

Output:

test-(1)
Social Media Dashboard With Next.js

Similar Reads