Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admin #16

Merged
merged 12 commits into from
Sep 29, 2024
34 changes: 34 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"bullmq": "^5.13.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"framer-motion": "^11.9.0",
"ioredis": "^5.4.1",
"jest": "^29.7.0",
"lodash.debounce": "^4.0.8",
Expand All @@ -48,6 +49,7 @@
"react": "^18.3.1",
"react-dom": "^18",
"react-email": "^3.0.1",
"react-icons": "^5.3.0",
"react-hook-form": "^7.53.0",
"resend": "^4.0.0",
"sonner": "^1.5.0",
Expand Down
13 changes: 13 additions & 0 deletions src/app/actions/get-user-by-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import prisma from "@/server/db"; // Adjust the import based on your structure

export async function getUserById(userId: string) {
const user = await prisma.user.findUnique({
where: { id: userId },
select: {
id: true,
role: true, // Include any other fields you need
},
});

return user;
}
88 changes: 88 additions & 0 deletions src/app/admin/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}

@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}

@layer utilities {
.text-balance {
text-wrap: balance;
}
}

@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}

@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
27 changes: 27 additions & 0 deletions src/app/admin/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use client";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import Providers from "@/components/Layout/Provider";
import { AdminNavbar } from "@/components/Admin/Navbar/navbar";

const inter = Inter({ subsets: ["latin"] });

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>
<Providers>
<div className="flex">
<AdminNavbar />
{children}
</div>
</Providers>
</body>
</html>
);
}
189 changes: 189 additions & 0 deletions src/components/Admin/Navbar/navbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
"use client";
import React, { Dispatch, SetStateAction, useState } from "react";
import { IconType } from "react-icons";
import { FiChevronDown, FiChevronsRight, FiUser } from "react-icons/fi";
import { RiCoupon3Line } from "react-icons/ri";
import { motion } from "framer-motion";
import Link from "next/link";

export const AdminNavbar = () => {
return (
<div className="flex bg-indigo-50 h-screen">
<Sidebar />
<NavbarContent />
</div>
);
};

const Sidebar = () => {
const [open, setOpen] = useState(true);
const [selected, setSelected] = useState("Dashboard");

return (
<motion.nav
layout
className="sticky top-0 bottom-0 h-screen shrink-0 border-r border-slate-300 bg-white p-2"
style={{
width: open ? "225px" : "fit-content",
}}
>
<TitleSection open={open} />

<div className="space-y-1">
<Option
Icon={RiCoupon3Line}
title="Coupon"
selected={selected}
setSelected={setSelected}
open={open}
href="/admin"
/>
<Option
Icon={FiUser}
title="Users"
selected={selected}
setSelected={setSelected}
open={open}
href="/admin/users"
/>
</div>

<ToggleClose open={open} setOpen={setOpen} />
</motion.nav>
);
};

const Option = ({
Icon,
title,
selected,
setSelected,
open,
notifs,
href,
}: {
Icon: IconType;
title: string;
selected: string;
setSelected: Dispatch<SetStateAction<string>>;
open: boolean;
notifs?: number;
href?: string;
}) => {
return (
<Link href={href ?? ""}>
<motion.button
layout
onClick={() => setSelected(title)}
className={`relative flex h-10 w-full items-center rounded-md transition-colors ${
selected === title ? "bg-indigo-100 text-indigo-800" : "text-slate-500 hover:bg-slate-100"
}`}
>
<motion.div layout className="grid h-full w-10 place-content-center text-lg">
<Icon />
</motion.div>
{open && (
<motion.span
layout
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.125 }}
className="text-xs font-medium"
>
{title}
</motion.span>
)}

{notifs && open && (
<motion.span
initial={{ scale: 0, opacity: 0 }}
animate={{
opacity: 1,
scale: 1,
}}
style={{ y: "-50%" }}
transition={{ delay: 0.5 }}
className="absolute right-2 top-1/2 size-4 rounded bg-indigo-500 text-xs text-white"
>
{notifs}
</motion.span>
)}
</motion.button>
</Link>
);
};

const TitleSection = ({ open }: { open: boolean }) => {
return (
<div className="mb-3 border-b border-slate-300 pb-3">
<div className="flex cursor-pointer items-center justify-between rounded-md transition-colors hover:bg-slate-100">
<div className="flex items-center gap-2">
<Logo />
{open && (
<motion.div
layout
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.125 }}
>
<span className="block text-xs font-semibold">Tedxsjec</span>
<span className="block text-xs text-slate-500">Admin Page</span>
</motion.div>
)}
</div>
{open && <FiChevronDown className="mr-2" />}
</div>
</div>
);
};

const Logo = () => {
// Temp logo from https://logoipsum.com/
return (
<motion.div layout className="grid size-10 shrink-0 place-content-center rounded-md bg-indigo-600">
<svg
width="24"
height="auto"
viewBox="0 0 50 39"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="fill-slate-50"
>
<path d="M16.4992 2H37.5808L22.0816 24.9729H1L16.4992 2Z" stopColor="#000000"></path>
<path
d="M17.4224 27.102L11.4192 36H33.5008L49 13.0271H32.7024L23.2064 27.102H17.4224Z"
stopColor="#000000"
></path>
</svg>
</motion.div>
);
};

const ToggleClose = ({ open, setOpen }: { open: boolean; setOpen: Dispatch<SetStateAction<boolean>> }) => {
return (
<motion.button
layout
onClick={() => setOpen((pv) => !pv)}
className="absolute bottom-0 left-0 right-0 border-t border-slate-300 transition-colors hover:bg-slate-100"
>
<div className="flex items-center p-2">
<motion.div layout className="grid size-10 place-content-center text-lg">
<FiChevronsRight className={`transition-transform ${open && "rotate-180"}`} />
</motion.div>
{open && (
<motion.span
layout
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.125 }}
className="text-xs font-medium"
>
Hide
</motion.span>
)}
</div>
</motion.button>
);
};

const NavbarContent = () => <div className="h-[200vh] w-full"></div>;
Loading
Loading