diff --git a/apps/edr-freight-web/backoffice/public/assets/edr_image.jpg b/apps/edr-freight-web/backoffice/public/assets/edr_image.jpg new file mode 100644 index 000000000..b89941eea Binary files /dev/null and b/apps/edr-freight-web/backoffice/public/assets/edr_image.jpg differ diff --git a/apps/edr-freight-web/backoffice/public/assets/edr_image.png b/apps/edr-freight-web/backoffice/public/assets/edr_image.png new file mode 100644 index 000000000..1654c747c Binary files /dev/null and b/apps/edr-freight-web/backoffice/public/assets/edr_image.png differ diff --git a/apps/edr-freight-web/backoffice/src/components/auth/AuthShell.tsx b/apps/edr-freight-web/backoffice/src/components/auth/AuthShell.tsx index ecf06c7c9..c4fd7f39e 100644 --- a/apps/edr-freight-web/backoffice/src/components/auth/AuthShell.tsx +++ b/apps/edr-freight-web/backoffice/src/components/auth/AuthShell.tsx @@ -1,32 +1,86 @@ import type { ReactNode } from "react"; -import { ArrowUpRight, ChevronDown, Globe } from "lucide-react"; +import { Box, Image, Stack, Text, Title } from "@mantine/core"; +import { ChevronDown, Globe } from "lucide-react"; -const LOGIN_IMAGE = "/assets/login.png"; +const EDR_IMAGE = "/assets/edr_image.png"; const EDR_LOGO = "/assets/logo.svg"; -const LeftPanelDecor = () => ( -
) => ( + - - {[0, 1, 2, 3, 4, 5].map((ring) => ( - - ))} - -
-
+ EDR Freight + + + + {tagline ?? "Ethiopian Djibouti Railway"} + + + {taglineBody ?? + "Manage bookings, track cargo, and run day-to-day logistics for the Ethio–Djibouti Railway from a single backoffice."} + + + + + {/* Bottom-right: brand image with a center-to-edge opacity fade, no color tint. */} + +
); const RightPanelDecor = () => ( @@ -55,51 +109,6 @@ 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) => ( -
- Ethio Djibouti Railway -
- - -
- EDR Freight -
- -
-
-
-
- - {tagline ?? "Empower Your Freight Operations"} - -
-

- {taglineBody ?? - "Sign in to manage bookings, track cargo, and run logistics operations on the Ethio Djibouti Railway freight platform."} -

-
-
-
-); - const LanguageSelector = () => (
@@ -126,7 +135,7 @@ export default function AuthShell({
-
+
{children}
diff --git a/apps/edr-freight-web/portal/public/assets/edr_image.jpg b/apps/edr-freight-web/portal/public/assets/edr_image.jpg new file mode 100644 index 000000000..b89941eea Binary files /dev/null and b/apps/edr-freight-web/portal/public/assets/edr_image.jpg differ diff --git a/apps/edr-freight-web/portal/public/assets/edr_image.png b/apps/edr-freight-web/portal/public/assets/edr_image.png new file mode 100644 index 000000000..1654c747c Binary files /dev/null and b/apps/edr-freight-web/portal/public/assets/edr_image.png differ diff --git a/apps/edr-freight-web/portal/src/components/auth/AuthShell.tsx b/apps/edr-freight-web/portal/src/components/auth/AuthShell.tsx index 5a4adf587..439f41d19 100644 --- a/apps/edr-freight-web/portal/src/components/auth/AuthShell.tsx +++ b/apps/edr-freight-web/portal/src/components/auth/AuthShell.tsx @@ -1,40 +1,25 @@ import type { ReactNode } from "react"; -import { ArrowUpRight, ChevronDown, Globe } from "lucide-react"; +import { Box, Image, Stack, Text, Title } from "@mantine/core"; +import { ChevronDown, Globe } from "lucide-react"; +import { Link } from "react-router-dom"; -const LOGIN_IMAGE = "/assets/login.png"; +const EDR_IMAGE = "/assets/edr_image.png"; const EDR_LOGO = "/assets/logo.svg"; +/** Muted deep-green brand wash for the left panel. */ +const LEFT_PANEL_BG = + "linear-gradient(158deg, #2E6B55 0%, #21503F 46%, #16352A 100%)"; + +/** Radial opacity mask: image fully opaque at its center, fading to nothing at the edges. */ +const IMAGE_FADE_MASK = + "linear-gradient(-90deg, #000 95%, #0009 97%, #0000 100%), linear-gradient(0deg, #000 80%, #0001 100%)"; + 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 = () => ( -
- - {[0, 1, 2, 3, 4, 5].map((ring) => ( - - ))} - -
-
-); - const RightPanelDecor = () => (
( export interface AuthShellProps { children: ReactNode; - /** Tagline shown in the highlighted card over the left image panel. */ + /** Headline shown in the top-left of the green panel. */ tagline?: string; taglineBody?: string; } @@ -72,45 +57,63 @@ const LeftPanel = ({ tagline, taglineBody, }: Pick) => ( -
- Ethio Djibouti Railway -
- - -
- + {/* Top-left: logo, title, description — stacked, left aligned. */} + + EDR Freight - - Support - - -
-
-
-
-
- - {tagline ?? "Empower Your Freight Operations"} - -
-

+ + + {tagline ?? "Ethiopian Djibouti Railway"} + + {taglineBody ?? - "Sign in to manage shipments, track cargo, and run logistics operations on the Ethio Djibouti Railway freight platform."} -

-
-
-
+ "Sign in to book shipments, track cargo, and manage your freight on the Ethio–Djibouti Railway platform."} + + + + + {/* Bottom-right: brand image with a center-to-edge opacity fade, no color tint. */} + + ); const LanguageSelector = () => ( @@ -125,24 +128,24 @@ const FormFooter = () => ( ); @@ -169,8 +172,8 @@ export default function AuthShell({
-
-
+
+
{children}
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 323b5d5a8..885888a05 100644 --- a/apps/edr-freight-web/portal/src/pages/accounts/LoginPage.tsx +++ b/apps/edr-freight-web/portal/src/pages/accounts/LoginPage.tsx @@ -1,7 +1,7 @@ import { type FormEvent, useState } from "react"; import { Alert, Button, PasswordInput, Stack, TextInput } from "@mantine/core"; import { AlertCircle } from "lucide-react"; -import { useLocation, useNavigate } from "react-router-dom"; +import { Link, useLocation, useNavigate } from "react-router-dom"; import useAuth from "@/hooks/useAuth"; import AuthShell from "@/components/auth/AuthShell"; @@ -79,9 +79,12 @@ export default function LoginPage() {
/[A-Z]/.test(v) }, { label: "One lowercase letter", test: (v: string) => /[a-z]/.test(v) }, { label: "One number", test: (v: string) => /\d/.test(v) }, - { label: "One special character", test: (v: string) => /[^A-Za-z0-9]/.test(v) }, + { + label: "One special character", + test: (v: string) => /[^A-Za-z0-9]/.test(v), + }, ] as const; const userSchema = z @@ -52,8 +55,14 @@ const userSchema = z .min(1, "Phone number is required") .refine(isValidPhone, "Enter a valid phone number"), userType: z.string(), - firstName: z.object({ en: z.string().min(2, "Name is required"), am: z.string().nullable() }), - lastName: z.object({ en: z.string().min(2, "Name is required"), am: z.string().nullable() }), + firstName: z.object({ + en: z.string().min(2, "Name is required"), + am: z.string().nullable(), + }), + lastName: z.object({ + en: z.string().min(2, "Name is required"), + am: z.string().nullable(), + }), password: z .string() .min(8, "Password must be at least 8 characters") @@ -239,12 +248,11 @@ export default function SignupPage() { taglineBody="Join EDR Freight to manage shipments, track consignments, and streamline logistics workflows across Ethiopia and Djibouti." >
-
- EDR Freight -
- {stage === "form" ? ( -
+

Create account @@ -254,7 +262,7 @@ export default function SignupPage() {

- + { const met = req.test(passwordValue); return ( -
+
- {met ? : } + {met ? ( + + ) : ( + + )} - + {req.label}
@@ -364,7 +382,11 @@ export default function SignupPage() { /> {error ? ( - }> + } + > {error} ) : null} @@ -414,7 +436,11 @@ export default function SignupPage() {
{otpError ? ( - }> + } + > {otpError} ) : null}