This commit is contained in:
NotII
2025-03-23 22:52:47 +00:00
parent 290bba7f05
commit 4d8e199ab6
6 changed files with 177 additions and 135 deletions

View File

@@ -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 (
<div className="flex items-center justify-center min-h-screen bg-gray-100 dark:bg-[#0F0F12]">
<div className="w-full max-w-md p-8 space-y-8 bg-white dark:bg-[#1F1F23] rounded-xl shadow-lg">
<div className="text-center">
<h2 className="mt-6 text-3xl font-bold text-gray-900 dark:text-white">Welcome back</h2>
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">Please sign in to your account</p>
</div>
<form className="mt-8 space-y-6" onSubmit={handleLogin}>
<div className="space-y-4">
<div>
<Label htmlFor="username">Username</Label>
<Input
id="username"
name="username"
type="text"
autoComplete="username"
required
value={username}
onChange={(e) => setUsername(e.target.value)}
className="mt-1"
/>
</div>
<div>
<Label htmlFor="password">Password</Label>
<Input
id="password"
name="password"
type="password"
autoComplete="current-password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
className="mt-1"
/>
</div>
</div>
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? "Signing in..." : "Sign in"}
</Button>
</form>
<p className="mt-10 text-sm text-center text-gray-600 dark:text-gray-400">
Don't have an account?{" "}
<Link href="/auth/register" className="text-blue-600 hover:underline dark:text-blue-400">
Sign up
</Link>
</p>
</div>
</div>
);
}

View File

@@ -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 (
<div className="flex items-center justify-center min-h-screen bg-gray-100 dark:bg-[#0F0F12]">
<div className="w-full max-w-md p-8 space-y-8 bg-white dark:bg-[#1F1F23] rounded-xl shadow-lg">
<div className="text-center">
<h2 className="mt-6 text-3xl font-bold text-gray-900 dark:text-white">Welcome back</h2>
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">Please sign in to your account</p>
</div>
<form className="mt-8 space-y-6" onSubmit={handleLogin}>
<div className="space-y-4">
<div>
<Label htmlFor="username">Username</Label>
<Input
id="username"
name="username"
type="text"
autoComplete="username"
required
value={username}
onChange={(e) => setUsername(e.target.value)}
className="mt-1"
/>
</div>
<div>
<Label htmlFor="password">Password</Label>
<Input
id="password"
name="password"
type="password"
autoComplete="current-password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
className="mt-1"
/>
</div>
</div>
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? "Signing in..." : "Sign in"}
</Button>
</form>
<p className="mt-10 text-sm text-center text-gray-600 dark:text-gray-400">
Don't have an account?{" "}
<Link href="/auth/register" className="text-blue-600 hover:underline dark:text-blue-400">
Sign up
</Link>
</p>
</div>
</div>
);
}
// Use lazy loading for the form component
const LoginForm = lazy(() => import('./components/LoginForm'));
// Simple loading state for the Suspense boundary
function LoginLoading() {

View File

@@ -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

View File

@@ -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

20
package-lock.json generated
View File

@@ -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",

View File

@@ -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",