hmm
This commit is contained in:
134
app/auth/login/components/LoginForm.tsx
Normal file
134
app/auth/login/components/LoginForm.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,138 +1,12 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState, useEffect, Suspense } from "react";
|
import React, { Suspense, lazy } from "react";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
// Separate the main login functionality into a client component
|
// Use lazy loading for the form component
|
||||||
function LoginForm() {
|
const LoginForm = lazy(() => import('./components/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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple loading state for the Suspense boundary
|
// Simple loading state for the Suspense boundary
|
||||||
function LoginLoading() {
|
function LoginLoading() {
|
||||||
|
|||||||
@@ -75,8 +75,13 @@ export default function OrderNotifications() {
|
|||||||
|
|
||||||
const checkForNewOrders = async () => {
|
const checkForNewOrders = async () => {
|
||||||
try {
|
try {
|
||||||
// Fetch orders from the last 24 hours that are in paid status
|
// Get orders from the last 24 hours with a more efficient query
|
||||||
const orderData = await clientFetch("/orders?status=paid&limit=10");
|
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 || [];
|
const orders: Order[] = orderData.orders || [];
|
||||||
|
|
||||||
// If this is the first fetch, just store the orders without notifications
|
// If this is the first fetch, just store the orders without notifications
|
||||||
|
|||||||
@@ -102,8 +102,13 @@ export default function UnifiedNotifications() {
|
|||||||
|
|
||||||
const checkForNewOrders = async () => {
|
const checkForNewOrders = async () => {
|
||||||
try {
|
try {
|
||||||
// Fetch orders from the last 24 hours that are in paid status
|
// Get orders from the last 24 hours with a more efficient query
|
||||||
const orderData = await clientFetch("/orders?status=paid&limit=10");
|
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 || [];
|
const orders: Order[] = orderData.orders || [];
|
||||||
|
|
||||||
// If this is the first fetch, just store the orders without notifications
|
// If this is the first fetch, just store the orders without notifications
|
||||||
|
|||||||
20
package-lock.json
generated
20
package-lock.json
generated
@@ -68,6 +68,7 @@
|
|||||||
"@types/node": "^22",
|
"@types/node": "^22",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^9.19.0",
|
"eslint": "^9.19.0",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
@@ -2700,6 +2701,25 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
|
|||||||
@@ -4,9 +4,12 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"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",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint",
|
||||||
|
"clean": "rm -rf .next && rm -rf node_modules/.cache"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.9.1",
|
"@hookform/resolvers": "^3.9.1",
|
||||||
@@ -69,6 +72,7 @@
|
|||||||
"@types/node": "^22",
|
"@types/node": "^22",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^9.19.0",
|
"eslint": "^9.19.0",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
|
|||||||
Reference in New Issue
Block a user