Sửa lỗi tái cấu trúc thư mục và khai báo import
This commit is contained in:
1 parent
967f6b4f6a
commit
4716b841dc
46 files changed
+547
-6554
No files matched your search
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,14 +1,14 @@
|
||||
import 'reflect-metadata';
|
||||
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 { PrismaService } from './prisma.service.js';
|
||||
import { PrismaService } from '../prisma/prisma.service.js';
|
||||
import 'dotenv/config';
|
||||
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 { JwtAuthGuard } from './jwt-auth.guard.js';
|
||||
import { JwtStrategy } from './jwt.strategy.js';
|
||||
import { TourRoleGuard } from './rbac.middleware.js';
|
||||
import { JwtAuthGuard } from './auth/jwt-auth.guard.js';
|
||||
import { JwtStrategy } from './auth/jwt.strategy.js';
|
||||
import { TourRoleGuard } from './common/rbac.middleware.js';
|
||||
|
||||
@Controller()
|
||||
class AppController {
|
||||
File renamed without changes.
@@ -1,12 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"ignoreDeprecations": "5.0",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"allowImportingTsExtensions": true,
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
"noEmit": false,
|
||||
"jsx": "react-jsx",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
@@ -15,12 +10,18 @@
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": false,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"noFallthroughCasesInSwitch": false
|
||||
}
|
||||
}
|
||||
"noFallthroughCasesInSwitch": false,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
@@ -7,6 +7,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/index.tsx"></script>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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 {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
"autoprefixer": {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
};
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { LandingPage } from './LandingPage.js';
|
||||
import { TourDetailPage } from './TourDetailPage.js';
|
||||
import { ExploreMap } from './ExploreMap.js';
|
||||
import { SignupPage } from './SignupPage.js';
|
||||
import { useTourStore } from './useTourStore.js';
|
||||
import { LandingPage } from '@/pages/LandingPage';
|
||||
import { TourDetailPage } from '@/pages/TourDetailPage';
|
||||
import { ExploreMap } from '@/pages/ExploreMap';
|
||||
import { SignupPage } from '@/pages/SignupPage';
|
||||
import { useTourStore } from '@/store/useTourStore';
|
||||
|
||||
const App = () => {
|
||||
type View = 'landing' | 'explore' | 'detail' | 'signup';
|
||||
@@ -14,9 +14,6 @@ const App = () => {
|
||||
const fetchTour = useTourStore(state => state.fetchTour);
|
||||
|
||||
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) {
|
||||
@@ -26,7 +23,7 @@ const App = () => {
|
||||
setIsUserLoaded(true); // Đánh dấu user đã được load
|
||||
|
||||
// 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(data => setIsInitialSetup(!!data.isInitialSetup))
|
||||
.catch(() => setIsInitialSetup(false));
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect, useRef } from '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 L from 'leaflet';
|
||||
|
||||
File renamed without changes.
File renamed without changes.
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from '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 }) => {
|
||||
const [title, setTitle] = useState('');
|
||||
@@ -23,8 +23,7 @@ export const CreateTourModal = ({ isOpen, onClose, onSuccess }: { isOpen: boolea
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const API_BASE = `http://${window.location.hostname}:3001`;
|
||||
const res = await fetch(`${API_BASE}/api/v1/users?q=${encodeURIComponent(value)}`, {
|
||||
const res = await fetch(`/api/v1/users?q=${encodeURIComponent(value)}`, {
|
||||
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
|
||||
});
|
||||
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 { Wallet, Users, Info } from 'lucide-react';
|
||||
import { useTourStore } from './useTourStore.js';
|
||||
import { useTourStore } from '@/store/useTourStore';
|
||||
|
||||
export const ExpenseManager = () => {
|
||||
const { legs } = useTourStore();
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useState } from 'react';
|
||||
import { format, differenceInMinutes, parseISO } from 'date-fns';
|
||||
import { CheckCircle2, Circle, Clock, MapPin, AlertCircle, Zap, Navigation, Edit2, Trash2, Plus, List } from 'lucide-react';
|
||||
import { useTourStore } from './useTourStore.js';
|
||||
import { ConfirmModal } from './ConfirmModal.js';
|
||||
import { useTourStore } from '@/store/useTourStore';
|
||||
import { ConfirmModal } from '@/components/ConfirmModal';
|
||||
|
||||
const TimeVariance = ({ planned, actual }: { planned: string, actual: string | null }) => {
|
||||
if (!actual) return null;
|
||||
@@ -40,7 +40,17 @@ export const ItineraryTimeline = ({
|
||||
onAddLocation,
|
||||
onEditLocation
|
||||
}: { 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 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;
|
||||
import L from 'leaflet';
|
||||
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 { UserManagementModal } from './UserManagementModal.js';
|
||||
import { CreateTourModal } from './CreateTourModal.js';
|
||||
import { UserManagementModal } from '@/components/UserManagementModal';
|
||||
import { CreateTourModal } from '../components/CreateTourModal';
|
||||
|
||||
// Fix lỗi icon mặc định của Leaflet
|
||||
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 }) => {
|
||||
// Thêm fetchTour vào destructuring từ store
|
||||
const { publicTours, fetchPublicTours, fetchTour, setMapCenter } = useTourStore();
|
||||
// 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 = 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)
|
||||
const [initialViewState] = useState(() => {
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useMemo } from '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 = [
|
||||
"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);
|
||||
try {
|
||||
const API_BASE = `http://${window.location.hostname}:3001`;
|
||||
|
||||
const response = await fetch(`${API_BASE}/api/v1/auth/signup`, {
|
||||
const response = await fetch(`/api/v1/auth/signup`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -1,15 +1,14 @@
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { ItineraryTimeline } from './ItineraryTimeline.js';
|
||||
import { ExpenseManager } from './ExpenseManager.js';
|
||||
import { useTourStore } from './useTourStore.js';
|
||||
import { AddLocationModal } from './AddLocationModal.js';
|
||||
import { AddMemberModal } from './AddMemberModal.js';
|
||||
import { ConfirmModal } from './ConfirmModal.js';
|
||||
import { NotificationModal, useNotificationModal } from './components/NotificationModal.js';
|
||||
import { MapContainer, TileLayer, Marker, Popup, useMapEvents, Polyline } from 'react-leaflet';
|
||||
import { ItineraryTimeline } from '../components/ItineraryTimeline';
|
||||
import { ExpenseManager } from '../components/ExpenseManager';
|
||||
import { useTourStore } from '@/store/useTourStore';
|
||||
import { AddLocationModal } from '@/components/AddLocationModal';
|
||||
import { AddMemberModal } from '../components/AddMemberModal';
|
||||
import { ConfirmModal } from '../components/ConfirmModal';
|
||||
import { NotificationModal, useNotificationModal } from '@/components/NotificationModal';
|
||||
import { MapContainer, TileLayer, Marker, Popup, useMapEvents, Polyline, useMap } from 'react-leaflet';
|
||||
import _MarkerClusterGroup from 'react-leaflet-cluster';
|
||||
const MarkerClusterGroup = (_MarkerClusterGroup as any).default || _MarkerClusterGroup;
|
||||
import { useMap } from 'react-leaflet';
|
||||
import {
|
||||
Map as MapIcon,
|
||||
Wallet,
|
||||
@@ -175,12 +174,24 @@ export const TourDetailPage = ({ onBack }: { onBack: () => void }) => {
|
||||
const [joinRequestActionId, setJoinRequestActionId] = useState<string | null>(null);
|
||||
const [confirmState, setConfirmState] = useState<{ open: boolean; title?: string; message?: string; onConfirm?: () => void }>({ open: false });
|
||||
|
||||
const {
|
||||
currentTour, legs, fetchTour, fetchPublicTours, publicTours,
|
||||
userRole, mapCenter, setMapCenter, updateTourStartPoint,
|
||||
updateTourEndPoint, initializeLegs, addLocation, addMember, removeMember,
|
||||
fetchJoinRequests, acceptJoinRequest, rejectJoinRequest
|
||||
} = useTourStore();
|
||||
// Tách biệt các state và actions để tối ưu performance
|
||||
const currentTour = useTourStore(state => state.currentTour);
|
||||
const legs = useTourStore(state => state.legs);
|
||||
const publicTours = useTourStore(state => state.publicTours);
|
||||
const userRole = useTourStore(state => state.userRole);
|
||||
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();
|
||||
|
||||
File renamed without changes.
@@ -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;
|
||||
@@ -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" }]
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
Generated
+129
-6421
File diff suppressed because it is too large.
Load diff
+12
-49
@@ -1,57 +1,20 @@
|
||||
{
|
||||
"name": "travel-planning-backend",
|
||||
"name": "travel-planning-monorepo",
|
||||
"version": "1.0.0",
|
||||
"description": "Travel Planning Backend with NestJS and Prisma 7",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
"backend",
|
||||
"frontend"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"frontend": "vite",
|
||||
"db:migrate": "npx prisma migrate dev",
|
||||
"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"
|
||||
"build": "npm run build --workspace=backend && npm run build --workspace=frontend",
|
||||
"start:dev": "concurrently \"npm run start:dev --workspace=backend\" \"npm run dev --workspace=frontend\"",
|
||||
"db:migrate": "npm run db:migrate --workspace=backend",
|
||||
"db:generate": "npm run db:generate --workspace=backend",
|
||||
"db:seed": "npm run db:seed --workspace=backend"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
"@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"
|
||||
"concurrently": "^8.2.2"
|
||||
}
|
||||
}
|
||||
@@ -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."
|
||||
@@ -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
@@ -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."
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user