diff --git a/app/auth/login/components/LoginForm.tsx b/app/auth/login/components/LoginForm.tsx index 5fee7f5..1cd4894 100644 --- a/app/auth/login/components/LoginForm.tsx +++ b/app/auth/login/components/LoginForm.tsx @@ -1,17 +1,20 @@ "use client" -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef } 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"; +import { Loader2 } from "lucide-react"; export default function LoginForm() { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [isLoading, setIsLoading] = useState(false); + const [loginSuccess, setLoginSuccess] = useState(false); + const redirectTimeoutRef = useRef(null); const router = useRouter(); const searchParams = useSearchParams(); const redirectUrl = searchParams.get("redirectUrl") || "/dashboard"; @@ -28,6 +31,15 @@ export default function LoginForm() { } }, [router]); + // Cleanup timeout on unmount + useEffect(() => { + return () => { + if (redirectTimeoutRef.current) { + clearTimeout(redirectTimeoutRef.current); + } + }; + }, []); + async function handleLogin(e: React.FormEvent) { e.preventDefault(); setIsLoading(true); @@ -57,10 +69,19 @@ export default function LoginForm() { document.cookie = `Authorization=${data.token}; path=/; Secure; SameSite=Strict; max-age=604800`; localStorage.setItem("Authorization", data.token); + // Show success notification toast.success("Login successful"); - // Redirect to dashboard or the original redirect URL + // Set success state for animation + setLoginSuccess(true); + + // Try Next.js router navigation router.push(redirectUrl); + + // Set up a fallback manual redirect if Next.js navigation doesn't work + redirectTimeoutRef.current = setTimeout(() => { + window.location.href = redirectUrl; + }, 1500); // Wait 1.5 seconds before trying manual redirect } else { // Handle HTTP error responses const errorMessage = data.error || data.message || data.details || "Invalid credentials"; @@ -68,20 +89,20 @@ export default function LoginForm() { description: errorMessage, }); console.error("Login error response:", { status: response.status, data }); + setIsLoading(false); } } 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

@@ -89,7 +110,7 @@ export default function LoginForm() {
-
+
setUsername(e.target.value)} className="mt-1" + disabled={isLoading || loginSuccess} />
-
+
setPassword(e.target.value)} className="mt-1" + disabled={isLoading || loginSuccess} />
- -

+

Don't have an account?{" "} Sign up diff --git a/app/dashboard/chats/[id]/loading.tsx b/app/dashboard/chats/[id]/loading.tsx new file mode 100644 index 0000000..6d48584 --- /dev/null +++ b/app/dashboard/chats/[id]/loading.tsx @@ -0,0 +1,100 @@ +import Layout from "@/components/layout/layout"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Loader2 } from "lucide-react"; + +export default function ChatDetailLoading() { + return ( + +

+ {/* Header */} +
+
+ + +
+ +
+ + {/* Chat window */} +
+ {/* Chat messages area */} +
+
+
+ +

Loading messages...

+
+
+ +
+ {/* Customer messages */} +
+
+
+ + +
+
+ +
+
+
+ + {/* Vendor messages */} +
+
+
+ + +
+
+ + +
+
+
+ + {/* Customer messages */} +
+
+
+ + +
+
+ + +
+
+
+ + {/* Vendor messages */} +
+
+
+ + +
+
+ + + +
+
+
+
+
+ + {/* Chat input area */} +
+
+ + +
+
+
+
+ + ); +} \ No newline at end of file diff --git a/app/dashboard/chats/loading.tsx b/app/dashboard/chats/loading.tsx new file mode 100644 index 0000000..a98b878 --- /dev/null +++ b/app/dashboard/chats/loading.tsx @@ -0,0 +1,15 @@ +import Layout from "@/components/layout/layout"; +import PageLoading from "@/components/dashboard/page-loading"; + +export default function ChatsLoading() { + return ( + + + + ); +} \ No newline at end of file diff --git a/app/dashboard/loading.tsx b/app/dashboard/loading.tsx new file mode 100644 index 0000000..2f28d14 --- /dev/null +++ b/app/dashboard/loading.tsx @@ -0,0 +1,78 @@ +"use client" + +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Skeleton } from "@/components/ui/skeleton"; +import Layout from "@/components/layout/layout"; +import { Loader2 } from "lucide-react"; + +export default function DashboardLoading() { + return ( + +
+ {/* Header skeleton with greeting & quote */} +
+ + +
+ + {/* Order statistics skeletons */} +
+ {[...Array(4)].map((_, i) => ( + + + + + +
+ + + + +
+
+
+ ))} +
+ + {/* Best selling products skeleton */} + +
+
+ +

Loading dashboard data...

+
+
+ + +
+ + + + + + +
+
+ + +
+ {[...Array(5)].map((_, i) => ( +
+ +
+ + +
+
+ + +
+
+ ))} +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/dashboard/orders/[id]/loading.tsx b/app/dashboard/orders/[id]/loading.tsx new file mode 100644 index 0000000..ebf4d80 --- /dev/null +++ b/app/dashboard/orders/[id]/loading.tsx @@ -0,0 +1,105 @@ +import Layout from "@/components/layout/layout"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Skeleton } from "@/components/ui/skeleton"; +import { Loader2 } from "lucide-react"; + +export default function OrderDetailLoading() { + return ( + +
+ {/* Header */} +
+
+ + +
+ +
+ +
+ {/* Order Info Card */} + + + + + + + + {[...Array(6)].map((_, i) => ( +
+ + +
+ ))} +
+
+ + {/* Customer Info Card */} + + + + + + + + {[...Array(4)].map((_, i) => ( +
+ + +
+ ))} +
+
+ + {/* Order Items Card */} + +
+
+ +

Loading order details...

+
+
+ + + + + + + +
+ {[...Array(3)].map((_, i) => ( +
+ +
+ + +
+
+ + +
+
+ ))} +
+ +
+
+ {[...Array(3)].map((_, i) => ( +
+ + +
+ ))} +
+ + +
+
+
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/dashboard/orders/loading.tsx b/app/dashboard/orders/loading.tsx new file mode 100644 index 0000000..63532d6 --- /dev/null +++ b/app/dashboard/orders/loading.tsx @@ -0,0 +1,15 @@ +import Layout from "@/components/layout/layout"; +import PageLoading from "@/components/dashboard/page-loading"; + +export default function OrdersLoading() { + return ( + + + + ); +} \ No newline at end of file diff --git a/app/dashboard/products/loading.tsx b/app/dashboard/products/loading.tsx new file mode 100644 index 0000000..ee033d9 --- /dev/null +++ b/app/dashboard/products/loading.tsx @@ -0,0 +1,15 @@ +import Layout from "@/components/layout/layout"; +import PageLoading from "@/components/dashboard/page-loading"; + +export default function ProductsLoading() { + return ( + + + + ); +} \ No newline at end of file diff --git a/components/dashboard/page-loading.tsx b/components/dashboard/page-loading.tsx new file mode 100644 index 0000000..35f94b9 --- /dev/null +++ b/components/dashboard/page-loading.tsx @@ -0,0 +1,114 @@ +"use client" + +import { Skeleton } from "@/components/ui/skeleton"; +import { Card } from "@/components/ui/card"; +import { Loader2 } from "lucide-react"; + +interface PageLoadingProps { + title?: string; + subtitle?: string; + itemsCount?: number; + layout?: 'list' | 'grid' | 'table'; +} + +export default function PageLoading({ + title = "Loading data...", + subtitle, + itemsCount = 5, + layout = 'list' +}: PageLoadingProps) { + return ( +
+ {/* Header skeleton */} +
+
+ + {subtitle && } +
+ +
+ + {/* Main content skeleton */} + +
+
+ +

{title}

+ {subtitle &&

{subtitle}

} +
+
+ + {layout === 'list' && ( +
+ {[...Array(itemsCount)].map((_, i) => ( +
+ +
+ + +
+
+ + +
+
+ ))} +
+ )} + + {layout === 'grid' && ( +
+ {[...Array(itemsCount)].map((_, i) => ( + + + + +
+ + +
+
+ ))} +
+ )} + + {layout === 'table' && ( +
+
+
+ + + + +
+
+ +
+ {[...Array(itemsCount)].map((_, i) => ( +
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+
+ ))} +
+
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/lib/api-utils.ts b/lib/api-utils.ts index 4ee689d..6a3ac4c 100644 --- a/lib/api-utils.ts +++ b/lib/api-utils.ts @@ -1,4 +1,5 @@ /** +<<<<<<< Updated upstream * API utilities for consistent request handling */ @@ -33,17 +34,63 @@ export function getServerApiUrl(endpoint: string): string { return apiUrl.endsWith('/') ? `${apiUrl}${cleanEndpoint}` : `${apiUrl}/${cleanEndpoint}`; +======= + * API utilities for client and server-side requests + */ + +/** + * Normalizes the API URL to ensure it uses the proper prefix + * For client-side, ensures all requests go through the Next.js API proxy + */ +export function normalizeApiUrl(url: string): string { + // If URL already starts with http or https, return as is + if (url.startsWith('http://') || url.startsWith('https://')) { + return url; + } + + // If URL already starts with /api, use as is + if (url.startsWith('/api/')) { + return url; + } + + // Otherwise, ensure it has the /api prefix + return `/api${url.startsWith('/') ? '' : '/'}${url}`; +} + +/** + * Get the server API URL for server-side requests + */ +export function getServerApiUrl(endpoint: string): string { + // Get the base API URL from environment + const baseUrl = process.env.SERVER_API_URL || process.env.NEXT_PUBLIC_API_URL || 'https://internal-api.inboxi.ng/api'; + + // Ensure it doesn't have trailing slash + const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; + + // Ensure endpoint has leading slash + const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; + + return `${normalizedBaseUrl}${normalizedEndpoint}`; +>>>>>>> Stashed changes } /** * Get the authentication token from cookies or localStorage +<<<<<<< Updated upstream */ export function getAuthToken(): string | null { if (typeof document === 'undefined') return null; // Guard for SSR +======= + * Only available in client-side code + */ +export function getAuthToken(): string | null { + if (typeof document === 'undefined') return null; +>>>>>>> Stashed changes return document.cookie .split('; ') .find(row => row.startsWith('Authorization=')) +<<<<<<< Updated upstream ?.split('=')[1] || localStorage.getItem('Authorization'); } @@ -72,5 +119,27 @@ export function createApiHeaders(token?: string | null, customHeaders: Record = {}): Headers { + const headers = new Headers({ + 'Content-Type': 'application/json', + ...additionalHeaders + }); + + // Use provided token or try to get it from storage + const authToken = token || getAuthToken(); + + if (authToken) { + headers.append('Authorization', `Bearer ${authToken}`); + } + +>>>>>>> Stashed changes return headers; } \ No newline at end of file diff --git a/lib/client-utils.ts b/lib/client-utils.ts index 59a0676..fccbe20 100644 --- a/lib/client-utils.ts +++ b/lib/client-utils.ts @@ -21,12 +21,6 @@ export async function clientFetch(url: string, options: RequestInit = { if (!res.ok) { const errorData = await res.json().catch(() => ({})); const errorMessage = errorData.message || errorData.error || `Request failed: ${res.status} ${res.statusText}`; - console.error('API Error:', { - status: res.status, - url: fullUrl, - response: errorData, - method: options.method || 'GET' - }); throw new Error(errorMessage); } diff --git a/next.config.mjs b/next.config.mjs index 29ae3a9..e28e8c9 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -24,6 +24,7 @@ const nextConfig = { }, ]; }, +<<<<<<< Updated upstream // Build optimization settings for slower CPUs experimental: { swcMinify: true, @@ -31,6 +32,8 @@ const nextConfig = { logLevel: 'error' } }, +======= +>>>>>>> Stashed changes // Reduce memory usage during builds onDemandEntries: { // Period (in ms) where the server will keep pages in the buffer