Pada tutorial kali ini kita akan belajar menambahkan layout dashboard dan landing page secara general agar kedepan kita lebih mudah fokus ke fitur yang akan dibangun.
Step 1: Membangun Layout Dashboard
Agar dahsboard tidak terdapat container pada root layout app, hapus element main menjadi seperti berikut:
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import Provider from "@/provider";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "E-Commerce Tahu Coding",
description: "Modern E-Commerce with latest stack",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode,
}) {
return (
<html lang="en">
<body className={inter.className}>
<Provider>{children}</Provider>
</body>
</html>
);
}
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import Provider from "@/provider";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "E-Commerce Tahu Coding",
description: "Modern E-Commerce with latest stack",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode,
}) {
return (
<html lang="en">
<body className={inter.className}>
<Provider>{children}</Provider>
</body>
</html>
);
}
Pada folder dashboard tambahkan folder dan file berikut:
Selanjutnya, pada root layout dashboard tambahkan code berikut:
import type { Metadata } from "next";
import { getServerSession } from "next-auth";
import { OPTIONS } from "../api/auth/[...nextauth]/route";
import Main from "./components/main";
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default async function RootLayout({
children,
}: {
children: React.ReactNode,
}) {
const session = await getServerSession(OPTIONS);
return <Main session={session}>{children}</Main>;
}
import type { Metadata } from "next";
import { getServerSession } from "next-auth";
import { OPTIONS } from "../api/auth/[...nextauth]/route";
import Main from "./components/main";
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default async function RootLayout({
children,
}: {
children: React.ReactNode,
}) {
const session = await getServerSession(OPTIONS);
return <Main session={session}>{children}</Main>;
}
Selanjutnya pada component Main kita akan membentuk layout dashboard yang terdiri dari, header, sidebar, content dan footer, modifikasi component tersebut sebagai berikut:
"use client";
import React, { ReactNode } from "react";
import { Layout, theme } from "antd";
import { usePathname } from "next/navigation";
import { Session } from "next-auth";
import items from "./menu";
import Sidebar from "./sidebar";
const { Header, Content, Footer } = Layout;
type Props = {
children: ReactNode,
session: Session | null,
};
const Main: React.FC<Props> = ({ children, session }) => {
const {
token: { colorBgContainer },
} = theme.useToken();
const pathname = usePathname(); //use this to get url pathname to make sure active page selected when user hard refresh some page in dashboard
return (
<Layout style={{ minHeight: "100vh" }} hasSider>
<Sidebar defaultSelectedKeys={[pathname]} items={items} />
<Layout>
<Header style={{ padding: 0, background: colorBgContainer }} />
<Content style={{ margin: "0 16px" }}>
<div
style={{
padding: 24,
minHeight: 360,
background: colorBgContainer,
}}
>
{children}
</div>
</Content>
<Footer style={{ textAlign: "center" }}>
E-Commerce Cart ©2023 Created by Tahu Coding
</Footer>
</Layout>
</Layout>
);
};
export default Main;
"use client";
import React, { ReactNode } from "react";
import { Layout, theme } from "antd";
import { usePathname } from "next/navigation";
import { Session } from "next-auth";
import items from "./menu";
import Sidebar from "./sidebar";
const { Header, Content, Footer } = Layout;
type Props = {
children: ReactNode,
session: Session | null,
};
const Main: React.FC<Props> = ({ children, session }) => {
const {
token: { colorBgContainer },
} = theme.useToken();
const pathname = usePathname(); //use this to get url pathname to make sure active page selected when user hard refresh some page in dashboard
return (
<Layout style={{ minHeight: "100vh" }} hasSider>
<Sidebar defaultSelectedKeys={[pathname]} items={items} />
<Layout>
<Header style={{ padding: 0, background: colorBgContainer }} />
<Content style={{ margin: "0 16px" }}>
<div
style={{
padding: 24,
minHeight: 360,
background: colorBgContainer,
}}
>
{children}
</div>
</Content>
<Footer style={{ textAlign: "center" }}>
E-Commerce Cart ©2023 Created by Tahu Coding
</Footer>
</Layout>
</Layout>
);
};
export default Main;
Code diatas berfungsi sebagai wrapper content yang ada pada dashboard dan juga rangka dari layout dashboard, kamu bisa lihat author memanggil pathname dan menurunkannya sebagai props pada sidebar agar ketika user melakukan refresh menu yang aktif akan di highlight pada sidebar.
Selanjutnya pada component Sidebar modifikasi menggunakan code berikut:
"use client";
import React, { useState } from "react";
import { Layout, Menu } from "antd";
import { LogoutOutlined } from "@ant-design/icons";
import { signOut } from "next-auth/react";
import { MenuItem } from "./menu";
const { Sider } = Layout;
const Sidebar: React.FC<{
defaultSelectedKeys: string[],
items: MenuItem[],
}> = ({ defaultSelectedKeys = [], items = [] }) => {
const [collapsed, setCollapsed] = useState(false);
//handle logout using next auth
const handleLogout = () => {
signOut();
};
return (
<Sider
collapsible
collapsed={collapsed}
onCollapse={(value) => setCollapsed(value)}
>
<div
style={{
padding: "1rem",
fontWeight: 600,
textAlign: "center",
color: "white",
}}
>
<h1>LOGO</h1>
</div>
<Menu
theme="dark"
defaultSelectedKeys={defaultSelectedKeys}
defaultOpenKeys={defaultSelectedKeys}
mode="inline"
items={items}
/>
<Menu
theme="dark"
defaultSelectedKeys={defaultSelectedKeys}
defaultOpenKeys={defaultSelectedKeys}
mode="inline"
items={[
{
key: "logout",
label: <div onClick={handleLogout}>Logout</div>,
icon: <LogoutOutlined />,
},
]}
/>
</Sider>
);
};
export default Sidebar;
"use client";
import React, { useState } from "react";
import { Layout, Menu } from "antd";
import { LogoutOutlined } from "@ant-design/icons";
import { signOut } from "next-auth/react";
import { MenuItem } from "./menu";
const { Sider } = Layout;
const Sidebar: React.FC<{
defaultSelectedKeys: string[],
items: MenuItem[],
}> = ({ defaultSelectedKeys = [], items = [] }) => {
const [collapsed, setCollapsed] = useState(false);
//handle logout using next auth
const handleLogout = () => {
signOut();
};
return (
<Sider
collapsible
collapsed={collapsed}
onCollapse={(value) => setCollapsed(value)}
>
<div
style={{
padding: "1rem",
fontWeight: 600,
textAlign: "center",
color: "white",
}}
>
<h1>LOGO</h1>
</div>
<Menu
theme="dark"
defaultSelectedKeys={defaultSelectedKeys}
defaultOpenKeys={defaultSelectedKeys}
mode="inline"
items={items}
/>
<Menu
theme="dark"
defaultSelectedKeys={defaultSelectedKeys}
defaultOpenKeys={defaultSelectedKeys}
mode="inline"
items={[
{
key: "logout",
label: <div onClick={handleLogout}>Logout</div>,
icon: <LogoutOutlined />,
},
]}
/>
</Sider>
);
};
export default Sidebar;
Component ini bertanggung jawab sebagai UI yang akan merender menu berdasarkan data yang kita import, selain itu author juga menambahkan fungsi logout dari next auth pada menu paling bawah agar setiap page pada dashboard memiliki akses.
Selanjutnya tambahkan data menu pada file menu.tsx:
import {
TransactionOutlined,
AppstoreOutlined,
DashboardOutlined,
ProfileOutlined,
ContainerOutlined,
} from "@ant-design/icons";
import type { MenuProps } from "antd";
import Link from "next/link";
export type MenuItem = Required<MenuProps>["items"][number];
// transform parameter to valid menu object
const getItem = (
label: React.ReactNode,
key: string,
icon?: React.ReactNode,
children?: MenuItem[]
): MenuItem => {
const labelLink = <Link href={key}>{label}</Link>;
return {
key,
icon,
children,
label: labelLink,
};
};
const items: MenuItem[] = [
getItem("Dashboard", "/dashboard", <DashboardOutlined />),
getItem("Transactions", "/dashboard/transactions", <TransactionOutlined />),
getItem("Products", "/dashboard/products", <ContainerOutlined />),
getItem("Categories", "/dashboard/categories", <AppstoreOutlined />),
getItem("Profile", "/dashboard/profile", <ProfileOutlined />),
];
export default items;
import {
TransactionOutlined,
AppstoreOutlined,
DashboardOutlined,
ProfileOutlined,
ContainerOutlined,
} from "@ant-design/icons";
import type { MenuProps } from "antd";
import Link from "next/link";
export type MenuItem = Required<MenuProps>["items"][number];
// transform parameter to valid menu object
const getItem = (
label: React.ReactNode,
key: string,
icon?: React.ReactNode,
children?: MenuItem[]
): MenuItem => {
const labelLink = <Link href={key}>{label}</Link>;
return {
key,
icon,
children,
label: labelLink,
};
};
const items: MenuItem[] = [
getItem("Dashboard", "/dashboard", <DashboardOutlined />),
getItem("Transactions", "/dashboard/transactions", <TransactionOutlined />),
getItem("Products", "/dashboard/products", <ContainerOutlined />),
getItem("Categories", "/dashboard/categories", <AppstoreOutlined />),
getItem("Profile", "/dashboard/profile", <ProfileOutlined />),
];
export default items;
File ini berfungsi sebagai mapper dari menu yang akan kita tampilkan di sisi aplikasi, kamu dapat menggunakan icon apapun sesuai seleramu asal mengimportnya dari Ant Design Icon.
Selanjutnya agar tampilan page dashboard kita simple, hapus component logoutButton.tsx dan modifikasi seperti berikut:
import { getServerSession } from "next-auth";
import React from "react";
import { OPTIONS } from "../api/auth/[...nextauth]/route";
const Dashboard = async () => {
const session = await getServerSession(OPTIONS);
return <div>Welcome: {session?.user?.email}</div>;
};
export default Dashboard;
import { getServerSession } from "next-auth";
import React from "react";
import { OPTIONS } from "../api/auth/[...nextauth]/route";
const Dashboard = async () => {
const session = await getServerSession(OPTIONS);
return <div>Welcome: {session?.user?.email}</div>;
};
export default Dashboard;
Pada tiap page, yaitu categories, products, profile, transactions cukup tambahkan component placeholder dengan format seperti berikut:
import React from "react";
// please rename based on the name of the page
const PageName = () => {
return <div>PageName</div>;
};
export default PageName;
import React from "react";
// please rename based on the name of the page
const PageName = () => {
return <div>PageName</div>;
};
export default PageName;
Jika semua step diatas sudah berhasil, lakukan login maka tampilan dashboard akan menjadi sebagai berikut:
![Dashboard](/_next/image?url=%2Fstatic%2Fimages%2Ftutorial%2Fpart-3-tutorial-nextjs-13-typescript-ecommerce-cart-dengan-payment-gateway-antd-supabase-redux-toolkit-next-auth-postgre-sql-prisma%2Fdashboard.png&w=2048&q=100)
Kamu dapat mencoba beberapa fitur yang ada seperti navigasi antar page, logout hingga collapsed sidebar width dengan mengklik icon panah ke kiri pada bagian bawah sidebar.
Step 2: Adjust Redirect
Karena aplikasi yang kita bikin adalah e-commerce tentunya cukup aneh bukan jika setelah login user diarahkan ke halaman dashboard, maka dari itu kita akan melakukan penyesuaian agar user di redirect ke page landing setelah login
Pada file middleware adjust redirect ketika buka page login atau register ke landing menjadi seperti berikut:
import { getToken } from "next-auth/jwt";
import { NextRequest, NextResponse } from "next/server";
//this is custom middleware you may improve it as you like
export default async function middleware(req: NextRequest) {
// Get the pathname of the request (e.g. /, /protected)
const path = req.nextUrl.pathname;
// If it's the root path, just render it
if (path === "/") {
return NextResponse.next();
}
//decript jwt based on NEXTAUTH_SECRET
const session = await getToken({
req,
secret: process.env.NEXTAUTH_SECRET,
});
const isProtected = path.includes("/dashboard");
//if jwt token valid then continue, otherwise redirect to login
if (!session && isProtected) {
return NextResponse.redirect(new URL("/login", req.url));
} else if (session && (path === "/login" || path === "/register")) {
return NextResponse.redirect(new URL("/", req.url));
}
return NextResponse.next();
}
import { getToken } from "next-auth/jwt";
import { NextRequest, NextResponse } from "next/server";
//this is custom middleware you may improve it as you like
export default async function middleware(req: NextRequest) {
// Get the pathname of the request (e.g. /, /protected)
const path = req.nextUrl.pathname;
// If it's the root path, just render it
if (path === "/") {
return NextResponse.next();
}
//decript jwt based on NEXTAUTH_SECRET
const session = await getToken({
req,
secret: process.env.NEXTAUTH_SECRET,
});
const isProtected = path.includes("/dashboard");
//if jwt token valid then continue, otherwise redirect to login
if (!session && isProtected) {
return NextResponse.redirect(new URL("/login", req.url));
} else if (session && (path === "/login" || path === "/register")) {
return NextResponse.redirect(new URL("/", req.url));
}
return NextResponse.next();
}
Pada component LoginForm modifikasi variable callbackUrl menjadi seperti berikut:
//...rest of other codes
const LoginForm = () => {
const router = useRouter();
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const searchParams = useSearchParams();
const callbackUrl = searchParams.get("callbackUrl") || "/";
//...rest of other codes
};
export default LoginForm;
//...rest of other codes
const LoginForm = () => {
const router = useRouter();
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const searchParams = useSearchParams();
const callbackUrl = searchParams.get("callbackUrl") || "/";
//...rest of other codes
};
export default LoginForm;
Note: jika tampilan login dan register dan landing tidak ada container abaikan dulu.
Step 3: Membangun Layout Landing
Pada root app hapus page.tsx, kemudian tambahkan folder dan file berikut:
Selanjutnya hapus page.tsx pada root app agar layout landing dapat digrouping dan kita dapat menerapkan container sesuai kebutuhan.
Pada layout landing tambahkan code berikut:
import NonAuthLayout from "@/components/nonAuthLayout";
export default async function Layout({
children,
}: {
children: React.ReactNode,
}) {
return <NonAuthLayout withNavbar>{children}</NonAuthLayout>;
}
import NonAuthLayout from "@/components/nonAuthLayout";
export default async function Layout({
children,
}: {
children: React.ReactNode,
}) {
return <NonAuthLayout withNavbar>{children}</NonAuthLayout>;
}
Selanjutnya pada page.tsx di folder yg sama tambahkan code berikut:
import React from "react";
const Landing = () => {
return (
<div className="py-6">
<section className="bg-slate-600 h-48 rounded-md mb-6 flex items-center justify-center text-white">
Banner
</section>
<section>
<p className="text-center font-semibold text-2xl mb-6">
Latest Products
</p>
<div className="grid grid-cols-4 gap-4">
{[...Array(8)]
.map((_, index) => index + 1)
.map((item) => (
<div
key={item}
className="h-80 border border-gray-200 rounded-md flex items-center justify-center"
>
Product {item}
</div>
))}
</div>
</section>
</div>
);
};
export default Landing;
import React from "react";
const Landing = () => {
return (
<div className="py-6">
<section className="bg-slate-600 h-48 rounded-md mb-6 flex items-center justify-center text-white">
Banner
</section>
<section>
<p className="text-center font-semibold text-2xl mb-6">
Latest Products
</p>
<div className="grid grid-cols-4 gap-4">
{[...Array(8)]
.map((_, index) => index + 1)
.map((item) => (
<div
key={item}
className="h-80 border border-gray-200 rounded-md flex items-center justify-center"
>
Product {item}
</div>
))}
</div>
</section>
</div>
);
};
export default Landing;
Selanjutnya pada layout auth modifikasi menjadi seperti berikut:
import NonAuthLayout from "@/components/nonAuthLayout";
export default async function Layout({
children,
}: {
children: React.ReactNode,
}) {
return <NonAuthLayout>{children}</NonAuthLayout>;
}
import NonAuthLayout from "@/components/nonAuthLayout";
export default async function Layout({
children,
}: {
children: React.ReactNode,
}) {
return <NonAuthLayout>{children}</NonAuthLayout>;
}
Tujuan dari menambahkan layout baru pada auth dan landing adalah agar masing-masing dari group tersebut memiliki container.
Pada component NonAuthLayout tambahkan code berikut:
import React from "react";
import Navbar from "./navbar";
const NonAuthLayout = ({
children,
withNavbar = false,
}: {
children: React.ReactNode,
withNavbar?: boolean,
}) => {
return (
<>
{withNavbar && <Navbar />}
<div className="mx-auto max-w-7xl px-8 min-h-screen">{children}</div>
</>
);
};
export default NonAuthLayout;
import React from "react";
import Navbar from "./navbar";
const NonAuthLayout = ({
children,
withNavbar = false,
}: {
children: React.ReactNode,
withNavbar?: boolean,
}) => {
return (
<>
{withNavbar && <Navbar />}
<div className="mx-auto max-w-7xl px-8 min-h-screen">{children}</div>
</>
);
};
export default NonAuthLayout;
Selanjutnya pada Navbar tambahkan logic untuk menampilkan menu login/register atau dashbaord berdasarkan status login.
"use client";
import React from "react";
import { Button, Skeleton } from "antd";
import Link from "next/link";
import { useSession } from "next-auth/react";
const Navbar = () => {
const { status } = useSession();
return (
<nav className="h-16 shadow-md flex px-7 items-center justify-between">
<p className="font-semibold">E-Commerce Tahu Coding</p>
{status === "unauthenticated" ? (
<div className="flex gap-2 items-center">
<Link href="/login">
<Button type="primary">Login</Button>
</Link>
<Link href="/register">
<Button type="dashed">Register</Button>
</Link>
</div>
) : status === "authenticated" ? (
<Link href="/dashboard">
<Button type="primary">Dashboard</Button>
</Link>
) : (
<Skeleton.Button active={true} />
)}
</nav>
);
};
export default Navbar;
"use client";
import React from "react";
import { Button, Skeleton } from "antd";
import Link from "next/link";
import { useSession } from "next-auth/react";
const Navbar = () => {
const { status } = useSession();
return (
<nav className="h-16 shadow-md flex px-7 items-center justify-between">
<p className="font-semibold">E-Commerce Tahu Coding</p>
{status === "unauthenticated" ? (
<div className="flex gap-2 items-center">
<Link href="/login">
<Button type="primary">Login</Button>
</Link>
<Link href="/register">
<Button type="dashed">Register</Button>
</Link>
</div>
) : status === "authenticated" ? (
<Link href="/dashboard">
<Button type="primary">Dashboard</Button>
</Link>
) : (
<Skeleton.Button active={true} />
)}
</nav>
);
};
export default Navbar;
Jika sudah maka tampilan page landing kita akan menjadi seperti berikut.
![Dashboard](/_next/image?url=%2Fstatic%2Fimages%2Ftutorial%2Fpart-3-tutorial-nextjs-13-typescript-ecommerce-cart-dengan-payment-gateway-antd-supabase-redux-toolkit-next-auth-postgre-sql-prisma%2Flanding.png&w=2048&q=100)
Bonus: Menentukan Menu Based On role
Jika kamu perhatikan, menu yang tampil pada sidebar tidak ada perbedaan ketika login menggunakan role "admin" atau "user", untuk menghindari hal tersebut kita akan menampilkan menu berdasarkan role pada sisi server side.
Modifikasi file menu.tsx agar support 2 jenis menu:
import {
TransactionOutlined,
AppstoreOutlined,
DashboardOutlined,
ProfileOutlined,
ContainerOutlined,
} from "@ant-design/icons";
import type { MenuProps } from "antd";
import Link from "next/link";
export type MenuItem = Required<MenuProps>["items"][number];
const getItem = (
label: React.ReactNode,
key: string,
icon?: React.ReactNode,
children?: MenuItem[]
): MenuItem => {
const labelLink = <Link href={key}>{label}</Link>;
return {
key,
icon,
children,
label: labelLink,
};
};
export const items: MenuItem[] = [
//for admin
getItem("Dashboard", "/dashboard", <DashboardOutlined />),
getItem("Transactions", "/dashboard/transactions", <TransactionOutlined />),
getItem("Products", "/dashboard/products", <ContainerOutlined />),
getItem("Categories", "/dashboard/categories", <AppstoreOutlined />),
getItem("Profile", "/dashboard/profile", <ProfileOutlined />),
];
export const userItems: MenuItem[] = [
//for normal user
getItem("Dashboard", "/dashboard", <DashboardOutlined />),
getItem("Transactions", "/dashboard/transactions", <TransactionOutlined />),
getItem("Profile", "/dashboard/profile", <ProfileOutlined />),
];
import {
TransactionOutlined,
AppstoreOutlined,
DashboardOutlined,
ProfileOutlined,
ContainerOutlined,
} from "@ant-design/icons";
import type { MenuProps } from "antd";
import Link from "next/link";
export type MenuItem = Required<MenuProps>["items"][number];
const getItem = (
label: React.ReactNode,
key: string,
icon?: React.ReactNode,
children?: MenuItem[]
): MenuItem => {
const labelLink = <Link href={key}>{label}</Link>;
return {
key,
icon,
children,
label: labelLink,
};
};
export const items: MenuItem[] = [
//for admin
getItem("Dashboard", "/dashboard", <DashboardOutlined />),
getItem("Transactions", "/dashboard/transactions", <TransactionOutlined />),
getItem("Products", "/dashboard/products", <ContainerOutlined />),
getItem("Categories", "/dashboard/categories", <AppstoreOutlined />),
getItem("Profile", "/dashboard/profile", <ProfileOutlined />),
];
export const userItems: MenuItem[] = [
//for normal user
getItem("Dashboard", "/dashboard", <DashboardOutlined />),
getItem("Transactions", "/dashboard/transactions", <TransactionOutlined />),
getItem("Profile", "/dashboard/profile", <ProfileOutlined />),
];
Selanjutnya pada layout dashboard tambahkan logic untuk get user data dan get menu berdasarkan role.
import type { Metadata } from "next";
import { getServerSession } from "next-auth";
import { OPTIONS } from "../api/auth/[...nextauth]/route";
import Main from "./components/main";
import prisma from "@/lib/prisma";
import { userItems, items } from "./components/menu";
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default async function RootLayout({
children,
}: {
children: React.ReactNode,
}) {
const session = await getServerSession(OPTIONS);
// it safe to run this query on the server, don't call it on client side!
const user = await prisma.user.findUnique({
where: {
email: session?.user?.email ?? "",
},
select: {
id: true,
name: true,
email: true,
image: true,
role: true,
},
});
//need to run this on server to make sure no one can change the role on frontend to see admin menu
const menu = user?.role === "user" ? userItems : items;
return (
<Main user={user} menu={menu}>
{children}
</Main>
);
}
import type { Metadata } from "next";
import { getServerSession } from "next-auth";
import { OPTIONS } from "../api/auth/[...nextauth]/route";
import Main from "./components/main";
import prisma from "@/lib/prisma";
import { userItems, items } from "./components/menu";
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default async function RootLayout({
children,
}: {
children: React.ReactNode,
}) {
const session = await getServerSession(OPTIONS);
// it safe to run this query on the server, don't call it on client side!
const user = await prisma.user.findUnique({
where: {
email: session?.user?.email ?? "",
},
select: {
id: true,
name: true,
email: true,
image: true,
role: true,
},
});
//need to run this on server to make sure no one can change the role on frontend to see admin menu
const menu = user?.role === "user" ? userItems : items;
return (
<Main user={user} menu={menu}>
{children}
</Main>
);
}
Selanjutnya agar layout dashboard kita bisa menggunakan menu tadi, passing menu sebagai props pada component Main:
"use client";
import React, { ReactNode } from "react";
import { Layout, theme } from "antd";
import { usePathname } from "next/navigation";
import Sidebar from "./sidebar";
import { MenuItem } from "./menu";
const { Header, Content, Footer } = Layout;
type Props = {
children: ReactNode,
user: {
id: string,
name: string,
email: string,
image: string | null,
role: string,
} | null,
menu: MenuItem[],
};
const Main: React.FC<Props> = ({ children, user, menu }) => {
const {
token: { colorBgContainer },
} = theme.useToken();
const pathname = usePathname();
return (
<Layout style={{ minHeight: "100vh" }} hasSider>
<Sidebar defaultSelectedKeys={[pathname]} items={menu} />
<Layout>
<Header style={{ padding: 0, background: colorBgContainer }} />
<Content style={{ margin: "0 16px" }}>
<div
style={{
padding: 24,
minHeight: 360,
background: colorBgContainer,
}}
>
{children}
</div>
</Content>
<Footer style={{ textAlign: "center" }}>
E-Commerce Cart ©2023 Created by Tahu Coding
</Footer>
</Layout>
</Layout>
);
};
export default Main;
"use client";
import React, { ReactNode } from "react";
import { Layout, theme } from "antd";
import { usePathname } from "next/navigation";
import Sidebar from "./sidebar";
import { MenuItem } from "./menu";
const { Header, Content, Footer } = Layout;
type Props = {
children: ReactNode,
user: {
id: string,
name: string,
email: string,
image: string | null,
role: string,
} | null,
menu: MenuItem[],
};
const Main: React.FC<Props> = ({ children, user, menu }) => {
const {
token: { colorBgContainer },
} = theme.useToken();
const pathname = usePathname();
return (
<Layout style={{ minHeight: "100vh" }} hasSider>
<Sidebar defaultSelectedKeys={[pathname]} items={menu} />
<Layout>
<Header style={{ padding: 0, background: colorBgContainer }} />
<Content style={{ margin: "0 16px" }}>
<div
style={{
padding: 24,
minHeight: 360,
background: colorBgContainer,
}}
>
{children}
</div>
</Content>
<Footer style={{ textAlign: "center" }}>
E-Commerce Cart ©2023 Created by Tahu Coding
</Footer>
</Layout>
</Layout>
);
};
export default Main;
Sekarang coba refresh aplikasinya maka menu untuk role "user" akan tampil dengan benar.
![User Dashboard](/_next/image?url=%2Fstatic%2Fimages%2Ftutorial%2Fpart-3-tutorial-nextjs-13-typescript-ecommerce-cart-dengan-payment-gateway-antd-supabase-redux-toolkit-next-auth-postgre-sql-prisma%2Fmenu.png&w=2048&q=100)
Meski UI dari page landing masih sederhana, kedepan kita akan merubahnya menjadi tampilan yang lebih menarik, interaktif dan responsive.
Selamat kamu sudah menyelesaikan tutorial kali ini, kedepan kita akan belajar bagaimana cara men-generate data dummy pada database menggunakan faker.
Code untuk tutorial kali ini link repo, jangan lupa star di repo ya!
Stay tune dan jangan lupa share & support Tahu Coding, Terima Kasih!