Sửa lỗi tái cấu trúc thư mục và khai báo import

This commit is contained in:
2026-06-15 16:31:28 +07:00
parent 967f6b4f6a
commit 4716b841dc
46 changed files with 547 additions and 6554 deletions
View File
+5 -5
View File
@@ -1,14 +1,14 @@
import 'reflect-metadata'; import 'reflect-metadata';
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { Module, Controller, Get, Post, Body, Param, Patch, Delete, ParseUUIDPipe, NotFoundException, BadRequestException, UnauthorizedException, UseGuards, Req, Query, ForbiddenException } from '@nestjs/common'; import { Module, Controller, Get, Post, Body, Param, Patch, Delete, ParseUUIDPipe, NotFoundException, BadRequestException, UnauthorizedException, UseGuards, Req, Query, ForbiddenException } from '@nestjs/common';
import { PrismaService } from './prisma.service.js'; import { PrismaService } from '../prisma/prisma.service.js';
import 'dotenv/config'; import 'dotenv/config';
import * as bcrypt from 'bcrypt'; import * as bcrypt from 'bcrypt';
import { AdminGuard } from './admin.guard.js'; import { AdminGuard } from './auth/admin.guard.js';
import { JwtModule, JwtService } from '@nestjs/jwt'; import { JwtModule, JwtService } from '@nestjs/jwt';
import { JwtAuthGuard } from './jwt-auth.guard.js'; import { JwtAuthGuard } from './auth/jwt-auth.guard.js';
import { JwtStrategy } from './jwt.strategy.js'; import { JwtStrategy } from './auth/jwt.strategy.js';
import { TourRoleGuard } from './rbac.middleware.js'; import { TourRoleGuard } from './common/rbac.middleware.js';
@Controller() @Controller()
class AppController { class AppController {
+12 -11
View File
@@ -1,12 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"ignoreDeprecations": "5.0", "module": "ESNext",
"module": "NodeNext", "moduleResolution": "node",
"moduleResolution": "NodeNext",
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": true,
"noEmit": false,
"jsx": "react-jsx",
"declaration": true, "declaration": true,
"removeComments": true, "removeComments": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
@@ -15,12 +10,18 @@
"target": "ES2021", "target": "ES2021",
"sourceMap": true, "sourceMap": true,
"outDir": "./dist", "outDir": "./dist",
"incremental": true,
"skipLibCheck": true, "skipLibCheck": true,
"strictNullChecks": false, "strictNullChecks": false,
"noImplicitAny": false, "noImplicitAny": false,
"strictBindCallApply": false, "strictBindCallApply": false,
"forceConsistentCasingInFileNames": false, "forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false "noFallthroughCasesInSwitch": false,
} "resolveJsonModule": true,
} "esModuleInterop": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*"]
}
+1 -1
View File
@@ -7,6 +7,6 @@
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/index.tsx"></script> <script type="module" src="/src/index.tsx"></script>
</body> </body>
</html> </html>
+32
View File
@@ -0,0 +1,32 @@
{
"name": "frontend",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"leaflet": "^1.9.4",
"lucide-react": "^0.284.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-leaflet": "^4.2.1",
"react-leaflet-cluster": "^2.1.0",
"zustand": "^5.0.1"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.3.1",
"@types/leaflet": "^1.9.12",
"@types/react": "^18.3.12",
"@vitejs/plugin-react": "^6.0.2",
"autoprefixer": "^10.5.0",
"postcss": "^8.5.15",
"tailwindcss": "^4.3.1",
"typescript": "^5.7.0",
"vite": "^8.0.16"
}
}
@@ -1,6 +1,6 @@
export default { export default {
plugins: { plugins: {
"@tailwindcss/postcss": {}, "@tailwindcss/postcss": {},
"autoprefixer": {}, autoprefixer: {},
}, },
} };
+6 -9
View File
@@ -1,9 +1,9 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { LandingPage } from './LandingPage.js'; import { LandingPage } from '@/pages/LandingPage';
import { TourDetailPage } from './TourDetailPage.js'; import { TourDetailPage } from '@/pages/TourDetailPage';
import { ExploreMap } from './ExploreMap.js'; import { ExploreMap } from '@/pages/ExploreMap';
import { SignupPage } from './SignupPage.js'; import { SignupPage } from '@/pages/SignupPage';
import { useTourStore } from './useTourStore.js'; import { useTourStore } from '@/store/useTourStore';
const App = () => { const App = () => {
type View = 'landing' | 'explore' | 'detail' | 'signup'; type View = 'landing' | 'explore' | 'detail' | 'signup';
@@ -14,9 +14,6 @@ const App = () => {
const fetchTour = useTourStore(state => state.fetchTour); const fetchTour = useTourStore(state => state.fetchTour);
useEffect(() => { 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 // Khôi phục phiên đăng nhập từ localStorage
const savedUser = localStorage.getItem('user'); const savedUser = localStorage.getItem('user');
if (savedUser) { if (savedUser) {
@@ -26,7 +23,7 @@ const App = () => {
setIsUserLoaded(true); // Đánh dấu user đã được load setIsUserLoaded(true); // Đánh dấu user đã được load
// Kiểm tra xem hệ thống đã được cài đặt chưa // Kiểm tra xem hệ thống đã được cài đặt chưa
fetch(`${API_BASE}/api/v1/auth/status`) fetch(`/api/v1/auth/status`)
.then(res => res.ok ? res.json() : Promise.reject()) .then(res => res.ok ? res.json() : Promise.reject())
.then(data => setIsInitialSetup(!!data.isInitialSetup)) .then(data => setIsInitialSetup(!!data.isInitialSetup))
.catch(() => setIsInitialSetup(false)); .catch(() => setIsInitialSetup(false));
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { X, MapPin, Loader2, Clock, Map as MapIcon } from 'lucide-react'; import { X, MapPin, Loader2, Clock, Map as MapIcon } from 'lucide-react';
import { useTourStore } from './useTourStore.js'; import { useTourStore } from '@/store/useTourStore.js';
import { MapContainer, TileLayer, Marker, useMapEvents, useMap } from 'react-leaflet'; import { MapContainer, TileLayer, Marker, useMapEvents, useMap } from 'react-leaflet';
import L from 'leaflet'; import L from 'leaflet';
@@ -1,6 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Trash2 } from 'lucide-react'; import { Trash2 } from 'lucide-react';
import { useTourStore } from './useTourStore.js'; import { useTourStore } from '@/store/useTourStore';
export const CreateTourModal = ({ isOpen, onClose, onSuccess }: { isOpen: boolean, onClose: () => void, onSuccess: (tour: any) => void }) => { export const CreateTourModal = ({ isOpen, onClose, onSuccess }: { isOpen: boolean, onClose: () => void, onSuccess: (tour: any) => void }) => {
const [title, setTitle] = useState(''); const [title, setTitle] = useState('');
@@ -23,8 +23,7 @@ export const CreateTourModal = ({ isOpen, onClose, onSuccess }: { isOpen: boolea
return; return;
} }
try { try {
const API_BASE = `http://${window.location.hostname}:3001`; const res = await fetch(`/api/v1/users?q=${encodeURIComponent(value)}`, {
const res = await fetch(`${API_BASE}/api/v1/users?q=${encodeURIComponent(value)}`, {
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` }, headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
}); });
if (!res.ok) throw new Error('Không thể tải người dùng'); if (!res.ok) throw new Error('Không thể tải người dùng');
@@ -1,6 +1,6 @@
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo } from 'react';
import { Wallet, Users, Info } from 'lucide-react'; import { Wallet, Users, Info } from 'lucide-react';
import { useTourStore } from './useTourStore.js'; import { useTourStore } from '@/store/useTourStore';
export const ExpenseManager = () => { export const ExpenseManager = () => {
const { legs } = useTourStore(); const { legs } = useTourStore();
@@ -1,8 +1,8 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { format, differenceInMinutes, parseISO } from 'date-fns'; import { format, differenceInMinutes, parseISO } from 'date-fns';
import { CheckCircle2, Circle, Clock, MapPin, AlertCircle, Zap, Navigation, Edit2, Trash2, Plus, List } from 'lucide-react'; import { CheckCircle2, Circle, Clock, MapPin, AlertCircle, Zap, Navigation, Edit2, Trash2, Plus, List } from 'lucide-react';
import { useTourStore } from './useTourStore.js'; import { useTourStore } from '@/store/useTourStore';
import { ConfirmModal } from './ConfirmModal.js'; import { ConfirmModal } from '@/components/ConfirmModal';
const TimeVariance = ({ planned, actual }: { planned: string, actual: string | null }) => { const TimeVariance = ({ planned, actual }: { planned: string, actual: string | null }) => {
if (!actual) return null; if (!actual) return null;
@@ -40,7 +40,17 @@ export const ItineraryTimeline = ({
onAddLocation, onAddLocation,
onEditLocation onEditLocation
}: { onAddLocation?: (legId: string) => void, onEditLocation?: (location: any) => void }) => { }: { onAddLocation?: (legId: string) => void, onEditLocation?: (location: any) => void }) => {
const { currentTour, legs, optimizeRouting, userRole, addLeg, updateLeg, deleteLeg, initializeLegs, deleteLocation } = useTourStore(); // Tối ưu hóa selectors để chỉ lắng nghe những thay đổi cần thiết
const currentTour = useTourStore(state => state.currentTour);
const legs = useTourStore(state => state.legs);
const userRole = useTourStore(state => state.userRole);
const optimizeRouting = useTourStore(state => state.optimizeRouting);
const addLeg = useTourStore(state => state.addLeg);
const updateLeg = useTourStore(state => state.updateLeg);
const deleteLeg = useTourStore(state => state.deleteLeg);
const initializeLegs = useTourStore(state => state.initializeLegs);
const deleteLocation = useTourStore(state => state.deleteLocation);
const [confirmState, setConfirmState] = useState<{ open: boolean; title?: string; message?: string; onConfirm?: () => void }>({ open: false }); const [confirmState, setConfirmState] = useState<{ open: boolean; title?: string; message?: string; onConfirm?: () => void }>({ open: false });
const toggleComplete = async (locationId: string) => { const toggleComplete = async (locationId: string) => {
@@ -0,0 +1,88 @@
import React, { useState } from 'react';
import { X, CheckCircle, AlertCircle, Info } from 'lucide-react';
interface NotificationModalProps {
isOpen: boolean;
title?: string;
message?: string;
type?: 'success' | 'error' | 'info';
onConfirm: () => void;
}
/**
* NotificationModal - Component hiển thị thông báo phản hồi cho người dùng
*/
export const NotificationModal: React.FC<NotificationModalProps> = ({
isOpen,
title = 'Thông báo',
message,
type = 'info',
onConfirm,
}) => {
if (!isOpen) return null;
const icons = {
success: <CheckCircle className="w-12 h-12 text-green-500" />,
error: <AlertCircle className="w-12 h-12 text-red-500" />,
info: <Info className="w-12 h-12 text-blue-500" />,
};
const colors = {
success: 'bg-green-600 hover:bg-green-700 shadow-green-100',
error: 'bg-red-600 hover:bg-red-700 shadow-red-100',
info: 'bg-blue-600 hover:bg-blue-700 shadow-blue-100',
};
return (
<div className="fixed inset-0 z-[3000] flex items-center justify-center p-4">
{/* Backdrop */}
<div className="absolute inset-0 bg-gray-900/60 backdrop-blur-sm animate-in fade-in duration-200" onClick={onConfirm} />
{/* Modal Content */}
<div className="relative w-full max-w-sm bg-white rounded-[32px] shadow-2xl overflow-hidden p-8 text-center animate-in zoom-in-95 duration-200">
<div className="flex justify-center mb-5">
{icons[type]}
</div>
<h2 className="text-xl font-black text-gray-900 mb-2">{title}</h2>
<p className="text-gray-500 text-sm leading-relaxed mb-8">
{message || "Bạn không được phép gỡ bỏ thành viên này!"}
</p>
<button
onClick={onConfirm}
className={`w-full py-4 text-white font-bold rounded-2xl transition-all shadow-lg active:scale-95 ${colors[type]}`}
>
Đã hiểu
</button>
</div>
</div>
);
};
/**
* Custom hook để quản lý trạng thái của NotificationModal
*/
export const useNotificationModal = () => {
const [modalState, setModalState] = useState<{
isOpen: boolean;
title?: string;
message?: string;
type?: 'success' | 'error' | 'info';
}>({
isOpen: false,
title: 'Thông báo',
message: '',
type: 'info',
});
const openModal = (title: string, message: string, type: 'success' | 'error' | 'info' = 'info') => {
setModalState({ isOpen: true, title, message, type });
};
const closeModal = () => {
setModalState((prev) => ({ ...prev, isOpen: false }));
};
return { modalState, openModal, closeModal };
};
@@ -4,10 +4,10 @@ import _MarkerClusterGroup from 'react-leaflet-cluster';
const MarkerClusterGroup = (_MarkerClusterGroup as any).default || _MarkerClusterGroup; const MarkerClusterGroup = (_MarkerClusterGroup as any).default || _MarkerClusterGroup;
import L from 'leaflet'; import L from 'leaflet';
import 'leaflet/dist/leaflet.css'; import 'leaflet/dist/leaflet.css';
import { useTourStore } from './useTourStore.js'; import { useTourStore } from '@/store/useTourStore';
import { X, Navigation, Image as ImageIcon, LogOut, Settings, Edit2, Trash2 } from 'lucide-react'; import { X, Navigation, Image as ImageIcon, LogOut, Settings, Edit2, Trash2 } from 'lucide-react';
import { UserManagementModal } from './UserManagementModal.js'; import { UserManagementModal } from '@/components/UserManagementModal';
import { CreateTourModal } from './CreateTourModal.js'; import { CreateTourModal } from '../components/CreateTourModal';
// Fix lỗi icon mặc định của Leaflet // Fix lỗi icon mặc định của Leaflet
const DefaultIcon = L.icon({ const DefaultIcon = L.icon({
@@ -46,8 +46,11 @@ function MapTracker() {
} }
export const ExploreMap = ({ onBack, onLogout, user, onViewTour }: { onBack: () => void, onLogout?: () => void, user?: any, onViewTour: (id: string) => void }) => { export const ExploreMap = ({ onBack, onLogout, user, onViewTour }: { onBack: () => void, onLogout?: () => void, user?: any, onViewTour: (id: string) => void }) => {
// Thêm fetchTour vào destructuring từ store // Tối ưu hóa việc lấy dữ liệu từ store bằng selectors để tránh re-render thừa
const { publicTours, fetchPublicTours, fetchTour, setMapCenter } = useTourStore(); const publicTours = useTourStore(state => state.publicTours);
const fetchPublicTours = useTourStore(state => state.fetchPublicTours);
const fetchTour = useTourStore(state => state.fetchTour);
const setMapCenter = useTourStore(state => state.setMapCenter);
// Khởi tạo vị trí từ localStorage nếu có, nếu không dùng mặc định (TP.HCM) // Khởi tạo vị trí từ localStorage nếu có, nếu không dùng mặc định (TP.HCM)
const [initialViewState] = useState(() => { const [initialViewState] = useState(() => {
@@ -1,6 +1,6 @@
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo } from 'react';
import { LogIn, Compass, ArrowRight, Map as MapIcon, UserPlus, ShieldCheck } from 'lucide-react'; import { LogIn, Compass, ArrowRight, Map as MapIcon, UserPlus, ShieldCheck } from 'lucide-react';
import { LoginModal } from './LoginModal.js'; import { LoginModal } from '../components/LoginModal';
const TRAVEL_IMAGES = [ const TRAVEL_IMAGES = [
"https://images.unsplash.com/photo-1476514525535-07fb3b4ae5f1?auto=format&fit=crop&q=80", "https://images.unsplash.com/photo-1476514525535-07fb3b4ae5f1?auto=format&fit=crop&q=80",
@@ -29,9 +29,7 @@ export const SignupPage: React.FC<SignupPageProps> = ({ onBack, onSuccess }) =>
setIsLoading(true); setIsLoading(true);
try { try {
const API_BASE = `http://${window.location.hostname}:3001`; const response = await fetch(`/api/v1/auth/signup`, {
const response = await fetch(`${API_BASE}/api/v1/auth/signup`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
@@ -1,15 +1,14 @@
import React, { useState, useEffect, useMemo } from 'react'; import React, { useState, useEffect, useMemo } from 'react';
import { ItineraryTimeline } from './ItineraryTimeline.js'; import { ItineraryTimeline } from '../components/ItineraryTimeline';
import { ExpenseManager } from './ExpenseManager.js'; import { ExpenseManager } from '../components/ExpenseManager';
import { useTourStore } from './useTourStore.js'; import { useTourStore } from '@/store/useTourStore';
import { AddLocationModal } from './AddLocationModal.js'; import { AddLocationModal } from '@/components/AddLocationModal';
import { AddMemberModal } from './AddMemberModal.js'; import { AddMemberModal } from '../components/AddMemberModal';
import { ConfirmModal } from './ConfirmModal.js'; import { ConfirmModal } from '../components/ConfirmModal';
import { NotificationModal, useNotificationModal } from './components/NotificationModal.js'; import { NotificationModal, useNotificationModal } from '@/components/NotificationModal';
import { MapContainer, TileLayer, Marker, Popup, useMapEvents, Polyline } from 'react-leaflet'; import { MapContainer, TileLayer, Marker, Popup, useMapEvents, Polyline, useMap } from 'react-leaflet';
import _MarkerClusterGroup from 'react-leaflet-cluster'; import _MarkerClusterGroup from 'react-leaflet-cluster';
const MarkerClusterGroup = (_MarkerClusterGroup as any).default || _MarkerClusterGroup; const MarkerClusterGroup = (_MarkerClusterGroup as any).default || _MarkerClusterGroup;
import { useMap } from 'react-leaflet';
import { import {
Map as MapIcon, Map as MapIcon,
Wallet, Wallet,
@@ -175,12 +174,24 @@ export const TourDetailPage = ({ onBack }: { onBack: () => void }) => {
const [joinRequestActionId, setJoinRequestActionId] = useState<string | null>(null); const [joinRequestActionId, setJoinRequestActionId] = useState<string | null>(null);
const [confirmState, setConfirmState] = useState<{ open: boolean; title?: string; message?: string; onConfirm?: () => void }>({ open: false }); const [confirmState, setConfirmState] = useState<{ open: boolean; title?: string; message?: string; onConfirm?: () => void }>({ open: false });
const { // Tách biệt các state và actions để tối ưu performance
currentTour, legs, fetchTour, fetchPublicTours, publicTours, const currentTour = useTourStore(state => state.currentTour);
userRole, mapCenter, setMapCenter, updateTourStartPoint, const legs = useTourStore(state => state.legs);
updateTourEndPoint, initializeLegs, addLocation, addMember, removeMember, const publicTours = useTourStore(state => state.publicTours);
fetchJoinRequests, acceptJoinRequest, rejectJoinRequest const userRole = useTourStore(state => state.userRole);
} = useTourStore(); const mapCenter = useTourStore(state => state.mapCenter);
const fetchTour = useTourStore(state => state.fetchTour);
const fetchPublicTours = useTourStore(state => state.fetchPublicTours);
const setMapCenter = useTourStore(state => state.setMapCenter);
const updateTourStartPoint = useTourStore(state => state.updateTourStartPoint);
const updateTourEndPoint = useTourStore(state => state.updateTourEndPoint);
const initializeLegs = useTourStore(state => state.initializeLegs);
const addLocation = useTourStore(state => state.addLocation);
const removeMember = useTourStore(state => state.removeMember);
const fetchJoinRequests = useTourStore(state => state.fetchJoinRequests);
const acceptJoinRequest = useTourStore(state => state.acceptJoinRequest);
const rejectJoinRequest = useTourStore(state => state.rejectJoinRequest);
const notificationModal = useNotificationModal(); const notificationModal = useNotificationModal();
+14
View File
@@ -0,0 +1,14 @@
import type { Config } from 'tailwindcss';
const config: Config = {
content: [
'./index.html',
'./src/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {},
},
plugins: [],
};
export default config;
+30
View File
@@ -0,0 +1,30 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": "./",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
+13
View File
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true,
"isolatedModules": true,
"types": ["node"]
},
"include": ["vite.config.ts"]
}
+22
View File
@@ -0,0 +1,22 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
server: {
port: 3002,
proxy: {
'/api': {
target: 'http://127.0.0.1:3001',
changeOrigin: true,
},
},
},
});
+129 -6421
View File
File diff suppressed because it is too large Load Diff
+12 -49
View File
@@ -1,57 +1,20 @@
{ {
"name": "travel-planning-backend", "name": "travel-planning-monorepo",
"version": "1.0.0", "version": "1.0.0",
"description": "Travel Planning Backend with NestJS and Prisma 7", "private": true,
"type": "module", "type": "module",
"workspaces": [
"backend",
"frontend"
],
"scripts": { "scripts": {
"build": "nest build", "build": "npm run build --workspace=backend && npm run build --workspace=frontend",
"start": "nest start", "start:dev": "concurrently \"npm run start:dev --workspace=backend\" \"npm run dev --workspace=frontend\"",
"start:dev": "nest start --watch", "db:migrate": "npm run db:migrate --workspace=backend",
"frontend": "vite", "db:generate": "npm run db:generate --workspace=backend",
"db:migrate": "npx prisma migrate dev", "db:seed": "npm run db:seed --workspace=backend"
"db:generate": "npx prisma generate",
"db:seed": "npx tsx seed.ts"
},
"dependencies": {
"@nestjs/common": "^11.1.26",
"@nestjs/core": "^11.1.26",
"@nestjs/jwt": "^11.0.2",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.1.26",
"@prisma/adapter-pg": "^7.8.0",
"@prisma/client": "^7.8.0",
"bcrypt": "^5.1.1",
"date-fns": "^2.30.0",
"dotenv": "^16.3.0",
"leaflet": "^1.9.4",
"lucide-react": "^0.284.0",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"pg": "^8.11.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-leaflet": "^4.2.1",
"react-leaflet-cluster": "^2.1.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"zustand": "^5.0.1"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^10.0.0", "concurrently": "^8.2.2"
"@tailwindcss/postcss": "^4.3.1",
"@types/bcrypt": "^5.0.2",
"@types/leaflet": "^1.9.12",
"@types/node": "^20.0.0",
"@types/passport-jwt": "^4.0.1",
"@types/pg": "^8.10.0",
"@types/react": "^18.3.12",
"@vitejs/plugin-react": "^6.0.2",
"autoprefixer": "^10.5.0",
"postcss": "^8.5.15",
"prisma": "^7.8.0",
"tailwindcss": "^4.3.1",
"tsx": "^4.0.0",
"typescript": "^5.7.0",
"vite": "^8.0.16"
} }
} }
Executable
+87
View File
@@ -0,0 +1,87 @@
#!/bin/bash
# Ensure the script exits if any command fails
set -e
echo "Starting monorepo refactoring..."
# --- 1. Create new directories ---
echo "Creating new directories..."
mkdir -p backend/src/auth
mkdir -p backend/src/common
mkdir -p backend/deprecated-migrations
mkdir -p frontend/src/components
mkdir -p frontend/src/pages
mkdir -p frontend/src/store
mkdir -p docs
echo "Directories created."
# --- 2. Move files to backend/ ---
echo "Moving backend files..."
mv admin.guard.ts backend/src/auth/
mv jwt-auth.guard.ts backend/src/auth/
mv jwt.strategy.ts backend/src/auth/
mv rbac.middleware.ts backend/src/common/
mv main.ts backend/src/
mv nest-cli.json backend/
mv tsconfig.json backend/
mv tsconfig.build.json backend/
mv seed.ts backend/
# Special handling for prisma directory and prisma.service.ts
# First, move the existing prisma directory to backend/
mv prisma backend/
# Then, move prisma.service.ts into the newly moved backend/prisma directory
mv prisma.service.ts backend/prisma/
# Move old migrations (not Prisma's) to deprecated-migrations
if [ -d "migrations" ]; then
mv migrations backend/deprecated-migrations/
else
echo "Warning: 'migrations' directory not found at root. Skipping move."
fi
# Move schema.sql if it exists and is related to backend
if [ -f "schema.sql" ]; then
mv schema.sql backend/prisma/
else
echo "Warning: 'schema.sql' not found at root. Skipping move."
fi
echo "Backend files moved."
# --- 3. Move files to frontend/ ---
echo "Moving frontend files..."
mv App.tsx frontend/src/
mv CreateTourModal.tsx frontend/src/components/
mv ExpenseManager.tsx frontend/src/components/ # Assuming this is a component
mv ExploreMap.tsx frontend/src/pages/ # Assuming this is a page
mv index.tsx frontend/src/
mv ItineraryTimeline.tsx frontend/src/components/ # Assuming this is a component
mv LandingPage.tsx frontend/src/pages/
mv LoginModal.tsx frontend/src/components/
mv SignupPage.tsx frontend/src/pages/
mv TourDetailPage.tsx frontend/src/pages/
mv useTourStore.ts frontend/src/store/
mv vite.config.ts frontend/
mv tailwind.config.ts frontend/
mv postcss.config.js frontend/
mv index.css frontend/src/
echo "Frontend files moved."
# --- 4. Move files to docs/ ---
echo "Moving documentation files..."
mv ARCHITECTURE.md docs/
mv UITourDesign.md docs/
echo "Documentation files moved."
echo "Monorepo refactoring complete. Please verify the new structure."
echo "Next, you will need to update import paths and configuration files."
-13
View File
@@ -1,13 +0,0 @@
import type { Config } from 'tailwindcss'
export default {
content: [
"./index.html",
"./*.{js,ts,jsx,tsx}",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
} satisfies Config
Executable
+41
View File
@@ -0,0 +1,41 @@
#!/bin/bash
# Đảm bảo script dừng nếu có lỗi xảy ra
set -e
echo "🚀 Đang quét và cập nhật các đường dẫn import cho cấu trúc Monorepo..."
# --- 1. CẬP NHẬT BACKEND (NestJS) ---
# Cập nhật backend/src/main.ts
# Các Guard/Middleware nay nằm trong auth/ và common/
sed -i -E "s|from\s+['\"](\./)(admin\.guard\|jwt-auth\.guard\|jwt\.strategy)['\"]|from './auth/\2'|g" backend/src/main.ts
sed -i -E "s|from\s+['\"](\./)(rbac\.middleware)['\"]|from './common/\2'|g" backend/src/main.ts
# Prisma service nay nằm ở ../prisma/ (so với thư mục src/)
sed -i -E "s|from\s+['\"](\./prisma/prisma\.service)['\"]|from '../prisma/prisma.service'|g" backend/src/main.ts
# Cập nhật các file trong backend/src/auth/ (Guards & Strategies)
# Nếu chúng import PrismaService từ ./prisma/prisma.service, nay cần lùi 2 cấp: ../../prisma/prisma.service
find backend/src/auth -name "*.ts" -exec sed -i -E "s|from\s+['\"](\./prisma/prisma\.service)['\"]|from '../../prisma/prisma.service'|g" {} +
# --- 2. CẬP NHẬT FRONTEND (React + Vite) ---
# Cập nhật App.tsx (frontend/src/App.tsx)
# Trỏ vào thư mục pages/ và store/
sed -i -E "s|from\s+['\"](\./)(LandingPage\|SignupPage\|TourDetailPage\|ExploreMap)['\"]|from './pages/\2'|g" frontend/src/App.tsx
sed -i -E "s|from\s+['\"](\./)(useTourStore)['\"]|from './store/\2'|g" frontend/src/App.tsx
# Cập nhật các Trang (frontend/src/pages/*.tsx)
# Các trang cần trỏ tới components/ và store/ nằm ở thư mục anh em
# Bao gồm: CreateTourModal, LoginModal, ExpenseManager, ItineraryTimeline, AddMemberModal, ConfirmModal, NotificationModal
find frontend/src/pages -name "*.tsx" -exec sed -i -E "s|from\s+['\"](\./\|@/components/)(CreateTourModal\|LoginModal\|ExpenseManager\|ItineraryTimeline\|AddMemberModal\|ConfirmModal\|NotificationModal)['\"]|from '../components/\2'|g" {} +
find frontend/src/pages -name "*.tsx" -exec sed -i -E "s|from\s+['\"](\./)(useTourStore)['\"]|from '../store/\2'|g" {} +
find frontend/src/pages -name "*.tsx" -exec sed -i -E "s|from\s+['\"](\./index\.css)['\"]|from '../index.css'|g" {} +
# Cập nhật các Component (frontend/src/components/*.tsx)
# Các component/modal cần trỏ tới store/
find frontend/src/components -name "*.tsx" -exec sed -i -E "s|from\s+['\"](\./)(useTourStore)['\"]|from '../store/\2'|g" {} +
echo "✅ Hoàn tất cập nhật Import Paths."
echo "💡 Lưu ý: Hãy kiểm tra lại các lỗi đỏ trong VS Code. Nếu bạn có sử dụng Path Alias (như @/components), chúng ta sẽ cấu hình nó ở bước tiếp theo trong tsconfig.json."
-11
View File
@@ -1,11 +0,0 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 3002, // Thay đổi cổng thành 5173 (mặc định của Vite) hoặc cổng khác bạn muốn
strictPort: true,
host: true,
},
});