├── jsconfig.json ├── next.config.js ├── postcss.config.js ├── src ├── app │ ├── favicon.ico │ ├── unauth-page │ │ └── page.js │ ├── products │ │ └── page.js │ ├── visitors │ │ └── page.js │ ├── globals.css │ ├── api │ │ ├── user │ │ │ └── route.js │ │ ├── product │ │ │ ├── all-products │ │ │ │ └── route.js │ │ │ └── add-product │ │ │ │ └── route.js │ │ ├── visitors │ │ │ ├── all-visitors │ │ │ │ └── route.js │ │ │ └── add-visitor │ │ │ │ └── route.js │ │ └── auth │ │ │ └── [...nextauth] │ │ │ └── route.js │ ├── page.js │ └── layout.js ├── auth-provider │ └── index.js ├── components │ ├── FormControls │ │ ├── button.js │ │ ├── input.js │ │ └── select.js │ ├── card │ │ └── index.js │ ├── visitors │ │ ├── visitors-list.js │ │ └── visitors-layout.js │ ├── products │ │ ├── product-listing.js │ │ └── product-layout.js │ ├── header │ │ └── index.js │ ├── Table │ │ └── index.js │ ├── DeviceAnalytics │ │ └── index.js │ ├── VisitorsAnalytics │ │ └── index.js │ ├── YearlyAnalyticsChart │ │ └── index.js │ ├── sidebar │ │ └── index.js │ ├── dashboard │ │ └── index.js │ └── Modal │ │ └── index.js ├── models │ ├── product │ │ └── index.js │ ├── visitors │ │ └── index.js │ └── user │ │ └── index.js ├── database │ └── index.js ├── context │ └── index.js └── utils │ └── config.js ├── .gitignore ├── public ├── vercel.svg └── next.svg ├── package.json ├── README.md └── tailwind.config.js /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./src/*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sangammukherjee/Next-js-google-auth-admin-dashboard/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/unauth-page/page.js: -------------------------------------------------------------------------------- 1 | export default function UnauthPage() { 2 | return
You are not authenticated. Please Login
; 3 | } 4 | -------------------------------------------------------------------------------- /src/auth-provider/index.js: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import {SessionProvider} from 'next-auth/react'; 4 | 5 | 6 | const NextAuthProvider = ({children})=>{ 7 | return {children} 8 | } 9 | 10 | export default NextAuthProvider; -------------------------------------------------------------------------------- /src/app/products/page.js: -------------------------------------------------------------------------------- 1 | import ProductLayout from "@/components/products/product-layout"; 2 | import ProductListing from "@/components/products/product-listing"; 3 | 4 | export default function Products() { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/app/visitors/page.js: -------------------------------------------------------------------------------- 1 | import VisitorsLayout from "@/components/visitors/visitors-layout"; 2 | import VisitorsList from "@/components/visitors/visitors-list"; 3 | 4 | export default function Visitors() { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/components/FormControls/button.js: -------------------------------------------------------------------------------- 1 | export default function Button({ text, onClick }) { 2 | return ( 3 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/models/product/index.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const ProductSchema = new mongoose.Schema({ 4 | name: String, 5 | price: Number, 6 | visitors: Number, 7 | sales: Number, 8 | month: String, 9 | }, {timestamps : true}); 10 | 11 | const Product = 12 | mongoose.models.Products || mongoose.model("Products", ProductSchema); 13 | 14 | export default Product; 15 | -------------------------------------------------------------------------------- /src/database/index.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const connectToDB = async () => { 4 | try { 5 | await mongoose.connect( 6 | "mongodb+srv://sangammukherjee2022:sangammukherjee2023@cluster0.fca1inn.mongodb.net/" 7 | ); 8 | console.log("Connected to mongodb"); 9 | } catch (error) { 10 | console.log(error); 11 | } 12 | }; 13 | 14 | export default connectToDB; 15 | -------------------------------------------------------------------------------- /src/models/visitors/index.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const VisitorsSchema = new mongoose.Schema( 4 | { 5 | visitors: Number, 6 | location: String, 7 | device: String, 8 | premiumUserNo: Number, 9 | month: String, 10 | }, 11 | { timestamps: true } 12 | ); 13 | 14 | const Visitor = 15 | mongoose.models.Visitors || mongoose.model("Visitors", VisitorsSchema); 16 | 17 | export default Visitor; 18 | -------------------------------------------------------------------------------- /src/models/user/index.js: -------------------------------------------------------------------------------- 1 | const { default: mongoose } = require("mongoose"); 2 | 3 | const userSchema = new mongoose.Schema( 4 | { 5 | email: { 6 | type: String, 7 | required: true, 8 | }, 9 | name: { 10 | type: String, 11 | required: true, 12 | }, 13 | }, 14 | { timestamps: true } 15 | ); 16 | 17 | const User = mongoose.models.User || mongoose.model("User", userSchema); 18 | 19 | export default User; 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /src/components/card/index.js: -------------------------------------------------------------------------------- 1 | export default function Card({ data, label, icon }) { 2 | return ( 3 |
4 |
5 | {icon} 6 |
7 |
8 |
9 |

{data}

10 | {label} 11 |
12 |
13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/FormControls/input.js: -------------------------------------------------------------------------------- 1 | export default function Input({ label, placeholder, onChange, value, type }) { 2 | return ( 3 |
4 |

5 | {label} 6 |

7 | 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "google-oauth-dashboard", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "autoprefixer": "10.4.14", 13 | "mongoose": "^7.4.2", 14 | "next": "13.4.13", 15 | "next-auth": "^4.23.0", 16 | "postcss": "8.4.27", 17 | "react": "18.2.0", 18 | "react-apexcharts": "^1.4.1", 19 | "react-dom": "18.2.0", 20 | "react-icons": "^4.10.1", 21 | "react-spinners": "^0.13.8", 22 | "tailwindcss": "3.3.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | -------------------------------------------------------------------------------- /src/app/api/user/route.js: -------------------------------------------------------------------------------- 1 | import connectToDB from "@/database"; 2 | import User from "@/models/user"; 3 | import { NextResponse } from "next/server"; 4 | 5 | export async function POST(req) { 6 | try { 7 | await connectToDB(); 8 | const { name, email } = await req.json(); 9 | 10 | const newUser = await User.create({ name, email }); 11 | 12 | if (newUser) { 13 | return NextResponse.json({ 14 | success: true, 15 | message: "User registered", 16 | }); 17 | } else { 18 | return NextResponse.json({ 19 | success: false, 20 | message: "failed to register the user ! Please try again", 21 | }); 22 | } 23 | } catch (e) { 24 | console.log(e); 25 | 26 | return NextResponse.json({ 27 | success: false, 28 | message: "Something went wrong ! Please try again", 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/FormControls/select.js: -------------------------------------------------------------------------------- 1 | export default function Select({ label, value, onChange, options = [] }) { 2 | return ( 3 |
4 |

5 | {label} 6 |

7 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/app/api/product/all-products/route.js: -------------------------------------------------------------------------------- 1 | import connectToDB from "@/database"; 2 | import Product from "@/models/product"; 3 | import { NextResponse } from "next/server"; 4 | 5 | export const dynamic = "force-dynamic"; 6 | 7 | export async function GET(req) { 8 | try { 9 | await connectToDB(); 10 | const getAllProducts = await Product.find({}); 11 | 12 | if (getAllProducts) { 13 | return NextResponse.json({ 14 | success: true, 15 | data: getAllProducts, 16 | }); 17 | } else { 18 | return NextResponse.json({ 19 | success: false, 20 | message: 21 | "failed to fetch the products ! Please try again after some time", 22 | }); 23 | } 24 | } catch (e) { 25 | console.log(e); 26 | 27 | return NextResponse.json({ 28 | success: false, 29 | message: "Something went wrong", 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/app/api/visitors/all-visitors/route.js: -------------------------------------------------------------------------------- 1 | import connectToDB from "@/database"; 2 | import Visitor from "@/models/visitors"; 3 | import { NextResponse } from "next/server"; 4 | 5 | export const dynamic = "force-dynamic"; 6 | 7 | export async function GET(req) { 8 | try { 9 | await connectToDB(); 10 | const getAllVisitorsInfo = await Visitor.find({}); 11 | 12 | if (getAllVisitorsInfo) { 13 | return NextResponse.json({ 14 | success: true, 15 | data: getAllVisitorsInfo, 16 | }); 17 | } else { 18 | return NextResponse.json({ 19 | success: false, 20 | message: 21 | "failed to fetch the visitors ! Please try again after some time", 22 | }); 23 | } 24 | } catch (e) { 25 | console.log(e); 26 | 27 | return NextResponse.json({ 28 | success: false, 29 | message: "Something went wrong", 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/app/api/product/add-product/route.js: -------------------------------------------------------------------------------- 1 | import connectToDB from "@/database"; 2 | import Product from "@/models/product"; 3 | import { NextResponse } from "next/server"; 4 | 5 | export const dynamic = "force-dynamic"; 6 | 7 | export async function POST(req) { 8 | try { 9 | await connectToDB(); 10 | 11 | const extractData = await req.json(); 12 | const newlyCreatedProduct = await Product.create(extractData); 13 | 14 | if (newlyCreatedProduct) { 15 | return NextResponse.json({ 16 | success: true, 17 | message: "Product added successfully", 18 | }); 19 | } else { 20 | return NextResponse.json({ 21 | success: false, 22 | message: "failed to add a product ! Please try after some time.", 23 | }); 24 | } 25 | } catch (e) { 26 | console.log(e); 27 | 28 | return NextResponse.json({ 29 | success: false, 30 | message: "Something went wrong", 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/page.js: -------------------------------------------------------------------------------- 1 | import DashboardLayout from "@/components/dashboard"; 2 | 3 | //get all products 4 | async function extractAllProducts() { 5 | const res = await fetch("http://localhost:3000/api/product/all-products", { 6 | method: "GET", 7 | cache: "no-store", 8 | }); 9 | 10 | const data = await res.json(); 11 | 12 | return data; 13 | } 14 | 15 | //get all visitors list 16 | 17 | async function extractAllVisitors() { 18 | const res = await fetch("http://localhost:3000/api/visitors/all-visitors", { 19 | method: "GET", 20 | cache: "no-store", 21 | }); 22 | 23 | const data = await res.json(); 24 | 25 | return data; 26 | } 27 | 28 | export default async function Home() { 29 | const allProducts = await extractAllProducts(); 30 | const allVisitors = await extractAllVisitors(); 31 | 32 | return ( 33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/app/api/visitors/add-visitor/route.js: -------------------------------------------------------------------------------- 1 | import connectToDB from "@/database"; 2 | import visitor from "@/models/visitors"; 3 | import { NextResponse } from "next/server"; 4 | 5 | export const dynamic = "force-dynamic"; 6 | 7 | export async function POST(req) { 8 | try { 9 | await connectToDB(); 10 | 11 | const extractData = await req.json(); 12 | const newlyCreatedVisitorsInfo = await visitor.create(extractData); 13 | 14 | if (newlyCreatedVisitorsInfo) { 15 | return NextResponse.json({ 16 | success: true, 17 | message: "Visitors data added successfully", 18 | }); 19 | } else { 20 | return NextResponse.json({ 21 | success: false, 22 | message: "failed to add a visitor ! Please try after some time.", 23 | }); 24 | } 25 | } catch (e) { 26 | console.log(e); 27 | 28 | return NextResponse.json({ 29 | success: false, 30 | message: "Something went wrong", 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/visitors/visitors-list.js: -------------------------------------------------------------------------------- 1 | import { deviceMapper, monthsMapper, visitorsTableHeaders } from "@/utils/config"; 2 | import Table from "../Table"; 3 | 4 | async function extractAllVisitors() { 5 | const res = await fetch("http://localhost:3000/api/visitors/all-visitors", { 6 | method: "GET", 7 | cache: "no-store", 8 | }); 9 | 10 | 11 | const data = await res.json(); 12 | 13 | return data; 14 | } 15 | 16 | export default async function VisitorsList() { 17 | const allVisitors = await extractAllVisitors(); 18 | return ( 19 | ({ 25 | ...item, 26 | month : monthsMapper[item.month], 27 | device : deviceMapper[item.device] 28 | })) 29 | : [] 30 | } 31 | /> 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/components/products/product-listing.js: -------------------------------------------------------------------------------- 1 | import { monthsMapper, productTableHeaders } from "@/utils/config"; 2 | import Table from "../Table"; 3 | 4 | async function extractAllProducts() { 5 | const res = await fetch("http://localhost:3000/api/product/all-products", { 6 | method: "GET", 7 | cache: "no-store", 8 | }); 9 | 10 | const data = await res.json(); 11 | 12 | return data; 13 | } 14 | 15 | export default async function ProductListing() { 16 | const allProducts = await extractAllProducts(); 17 | 18 | console.log(allProducts); 19 | 20 | return ( 21 |
({ 27 | ...item, 28 | revenue: parseInt(item.price * item.sales), 29 | month: monthsMapper[item.month], 30 | })) 31 | : [] 32 | } 33 | /> 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/app/layout.js: -------------------------------------------------------------------------------- 1 | import GlobalState from "@/context"; 2 | import "./globals.css"; 3 | import { Inter } from "next/font/google"; 4 | import Sidebar from "@/components/sidebar"; 5 | import Header from "@/components/header"; 6 | import NextAuthProvider from "@/auth-provider"; 7 | 8 | const inter = Inter({ subsets: ["latin"] }); 9 | 10 | export const metadata = { 11 | title: "Create Next App", 12 | description: "Generated by create next app", 13 | }; 14 | 15 | export default function RootLayout({ children }) { 16 | return ( 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 |
25 |
26 |
27 |
28 | {children} 29 |
30 |
31 |
32 |
33 |
34 |
35 | 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/api/auth/[...nextauth]/route.js: -------------------------------------------------------------------------------- 1 | import connectToDB from "@/database"; 2 | import User from "@/models/user"; 3 | import NextAuth from "next-auth/next"; 4 | import GoogleProvider from "next-auth/providers/google"; 5 | 6 | const authOptions = { 7 | providers: [ 8 | GoogleProvider({ 9 | clientId: 10 | "977343913580-83i2einc6e40pec56gknd2ejlf6pkar2.apps.googleusercontent.com", 11 | clientSecret: "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", 12 | }), 13 | ], 14 | callbacks: { 15 | async signIn({ user, account }) { 16 | if (account.provider === "google") { 17 | const { name, email } = user; 18 | try { 19 | await connectToDB(); 20 | const isUserExists = await User.findOne({ email }); 21 | 22 | if (!isUserExists) { 23 | const res = await fetch("http://localhost:3000/api/user", { 24 | method: "POST", 25 | headers: { 26 | "Content-Type": "application/json", 27 | }, 28 | body: JSON.stringify({ name, email }), 29 | }); 30 | 31 | if (res.success) { 32 | return user; 33 | } 34 | } 35 | } catch (error) { 36 | console.log(error); 37 | } 38 | } 39 | 40 | return user; 41 | }, 42 | }, 43 | }; 44 | 45 | const handler = NextAuth(authOptions); 46 | 47 | export { handler as GET, handler as POST }; 48 | -------------------------------------------------------------------------------- /src/context/index.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useSession } from "next-auth/react"; 4 | import { usePathname, useRouter } from "next/navigation"; 5 | import { createContext, useEffect, useState } from "react"; 6 | import { PulseLoader } from "react-spinners"; 7 | 8 | export const GlobalContext = createContext(null); 9 | 10 | export default function GlobalState({ children }) { 11 | const [sideBarOpen, setSideBarOpen] = useState(false); 12 | const [loader, setLoader] = useState(true); 13 | const { status } = useSession(); 14 | const pathName = usePathname(); 15 | const router = useRouter(); 16 | 17 | useEffect(() => { 18 | if (status === "loading") setLoader(true); 19 | if ( 20 | status === "unauthenticated" && 21 | pathName.includes("/" || "/products" || "/visitors") 22 | ) { 23 | router.push("/unauth-page"); 24 | setLoader(false); 25 | } 26 | 27 | if (status === "authenticated") setLoader(false); 28 | }, [status]); 29 | 30 | if (loader) { 31 | return ( 32 |
33 | 39 |
40 | ); 41 | } 42 | 43 | return ( 44 | 45 | {children} 46 | 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file. 18 | 19 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /src/components/header/index.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { GlobalContext } from "@/context"; 4 | import { signIn, signOut, useSession } from "next-auth/react"; 5 | import { useRouter } from "next/navigation"; 6 | import { useContext, useEffect } from "react"; 7 | 8 | export default function Header() { 9 | const { sideBarOpen, setSideBarOpen } = useContext(GlobalContext); 10 | 11 | const { status } = useSession(); 12 | const router = useRouter(); 13 | 14 | console.log(status); 15 | 16 | useEffect(() => { 17 | if (status === "authenticated") router.push("/"); 18 | }, [status]); 19 | 20 | return ( 21 |
22 |
23 |
24 | 27 |
28 | 36 |
37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/components/Table/index.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | export default function Table({ 4 | data = [], 5 | tableHeaderText, 6 | tableHeaderCells = [], 7 | }) { 8 | console.log(data); 9 | 10 | return ( 11 |
12 |

13 | {tableHeaderText} 14 |

15 |
16 |
17 | {tableHeaderCells && tableHeaderCells.length 18 | ? tableHeaderCells.map((item) => ( 19 |
20 | {item.label} 21 |
22 | )) 23 | : null} 24 |
25 | {data && data.length 26 | ? data.map((item) => ( 27 |
31 | {tableHeaderCells.map((tableCell) => ( 32 |
36 |

{item[tableCell.id]}

37 |
38 | ))} 39 |
40 | )) 41 | : null} 42 |
43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/components/products/product-layout.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect, useState } from "react"; 4 | import Button from "../FormControls/button"; 5 | import Modal from "../Modal"; 6 | import { productFormControls } from "@/utils/config"; 7 | import { useRouter } from "next/navigation"; 8 | 9 | const intialFormData = { 10 | name: "", 11 | price: "", 12 | visitors: 0, 13 | sales: 0, 14 | month: "", 15 | }; 16 | 17 | export default function ProductLayout({ children }) { 18 | const [showModal, setShowModal] = useState(false); 19 | const [formData, setFormData] = useState(intialFormData); 20 | 21 | const router = useRouter(); 22 | 23 | console.log(formData); 24 | 25 | async function handleAddProduct() { 26 | const res = await fetch("/api/product/add-product", { 27 | method: "POST", 28 | headers: { 29 | "Content-Type": "application/json", 30 | }, 31 | body: JSON.stringify(formData), 32 | }); 33 | 34 | const data = await res.json(); 35 | 36 | if (data && data.success) { 37 | setFormData(intialFormData); 38 | setShowModal(false); 39 | router.refresh(); 40 | } else { 41 | setFormData(intialFormData); 42 | setShowModal(false); 43 | } 44 | } 45 | 46 | return ( 47 |
48 |
59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /src/components/DeviceAnalytics/index.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { deviceAnalyticsChartOptions } from "@/utils/config"; 4 | import ReactApexChart from "react-apexcharts"; 5 | 6 | function getDeviceByVisitors(data, getDevice) { 7 | if ( 8 | data && 9 | data.length && 10 | data.filter((item) => item.device === getDevice).length === 0 11 | ) 12 | return 0; 13 | 14 | return data && data.length 15 | ? data 16 | .filter((item) => item.device === getDevice) 17 | .reduce((acc, obj) => acc + obj.visitors, 0) 18 | : 0; 19 | } 20 | 21 | export default function DeviceAnalytics({ allVisitors }) { 22 | const series = [ 23 | getDeviceByVisitors(allVisitors, "desktop"), 24 | getDeviceByVisitors(allVisitors, "laptop"), 25 | getDeviceByVisitors(allVisitors, "tablet"), 26 | getDeviceByVisitors(allVisitors, "mobile"), 27 | ]; 28 | 29 | return ( 30 |
31 |
32 |

Visitor Analytics By Devices

33 |
34 |
38 | 44 |
45 |
46 |
47 |
48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /src/components/visitors/visitors-layout.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState } from "react"; 4 | import Button from "../FormControls/button"; 5 | import Modal from "../Modal"; 6 | import { visiorsFormControls } from "@/utils/config"; 7 | import { useRouter } from "next/navigation"; 8 | 9 | const initialFormData = { 10 | visitors: 0, 11 | location: "", 12 | device: "", 13 | premiumUserNo: 0, 14 | month: "", 15 | }; 16 | 17 | export default function VisitorsLayout({ children }) { 18 | const [showModal, setShowModal] = useState(false); 19 | const [formData, setFormData] = useState(initialFormData); 20 | const router = useRouter(); 21 | 22 | async function handleAddVisitors() { 23 | const res = await fetch("/api/visitors/add-visitor", { 24 | method: "POST", 25 | headers: { 26 | "Content-Type": "application/json", 27 | }, 28 | body: JSON.stringify(formData), 29 | }); 30 | 31 | const data = await res.json(); 32 | 33 | console.log(data); 34 | 35 | if (data && data.success) { 36 | setFormData(initialFormData); 37 | setShowModal(false); 38 | router.refresh(); 39 | } else { 40 | setFormData(initialFormData); 41 | setShowModal(false); 42 | } 43 | } 44 | 45 | return ( 46 |
47 |
58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /src/components/VisitorsAnalytics/index.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { visitorAnalyticsChartOptions } from "@/utils/config"; 4 | import ReactApexChart from "react-apexcharts"; 5 | 6 | function getAllVisitorsByCountry(data, country) { 7 | if (data && data.length === 0) return 0; 8 | 9 | return data 10 | .filter((item) => item.location === country) 11 | .reduce((acc, visitorItem) => acc + visitorItem.visitors, 0); 12 | } 13 | function getAllPremiumVisitorsByCountry(data, country) { 14 | if (data && data.length === 0) return 0; 15 | 16 | return data 17 | .filter((item) => item.location === country) 18 | .reduce((acc, visitorItem) => acc + visitorItem.premiumUserNo, 0); 19 | } 20 | 21 | export default function VisitorsAnalytics({ allVisitors }) { 22 | const uniqueLocation = [...new Set(allVisitors.map((item) => item.location))]; 23 | console.log(uniqueLocation, "allVisitors"); 24 | 25 | const maxUniqueLocationToShow = uniqueLocation.slice( 26 | 0, 27 | uniqueLocation && uniqueLocation.length > 4 ? 4 : uniqueLocation.length 28 | ); 29 | 30 | let updatedOptions = { 31 | ...visitorAnalyticsChartOptions, 32 | xaxis: { 33 | categories: maxUniqueLocationToShow, 34 | }, 35 | }; 36 | 37 | const series = [ 38 | { 39 | name: "Visitors", 40 | data: maxUniqueLocationToShow.map((locationItem) => 41 | getAllVisitorsByCountry(allVisitors, locationItem) 42 | ), 43 | }, 44 | { 45 | name: "Premium Visitors", 46 | data: maxUniqueLocationToShow.map((locationItem) => 47 | getAllPremiumVisitorsByCountry(allVisitors, locationItem) 48 | ), 49 | }, 50 | ]; 51 | 52 | return ( 53 |
54 |
55 |

Visitors By Country

56 |
57 |
58 | 64 |
65 |
66 |
67 |
68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /src/components/YearlyAnalyticsChart/index.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { yearlyAnalyticsChartOptions } from "@/utils/config"; 4 | import ReactApexChart from "react-apexcharts"; 5 | 6 | const monthsArray = [ 7 | "jan", 8 | "feb", 9 | "mar", 10 | "apr", 11 | "may", 12 | "jun", 13 | "jul", 14 | "aug", 15 | "sep", 16 | "oct", 17 | "nov", 18 | "dec", 19 | ]; 20 | 21 | function getSales(products, getMonth) { 22 | if (products.filter((item) => item.month === getMonth).length === 0) return 0; 23 | 24 | return products 25 | .filter((item) => item.month === getMonth) 26 | .reduce((acc, productItem) => acc + productItem.sales, 0); 27 | } 28 | 29 | function getRevenue(products, getMonth) { 30 | if (products.filter((item) => item.month === getMonth).length === 0) return 0; 31 | 32 | return products 33 | .filter((item) => item.month === getMonth) 34 | .reduce((acc, productItem) => acc + productItem.revenue, 0); 35 | } 36 | 37 | function getCost(products, getMonth) { 38 | if (products.filter((item) => item.month === getMonth).length === 0) return 0; 39 | 40 | return products 41 | .filter((item) => item.month === getMonth) 42 | .reduce((acc, productItem) => acc + productItem.cost, 0); 43 | } 44 | 45 | export default function YearlyAnalyticsChart({ allProducts }) { 46 | console.log(allProducts); 47 | const series = [ 48 | { 49 | name: "Sales", 50 | data: monthsArray.map((item) => getSales(allProducts, item)), 51 | }, 52 | { 53 | name: "Cost", 54 | data: monthsArray.map((item) => getCost(allProducts, item)), 55 | }, 56 | { 57 | name: "Revenue", 58 | data: monthsArray.map((item) => getRevenue(allProducts, item)), 59 | }, 60 | ]; 61 | 62 | return ( 63 |
64 |
65 |

Yearly Analytics Overview

66 |
67 |
68 | 74 |
75 |
76 |
77 |
78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /src/components/sidebar/index.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { LuLayoutDashboard } from "react-icons/lu"; 4 | import { TbBrandProducthunt } from "react-icons/tb"; 5 | import { PiUsersFourLight } from "react-icons/pi"; 6 | import { useContext } from "react"; 7 | import { GlobalContext } from "@/context"; 8 | import Link from "next/link"; 9 | import { usePathname, useRouter } from "next/navigation"; 10 | import { useSession } from "next-auth/react"; 11 | 12 | const menuItems = [ 13 | { 14 | id: "dashboard", 15 | label: "Dashboard", 16 | path: "/", 17 | icon: , 18 | }, 19 | { 20 | id: "products", 21 | label: "Products", 22 | path: "/products", 23 | icon: , 24 | }, 25 | { 26 | id: "visitors", 27 | label: "Visitors", 28 | path: "/visitors", 29 | icon: , 30 | }, 31 | ]; 32 | 33 | export default function Sidebar() { 34 | const { sideBarOpen, setSideBarOpen } = useContext(GlobalContext); 35 | const {status} = useSession() 36 | 37 | const pathName = usePathname(); 38 | const router = useRouter(); 39 | 40 | const handlenavigate = (getMenuItem) => { 41 | if(status === 'unauthenticated') { 42 | router.push('/unauth-page') 43 | return 44 | } 45 | router.push(getMenuItem.path); 46 | }; 47 | 48 | return ( 49 | 81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /src/components/dashboard/index.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Card from "../card"; 4 | import { FaUsers } from "react-icons/fa"; 5 | import { BiMoneyWithdraw } from "react-icons/bi"; 6 | import { MdOutlineProductionQuantityLimits } from "react-icons/md"; 7 | import { BsFillPersonCheckFill } from "react-icons/bs"; 8 | import YearlyAnalyticsChart from "../YearlyAnalyticsChart"; 9 | import VisitorsAnalytics from "../VisitorsAnalytics"; 10 | import DeviceAnalytics from "../DeviceAnalytics"; 11 | 12 | export default function DashboardLayout({ allVisitors, allProducts }) { 13 | console.log(allProducts, allVisitors); 14 | return ( 15 |
16 |
17 | } 19 | data={ 20 | allVisitors && allVisitors.length 21 | ? allVisitors.reduce( 22 | (acc, visitorItem) => 23 | parseInt(acc + visitorItem.premiumUserNo), 24 | 0 25 | ) 26 | : 0 27 | } 28 | label={"Total Premium Visitors"} 29 | /> 30 | } 33 | label={"Total Products"} 34 | /> 35 | parseInt(acc + productItem.sales), 40 | 0 41 | ) 42 | : 0 43 | } 44 | label={"Total Sales"} 45 | icon={} 46 | /> 47 | parseInt(acc + visitorItem.visitors), 52 | 0 53 | ) 54 | : 0 55 | } 56 | label={"Total Visitors"} 57 | icon={} 58 | /> 59 |
60 |
61 | ({ 65 | ...productItem, 66 | revenue: 67 | productItem.price * productItem.sales - 68 | productItem.sales * 10, 69 | cost: productItem.sales * 10, 70 | })) 71 | : [] 72 | } 73 | /> 74 | 77 |
78 |
79 | 82 |
83 |
84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /src/components/Modal/index.js: -------------------------------------------------------------------------------- 1 | import Button from "../FormControls/button"; 2 | import Input from "../FormControls/input"; 3 | import Select from "../FormControls/select"; 4 | 5 | export default function Modal({ 6 | show, 7 | title, 8 | formControls = [], 9 | onAdd, 10 | formData, 11 | setFormData, 12 | setShow, 13 | }) { 14 | return ( 15 | <> 16 | {show ? ( 17 | <> 18 |
19 |
20 |
21 |
22 |

{title}

23 |
24 |
25 | {formControls && formControls.length 26 | ? formControls.map((item) => 27 | item.componentType === "input" ? ( 28 | 34 | setFormData({ 35 | ...formData, 36 | [item.id]: 37 | item.type === "number" 38 | ? parseInt(e.target.value) 39 | : e.target.value, 40 | }) 41 | } 42 | /> 43 | ) : item.componentType === "select" ? ( 44 |