Refine auth layouts and add seed login shortcuts
This commit is contained in:
@@ -76,14 +76,14 @@ export default function RegisterPage() {
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main className="auth-screen">
|
||||
<main className="auth-screen auth-screen--merchant-register">
|
||||
<section className="auth-copy">
|
||||
<span className="eyebrow">TPOS TRIAL</span>
|
||||
<h1>Tạo tài khoản doanh nghiệp</h1>
|
||||
<p>Bắt đầu dùng thử miễn phí cho merchant, cửa hàng hoặc chuỗi vận hành. Không cần thẻ tín dụng.</p>
|
||||
</section>
|
||||
|
||||
<form className="auth-card auth-card--orange" onSubmit={submit}>
|
||||
<form className="auth-card auth-card--orange auth-card--register" onSubmit={submit}>
|
||||
<div className="auth-card__head">
|
||||
<span className="auth-role-badge auth-role-badge--orange">
|
||||
<Building2 size={14} />
|
||||
@@ -98,53 +98,55 @@ export default function RegisterPage() {
|
||||
<span>Thiết lập trong vài phút, có thể thêm cửa hàng và nhân viên sau khi đăng nhập.</span>
|
||||
</div>
|
||||
|
||||
<label className="auth-field">
|
||||
<span>Tên doanh nghiệp</span>
|
||||
<div>
|
||||
<Store size={16} />
|
||||
<input value={businessName} onChange={(event) => setBusinessName(event.target.value)} autoComplete="organization" />
|
||||
</div>
|
||||
</label>
|
||||
<div className="auth-register-grid">
|
||||
<label className="auth-field">
|
||||
<span>Tên doanh nghiệp</span>
|
||||
<div>
|
||||
<Store size={16} />
|
||||
<input value={businessName} onChange={(event) => setBusinessName(event.target.value)} autoComplete="organization" />
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label className="auth-field">
|
||||
<span>Người liên hệ</span>
|
||||
<div>
|
||||
<UserCheck size={16} />
|
||||
<input value={ownerName} onChange={(event) => setOwnerName(event.target.value)} autoComplete="name" />
|
||||
</div>
|
||||
</label>
|
||||
<label className="auth-field">
|
||||
<span>Người liên hệ</span>
|
||||
<div>
|
||||
<UserCheck size={16} />
|
||||
<input value={ownerName} onChange={(event) => setOwnerName(event.target.value)} autoComplete="name" />
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label className="auth-field">
|
||||
<span>Email</span>
|
||||
<div>
|
||||
<Mail size={16} />
|
||||
<input value={email} onChange={(event) => setEmail(event.target.value)} autoComplete="email" inputMode="email" />
|
||||
</div>
|
||||
</label>
|
||||
<label className="auth-field">
|
||||
<span>Email</span>
|
||||
<div>
|
||||
<Mail size={16} />
|
||||
<input value={email} onChange={(event) => setEmail(event.target.value)} autoComplete="email" inputMode="email" />
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label className="auth-field">
|
||||
<span>Số điện thoại</span>
|
||||
<div>
|
||||
<Phone size={16} />
|
||||
<input value={phone} onChange={(event) => setPhone(event.target.value)} autoComplete="tel" inputMode="tel" />
|
||||
</div>
|
||||
</label>
|
||||
<label className="auth-field">
|
||||
<span>Số điện thoại</span>
|
||||
<div>
|
||||
<Phone size={16} />
|
||||
<input value={phone} onChange={(event) => setPhone(event.target.value)} autoComplete="tel" inputMode="tel" />
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label className="auth-field">
|
||||
<span>Mật khẩu</span>
|
||||
<div>
|
||||
<Lock size={16} />
|
||||
<input type="password" value={password} onChange={(event) => setPassword(event.target.value)} autoComplete="new-password" />
|
||||
</div>
|
||||
</label>
|
||||
<label className="auth-field">
|
||||
<span>Mật khẩu</span>
|
||||
<div>
|
||||
<Lock size={16} />
|
||||
<input type="password" value={password} onChange={(event) => setPassword(event.target.value)} autoComplete="new-password" />
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label className="auth-field">
|
||||
<span>Xác nhận mật khẩu</span>
|
||||
<div>
|
||||
<Lock size={16} />
|
||||
<input type="password" value={confirmPassword} onChange={(event) => setConfirmPassword(event.target.value)} autoComplete="new-password" />
|
||||
</div>
|
||||
</label>
|
||||
<label className="auth-field">
|
||||
<span>Xác nhận mật khẩu</span>
|
||||
<div>
|
||||
<Lock size={16} />
|
||||
<input type="password" value={confirmPassword} onChange={(event) => setConfirmPassword(event.target.value)} autoComplete="new-password" />
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{message ? <div className={isSuccess ? "auth-message auth-message--ok" : "auth-message"}>{message}</div> : null}
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
|
||||
.login-portal__grid {
|
||||
max-width: 780px;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.login-role-card:hover {
|
||||
border-color: var(--role-color);
|
||||
transform: translateY(-2px);
|
||||
@@ -51,6 +56,10 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.login-portal__grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.tpos-mobile-nav {
|
||||
display: block;
|
||||
}
|
||||
@@ -156,8 +165,56 @@
|
||||
min-height: calc(100vh - 72px);
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.auth-screen--merchant-register {
|
||||
width: 100%;
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
grid-template-columns: minmax(280px, 360px) minmax(0, 760px);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.auth-screen--merchant-register .auth-copy {
|
||||
max-width: 360px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.auth-screen--merchant-register .auth-copy h1 {
|
||||
font-size: 34px;
|
||||
}
|
||||
|
||||
.auth-screen--merchant-register .auth-copy p {
|
||||
max-width: 340px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.auth-card--register {
|
||||
max-width: 760px;
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.auth-card--register .auth-card__head {
|
||||
max-width: 560px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.auth-register-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14px 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.auth-register-grid .auth-field {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.auth-screen--split {
|
||||
grid-template-columns: minmax(520px, 720px) minmax(360px, 440px);
|
||||
grid-template-columns: minmax(0, 1fr) minmax(520px, 1fr);
|
||||
justify-content: stretch;
|
||||
align-content: stretch;
|
||||
gap: 0;
|
||||
@@ -169,7 +226,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 64px;
|
||||
padding: clamp(48px, 6vw, 120px);
|
||||
background:
|
||||
radial-gradient(circle at 20% 10%, rgba(255, 255, 255, 0.16), transparent 24%),
|
||||
linear-gradient(180deg, #ff5c00 0%, #ff8a4c 52%, #ffb347 100%);
|
||||
@@ -224,6 +281,9 @@
|
||||
.auth-screen--split .auth-card {
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
width: min(100% - 96px, 480px);
|
||||
max-width: 480px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.auth-card__head {
|
||||
@@ -251,21 +311,25 @@
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.auth-card--blue,
|
||||
.auth-role-badge--blue,
|
||||
.auth-card--blue .auth-submit {
|
||||
--auth-tone: #3b82f6;
|
||||
}
|
||||
|
||||
.auth-card--green,
|
||||
.auth-role-badge--green,
|
||||
.auth-card--green .auth-submit {
|
||||
--auth-tone: #22c55e;
|
||||
}
|
||||
|
||||
.auth-card--pink,
|
||||
.auth-role-badge--pink,
|
||||
.auth-card--pink .auth-submit {
|
||||
--auth-tone: #ec4899;
|
||||
}
|
||||
|
||||
.auth-card--orange,
|
||||
.auth-role-badge--orange,
|
||||
.auth-card--orange .auth-submit {
|
||||
--auth-tone: #ff5c00;
|
||||
@@ -284,6 +348,25 @@
|
||||
background: var(--auth-tone);
|
||||
}
|
||||
|
||||
.auth-field > div:focus-within {
|
||||
border-color: color-mix(in srgb, var(--auth-tone, #ff5c00) 54%, #2a2a2e);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--auth-tone, #ff5c00) 16%, transparent);
|
||||
}
|
||||
|
||||
.auth-field svg,
|
||||
.auth-phone-prefix {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.auth-submit {
|
||||
transition: transform 160ms ease, filter 160ms ease;
|
||||
}
|
||||
|
||||
.auth-submit:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
filter: brightness(1.06);
|
||||
}
|
||||
|
||||
.auth-security-hint {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
@@ -298,6 +381,112 @@
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.auth-demo-strip {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin: 0 0 16px;
|
||||
padding: 12px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #2a2a2e;
|
||||
border-radius: 12px;
|
||||
background: #0a0a0b;
|
||||
}
|
||||
|
||||
.auth-demo-strip__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.auth-demo-strip__head span {
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.auth-demo-strip__head small {
|
||||
color: #8b8b90;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.auth-demo-list {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: minmax(200px, 1fr);
|
||||
gap: 8px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
overscroll-behavior-x: contain;
|
||||
padding-bottom: 2px;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.auth-demo-account {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 74px;
|
||||
display: grid;
|
||||
align-content: center;
|
||||
justify-items: start;
|
||||
gap: 3px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #2a2a2e;
|
||||
border-radius: 10px;
|
||||
background: #141416;
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.auth-demo-account:hover,
|
||||
.auth-demo-account--active {
|
||||
border-color: color-mix(in srgb, var(--auth-tone, #ff5c00) 58%, #2a2a2e);
|
||||
background: color-mix(in srgb, var(--auth-tone, #ff5c00) 12%, #141416);
|
||||
}
|
||||
|
||||
.auth-demo-account span {
|
||||
color: var(--auth-tone, #ff5c00);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.auth-demo-account strong {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: #f4f4f5;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.auth-demo-account code {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
padding: 4px 7px;
|
||||
border-radius: 7px;
|
||||
background: #050506;
|
||||
color: #d4d4d8;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.auth-alt-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -362,11 +551,95 @@
|
||||
padding-left: 12px !important;
|
||||
}
|
||||
|
||||
.auth-screen--split .auth-card {
|
||||
padding: 24px 32px;
|
||||
}
|
||||
|
||||
.auth-screen--split .auth-card__head {
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.auth-screen--split .auth-card__head h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.auth-screen--split .auth-card__head p {
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.auth-screen--split .auth-security-hint {
|
||||
margin-bottom: 12px;
|
||||
padding: 9px 14px;
|
||||
}
|
||||
|
||||
.auth-screen--split .auth-demo-strip {
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.auth-screen--split .auth-demo-account {
|
||||
min-height: 34px;
|
||||
padding: 7px 10px;
|
||||
}
|
||||
|
||||
.auth-screen--split .auth-field {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.auth-screen--split .auth-field input,
|
||||
.auth-screen--split .auth-phone-prefix {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.auth-screen--split .auth-social-row {
|
||||
gap: 8px;
|
||||
margin: 8px 0 10px;
|
||||
}
|
||||
|
||||
.auth-screen--split .auth-social-row button {
|
||||
min-height: 34px;
|
||||
}
|
||||
|
||||
.auth-screen--split .auth-alt-links {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.auth-top-nav > div a:not(.btn-accent) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.auth-screen--merchant-register {
|
||||
max-width: 100%;
|
||||
grid-template-columns: minmax(0, 440px);
|
||||
}
|
||||
|
||||
.auth-screen--merchant-register .auth-copy {
|
||||
max-width: 440px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.auth-screen--merchant-register .auth-copy h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.auth-screen--merchant-register .auth-copy p {
|
||||
max-width: 360px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.auth-card--register {
|
||||
max-width: 440px;
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.auth-register-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.auth-screen--split {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@@ -376,7 +649,174 @@
|
||||
}
|
||||
|
||||
.auth-screen--split .auth-card {
|
||||
margin: 24px;
|
||||
width: calc(100% - 48px);
|
||||
margin: 24px auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.login-portal {
|
||||
justify-content: flex-start;
|
||||
padding: 88px 24px 48px;
|
||||
}
|
||||
|
||||
.login-portal__grid {
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
.login-role-card {
|
||||
min-height: 150px;
|
||||
padding: 22px 18px;
|
||||
}
|
||||
|
||||
.login-role-card p {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.auth-top-nav {
|
||||
height: 64px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.auth-top-nav > div {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.auth-top-nav .btn-accent {
|
||||
min-height: 40px;
|
||||
padding: 0 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.auth-screen {
|
||||
min-height: calc(100vh - 64px);
|
||||
align-content: start;
|
||||
padding: 32px 24px 42px;
|
||||
}
|
||||
|
||||
.auth-screen--merchant-register {
|
||||
gap: 16px;
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
padding: 24px;
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.auth-demo-strip__head {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.auth-demo-strip__head small {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.auth-demo-account {
|
||||
grid-template-columns: 1fr;
|
||||
align-items: start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.auth-demo-account strong,
|
||||
.auth-demo-account code {
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.auth-card__head h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.auth-security-hint {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.auth-screen--split {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.auth-screen--split .auth-card {
|
||||
width: calc(100% - 48px);
|
||||
max-width: calc(100% - 48px);
|
||||
margin: 24px auto;
|
||||
}
|
||||
|
||||
.auth-screen--merchant-register .auth-copy h1 {
|
||||
margin-bottom: 0;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.auth-screen--merchant-register .auth-copy p {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.auth-card--register {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.auth-card--register .auth-card__head {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.auth-card--register .auth-card__head p {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.auth-card--register .auth-security-hint {
|
||||
margin-bottom: 14px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.auth-register-grid {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.auth-card--register .auth-field > div {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.auth-card--register .auth-field input {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.auth-demo-strip {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.auth-demo-list {
|
||||
grid-auto-columns: minmax(92px, 1fr);
|
||||
}
|
||||
|
||||
.auth-demo-account {
|
||||
min-height: 38px;
|
||||
justify-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.auth-demo-account strong,
|
||||
.auth-demo-account code {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 360px) and (max-width: 520px) {
|
||||
.login-portal__grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.login-role-card {
|
||||
min-height: 142px;
|
||||
padding: 18px 12px;
|
||||
}
|
||||
|
||||
.login-role-card span {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import type { FormEvent } from "react";
|
||||
import { useMemo, useState, useTransition } from "react";
|
||||
import { useEffect, useMemo, useState, useTransition } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { ArrowRight, Heart, Lock, Mail, MessageSquare, ShieldAlert, ShieldCheck, Store, UserCheck } from "lucide-react";
|
||||
|
||||
const seedAccounts = {
|
||||
admin: { label: "Admin", email: "admin@goodgo.vn", password: "Admin@123" },
|
||||
staff: { label: "Staff", email: "staff@goodgo.vn", password: "Staff@123" },
|
||||
marketing: { label: "Marketing", email: "marketing@goodgo.vn", password: "Marketing@123" },
|
||||
customer: { label: "Customer", email: "customer@goodgo.vn", password: "Customer@123" },
|
||||
superadmin: { label: "Superadmin", email: "superadmin@goodgo.vn", password: "SuperAdmin@123" }
|
||||
} as const;
|
||||
|
||||
type SeedRole = keyof typeof seedAccounts;
|
||||
type AuthRole = SeedRole | "branch";
|
||||
|
||||
const demoAccounts = [
|
||||
{ role: "admin", ...seedAccounts.admin },
|
||||
{ role: "staff", ...seedAccounts.staff },
|
||||
{ role: "marketing", ...seedAccounts.marketing },
|
||||
{ role: "customer", ...seedAccounts.customer },
|
||||
{ role: "superadmin", ...seedAccounts.superadmin }
|
||||
] satisfies Array<{ role: SeedRole; label: string; email: string; password: string }>;
|
||||
|
||||
type DemoAccount = (typeof demoAccounts)[number];
|
||||
|
||||
type RoleCard = {
|
||||
role: string;
|
||||
role: AuthRole;
|
||||
title: string;
|
||||
heading: string;
|
||||
subtitle: string;
|
||||
@@ -38,8 +59,8 @@ const roleCards: RoleCard[] = [
|
||||
submit: "Đăng nhập bảo mật",
|
||||
href: "/auth/login/admin",
|
||||
icon: ShieldCheck,
|
||||
email: "",
|
||||
password: "",
|
||||
email: seedAccounts.admin.email,
|
||||
password: seedAccounts.admin.password,
|
||||
tone: "blue",
|
||||
links: [{ label: "Chi nhánh", href: "/auth/login/branch" }, { label: "Nhân viên", href: "/auth/login/staff" }]
|
||||
},
|
||||
@@ -73,8 +94,8 @@ const roleCards: RoleCard[] = [
|
||||
submit: "Đăng nhập ca làm việc",
|
||||
href: "/auth/login/staff",
|
||||
icon: UserCheck,
|
||||
email: "",
|
||||
password: "",
|
||||
email: seedAccounts.staff.email,
|
||||
password: seedAccounts.staff.password,
|
||||
tone: "green",
|
||||
links: [{ label: "Liên hệ quản lý", href: "/#contact" }, { label: "Admin", href: "/auth/login/admin" }]
|
||||
},
|
||||
@@ -89,8 +110,8 @@ const roleCards: RoleCard[] = [
|
||||
submit: "Tiếp tục",
|
||||
href: "/auth/login/customer",
|
||||
icon: Heart,
|
||||
email: "",
|
||||
password: "",
|
||||
email: seedAccounts.customer.email,
|
||||
password: seedAccounts.customer.password,
|
||||
tone: "pink",
|
||||
split: true,
|
||||
brandTitle: "aPOS Loyalty",
|
||||
@@ -108,8 +129,8 @@ const roleCards: RoleCard[] = [
|
||||
submit: "Vào marketing",
|
||||
href: "/auth/login/marketing",
|
||||
icon: MessageSquare,
|
||||
email: "",
|
||||
password: "",
|
||||
email: seedAccounts.marketing.email,
|
||||
password: seedAccounts.marketing.password,
|
||||
tone: "pink",
|
||||
links: [{ label: "Admin", href: "/auth/login/admin" }, { label: "Nhân viên", href: "/auth/login/staff" }]
|
||||
},
|
||||
@@ -124,8 +145,8 @@ const roleCards: RoleCard[] = [
|
||||
submit: "Vào platform",
|
||||
href: "/auth/login/superadmin",
|
||||
icon: ShieldAlert,
|
||||
email: "",
|
||||
password: "",
|
||||
email: seedAccounts.superadmin.email,
|
||||
password: seedAccounts.superadmin.password,
|
||||
tone: "orange",
|
||||
links: [{ label: "Admin", href: "/auth/login/admin" }]
|
||||
}
|
||||
@@ -136,15 +157,33 @@ export function TposAuth({ mode = "login", role = "admin" }: { mode?: string; ro
|
||||
const searchParams = useSearchParams();
|
||||
const selected = useMemo(() => roleCards.find((card) => card.role === role) ?? roleCards[0], [role]);
|
||||
const isCustomerLogin = selected.role === "customer";
|
||||
const demoRole = searchParams.get("demo");
|
||||
const [email, setEmail] = useState(selected.email);
|
||||
const [password, setPassword] = useState(selected.password);
|
||||
const [displayName, setDisplayName] = useState("");
|
||||
const [phone, setPhone] = useState("");
|
||||
const [phone, setPhone] = useState(isCustomerLogin ? selected.email : "");
|
||||
const [otp, setOtp] = useState("");
|
||||
const [message, setMessage] = useState<string | null>(null);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const SelectedIcon = selected.icon;
|
||||
|
||||
useEffect(() => {
|
||||
const requestedDemo = demoAccounts.find((account) => account.role === demoRole);
|
||||
const roleDemo = demoAccounts.find((account) => account.role === selected.role);
|
||||
const account = requestedDemo?.role === selected.role ? requestedDemo : roleDemo;
|
||||
setEmail(account?.email ?? selected.email);
|
||||
setPassword(account?.password ?? selected.password);
|
||||
setPhone(account?.role === "customer" ? account.email : "");
|
||||
setMessage(null);
|
||||
}, [demoRole, selected.email, selected.password, selected.role]);
|
||||
|
||||
function demoLoginHref(account: DemoAccount) {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
params.set("demo", account.role);
|
||||
if (account.role !== selected.role) params.delete("returnUrl");
|
||||
return `/auth/login/${account.role}?${params.toString()}`;
|
||||
}
|
||||
|
||||
async function submit(event?: FormEvent<HTMLFormElement>) {
|
||||
event?.preventDefault();
|
||||
setMessage(null);
|
||||
@@ -283,6 +322,26 @@ export function TposAuth({ mode = "login", role = "admin" }: { mode?: string; ro
|
||||
<ShieldCheck size={16} />
|
||||
<span>{selected.hint}</span>
|
||||
</div>
|
||||
<div className="auth-demo-strip" aria-label="Tài khoản seed demo">
|
||||
<div className="auth-demo-strip__head">
|
||||
<span>Tài khoản seed demo</span>
|
||||
<small>Nhấn để tự điền</small>
|
||||
</div>
|
||||
<div className="auth-demo-list">
|
||||
{demoAccounts.map((account) => (
|
||||
<Link
|
||||
key={account.role}
|
||||
href={demoLoginHref(account)}
|
||||
className={account.role === selected.role ? "auth-demo-account auth-demo-account--active" : "auth-demo-account"}
|
||||
aria-current={account.role === selected.role ? "page" : undefined}
|
||||
>
|
||||
<span>{account.label}</span>
|
||||
<strong>{account.email}</strong>
|
||||
<code>{account.password}</code>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{isCustomerLogin ? (
|
||||
<>
|
||||
<label className="auth-field">
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import Link from "next/link";
|
||||
import type { CSSProperties } from "react";
|
||||
import { Building2, Heart, Store, UserCheck } from "lucide-react";
|
||||
import { Building2, Heart, MessageSquare, ShieldAlert, Store, UserCheck } from "lucide-react";
|
||||
|
||||
const roles = [
|
||||
{ title: "Chủ doanh nghiệp", href: "/auth/login/admin", icon: Building2, color: "#3B82F6", description: "Quản lý toàn bộ hệ thống, cửa hàng, nhân viên" },
|
||||
{ title: "Quản lý chi nhánh", href: "/auth/login/branch", icon: Store, color: "#8B5CF6", description: "Quản lý chi nhánh, ca làm việc, báo cáo" },
|
||||
{ title: "Nhân viên", href: "/auth/login/staff", icon: UserCheck, color: "#22C55E", description: "Thu ngân, barista, phục vụ, bếp" },
|
||||
{ title: "Khách hàng", href: "/auth/login/customer", icon: Heart, color: "#EC4899", description: "Tích điểm, ưu đãi, lịch sử mua hàng" }
|
||||
{ title: "Marketing", href: "/auth/login/marketing", icon: MessageSquare, color: "#EC4899", description: "Campaign, livechat, khách hàng và AI chatbot" },
|
||||
{ title: "Khách hàng", href: "/auth/login/customer", icon: Heart, color: "#F43F5E", description: "Tích điểm, ưu đãi, lịch sử mua hàng" },
|
||||
{ title: "Super Admin", href: "/auth/login/superadmin", icon: ShieldAlert, color: "#FF5C00", description: "Platform, merchant, feature flags và health checks" }
|
||||
];
|
||||
|
||||
export function TposLoginPortal() {
|
||||
|
||||
Reference in New Issue
Block a user