Files
solution-erp/fe-user/src/pages/LoginPage.tsx
pqhuy1987 4abb5596d5
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m54s
[CLAUDE] FE-Admin+FE-User: brand identity từ Solutions logo
Lấy logo gốc từ template docx (SOL-CCM-FO-002.05) và brand color
exact pixel-sampled #1F7DC1 từ chữ "Solutions".

Thay đổi:
- logo.png (407x145, từ header docx) đặt vào /public cả 2 app
- favicon.svg: "S" trắng trên nền vuông brand blue bo góc
- index.css: palette brand-50..900 generate quanh #1F7DC1 + accent
  red-500/600 cho ® mark + font Be Vietnam Pro (Google Fonts,
  designed cho tiếng Việt, diacritics đẹp) với fallback Inter
  + JetBrains Mono cho font-mono + tùy chỉnh scrollbar
- Layout sidebar: logo.png 32px + "Admin"/"ERP" subtitle (thay
  text "SOLUTION ERP" đơn điệu)
- LoginPage: gradient background brand-50 + 2 decorative orbs
  blur, rounded-2xl card + backdrop-blur, big logo 56px + subtitle
  tracking-[0.2em]
- index.html: lang="vi", title "Solutions ERP · Admin" / "Solutions
  ERP", theme-color #1F7DC1 cho mobile address bar, preconnect
  fonts.gstatic.com để load Google Fonts nhanh hơn

Tất cả màu hardcoded trong component đã dùng `brand-600` → tự
map sang palette mới, không cần đổi logic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 15:57:45 +07:00

88 lines
3.6 KiB
TypeScript

import { useState, type FormEvent } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import { toast } from 'sonner'
import { Button } from '@/components/ui/Button'
import { Input } from '@/components/ui/Input'
import { Label } from '@/components/ui/Label'
import { useAuth } from '@/contexts/AuthContext'
import axios from 'axios'
export function LoginPage() {
const { login } = useAuth()
const navigate = useNavigate()
const location = useLocation()
const [email, setEmail] = useState('admin@solutionerp.local')
const [password, setPassword] = useState('Admin@123456')
const [isSubmitting, setIsSubmitting] = useState(false)
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault()
setIsSubmitting(true)
try {
await login({ email, password })
const redirectTo = (location.state as { from?: { pathname: string } } | null)?.from?.pathname ?? '/inbox'
navigate(redirectTo, { replace: true })
toast.success('Đăng nhập thành công')
} catch (err) {
let msg: string
if (axios.isAxiosError(err)) {
if (err.code === 'ERR_NETWORK' || err.message === 'Network Error') {
const apiUrl = (import.meta.env.VITE_API_BASE_URL ?? window.location.origin) + '/api'
msg = `Không thể kết nối ${apiUrl}. Check dev tools F12 → Network. Có thể do cache cũ — thử Ctrl+Shift+R hoặc tab ẩn danh.`
} else {
msg = err.response?.data?.detail ?? err.response?.data?.title ?? err.message
}
} else {
msg = 'Lỗi không xác định'
}
toast.error(`Đăng nhập thất bại: ${msg}`, { duration: 8000 })
} finally {
setIsSubmitting(false)
}
}
return (
<div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-gradient-to-br from-slate-50 via-brand-50 to-slate-100 px-4">
<div className="pointer-events-none absolute -left-32 -top-32 h-96 w-96 rounded-full bg-brand-200/40 blur-3xl" />
<div className="pointer-events-none absolute -bottom-32 -right-32 h-96 w-96 rounded-full bg-brand-300/30 blur-3xl" />
<div className="relative w-full max-w-md rounded-2xl border border-slate-200/70 bg-white/90 p-10 shadow-xl backdrop-blur">
<div className="mb-8 flex flex-col items-center text-center">
<img src="/logo.png" alt="Solutions" className="h-14 w-auto" />
<div className="mt-4 text-xs font-semibold uppercase tracking-[0.2em] text-brand-600">
ERP · Quản hợp đng
</div>
<div className="mt-1 text-sm text-slate-500">Đăng nhập đ tiếp tục</div>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-1.5">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
autoComplete="email"
required
/>
</div>
<div className="space-y-1.5">
<Label htmlFor="password">Mật khẩu</Label>
<Input
id="password"
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
autoComplete="current-password"
required
/>
</div>
<Button type="submit" className="w-full" disabled={isSubmitting}>
{isSubmitting ? 'Đang đăng nhập…' : 'Đăng nhập'}
</Button>
</form>
</div>
</div>
)
}