feature/syasya/testlayout #8
							
								
								
									
										11
									
								
								.env.example
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								.env.example
									
									
									
									
									
								
							@ -1,11 +1,6 @@
 | 
				
			|||||||
NEXT_PUBLIC_API_BASE_URL=http://localhost:3005
 | 
					NEXT_PUBLIC_API_BASE_URL="http://localhost:8000"
 | 
				
			||||||
 | 
					INTERNAL_API_BASE_URL="http://localhost:3005"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DATABASE_URL="postgresql://postgres:root@localhost:5432/rooftop?schema=public"
 | 
					DATABASE_URL="postgresql://postgres:root@localhost:5432/rooftop?schema=public"
 | 
				
			||||||
JWT_SECRET="secret_key"
 | 
					JWT_SECRET="secret_key"
 | 
				
			||||||
 | 
					 | 
				
			||||||
#SUNGROW
 | 
					 | 
				
			||||||
SUNGROW_SECRET_KEY=
 | 
					 | 
				
			||||||
SUNGROW_APP_KEY=
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#CHINT
 | 
					 | 
				
			||||||
NEXT_PUBLIC_CHINT_TOKEN=
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -27,8 +27,8 @@ jobs:
 | 
				
			|||||||
      - name: Build
 | 
					      - name: Build
 | 
				
			||||||
        run: npm run build
 | 
					        run: npm run build
 | 
				
			||||||
        env:
 | 
					        env:
 | 
				
			||||||
          NEXT_PUBLIC_URL: 'http://localhost:3000'
 | 
					          NEXT_PUBLIC_URL: 'http://localhost:3005'
 | 
				
			||||||
          NEXT_PUBLIC_FORECAST_URL: 'http://localhost:3001'
 | 
					          NEXT_PUBLIC_FORECAST_URL: 'http://localhost:3005'
 | 
				
			||||||
          DATABASE_URL: 'postgresql://dummy:dummy@localhost:5432/dummy'
 | 
					          DATABASE_URL: 'postgresql://dummy:dummy@localhost:5432/dummy'
 | 
				
			||||||
          SMTP_EMAIL: 'dummy@example.com'
 | 
					          SMTP_EMAIL: 'dummy@example.com'
 | 
				
			||||||
          SMTP_EMAIL_PASSWORD: 'dummy'
 | 
					          SMTP_EMAIL_PASSWORD: 'dummy'
 | 
				
			||||||
 | 
				
			|||||||
@ -59,6 +59,7 @@ const AdminDashboard = () => {
 | 
				
			|||||||
  const router = useRouter();
 | 
					  const router = useRouter();
 | 
				
			||||||
  const pathname = usePathname();
 | 
					  const pathname = usePathname();
 | 
				
			||||||
  const searchParams = useSearchParams();
 | 
					  const searchParams = useSearchParams();
 | 
				
			||||||
 | 
					  const [authChecked, setAuthChecked] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // --- load CRM projects dynamically ---
 | 
					  // --- load CRM projects dynamically ---
 | 
				
			||||||
  const [sites, setSites] = useState<CrmProject[]>([]);
 | 
					  const [sites, setSites] = useState<CrmProject[]>([]);
 | 
				
			||||||
@ -67,6 +68,30 @@ const AdminDashboard = () => {
 | 
				
			|||||||
  // near other refs
 | 
					  // near other refs
 | 
				
			||||||
  const loggingRef = useRef<HTMLDivElement | null>(null);
 | 
					  const loggingRef = useRef<HTMLDivElement | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    const checkAuth = async () => {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const res = await fetch('/api/auth/me', { credentials: 'include' });
 | 
				
			||||||
 | 
					        if (!res.ok) {
 | 
				
			||||||
 | 
					          router.replace('/login');
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const data = await res.json();
 | 
				
			||||||
 | 
					        if (!data.user) {
 | 
				
			||||||
 | 
					          router.replace('/login');
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } catch {
 | 
				
			||||||
 | 
					        router.replace('/login');
 | 
				
			||||||
 | 
					      } finally {
 | 
				
			||||||
 | 
					        setAuthChecked(true);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    checkAuth();
 | 
				
			||||||
 | 
					  }, [router]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    setSitesLoading(true);
 | 
					    setSitesLoading(true);
 | 
				
			||||||
@ -288,7 +313,7 @@ const AdminDashboard = () => {
 | 
				
			|||||||
            setHasTodayData(true);   // and today has data too
 | 
					            setHasTodayData(true);   // and today has data too
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } catch {
 | 
					  } catch {
 | 
				
			||||||
          // ignore and keep polling
 | 
					          // ignore and keep polling
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        await new Promise(r => setTimeout(r, 3000));
 | 
					        await new Promise(r => setTimeout(r, 3000));
 | 
				
			||||||
@ -300,6 +325,10 @@ const AdminDashboard = () => {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // ---------- RENDER ----------
 | 
					  // ---------- RENDER ----------
 | 
				
			||||||
 | 
					  if (!authChecked) {
 | 
				
			||||||
 | 
					    return <div>Checking authentication…</div>;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
  if (sitesLoading) {
 | 
					  if (sitesLoading) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <DashboardLayout>
 | 
					      <DashboardLayout>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,73 +1,100 @@
 | 
				
			|||||||
 | 
					// app/login/page.tsx
 | 
				
			||||||
 | 
					'use client';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import Link from 'next/link';
 | 
					import Link from 'next/link';
 | 
				
			||||||
import { Metadata } from 'next';
 | 
					import React, { useEffect, useState } from 'react';
 | 
				
			||||||
import React from 'react';
 | 
					import { useRouter } from 'next/navigation';
 | 
				
			||||||
import ComponentsAuthLoginForm from '@/components/auth/components-auth-login-form';
 | 
					import ComponentsAuthLoginForm from '@/components/auth/components-auth-login-form';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = {}
 | 
					export default function LoginPage() {
 | 
				
			||||||
 | 
					  const router = useRouter();
 | 
				
			||||||
 | 
					  const [ready, setReady] = useState(false); // gate to avoid UI flash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const LoginPage = (props: Props) => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    return (
 | 
					    let cancelled = false;
 | 
				
			||||||
        <div className="relative min-h-screen overflow-hidden bg-[#060818] text-white">
 | 
					 | 
				
			||||||
            {/* Background gradient layer */}
 | 
					 | 
				
			||||||
            <div className="absolute inset-0 -z-10">
 | 
					 | 
				
			||||||
                <img
 | 
					 | 
				
			||||||
                    src="/assets/images/auth/bg-gradient.png"
 | 
					 | 
				
			||||||
                    alt="background gradient"
 | 
					 | 
				
			||||||
                    className="h-full w-full object-cover"
 | 
					 | 
				
			||||||
                />
 | 
					 | 
				
			||||||
                <div className="absolute inset-0 bg-black/50 backdrop-blur-sm" />
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            {/* Background decorative objects */}
 | 
					    (async () => {
 | 
				
			||||||
            <img
 | 
					      try {
 | 
				
			||||||
                src="/assets/images/auth/coming-soon-object1.png"
 | 
					        const res = await fetch('/api/auth/me', {
 | 
				
			||||||
                alt="left decor"
 | 
					          method: 'GET',
 | 
				
			||||||
                className="absolute left-0 top-1/2 hidden h-full max-h-[893px] -translate-y-1/2 brightness-125 md:block"
 | 
					          cache: 'no-store',
 | 
				
			||||||
            />
 | 
					          credentials: 'include', // safe even if same-origin
 | 
				
			||||||
            <img
 | 
					        });
 | 
				
			||||||
                src="/assets/images/auth/coming-soon-object3.png"
 | 
					        if (!cancelled && res.ok) {
 | 
				
			||||||
                alt="right decor"
 | 
					          router.replace('/adminDashboard');
 | 
				
			||||||
                className="absolute right-0 top-0 hidden h-[300px] brightness-125 md:block"
 | 
					          return;
 | 
				
			||||||
            />
 | 
					        }
 | 
				
			||||||
 | 
					      } catch {
 | 
				
			||||||
 | 
					        // ignore errors; just show the form
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (!cancelled) setReady(true);
 | 
				
			||||||
 | 
					    })();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            {/* Centered card wrapper */}
 | 
					    return () => { cancelled = true; };
 | 
				
			||||||
            <div className="relative flex min-h-screen items-center justify-center px-6 py-10 sm:px-16">
 | 
					  }, [router]);
 | 
				
			||||||
                <div
 | 
					
 | 
				
			||||||
                    className="relative w-full max-w-[870px] rounded-2xl p-1
 | 
					  if (!ready) return null; // or a small spinner if you prefer
 | 
				
			||||||
                               bg-[linear-gradient(45deg,#fffbe6_0%,rgba(255,251,230,0)_25%,rgba(255,251,230,0)_75%,#fffbe6_100%)]
 | 
					
 | 
				
			||||||
                               dark:bg-[linear-gradient(52.22deg,#facc15_0%,rgba(250,204,21,0)_20%,rgba(250,204,21,0)_80%,#facc15_100%)]"
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="relative min-h-screen overflow-hidden bg-[#060818] text-white">
 | 
				
			||||||
 | 
					      {/* Background gradient layer */}
 | 
				
			||||||
 | 
					      <div className="absolute inset-0 -z-10">
 | 
				
			||||||
 | 
					        <img
 | 
				
			||||||
 | 
					          src="/assets/images/auth/bg-gradient.png"
 | 
				
			||||||
 | 
					          alt="background gradient"
 | 
				
			||||||
 | 
					          className="h-full w-full object-cover"
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <div className="absolute inset-0 bg-black/50 backdrop-blur-sm" />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {/* Background decorative objects */}
 | 
				
			||||||
 | 
					      <img
 | 
				
			||||||
 | 
					        src="/assets/images/auth/coming-soon-object1.png"
 | 
				
			||||||
 | 
					        alt="left decor"
 | 
				
			||||||
 | 
					        className="absolute left-0 top-1/2 hidden h-full max-h-[893px] -translate-y-1/2 brightness-125 md:block"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					      <img
 | 
				
			||||||
 | 
					        src="/assets/images/auth/coming-soon-object3.png"
 | 
				
			||||||
 | 
					        alt="right decor"
 | 
				
			||||||
 | 
					        className="absolute right-0 top-0 hidden h-[300px] brightness-125 md:block"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {/* Centered card wrapper */}
 | 
				
			||||||
 | 
					      <div className="relative flex min-h-screen items-center justify-center px-6 py-10 sm:px-16">
 | 
				
			||||||
 | 
					        <div
 | 
				
			||||||
 | 
					          className="relative w-full max-w-[870px] rounded-2xl p-1
 | 
				
			||||||
 | 
					                     bg-[linear-gradient(45deg,#fffbe6_0%,rgba(255,251,230,0)_25%,rgba(255,251,230,0)_75%,#fffbe6_100%)]
 | 
				
			||||||
 | 
					                     dark:bg-[linear-gradient(52.22deg,#facc15_0%,rgba(250,204,21,0)_20%,rgba(250,204,21,0)_80%,#facc15_100%)]"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          {/* Inner card (glassmorphic effect) */}
 | 
				
			||||||
 | 
					          <div className="relative z-10 rounded-2xl bg-white/10 px-8 py-16 backdrop-blur-lg dark:bg-white/10 lg:min-h-[600px]">
 | 
				
			||||||
 | 
					            <div className="mx-auto w-full max-w-[440px] text-center">
 | 
				
			||||||
 | 
					              {/* Header */}
 | 
				
			||||||
 | 
					              <h1 className="text-4xl font-extrabold uppercase tracking-wide text-yellow-400 mb-2">
 | 
				
			||||||
 | 
					                Sign In
 | 
				
			||||||
 | 
					              </h1>
 | 
				
			||||||
 | 
					              <p className="text-base font-medium text-gray-200 dark:text-gray-300 mb-8">
 | 
				
			||||||
 | 
					                Enter your email and password to access your account.
 | 
				
			||||||
 | 
					              </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              {/* Login form */}
 | 
				
			||||||
 | 
					              <ComponentsAuthLoginForm />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              {/* Footer link */}
 | 
				
			||||||
 | 
					              <div className="mt-6 text-sm text-gray-200 dark:text-gray-300">
 | 
				
			||||||
 | 
					                Don’t have an account?{' '}
 | 
				
			||||||
 | 
					                <Link
 | 
				
			||||||
 | 
					                  href="/register"
 | 
				
			||||||
 | 
					                  className="text-yellow-400 font-semibold underline transition hover:text-white"
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                    {/* Inner card (glassmorphic effect) */}
 | 
					                  SIGN UP
 | 
				
			||||||
                    <div className="relative z-10 rounded-2xl bg-white/10 px-8 py-16 backdrop-blur-lg dark:bg-white/10 lg:min-h-[600px]">
 | 
					                </Link>
 | 
				
			||||||
                        <div className="mx-auto w-full max-w-[440px] text-center">
 | 
					              </div>
 | 
				
			||||||
                            {/* Header */}
 | 
					 | 
				
			||||||
                            <h1 className="text-4xl font-extrabold uppercase tracking-wide text-yellow-400 mb-2">
 | 
					 | 
				
			||||||
                                Sign In
 | 
					 | 
				
			||||||
                            </h1>
 | 
					 | 
				
			||||||
                            <p className="text-base font-medium text-gray-200 dark:text-gray-300 mb-8">
 | 
					 | 
				
			||||||
                                Enter your email and password to access your account.
 | 
					 | 
				
			||||||
                            </p>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            {/* Login form */}
 | 
					 | 
				
			||||||
                            <ComponentsAuthLoginForm />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            {/* Footer link */}
 | 
					 | 
				
			||||||
                            <div className="mt-6 text-sm text-gray-200 dark:text-gray-300">
 | 
					 | 
				
			||||||
                                Don’t have an account?{" "}
 | 
					 | 
				
			||||||
                                <Link
 | 
					 | 
				
			||||||
                                    href="/register"
 | 
					 | 
				
			||||||
                                    className="text-yellow-400 font-semibold underline transition hover:text-white"
 | 
					 | 
				
			||||||
                                >
 | 
					 | 
				
			||||||
                                    SIGN UP
 | 
					 | 
				
			||||||
                                </Link>
 | 
					 | 
				
			||||||
                            </div>
 | 
					 | 
				
			||||||
                        </div>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    );
 | 
					      </div>
 | 
				
			||||||
};
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
export default LoginPage
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -72,7 +72,7 @@ useEffect(() => {
 | 
				
			|||||||
}, [pathname]); // re-fetch on route change (after login redirect)
 | 
					}, [pathname]); // re-fetch on route change (after login redirect)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleLogout = async () => {
 | 
					  const handleLogout = async () => {
 | 
				
			||||||
    await fetch('/api/auth/logout', { method: 'POST' });
 | 
					    await fetch('/api/logout', { method: 'POST' });
 | 
				
			||||||
    setUser(null);
 | 
					    setUser(null);
 | 
				
			||||||
    router.push('/login'); // go to login
 | 
					    router.push('/login'); // go to login
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
				
			|||||||
@ -10,10 +10,10 @@ export function middleware(req: NextRequest) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        jwt.verify(token, SECRET_KEY);
 | 
					        jwt.verify(token, SECRET_KEY);
 | 
				
			||||||
        return NextResponse.next();
 | 
					    return NextResponse.next();
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
        return NextResponse.redirect(new URL("/login", req.url));
 | 
					        return NextResponse.redirect(new URL("/login", req.url));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const config = { matcher: ["/dashboard", "/profile"] };
 | 
					export const config = { matcher: ["/dashboard", "/profile"] };
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										15
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										15
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -31,6 +31,7 @@
 | 
				
			|||||||
                "he": "^1.2.0",
 | 
					                "he": "^1.2.0",
 | 
				
			||||||
                "html2canvas": "^1.4.1",
 | 
					                "html2canvas": "^1.4.1",
 | 
				
			||||||
                "i18next": "^22.4.10",
 | 
					                "i18next": "^22.4.10",
 | 
				
			||||||
 | 
					                "jose": "^6.0.12",
 | 
				
			||||||
                "jsonwebtoken": "^9.0.2",
 | 
					                "jsonwebtoken": "^9.0.2",
 | 
				
			||||||
                "jspdf": "^3.0.1",
 | 
					                "jspdf": "^3.0.1",
 | 
				
			||||||
                "next": "14.0.3",
 | 
					                "next": "14.0.3",
 | 
				
			||||||
@ -7107,6 +7108,15 @@
 | 
				
			|||||||
                "jiti": "bin/jiti.js"
 | 
					                "jiti": "bin/jiti.js"
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "node_modules/jose": {
 | 
				
			||||||
 | 
					            "version": "6.0.12",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.12.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-T8xypXs8CpmiIi78k0E+Lk7T2zlK4zDyg+o1CZ4AkOHgDg98ogdP2BeZ61lTFKFyoEwJ9RgAgN+SdM3iPgNonQ==",
 | 
				
			||||||
 | 
					            "license": "MIT",
 | 
				
			||||||
 | 
					            "funding": {
 | 
				
			||||||
 | 
					                "url": "https://github.com/sponsors/panva"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "node_modules/js-sdsl": {
 | 
					        "node_modules/js-sdsl": {
 | 
				
			||||||
            "version": "4.2.0",
 | 
					            "version": "4.2.0",
 | 
				
			||||||
            "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
 | 
				
			||||||
@ -14693,6 +14703,11 @@
 | 
				
			|||||||
            "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz",
 | 
				
			||||||
            "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q=="
 | 
					            "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q=="
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "jose": {
 | 
				
			||||||
 | 
					            "version": "6.0.12",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.12.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-T8xypXs8CpmiIi78k0E+Lk7T2zlK4zDyg+o1CZ4AkOHgDg98ogdP2BeZ61lTFKFyoEwJ9RgAgN+SdM3iPgNonQ=="
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "js-sdsl": {
 | 
					        "js-sdsl": {
 | 
				
			||||||
            "version": "4.2.0",
 | 
					            "version": "4.2.0",
 | 
				
			||||||
            "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
 | 
				
			||||||
 | 
				
			|||||||
@ -32,6 +32,7 @@
 | 
				
			|||||||
        "he": "^1.2.0",
 | 
					        "he": "^1.2.0",
 | 
				
			||||||
        "html2canvas": "^1.4.1",
 | 
					        "html2canvas": "^1.4.1",
 | 
				
			||||||
        "i18next": "^22.4.10",
 | 
					        "i18next": "^22.4.10",
 | 
				
			||||||
 | 
					        "jose": "^6.0.12",
 | 
				
			||||||
        "jsonwebtoken": "^9.0.2",
 | 
					        "jsonwebtoken": "^9.0.2",
 | 
				
			||||||
        "jspdf": "^3.0.1",
 | 
					        "jspdf": "^3.0.1",
 | 
				
			||||||
        "next": "14.0.3",
 | 
					        "next": "14.0.3",
 | 
				
			||||||
 | 
				
			|||||||
@ -1,20 +1,22 @@
 | 
				
			|||||||
// pages/api/auth/logout.ts
 | 
					// pages/api/logout.ts  ->  /api/logout
 | 
				
			||||||
import type { NextApiRequest, NextApiResponse } from "next";
 | 
					import type { NextApiRequest, NextApiResponse } from 'next';
 | 
				
			||||||
 | 
					import { serialize } from 'cookie';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
 | 
					export default function handler(req: NextApiRequest, res: NextApiResponse) {
 | 
				
			||||||
  const isProd = process.env.NODE_ENV === "production";
 | 
					  const isProd = process.env.NODE_ENV === 'production';
 | 
				
			||||||
  res.setHeader(
 | 
					
 | 
				
			||||||
    "Set-Cookie",
 | 
					  const setCookie = serialize('token', '', {
 | 
				
			||||||
    [
 | 
					    httpOnly: true,
 | 
				
			||||||
      "token=", // empty token
 | 
					    secure: isProd,
 | 
				
			||||||
      "HttpOnly",
 | 
					    sameSite: 'strict',  // matches login
 | 
				
			||||||
      "Path=/",
 | 
					    path: '/',           // matches login
 | 
				
			||||||
      "SameSite=Strict",
 | 
					    maxAge: 0,
 | 
				
			||||||
      "Max-Age=0", // expire immediately
 | 
					    expires: new Date(0),
 | 
				
			||||||
      isProd ? "Secure" : "",
 | 
					  });
 | 
				
			||||||
    ]
 | 
					
 | 
				
			||||||
      .filter(Boolean)
 | 
					  res.setHeader('Set-Cookie', setCookie);
 | 
				
			||||||
      .join("; ")
 | 
					  res.setHeader('Cache-Control', 'no-store');
 | 
				
			||||||
  );
 | 
					  return res.status(200).json({ message: 'Logged out' });
 | 
				
			||||||
  return res.status(200).json({ message: "Logged out" });
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user