feature/syasya/testlayout #7
@ -19,6 +19,7 @@ const ComponentsAuthLoginForm = () => {
 | 
			
		||||
      const res = await axios.post('/api/login', { email, password });
 | 
			
		||||
      toast.success(res.data?.message || 'Login successful!');
 | 
			
		||||
      router.push('/adminDashboard');
 | 
			
		||||
      router.refresh();
 | 
			
		||||
      // token cookie is already set by the server:
 | 
			
		||||
    } catch (err: any) {
 | 
			
		||||
      console.error('Login error:', err);
 | 
			
		||||
 | 
			
		||||
@ -1,57 +1,73 @@
 | 
			
		||||
// Dropdown.tsx
 | 
			
		||||
'use client';
 | 
			
		||||
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
 | 
			
		||||
import { usePopper } from 'react-popper';
 | 
			
		||||
import type { ReactNode } from 'react';
 | 
			
		||||
 | 
			
		||||
const Dropdown = (props: any, forwardedRef: any) => {
 | 
			
		||||
    const [visibility, setVisibility] = useState<any>(false);
 | 
			
		||||
type DropdownProps = {
 | 
			
		||||
  button?: ReactNode;             // 👈 make optional
 | 
			
		||||
  children: ReactNode;
 | 
			
		||||
  btnClassName?: string;
 | 
			
		||||
  placement?: any;
 | 
			
		||||
  offset?: [number, number];
 | 
			
		||||
  panelClassName?: string;
 | 
			
		||||
  closeOnItemClick?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
    const referenceRef = useRef<any>();
 | 
			
		||||
    const popperRef = useRef<any>();
 | 
			
		||||
const Dropdown = (props: DropdownProps, forwardedRef: any) => {
 | 
			
		||||
  const [visible, setVisible] = useState(false);
 | 
			
		||||
  const referenceRef = useRef<HTMLButtonElement | null>(null);
 | 
			
		||||
  const popperRef = useRef<HTMLDivElement | null>(null);
 | 
			
		||||
 | 
			
		||||
  const { styles, attributes } = usePopper(referenceRef.current, popperRef.current, {
 | 
			
		||||
    placement: props.placement || 'bottom-end',
 | 
			
		||||
        modifiers: [
 | 
			
		||||
            {
 | 
			
		||||
                name: 'offset',
 | 
			
		||||
                options: {
 | 
			
		||||
                    offset: props.offset || [0],
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    modifiers: [{ name: 'offset', options: { offset: props.offset ?? [0, 8] } }],
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
    const handleDocumentClick = (event: any) => {
 | 
			
		||||
        if (referenceRef.current.contains(event.target) || popperRef.current.contains(event.target)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setVisibility(false);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
        document.addEventListener('mousedown', handleDocumentClick);
 | 
			
		||||
        return () => {
 | 
			
		||||
            document.removeEventListener('mousedown', handleDocumentClick);
 | 
			
		||||
    const onDoc = (e: MouseEvent) => {
 | 
			
		||||
      if (!referenceRef.current || !popperRef.current) return;
 | 
			
		||||
      if (referenceRef.current.contains(e.target as Node)) return;
 | 
			
		||||
      if (popperRef.current.contains(e.target as Node)) return;
 | 
			
		||||
      setVisible(false);
 | 
			
		||||
    };
 | 
			
		||||
    document.addEventListener('mousedown', onDoc);
 | 
			
		||||
    return () => document.removeEventListener('mousedown', onDoc);
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
    useImperativeHandle(forwardedRef, () => ({
 | 
			
		||||
        close() {
 | 
			
		||||
            setVisibility(false);
 | 
			
		||||
        },
 | 
			
		||||
    }));
 | 
			
		||||
  useImperativeHandle(forwardedRef, () => ({ close: () => setVisible(false) }));
 | 
			
		||||
 | 
			
		||||
  const defaultButton = (
 | 
			
		||||
    <span className="inline-flex h-9 w-9 items-center justify-center rounded-full bg-gray-200 dark:bg-gray-700" />
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
            <button ref={referenceRef} type="button" className={props.btnClassName} onClick={() => setVisibility(!visibility)}>
 | 
			
		||||
                {props.button}
 | 
			
		||||
      <button
 | 
			
		||||
        ref={referenceRef}
 | 
			
		||||
        type="button"
 | 
			
		||||
        className={props.btnClassName}
 | 
			
		||||
        onClick={() => setVisible((v) => !v)}
 | 
			
		||||
      >
 | 
			
		||||
        {props.button ?? defaultButton} {/* 👈 fallback */}
 | 
			
		||||
      </button>
 | 
			
		||||
 | 
			
		||||
            <div ref={popperRef} style={styles.popper} {...attributes.popper} className="z-50" onClick={() => setVisibility(!visibility)}>
 | 
			
		||||
                {visibility && props.children}
 | 
			
		||||
      <div
 | 
			
		||||
        ref={popperRef}
 | 
			
		||||
        style={styles.popper}
 | 
			
		||||
        {...attributes.popper}
 | 
			
		||||
        className="z-50"
 | 
			
		||||
      >
 | 
			
		||||
        {visible && (
 | 
			
		||||
          <div className={props.panelClassName ?? 'rounded-lg bg-white dark:bg-neutral-900 shadow-lg ring-1 ring-black/5'}>
 | 
			
		||||
            {props.children}
 | 
			
		||||
          </div>
 | 
			
		||||
        )}
 | 
			
		||||
      </div>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default forwardRef(Dropdown);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -6,251 +6,171 @@ import { IRootState } from '@/store';
 | 
			
		||||
import { toggleTheme, toggleSidebar, toggleRTL } from '@/store/themeConfigSlice';
 | 
			
		||||
import Dropdown from '@/components/dropdown';
 | 
			
		||||
import IconMenu from '@/components/icon/icon-menu';
 | 
			
		||||
import IconCalendar from '@/components/icon/icon-calendar';
 | 
			
		||||
import IconEdit from '@/components/icon/icon-edit';
 | 
			
		||||
import IconChatNotification from '@/components/icon/icon-chat-notification';
 | 
			
		||||
import IconSearch from '@/components/icon/icon-search';
 | 
			
		||||
import IconXCircle from '@/components/icon/icon-x-circle';
 | 
			
		||||
import IconSun from '@/components/icon/icon-sun';
 | 
			
		||||
import IconMoon from '@/components/icon/icon-moon';
 | 
			
		||||
import IconLaptop from '@/components/icon/icon-laptop';
 | 
			
		||||
import IconMailDot from '@/components/icon/icon-mail-dot';
 | 
			
		||||
import IconArrowLeft from '@/components/icon/icon-arrow-left';
 | 
			
		||||
import IconInfoCircle from '@/components/icon/icon-info-circle';
 | 
			
		||||
import IconBellBing from '@/components/icon/icon-bell-bing';
 | 
			
		||||
import IconUser from '@/components/icon/icon-user';
 | 
			
		||||
import IconMail from '@/components/icon/icon-mail';
 | 
			
		||||
import IconLockDots from '@/components/icon/icon-lock-dots';
 | 
			
		||||
import IconLogout from '@/components/icon/icon-logout';
 | 
			
		||||
import IconMenuDashboard from '@/components/icon/menu/icon-menu-dashboard';
 | 
			
		||||
import IconCaretDown from '@/components/icon/icon-caret-down';
 | 
			
		||||
import IconMenuApps from '@/components/icon/menu/icon-menu-apps';
 | 
			
		||||
import IconMenuComponents from '@/components/icon/menu/icon-menu-components';
 | 
			
		||||
import IconMenuElements from '@/components/icon/menu/icon-menu-elements';
 | 
			
		||||
import IconMenuDatatables from '@/components/icon/menu/icon-menu-datatables';
 | 
			
		||||
import IconMenuForms from '@/components/icon/menu/icon-menu-forms';
 | 
			
		||||
import IconMenuPages from '@/components/icon/menu/icon-menu-pages';
 | 
			
		||||
import IconMenuMore from '@/components/icon/menu/icon-menu-more';
 | 
			
		||||
import { usePathname, useRouter } from 'next/navigation';
 | 
			
		||||
import { getTranslation } from '@/i18n';
 | 
			
		||||
 | 
			
		||||
const Header = () => {
 | 
			
		||||
type UserData = { id: string; email: string; createdAt: string };
 | 
			
		||||
 | 
			
		||||
export default function Header() {
 | 
			
		||||
  const pathname = usePathname();
 | 
			
		||||
  const dispatch = useDispatch();
 | 
			
		||||
  const router = useRouter();
 | 
			
		||||
    const { t, i18n } = getTranslation();
 | 
			
		||||
  const themeConfig = useSelector((state: IRootState) => state.themeConfig);
 | 
			
		||||
  const isRtl = themeConfig.rtlClass === 'rtl';
 | 
			
		||||
 | 
			
		||||
  const [user, setUser] = useState<UserData | null>(null);
 | 
			
		||||
  const [loadingUser, setLoadingUser] = useState(true);
 | 
			
		||||
 | 
			
		||||
  // Highlight active menu (your original effect)
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
        const selector = document.querySelector('ul.horizontal-menu a[href="' + window.location.pathname + '"]');
 | 
			
		||||
    const selector = document.querySelector(
 | 
			
		||||
      'ul.horizontal-menu a[href="' + window.location.pathname + '"]'
 | 
			
		||||
    );
 | 
			
		||||
    if (selector) {
 | 
			
		||||
            const all: any = document.querySelectorAll('ul.horizontal-menu .nav-link.active');
 | 
			
		||||
            for (let i = 0; i < all.length; i++) {
 | 
			
		||||
                all[0]?.classList.remove('active');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let allLinks = document.querySelectorAll('ul.horizontal-menu a.active');
 | 
			
		||||
            for (let i = 0; i < allLinks.length; i++) {
 | 
			
		||||
                const element = allLinks[i];
 | 
			
		||||
                element?.classList.remove('active');
 | 
			
		||||
            }
 | 
			
		||||
            selector?.classList.add('active');
 | 
			
		||||
 | 
			
		||||
      document
 | 
			
		||||
        .querySelectorAll('ul.horizontal-menu .nav-link.active')
 | 
			
		||||
        .forEach((el) => el.classList.remove('active'));
 | 
			
		||||
      document
 | 
			
		||||
        .querySelectorAll('ul.horizontal-menu a.active')
 | 
			
		||||
        .forEach((el) => el.classList.remove('active'));
 | 
			
		||||
      selector.classList.add('active');
 | 
			
		||||
      const ul: any = selector.closest('ul.sub-menu');
 | 
			
		||||
      if (ul) {
 | 
			
		||||
                let ele: any = ul.closest('li.menu').querySelectorAll('.nav-link');
 | 
			
		||||
                if (ele) {
 | 
			
		||||
                    ele = ele[0];
 | 
			
		||||
                    setTimeout(() => {
 | 
			
		||||
                        ele?.classList.add('active');
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
        const ele: any = ul.closest('li.menu')?.querySelector('.nav-link');
 | 
			
		||||
        setTimeout(() => ele?.classList.add('active'));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }, [pathname]);
 | 
			
		||||
 | 
			
		||||
    const isRtl = useSelector((state: IRootState) => state.themeConfig.rtlClass) === 'rtl';
 | 
			
		||||
 | 
			
		||||
    const themeConfig = useSelector((state: IRootState) => state.themeConfig);
 | 
			
		||||
    const setLocale = (flag: string) => {
 | 
			
		||||
        if (flag.toLowerCase() === 'ae') {
 | 
			
		||||
            dispatch(toggleRTL('rtl'));
 | 
			
		||||
        } else {
 | 
			
		||||
            dispatch(toggleRTL('ltr'));
 | 
			
		||||
  async function loadUser() {
 | 
			
		||||
  try {
 | 
			
		||||
    const res = await fetch('/api/auth/me', {
 | 
			
		||||
      method: 'GET',
 | 
			
		||||
      credentials: 'include', // send cookie
 | 
			
		||||
      cache: 'no-store',      // avoid stale cached responses
 | 
			
		||||
    });
 | 
			
		||||
    if (!res.ok) throw new Error();
 | 
			
		||||
    const data = await res.json();
 | 
			
		||||
    setUser(data.user);
 | 
			
		||||
  } catch {
 | 
			
		||||
    setUser(null);
 | 
			
		||||
  } finally {
 | 
			
		||||
    setLoadingUser(false);
 | 
			
		||||
  }
 | 
			
		||||
        router.refresh();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    function createMarkup(messages: any) {
 | 
			
		||||
        return { __html: messages };
 | 
			
		||||
}
 | 
			
		||||
    const [messages, setMessages] = useState([
 | 
			
		||||
        {
 | 
			
		||||
            id: 1,
 | 
			
		||||
            image: '<span class="grid place-content-center w-9 h-9 rounded-full bg-success-light dark:bg-success text-success dark:text-success-light"><svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg></span>',
 | 
			
		||||
            title: 'Congratulations!',
 | 
			
		||||
            message: 'Your OS has been updated.',
 | 
			
		||||
            time: '1hr',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            id: 2,
 | 
			
		||||
            image: '<span class="grid place-content-center w-9 h-9 rounded-full bg-info-light dark:bg-info text-info dark:text-info-light"><svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg></span>',
 | 
			
		||||
            title: 'Did you know?',
 | 
			
		||||
            message: 'You can switch between artboards.',
 | 
			
		||||
            time: '2hr',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            id: 3,
 | 
			
		||||
            image: '<span class="grid place-content-center w-9 h-9 rounded-full bg-danger-light dark:bg-danger text-danger dark:text-danger-light"> <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg></span>',
 | 
			
		||||
            title: 'Something went wrong!',
 | 
			
		||||
            message: 'Send Reposrt',
 | 
			
		||||
            time: '2days',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            id: 4,
 | 
			
		||||
            image: '<span class="grid place-content-center w-9 h-9 rounded-full bg-warning-light dark:bg-warning text-warning dark:text-warning-light"><svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">    <circle cx="12" cy="12" r="10"></circle>    <line x1="12" y1="8" x2="12" y2="12"></line>    <line x1="12" y1="16" x2="12.01" y2="16"></line></svg></span>',
 | 
			
		||||
            title: 'Warning',
 | 
			
		||||
            message: 'Your password strength is low.',
 | 
			
		||||
            time: '5days',
 | 
			
		||||
        },
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    const removeMessage = (value: number) => {
 | 
			
		||||
        setMessages(messages.filter((user) => user.id !== value));
 | 
			
		||||
useEffect(() => {
 | 
			
		||||
  setLoadingUser(true);
 | 
			
		||||
  loadUser();
 | 
			
		||||
  // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
			
		||||
}, [pathname]); // re-fetch on route change (after login redirect)
 | 
			
		||||
 | 
			
		||||
  const handleLogout = async () => {
 | 
			
		||||
    await fetch('/api/auth/logout', { method: 'POST' });
 | 
			
		||||
    setUser(null);
 | 
			
		||||
    router.push('/login'); // go to login
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
    const [notifications, setNotifications] = useState([
 | 
			
		||||
        {
 | 
			
		||||
            id: 1,
 | 
			
		||||
            profile: 'user-profile.jpeg',
 | 
			
		||||
            message: '<strong class="text-sm mr-1">John Doe</strong>invite you to <strong>Prototyping</strong>',
 | 
			
		||||
            time: '45 min ago',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            id: 2,
 | 
			
		||||
            profile: 'profile-34.jpeg',
 | 
			
		||||
            message: '<strong class="text-sm mr-1">Adam Nolan</strong>mentioned you to <strong>UX Basics</strong>',
 | 
			
		||||
            time: '9h Ago',
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            id: 3,
 | 
			
		||||
            profile: 'profile-16.jpeg',
 | 
			
		||||
            message: '<strong class="text-sm mr-1">Anna Morgan</strong>Upload a file',
 | 
			
		||||
            time: '9h Ago',
 | 
			
		||||
        },
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    const removeNotification = (value: number) => {
 | 
			
		||||
        setNotifications(notifications.filter((user) => user.id !== value));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const [search, setSearch] = useState(false);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <header className={`z-40 ${themeConfig.semidark && themeConfig.menu === 'horizontal' ? 'dark' : ''}`}>
 | 
			
		||||
      <div className="shadow-sm">
 | 
			
		||||
                <div className="relative flex w-full items-center bg-white px-5 py-2.5 dark:bg-black">
 | 
			
		||||
        <div className="relative flex w-full items-center bg-white px-5 py-2.5 dark:bg-rtgray-900">
 | 
			
		||||
          {/* Logo */}
 | 
			
		||||
          <div className="horizontal-logo flex items-center justify-between ltr:mr-2 rtl:ml-2 lg:hidden">
 | 
			
		||||
            <Link href="/" className="main-logo flex shrink-0 items-center">
 | 
			
		||||
                            <img className="inline w-8 ltr:-ml-1 rtl:-mr-1" src="/assets/images/newfulllogo.png" alt="logo" />
 | 
			
		||||
                            <span className="hidden align-middle text-2xl  font-semibold  transition-all duration-300 ltr:ml-1.5 rtl:mr-1.5 dark:text-white-light md:inline">Rooftop Energy</span>
 | 
			
		||||
              <img className="inline w-8" src="/assets/images/newfulllogo.png" alt="logo" />
 | 
			
		||||
              <span className="hidden text-2xl font-semibold ltr:ml-1.5 dark:text-white-light md:inline">
 | 
			
		||||
                Rooftop Energy
 | 
			
		||||
              </span>
 | 
			
		||||
            </Link>
 | 
			
		||||
            <button
 | 
			
		||||
              type="button"
 | 
			
		||||
                            className="collapse-icon flex flex-none rounded-full bg-white-light/40 p-2 hover:bg-white-light/90 hover:text-primary ltr:ml-2 rtl:mr-2 dark:bg-dark/40 dark:text-[#d0d2d6] dark:hover:bg-dark/60 dark:hover:text-primary lg:hidden"
 | 
			
		||||
              onClick={() => dispatch(toggleSidebar())}
 | 
			
		||||
              className="collapse-icon flex p-2 rounded-full hover:bg-rtgray-200 dark:text-white dark:hover:bg-rtgray-700"
 | 
			
		||||
            >
 | 
			
		||||
                            <IconMenu className="h-5 w-5" />
 | 
			
		||||
              <IconMenu className="h-6 w-6" />
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
                    <div className="flex items-center justify-end space-x-1.5 ltr:ml-auto rtl:mr-auto rtl:space-x-reverse dark:text-[#d0d2d6] sm:flex-1 ltr:sm:ml-0 sm:rtl:mr-0 lg:space-x-2">
 | 
			
		||||
 | 
			
		||||
                        {/* ------------------- Start Theme Switch ------------------- */}
 | 
			
		||||
                        <div>
 | 
			
		||||
          {/* Right-side actions */}
 | 
			
		||||
          <div className="flex items-center justify-end space-x-1.5 ltr:ml-auto rtl:mr-auto rtl:space-x-reverse dark:text-[#d0d2d6] lg:space-x-2">
 | 
			
		||||
            {/* Theme toggle */}
 | 
			
		||||
            {themeConfig.theme === 'light' ? (
 | 
			
		||||
              <button
 | 
			
		||||
                                    className={`${
 | 
			
		||||
                                        themeConfig.theme === 'light' &&
 | 
			
		||||
                                        'flex items-center rounded-full bg-white-light/40 p-2 hover:bg-white-light/90 hover:text-primary dark:bg-dark/40 dark:hover:bg-dark/60'
 | 
			
		||||
                                    }`}
 | 
			
		||||
                onClick={() => dispatch(toggleTheme('dark'))}
 | 
			
		||||
                className="flex items-center p-2 rounded-full bg-white-light/40 hover:bg-white-light/90 dark:bg-rtgray-800 dark:hover:bg-rtgray-700"
 | 
			
		||||
              >
 | 
			
		||||
                <IconSun />
 | 
			
		||||
              </button>
 | 
			
		||||
            ) : (
 | 
			
		||||
                                ''
 | 
			
		||||
                            )}
 | 
			
		||||
                            {themeConfig.theme === 'dark' && (
 | 
			
		||||
              <button
 | 
			
		||||
                                    className={`${
 | 
			
		||||
                                        themeConfig.theme === 'dark' &&
 | 
			
		||||
                                        'flex items-center rounded-full bg-white-light/40 p-2 hover:bg-white-light/90 hover:text-primary dark:bg-dark/40 dark:hover:bg-dark/60'
 | 
			
		||||
                                    }`}
 | 
			
		||||
                onClick={() => dispatch(toggleTheme('light'))}
 | 
			
		||||
                className="flex items-center p-2 rounded-full bg-white-light/40 hover:bg-white-light/90 dark:bg-rtgray-800 dark:hover:bg-rtgray-700"
 | 
			
		||||
              >
 | 
			
		||||
                <IconMoon />
 | 
			
		||||
              </button>
 | 
			
		||||
            )}
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {/* ------------------- End Theme Switch ------------------- */}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            {/* User dropdown */}
 | 
			
		||||
            <div className="dropdown flex shrink-0 ">
 | 
			
		||||
              {loadingUser ? (
 | 
			
		||||
                <div className="h-9 w-9 rounded-full animate-pulse bg-gray-300 dark:bg-rtgray-800" />
 | 
			
		||||
              ) : user ? (
 | 
			
		||||
                <Dropdown
 | 
			
		||||
                                offset={[0, 8]}
 | 
			
		||||
                                placement={`${isRtl ? 'bottom-start' : 'bottom-end'}`}
 | 
			
		||||
                    placement={isRtl ? 'bottom-start' : 'bottom-end'}
 | 
			
		||||
                    btnClassName="relative group block"
 | 
			
		||||
                                button={<img className="h-9 w-9 rounded-full object-cover saturate-50 group-hover:saturate-100" src="/assets/images/user-profile.jpeg" alt="userProfile" />}
 | 
			
		||||
                            >
 | 
			
		||||
                                <ul className="w-[230px] !py-0 font-semibold text-dark dark:text-white-dark dark:text-white-light/90">
 | 
			
		||||
                                    <li>
 | 
			
		||||
                                        <div className="flex items-center px-4 py-4">
 | 
			
		||||
                                            <img className="h-10 w-10 rounded-md object-cover" src="/assets/images/user-profile.jpeg" alt="userProfile" />
 | 
			
		||||
                                            <div className="truncate ltr:pl-4 rtl:pr-4">
 | 
			
		||||
                                                <h4 className="text-base">
 | 
			
		||||
                                                    John Doe
 | 
			
		||||
                                                    <span className="rounded bg-success-light px-1 text-xs text-success ltr:ml-2 rtl:ml-2">Pro</span>
 | 
			
		||||
                                                </h4>
 | 
			
		||||
                                                <button type="button" className="text-black/60 hover:text-primary dark:text-dark-light/60 dark:hover:text-white">
 | 
			
		||||
                                                    johndoe@gmail.com
 | 
			
		||||
                                                </button>
 | 
			
		||||
                    panelClassName="rounded-lg shadow-lg border border-white/10 bg-gray-100 dark:bg-gray-800 p-2" // ✅
 | 
			
		||||
                    button={
 | 
			
		||||
                            <div className="h-9 w-9 rounded-full bg-gray-200 dark:bg-rtgray-800 flex items-center justify-center group-hover:bg-rtgray-300 dark:group-hover:bg-rtgray-700">
 | 
			
		||||
                            <IconUser className="h-5 w-5 text-gray-600 dark:text-gray-300" />
 | 
			
		||||
                            </div>
 | 
			
		||||
                        }
 | 
			
		||||
                    >
 | 
			
		||||
                    <ul className="w-[230px] font-semibold text-dark dark:text-white-dark bg-transparent"> {/* make sure this stays transparent */}
 | 
			
		||||
                    <li className="px-4 py-4 flex items-center">
 | 
			
		||||
                      <div className="truncate ltr:pl-1.5 rtl:pr-4">
 | 
			
		||||
                        <h4 className="text-sm text-left">{user.email}</h4>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li>
 | 
			
		||||
                      <Link href="/users/profile" className="dark:hover:text-white">
 | 
			
		||||
                                            <IconUser className="h-4.5 w-4.5 shrink-0 ltr:mr-2 rtl:ml-2" />
 | 
			
		||||
                                            Profile
 | 
			
		||||
                        <IconUser className="h-4.5 w-4.5 mr-2" /> Profile
 | 
			
		||||
                      </Link>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li>
 | 
			
		||||
                      <Link href="/apps/mailbox" className="dark:hover:text-white">
 | 
			
		||||
                                            <IconMail className="h-4.5 w-4.5 shrink-0 ltr:mr-2 rtl:ml-2" />
 | 
			
		||||
                                            Inbox
 | 
			
		||||
                        <IconMail className="h-4.5 w-4.5 mr-2" /> Inbox
 | 
			
		||||
                      </Link>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li>
 | 
			
		||||
                      <Link href="/auth/boxed-lockscreen" className="dark:hover:text-white">
 | 
			
		||||
                                            <IconLockDots className="h-4.5 w-4.5 shrink-0 ltr:mr-2 rtl:ml-2" />
 | 
			
		||||
                                            Lock Screen
 | 
			
		||||
                        <IconLockDots className="h-4.5 w-4.5 mr-2" /> Lock Screen
 | 
			
		||||
                      </Link>
 | 
			
		||||
                    </li>
 | 
			
		||||
                    <li className="border-t border-white-light dark:border-white-light/10">
 | 
			
		||||
                                        <Link href="/auth/boxed-signin" className="!py-3 text-danger">
 | 
			
		||||
                                            <IconLogout className="h-4.5 w-4.5 shrink-0 rotate-90 ltr:mr-2 rtl:ml-2" />
 | 
			
		||||
                                            Sign Out
 | 
			
		||||
                                        </Link>
 | 
			
		||||
                      <button onClick={handleLogout} className="flex w-full items-center py-3 text-danger">
 | 
			
		||||
                        <IconLogout className="h-4.5 w-4.5 mr-2 rotate-90" /> Sign Out
 | 
			
		||||
                      </button>
 | 
			
		||||
                    </li>
 | 
			
		||||
                  </ul>
 | 
			
		||||
                </Dropdown>
 | 
			
		||||
              ) : (
 | 
			
		||||
                <Link
 | 
			
		||||
                  href="/login"
 | 
			
		||||
                  className="rounded-md bg-yellow-400 px-3 py-1.5 text-black font-semibold hover:brightness-95"
 | 
			
		||||
                >
 | 
			
		||||
                  Sign In
 | 
			
		||||
                </Link>
 | 
			
		||||
              )}
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      </div>
 | 
			
		||||
    </header>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Header;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										5891
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5891
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -11,6 +11,7 @@
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
        "@emotion/react": "^11.10.6",
 | 
			
		||||
        "@headlessui/react": "^1.7.8",
 | 
			
		||||
        "@heroui/react": "^2.8.2",
 | 
			
		||||
        "@prisma/client": "^6.8.2",
 | 
			
		||||
        "@reduxjs/toolkit": "^1.9.1",
 | 
			
		||||
        "@tippyjs/react": "^4.2.6",
 | 
			
		||||
@ -27,6 +28,7 @@
 | 
			
		||||
        "date-fns": "^4.1.0",
 | 
			
		||||
        "eslint": "8.32.0",
 | 
			
		||||
        "eslint-config-next": "13.1.2",
 | 
			
		||||
        "framer-motion": "^12.23.12",
 | 
			
		||||
        "he": "^1.2.0",
 | 
			
		||||
        "html2canvas": "^1.4.1",
 | 
			
		||||
        "i18next": "^22.4.10",
 | 
			
		||||
 | 
			
		||||
@ -1,27 +1,46 @@
 | 
			
		||||
import { NextApiRequest, NextApiResponse } from "next";
 | 
			
		||||
import jwt from "jsonwebtoken";
 | 
			
		||||
// pages/api/auth/me.ts
 | 
			
		||||
import type { NextApiRequest, NextApiResponse } from "next";
 | 
			
		||||
import jwt, { JwtPayload } from "jsonwebtoken";
 | 
			
		||||
import { PrismaClient } from "@prisma/client";
 | 
			
		||||
 | 
			
		||||
const prisma = new PrismaClient();
 | 
			
		||||
const SECRET_KEY = process.env.JWT_SECRET as string;
 | 
			
		||||
 | 
			
		||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
 | 
			
		||||
  const authHeader = req.headers.authorization;
 | 
			
		||||
 | 
			
		||||
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
 | 
			
		||||
    return res.status(401).json({ message: "Unauthorized" });
 | 
			
		||||
function readCookieToken(req: NextApiRequest) {
 | 
			
		||||
  const cookie = req.headers.cookie || "";
 | 
			
		||||
  const match = cookie.split("; ").find((c) => c.startsWith("token="));
 | 
			
		||||
  return match?.split("=")[1];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  const token = authHeader.split(" ")[1]; // Extract token
 | 
			
		||||
function readAuthBearer(req: NextApiRequest) {
 | 
			
		||||
  const auth = req.headers.authorization;
 | 
			
		||||
  if (!auth?.startsWith("Bearer ")) return undefined;
 | 
			
		||||
  return auth.slice("Bearer ".length);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function hasEmail(payload: string | JwtPayload): payload is JwtPayload & { email: string } {
 | 
			
		||||
  return typeof payload === "object" && payload !== null && typeof (payload as any).email === "string";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
 | 
			
		||||
  if (req.method !== "GET") return res.status(405).json({ message: "Method not allowed" });
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    const decoded: any = jwt.verify(token, SECRET_KEY);
 | 
			
		||||
    const user = await prisma.user.findUnique({ where: { id: decoded.userId } });
 | 
			
		||||
    const token = readAuthBearer(req) ?? readCookieToken(req);
 | 
			
		||||
    if (!token) return res.status(401).json({ message: "Unauthorized" });
 | 
			
		||||
 | 
			
		||||
    const decoded = jwt.verify(token, SECRET_KEY);
 | 
			
		||||
    if (!hasEmail(decoded)) return res.status(401).json({ message: "Invalid token" });
 | 
			
		||||
 | 
			
		||||
    const user = await prisma.user.findUnique({
 | 
			
		||||
      where: { email: decoded.email },
 | 
			
		||||
      select: { id: true, email: true, createdAt: true },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (!user) return res.status(401).json({ message: "User not found" });
 | 
			
		||||
    return res.status(200).json({ user });
 | 
			
		||||
  } catch {
 | 
			
		||||
    return res.status(401).json({ message: "Invalid token" });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    res.json({ user });
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    res.status(401).json({ message: "Invalid token" });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
 | 
			
		||||
    const isMatch = await bcrypt.compare(password, user.password);
 | 
			
		||||
    if (!isMatch) return res.status(401).json({ message: "Invalid credentials" });
 | 
			
		||||
 | 
			
		||||
    const token = jwt.sign({ sub: user.id, email: user.email }, SECRET_KEY, { expiresIn: "1d" });
 | 
			
		||||
    const token = jwt.sign({ sub: String(user.id), email: user.email }, SECRET_KEY, { expiresIn: "1d" });
 | 
			
		||||
 | 
			
		||||
    const isProd = process.env.NODE_ENV === "production";
 | 
			
		||||
    const cookie = [
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										20
									
								
								pages/api/logout.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								pages/api/logout.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
// pages/api/auth/logout.ts
 | 
			
		||||
import type { NextApiRequest, NextApiResponse } from "next";
 | 
			
		||||
 | 
			
		||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
 | 
			
		||||
  const isProd = process.env.NODE_ENV === "production";
 | 
			
		||||
  res.setHeader(
 | 
			
		||||
    "Set-Cookie",
 | 
			
		||||
    [
 | 
			
		||||
      "token=", // empty token
 | 
			
		||||
      "HttpOnly",
 | 
			
		||||
      "Path=/",
 | 
			
		||||
      "SameSite=Strict",
 | 
			
		||||
      "Max-Age=0", // expire immediately
 | 
			
		||||
      isProd ? "Secure" : "",
 | 
			
		||||
    ]
 | 
			
		||||
      .filter(Boolean)
 | 
			
		||||
      .join("; ")
 | 
			
		||||
  );
 | 
			
		||||
  return res.status(200).json({ message: "Logged out" });
 | 
			
		||||
}
 | 
			
		||||
@ -24,7 +24,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
 | 
			
		||||
      select: { id: true, email: true, createdAt: true }, // do NOT expose password
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const token = jwt.sign({ sub: user.id, email: user.email }, SECRET_KEY, { expiresIn: "1d" });
 | 
			
		||||
    const token = jwt.sign({ sub: String(user.id), email: user.email }, SECRET_KEY, { expiresIn: "1d" });
 | 
			
		||||
 | 
			
		||||
    // Set a secure, httpOnly cookie
 | 
			
		||||
    const maxAge = 60 * 60 * 24; // 1 day
 | 
			
		||||
 | 
			
		||||
@ -449,6 +449,7 @@ hover:text-primary hover:before:!bg-primary ltr:before:mr-2 rtl:before:ml-2 dark
 | 
			
		||||
    /* dropdown */
 | 
			
		||||
    .dropdown {
 | 
			
		||||
        @apply relative;
 | 
			
		||||
        @apply z-50;
 | 
			
		||||
    }
 | 
			
		||||
    .dropdown > button {
 | 
			
		||||
        @apply flex;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user