updates on the design on the kpi cards
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import Loader from 'components/Loader';
|
||||
import { MdCheck, MdOutlineVisibility, MdOutlineVisibilityOff } from 'react-icons/md';
|
||||
import logo from 'assets/images/doormile-logo.png';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
@@ -8,71 +8,400 @@ import { OpenToast } from 'components/third-party/OpenToast';
|
||||
import { setLoginUser } from 'store/reducers/loginUserSlice';
|
||||
import { markSessionStart } from 'utils/session';
|
||||
import { loginAdmin } from 'pages/api/doormileApi';
|
||||
import { DT } from 'themes/dt/tokens';
|
||||
|
||||
// Astryx design system — see themes/astryx.js for the Doormile brand theme
|
||||
// and CLAUDE.md's <!-- ASTRYX:START --> block for the CLI workflow.
|
||||
// NOTE: custom CSS (xstyle/stylex.create()) isn't wired up yet — see the
|
||||
// comment in config-overrides.js. Everything below uses Astryx component
|
||||
// props only; the brand gradient panel and the two logo images are plain
|
||||
// native elements with inline `style` for that reason.
|
||||
// Astryx design system — see themes/astryx.js for the Doormile theme.
|
||||
//
|
||||
// Two things about how this page is built:
|
||||
//
|
||||
// 1. The brand panel and the password toggle are plain elements with a scoped
|
||||
// stylesheet, not Astryx layout components. Astryx has no breakpoint props,
|
||||
// no gradient/decoration surface and no end-slot on TextInput, so those
|
||||
// three needs cannot be expressed through component props at all. The
|
||||
// stylesheet below is the same escape hatch `themes/dt/primitives.js`
|
||||
// (TableScroll) and `PageLayout.js` (PageShell) already use.
|
||||
//
|
||||
// 2. EVERY selector in that stylesheet is prefixed `.dm-login`. The brand red
|
||||
// lives on this page only — the console's own palette (black accent, slate
|
||||
// ink) is deliberately untouched, so nothing here leaks into the operator
|
||||
// pages. Do not lift these colours into themes/astryx.js.
|
||||
import { AppShell } from '@astryxdesign/core/AppShell';
|
||||
import { Theme } from '@astryxdesign/core/theme';
|
||||
import { HStack } from '@astryxdesign/core/HStack';
|
||||
import { VStack } from '@astryxdesign/core/VStack';
|
||||
import { Center } from '@astryxdesign/core/Center';
|
||||
import { Card } from '@astryxdesign/core/Card';
|
||||
import { Heading } from '@astryxdesign/core/Heading';
|
||||
import { Text } from '@astryxdesign/core/Text';
|
||||
import { TextInput } from '@astryxdesign/core/TextInput';
|
||||
import { Button } from '@astryxdesign/core/Button';
|
||||
import { Link } from '@astryxdesign/core/Link';
|
||||
import { Divider } from '@astryxdesign/core/Divider';
|
||||
import { doormileTheme } from 'themes/astryx';
|
||||
|
||||
const brandPanelStyle = {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
width: '46%',
|
||||
height: '100vh',
|
||||
color: '#fff',
|
||||
padding: 48,
|
||||
background: `linear-gradient(150deg, ${DT.brand} 0%, #3A3A3A 100%)`
|
||||
};
|
||||
|
||||
const logoLockupStyle = { position: 'absolute', top: 48, left: 48, maxHeight: 60 };
|
||||
|
||||
const bulletDotStyle = {
|
||||
width: 22,
|
||||
height: 22,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.18)',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: 13,
|
||||
fontWeight: 700,
|
||||
flexShrink: 0
|
||||
};
|
||||
|
||||
// doormile-logo.png is a white asset; recolour to brand red for this
|
||||
// white-background card (the brand-panel logo stays white as-is).
|
||||
const formLogoStyle = {
|
||||
maxHeight: 48,
|
||||
filter: 'brightness(0) saturate(100%) invert(15%) sepia(93%) saturate(5437%) hue-rotate(346deg) brightness(81%) contrast(92%)'
|
||||
};
|
||||
// Doormile brand red. Stated once; every rule below reads the custom property
|
||||
// so the two tones can never drift apart.
|
||||
const BRAND = '#C8152D';
|
||||
const BRAND_DEEP = '#A90F24';
|
||||
|
||||
const BULLETS = ['Real-time fleet visibility', 'AI-optimised dispatch routes', 'Tenant, pricing & invoice control'];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Scoped stylesheet.
|
||||
//
|
||||
// Astryx exposes stable class hooks (`astryx-card`, `astryx-text-input`,
|
||||
// `astryx-button`, `astryx-field-label`, `astryx-link`) alongside its generated
|
||||
// StyleX classes, and a two-class descendant selector out-specifies them —
|
||||
// verified in the browser rather than assumed. That is what lets this restyle
|
||||
// real Astryx components instead of re-implementing them.
|
||||
// ---------------------------------------------------------------------------
|
||||
const LOGIN_CSS = `
|
||||
.dm-login {
|
||||
--dm-brand: ${BRAND};
|
||||
--dm-brand-deep: ${BRAND_DEEP};
|
||||
--dm-ring: rgba(200, 21, 45, 0.10);
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ---- Left brand panel --------------------------------------------------- */
|
||||
.dm-login__brand {
|
||||
flex: 0 0 44%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 56px;
|
||||
color: #fff;
|
||||
/* Depth without decoration: one light lift from the top-left, one shadow
|
||||
into the bottom-right, over a restrained two-stop brand ramp. The panel
|
||||
should read as lit, not as gradient-y. */
|
||||
background:
|
||||
radial-gradient(115% 85% at 10% 6%, rgba(255, 255, 255, 0.12) 0%, transparent 55%),
|
||||
radial-gradient(95% 85% at 95% 100%, rgba(0, 0, 0, 0.22) 0%, transparent 62%),
|
||||
linear-gradient(155deg, var(--dm-brand) 0%, #B31228 55%, var(--dm-brand-deep) 100%);
|
||||
}
|
||||
|
||||
/* A single oversized arc, barely visible — the "logistics curve". One shape,
|
||||
two hairlines, no pattern library. */
|
||||
.dm-login__brand::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -240px;
|
||||
bottom: -280px;
|
||||
width: 640px;
|
||||
height: 640px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
box-shadow: 0 0 0 72px rgba(255, 255, 255, 0.035);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Faint route lines, masked so they fade out rather than tile edge to edge. */
|
||||
.dm-login__brand::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background-image: repeating-linear-gradient(118deg, rgba(255, 255, 255, 0.055) 0 1px, transparent 1px 78px);
|
||||
-webkit-mask-image: radial-gradient(130% 100% at 85% 12%, #000 0%, transparent 68%);
|
||||
mask-image: radial-gradient(130% 100% at 85% 12%, #000 0%, transparent 68%);
|
||||
}
|
||||
|
||||
.dm-login__logo {
|
||||
position: absolute;
|
||||
top: 56px;
|
||||
left: 56px;
|
||||
height: 46px;
|
||||
width: auto;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dm-login__copy {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: 460px;
|
||||
}
|
||||
|
||||
.dm-login__headline {
|
||||
margin: 0;
|
||||
font-family: var(--font-family-heading);
|
||||
font-size: 34px;
|
||||
line-height: 1.18;
|
||||
letter-spacing: -0.02em;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dm-login__lede {
|
||||
margin: 18px 0 0;
|
||||
font-size: 16px;
|
||||
line-height: 1.65;
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
}
|
||||
|
||||
.dm-login__features {
|
||||
margin: 34px 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.dm-login__feature {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-size: 15px;
|
||||
line-height: 1.4;
|
||||
color: rgba(255, 255, 255, 0.94);
|
||||
}
|
||||
|
||||
.dm-login__tick {
|
||||
flex: 0 0 auto;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
border: 1px solid rgba(255, 255, 255, 0.22);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* ---- Right form panel --------------------------------------------------- */
|
||||
.dm-login__form {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f8f9fb;
|
||||
padding: 48px 24px;
|
||||
}
|
||||
|
||||
.dm-login__stack {
|
||||
width: 100%;
|
||||
max-width: 436px;
|
||||
}
|
||||
|
||||
.dm-login .astryx-card {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 16px;
|
||||
/* Elevated, not floating. */
|
||||
box-shadow: 0 12px 35px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.dm-login__title {
|
||||
margin: 0;
|
||||
font-family: var(--font-family-heading);
|
||||
font-size: 26px;
|
||||
line-height: 1.25;
|
||||
letter-spacing: -0.01em;
|
||||
font-weight: 700;
|
||||
color: #101828;
|
||||
}
|
||||
|
||||
.dm-login__subtitle {
|
||||
margin: 6px 0 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: #667085;
|
||||
}
|
||||
|
||||
/* ---- Fields ------------------------------------------------------------- */
|
||||
.dm-login .astryx-field-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #344054;
|
||||
}
|
||||
|
||||
.dm-login .astryx-text-input {
|
||||
height: 48px;
|
||||
border-radius: 10px;
|
||||
border-color: #d1d5db;
|
||||
background: #ffffff;
|
||||
padding-inline: 14px;
|
||||
transition: border-color 160ms ease, box-shadow 160ms ease;
|
||||
}
|
||||
|
||||
.dm-login .astryx-text-input input {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* One focus treatment for the whole form. :focus-within so the ring follows
|
||||
the field, not the bare <input> that sits inside Astryx's bordered wrapper.
|
||||
|
||||
Plain two-class specificity is enough: the resting rule above already
|
||||
out-specifies Astryx's own StyleX rules on this element, and this selector
|
||||
is strictly more specific than that one. If a future Astryx version starts
|
||||
winning here, the escape hatch is StyleX's own repeated-:not(#id) idiom —
|
||||
but write it with IDs that simply never exist rather than copying StyleX's
|
||||
escaped-hash form, because a backslash inside THIS template literal is
|
||||
consumed by JS before the CSS parser sees it and the rule is then dropped
|
||||
silently. */
|
||||
.dm-login .astryx-text-input:focus-within {
|
||||
border-color: var(--dm-brand);
|
||||
box-shadow: 0 0 0 3px var(--dm-ring);
|
||||
}
|
||||
|
||||
/* Chrome paints autofilled inputs pale blue, which is the "browser-looking"
|
||||
field the design brief calls out. An inset shadow the size of the box is the
|
||||
only way to repaint it. */
|
||||
.dm-login .astryx-text-input input:-webkit-autofill,
|
||||
.dm-login .astryx-text-input input:-webkit-autofill:hover,
|
||||
.dm-login .astryx-text-input input:-webkit-autofill:focus {
|
||||
-webkit-box-shadow: 0 0 0 40px #ffffff inset;
|
||||
box-shadow: 0 0 0 40px #ffffff inset;
|
||||
-webkit-text-fill-color: #101828;
|
||||
}
|
||||
|
||||
/* ---- Password visibility toggle ----------------------------------------- */
|
||||
.dm-login__pw {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Keep typed characters clear of the toggle. */
|
||||
.dm-login__pw .astryx-text-input {
|
||||
padding-right: 46px;
|
||||
}
|
||||
|
||||
.dm-login__pw-toggle {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
bottom: 0;
|
||||
height: 48px;
|
||||
width: 42px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
color: #667085;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: color 160ms ease, background-color 160ms ease;
|
||||
}
|
||||
|
||||
.dm-login__pw-toggle:hover {
|
||||
color: #344054;
|
||||
background: #f2f4f7;
|
||||
}
|
||||
|
||||
.dm-login__pw-toggle:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px var(--dm-ring);
|
||||
color: var(--dm-brand);
|
||||
}
|
||||
|
||||
/* ---- Primary action ----------------------------------------------------- */
|
||||
.dm-login .astryx-button {
|
||||
height: 48px;
|
||||
border-radius: 10px;
|
||||
background: var(--dm-brand);
|
||||
border-color: var(--dm-brand);
|
||||
color: #ffffff;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
transition: background-color 160ms ease, transform 120ms ease, box-shadow 160ms ease;
|
||||
}
|
||||
|
||||
.dm-login .astryx-button:hover:not([disabled]):not([aria-disabled='true']) {
|
||||
background: var(--dm-brand-deep);
|
||||
border-color: var(--dm-brand-deep);
|
||||
}
|
||||
|
||||
.dm-login .astryx-button:active:not([disabled]):not([aria-disabled='true']) {
|
||||
transform: translateY(1px);
|
||||
background: #970d20;
|
||||
}
|
||||
|
||||
.dm-login .astryx-button:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px var(--dm-ring);
|
||||
}
|
||||
|
||||
.dm-login .astryx-button[disabled],
|
||||
.dm-login .astryx-button[aria-disabled='true'] {
|
||||
opacity: 0.7;
|
||||
cursor: progress;
|
||||
}
|
||||
|
||||
/* ---- Footer ------------------------------------------------------------- */
|
||||
.dm-login__footer {
|
||||
margin-top: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 20px;
|
||||
font-size: 13px;
|
||||
color: #98a2b3;
|
||||
}
|
||||
|
||||
.dm-login__footer .astryx-link {
|
||||
font-size: 13px;
|
||||
color: #667085;
|
||||
}
|
||||
|
||||
.dm-login__footer .astryx-link:hover {
|
||||
color: var(--dm-brand);
|
||||
}
|
||||
|
||||
/* ---- Tablet ------------------------------------------------------------- */
|
||||
@media (max-width: 1100px) {
|
||||
.dm-login__brand { flex-basis: 42%; padding: 40px; }
|
||||
.dm-login__logo { top: 40px; left: 40px; height: 40px; }
|
||||
.dm-login__headline { font-size: 28px; }
|
||||
.dm-login__lede { font-size: 15px; margin-top: 14px; }
|
||||
.dm-login__features { margin-top: 26px; gap: 13px; }
|
||||
}
|
||||
|
||||
/* ---- Mobile ------------------------------------------------------------- */
|
||||
/* The brand panel COLLAPSES to a logo strip rather than being hidden and the
|
||||
logo re-rendered above the card. That keeps exactly one <img> of the logo in
|
||||
the DOM at every breakpoint — a duplicate that is merely display:none is
|
||||
still a duplicate to a screen reader and to anyone reading the markup. */
|
||||
@media (max-width: 860px) {
|
||||
.dm-login { flex-direction: column; min-height: 100dvh; }
|
||||
|
||||
.dm-login__brand {
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 22px 24px;
|
||||
}
|
||||
.dm-login__brand::before,
|
||||
.dm-login__brand::after { display: none; }
|
||||
|
||||
.dm-login__logo { position: static; height: 34px; }
|
||||
.dm-login__copy { display: none; }
|
||||
|
||||
.dm-login__form {
|
||||
flex: 1 1 auto;
|
||||
align-items: flex-start;
|
||||
padding: 32px 20px 40px;
|
||||
}
|
||||
.dm-login__title { font-size: 23px; }
|
||||
.dm-login__footer { margin-top: 22px; gap: 8px 16px; }
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
.dm-login__form { padding-inline: 16px; }
|
||||
}
|
||||
`;
|
||||
|
||||
const Login = () => {
|
||||
const dispatch = useDispatch();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
const emailRef = useRef(null);
|
||||
const passwordRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
// 'authname' is the AUTH_PRESENCE_KEY (utils/session.js) — App.js and the
|
||||
@@ -83,10 +412,24 @@ const Login = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Astryx's TextInput has no `autoComplete` prop and BaseProps declares only
|
||||
// `xstyle`, so an unknown prop would be dropped rather than forwarded. It
|
||||
// does forward `ref` to the real <input>, which is the supported way to
|
||||
// reach the element — password managers need these attributes to offer a
|
||||
// saved credential at all. Re-applied when the toggle swaps `type`.
|
||||
useEffect(() => {
|
||||
emailRef.current?.setAttribute('autocomplete', 'email');
|
||||
passwordRef.current?.setAttribute('autocomplete', 'current-password');
|
||||
}, [showPassword]);
|
||||
|
||||
const opentoast = (message, variant = 'error') => OpenToast(message, variant, 2000);
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
// Guard against a second submit while the first is still in flight —
|
||||
// the button is disabled while loading, but Enter in a field is not.
|
||||
if (loading) return;
|
||||
|
||||
if (!email || !password) {
|
||||
opentoast('Enter your email and password');
|
||||
return;
|
||||
@@ -123,83 +466,105 @@ const Login = () => {
|
||||
return (
|
||||
<Theme theme={doormileTheme} mode="light">
|
||||
<AppShell contentPadding={0}>
|
||||
{loading && <Loader />}
|
||||
<HStack gap={0} height="100vh" wrap="nowrap">
|
||||
{/* ---- Left brand panel (plain element: see the note at the top of
|
||||
this file about custom styling not going through Astryx yet) ---- */}
|
||||
<div style={brandPanelStyle}>
|
||||
<img src={logo} alt="Doormile" style={logoLockupStyle} />
|
||||
<style>{LOGIN_CSS}</style>
|
||||
|
||||
<VStack gap={2} maxWidth={430}>
|
||||
<Heading level={1} color="inherit">
|
||||
Operate your dispatch, end to end.
|
||||
</Heading>
|
||||
<Text type="large" color="inherit">
|
||||
<div className="dm-login">
|
||||
{/* ---- Brand panel. The ONLY Doormile logo on this page. ---- */}
|
||||
<aside className="dm-login__brand">
|
||||
<img className="dm-login__logo" src={logo} alt="Doormile" />
|
||||
|
||||
<div className="dm-login__copy">
|
||||
<h1 className="dm-login__headline">Operate your dispatch, end to end.</h1>
|
||||
<p className="dm-login__lede">
|
||||
Orders, AI route optimisation, live rider tracking and billing — all in the Doormile Express operator console.
|
||||
</Text>
|
||||
|
||||
<VStack gap={1.5} padding={0}>
|
||||
</p>
|
||||
<ul className="dm-login__features">
|
||||
{BULLETS.map((t) => (
|
||||
<HStack key={t} gap={1.25} vAlign="center">
|
||||
<span style={bulletDotStyle}>✓</span>
|
||||
<Text color="inherit">{t}</Text>
|
||||
</HStack>
|
||||
<li key={t} className="dm-login__feature">
|
||||
<span className="dm-login__tick" aria-hidden="true">
|
||||
<MdCheck size={14} />
|
||||
</span>
|
||||
{t}
|
||||
</li>
|
||||
))}
|
||||
</VStack>
|
||||
</VStack>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* ---- Right form panel ---- */}
|
||||
<Center axis="both" width="54%" height="100vh">
|
||||
<VStack width="100%" maxWidth={440} gap={4} padding={3}>
|
||||
<Card padding={8} elevation="med">
|
||||
<VStack gap={6} padding={0}>
|
||||
<VStack gap={1.5} hAlign="center" padding={0}>
|
||||
<img src={logo} alt="Doormile" style={formLogoStyle} />
|
||||
<Heading level={2}>Welcome back</Heading>
|
||||
<Text type="supporting">Sign in to the Doormile Express console</Text>
|
||||
</VStack>
|
||||
{/* ---- Form panel ---- */}
|
||||
<main className="dm-login__form">
|
||||
<div className="dm-login__stack">
|
||||
<Card padding={8} elevation="none">
|
||||
<VStack gap={5} padding={0}>
|
||||
<div>
|
||||
<h2 className="dm-login__title">Welcome back</h2>
|
||||
<p className="dm-login__subtitle">Sign in to the Doormile Express console</p>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<form noValidate onSubmit={handleSubmit}>
|
||||
<VStack gap={4} padding={0}>
|
||||
<TextInput
|
||||
ref={emailRef}
|
||||
hasAutoFocus
|
||||
label="E-mail Address"
|
||||
label="Email address"
|
||||
type="email"
|
||||
size="lg"
|
||||
placeholder="you@company.com"
|
||||
value={email}
|
||||
isDisabled={loading}
|
||||
onChange={(value) => setEmail(value.toLocaleLowerCase())}
|
||||
/>
|
||||
<TextInput
|
||||
label="Password"
|
||||
type="password"
|
||||
|
||||
<div className="dm-login__pw">
|
||||
<TextInput
|
||||
ref={passwordRef}
|
||||
label="Password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
size="lg"
|
||||
placeholder="Enter your password"
|
||||
value={password}
|
||||
isDisabled={loading}
|
||||
onChange={(value) => setPassword(value)}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="dm-login__pw-toggle"
|
||||
onClick={() => setShowPassword((v) => !v)}
|
||||
aria-label={showPassword ? 'Hide password' : 'Show password'}
|
||||
aria-pressed={showPassword}
|
||||
>
|
||||
{showPassword ? <MdOutlineVisibilityOff size={19} /> : <MdOutlineVisibility size={19} />}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
label={loading ? 'Signing in…' : 'Sign in'}
|
||||
type="submit"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
value={password}
|
||||
onChange={(value) => setPassword(value)}
|
||||
width="100%"
|
||||
isLoading={loading}
|
||||
isDisabled={loading}
|
||||
/>
|
||||
<Button label="Sign in" type="submit" variant="primary" size="lg" width="100%" />
|
||||
</VStack>
|
||||
</form>
|
||||
</VStack>
|
||||
</Card>
|
||||
|
||||
{/* footer */}
|
||||
<HStack justify="center" wrap="wrap" gap={2}>
|
||||
<Link href="https://doormile.com" target="_blank" isExternalLink type="supporting">
|
||||
© All rights reserved
|
||||
</Link>
|
||||
<div className="dm-login__footer">
|
||||
<span>© {new Date().getFullYear()} Doormile</span>
|
||||
<Link href="https://doormile.com/terms-of-service" target="_blank" isExternalLink type="supporting">
|
||||
Terms and Conditions
|
||||
Terms & Conditions
|
||||
</Link>
|
||||
<Link href="https://doormile.com/privacy-policy" target="_blank" isExternalLink type="supporting">
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</Center>
|
||||
</HStack>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</AppShell>
|
||||
</Theme>
|
||||
);
|
||||
|
||||
@@ -38,7 +38,7 @@ import { Table, TableHeader, TableBody, TableRow, TableCell, TableHeaderCell } f
|
||||
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
|
||||
import PageHeader from 'components/nearle_components/PageHeader';
|
||||
import { PageShell, KpiGrid, DataCard } from 'components/nearle_components/PageLayout';
|
||||
import HoverSocialCard from 'components/cards/statistics/HoverSocialCard';
|
||||
import StatCard from 'components/nearle_components/StatCard';
|
||||
import { parseDoormileTimestamp } from 'utils/doormileTimestamp';
|
||||
import { fetchPaymentType, fetchRidersList, finalCreatedeliveries, notifyRider } from '../../api/api';
|
||||
import { OpenToast } from 'components/third-party/OpenToast';
|
||||
@@ -561,17 +561,16 @@ const OrdersPreview = () => {
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Was four <HoverSocialCard>s — solid-colour tiles that were the only
|
||||
KPI design in the app not coming from StatCard, so this row looked
|
||||
nothing like the KPI row on the other fifteen pages. Same data,
|
||||
same semantic colours, shared tile. */}
|
||||
{aiMode == 1 && (
|
||||
<KpiGrid>
|
||||
<HoverSocialCard
|
||||
secondary={metaData?.total_orders}
|
||||
primary="Orders"
|
||||
percentage={<MdDirectionsBike />}
|
||||
color={STATUS.delivered}
|
||||
/>
|
||||
<HoverSocialCard secondary={metaData?.total_riders} primary="Riders" percentage={<MdDirectionsBike />} color={STATUS.pending} />
|
||||
<HoverSocialCard secondary={zoneData?.length} primary="Zones" percentage={<FaMapLocationDot />} color="#0ea5e9" />
|
||||
<HoverSocialCard secondary={zoneData?.length} primary="Kilometer" percentage={<FaMapLocationDot />} color={STATUS.cancelled} />
|
||||
<StatCard title="Orders" value={metaData?.total_orders} icon={<MdDirectionsBike size={20} />} color={STATUS.delivered} />
|
||||
<StatCard title="Riders" value={metaData?.total_riders} icon={<MdDirectionsBike size={20} />} color={STATUS.pending} />
|
||||
<StatCard title="Zones" value={zoneData?.length} icon={<FaMapLocationDot size={20} />} color={STATUS.info} />
|
||||
<StatCard title="Kilometer" value={zoneData?.length} icon={<FaMapLocationDot size={20} />} color={STATUS.cancelled} />
|
||||
</KpiGrid>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user