mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 11:08:12 +00:00
style: auth clean up
This commit is contained in:
BIN
apps/edr-freight-web/backoffice/public/assets/edr_image.jpg
Normal file
BIN
apps/edr-freight-web/backoffice/public/assets/edr_image.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 158 KiB |
BIN
apps/edr-freight-web/backoffice/public/assets/edr_image.png
Normal file
BIN
apps/edr-freight-web/backoffice/public/assets/edr_image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 861 KiB |
@@ -1,32 +1,86 @@
|
|||||||
import type { ReactNode } from "react";
|
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 EDR_LOGO = "/assets/logo.svg";
|
||||||
|
|
||||||
const LeftPanelDecor = () => (
|
/** Muted deep-green brand wash for the left panel. */
|
||||||
<div
|
const LEFT_PANEL_BG =
|
||||||
className="pointer-events-none absolute inset-0 overflow-hidden"
|
"linear-gradient(158deg, #2E6B55 0%, #21503F 46%, #16352A 100%)";
|
||||||
aria-hidden
|
|
||||||
|
/** 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 interface AuthShellProps {
|
||||||
|
children: ReactNode;
|
||||||
|
/** Headline shown in the top-left of the green panel. */
|
||||||
|
tagline?: string;
|
||||||
|
taglineBody?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LeftPanel = ({
|
||||||
|
tagline,
|
||||||
|
taglineBody,
|
||||||
|
}: Pick<AuthShellProps, "tagline" | "taglineBody">) => (
|
||||||
|
<Box
|
||||||
|
className="relative hidden shrink-0 overflow-hidden rounded-2xl shadow-[0_8px_32px_rgba(15,23,42,0.12)] lg:flex lg:h-auto lg:min-h-0 lg:flex-1 lg:basis-1/2 lg:rounded-[28px]"
|
||||||
|
style={{ background: LEFT_PANEL_BG }}
|
||||||
>
|
>
|
||||||
<svg
|
{/* Top-left: logo, title, description — stacked, left aligned. */}
|
||||||
className="absolute -bottom-24 -left-24 h-[420px] w-[420px] text-white/[0.07]"
|
<Stack
|
||||||
viewBox="0 0 400 400"
|
gap="xl"
|
||||||
fill="none"
|
className="relative z-10 p-8 lg:p-10"
|
||||||
|
style={{ maxWidth: 520 }}
|
||||||
>
|
>
|
||||||
{[0, 1, 2, 3, 4, 5].map((ring) => (
|
<Image
|
||||||
<circle
|
src={EDR_LOGO}
|
||||||
key={ring}
|
alt="EDR Freight"
|
||||||
cx="200"
|
h={40}
|
||||||
cy="200"
|
w="auto"
|
||||||
r={60 + ring * 36}
|
fit="contain"
|
||||||
stroke="currentColor"
|
style={{ filter: "brightness(0) invert(1)", alignSelf: "flex-start" }}
|
||||||
strokeWidth="1"
|
/>
|
||||||
/>
|
|
||||||
))}
|
<Stack gap="sm">
|
||||||
</svg>
|
<Title
|
||||||
<div className="absolute right-0 top-0 h-40 w-40 rounded-full bg-white/[0.06] blur-2xl" />
|
order={1}
|
||||||
</div>
|
c="white"
|
||||||
|
fz={38}
|
||||||
|
fw={800}
|
||||||
|
lh={1.08}
|
||||||
|
style={{ letterSpacing: "-0.02em" }}
|
||||||
|
>
|
||||||
|
{tagline ?? "Ethiopian Djibouti Railway"}
|
||||||
|
</Title>
|
||||||
|
<Text fz="md" lh={1.6} c="rgba(255,255,255,0.82)" maw={440}>
|
||||||
|
{taglineBody ??
|
||||||
|
"Manage bookings, track cargo, and run day-to-day logistics for the Ethio–Djibouti Railway from a single backoffice."}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{/* Bottom-right: brand image with a center-to-edge opacity fade, no color tint. */}
|
||||||
|
<Image
|
||||||
|
src={EDR_IMAGE}
|
||||||
|
alt=""
|
||||||
|
aria-hidden
|
||||||
|
fit="cover"
|
||||||
|
pos="absolute"
|
||||||
|
right={0}
|
||||||
|
bottom={0}
|
||||||
|
w="80%"
|
||||||
|
h="60%"
|
||||||
|
style={{
|
||||||
|
WebkitMaskImage: IMAGE_FADE_MASK,
|
||||||
|
maskImage: IMAGE_FADE_MASK,
|
||||||
|
pointerEvents: "none",
|
||||||
|
opacity: 0.9,
|
||||||
|
maskComposite: "intersect",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
const RightPanelDecor = () => (
|
const RightPanelDecor = () => (
|
||||||
@@ -55,51 +109,6 @@ const RightPanelDecor = () => (
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
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<AuthShellProps, "tagline" | "taglineBody">) => (
|
|
||||||
<div className="relative hidden shrink-0 flex-col overflow-hidden rounded-2xl shadow-[0_8px_32px_rgba(15,23,42,0.1)] lg:flex lg:h-auto lg:min-h-0 lg:flex-1 lg:basis-1/2 lg:rounded-[28px]">
|
|
||||||
<img
|
|
||||||
src={LOGIN_IMAGE}
|
|
||||||
alt="Ethio Djibouti Railway"
|
|
||||||
className="absolute inset-0 h-full w-full object-cover object-center"
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-[#0a2e1a]/92 via-[#0f4a2a]/55 to-[#1a5c34]/45" />
|
|
||||||
<LeftPanelDecor />
|
|
||||||
|
|
||||||
<div className="relative z-10 flex shrink-0 items-center justify-between px-4 pt-4 sm:px-6 sm:pt-6 lg:px-8 lg:pt-8">
|
|
||||||
<img
|
|
||||||
src={EDR_LOGO}
|
|
||||||
alt="EDR Freight"
|
|
||||||
className="h-7 w-auto brightness-0 invert sm:h-9"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="relative z-10 mt-auto hidden px-8 pb-8 lg:block">
|
|
||||||
<div className="max-w-md rounded-2xl border border-white/15 bg-black/30 p-5 backdrop-blur-md">
|
|
||||||
<div className="mb-2 flex items-center gap-2">
|
|
||||||
<div className="h-2 w-2 shrink-0 rounded-full bg-primary" />
|
|
||||||
<span className="text-sm font-semibold text-white">
|
|
||||||
{tagline ?? "Empower Your Freight Operations"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-sm leading-relaxed text-white/85">
|
|
||||||
{taglineBody ??
|
|
||||||
"Sign in to manage bookings, track cargo, and run logistics operations on the Ethio Djibouti Railway freight platform."}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const LanguageSelector = () => (
|
const LanguageSelector = () => (
|
||||||
<div className="flex cursor-pointer items-center gap-1.5 rounded-full border border-gray-200/80 bg-white px-3 py-1.5 text-sm text-gray-600 shadow-sm">
|
<div className="flex cursor-pointer items-center gap-1.5 rounded-full border border-gray-200/80 bg-white px-3 py-1.5 text-sm text-gray-600 shadow-sm">
|
||||||
<Globe className="h-4 w-4 text-gray-500" />
|
<Globe className="h-4 w-4 text-gray-500" />
|
||||||
@@ -126,7 +135,7 @@ export default function AuthShell({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative z-10 min-h-0 flex-1 overflow-y-auto overscroll-contain">
|
<div className="relative z-10 min-h-0 flex-1 overflow-y-auto overscroll-contain">
|
||||||
<div className="flex min-h-full justify-center px-4 py-4 sm:px-6 sm:py-6 lg:px-8 lg:py-8">
|
<div className="flex min-h-full justify-center">
|
||||||
<div className="my-auto w-full max-w-xl rounded-3xl px-5 py-6 sm:px-7 sm:py-8 lg:px-9 lg:py-9">
|
<div className="my-auto w-full max-w-xl rounded-3xl px-5 py-6 sm:px-7 sm:py-8 lg:px-9 lg:py-9">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
BIN
apps/edr-freight-web/portal/public/assets/edr_image.jpg
Normal file
BIN
apps/edr-freight-web/portal/public/assets/edr_image.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 158 KiB |
BIN
apps/edr-freight-web/portal/public/assets/edr_image.png
Normal file
BIN
apps/edr-freight-web/portal/public/assets/edr_image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 861 KiB |
@@ -1,40 +1,25 @@
|
|||||||
import type { ReactNode } from "react";
|
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";
|
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 =
|
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";
|
"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 =
|
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";
|
"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 = () => (
|
|
||||||
<div
|
|
||||||
className="pointer-events-none absolute inset-0 overflow-hidden"
|
|
||||||
aria-hidden
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
className="absolute -bottom-24 -left-24 h-[420px] w-[420px] text-white/[0.07]"
|
|
||||||
viewBox="0 0 400 400"
|
|
||||||
fill="none"
|
|
||||||
>
|
|
||||||
{[0, 1, 2, 3, 4, 5].map((ring) => (
|
|
||||||
<circle
|
|
||||||
key={ring}
|
|
||||||
cx="200"
|
|
||||||
cy="200"
|
|
||||||
r={60 + ring * 36}
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="1"
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</svg>
|
|
||||||
<div className="absolute right-0 top-0 h-40 w-40 rounded-full bg-white/[0.06] blur-2xl" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const RightPanelDecor = () => (
|
const RightPanelDecor = () => (
|
||||||
<div
|
<div
|
||||||
className="pointer-events-none absolute inset-0 overflow-hidden"
|
className="pointer-events-none absolute inset-0 overflow-hidden"
|
||||||
@@ -63,7 +48,7 @@ const RightPanelDecor = () => (
|
|||||||
|
|
||||||
export interface AuthShellProps {
|
export interface AuthShellProps {
|
||||||
children: ReactNode;
|
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;
|
tagline?: string;
|
||||||
taglineBody?: string;
|
taglineBody?: string;
|
||||||
}
|
}
|
||||||
@@ -72,45 +57,63 @@ const LeftPanel = ({
|
|||||||
tagline,
|
tagline,
|
||||||
taglineBody,
|
taglineBody,
|
||||||
}: Pick<AuthShellProps, "tagline" | "taglineBody">) => (
|
}: Pick<AuthShellProps, "tagline" | "taglineBody">) => (
|
||||||
<div className="relative hidden shrink-0 flex-col overflow-hidden rounded-2xl bg-[#011F12] shadow-[0_8px_32px_rgba(15,23,42,0.1)] lg:flex lg:h-auto lg:min-h-0 lg:flex-1 lg:basis-1/2 lg:rounded-[28px]">
|
<Box
|
||||||
<img
|
className="relative hidden shrink-0 overflow-hidden rounded-2xl shadow-[0_8px_32px_rgba(15,23,42,0.12)] lg:flex lg:h-auto lg:min-h-0 lg:flex-1 lg:basis-1/2 lg:rounded-[28px]"
|
||||||
src={LOGIN_IMAGE}
|
style={{ background: LEFT_PANEL_BG }}
|
||||||
alt="Ethio Djibouti Railway"
|
>
|
||||||
className="absolute inset-0 h-auto w-full object-cover object-center"
|
{/* Top-left: logo, title, description — stacked, left aligned. */}
|
||||||
/>
|
<Stack
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-[#0a2e1a]/92 via-[#0f4a2a]/15 to-[#1a5c34]/45" />
|
gap="xl"
|
||||||
<LeftPanelDecor />
|
className="relative z-10 p-8 lg:p-10"
|
||||||
|
style={{ maxWidth: 520 }}
|
||||||
<div className="relative z-10 flex shrink-0 items-center justify-between px-4 pt-4 sm:px-6 sm:pt-6 lg:px-8 lg:pt-8">
|
>
|
||||||
<img
|
<Image
|
||||||
src={EDR_LOGO}
|
src={EDR_LOGO}
|
||||||
alt="EDR Freight"
|
alt="EDR Freight"
|
||||||
className="h-7 w-auto brightness-0 invert sm:h-9"
|
h={40}
|
||||||
|
w="auto"
|
||||||
|
fit="contain"
|
||||||
|
style={{ filter: "brightness(0) invert(1)", alignSelf: "flex-start" }}
|
||||||
/>
|
/>
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
className="flex items-center gap-1.5 rounded-full border border-white/70 bg-white/10 px-3 py-1.5 text-xs font-medium text-white backdrop-blur-sm transition-colors hover:bg-white/20 sm:px-4 sm:py-2 sm:text-sm"
|
|
||||||
>
|
|
||||||
Support
|
|
||||||
<ArrowUpRight className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="relative z-10 mt-auto hidden px-8 pb-8 lg:block">
|
<Stack gap="sm">
|
||||||
<div className="max-w-md rounded-2xl border border-white/15 bg-black/30 p-5 backdrop-blur-md">
|
<Title
|
||||||
<div className="mb-2 flex items-center gap-2">
|
order={1}
|
||||||
<div className="h-2 w-2 shrink-0 rounded-full bg-primary" />
|
c="white"
|
||||||
<span className="text-sm font-semibold text-white">
|
fz={38}
|
||||||
{tagline ?? "Empower Your Freight Operations"}
|
fw={800}
|
||||||
</span>
|
lh={1.08}
|
||||||
</div>
|
style={{ letterSpacing: "-0.02em" }}
|
||||||
<p className="text-sm leading-relaxed text-white/85">
|
>
|
||||||
|
{tagline ?? "Ethiopian Djibouti Railway"}
|
||||||
|
</Title>
|
||||||
|
<Text fz="md" lh={1.6} c="rgba(255,255,255,0.82)" maw={440}>
|
||||||
{taglineBody ??
|
{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."}
|
||||||
</p>
|
</Text>
|
||||||
</div>
|
</Stack>
|
||||||
</div>
|
</Stack>
|
||||||
</div>
|
|
||||||
|
{/* Bottom-right: brand image with a center-to-edge opacity fade, no color tint. */}
|
||||||
|
<Image
|
||||||
|
src={EDR_IMAGE}
|
||||||
|
alt=""
|
||||||
|
aria-hidden
|
||||||
|
fit="cover"
|
||||||
|
pos="absolute"
|
||||||
|
right={0}
|
||||||
|
bottom={0}
|
||||||
|
w="80%"
|
||||||
|
h="60%"
|
||||||
|
style={{
|
||||||
|
WebkitMaskImage: IMAGE_FADE_MASK,
|
||||||
|
maskImage: IMAGE_FADE_MASK,
|
||||||
|
opacity: 0.9,
|
||||||
|
pointerEvents: "none",
|
||||||
|
maskComposite: "intersect",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
const LanguageSelector = () => (
|
const LanguageSelector = () => (
|
||||||
@@ -125,24 +128,24 @@ const FormFooter = () => (
|
|||||||
<div className="relative z-10 flex shrink-0 flex-col items-center justify-between gap-3 border-t border-gray-100 px-4 py-4 text-xs text-gray-400 sm:flex-row sm:gap-4 sm:px-6 sm:py-4 lg:px-8 lg:pb-6">
|
<div className="relative z-10 flex shrink-0 flex-col items-center justify-between gap-3 border-t border-gray-100 px-4 py-4 text-xs text-gray-400 sm:flex-row sm:gap-4 sm:px-6 sm:py-4 lg:px-8 lg:pb-6">
|
||||||
<span className="shrink-0">© 2026 EDR Freight</span>
|
<span className="shrink-0">© 2026 EDR Freight</span>
|
||||||
<div className="flex flex-wrap items-center justify-center gap-3 sm:justify-end sm:gap-6">
|
<div className="flex flex-wrap items-center justify-center gap-3 sm:justify-end sm:gap-6">
|
||||||
<a
|
<Link
|
||||||
href="#"
|
to="#"
|
||||||
className="font-semibold text-gray-700 transition-colors hover:text-primary"
|
className="font-semibold text-gray-700 transition-colors hover:text-primary"
|
||||||
>
|
>
|
||||||
Terms & Conditions
|
Terms & Conditions
|
||||||
</a>
|
</Link>
|
||||||
<a
|
<Link
|
||||||
href="#"
|
to="#"
|
||||||
className="font-semibold text-gray-700 transition-colors hover:text-primary"
|
className="font-semibold text-gray-700 transition-colors hover:text-primary"
|
||||||
>
|
>
|
||||||
Privacy Policy
|
Privacy Policy
|
||||||
</a>
|
</Link>
|
||||||
<a
|
<Link
|
||||||
href="#"
|
to="#"
|
||||||
className="font-semibold text-gray-700 transition-colors hover:text-primary"
|
className="font-semibold text-gray-700 transition-colors hover:text-primary"
|
||||||
>
|
>
|
||||||
Help & Support
|
Help & Support
|
||||||
</a>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -169,8 +172,8 @@ export default function AuthShell({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative z-10 min-h-0 flex-1 overflow-y-auto overscroll-contain">
|
<div className="relative z-10 min-h-0 flex-1 overflow-y-auto overscroll-contain">
|
||||||
<div className="flex min-h-full justify-center px-4 py-4 sm:px-6 sm:py-6 lg:px-8 lg:py-8">
|
<div className="flex min-h-full justify-center">
|
||||||
<div className="my-auto w-full max-w-xl rounded-3xl px-5 py-6 sm:px-7 sm:py-8 lg:px-9 lg:py-9">
|
<div className="my-auto w-full max-w-xl rounded-3xl px-5 py-6 sm:px-7 sm:py-8 lg:px-9">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { type FormEvent, useState } from "react";
|
import { type FormEvent, useState } from "react";
|
||||||
import { Alert, Button, PasswordInput, Stack, TextInput } from "@mantine/core";
|
import { Alert, Button, PasswordInput, Stack, TextInput } from "@mantine/core";
|
||||||
import { AlertCircle } from "lucide-react";
|
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 useAuth from "@/hooks/useAuth";
|
||||||
import AuthShell from "@/components/auth/AuthShell";
|
import AuthShell from "@/components/auth/AuthShell";
|
||||||
@@ -79,9 +79,12 @@ export default function LoginPage() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="mb-1.5 flex items-center justify-between">
|
<div className="mb-1.5 flex items-center justify-between">
|
||||||
<span className="text-sm font-medium text-gray-800">Password</span>
|
<span className="text-sm font-medium text-gray-800">Password</span>
|
||||||
<a href="#" className="text-xs font-semibold text-primary hover:underline">
|
<Link
|
||||||
|
to="#"
|
||||||
|
className="text-xs font-semibold text-primary hover:underline"
|
||||||
|
>
|
||||||
Forgot password?
|
Forgot password?
|
||||||
</a>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
placeholder="Enter your password"
|
placeholder="Enter your password"
|
||||||
|
|||||||
@@ -41,7 +41,10 @@ const passwordRequirements = [
|
|||||||
{ label: "One uppercase letter", test: (v: string) => /[A-Z]/.test(v) },
|
{ label: "One uppercase letter", test: (v: string) => /[A-Z]/.test(v) },
|
||||||
{ label: "One lowercase letter", test: (v: string) => /[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 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;
|
] as const;
|
||||||
|
|
||||||
const userSchema = z
|
const userSchema = z
|
||||||
@@ -52,8 +55,14 @@ const userSchema = z
|
|||||||
.min(1, "Phone number is required")
|
.min(1, "Phone number is required")
|
||||||
.refine(isValidPhone, "Enter a valid phone number"),
|
.refine(isValidPhone, "Enter a valid phone number"),
|
||||||
userType: z.string(),
|
userType: z.string(),
|
||||||
firstName: z.object({ en: z.string().min(2, "Name is required"), am: z.string().nullable() }),
|
firstName: z.object({
|
||||||
lastName: z.object({ en: z.string().min(2, "Name is required"), am: z.string().nullable() }),
|
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
|
password: z
|
||||||
.string()
|
.string()
|
||||||
.min(8, "Password must be at least 8 characters")
|
.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."
|
taglineBody="Join EDR Freight to manage shipments, track consignments, and streamline logistics workflows across Ethiopia and Djibouti."
|
||||||
>
|
>
|
||||||
<div className="flex w-full flex-col">
|
<div className="flex w-full flex-col">
|
||||||
<div className="mb-4 flex justify-center sm:mb-6">
|
|
||||||
<img src={EDR_LOGO} alt="EDR Freight" className="h-9 w-auto sm:h-11" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{stage === "form" ? (
|
{stage === "form" ? (
|
||||||
<form onSubmit={handleSubmit(requestOtp)} className="flex w-full flex-col">
|
<form
|
||||||
|
onSubmit={handleSubmit(requestOtp)}
|
||||||
|
className="flex w-full flex-col"
|
||||||
|
>
|
||||||
<div className="mb-4 space-y-1.5 text-center sm:mb-5">
|
<div className="mb-4 space-y-1.5 text-center sm:mb-5">
|
||||||
<h1 className="text-xl font-bold tracking-tight text-gray-900 sm:text-2xl">
|
<h1 className="text-xl font-bold tracking-tight text-gray-900 sm:text-2xl">
|
||||||
Create account
|
Create account
|
||||||
@@ -254,7 +262,7 @@ export default function SignupPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Stack gap="md">
|
<Stack gap="sm">
|
||||||
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
|
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
|
||||||
<TextInput
|
<TextInput
|
||||||
label="First name"
|
label="First name"
|
||||||
@@ -336,15 +344,25 @@ export default function SignupPage() {
|
|||||||
{passwordRequirements.map((req) => {
|
{passwordRequirements.map((req) => {
|
||||||
const met = req.test(passwordValue);
|
const met = req.test(passwordValue);
|
||||||
return (
|
return (
|
||||||
<div key={req.label} className="flex items-center gap-2">
|
<div
|
||||||
|
key={req.label}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
className={`flex h-4 w-4 shrink-0 items-center justify-center rounded-full ${
|
className={`flex h-4 w-4 shrink-0 items-center justify-center rounded-full ${met
|
||||||
met ? "bg-primary text-primary-foreground" : "bg-gray-200 text-gray-500"
|
? "bg-primary text-primary-foreground"
|
||||||
}`}
|
: "bg-gray-200 text-gray-500"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{met ? <Check className="h-2.5 w-2.5" /> : <X className="h-2.5 w-2.5" />}
|
{met ? (
|
||||||
|
<Check className="h-2.5 w-2.5" />
|
||||||
|
) : (
|
||||||
|
<X className="h-2.5 w-2.5" />
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span className={`text-xs ${met ? "text-primary" : "text-gray-500"}`}>
|
<span
|
||||||
|
className={`text-xs ${met ? "text-primary" : "text-gray-500"}`}
|
||||||
|
>
|
||||||
{req.label}
|
{req.label}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -364,7 +382,11 @@ export default function SignupPage() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{error ? (
|
{error ? (
|
||||||
<Alert color="red" variant="light" icon={<AlertCircle size={18} />}>
|
<Alert
|
||||||
|
color="red"
|
||||||
|
variant="light"
|
||||||
|
icon={<AlertCircle size={18} />}
|
||||||
|
>
|
||||||
{error}
|
{error}
|
||||||
</Alert>
|
</Alert>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -414,7 +436,11 @@ export default function SignupPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{otpError ? (
|
{otpError ? (
|
||||||
<Alert color="red" variant="light" icon={<AlertCircle size={18} />}>
|
<Alert
|
||||||
|
color="red"
|
||||||
|
variant="light"
|
||||||
|
icon={<AlertCircle size={18} />}
|
||||||
|
>
|
||||||
{otpError}
|
{otpError}
|
||||||
</Alert>
|
</Alert>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
Reference in New Issue
Block a user