Cài đặt thành công tính năng người dùng đăng nhập

This commit is contained in:
2026-06-13 17:57:00 +07:00
parent f38d7e01a1
commit ea5799ffb5
15 changed files with 176 additions and 21 deletions
+13
View File
@@ -8,11 +8,18 @@ const App = () => {
type View = 'landing' | 'explore' | 'detail' | 'signup';
const [view, setView] = useState<View>('landing');
const [isInitialSetup, setIsInitialSetup] = useState(false);
const [user, setUser] = useState<any>(null);
useEffect(() => {
// Tự động xác định địa chỉ IP của Backend dựa trên hostname hiện tại
const API_BASE = `http://${window.location.hostname}:3001`;
// Khôi phục phiên đăng nhập từ localStorage
const savedUser = localStorage.getItem('user');
if (savedUser) {
setUser(JSON.parse(savedUser));
}
// Kiểm tra xem hệ thống đã được cài đặt chưa
fetch(`${API_BASE}/api/v1/auth/status`)
.then(res => res.ok ? res.json() : Promise.reject())
@@ -20,6 +27,11 @@ const App = () => {
.catch(() => setIsInitialSetup(false));
}, [view]);
const handleLoginSuccess = (userData: any) => {
setUser(userData);
setView('explore'); // Chuyển sang bản đồ khám phá sau khi đăng nhập
};
return (
<div className="app-container">
{view === 'landing' && (
@@ -28,6 +40,7 @@ const App = () => {
onContinue={() => setView('explore')}
onGoToSignup={() => setView('signup')}
onGoToMap={() => setView('explore')}
onLoginSuccess={handleLoginSuccess}
/>
)}
+3 -1
View File
@@ -15,10 +15,11 @@ interface LandingPageProps {
onContinue?: () => void;
onGoToSignup?: () => void;
onGoToMap?: () => void;
onLoginSuccess?: (user: any) => void;
isInitialSetup?: boolean;
}
export const LandingPage: React.FC<LandingPageProps> = ({ onContinue, onGoToSignup, onGoToMap, isInitialSetup }) => {
export const LandingPage: React.FC<LandingPageProps> = ({ onContinue, onGoToSignup, onGoToMap, onLoginSuccess, isInitialSetup }) => {
const [isLoginModalOpen, setIsLoginModalOpen] = useState(false);
// Chọn ngẫu nhiên một hình ảnh khi người dùng truy cập trang
@@ -154,6 +155,7 @@ export const LandingPage: React.FC<LandingPageProps> = ({ onContinue, onGoToSign
isOpen={isLoginModalOpen}
onClose={() => setIsLoginModalOpen(false)}
onSwitchToSignup={onGoToSignup}
onLoginSuccess={onLoginSuccess}
/>
</div>
);
+59 -7
View File
@@ -1,15 +1,54 @@
import React from 'react';
import { X, Mail, Lock, ArrowRight } from 'lucide-react';
import React, { useState } from 'react';
import { X, Mail, Lock, ArrowRight, Loader2 } from 'lucide-react';
interface LoginModalProps {
isOpen: boolean;
onClose: () => void;
onSwitchToSignup?: () => void;
onLoginSuccess?: (user: any) => void;
}
export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose, onSwitchToSignup }) => {
export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose, onSwitchToSignup, onLoginSuccess }) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
if (!isOpen) return null;
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setIsLoading(true);
try {
const API_BASE = `http://${window.location.hostname}:3001`;
const response = await fetch(`${API_BASE}/api/v1/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || 'Đăng nhập thất bại');
}
// Lưu phiên đăng nhập
localStorage.setItem('token', data.access_token);
localStorage.setItem('user', JSON.stringify(data.user));
if (onLoginSuccess) {
onLoginSuccess(data.user);
}
onClose();
} catch (err: any) {
setError(err.message);
} finally {
setIsLoading(false);
}
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6">
{/* Backdrop */}
@@ -34,7 +73,13 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose, onSwitc
</button>
</div>
<form className="space-y-6" onSubmit={(e) => e.preventDefault()}>
{error && (
<div className="mb-6 p-4 bg-red-50 text-red-600 rounded-2xl text-sm font-bold animate-in fade-in slide-in-from-top-1">
{error}
</div>
)}
<form className="space-y-6" onSubmit={handleSubmit}>
<div className="space-y-2">
<label className="text-sm font-semibold text-gray-700 ml-1">Email</label>
<div className="relative group">
@@ -42,6 +87,9 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose, onSwitc
<input
type="email"
placeholder="name@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="w-full pl-12 pr-4 py-4 bg-gray-50 border border-gray-100 rounded-2xl focus:ring-2 focus:ring-blue-500 focus:bg-white outline-none transition-all"
/>
</div>
@@ -57,6 +105,9 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose, onSwitc
<input
type="password"
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
className="w-full pl-12 pr-4 py-4 bg-gray-50 border border-gray-100 rounded-2xl focus:ring-2 focus:ring-blue-500 focus:bg-white outline-none transition-all"
/>
</div>
@@ -64,10 +115,11 @@ export const LoginModal: React.FC<LoginModalProps> = ({ isOpen, onClose, onSwitc
<button
type="submit"
className="w-full flex items-center justify-center gap-2 bg-blue-600 hover:bg-blue-700 text-white font-bold py-4 rounded-2xl shadow-lg shadow-blue-200 transition-all active:scale-[0.98]"
disabled={isLoading}
className="w-full flex items-center justify-center gap-2 bg-blue-600 hover:bg-blue-700 text-white font-bold py-4 rounded-2xl shadow-lg shadow-blue-200 transition-all active:scale-[0.98] disabled:opacity-50"
>
Đăng nhập
<ArrowRight className="w-5 h-5" />
{isLoading ? 'Đang xử lý...' : 'Đăng nhập'}
{isLoading ? <Loader2 className="w-5 h-5 animate-spin" /> : <ArrowRight className="w-5 h-5" />}
</button>
</form>
Vendored
+10 -1
View File
@@ -7,14 +7,23 @@ import { SignupPage } from './SignupPage.js';
const App = () => {
const [view, setView] = useState('landing');
const [isInitialSetup, setIsInitialSetup] = useState(false);
const [user, setUser] = useState(null);
useEffect(() => {
const API_BASE = `http://${window.location.hostname}:3001`;
const savedUser = localStorage.getItem('user');
if (savedUser) {
setUser(JSON.parse(savedUser));
}
fetch(`${API_BASE}/api/v1/auth/status`)
.then(res => res.ok ? res.json() : Promise.reject())
.then(data => setIsInitialSetup(!!data.isInitialSetup))
.catch(() => setIsInitialSetup(false));
}, [view]);
return (_jsxs("div", { className: "app-container", children: [view === 'landing' && (_jsx(LandingPage, { isInitialSetup: isInitialSetup, onContinue: () => setView('explore'), onGoToSignup: () => setView('signup'), onGoToMap: () => setView('explore') })), view === 'signup' && (_jsx(SignupPage, { onBack: () => setView('landing'), onSuccess: () => setView('landing') })), view === 'explore' && (_jsx(ExploreMap, { onBack: () => setView('landing') })), view === 'detail' && (_jsx(TourDetailPage, {}))] }));
const handleLoginSuccess = (userData) => {
setUser(userData);
setView('explore');
};
return (_jsxs("div", { className: "app-container", children: [view === 'landing' && (_jsx(LandingPage, { isInitialSetup: isInitialSetup, onContinue: () => setView('explore'), onGoToSignup: () => setView('signup'), onGoToMap: () => setView('explore'), onLoginSuccess: handleLoginSuccess })), view === 'signup' && (_jsx(SignupPage, { onBack: () => setView('landing'), onSuccess: () => setView('landing') })), view === 'explore' && (_jsx(ExploreMap, { onBack: () => setView('landing') })), view === 'detail' && (_jsx(TourDetailPage, {}))] }));
};
export default App;
//# sourceMappingURL=App.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"App.js","sourceRoot":"","sources":["../App.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,GAAG,GAAG,GAAG,EAAE;IAEf,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAO,SAAS,CAAC,CAAC;IAClD,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAE5D,SAAS,CAAC,GAAG,EAAE;QAEb,MAAM,QAAQ,GAAG,UAAU,MAAM,CAAC,QAAQ,CAAC,QAAQ,OAAO,CAAC;QAG3D,KAAK,CAAC,GAAG,QAAQ,qBAAqB,CAAC;aACpC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;aACnD,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;aACtD,KAAK,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3C,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,OAAO,CACL,eAAK,SAAS,EAAC,eAAe,aAC3B,IAAI,KAAK,SAAS,IAAI,CACrB,KAAC,WAAW,IACV,cAAc,EAAE,cAAc,EAC9B,UAAU,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,EACpC,YAAY,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EACrC,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,GACnC,CACH,EAEA,IAAI,KAAK,QAAQ,IAAI,CACpB,KAAC,UAAU,IACT,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,EAChC,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,GACnC,CACH,EAEA,IAAI,KAAK,SAAS,IAAI,CACrB,KAAC,UAAU,IAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,GAAI,CACjD,EAEA,IAAI,KAAK,QAAQ,IAAI,CACpB,KAAC,cAAc,KAAG,CACnB,IACG,CACP,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,GAAG,CAAC"}
{"version":3,"file":"App.js","sourceRoot":"","sources":["../App.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,MAAM,GAAG,GAAG,GAAG,EAAE;IAEf,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAO,SAAS,CAAC,CAAC;IAClD,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC5D,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAM,IAAI,CAAC,CAAC;IAE5C,SAAS,CAAC,GAAG,EAAE;QAEb,MAAM,QAAQ,GAAG,UAAU,MAAM,CAAC,QAAQ,CAAC,QAAQ,OAAO,CAAC;QAG3D,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC/C,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;QACjC,CAAC;QAGD,KAAK,CAAC,GAAG,QAAQ,qBAAqB,CAAC;aACpC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;aACnD,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;aACtD,KAAK,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3C,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,MAAM,kBAAkB,GAAG,CAAC,QAAa,EAAE,EAAE;QAC3C,OAAO,CAAC,QAAQ,CAAC,CAAC;QAClB,OAAO,CAAC,SAAS,CAAC,CAAC;IACrB,CAAC,CAAC;IAEF,OAAO,CACL,eAAK,SAAS,EAAC,eAAe,aAC3B,IAAI,KAAK,SAAS,IAAI,CACrB,KAAC,WAAW,IACV,cAAc,EAAE,cAAc,EAC9B,UAAU,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,EACpC,YAAY,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,EACrC,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,EACnC,cAAc,EAAE,kBAAkB,GAClC,CACH,EAEA,IAAI,KAAK,QAAQ,IAAI,CACpB,KAAC,UAAU,IACT,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,EAChC,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,GACnC,CACH,EAEA,IAAI,KAAK,SAAS,IAAI,CACrB,KAAC,UAAU,IAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,GAAI,CACjD,EAEA,IAAI,KAAK,QAAQ,IAAI,CACpB,KAAC,cAAc,KAAG,CACnB,IACG,CACP,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,GAAG,CAAC"}
+1
View File
@@ -3,6 +3,7 @@ interface LandingPageProps {
onContinue?: () => void;
onGoToSignup?: () => void;
onGoToMap?: () => void;
onLoginSuccess?: (user: any) => void;
isInitialSetup?: boolean;
}
export declare const LandingPage: React.FC<LandingPageProps>;
+2 -2
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"LandingPage.js","sourceRoot":"","sources":["../LandingPage.tsx"],"names":[],"mappings":"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"}
{"version":3,"file":"LandingPage.js","sourceRoot":"","sources":["../LandingPage.tsx"],"names":[],"mappings":"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"}
+1
View File
@@ -3,6 +3,7 @@ interface LoginModalProps {
isOpen: boolean;
onClose: () => void;
onSwitchToSignup?: () => void;
onLoginSuccess?: (user: any) => void;
}
export declare const LoginModal: React.FC<LoginModalProps>;
export {};
+37 -3
View File
@@ -1,8 +1,42 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { X, Mail, Lock, ArrowRight } from 'lucide-react';
export const LoginModal = ({ isOpen, onClose, onSwitchToSignup }) => {
import { useState } from 'react';
import { X, Mail, Lock, ArrowRight, Loader2 } from 'lucide-react';
export const LoginModal = ({ isOpen, onClose, onSwitchToSignup, onLoginSuccess }) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
if (!isOpen)
return null;
return (_jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6", children: [_jsx("div", { className: "absolute inset-0 bg-gray-900/60 backdrop-blur-sm animate-in fade-in duration-300", onClick: onClose }), _jsx("div", { className: "relative w-full max-w-md bg-white rounded-3xl shadow-2xl overflow-hidden animate-in zoom-in-95 duration-300", children: _jsxs("div", { className: "p-8 sm:p-10", children: [_jsxs("div", { className: "flex justify-between items-start mb-8", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-3xl font-bold text-gray-900", children: "\u0110\u0103ng nh\u1EADp" }), _jsx("p", { className: "text-gray-500 mt-2", children: "Ch\u00E0o m\u1EEBng b\u1EA1n quay tr\u1EDF l\u1EA1i!" })] }), _jsx("button", { onClick: onClose, className: "p-2 hover:bg-gray-100 rounded-full transition-colors text-gray-400 hover:text-gray-600", children: _jsx(X, { className: "w-6 h-6" }) })] }), _jsxs("form", { className: "space-y-6", onSubmit: (e) => e.preventDefault(), children: [_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm font-semibold text-gray-700 ml-1", children: "Email" }), _jsxs("div", { className: "relative group", children: [_jsx(Mail, { className: "absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 group-focus-within:text-blue-500 transition-colors" }), _jsx("input", { type: "email", placeholder: "name@example.com", className: "w-full pl-12 pr-4 py-4 bg-gray-50 border border-gray-100 rounded-2xl focus:ring-2 focus:ring-blue-500 focus:bg-white outline-none transition-all" })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex justify-between items-center px-1", children: [_jsx("label", { className: "text-sm font-semibold text-gray-700", children: "M\u1EADt kh\u1EA9u" }), _jsx("button", { className: "text-xs font-bold text-blue-600 hover:text-blue-700", children: "Qu\u00EAn m\u1EADt kh\u1EA9u?" })] }), _jsxs("div", { className: "relative group", children: [_jsx(Lock, { className: "absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 group-focus-within:text-blue-500 transition-colors" }), _jsx("input", { type: "password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", className: "w-full pl-12 pr-4 py-4 bg-gray-50 border border-gray-100 rounded-2xl focus:ring-2 focus:ring-blue-500 focus:bg-white outline-none transition-all" })] })] }), _jsxs("button", { type: "submit", className: "w-full flex items-center justify-center gap-2 bg-blue-600 hover:bg-blue-700 text-white font-bold py-4 rounded-2xl shadow-lg shadow-blue-200 transition-all active:scale-[0.98]", children: ["\u0110\u0103ng nh\u1EADp", _jsx(ArrowRight, { className: "w-5 h-5" })] })] }), _jsx("div", { className: "mt-10 pt-8 border-t border-gray-100 text-center", children: _jsxs("p", { className: "text-gray-500", children: ["Ch\u01B0a c\u00F3 t\u00E0i kho\u1EA3n?", ' ', _jsx("button", { onClick: () => { onClose(); onSwitchToSignup?.(); }, className: "font-bold text-blue-600 hover:underline", children: "T\u1EA1o t\u00E0i kho\u1EA3n ngay" })] }) })] }) })] }));
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
setIsLoading(true);
try {
const API_BASE = `http://${window.location.hostname}:3001`;
const response = await fetch(`${API_BASE}/api/v1/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || 'Đăng nhập thất bại');
}
localStorage.setItem('token', data.access_token);
localStorage.setItem('user', JSON.stringify(data.user));
if (onLoginSuccess) {
onLoginSuccess(data.user);
}
onClose();
}
catch (err) {
setError(err.message);
}
finally {
setIsLoading(false);
}
};
return (_jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6", children: [_jsx("div", { className: "absolute inset-0 bg-gray-900/60 backdrop-blur-sm animate-in fade-in duration-300", onClick: onClose }), _jsx("div", { className: "relative w-full max-w-md bg-white rounded-3xl shadow-2xl overflow-hidden animate-in zoom-in-95 duration-300", children: _jsxs("div", { className: "p-8 sm:p-10", children: [_jsxs("div", { className: "flex justify-between items-start mb-8", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-3xl font-bold text-gray-900", children: "\u0110\u0103ng nh\u1EADp" }), _jsx("p", { className: "text-gray-500 mt-2", children: "Ch\u00E0o m\u1EEBng b\u1EA1n quay tr\u1EDF l\u1EA1i!" })] }), _jsx("button", { onClick: onClose, className: "p-2 hover:bg-gray-100 rounded-full transition-colors text-gray-400 hover:text-gray-600", children: _jsx(X, { className: "w-6 h-6" }) })] }), error && (_jsx("div", { className: "mb-6 p-4 bg-red-50 text-red-600 rounded-2xl text-sm font-bold animate-in fade-in slide-in-from-top-1", children: error })), _jsxs("form", { className: "space-y-6", onSubmit: handleSubmit, children: [_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm font-semibold text-gray-700 ml-1", children: "Email" }), _jsxs("div", { className: "relative group", children: [_jsx(Mail, { className: "absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 group-focus-within:text-blue-500 transition-colors" }), _jsx("input", { type: "email", placeholder: "name@example.com", value: email, onChange: (e) => setEmail(e.target.value), required: true, className: "w-full pl-12 pr-4 py-4 bg-gray-50 border border-gray-100 rounded-2xl focus:ring-2 focus:ring-blue-500 focus:bg-white outline-none transition-all" })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex justify-between items-center px-1", children: [_jsx("label", { className: "text-sm font-semibold text-gray-700", children: "M\u1EADt kh\u1EA9u" }), _jsx("button", { className: "text-xs font-bold text-blue-600 hover:text-blue-700", children: "Qu\u00EAn m\u1EADt kh\u1EA9u?" })] }), _jsxs("div", { className: "relative group", children: [_jsx(Lock, { className: "absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400 group-focus-within:text-blue-500 transition-colors" }), _jsx("input", { type: "password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", value: password, onChange: (e) => setPassword(e.target.value), required: true, className: "w-full pl-12 pr-4 py-4 bg-gray-50 border border-gray-100 rounded-2xl focus:ring-2 focus:ring-blue-500 focus:bg-white outline-none transition-all" })] })] }), _jsxs("button", { type: "submit", disabled: isLoading, className: "w-full flex items-center justify-center gap-2 bg-blue-600 hover:bg-blue-700 text-white font-bold py-4 rounded-2xl shadow-lg shadow-blue-200 transition-all active:scale-[0.98] disabled:opacity-50", children: [isLoading ? 'Đang xử lý...' : 'Đăng nhập', isLoading ? _jsx(Loader2, { className: "w-5 h-5 animate-spin" }) : _jsx(ArrowRight, { className: "w-5 h-5" })] })] }), _jsx("div", { className: "mt-10 pt-8 border-t border-gray-100 text-center", children: _jsxs("p", { className: "text-gray-500", children: ["Ch\u01B0a c\u00F3 t\u00E0i kho\u1EA3n?", ' ', _jsx("button", { onClick: () => { onClose(); onSwitchToSignup?.(); }, className: "font-bold text-blue-600 hover:underline", children: "T\u1EA1o t\u00E0i kho\u1EA3n ngay" })] }) })] }) })] }));
};
//# sourceMappingURL=LoginModal.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"LoginModal.js","sourceRoot":"","sources":["../LoginModal.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAQzD,MAAM,CAAC,MAAM,UAAU,GAA8B,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,EAAE,EAAE;IAC7F,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,OAAO,CACL,eAAK,SAAS,EAAC,gEAAgE,aAE7E,cACE,SAAS,EAAC,kFAAkF,EAC5F,OAAO,EAAE,OAAO,GAChB,EAGF,cAAK,SAAS,EAAC,6GAA6G,YAC1H,eAAK,SAAS,EAAC,aAAa,aAC1B,eAAK,SAAS,EAAC,uCAAuC,aACpD,0BACE,aAAI,SAAS,EAAC,kCAAkC,yCAAe,EAC/D,YAAG,SAAS,EAAC,oBAAoB,qEAAgC,IAC7D,EACN,iBACE,OAAO,EAAE,OAAO,EAChB,SAAS,EAAC,wFAAwF,YAElG,KAAC,CAAC,IAAC,SAAS,EAAC,SAAS,GAAG,GAClB,IACL,EAEN,gBAAM,SAAS,EAAC,WAAW,EAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,aAC7D,eAAK,SAAS,EAAC,WAAW,aACxB,gBAAO,SAAS,EAAC,0CAA0C,sBAAc,EACzE,eAAK,SAAS,EAAC,gBAAgB,aAC7B,KAAC,IAAI,IAAC,SAAS,EAAC,mHAAmH,GAAG,EACtI,gBACE,IAAI,EAAC,OAAO,EACZ,WAAW,EAAC,kBAAkB,EAC9B,SAAS,EAAC,kJAAkJ,GAC5J,IACE,IACF,EAEN,eAAK,SAAS,EAAC,WAAW,aACxB,eAAK,SAAS,EAAC,wCAAwC,aACrD,gBAAO,SAAS,EAAC,qCAAqC,mCAAiB,EACvE,iBAAQ,SAAS,EAAC,qDAAqD,8CAAwB,IAC3F,EACN,eAAK,SAAS,EAAC,gBAAgB,aAC7B,KAAC,IAAI,IAAC,SAAS,EAAC,mHAAmH,GAAG,EACtI,gBACE,IAAI,EAAC,UAAU,EACf,WAAW,EAAC,kDAAU,EACtB,SAAS,EAAC,kJAAkJ,GAC5J,IACE,IACF,EAEN,kBACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,gLAAgL,yCAG1L,KAAC,UAAU,IAAC,SAAS,EAAC,SAAS,GAAG,IAC3B,IACJ,EAEP,cAAK,SAAS,EAAC,iDAAiD,YAC9D,aAAG,SAAS,EAAC,eAAe,uDACP,GAAG,EACtB,iBACE,OAAO,EAAE,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC,CAAC,EACnD,SAAS,EAAC,yCAAyC,kDAG5C,IACP,GACA,IACF,GACF,IACF,CACP,CAAC;AACJ,CAAC,CAAC"}
{"version":3,"file":"LoginModal.js","sourceRoot":"","sources":["../LoginModal.tsx"],"names":[],"mappings":"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"}
+24 -1
View File
@@ -11,7 +11,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { NestFactory } from '@nestjs/core';
import { Module, Controller, Get, Post, Body, Param, ParseIntPipe, NotFoundException, BadRequestException } from '@nestjs/common';
import { Module, Controller, Get, Post, Body, Param, ParseIntPipe, NotFoundException, BadRequestException, UnauthorizedException } from '@nestjs/common';
import { PrismaService } from './prisma.service.js';
import 'dotenv/config';
import * as bcrypt from 'bcrypt';
@@ -38,6 +38,22 @@ let AuthController = class AuthController {
console.log(`[Status Check] Users found: ${userCount}`);
return { isInitialSetup: userCount === 0 };
}
async login(body) {
const { email, password } = body;
const user = await this.prisma.user.findUnique({ where: { email } });
if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
throw new UnauthorizedException('Email hoặc mật khẩu không chính xác');
}
return {
access_token: 'simulated-jwt-token',
user: {
id: user.id,
email: user.email,
name: user.name,
isAdmin: user.isAdmin,
},
};
}
async signup(body) {
const { email, password, name } = body;
const existingUser = await this.prisma.user.findUnique({ where: { email } });
@@ -58,6 +74,13 @@ __decorate([
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], AuthController.prototype, "getStatus", null);
__decorate([
Post('login'),
__param(0, Body()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], AuthController.prototype, "login", null);
__decorate([
Post('signup'),
__param(0, Body()),
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"main.js","sourceRoot":"","sources":["../main.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"main.js","sourceRoot":"","sources":["../main.ts"],"names":[],"mappings":"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"}
+1 -1
View File
File diff suppressed because one or more lines are too long
+21 -1
View File
@@ -1,5 +1,5 @@
import { NestFactory } from '@nestjs/core';
import { Module, Controller, Get, Post, Body, Param, ParseIntPipe, NotFoundException, BadRequestException } from '@nestjs/common';
import { Module, Controller, Get, Post, Body, Param, ParseIntPipe, NotFoundException, BadRequestException, UnauthorizedException } from '@nestjs/common';
import { PrismaService } from './prisma.service.js';
import 'dotenv/config';
import * as bcrypt from 'bcrypt';
@@ -23,6 +23,26 @@ class AuthController {
return { isInitialSetup: userCount === 0 };
}
@Post('login')
async login(@Body() body: any) {
const { email, password } = body;
const user = await this.prisma.user.findUnique({ where: { email } });
if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
throw new UnauthorizedException('Email hoặc mật khẩu không chính xác');
}
return {
access_token: 'simulated-jwt-token',
user: {
id: user.id,
email: user.email,
name: user.name,
isAdmin: user.isAdmin,
},
};
}
@Post('signup')
async signup(@Body() body: any) {
const { email, password, name } = body;