Initial commit for new panel

This commit is contained in:
AmirReza Jamali
2025-09-09 11:20:26 +03:30
commit 1d4ccb3575
343 changed files with 20031 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
import type { IconProps } from "@/types/icon-props";
export function MenuIcon(props: IconProps) {
return (
<svg
width="25"
height="24"
viewBox="0 0 25 24"
fill="currentColor"
{...props}
>
<path d="M3.5625 6C3.5625 5.58579 3.89829 5.25 4.3125 5.25H20.3125C20.7267 5.25 21.0625 5.58579 21.0625 6C21.0625 6.41421 20.7267 6.75 20.3125 6.75L4.3125 6.75C3.89829 6.75 3.5625 6.41422 3.5625 6Z" />
<path d="M3.5625 18C3.5625 17.5858 3.89829 17.25 4.3125 17.25L20.3125 17.25C20.7267 17.25 21.0625 17.5858 21.0625 18C21.0625 18.4142 20.7267 18.75 20.3125 18.75L4.3125 18.75C3.89829 18.75 3.5625 18.4142 3.5625 18Z" />
<path d="M4.3125 11.25C3.89829 11.25 3.5625 11.5858 3.5625 12C3.5625 12.4142 3.89829 12.75 4.3125 12.75L20.3125 12.75C20.7267 12.75 21.0625 12.4142 21.0625 12C21.0625 11.5858 20.7267 11.25 20.3125 11.25L4.3125 11.25Z" />
</svg>
);
}
+54
View File
@@ -0,0 +1,54 @@
"use client";
import Image from "next/image";
import Link from "next/link";
import { useSidebarContext } from "../sidebar/sidebar-context";
import { MenuIcon } from "./icons";
import { Notification } from "./notification";
import { ThemeToggleSwitch } from "./theme-toggle";
import { UserInfo } from "./user-info";
export function Header() {
const { toggleSidebar, isMobile } = useSidebarContext();
return (
<header className="sticky top-0 z-30 flex items-center justify-between border-b border-stroke bg-white px-4 py-5 shadow-1 dark:border-stroke-dark dark:bg-gray-dark md:px-5 2xl:px-10">
<button
onClick={toggleSidebar}
className="rounded-lg border px-1.5 py-1 dark:border-stroke-dark dark:bg-[#020D1A] hover:dark:bg-[#FFFFFF1A] lg:hidden"
>
<MenuIcon />
<span className="sr-only">Toggle Sidebar</span>
</button>
{isMobile && (
<Link href={"/"} className="ml-2 max-[430px]:hidden min-[375px]:ml-4">
<Image
src={"/images/logo/logo-icon.svg"}
width={32}
height={32}
alt=""
role="presentation"
/>
</Link>
)}
<div className="max-xl:hidden">
<h1 className="mb-0.5 text-heading-5 font-bold text-dark dark:text-white">
Opportunity Lens
</h1>
<p className="font-medium">Opportunity Lens management panel</p>
</div>
<div className="flex flex-1 items-center justify-end gap-2 min-[375px]:gap-4">
<ThemeToggleSwitch />
<Notification />
<div className="shrink-0">
<UserInfo />
</div>
</div>
</header>
);
}
@@ -0,0 +1,14 @@
import { SVGProps } from "react";
export function BellIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg width={20} height={20} viewBox="0 0 20 20" fill="none" {...props}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 1.042A6.458 6.458 0 003.542 7.5v.587c0 .58-.172 1.148-.495 1.631l-.957 1.436a2.934 2.934 0 001.67 4.459c.63.171 1.264.316 1.903.435l.002.005c.64 1.71 2.353 2.905 4.335 2.905 1.982 0 3.694-1.196 4.335-2.905l.002-.005a23.736 23.736 0 001.903-.435 2.934 2.934 0 001.67-4.459l-.958-1.436a2.941 2.941 0 01-.494-1.631V7.5A6.458 6.458 0 0010 1.042zm2.813 15.239a23.71 23.71 0 01-5.627 0c.593.85 1.623 1.427 2.814 1.427 1.19 0 2.221-.576 2.813-1.427zM4.792 7.5a5.208 5.208 0 1110.416 0v.587c0 .827.245 1.636.704 2.325l.957 1.435c.638.957.151 2.257-.958 2.56a22.467 22.467 0 01-11.822 0 1.684 1.684 0 01-.959-2.56l.958-1.435a4.192 4.192 0 00.704-2.325V7.5z"
fill="currentColor"
/>
</svg>
);
}
@@ -0,0 +1,129 @@
"use client";
import {
Dropdown,
DropdownContent,
DropdownTrigger,
} from "@/components/ui/dropdown";
import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/lib/utils";
import Image from "next/image";
import Link from "next/link";
import { useState } from "react";
import { BellIcon } from "./icons";
const notificationList = [
{
image: "/images/user/user-15.png",
title: "Piter Joined the Team!",
subTitle: "Congratulate him",
},
{
image: "/images/user/user-03.png",
title: "New message",
subTitle: "Devid sent a new message",
},
{
image: "/images/user/user-26.png",
title: "New Payment received",
subTitle: "Check your earnings",
},
{
image: "/images/user/user-28.png",
title: "Jolly completed tasks",
subTitle: "Assign new task",
},
{
image: "/images/user/user-27.png",
title: "Roman Joined the Team!",
subTitle: "Congratulate him",
},
];
export function Notification() {
const [isOpen, setIsOpen] = useState(false);
const [isDotVisible, setIsDotVisible] = useState(true);
const isMobile = useIsMobile();
return (
<Dropdown
isOpen={isOpen}
setIsOpen={(open) => {
setIsOpen(open);
if (setIsDotVisible) setIsDotVisible(false);
}}
>
<DropdownTrigger
className="grid size-12 place-items-center rounded-full border bg-gray-2 text-dark outline-none hover:text-primary focus-visible:border-primary focus-visible:text-primary dark:border-dark-4 dark:bg-dark-3 dark:text-white dark:focus-visible:border-primary"
aria-label="View Notifications"
>
<span className="relative">
<BellIcon />
{isDotVisible && (
<span
className={cn(
"absolute right-0 top-0 z-1 size-2 rounded-full bg-red-light ring-2 ring-gray-2 dark:ring-dark-3",
)}
>
<span className="absolute inset-0 -z-1 animate-ping rounded-full bg-red-light opacity-75" />
</span>
)}
</span>
</DropdownTrigger>
<DropdownContent
align={isMobile ? "end" : "center"}
className="border border-stroke bg-white px-3.5 py-3 shadow-md dark:border-dark-3 dark:bg-gray-dark min-[350px]:min-w-[20rem]"
>
<div className="mb-1 flex items-center justify-between px-2 py-1.5">
<span className="text-lg font-medium text-dark dark:text-white">
Notifications
</span>
<span className="rounded-md bg-primary px-[9px] py-0.5 text-xs font-medium text-white">
5 new
</span>
</div>
<ul className="mb-3 max-h-[23rem] space-y-1.5 overflow-y-auto">
{notificationList.map((item, index) => (
<li key={index} role="menuitem">
<Link
href="#"
onClick={() => setIsOpen(false)}
className="flex items-center gap-4 rounded-lg px-2 py-1.5 outline-none hover:bg-gray-2 focus-visible:bg-gray-2 dark:hover:bg-dark-3 dark:focus-visible:bg-dark-3"
>
<Image
src={item.image}
className="size-14 rounded-full object-cover"
width={200}
height={200}
alt="User"
/>
<div>
<strong className="block text-sm font-medium text-dark dark:text-white">
{item.title}
</strong>
<span className="truncate text-sm font-medium text-dark-5 dark:text-dark-6">
{item.subTitle}
</span>
</div>
</Link>
</li>
))}
</ul>
<Link
href="#"
onClick={() => setIsOpen(false)}
className="block rounded-lg border border-primary p-2 text-center text-sm font-medium tracking-wide text-primary outline-none transition-colors hover:bg-blue-light-5 focus:bg-blue-light-5 focus:text-primary focus-visible:border-primary dark:border-dark-3 dark:text-dark-6 dark:hover:border-dark-5 dark:hover:bg-dark-3 dark:hover:text-dark-7 dark:focus-visible:border-dark-5 dark:focus-visible:bg-dark-3 dark:focus-visible:text-dark-7"
>
See all notifications
</Link>
</DropdownContent>
</Dropdown>
);
}
@@ -0,0 +1,33 @@
import type { SVGProps } from "react";
export function Sun(props: SVGProps<SVGSVGElement>) {
return (
<svg
width={20}
height={20}
viewBox="0 0 20 20"
fill="currentColor"
{...props}
>
<path d="M10 1.042c.345 0 .625.28.625.625V2.5a.625.625 0 11-1.25 0v-.833c0-.346.28-.625.625-.625zM3.666 3.665a.625.625 0 01.883 0l.328.328a.625.625 0 01-.884.884l-.327-.328a.625.625 0 010-.884zm12.668 0a.625.625 0 010 .884l-.327.328a.625.625 0 01-.884-.884l.327-.327a.625.625 0 01.884 0zM10 5.626a4.375 4.375 0 100 8.75 4.375 4.375 0 000-8.75zM4.375 10a5.625 5.625 0 1111.25 0 5.625 5.625 0 01-11.25 0zm-3.333 0c0-.345.28-.625.625-.625H2.5a.625.625 0 110 1.25h-.833A.625.625 0 011.042 10zm15.833 0c0-.345.28-.625.625-.625h.833a.625.625 0 010 1.25H17.5a.625.625 0 01-.625-.625zm-1.752 5.123a.625.625 0 01.884 0l.327.327a.625.625 0 11-.884.884l-.327-.327a.625.625 0 010-.884zm-10.246 0a.625.625 0 010 .884l-.328.327a.625.625 0 11-.883-.884l.327-.327a.625.625 0 01.884 0zM10 16.875c.345 0 .625.28.625.625v.833a.625.625 0 01-1.25 0V17.5c0-.345.28-.625.625-.625z" />
</svg>
);
}
export function Moon(props: SVGProps<SVGSVGElement>) {
return (
<svg
width={20}
height={20}
viewBox="0 0 20 20"
fill="currentColor"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M9.18 2.334a7.71 7.71 0 108.485 8.485A6.042 6.042 0 119.18 2.335zM1.042 10a8.958 8.958 0 018.958-8.958c.598 0 .896.476.948.855.049.364-.086.828-.505 1.082a4.792 4.792 0 106.579 6.579c.253-.42.717-.555 1.081-.506.38.052.856.35.856.948A8.958 8.958 0 011.04 10z"
/>
</svg>
);
}
@@ -0,0 +1,56 @@
import { cn } from "@/lib/utils";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
import { Moon, Sun } from "./icons";
const THEMES = [
{
name: "light",
Icon: Sun,
},
{
name: "dark",
Icon: Moon,
},
];
export function ThemeToggleSwitch() {
const { setTheme, theme } = useTheme();
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return null;
}
return (
<button
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
className="group rounded-full bg-gray-3 p-[5px] text-[#111928] outline-1 outline-primary focus-visible:outline dark:bg-[#020D1A] dark:text-current"
>
<span className="sr-only">
Switch to {theme === "light" ? "dark" : "light"} mode
</span>
<span aria-hidden className="relative flex gap-2.5">
{/* Indicator */}
<span className="absolute size-[38px] rounded-full border border-gray-200 bg-white transition-all dark:translate-x-[48px] dark:border-none dark:bg-dark-2 dark:group-hover:bg-dark-3" />
{THEMES.map(({ name, Icon }) => (
<span
key={name}
className={cn(
"relative grid size-[38px] place-items-center rounded-full",
name === "dark" && "dark:text-white",
)}
>
<Icon />
</span>
))}
</span>
</button>
);
}
@@ -0,0 +1,66 @@
import type { SVGProps } from "react";
type SVGPropsType = SVGProps<SVGSVGElement>;
export function UserIcon(props: SVGPropsType) {
return (
<svg
width={20}
height={20}
viewBox="0 0 18 18"
fill="currentColor"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M9 .938a3.562 3.562 0 100 7.124A3.562 3.562 0 009 .938zM6.562 4.5a2.437 2.437 0 114.875 0 2.437 2.437 0 01-4.875 0zM9 9.188c-1.735 0-3.334.394-4.518 1.06-1.167.657-2.045 1.652-2.045 2.877v.076c0 .872-.001 1.965.958 2.747.472.384 1.132.657 2.025.838.894.181 2.06.276 3.58.276s2.685-.095 3.58-.276c.893-.18 1.553-.454 2.025-.838.96-.782.958-1.875.957-2.747v-.076c0-1.226-.877-2.22-2.044-2.877-1.184-.666-2.783-1.06-4.518-1.06zm-5.438 3.937c0-.639.467-1.331 1.471-1.896.987-.555 2.388-.916 3.967-.916 1.579 0 2.98.36 3.967.916 1.004.565 1.47 1.258 1.47 1.896 0 .98-.03 1.533-.542 1.95-.278.227-.743.448-1.538.609-.793.16-1.876.254-3.357.254-1.48 0-2.564-.094-3.357-.255-.795-.16-1.26-.381-1.538-.608-.512-.417-.543-.97-.543-1.95z"
/>
</svg>
);
}
export function SettingsIcon(props: SVGPropsType) {
return (
<svg
width={20}
height={20}
viewBox="0 0 18 18"
fill="currentColor"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M9 6.188a2.813 2.813 0 100 5.625 2.813 2.813 0 000-5.626zM7.312 9a1.688 1.688 0 113.376 0 1.688 1.688 0 01-3.376 0z"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8.981.938c-.333 0-.612 0-.84.015a2.117 2.117 0 00-.68.142c-.506.209-.907.61-1.117 1.116-.108.263-.138.54-.15.841a.65.65 0 01-.311.55.65.65 0 01-.631-.005c-.267-.141-.522-.254-.804-.291a2.063 2.063 0 00-1.525.408c-.187.144-.33.32-.462.519-.128.19-.267.43-.434.72l-.019.032c-.166.289-.306.53-.406.735a2.117 2.117 0 00-.218.66c-.071.543.076 1.091.409 1.525.173.226.398.39.654.55A.65.65 0 012.766 9a.65.65 0 01-.32.544c-.255.16-.48.325-.653.55-.333.435-.48.983-.409 1.526.03.233.113.445.218.66.1.205.24.446.406.735l.02.033c.166.288.305.53.433.72.133.197.275.374.462.518.434.333.983.48 1.525.408.282-.037.537-.15.804-.29a.65.65 0 01.63-.005.65.65 0 01.313.549c.011.3.04.578.15.841.209.506.61.907 1.116 1.117.217.09.442.125.68.14.228.017.507.017.84.017h.038c.333 0 .612 0 .84-.016.238-.016.463-.051.68-.142.506-.209.907-.61 1.117-1.116.108-.263.138-.54.15-.841a.65.65 0 01.311-.55.65.65 0 01.631.005c.267.141.522.254.804.291a2.062 2.062 0 001.525-.408c.187-.144.33-.32.462-.519.128-.19.267-.43.434-.72l.019-.032c.166-.289.305-.53.406-.736.105-.214.187-.426.218-.66a2.062 2.062 0 00-.409-1.524c-.173-.226-.398-.39-.654-.55A.65.65 0 0115.234 9a.65.65 0 01.32-.544c.255-.16.48-.325.653-.55.333-.435.48-.983.409-1.526a2.117 2.117 0 00-.218-.66c-.1-.205-.24-.446-.406-.735l-.02-.033c-.166-.288-.305-.53-.433-.72a2.117 2.117 0 00-.462-.518 2.062 2.062 0 00-1.525-.408c-.282.037-.537.15-.804.29a.65.65 0 01-.63.005.65.65 0 01-.313-.549c-.011-.3-.04-.578-.15-.841a2.063 2.063 0 00-1.116-1.116 2.118 2.118 0 00-.68-.142c-.228-.016-.507-.016-.84-.015H8.98zm-1.09 1.196c.058-.024.146-.046.327-.059.185-.012.425-.013.782-.013.357 0 .597 0 .782.013.181.013.269.035.327.059.23.095.412.278.507.507.03.073.055.186.065.453.022.595.329 1.167.874 1.481a1.775 1.775 0 001.719.016c.237-.125.347-.16.425-.17a.938.938 0 01.693.186c.05.038.113.103.214.253.103.155.223.362.402.671.179.31.298.517.38.684.08.163.104.25.113.312a.937.937 0 01-.186.693c-.048.062-.133.14-.36.283A1.775 1.775 0 0014.109 9c0 .629.342 1.18.846 1.497.227.143.312.22.36.283a.938.938 0 01.186.693c-.009.062-.033.15-.113.312-.082.167-.201.374-.38.684-.179.309-.299.516-.402.67-.101.151-.165.216-.214.254a.937.937 0 01-.693.186c-.078-.01-.188-.045-.425-.17a1.775 1.775 0 00-1.72.016 1.775 1.775 0 00-.873 1.48c-.01.268-.035.381-.065.454a.937.937 0 01-.507.507 1.034 1.034 0 01-.327.059c-.185.012-.425.012-.782.012-.357 0-.597 0-.782-.012a1.033 1.033 0 01-.327-.059.937.937 0 01-.507-.507c-.03-.073-.055-.186-.065-.454a1.775 1.775 0 00-.874-1.48 1.775 1.775 0 00-1.719-.016c-.237.125-.347.16-.425.17a.937.937 0 01-.693-.186 1.034 1.034 0 01-.214-.253 12.818 12.818 0 01-.402-.671c-.179-.31-.298-.517-.38-.684a1.035 1.035 0 01-.113-.312.937.937 0 01.186-.693c.048-.063.133-.14.36-.283.504-.316.846-.868.846-1.497 0-.629-.342-1.18-.846-1.497-.227-.143-.312-.22-.36-.283a.937.937 0 01-.186-.693c.009-.062.033-.15.113-.312.082-.167.201-.375.38-.684.179-.31.299-.517.402-.67.101-.151.165-.216.214-.254a.938.938 0 01.693-.186c.078.01.188.045.425.17a1.775 1.775 0 001.72-.016c.544-.314.85-.886.873-1.48.01-.268.035-.381.065-.454a.937.937 0 01.507-.507z"
/>
</svg>
);
}
export function LogOutIcon(props: SVGPropsType) {
return (
<svg
width={20}
height={20}
viewBox="0 0 18 18"
fill="currentColor"
{...props}
>
<g clipPath="url(#clip0_7095_11691)">
<path d="M11.209.938c-1.026 0-1.852 0-2.503.087-.675.09-1.243.285-1.695.736-.393.394-.592.878-.697 1.446-.101.553-.12 1.229-.125 2.04a.562.562 0 101.125.006c.005-.82.026-1.401.107-1.842.078-.426.203-.672.386-.854.207-.208.499-.343 1.05-.417.566-.076 1.317-.078 2.393-.078H12c1.077 0 1.828.002 2.394.078.55.074.842.21 1.05.417.207.207.342.499.416 1.05.077.566.078 1.316.078 2.393v6c0 1.077-.002 1.827-.078 2.394-.074.55-.209.842-.417 1.05-.207.207-.499.342-1.049.416-.566.076-1.317.078-2.394.078h-.75c-1.076 0-1.827-.002-2.394-.078-.55-.074-.842-.21-1.05-.417-.182-.182-.307-.428-.385-.854-.081-.44-.102-1.022-.107-1.842a.563.563 0 00-1.125.006c.004.811.024 1.487.125 2.04.105.568.304 1.052.697 1.446.452.451 1.02.645 1.695.736.65.087 1.477.087 2.503.087h.832c1.026 0 1.853 0 2.503-.087.675-.09 1.243-.285 1.695-.736.451-.452.645-1.02.736-1.695.088-.65.088-1.477.088-2.503V5.96c0-1.026 0-1.853-.088-2.503-.09-.675-.285-1.243-.736-1.695-.452-.451-1.02-.645-1.695-.736-.65-.088-1.477-.088-2.503-.087h-.832z" />
<path d="M11.25 8.438a.562.562 0 110 1.124H3.02l1.471 1.26a.563.563 0 01-.732.855l-2.625-2.25a.562.562 0 010-.854l2.625-2.25a.562.562 0 11.732.854l-1.47 1.26h8.229z" />
</g>
<defs>
<clipPath id="clip0_7095_11691">
<rect width={18} height={18} rx={5} />
</clipPath>
</defs>
</svg>
);
}
@@ -0,0 +1,119 @@
"use client";
import { ChevronUpIcon } from "@/assets/icons";
import {
Dropdown,
DropdownContent,
DropdownTrigger,
} from "@/components/ui/dropdown";
import { cn } from "@/lib/utils";
import Image from "next/image";
import Link from "next/link";
import { useState } from "react";
import { LogOutIcon, SettingsIcon, UserIcon } from "./icons";
export function UserInfo() {
const [isOpen, setIsOpen] = useState(false);
const USER = {
name: "John Smith",
email: "johnson@nextadmin.com",
img: "/images/user/user-03.png",
};
return (
<Dropdown isOpen={isOpen} setIsOpen={setIsOpen}>
<DropdownTrigger className="rounded align-middle outline-none ring-primary ring-offset-2 focus-visible:ring-1 dark:ring-offset-gray-dark">
<span className="sr-only">My Account</span>
<figure className="flex items-center gap-3">
<Image
src={USER.img}
className="size-12"
alt={`Avatar of ${USER.name}`}
role="presentation"
width={200}
height={200}
/>
<figcaption className="flex items-center gap-1 font-medium text-dark dark:text-dark-6 max-[1024px]:sr-only">
<span>{USER.name}</span>
<ChevronUpIcon
aria-hidden
className={cn(
"rotate-180 transition-transform",
isOpen && "rotate-0",
)}
strokeWidth={1.5}
/>
</figcaption>
</figure>
</DropdownTrigger>
<DropdownContent
className="border border-stroke bg-white shadow-md dark:border-dark-3 dark:bg-gray-dark min-[230px]:min-w-[17.5rem]"
align="end"
>
<h2 className="sr-only">User information</h2>
<figure className="flex items-center gap-2.5 px-5 py-3.5">
<Image
src={USER.img}
className="size-12"
alt={`Avatar for ${USER.name}`}
role="presentation"
width={200}
height={200}
/>
<figcaption className="space-y-1 text-base font-medium">
<div className="mb-2 leading-none text-dark dark:text-white">
{USER.name}
</div>
<div className="leading-none text-gray-6">{USER.email}</div>
</figcaption>
</figure>
<hr className="border-[#E8E8E8] dark:border-dark-3" />
<div className="p-2 text-base text-[#4B5563] dark:text-dark-6 [&>*]:cursor-pointer">
<Link
href={"/profile"}
onClick={() => setIsOpen(false)}
className="flex w-full items-center gap-2.5 rounded-lg px-2.5 py-[9px] hover:bg-gray-2 hover:text-dark dark:hover:bg-dark-3 dark:hover:text-white"
>
<UserIcon />
<span className="mr-auto text-base font-medium">View profile</span>
</Link>
<Link
href={"/pages/settings"}
onClick={() => setIsOpen(false)}
className="flex w-full items-center gap-2.5 rounded-lg px-2.5 py-[9px] hover:bg-gray-2 hover:text-dark dark:hover:bg-dark-3 dark:hover:text-white"
>
<SettingsIcon />
<span className="mr-auto text-base font-medium">
Account Settings
</span>
</Link>
</div>
<hr className="border-[#E8E8E8] dark:border-dark-3" />
<div className="p-2 text-base text-[#4B5563] dark:text-dark-6">
<button
className="flex w-full items-center gap-2.5 rounded-lg px-2.5 py-[9px] hover:bg-gray-2 hover:text-dark dark:hover:bg-dark-3 dark:hover:text-white"
onClick={() => setIsOpen(false)}
>
<LogOutIcon />
<span className="text-base font-medium">Log out</span>
</button>
</div>
</DropdownContent>
</Dropdown>
);
}