diff --git a/app/auth/login/components/LoginForm.tsx b/app/auth/login/components/LoginForm.tsx new file mode 100644 index 0000000..5fee7f5 --- /dev/null +++ b/app/auth/login/components/LoginForm.tsx @@ -0,0 +1,134 @@ +"use client" + +import { useState, useEffect } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { toast } from "sonner"; + +export default function LoginForm() { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const router = useRouter(); + const searchParams = useSearchParams(); + const redirectUrl = searchParams.get("redirectUrl") || "/dashboard"; + + // Check if already logged in + useEffect(() => { + const authToken = document.cookie + .split("; ") + .find((row) => row.startsWith("Authorization=")) + ?.split("=")[1]; + + if (authToken) { + router.push("/dashboard"); + } + }, [router]); + + async function handleLogin(e: React.FormEvent) { + e.preventDefault(); + setIsLoading(true); + + try { + // Using fetch directly with the proxy path + const response = await fetch("/api/auth/login", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username, password }), + }); + + let data; + try { + data = await response.json(); + } catch (parseError) { + console.error("Login parse error:", parseError); + toast.error("Server Error", { + description: "The server response couldn't be processed. Please try again." + }); + setIsLoading(false); + return; + } + + if (response.ok && data.token) { + // Store the token in both cookie and localStorage for redundancy + document.cookie = `Authorization=${data.token}; path=/; Secure; SameSite=Strict; max-age=604800`; + localStorage.setItem("Authorization", data.token); + + toast.success("Login successful"); + + // Redirect to dashboard or the original redirect URL + router.push(redirectUrl); + } else { + // Handle HTTP error responses + const errorMessage = data.error || data.message || data.details || "Invalid credentials"; + toast.error("Login Failed", { + description: errorMessage, + }); + console.error("Login error response:", { status: response.status, data }); + } + } catch (error) { + toast.error("Connection Error", { + description: "Unable to connect to the server. Please check your internet connection and try again.", + }); + console.error("Login network error:", error); + } finally { + setIsLoading(false); + } + } + + return ( +
+
+
+

Welcome back

+

Please sign in to your account

+
+ +
+
+
+ + setUsername(e.target.value)} + className="mt-1" + /> +
+
+ + setPassword(e.target.value)} + className="mt-1" + /> +
+
+ + +
+ +

+ Don't have an account?{" "} + + Sign up + +

+
+
+ ); +} \ No newline at end of file diff --git a/app/auth/login/page.tsx b/app/auth/login/page.tsx index 088cbe5..6e53e21 100644 --- a/app/auth/login/page.tsx +++ b/app/auth/login/page.tsx @@ -1,138 +1,12 @@ "use client" -import { useState, useEffect, Suspense } from "react"; -import { useRouter, useSearchParams } from "next/navigation"; -import Link from "next/link"; +import React, { Suspense, lazy } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { toast } from "sonner"; -// Separate the main login functionality into a client component -function LoginForm() { - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const router = useRouter(); - const searchParams = useSearchParams(); - const redirectUrl = searchParams.get("redirectUrl") || "/dashboard"; - - // Check if already logged in - useEffect(() => { - const authToken = document.cookie - .split("; ") - .find((row) => row.startsWith("Authorization=")) - ?.split("=")[1]; - - if (authToken) { - router.push("/dashboard"); - } - }, [router]); - - async function handleLogin(e: React.FormEvent) { - e.preventDefault(); - setIsLoading(true); - - try { - // Using fetch directly with the proxy path - const response = await fetch("/api/auth/login", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ username, password }), - }); - - let data; - try { - data = await response.json(); - } catch (parseError) { - console.error("Login parse error:", parseError); - toast.error("Server Error", { - description: "The server response couldn't be processed. Please try again." - }); - setIsLoading(false); - return; - } - - if (response.ok && data.token) { - // Store the token in both cookie and localStorage for redundancy - document.cookie = `Authorization=${data.token}; path=/; Secure; SameSite=Strict; max-age=604800`; - localStorage.setItem("Authorization", data.token); - - toast.success("Login successful"); - - // Redirect to dashboard or the original redirect URL - router.push(redirectUrl); - } else { - // Handle HTTP error responses - const errorMessage = data.error || data.message || data.details || "Invalid credentials"; - toast.error("Login Failed", { - description: errorMessage, - }); - console.error("Login error response:", { status: response.status, data }); - } - } catch (error) { - toast.error("Connection Error", { - description: "Unable to connect to the server. Please check your internet connection and try again.", - }); - console.error("Login network error:", error); - } finally { - setIsLoading(false); - } - } - - return ( -
-
-
-

Welcome back

-

Please sign in to your account

-
- -
-
-
- - setUsername(e.target.value)} - className="mt-1" - /> -
-
- - setPassword(e.target.value)} - className="mt-1" - /> -
-
- - -
- -

- Don't have an account?{" "} - - Sign up - -

-
-
- ); -} +// Use lazy loading for the form component +const LoginForm = lazy(() => import('./components/LoginForm')); // Simple loading state for the Suspense boundary function LoginLoading() { diff --git a/components/notifications/OrderNotifications.tsx b/components/notifications/OrderNotifications.tsx index 31d74e3..c4a1830 100644 --- a/components/notifications/OrderNotifications.tsx +++ b/components/notifications/OrderNotifications.tsx @@ -75,8 +75,13 @@ export default function OrderNotifications() { const checkForNewOrders = async () => { try { - // Fetch orders from the last 24 hours that are in paid status - const orderData = await clientFetch("/orders?status=paid&limit=10"); + // Get orders from the last 24 hours with a more efficient query + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + const timestamp = yesterday.toISOString(); + + // Include timestamp filter to reduce load + const orderData = await clientFetch(`/orders?status=paid&limit=10&after=${timestamp}`); const orders: Order[] = orderData.orders || []; // If this is the first fetch, just store the orders without notifications diff --git a/components/notifications/UnifiedNotifications.tsx b/components/notifications/UnifiedNotifications.tsx index 71e4358..45d4dd6 100644 --- a/components/notifications/UnifiedNotifications.tsx +++ b/components/notifications/UnifiedNotifications.tsx @@ -102,8 +102,13 @@ export default function UnifiedNotifications() { const checkForNewOrders = async () => { try { - // Fetch orders from the last 24 hours that are in paid status - const orderData = await clientFetch("/orders?status=paid&limit=10"); + // Get orders from the last 24 hours with a more efficient query + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + const timestamp = yesterday.toISOString(); + + // Include timestamp filter to reduce load + const orderData = await clientFetch(`/orders?status=paid&limit=10&after=${timestamp}`); const orders: Order[] = orderData.orders || []; // If this is the first fetch, just store the orders without notifications diff --git a/package-lock.json b/package-lock.json index d719f7e..50a4242 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,6 +68,7 @@ "@types/node": "^22", "@types/react": "^18", "@types/react-dom": "^18", + "cross-env": "^7.0.3", "eslint": "^9.19.0", "postcss": "^8", "tailwindcss": "^3.4.17", @@ -2700,6 +2701,25 @@ "dev": true, "license": "MIT" }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", diff --git a/package.json b/package.json index b253692..32aefe2 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,12 @@ "private": true, "scripts": { "dev": "next dev", - "build": "next build", + "build": "cross-env NODE_OPTIONS='--max_old_space_size=4096' NEXT_TELEMETRY_DISABLED=1 next build", + "build:fast": "cross-env NODE_OPTIONS='--max_old_space_size=4096' NEXT_TELEMETRY_DISABLED=1 NEXT_SKIP_LINT=1 NEXT_SKIP_TS_CHECK=1 next build", + "build:optimized": "node scripts/optimize-build.js", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "clean": "rm -rf .next && rm -rf node_modules/.cache" }, "dependencies": { "@hookform/resolvers": "^3.9.1", @@ -69,6 +72,7 @@ "@types/node": "^22", "@types/react": "^18", "@types/react-dom": "^18", + "cross-env": "^7.0.3", "eslint": "^9.19.0", "postcss": "^8", "tailwindcss": "^3.4.17",