From 51e0d4897aade69fcc73dff9a80816bd4410de87 Mon Sep 17 00:00:00 2001
From: Marshal
Date: Wed, 17 Jun 2026 17:49:16 +0000
Subject: [PATCH] update authentication pages with new layout and functionality
---
.../src/pages/payments/PaymentsPage.tsx | 77 ++++-
.../portal/src/components/auth/AuthShell.tsx | 151 ++++++++++
.../portal/src/pages/accounts/LoginPage.tsx | 259 +++++++---------
.../portal/src/pages/accounts/SignupPage.tsx | 280 ++++++++++--------
4 files changed, 490 insertions(+), 277 deletions(-)
create mode 100644 apps/edr-freight-web/portal/src/components/auth/AuthShell.tsx
diff --git a/apps/edr-freight-web/backoffice/src/pages/payments/PaymentsPage.tsx b/apps/edr-freight-web/backoffice/src/pages/payments/PaymentsPage.tsx
index 07e212b76..af92d38fd 100644
--- a/apps/edr-freight-web/backoffice/src/pages/payments/PaymentsPage.tsx
+++ b/apps/edr-freight-web/backoffice/src/pages/payments/PaymentsPage.tsx
@@ -12,9 +12,11 @@ import {
Text,
TextInput,
} from "@mantine/core";
+import { Badge as MantineBadge } from "@mantine/core";
import {
CheckCircle2,
CircleDollarSign,
+ LayoutGrid,
Loader2,
RotateCcw,
Search,
@@ -24,6 +26,7 @@ import {
} from "lucide-react";
import Breadcrumbs from "@/components/ui/Breadcrumbs";
+import "@/components/overview/overview.css";
import { usePaymentList, usePaymentSummary } from "@/hooks/usePayments";
import type {
PaymentMethod,
@@ -39,11 +42,16 @@ import {
} from "@edr/ui-common";
const STATUS_TABS = [
- { key: "all", label: "All", statuses: undefined as string | undefined },
- { key: "success", label: "Success", statuses: "success" },
- { key: "processing", label: "Processing", statuses: "processing,action-required" },
- { key: "failed", label: "Failed", statuses: "failed,canceled" },
- { key: "refunded", label: "Refunded", statuses: "refunded" },
+ { key: "all", label: "All", statuses: undefined as string | undefined, icon: LayoutGrid },
+ { key: "success", label: "Success", statuses: "success", icon: CheckCircle2 },
+ {
+ key: "processing",
+ label: "Processing",
+ statuses: "processing,action-required",
+ icon: Loader2,
+ },
+ { key: "failed", label: "Failed", statuses: "failed,canceled", icon: XCircle },
+ { key: "refunded", label: "Refunded", statuses: "refunded", icon: RotateCcw },
] as const;
type StatusTabKey = (typeof STATUS_TABS)[number]["key"];
@@ -166,6 +174,20 @@ export default function PaymentsPage() {
const val = (n?: number) => (summaryLoading ? "—" : (n ?? 0));
+ const tabCounts: Record = {
+ all:
+ summary === undefined
+ ? undefined
+ : (summary.success ?? 0) +
+ (summary.processing ?? 0) +
+ (summary.failed ?? 0) +
+ (summary.refunded ?? 0),
+ success: summary?.success,
+ processing: summary?.processing,
+ failed: summary?.failed,
+ refunded: summary?.refunded,
+ };
+
const columns: ColumnDef[] = [
{
id: "order",
@@ -274,13 +296,48 @@ export default function PaymentsPage() {
setStatusTab((value as StatusTabKey) ?? "all");
setPagination({ pageIndex: 0, pageSize: pagination.pageSize });
}}
+ variant="pills"
+ color="green"
+ keepMounted={false}
+ classNames={{ list: "ov-tablist", tab: "ov-tab" }}
>
- {STATUS_TABS.map((t) => (
-
- {t.label}
-
- ))}
+ {STATUS_TABS.map((t) => {
+ const isActive = statusTab === t.key;
+ const count = tabCounts[t.key];
+ const Icon = t.icon;
+ return (
+ }
+ rightSection={
+ count !== undefined ? (
+
+ {count}
+
+ ) : undefined
+ }
+ >
+ {t.label}
+
+ );
+ })}
diff --git a/apps/edr-freight-web/portal/src/components/auth/AuthShell.tsx b/apps/edr-freight-web/portal/src/components/auth/AuthShell.tsx
new file mode 100644
index 000000000..96d55bdd0
--- /dev/null
+++ b/apps/edr-freight-web/portal/src/components/auth/AuthShell.tsx
@@ -0,0 +1,151 @@
+import type { ReactNode } from "react";
+import { ArrowUpRight, ChevronDown, Globe } from "lucide-react";
+
+const LOGIN_IMAGE = "/assets/login.png";
+const EDR_LOGO = "/assets/logo.svg";
+
+export const fieldClass =
+ "h-11 w-full rounded-xl border border-gray-200/90 bg-white px-4 text-sm text-gray-900 shadow-sm placeholder:text-gray-400 outline-none transition-all duration-200 hover:border-gray-300 focus:border-primary focus:bg-white focus:ring-4 focus:ring-primary/10";
+
+export const primaryButtonClass =
+ "h-11 w-full rounded-full bg-primary text-sm font-semibold text-primary-foreground shadow-[0_8px_20px_-6px_rgba(16,94,52,0.5)] transition-all duration-200 hover:bg-primary/90 hover:shadow-[0_10px_24px_-6px_rgba(16,94,52,0.55)] active:scale-[0.99] disabled:cursor-not-allowed disabled:opacity-60 disabled:shadow-none";
+
+const LeftPanelDecor = () => (
+
+
+
+
+);
+
+const RightPanelDecor = () => (
+
+);
+
+export interface AuthShellProps {
+ children: ReactNode;
+ /** Tagline shown in the highlighted card over the left image panel. */
+ tagline?: string;
+ taglineBody?: string;
+}
+
+const LeftPanel = ({ tagline, taglineBody }: Pick) => (
+
+

+
+
+
+
+
+
+
+
+
+
+ {tagline ?? "Empower Your Freight Operations"}
+
+
+
+ {taglineBody ??
+ "Sign in to manage shipments, track cargo, and run logistics operations on the Ethio Djibouti Railway freight platform."}
+
+
+
+
+);
+
+const LanguageSelector = () => (
+
+
+ Eng
+
+
+);
+
+const FormFooter = () => (
+
+);
+
+export default function AuthShell({ children, tagline, taglineBody }: AuthShellProps) {
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+}
diff --git a/apps/edr-freight-web/portal/src/pages/accounts/LoginPage.tsx b/apps/edr-freight-web/portal/src/pages/accounts/LoginPage.tsx
index f8ba4ff45..9eb5a8d9b 100644
--- a/apps/edr-freight-web/portal/src/pages/accounts/LoginPage.tsx
+++ b/apps/edr-freight-web/portal/src/pages/accounts/LoginPage.tsx
@@ -1,39 +1,51 @@
-import { Alert, Box, Button, Group, PasswordInput, SegmentedControl, Stack, Text, TextInput } from "@mantine/core";
-import { ArrowRight, Mail, Phone } from "lucide-react";
-import { useState } from "react";
+import { type FormEvent, useState } from "react";
+import { ChevronDown, Eye, EyeOff, Mail, Smartphone } from "lucide-react";
import { useLocation, useNavigate } from "react-router-dom";
import useAuth from "@/hooks/useAuth";
-import AuthLayout from "@/components/auth/AuthLayout";
-import PhoneInput from "@/components/auth/PhoneInput";
+import AuthShell, { fieldClass, primaryButtonClass } from "@/components/auth/AuthShell";
+
+const EDR_LOGO = "/assets/logo.svg";
type LoginMethod = "email" | "phone";
+const loginMethods: Array<{
+ value: LoginMethod;
+ label: string;
+ icon: typeof Mail;
+ placeholder: string;
+}> = [
+ { value: "email", label: "Email", icon: Mail, placeholder: "name@company.com" },
+ { value: "phone", label: "Phone", icon: Smartphone, placeholder: "09XXXXXXXX" },
+];
+
export default function LoginPage() {
const navigate = useNavigate();
const location = useLocation();
const { login } = useAuth();
const [method, setMethod] = useState("email");
const [identifier, setIdentifier] = useState("");
- const [countryCode, setCountryCode] = useState("+251");
- const [phoneNumber, setPhoneNumber] = useState("");
+ const [countryCode] = useState("+251");
const [password, setPassword] = useState("");
+ const [showPassword, setShowPassword] = useState(false);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
+ const currentMethod = loginMethods.find((item) => item.value === method)!;
+
+ const handleSubmit = async (event: FormEvent) => {
+ event.preventDefault();
setError(null);
setLoading(true);
try {
const loginId =
method === "email"
? identifier
- : `${countryCode}${phoneNumber.startsWith("0") ? phoneNumber.slice(1) : phoneNumber}`;
+ : `${countryCode}${identifier.startsWith("0") ? identifier.slice(1) : identifier}`;
const result = await login({ email: loginId, password });
if (result.success) {
- const from = (location.state as { from?: { pathname: string } } | null)
- ?.from?.pathname;
+ const from = (location.state as { from?: { pathname: string } } | null)?.from
+ ?.pathname;
navigate(from ?? "/portal", { replace: true });
} else {
setError(result.error.message);
@@ -46,154 +58,105 @@ export default function LoginPage() {
};
return (
-
-
-
-
-
-
-
+
+
+
-
+
);
}
diff --git a/apps/edr-freight-web/portal/src/pages/accounts/SignupPage.tsx b/apps/edr-freight-web/portal/src/pages/accounts/SignupPage.tsx
index f85d11750..6de471b6d 100644
--- a/apps/edr-freight-web/portal/src/pages/accounts/SignupPage.tsx
+++ b/apps/edr-freight-web/portal/src/pages/accounts/SignupPage.tsx
@@ -1,7 +1,6 @@
-import { Alert, Box, Button, Group, PasswordInput, SimpleGrid, Stack, Text, TextInput, ThemeIcon } from "@mantine/core";
-import { zodResolver } from "@hookform/resolvers/zod";
-import { Check, ArrowRight, UserPlus, X } from "lucide-react";
import { useState } from "react";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { ArrowRight, Check, Eye, EyeOff, X } from "lucide-react";
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
import { z } from "zod";
@@ -9,8 +8,9 @@ import { z } from "zod";
import { userType } from "@/enums/userType";
import useAuth from "@/hooks/useAuth";
import type { SignupPayload } from "@/types/auth";
-import AuthLayout from "@/components/auth/AuthLayout";
-import PhoneInput from "@/components/auth/PhoneInput";
+import AuthShell, { fieldClass, primaryButtonClass } from "@/components/auth/AuthShell";
+
+const EDR_LOGO = "/assets/logo.svg";
const passwordRequirements = [
{ label: "At least 8 characters", test: (v: string) => v.length >= 8 },
@@ -44,11 +44,16 @@ const userSchema = z
type FormData = z.infer;
+const errorText = (msg?: string) =>
+ msg ? {msg}
: null;
+
export default function SignupPage() {
const navigate = useNavigate();
const { signup } = useAuth();
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
+ const [showPassword, setShowPassword] = useState(false);
+ const [showConfirm, setShowConfirm] = useState(false);
const {
register,
@@ -102,145 +107,182 @@ export default function SignupPage() {
const passwordValue = watch("password") ?? "";
return (
-
-
-
-
-
-
-
- Create Account
-
-
+
-
+
);
}