main create

This commit is contained in:
2026-06-13 16:06:24 +07:00
parent 3bb7649533
commit 568cb0e0bc
69 changed files with 9551 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
import React from 'react';
declare const App: () => React.JSX.Element;
export default App;
Vendored
+15
View File
@@ -0,0 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const LandingPage_1 = require("./LandingPage");
const TourDetailPage_1 = require("./TourDetailPage");
const App = () => {
const [isStarted, setIsStarted] = (0, react_1.useState)(false);
const handleContinue = () => {
setIsStarted(true);
};
return ((0, jsx_runtime_1.jsx)("div", { className: "app-container", children: !isStarted ? ((0, jsx_runtime_1.jsx)(LandingPage_1.LandingPage, { onContinue: handleContinue })) : ((0, jsx_runtime_1.jsx)(TourDetailPage_1.TourDetailPage, {})) }));
};
exports.default = App;
//# sourceMappingURL=App.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"App.js","sourceRoot":"","sources":["../App.tsx"],"names":[],"mappings":";;;AAAA,iCAAwC;AACxC,+CAA4C;AAC5C,qDAAkD;AAElD,MAAM,GAAG,GAAG,GAAG,EAAE;IAGf,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,IAAA,gBAAQ,EAAC,KAAK,CAAC,CAAC;IAElD,MAAM,cAAc,GAAG,GAAG,EAAE;QAC1B,YAAY,CAAC,IAAI,CAAC,CAAC;IACrB,CAAC,CAAC;IAEF,OAAO,CACL,gCAAK,SAAS,EAAC,eAAe,YAC3B,CAAC,SAAS,CAAC,CAAC,CAAC,CACZ,uBAAC,yBAAW,IAAC,UAAU,EAAE,cAAc,GAAI,CAC5C,CAAC,CAAC,CAAC,CACF,uBAAC,+BAAc,KAAG,CACnB,GACG,CACP,CAAC;AACJ,CAAC,CAAC;AAEF,kBAAe,GAAG,CAAC"}
+2
View File
@@ -0,0 +1,2 @@
import React from 'react';
export declare const ExpenseManager: () => React.JSX.Element;
+28
View File
@@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExpenseManager = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const lucide_react_1 = require("lucide-react");
const useTourStore_1 = require("./useTourStore");
const ExpenseManager = () => {
const { legs } = (0, useTourStore_1.useTourStore)();
const [adults, setAdults] = (0, react_1.useState)(2);
const [children, setChildren] = (0, react_1.useState)(1);
const [discount, setDiscount] = (0, react_1.useState)(30);
const totals = (0, react_1.useMemo)(() => {
const totalAmount = legs.reduce((acc, leg) => acc + leg.expenses.reduce((lAcc, exp) => lAcc + Number(exp.amount), 0), 0);
const childRateFactor = 1 - (discount / 100);
const weightedCount = adults + (children * childRateFactor);
const adultPrice = totalAmount / weightedCount;
const childPrice = adultPrice * childRateFactor;
return {
total: totalAmount,
adultPrice: Math.round(adultPrice),
childPrice: Math.round(childPrice)
};
}, [legs, adults, children, discount]);
return ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-6 animate-in fade-in slide-in-from-bottom-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "bg-white p-6 rounded-2xl border border-gray-100 shadow-sm", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 mb-6 text-blue-600", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Users, { className: "w-5 h-5" }), (0, jsx_runtime_1.jsx)("h3", { className: "font-bold", children: "C\u1EA5u h\u00ECnh th\u00E0nh vi\u00EAn" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-3 gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: "text-xs text-gray-400 block mb-1", children: "Ng\u01B0\u1EDDi l\u1EDBn" }), (0, jsx_runtime_1.jsx)("input", { type: "number", value: adults, onChange: e => setAdults(Number(e.target.value)), className: "w-full p-2 bg-gray-50 rounded-lg border-none focus:ring-2 focus:ring-blue-500" })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: "text-xs text-gray-400 block mb-1", children: "Tr\u1EBB em" }), (0, jsx_runtime_1.jsx)("input", { type: "number", value: children, onChange: e => setChildren(Number(e.target.value)), className: "w-full p-2 bg-gray-50 rounded-lg border-none focus:ring-2 focus:ring-blue-500" })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("label", { className: "text-xs text-gray-400 block mb-1", children: "Gi\u1EA3m tr\u1EBB em (%)" }), (0, jsx_runtime_1.jsx)("input", { type: "number", value: discount, onChange: e => setDiscount(Number(e.target.value)), className: "w-full p-2 bg-gray-50 rounded-lg border-none focus:ring-2 focus:ring-blue-500" })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "bg-blue-600 rounded-2xl p-6 text-white shadow-lg shadow-blue-200", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between items-start mb-8", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("p", { className: "text-blue-100 text-sm", children: "T\u1ED5ng chi ph\u00ED chuy\u1EBFn \u0111i" }), (0, jsx_runtime_1.jsxs)("h2", { className: "text-3xl font-bold mt-1", children: [totals.total.toLocaleString(), " VND"] })] }), (0, jsx_runtime_1.jsx)(lucide_react_1.Wallet, { className: "w-8 h-8 opacity-20" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "grid grid-cols-2 gap-4 border-t border-blue-500 pt-6", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("p", { className: "text-blue-100 text-xs uppercase tracking-wider font-semibold", children: "M\u1ED7i ng\u01B0\u1EDDi l\u1EDBn" }), (0, jsx_runtime_1.jsxs)("p", { className: "text-xl font-bold", children: [totals.adultPrice.toLocaleString(), "\u0111"] })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("p", { className: "text-blue-100 text-xs uppercase tracking-wider font-semibold", children: ["M\u1ED7i tr\u1EBB em (-", discount, "%)"] }), (0, jsx_runtime_1.jsxs)("p", { className: "text-xl font-bold", children: [totals.childPrice.toLocaleString(), "\u0111"] })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 text-gray-400 text-xs px-2", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Info, { className: "w-4 h-4" }), (0, jsx_runtime_1.jsx)("p", { children: "Chi ph\u00ED \u0111\u01B0\u1EE3c t\u1EF1 \u0111\u1ED9ng t\u00EDnh to\u00E1n d\u1EF1a tr\u00EAn h\u00F3a \u0111\u01A1n c\u1EE7a c\u00E1c ch\u1EB7ng." })] })] }));
};
exports.ExpenseManager = ExpenseManager;
//# sourceMappingURL=ExpenseManager.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"ExpenseManager.js","sourceRoot":"","sources":["../ExpenseManager.tsx"],"names":[],"mappings":"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"}
+2
View File
@@ -0,0 +1,2 @@
import React from 'react';
export declare const ItineraryTimeline: () => React.JSX.Element;
+23
View File
@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ItineraryTimeline = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const date_fns_1 = require("date-fns");
const lucide_react_1 = require("lucide-react");
const useTourStore_1 = require("./useTourStore");
const TimeVariance = ({ planned, actual }) => {
if (!actual)
return null;
const diff = (0, date_fns_1.differenceInMinutes)((0, date_fns_1.parseISO)(actual), (0, date_fns_1.parseISO)(planned));
const isLate = diff > 0;
return ((0, jsx_runtime_1.jsxs)("div", { className: `flex items-center text-xs font-medium mt-1 ${isLate ? 'text-red-500' : 'text-green-600'}`, children: [isLate ? (0, jsx_runtime_1.jsx)(lucide_react_1.AlertCircle, { className: "w-3 h-3 mr-1" }) : (0, jsx_runtime_1.jsx)(lucide_react_1.CheckCircle2, { className: "w-3 h-3 mr-1" }), (0, jsx_runtime_1.jsx)("span", { children: isLate ? `Trễ ${diff} phút` : diff === 0 ? 'Đúng giờ' : `Sớm ${Math.abs(diff)} phút` })] }));
};
const ItineraryTimeline = () => {
const { legs } = (0, useTourStore_1.useTourStore)();
const toggleComplete = async (placeId) => {
console.log("Toggle status for place:", placeId);
};
return ((0, jsx_runtime_1.jsxs)("div", { className: "max-w-2xl mx-auto p-4 sm:p-6 bg-gray-50 min-h-screen", children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-2xl font-bold text-gray-800 mb-8 px-2", children: "L\u1ED9 tr\u00ECnh chuy\u1EBFn \u0111i" }), (0, jsx_runtime_1.jsx)("div", { className: "space-y-8", children: legs.map((leg, legIdx) => ((0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center mb-4 px-2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "bg-blue-600 text-white text-sm font-bold px-3 py-1 rounded-full shadow-sm", children: ["Ch\u1EB7ng ", leg.sequenceNumber] }), (0, jsx_runtime_1.jsx)("div", { className: "ml-4 h-[1px] flex-1 bg-gray-200" })] }), (0, jsx_runtime_1.jsx)("div", { className: "absolute left-6 top-10 bottom-0 w-0.5 bg-gray-200 -z-0" }), (0, jsx_runtime_1.jsx)("div", { className: "space-y-6 ml-2", children: leg.places.map((place) => ((0, jsx_runtime_1.jsxs)("div", { className: "relative flex group", children: [(0, jsx_runtime_1.jsx)("div", { className: "z-10 mt-1.5 mr-4", children: (0, jsx_runtime_1.jsx)("button", { onClick: () => toggleComplete(place.id), className: `transition-colors duration-200 ${place.isCompleted ? 'text-green-500' : 'text-gray-300 hover:text-blue-500'}`, children: place.isCompleted ? ((0, jsx_runtime_1.jsx)(lucide_react_1.CheckCircle2, { className: "w-8 h-8 bg-white rounded-full" })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.Circle, { className: "w-8 h-8 bg-white rounded-full fill-white" })) }) }), (0, jsx_runtime_1.jsxs)("div", { className: `flex-1 bg-white p-4 rounded-xl border transition-all duration-200 ${place.isCompleted ? 'border-green-100 bg-green-50/30' : 'border-gray-100 shadow-sm hover:shadow-md'}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between items-start", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { className: `font-semibold text-lg ${place.isCompleted ? 'text-gray-500 line-through' : 'text-gray-800'}`, children: place.name }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center text-sm text-gray-500 mt-1", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.MapPin, { className: "w-3 h-3 mr-1" }), (0, jsx_runtime_1.jsx)("span", { className: "truncate max-w-[200px] sm:max-w-md", children: place.address })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "text-right flex flex-col items-end", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center text-sm font-medium text-blue-600", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Clock, { className: "w-3 h-3 mr-1" }), (0, date_fns_1.format)((0, date_fns_1.parseISO)(place.arrivalTime), 'HH:mm')] }), place.isCompleted && place.completedAt && ((0, jsx_runtime_1.jsxs)("div", { className: "text-[10px] text-gray-400 mt-1 italic", children: ["Th\u1EF1c t\u1EBF: ", (0, date_fns_1.format)((0, date_fns_1.parseISO)(place.completedAt), 'HH:mm')] }))] })] }), (0, jsx_runtime_1.jsx)(TimeVariance, { planned: place.arrivalTime, actual: place.completedAt })] })] }, place.id))) }), leg.notes && ((0, jsx_runtime_1.jsxs)("p", { className: "ml-14 mt-4 text-sm text-gray-400 italic", children: ["* ", leg.notes] }))] }, leg.id))) })] }));
};
exports.ItineraryTimeline = ItineraryTimeline;
//# sourceMappingURL=ItineraryTimeline.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"ItineraryTimeline.js","sourceRoot":"","sources":["../ItineraryTimeline.tsx"],"names":[],"mappings":"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"}
+6
View File
@@ -0,0 +1,6 @@
import React from 'react';
interface LandingPageProps {
onContinue?: () => void;
}
export declare const LandingPage: React.FC<LandingPageProps>;
export {};
+13
View File
@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LandingPage = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const lucide_react_1 = require("lucide-react");
const LoginModal_1 = require("./LoginModal");
const LandingPage = ({ onContinue }) => {
const [isLoginModalOpen, setIsLoginModalOpen] = (0, react_1.useState)(false);
return ((0, jsx_runtime_1.jsxs)("div", { className: "flex h-screen w-full flex-col md:flex-row overflow-hidden font-sans relative", children: [(0, jsx_runtime_1.jsxs)("div", { className: "hidden md:block md:w-1/2 relative bg-gray-900", children: [(0, jsx_runtime_1.jsx)("img", { src: "https://images.unsplash.com/photo-1503220317375-aaad61436b1b?q=80&w=2070&auto=format&fit=crop", alt: "Travel Planner Background", className: "absolute inset-0 h-full w-full object-cover opacity-70" }), (0, jsx_runtime_1.jsx)("div", { className: "absolute inset-0 bg-gradient-to-t from-blue-900/60 to-transparent" }), (0, jsx_runtime_1.jsxs)("div", { className: "absolute bottom-16 left-16 text-white z-10", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 mb-6", children: [(0, jsx_runtime_1.jsx)("div", { className: "p-3 bg-white/20 backdrop-blur-md rounded-2xl", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Compass, { className: "w-8 h-8 text-white" }) }), (0, jsx_runtime_1.jsx)("span", { className: "text-3xl font-black tracking-tighter uppercase", children: "Travel Planner" })] }), (0, jsx_runtime_1.jsx)("h2", { className: "text-5xl font-bold leading-tight max-w-lg", children: "Kh\u00E1m ph\u00E1 th\u1EBF gi\u1EDBi, l\u01B0u gi\u1EEF k\u1EC9 ni\u1EC7m." }), (0, jsx_runtime_1.jsx)("p", { className: "mt-4 text-blue-100 text-lg opacity-80 max-w-sm", children: "H\u1EE3p t\u00E1c c\u00F9ng b\u1EA1n b\u00E8 \u0111\u1EC3 l\u00EAn l\u1ECBch tr\u00ECnh ho\u00E0n h\u1EA3o nh\u1EA5t cho m\u1ECDi chuy\u1EBFn \u0111i." })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "flex-1 flex flex-col justify-center items-center bg-white p-8 sm:p-12 md:p-20", children: (0, jsx_runtime_1.jsxs)("div", { className: "max-w-md w-full space-y-12", children: [(0, jsx_runtime_1.jsxs)("div", { className: "space-y-6", children: [(0, jsx_runtime_1.jsx)("h1", { className: "text-4xl md:text-5xl font-extrabold text-gray-900 leading-tight", children: "Ch\u00E0o m\u1EEBng b\u1EA1n \u0111\u1EBFn v\u1EDBi trang t\u1EA1o k\u1EBF ho\u1EA1ch du l\u1ECBch" }), (0, jsx_runtime_1.jsx)("p", { className: "text-xl text-gray-600 leading-relaxed", children: "\u0110\u0103ng nh\u1EADp \u0111\u1EC3 v\u00E0o t\u1EA1o k\u1EBF ho\u1EA1ch c\u00E1 nh\u00E2n ho\u1EB7c nh\u1EA5n n\u00FAt ti\u1EBFp t\u1EE5c \u0111\u1EC3 tham quan c\u00E1c h\u00ECnh \u1EA3nh du l\u1ECBch \u0111\u01B0\u1EE3c chia s\u1EBB." })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-4", children: [(0, jsx_runtime_1.jsxs)("button", { onClick: () => setIsLoginModalOpen(true), className: "group flex items-center justify-center gap-3 bg-blue-600 hover:bg-blue-700 text-white font-bold py-5 px-8 rounded-2xl shadow-xl shadow-blue-200 transition-all active:scale-95", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.LogIn, { className: "w-5 h-5 group-hover:translate-x-1 transition-transform" }), "\u0110\u0103ng nh\u1EADp ngay"] }), (0, jsx_runtime_1.jsxs)("button", { onClick: onContinue, className: "flex items-center justify-center gap-3 bg-gray-50 hover:bg-gray-100 text-gray-700 font-bold py-5 px-8 rounded-2xl border border-gray-200 transition-all active:scale-95", children: ["Ti\u1EBFp t\u1EE5c tham quan \u1EA3nh", (0, jsx_runtime_1.jsx)(lucide_react_1.ArrowRight, { className: "w-5 h-5" })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "pt-12 flex items-center gap-4 text-sm text-gray-400 border-t border-gray-100", children: [(0, jsx_runtime_1.jsx)("div", { className: "flex -space-x-3", children: [1, 2, 3, 4].map(i => ((0, jsx_runtime_1.jsx)("img", { className: "w-10 h-10 rounded-full border-4 border-white", src: `https://i.pravatar.cc/100?img=${i + 20}`, alt: "user" }, i))) }), (0, jsx_runtime_1.jsx)("p", { className: "font-medium", children: "Tham gia c\u00F9ng +2,400 ng\u01B0\u1EDDi du l\u1ECBch kh\u00E1c" })] })] }) }), (0, jsx_runtime_1.jsx)(LoginModal_1.LoginModal, { isOpen: isLoginModalOpen, onClose: () => setIsLoginModalOpen(false) })] }));
};
exports.LandingPage = LandingPage;
//# sourceMappingURL=LandingPage.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"LandingPage.js","sourceRoot":"","sources":["../LandingPage.tsx"],"names":[],"mappings":";;;;AAAA,iCAAwC;AACxC,+CAAkE;AAClE,6CAA0C;AAMnC,MAAM,WAAW,GAA+B,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE;IACxE,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,IAAA,gBAAQ,EAAC,KAAK,CAAC,CAAC;IAEhE,OAAO,CACL,iCAAK,SAAS,EAAC,8EAA8E,aAE3F,iCAAK,SAAS,EAAC,+CAA+C,aAC5D,gCACE,GAAG,EAAC,+FAA+F,EACnG,GAAG,EAAC,2BAA2B,EAC/B,SAAS,EAAC,wDAAwD,GAClE,EAEF,gCAAK,SAAS,EAAC,mEAAmE,GAAG,EAErF,iCAAK,SAAS,EAAC,4CAA4C,aACzD,iCAAK,SAAS,EAAC,8BAA8B,aAC3C,gCAAK,SAAS,EAAC,8CAA8C,YAC3D,uBAAC,sBAAO,IAAC,SAAS,EAAC,oBAAoB,GAAG,GACtC,EACN,iCAAM,SAAS,EAAC,gDAAgD,+BAAsB,IAClF,EACN,+BAAI,SAAS,EAAC,2CAA2C,4FAEpD,EACL,8BAAG,SAAS,EAAC,gDAAgD,uKAEzD,IACA,IACF,EAGN,gCAAK,SAAS,EAAC,+EAA+E,YAC5F,iCAAK,SAAS,EAAC,4BAA4B,aACzC,iCAAK,SAAS,EAAC,WAAW,aACxB,+BAAI,SAAS,EAAC,iEAAiE,mHAE1E,EACL,8BAAG,SAAS,EAAC,uCAAuC,+PAEhD,IACA,EAEN,iCAAK,SAAS,EAAC,qBAAqB,aAElC,oCACE,OAAO,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,EACxC,SAAS,EAAC,gLAAgL,aAE1L,uBAAC,oBAAK,IAAC,SAAS,EAAC,wDAAwD,GAAG,qCAErE,EAGT,oCACE,OAAO,EAAE,UAAU,EACnB,SAAS,EAAC,yKAAyK,sDAGnL,uBAAC,yBAAU,IAAC,SAAS,EAAC,SAAS,GAAG,IAC3B,IACL,EAEN,iCAAK,SAAS,EAAC,8EAA8E,aAC3F,gCAAK,SAAS,EAAC,iBAAiB,YAC7B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CACrB,gCAAa,SAAS,EAAC,8CAA8C,EAAC,GAAG,EAAE,iCAAiC,CAAC,GAAC,EAAE,EAAE,EAAE,GAAG,EAAC,MAAM,IAApH,CAAC,CAAsH,CAClI,CAAC,GACE,EACN,8BAAG,SAAS,EAAC,aAAa,iFAA4C,IAClE,IACF,GACF,EAGN,uBAAC,uBAAU,IAAC,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,KAAK,CAAC,GAAI,IAC/E,CACP,CAAC;AACJ,CAAC,CAAC;AA9EW,QAAA,WAAW,eA8EtB"}
+7
View File
@@ -0,0 +1,7 @@
import React from 'react';
interface LoginModalProps {
isOpen: boolean;
onClose: () => void;
}
export declare const LoginModal: React.FC<LoginModalProps>;
export {};
+12
View File
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LoginModal = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const lucide_react_1 = require("lucide-react");
const LoginModal = ({ isOpen, onClose }) => {
if (!isOpen)
return null;
return ((0, jsx_runtime_1.jsxs)("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6", children: [(0, jsx_runtime_1.jsx)("div", { className: "absolute inset-0 bg-gray-900/60 backdrop-blur-sm animate-in fade-in duration-300", onClick: onClose }), (0, jsx_runtime_1.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: (0, jsx_runtime_1.jsxs)("div", { className: "p-8 sm:p-10", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between items-start mb-8", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h2", { className: "text-3xl font-bold text-gray-900", children: "\u0110\u0103ng nh\u1EADp" }), (0, jsx_runtime_1.jsx)("p", { className: "text-gray-500 mt-2", children: "Ch\u00E0o m\u1EEBng b\u1EA1n quay tr\u1EDF l\u1EA1i!" })] }), (0, jsx_runtime_1.jsx)("button", { onClick: onClose, className: "p-2 hover:bg-gray-100 rounded-full transition-colors text-gray-400 hover:text-gray-600", children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { className: "w-6 h-6" }) })] }), (0, jsx_runtime_1.jsxs)("form", { className: "space-y-6", onSubmit: (e) => e.preventDefault(), children: [(0, jsx_runtime_1.jsxs)("div", { className: "space-y-2", children: [(0, jsx_runtime_1.jsx)("label", { className: "text-sm font-semibold text-gray-700 ml-1", children: "Email" }), (0, jsx_runtime_1.jsxs)("div", { className: "relative group", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.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" }), (0, jsx_runtime_1.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" })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex justify-between items-center px-1", children: [(0, jsx_runtime_1.jsx)("label", { className: "text-sm font-semibold text-gray-700", children: "M\u1EADt kh\u1EA9u" }), (0, jsx_runtime_1.jsx)("button", { className: "text-xs font-bold text-blue-600 hover:text-blue-700", children: "Qu\u00EAn m\u1EADt kh\u1EA9u?" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "relative group", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.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" }), (0, jsx_runtime_1.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" })] })] }), (0, jsx_runtime_1.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", (0, jsx_runtime_1.jsx)(lucide_react_1.ArrowRight, { className: "w-5 h-5" })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "mt-10 pt-8 border-t border-gray-100 text-center", children: (0, jsx_runtime_1.jsxs)("p", { className: "text-gray-500", children: ["Ch\u01B0a c\u00F3 t\u00E0i kho\u1EA3n?", ' ', (0, jsx_runtime_1.jsx)("button", { className: "font-bold text-blue-600 hover:underline", children: "T\u1EA1o t\u00E0i kho\u1EA3n ngay" })] }) })] }) })] }));
};
exports.LoginModal = LoginModal;
//# sourceMappingURL=LoginModal.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"LoginModal.js","sourceRoot":"","sources":["../LoginModal.tsx"],"names":[],"mappings":";;;;AACA,+CAAyD;AAOlD,MAAM,UAAU,GAA8B,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;IAC3E,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,OAAO,CACL,iCAAK,SAAS,EAAC,gEAAgE,aAE7E,gCACE,SAAS,EAAC,kFAAkF,EAC5F,OAAO,EAAE,OAAO,GAChB,EAGF,gCAAK,SAAS,EAAC,6GAA6G,YAC1H,iCAAK,SAAS,EAAC,aAAa,aAC1B,iCAAK,SAAS,EAAC,uCAAuC,aACpD,4CACE,+BAAI,SAAS,EAAC,kCAAkC,yCAAe,EAC/D,8BAAG,SAAS,EAAC,oBAAoB,qEAAgC,IAC7D,EACN,mCACE,OAAO,EAAE,OAAO,EAChB,SAAS,EAAC,wFAAwF,YAElG,uBAAC,gBAAC,IAAC,SAAS,EAAC,SAAS,GAAG,GAClB,IACL,EAEN,kCAAM,SAAS,EAAC,WAAW,EAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,aAC7D,iCAAK,SAAS,EAAC,WAAW,aACxB,kCAAO,SAAS,EAAC,0CAA0C,sBAAc,EACzE,iCAAK,SAAS,EAAC,gBAAgB,aAC7B,uBAAC,mBAAI,IAAC,SAAS,EAAC,mHAAmH,GAAG,EACtI,kCACE,IAAI,EAAC,OAAO,EACZ,WAAW,EAAC,kBAAkB,EAC9B,SAAS,EAAC,kJAAkJ,GAC5J,IACE,IACF,EAEN,iCAAK,SAAS,EAAC,WAAW,aACxB,iCAAK,SAAS,EAAC,wCAAwC,aACrD,kCAAO,SAAS,EAAC,qCAAqC,mCAAiB,EACvE,mCAAQ,SAAS,EAAC,qDAAqD,8CAAwB,IAC3F,EACN,iCAAK,SAAS,EAAC,gBAAgB,aAC7B,uBAAC,mBAAI,IAAC,SAAS,EAAC,mHAAmH,GAAG,EACtI,kCACE,IAAI,EAAC,UAAU,EACf,WAAW,EAAC,kDAAU,EACtB,SAAS,EAAC,kJAAkJ,GAC5J,IACE,IACF,EAEN,oCACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,gLAAgL,yCAG1L,uBAAC,yBAAU,IAAC,SAAS,EAAC,SAAS,GAAG,IAC3B,IACJ,EAEP,gCAAK,SAAS,EAAC,iDAAiD,YAC9D,+BAAG,SAAS,EAAC,eAAe,uDACP,GAAG,EACtB,mCAAQ,SAAS,EAAC,yCAAyC,kDAA4B,IACrF,GACA,IACF,GACF,IACF,CACP,CAAC;AACJ,CAAC,CAAC;AA1EW,QAAA,UAAU,cA0ErB"}
+2
View File
@@ -0,0 +1,2 @@
import React from 'react';
export declare const TourDetailPage: () => React.JSX.Element;
+37
View File
@@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TourDetailPage = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const ItineraryTimeline_1 = require("./ItineraryTimeline");
const ExpenseManager_1 = require("./ExpenseManager");
const useTourStore_1 = require("./useTourStore");
const lucide_react_1 = require("lucide-react");
const TourDetailPage = () => {
const [activeTab, setActiveTab] = (0, react_1.useState)('plan');
const { currentTour, fetchTour, userRole } = (0, useTourStore_1.useTourStore)();
(0, react_1.useEffect)(() => {
fetchTour(1);
}, [fetchTour]);
const tabs = [
{ id: 'plan', label: 'Lộ trình', icon: lucide_react_1.Map, visible: ['OWNER', 'EDITOR', 'MEMBER_PLAN_ONLY'].includes(userRole || '') },
{ id: 'expense', label: 'Chi phí', icon: lucide_react_1.Wallet, visible: ['OWNER', 'EDITOR'].includes(userRole || '') },
{ id: 'photo', label: 'Ảnh', icon: lucide_react_1.Image, visible: true },
].filter(t => t.visible);
(0, react_1.useEffect)(() => {
if (userRole === 'MEMBER_PHOTO_ONLY') {
setActiveTab('photo');
}
}, [userRole]);
const tourInfo = {
title: currentTour?.title || "Hành trình khám phá TP.HCM",
date: "20 - 21 Tháng 11, 2023",
members: 5,
budget: "2.500.000 VND"
};
return ((0, jsx_runtime_1.jsxs)("div", { className: "min-h-screen bg-white", children: [(0, jsx_runtime_1.jsxs)("div", { className: "sticky top-0 z-30 bg-white border-b border-gray-100 px-4 py-3 flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("button", { className: "p-2 hover:bg-gray-100 rounded-full transition-colors", children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronLeft, { className: "w-6 h-6 text-gray-600" }) }), (0, jsx_runtime_1.jsx)("h1", { className: "text-lg font-bold text-gray-800 truncate px-4", children: tourInfo.title }), (0, jsx_runtime_1.jsx)("div", { className: "w-10" }), " "] }), (0, jsx_runtime_1.jsx)("div", { className: "bg-blue-600 text-white p-6 pb-20", children: (0, jsx_runtime_1.jsxs)("div", { className: "max-w-2xl mx-auto space-y-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-wrap gap-4 text-sm opacity-90", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Calendar, { className: "w-4 h-4 mr-1.5" }), tourInfo.date] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Users, { className: "w-4 h-4 mr-1.5" }), tourInfo.members, " th\u00E0nh vi\u00EAn"] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-baseline gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-3xl font-bold", children: tourInfo.budget }), (0, jsx_runtime_1.jsx)("span", { className: "text-blue-100 text-sm", children: "d\u1EF1 ki\u1EBFn" })] })] }) }), (0, jsx_runtime_1.jsxs)("div", { className: "max-w-2xl mx-auto -mt-12 px-4 pb-24", children: [(0, jsx_runtime_1.jsx)("div", { className: "bg-white rounded-2xl shadow-xl border border-gray-100 p-1 flex mb-6", children: tabs.map((tab) => ((0, jsx_runtime_1.jsxs)("button", { onClick: () => setActiveTab(tab.id), className: `flex-1 flex items-center justify-center py-3 rounded-xl text-sm font-semibold transition-all ${activeTab === tab.id
? 'bg-blue-50 text-blue-600 shadow-sm'
: 'text-gray-400 hover:text-gray-600'}`, children: [(0, jsx_runtime_1.jsx)(tab.icon, { className: `w-4 h-4 mr-2 ${activeTab === tab.id ? 'animate-pulse' : ''}` }), tab.label] }, tab.id))) }), (0, jsx_runtime_1.jsxs)("div", { className: "transition-opacity duration-300", children: [activeTab === 'plan' && ((0, jsx_runtime_1.jsx)("div", { className: "animate-in fade-in slide-in-from-bottom-4", children: (0, jsx_runtime_1.jsx)(ItineraryTimeline_1.ItineraryTimeline, {}) })), activeTab === 'expense' && ((0, jsx_runtime_1.jsx)("div", { className: "animate-in fade-in slide-in-from-bottom-4", children: (0, jsx_runtime_1.jsx)(ExpenseManager_1.ExpenseManager, {}) })), activeTab === 'photo' && ((0, jsx_runtime_1.jsx)("div", { className: "grid grid-cols-3 gap-2 animate-in fade-in", children: [1, 2, 3, 4, 5, 6].map((i) => ((0, jsx_runtime_1.jsxs)("div", { className: "aspect-square bg-gray-100 rounded-lg overflow-hidden relative group", children: [(0, jsx_runtime_1.jsx)("div", { className: "absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors" }), (0, jsx_runtime_1.jsx)("img", { src: `https://picsum.photos/seed/${i + 10}/400/400`, alt: "Tour photo", className: "w-full h-full object-cover" })] }, i))) }))] })] }), (0, jsx_runtime_1.jsx)("div", { className: "fixed bottom-6 left-1/2 -translate-x-1/2 z-40", children: (0, jsx_runtime_1.jsx)("button", { className: "bg-blue-600 text-white px-6 py-3 rounded-full shadow-lg hover:bg-blue-700 transition-all flex items-center font-bold", children: activeTab === 'plan' ? 'Thêm địa điểm' : activeTab === 'expense' ? 'Thêm chi phí' : 'Đăng ảnh' }) })] }));
};
exports.TourDetailPage = TourDetailPage;
//# sourceMappingURL=TourDetailPage.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"TourDetailPage.js","sourceRoot":"","sources":["../TourDetailPage.tsx"],"names":[],"mappings":"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"}
+11
View File
@@ -0,0 +1,11 @@
export declare class ExpenseEngine {
calculateSplit(totalCost: number, adultCount: number, childCount: number, childDiscountPercent: number): {
targetTotal: number;
actualTotal: number;
perAdult: number;
perChild: number;
roundingDifference: number;
adultsTotal: number;
childrenTotal: number;
};
}
+38
View File
@@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExpenseEngine = void 0;
const common_1 = require("@nestjs/common");
class ExpenseEngine {
calculateSplit(totalCost, adultCount, childCount, childDiscountPercent) {
if (totalCost < 0 || adultCount < 0 || childCount < 0 || childDiscountPercent < 0) {
throw new common_1.BadRequestException('Dữ liệu đầu vào không được là số âm.');
}
if (childDiscountPercent > 100) {
throw new common_1.BadRequestException('Phần trăm giảm giá không thể vượt quá 100%.');
}
if (adultCount + childCount === 0) {
throw new common_1.BadRequestException('Số lượng người tham gia phải lớn hơn 0.');
}
const discountRate = childDiscountPercent / 100;
const childRateFactor = 1 - discountRate;
const weightedParticipantCount = adultCount + (childCount * childRateFactor);
const adultPriceRaw = totalCost / weightedParticipantCount;
const childPriceRaw = adultPriceRaw * childRateFactor;
const perAdult = Math.round(adultPriceRaw);
const perChild = Math.round(childPriceRaw);
const adultsTotal = perAdult * adultCount;
const childrenTotal = perChild * childCount;
const actualSum = adultsTotal + childrenTotal;
return {
targetTotal: totalCost,
actualTotal: actualSum,
perAdult,
perChild,
roundingDifference: totalCost - actualSum,
adultsTotal,
childrenTotal,
};
}
}
exports.ExpenseEngine = ExpenseEngine;
//# sourceMappingURL=expense-engine.service.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"expense-engine.service.js","sourceRoot":"","sources":["../expense-engine.service.ts"],"names":[],"mappings":";;;AAAA,2CAAqD;AAMrD,MAAa,aAAa;IAQxB,cAAc,CACZ,SAAiB,EACjB,UAAkB,EAClB,UAAkB,EAClB,oBAA4B;QAG5B,IAAI,SAAS,GAAG,CAAC,IAAI,UAAU,GAAG,CAAC,IAAI,UAAU,GAAG,CAAC,IAAI,oBAAoB,GAAG,CAAC,EAAE,CAAC;YAClF,MAAM,IAAI,4BAAmB,CAAC,sCAAsC,CAAC,CAAC;QACxE,CAAC;QAED,IAAI,oBAAoB,GAAG,GAAG,EAAE,CAAC;YAC/B,MAAM,IAAI,4BAAmB,CAAC,6CAA6C,CAAC,CAAC;QAC/E,CAAC;QAGD,IAAI,UAAU,GAAG,UAAU,KAAK,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,4BAAmB,CAAC,yCAAyC,CAAC,CAAC;QAC3E,CAAC;QAED,MAAM,YAAY,GAAG,oBAAoB,GAAG,GAAG,CAAC;QAChD,MAAM,eAAe,GAAG,CAAC,GAAG,YAAY,CAAC;QAQzC,MAAM,wBAAwB,GAAG,UAAU,GAAG,CAAC,UAAU,GAAG,eAAe,CAAC,CAAC;QAE7E,MAAM,aAAa,GAAG,SAAS,GAAG,wBAAwB,CAAC;QAC3D,MAAM,aAAa,GAAG,aAAa,GAAG,eAAe,CAAC;QAGtD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAE3C,MAAM,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAC;QAC1C,MAAM,aAAa,GAAG,QAAQ,GAAG,UAAU,CAAC;QAC5C,MAAM,SAAS,GAAG,WAAW,GAAG,aAAa,CAAC;QAE9C,OAAO;YACL,WAAW,EAAE,SAAS;YACtB,WAAW,EAAE,SAAS;YACtB,QAAQ;YACR,QAAQ;YACR,kBAAkB,EAAE,SAAS,GAAG,SAAS;YACzC,WAAW;YACX,aAAa;SACd,CAAC;IACJ,CAAC;CACF;AA5DD,sCA4DC"}
+1
View File
@@ -0,0 +1 @@
import 'dotenv/config';
+86
View File
@@ -0,0 +1,86 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@nestjs/core");
const common_1 = require("@nestjs/common");
const prisma_service_1 = require("./prisma.service");
require("dotenv/config");
let AppController = class AppController {
getHello() {
return 'Travel Planning API is running!';
}
};
__decorate([
(0, common_1.Get)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", String)
], AppController.prototype, "getHello", null);
AppController = __decorate([
(0, common_1.Controller)()
], AppController);
let TourController = class TourController {
constructor(prisma) {
this.prisma = prisma;
}
async getTourDetails(id) {
const tour = await this.prisma.tour.findUnique({
where: { id },
include: {
members: true,
photos: true,
legs: {
orderBy: { sequenceNumber: 'asc' },
include: {
places: { orderBy: { sequenceInLeg: 'asc' } },
expenses: true,
},
},
},
});
if (!tour)
throw new common_1.NotFoundException(`Không tìm thấy Tour với ID ${id}`);
return tour;
}
};
__decorate([
(0, common_1.Get)(':id'),
__param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number]),
__metadata("design:returntype", Promise)
], TourController.prototype, "getTourDetails", null);
TourController = __decorate([
(0, common_1.Controller)('api/v1/tours'),
__metadata("design:paramtypes", [prisma_service_1.PrismaService])
], TourController);
let AppModule = class AppModule {
};
AppModule = __decorate([
(0, common_1.Module)({
controllers: [AppController, TourController],
providers: [prisma_service_1.PrismaService],
exports: [prisma_service_1.PrismaService]
})
], AppModule);
async function bootstrap() {
const app = await core_1.NestFactory.create(AppModule);
app.enableCors();
app.setGlobalPrefix('api');
const port = process.env.PORT || 3001;
await app.listen(port);
console.log(`🚀 Server is running on: http://localhost:${port}`);
}
bootstrap();
//# sourceMappingURL=main.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"main.js","sourceRoot":"","sources":["../main.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,uCAA2C;AAC3C,2CAAiG;AACjG,qDAAiD;AACjD,yBAAuB;AAGvB,IAAM,aAAa,GAAnB,MAAM,aAAa;IAEjB,QAAQ;QACN,OAAO,iCAAiC,CAAC;IAC3C,CAAC;CACF,CAAA;AAHC;IADC,IAAA,YAAG,GAAE;;;;6CAGL;AAJG,aAAa;IADlB,IAAA,mBAAU,GAAE;GACP,aAAa,CAKlB;AAED,IACM,cAAc,GADpB,MACM,cAAc;IAClB,YAAoB,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAGvC,AAAN,KAAK,CAAC,cAAc,CAA4B,EAAU;QACxD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;YAC7C,KAAK,EAAE,EAAE,EAAE,EAAE;YACb,OAAO,EAAE;gBACP,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,IAAI;gBACZ,IAAI,EAAE;oBACJ,OAAO,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE;oBAClC,OAAO,EAAE;wBACP,MAAM,EAAE,EAAE,OAAO,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE;wBAC7C,QAAQ,EAAE,IAAI;qBACf;iBACF;aACF;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,0BAAiB,CAAC,8BAA8B,EAAE,EAAE,CAAC,CAAC;QAC3E,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AAnBO;IADL,IAAA,YAAG,EAAC,KAAK,CAAC;IACW,WAAA,IAAA,cAAK,EAAC,IAAI,EAAE,qBAAY,CAAC,CAAA;;;;oDAkB9C;AAtBG,cAAc;IADnB,IAAA,mBAAU,EAAC,cAAc,CAAC;qCAEG,8BAAa;GADrC,cAAc,CAuBnB;AAOD,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,SAAS;IALd,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,aAAa,EAAE,cAAc,CAAC;QAC5C,SAAS,EAAE,CAAC,8BAAa,CAAC;QAC1B,OAAO,EAAE,CAAC,8BAAa,CAAC;KACzB,CAAC;GACI,SAAS,CAAG;AAElB,KAAK,UAAU,SAAS;IACtB,MAAM,GAAG,GAAG,MAAM,kBAAW,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAChD,GAAG,CAAC,UAAU,EAAE,CAAC;IACjB,GAAG,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAE3B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC;IACtC,MAAM,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACvB,OAAO,CAAC,GAAG,CAAC,6CAA6C,IAAI,EAAE,CAAC,CAAC;AACnE,CAAC;AACD,SAAS,EAAE,CAAC"}
+3
View File
@@ -0,0 +1,3 @@
import 'dotenv/config';
declare const _default: import("@prisma/config").PrismaConfigInternal;
export default _default;
+10
View File
@@ -0,0 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const config_1 = require("@prisma/config");
require("dotenv/config");
exports.default = (0, config_1.defineConfig)({
datasource: {
url: process.env.DATABASE_URL,
},
});
//# sourceMappingURL=prisma.config.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"prisma.config.js","sourceRoot":"","sources":["../prisma.config.ts"],"names":[],"mappings":";;AAAA,2CAA8C;AAC9C,yBAAuB;AAEvB,kBAAe,IAAA,qBAAY,EAAC;IAC1B,UAAU,EAAE;QAEV,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY;KAC9B;CACF,CAAC,CAAC"}
+9
View File
@@ -0,0 +1,9 @@
import { OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import 'dotenv/config';
export declare class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
private pool;
constructor();
onModuleInit(): Promise<void>;
onModuleDestroy(): Promise<void>;
}
+36
View File
@@ -0,0 +1,36 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PrismaService = void 0;
const common_1 = require("@nestjs/common");
const client_1 = require("@prisma/client");
const adapter_pg_1 = require("@prisma/adapter-pg");
const pg_1 = require("pg");
require("dotenv/config");
let PrismaService = class PrismaService extends client_1.PrismaClient {
constructor() {
const pool = new pg_1.Pool({ connectionString: process.env.DATABASE_URL });
const adapter = new adapter_pg_1.PrismaPg(pool);
super({ adapter });
this.pool = pool;
}
async onModuleInit() { await this.$connect(); }
async onModuleDestroy() {
await this.$disconnect();
await this.pool.end();
}
};
exports.PrismaService = PrismaService;
exports.PrismaService = PrismaService = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [])
], PrismaService);
//# sourceMappingURL=prisma.service.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"prisma.service.js","sourceRoot":"","sources":["../prisma.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA2E;AAC3E,2CAA8C;AAC9C,mDAA8C;AAC9C,2BAA0B;AAC1B,yBAAuB;AAGhB,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,qBAAY;IAG7C;QACE,MAAM,IAAI,GAAG,IAAI,SAAI,CAAC,EAAE,gBAAgB,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,IAAI,qBAAQ,CAAC,IAAI,CAAC,CAAC;QACnC,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QACnB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,YAAY,KAAK,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAC/C,KAAK,CAAC,eAAe;QACnB,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;QACzB,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;IACxB,CAAC;CACF,CAAA;AAfY,sCAAa;wBAAb,aAAa;IADzB,IAAA,mBAAU,GAAE;;GACA,aAAa,CAezB"}
+7
View File
@@ -0,0 +1,7 @@
import { CanActivate, ExecutionContext } from '@nestjs/common';
import { PrismaService } from './prisma.service';
export declare class TourRoleGuard implements CanActivate {
private prisma;
constructor(prisma: PrismaService);
canActivate(context: ExecutionContext): Promise<boolean>;
}
+54
View File
@@ -0,0 +1,54 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TourRoleGuard = void 0;
const common_1 = require("@nestjs/common");
const prisma_service_1 = require("./prisma.service");
let TourRoleGuard = class TourRoleGuard {
constructor(prisma) {
this.prisma = prisma;
}
async canActivate(context) {
const request = context.switchToHttp().getRequest();
const user = request.user;
const tourId = parseInt(request.params.id || request.params.tourId);
const path = request.url;
if (!user || isNaN(tourId)) {
throw new common_1.ForbiddenException("Thông tin xác thực hoặc mã Tour không hợp lệ.");
}
const membership = await this.prisma.tourMember.findUnique({
where: {
tourId_userId: {
tourId: tourId,
userId: user.id,
},
},
});
if (!membership) {
throw new common_1.ForbiddenException("Bạn không phải là thành viên của tour này.");
}
request.tourMembership = membership;
const role = membership.role;
const isPlanPath = path.includes('/plans');
const isExpensePath = path.includes('/expenses');
if ((role === 'MEMBER_PHOTO_ONLY' || role === 'VIEWER_EXTERNAL') &&
(isPlanPath || isExpensePath)) {
throw new common_1.ForbiddenException("Bạn không có quyền xem kế hoạch và chi phí của tour này.");
}
return true;
}
};
exports.TourRoleGuard = TourRoleGuard;
exports.TourRoleGuard = TourRoleGuard = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [prisma_service_1.PrismaService])
], TourRoleGuard);
//# sourceMappingURL=rbac.middleware.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"rbac.middleware.js","sourceRoot":"","sources":["../rbac.middleware.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA+F;AAC/F,qDAAiD;AAG1C,IAAM,aAAa,GAAnB,MAAM,aAAa;IACxB,YAAoB,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAE7C,KAAK,CAAC,WAAW,CAAC,OAAyB;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QAG1B,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACpE,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC;QAEzB,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,2BAAkB,CAAC,+CAA+C,CAAC,CAAC;QAChF,CAAC;QAOD,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC;YACzD,KAAK,EAAE;gBACL,aAAa,EAAE;oBACb,MAAM,EAAE,MAAM;oBACd,MAAM,EAAE,IAAI,CAAC,EAAE;iBAChB;aACF;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,2BAAkB,CAAC,4CAA4C,CAAC,CAAC;QAC7E,CAAC;QAGD,OAAO,CAAC,cAAc,GAAG,UAAU,CAAC;QAEpC,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC;QAC7B,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC3C,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAGjD,IACE,CAAC,IAAI,KAAK,mBAAmB,IAAI,IAAI,KAAK,iBAAiB,CAAC;YAC5D,CAAC,UAAU,IAAI,aAAa,CAAC,EAC7B,CAAC;YACD,MAAM,IAAI,2BAAkB,CAAC,0DAA0D,CAAC,CAAC;QAC3F,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AAlDY,sCAAa;wBAAb,aAAa;IADzB,IAAA,mBAAU,GAAE;qCAEiB,8BAAa;GAD9B,aAAa,CAkDzB"}
+1
View File
@@ -0,0 +1 @@
import 'dotenv/config';
+97
View File
@@ -0,0 +1,97 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const client_1 = require("@prisma/client");
const adapter_pg_1 = require("@prisma/adapter-pg");
const pg_1 = require("pg");
require("dotenv/config");
const pool = new pg_1.Pool({ connectionString: process.env.DATABASE_URL });
const adapter = new adapter_pg_1.PrismaPg(pool);
const prisma = new client_1.PrismaClient({ adapter });
async function main() {
console.log('--- Đang xóa dữ liệu cũ... ---');
await prisma.expense.deleteMany();
await prisma.photo.deleteMany();
await prisma.tour.deleteMany();
await prisma.user.deleteMany();
console.log('--- Đang tạo người dùng mẫu... ---');
const owner = await prisma.user.create({
data: {
email: 'owner@travel.com',
name: 'Lộc Phạm (Chủ Tour)',
passwordHash: 'hashed_password_123',
},
});
const photoMember = await prisma.user.create({
data: {
email: 'photomember@travel.com',
name: 'Nguyễn Văn Ảnh (Chỉ xem ảnh)',
passwordHash: 'hashed_password_456',
},
});
console.log('--- Đang tạo Tour và phân quyền... ---');
const tour = await prisma.tour.create({
data: {
title: 'Hành trình khám phá TP.HCM',
startDate: new Date('2023-11-20'),
endDate: new Date('2023-11-21'),
creatorId: owner.id,
members: {
create: [
{ userId: owner.id, role: 'OWNER' },
{ userId: photoMember.id, role: 'MEMBER_PHOTO_ONLY' },
],
},
},
});
console.log('--- Đang tạo chặng và địa điểm... ---');
const leg1 = await prisma.leg.create({
data: {
tourId: tour.id,
sequenceNumber: 1,
notes: 'Khám phá lịch sử trung tâm',
places: {
create: [
{
name: 'Dinh Độc Lập',
address: '135 Nam Kỳ Khởi Nghĩa, Quận 1',
latitude: 10.777,
longitude: 106.695,
sequenceInLeg: 1,
arrivalTime: new Date('2023-11-20T08:00:00Z'),
},
{
name: 'Bưu điện Thành phố',
address: '02 Công xã Paris, Quận 1',
latitude: 10.779,
longitude: 106.699,
sequenceInLeg: 2,
arrivalTime: new Date('2023-11-20T10:00:00Z'),
},
],
},
},
});
console.log('--- Đang tạo chi phí mẫu... ---');
await prisma.expense.create({
data: {
legId: leg1.id,
category: 'DINING',
amount: 500000,
currency: 'VND',
description: 'Ăn trưa đặc sản Quận 1',
},
});
console.log('--- Seed dữ liệu hoàn tất! ---');
console.log(`Email đăng nhập Owner: ${owner.email}`);
console.log(`Email đăng nhập Photo Only: ${photoMember.email}`);
console.log(`Tour ID để test: ${tour.id}`);
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await pool.end();
});
//# sourceMappingURL=seed.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"seed.js","sourceRoot":"","sources":["../seed.ts"],"names":[],"mappings":"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"}
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
interface TourState {
currentTour: any;
legs: any[];
userRole: string | null;
setTour: (tour: any) => void;
updateLegs: (legs: any[]) => void;
optimizeRouting: () => Promise<void>;
fetchTour: (id: number) => Promise<void>;
}
export declare const useTourStore: import("zustand").UseBoundStore<import("zustand").StoreApi<TourState>>;
export {};
+21
View File
@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useTourStore = void 0;
const zustand_1 = require("zustand");
exports.useTourStore = (0, zustand_1.create)((set, get) => ({
currentTour: null,
legs: [],
userRole: null,
setTour: (tour) => set({ currentTour: tour }),
updateLegs: (legs) => set({ legs }),
fetchTour: async (id) => {
const currentUserId = 1;
const response = await fetch(`http://localhost:3001/api/v1/tours/${id}`);
const data = await response.json();
const role = data.members?.find((m) => m.userId === currentUserId)?.role || 'VIEWER_EXTERNAL';
set({ currentTour: data, legs: data.legs || [], userRole: role });
},
optimizeRouting: async () => {
},
}));
//# sourceMappingURL=useTourStore.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"useTourStore.js","sourceRoot":"","sources":["../useTourStore.ts"],"names":[],"mappings":";;;AAAA,qCAAiC;AAYpB,QAAA,YAAY,GAAG,IAAA,gBAAM,EAAY,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAC3D,WAAW,EAAE,IAAI;IACjB,IAAI,EAAE,EAAE;IACR,QAAQ,EAAE,IAAI;IACd,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAC7C,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;IACnC,SAAS,EAAE,KAAK,EAAE,EAAU,EAAE,EAAE;QAE9B,MAAM,aAAa,GAAG,CAAC,CAAC;QACxB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,sCAAsC,EAAE,EAAE,CAAC,CAAC;QACzE,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAEnC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,aAAa,CAAC,EAAE,IAAI,IAAI,iBAAiB,CAAC;QACnG,GAAG,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACpE,CAAC;IACD,eAAe,EAAE,KAAK,IAAI,EAAE;IAE5B,CAAC;CACF,CAAC,CAAC,CAAC"}