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

This commit is contained in:
3dtours committed 2026-06-15 16:31:28 +07:00
1 parent 967f6b4f6a
commit 4716b841dc
46 files changed
+542 -6549

No files matched your search

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
View File
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+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 {
File renamed without changes.
+10 -9
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/**/*"]
} }
File renamed without changes.
File renamed without changes.
+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';
File renamed without changes.
File renamed without changes.
@@ -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) => {
File renamed without changes.
@@ -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 };
};
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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();
File renamed without changes.
+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,
},
},
},
});
+126 -6418
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,
},
});