diff --git a/app/auth/login/components/LoginForm.tsx b/app/auth/login/components/LoginForm.tsx index 675df75..034068d 100644 --- a/app/auth/login/components/LoginForm.tsx +++ b/app/auth/login/components/LoginForm.tsx @@ -1,3 +1,4 @@ + "use client" import { useState, useEffect, useRef } from "react"; @@ -7,7 +8,8 @@ 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"; +import { Loader2, ArrowRight } from "lucide-react"; +import { motion } from "framer-motion"; export default function LoginForm() { const [username, setUsername] = useState(""); @@ -25,7 +27,7 @@ export default function LoginForm() { .split("; ") .find((row) => row.startsWith("Authorization=")) ?.split("=")[1]; - + if (authToken) { router.push("/dashboard"); } @@ -45,13 +47,12 @@ export default function LoginForm() { 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(); @@ -63,95 +64,106 @@ export default function LoginForm() { 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=10800`; localStorage.setItem("Authorization", data.token); - - // Show success notification - toast.success("Login successful"); - - // Set success state for animation + + toast.success("Welcome back!", { duration: 2000 }); 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 + }, 1500); } else { - // Handle HTTP error responses const errorMessage = data.error || data.message || data.details || "Invalid credentials"; - toast.error("Login Failed", { + toast.error("Access Denied", { 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.", + description: "Unable to connect to server.", }); - console.error("Login network error:", error); setIsLoading(false); } } return ( -
-
-
-

Welcome back

-

Please sign in to your account

+ +
+
+

Welcome back

+

Enter your credentials to access the dashboard

-
+
-
- +
+ setUsername(e.target.value)} - className="mt-1" + className="bg-white/5 border-white/10 text-white placeholder:text-zinc-500 focus:border-indigo-500/50 focus:ring-indigo-500/20 transition-all duration-300" + placeholder="Enter your username" disabled={isLoading || loginSuccess} />
-
- +
+
+ + + Forgot password? + +
setPassword(e.target.value)} - className="mt-1" + className="bg-white/5 border-white/10 text-white placeholder:text-zinc-500 focus:border-indigo-500/50 focus:ring-indigo-500/20 transition-all duration-300" + placeholder="Enter your password" disabled={isLoading || loginSuccess} />
- -

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

+
+

+ 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 eb97b37..f405367 100644 --- a/app/auth/login/page.tsx +++ b/app/auth/login/page.tsx @@ -2,28 +2,42 @@ import React, { Suspense, lazy } from "react"; + // Use lazy loading for the form component const LoginForm = lazy(() => import('./components/LoginForm')); -// Simple loading state for the Suspense boundary +// Background Component +const AuthBackground = () => ( +
+
+
+
+
+
+); + +// Loading State function LoginLoading() { return ( -
-
-
-
-

Loading login form...

-
+
+
+
+

Loading secure login...

); } -// Main page component that uses Suspense +// Main page component export default function LoginPage() { return ( - }> - - +
+ +
+ }> + + +
+
); } \ No newline at end of file diff --git a/app/auth/register/page.tsx b/app/auth/register/page.tsx index e61c5cd..b275b97 100644 --- a/app/auth/register/page.tsx +++ b/app/auth/register/page.tsx @@ -1,5 +1,5 @@ "use client"; -import { fetchData } from "@/lib/api"; + import { useState } from "react"; import { useRouter } from "next/navigation"; import Image from "next/image"; @@ -7,111 +7,159 @@ import Link from "next/link"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { Loader2, ArrowRight } from "lucide-react"; +import { motion } from "framer-motion"; +import { toast } from "@/hooks/use-toast"; + +// Matches LoginPage background +const AuthBackground = () => ( +
+
+
+
+
+
+); export default function RegisterPage() { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [invitationCode, setInvitationCode] = useState(""); - const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const router = useRouter(); async function handleRegister(e: React.FormEvent) { e.preventDefault(); - setError(""); setLoading(true); - const res = await fetchData( - `/api/auth/register`, - { + try { + const res = await fetch(`/api/auth/register`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password, invitationCode }), + }); + + const data = await res.json(); + + if (res.ok) { + toast({ + title: "Account Created! 🎉", + description: "Welcome to Ember Market. Redirecting to login...", + variant: "default", + }); + setTimeout(() => router.push("/auth/login"), 1500); + } else { + toast({ + title: "Registration Failed", + description: data.error || "Please check your details.", + variant: "destructive", + }); + setLoading(false); } - ); - - const data = await res; - - if (res) { - console.log("Registered successfully:", data); - router.push("/auth/login"); - } else { - setError(data.error || "Registration failed"); + } catch (error) { + toast({ + title: "Error", + description: "Something went wrong. Please try again.", + variant: "destructive", + }); + setLoading(false); } - - setLoading(false); } return ( -
-
-
-

- Create an Account -

-

- Sign up to start selling -

-
+
+ - {error &&

{error}

} +
+ +
+
+

Create your account

+

Start managing your store today

+
-
-
-
- - setUsername(e.target.value)} - className="mt-1" - /> -
-
- - setPassword(e.target.value)} - className="mt-1" - /> -
-
- - setInvitationCode(e.target.value)} - className="mt-1" - /> + +
+
+ + setUsername(e.target.value)} + className="bg-white/5 border-white/10 text-white placeholder:text-zinc-500 focus:border-indigo-500/50 focus:ring-indigo-500/20 transition-all duration-300" + placeholder="Choose a username" + disabled={loading} + /> +
+
+ + setPassword(e.target.value)} + className="bg-white/5 border-white/10 text-white placeholder:text-zinc-500 focus:border-indigo-500/50 focus:ring-indigo-500/20 transition-all duration-300" + placeholder="Create a strong password" + disabled={loading} + /> +
+
+ + setInvitationCode(e.target.value)} + className="bg-white/5 border-white/10 text-white placeholder:text-zinc-500 focus:border-indigo-500/50 focus:ring-indigo-500/20 transition-all duration-300" + placeholder="Enter your invite code" + disabled={loading} + /> +
+
+ + + + +
+

+ Already have an account?{" "} + + Sign in + +

- - - - -

- Already have an account?{" "} - - Sign in - -

+
); diff --git a/components/analytics/AnalyticsDashboard.tsx b/components/analytics/AnalyticsDashboard.tsx index d8154b8..5a3d858 100644 --- a/components/analytics/AnalyticsDashboard.tsx +++ b/components/analytics/AnalyticsDashboard.tsx @@ -46,6 +46,8 @@ import { DateRangePicker } from "@/components/ui/date-picker"; import { DateRange } from "react-day-picker"; import { addDays, startOfDay, endOfDay } from "date-fns"; import type { DateRange as ProfitDateRange } from "@/lib/services/profit-analytics-service"; +import { MotionWrapper } from "@/components/ui/motion-wrapper"; +import { motion } from "framer-motion"; // Lazy load chart components - already handled individually below @@ -195,7 +197,7 @@ export default function AnalyticsDashboard({ ]; return ( -
+
{/* Header with Privacy Toggle */}
@@ -241,197 +243,215 @@ export default function AnalyticsDashboard({
{/* Key Metrics Cards */} -
- {isLoading - ? [...Array(4)].map((_, i) => ) - : metrics.map((metric) => ( + +
+ {isLoading + ? [...Array(4)].map((_, i) => ) + : metrics.map((metric) => ( ))} -
- - {/* Completion Rate Card */} - - - - - Order Completion Rate - - - Percentage of orders that have been successfully completed - - - - {isLoading ? ( -
-
-
-
-
-
-
- ) : ( -
-
- {hideNumbers ? "**%" : `${data.orders.completionRate}%`} -
-
-
-
-
-
- - {hideNumbers - ? "** / **" - : `${data.orders.completed} / ${data.orders.total}`} - -
- )} - - - - {/* Time Period Selector */} -
-
-

Time Period

-

- Revenue, Profit, and Orders tabs use time filtering. Products and - Customers show all-time data. -

- -
- {/* Analytics Tabs */} -
- - - - - Growth - - - - Revenue - - - - Profit - - - - Products - - - - Customers - - - - Orders - - - - Predictions - - - - - }> - - - - - - }> - - - - - - {/* Date Range Selector for Profit Calculator */} - - - Date Range - - Select a custom date range for profit calculations - - - -
- - {profitDateRange?.from && profitDateRange?.to && ( -
- - {profitDateRange.from.toLocaleDateString()} -{" "} - {profitDateRange.to.toLocaleDateString()} - -
- )} + {/* Completion Rate Card */} + + + + + + Order Completion Rate + + + Percentage of orders that have been successfully completed + + + + {isLoading ? ( +
+
+
+
+
+
- - - }> - - - + ) : ( +
+
+ {hideNumbers ? "**%" : `${data.orders.completionRate}%`} +
+
+
+
+
+
+ + {hideNumbers + ? "** / **" + : `${data.orders.completed} / ${data.orders.total}`} + +
+ )} + + + - - }> - - - + {/* Time Period Selector */} +
+
+

Time Period

+

+ Revenue, Profit, and Orders tabs use time filtering. Products and + Customers show all-time data. +

+
+ +
- - }> - - - + {/* Analytics Tabs */} +
+ + + + + Growth + + + + Revenue + + + + Profit + + + + Products + + + + Customers + + + + Orders + + + + Predictions + + - - }> - - - + + + }> + + + + - - }> - - - - -
+ + + }> + + + + + + + + {/* Date Range Selector for Profit Calculator */} + + + Date Range + + Select a custom date range for profit calculations + + + +
+ + {profitDateRange?.from && profitDateRange?.to && ( +
+ + {profitDateRange.from.toLocaleDateString()} -{" "} + {profitDateRange.to.toLocaleDateString()} + +
+ )} +
+
+
+ }> + + +
+
+ + + + }> + + + + + + + + }> + + + + + + + + }> + + + + + + + + }> + + + + + +
+
); } diff --git a/components/analytics/MetricsCard.tsx b/components/analytics/MetricsCard.tsx index a2c4f6b..933883a 100644 --- a/components/analytics/MetricsCard.tsx +++ b/components/analytics/MetricsCard.tsx @@ -3,6 +3,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { TrendingUp, TrendingDown, Minus } from "lucide-react"; import { LucideIcon } from "lucide-react"; +import { motion } from "framer-motion"; interface MetricsCardProps { title: string; @@ -13,13 +14,13 @@ interface MetricsCardProps { trendValue: string; } -export default function MetricsCard({ - title, - value, - description, - icon: Icon, - trend, - trendValue +export default function MetricsCard({ + title, + value, + description, + icon: Icon, + trend, + trendValue }: MetricsCardProps) { const getTrendIcon = () => { switch (trend) { @@ -44,23 +45,25 @@ export default function MetricsCard({ }; return ( - - - - {title} - - - - -
{value}
-

{description}

-
- {getTrendIcon()} - - {trendValue} - -
-
-
+ + + + + {title} + + + + +
{value}
+

{description}

+
+ {getTrendIcon()} + + {trendValue} + +
+
+
+
); -} \ No newline at end of file +} \ No newline at end of file diff --git a/components/ui/motion-wrapper.tsx b/components/ui/motion-wrapper.tsx index a304739..bfd0b78 100644 --- a/components/ui/motion-wrapper.tsx +++ b/components/ui/motion-wrapper.tsx @@ -1,15 +1,30 @@ "use client"; -import { motion } from "framer-motion"; +import { motion, HTMLMotionProps } from "framer-motion"; +import { cn } from "@/lib/utils"; +import { forwardRef } from "react"; -export function MotionWrapper({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ); +interface MotionWrapperProps extends HTMLMotionProps<"div"> { + children: React.ReactNode; + className?: string; } + +export const MotionWrapper = forwardRef( + ({ children, className, ...props }, ref) => { + return ( + + {children} + + ); + } +); + +MotionWrapper.displayName = "MotionWrapper"; diff --git a/public/git-info.json b/public/git-info.json index bffac89..5a0a401 100644 --- a/public/git-info.json +++ b/public/git-info.json @@ -1,4 +1,4 @@ { - "commitHash": "624bfa5", - "buildTime": "2026-01-12T02:42:11.944Z" + "commitHash": "02ba4b0", + "buildTime": "2026-01-12T03:57:23.436Z" } \ No newline at end of file