nvis-fe-cms-libs 1.1.4 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,240 @@
1
- # React + Vite
2
-
3
- This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
-
5
- Currently, two official plugins are available:
6
-
7
- - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
8
- - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9
-
10
- ## Expanding the ESLint configuration
11
-
12
- If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
1
+ # 📦 nvis-fe-cms-lib
2
+
3
+ Thư viện các component React được đóng gói để sử dụng trong dự án CMS Landing Page.
4
+ Thay vì viết lại code cho từng section, bạn có thể import trực tiếp các component từ thư viện này.
5
+
6
+ ---
7
+
8
+ ## 🚀 Bước 1: Khởi Tạo Dự Án Thư Viện
9
+
10
+ Sử dụng Vite để tạo một dự án mới:
11
+
12
+ ```bash
13
+ npm create vite@latest nvis-fe-cms-lib -- --template react
14
+ ```
15
+
16
+ Cài đặt dependencies:
17
+
18
+ ```bash
19
+ cd nvis-fe-cms-lib
20
+ npm install
21
+ ```
22
+
23
+ ---
24
+
25
+ ## ⚙️ Bước 2: Cấu Hình Dự Án cho Thư Viện
26
+
27
+ ### 1. Sửa vite.config.js
28
+
29
+ ```javascript
30
+ import { defineConfig } from 'vite';
31
+ import react from '@vitejs/plugin-react';
32
+ import path from 'path';
33
+
34
+ export default defineConfig({
35
+ plugins: [react()],
36
+ build: {
37
+ lib: {
38
+ entry: path.resolve(__dirname, 'src/index.js'),
39
+ name: 'AsaCmsSections',
40
+ fileName: (format) => `nvis-fe-cms-libs.${format}.js`,
41
+ formats: ['es', 'umd']
42
+ },
43
+ rollupOptions: {
44
+ external: ['react', 'react-dom'],
45
+ output: {
46
+ globals: {
47
+ 'react': 'React',
48
+ 'react-dom': 'ReactDOM'
49
+ },
50
+ // Đảm bảo export đúng format
51
+ exports: 'named'
52
+ }
53
+ },
54
+ // Tối ưu cho library
55
+ target: 'es2015',
56
+ sourcemap: true,
57
+ // Tắt minification để debug dễ hơn (có thể bật lại sau)
58
+ minify: false
59
+ },
60
+ // Cấu hình cho development
61
+ resolve: {
62
+ alias: {
63
+ '@': path.resolve(__dirname, 'src')
64
+ }
65
+ }
66
+ });
67
+ ```
68
+
69
+ ### 2. Sửa package.json
70
+
71
+ ```json
72
+ {
73
+ "name": "nvis-fe-cms-libs",
74
+ "version": "1.0.0",
75
+ "type": "module",
76
+ "main": "dist/nvis-fe-cms-libs.umd.js",
77
+ "module": "dist/nvis-fe-cms-libs.es.js",
78
+ "types": "dist/index.d.ts",
79
+ "files": [
80
+ "dist",
81
+ "README.md"
82
+ ],
83
+ "scripts": {
84
+ "dev": "vite",
85
+ "build": "vite build",
86
+ "lint": "eslint .",
87
+ "preview": "vite preview",
88
+ "prepublishOnly": "npm run build"
89
+ },
90
+ "exports": {
91
+ ".": {
92
+ "import": "./dist/nvis-fe-cms-libs.es.js",
93
+ "require": "./dist/nvis-fe-cms-libs.umd.js",
94
+ "types": "./dist/index.d.ts"
95
+ },
96
+ "./dist/style.css": "./dist/style.css"
97
+ },
98
+ "keywords": [
99
+ "react",
100
+ "cms",
101
+ "components",
102
+ "ui",
103
+ "tailwindcss"
104
+ ],
105
+ "author": "NVIS FE Team",
106
+ "license": "MIT",
107
+ "repository": {
108
+ "type": "git",
109
+ "url": "https://github.com/NVISToolkit/nvis-fe-cms-lib.git"
110
+ },
111
+ "dependencies": {
112
+ "i18next": "^25.5.2",
113
+ "i18next-browser-languagedetector": "^8.2.0",
114
+ "react-i18next": "^15.7.3"
115
+ },
116
+ "devDependencies": {
117
+ "@eslint/js": "^9.33.0",
118
+ "@types/react": "^19.1.10",
119
+ "@types/react-dom": "^19.1.7",
120
+ "@vitejs/plugin-react": "^5.0.0",
121
+ "autoprefixer": "^10.4.21",
122
+ "eslint": "^9.33.0",
123
+ "eslint-plugin-react-hooks": "^5.2.0",
124
+ "eslint-plugin-react-refresh": "^0.4.20",
125
+ "globals": "^16.3.0",
126
+ "postcss": "^8.5.6",
127
+ "tailwindcss": "^3.4.17",
128
+ "vite": "^7.1.2"
129
+ },
130
+ "peerDependencies": {
131
+ "react": "^18.0.0 || ^19.0.0",
132
+ "react-dom": "^18.0.0 || ^19.0.0"
133
+ }
134
+ }
135
+ ```
136
+ - `exports` giúp các công cụ build tìm đúng file.
137
+ - `peerDependencies` đảm bảo dự án dùng thư viện có sẵn React, tránh xung đột.
138
+
139
+ ---
140
+
141
+ ## 🧩 Bước 3: Code và Cấu Trúc Component
142
+
143
+ ### Ví dụ Component (AboutCompanySection.jsx)
144
+
145
+ ```javascript
146
+ // src/components/AboutCompanySection.jsx
147
+ import React from "react";
148
+ import CompanyValues from "./CompanyValues";
149
+
150
+ const AboutCompanySection = ({ data, t, isDarkMode }) => {
151
+ // Kiểm tra t function
152
+ const safeT = typeof t === 'function' ? t : (key, options) => {
153
+ console.warn(`Translation function not provided for key: ${key}`);
154
+ return key;
155
+ };
156
+
157
+ const companyData = data?.sectionDataBindingItems?.[0]?.data;
158
+
159
+ if (!companyData) {
160
+ return null;
161
+ }
162
+
163
+ const name = companyData.name || safeT('aboutCompany.defaults.companyName');
164
+ const shortName = companyData.shortName || safeT('aboutCompany.defaults.shortName');
165
+ const shortDescription = companyData.shortDescription || safeT('aboutCompany.defaults.shortDescription');
166
+ const establishedYear = companyData.establishedYear || "----";
167
+ const experienceYears = companyData.experienceYears || "0";
168
+ const description = companyData.description || "";
169
+ ....
170
+
171
+ export default AboutCompanySection;
172
+ ```
173
+
174
+ ### File Đầu Vào (src/index.js)
175
+
176
+ ```javascript
177
+ // src/index.js
178
+ import AboutCompanySection from './sections/aboutCompany/AboutCompanySection';
179
+ import CompanyValues from './sections/aboutCompany/CompanyValues';
180
+
181
+ // Export các components
182
+ export { AboutCompanySection, CompanyValues };
183
+
184
+ // Default export (tuỳ chọn)
185
+ export default {
186
+ AboutCompanySection,
187
+ CompanyValues
188
+ };
189
+ ```
190
+
191
+ ---
192
+
193
+ ## 📦 Bước 4: Đóng Gói và Publish lên npm
194
+
195
+ 1. Tăng phiên bản trong `package.json` (ví dụ 1.0.2 → 1.0.3).
196
+ 2. Chạy build để tạo thư mục dist:
197
+
198
+ ```bash
199
+ npm run build
200
+ ```
201
+
202
+ 3. Đăng nhập và publish:
203
+
204
+ ```bash
205
+ npm login
206
+ npm publish
207
+ ```
208
+
209
+ ---
210
+
211
+ ## 🌐 Bước 5: Sử Dụng Thư Viện trong Dự Án CMS
212
+
213
+ Cài đặt:
214
+
215
+ ```bash
216
+ npm install nvis-fe-cms-lib@1.0.3
217
+ ```
218
+
219
+ Sử dụng trong `SectionRenderer.jsx`:
220
+
221
+ ```javascript
222
+ import React from 'react';
223
+ import { AboutCompanySection } from 'nvis-fe-cms-libs';
224
+ import { useTheme } from './contexts/ThemeContext';
225
+ import { useTranslation } from 'react-i18next';
226
+
227
+ const SectionRenderer = ({ section }) => {
228
+ const { isDarkMode } = useTheme();
229
+ const { t } = useTranslation();
230
+
231
+ return <AboutCompanySection data={section} t={t} isDarkMode={isDarkMode} />;
232
+ };
233
+ ```
234
+
235
+ ---
236
+
237
+ ## 🎉 Kết Luận
238
+
239
+ Thư viện **nvis-fe-cms-libs** giúp tách rời các section thành module độc lập, dễ tái sử dụng và bảo trì.
240
+ Bạn có thể mở rộng bằng cách thêm component mới và publish phiên bản tiếp theo lên npm.
@@ -513,7 +513,7 @@ const AboutCompanySection = ({ data, t, isDarkMode }) => {
513
513
  console.warn(`Translation function not provided for key: ${key}`);
514
514
  return key;
515
515
  };
516
- const companyData = (_b = (_a = data == null ? void 0 : data.sectionDataBindingItems) == null ? void 0 : _a[0]) == null ? void 0 : _b.data;
516
+ const companyData = ((_b = (_a = data == null ? void 0 : data.sectionDataBindingItems) == null ? void 0 : _a.filter((item) => item == null ? void 0 : item.data)) == null ? void 0 : _b.map((item) => item.data)) || [];
517
517
  if (!companyData) {
518
518
  return null;
519
519
  }
@@ -668,7 +668,7 @@ const AboutCompanySection = ({ data, t, isDarkMode }) => {
668
668
  };
669
669
  const TimelineSection = ({ data, t, isDarkMode }) => {
670
670
  var _a;
671
- const timelineData = ((_a = data == null ? void 0 : data.sectionDataBindingItems) == null ? void 0 : _a.map((item) => item.data)) || [];
671
+ const timelineData = ((_a = data == null ? void 0 : data.sectionDataBindingItems) == null ? void 0 : _a.filter((item) => item == null ? void 0 : item.data).map((item) => item.data)) || [];
672
672
  const [timelineVisible, setTimelineVisible] = useState([]);
673
673
  const timelineRefs = useRef([]);
674
674
  useEffect(() => {
@@ -967,8 +967,8 @@ function useWindowSize$1() {
967
967
  return width;
968
968
  }
969
969
  const PartnersSection = ({ data, t, isDarkMode, imageBaseUrl = "" }) => {
970
- var _a;
971
- const partnersData = ((_a = data == null ? void 0 : data.sectionDataBindingItems) == null ? void 0 : _a.map((item) => item.data)) || [];
970
+ var _a, _b;
971
+ const partnersData = ((_b = (_a = data == null ? void 0 : data.sectionDataBindingItems) == null ? void 0 : _a.filter((item) => item == null ? void 0 : item.data)) == null ? void 0 : _b.map((item) => item.data)) || [];
972
972
  const [currentPartnerSlide, setCurrentPartnerSlide] = useState(0);
973
973
  const [partnersVisible, setPartnersVisible] = useState(false);
974
974
  const partnersRef = useRef(null);
@@ -1087,7 +1087,7 @@ const MilestoneSection = ({ data, t, isDarkMode, getMilestoneTimeline }) => {
1087
1087
  const timelineRefs = useRef([]);
1088
1088
  useEffect(() => {
1089
1089
  const fetchMilestones = () => __async(null, null, function* () {
1090
- var _a;
1090
+ var _a, _b;
1091
1091
  if (getMilestoneTimeline) {
1092
1092
  try {
1093
1093
  setLoading(true);
@@ -1103,7 +1103,7 @@ const MilestoneSection = ({ data, t, isDarkMode, getMilestoneTimeline }) => {
1103
1103
  setLoading(false);
1104
1104
  }
1105
1105
  } else {
1106
- const milestonesData = ((_a = data == null ? void 0 : data.sectionDataBindingItems) == null ? void 0 : _a.map((item) => item.data)) || [];
1106
+ const milestonesData = ((_b = (_a = data == null ? void 0 : data.sectionDataBindingItems) == null ? void 0 : _a.filter((item) => item == null ? void 0 : item.data)) == null ? void 0 : _b.map((item) => item.data)) || [];
1107
1107
  setMilestones(milestonesData);
1108
1108
  setLoading(false);
1109
1109
  }
@@ -1244,12 +1244,15 @@ const MilestoneSection = ({ data, t, isDarkMode, getMilestoneTimeline }) => {
1244
1244
  const TestimonialsSection = ({ data, t, isDarkMode }) => {
1245
1245
  var _a;
1246
1246
  const [currentIndex, setCurrentIndex] = useState(0);
1247
- const testimonials = ((_a = data == null ? void 0 : data.sectionDataBindingItems) == null ? void 0 : _a.map((item) => ({
1248
- quote: item.data.content,
1249
- author: item.data.customerName,
1250
- position: item.data.customerTitle || t("testimonials.fallback.defaultPosition"),
1251
- company: item.data.customerCompany || ""
1252
- }))) || [];
1247
+ const testimonials = ((_a = data == null ? void 0 : data.sectionDataBindingItems) == null ? void 0 : _a.filter((item) => item == null ? void 0 : item.data).map((item) => {
1248
+ var _a2, _b, _c, _d;
1249
+ return {
1250
+ quote: ((_a2 = item.data) == null ? void 0 : _a2.content) || "",
1251
+ author: ((_b = item.data) == null ? void 0 : _b.customerName) || "",
1252
+ position: ((_c = item.data) == null ? void 0 : _c.customerTitle) || t("testimonials.fallback.defaultPosition"),
1253
+ company: ((_d = item.data) == null ? void 0 : _d.customerCompany) || ""
1254
+ };
1255
+ })) || [];
1253
1256
  const fallbackTestimonials = [
1254
1257
  {
1255
1258
  quote: t("testimonials.fallback.testimonial1.quote"),
@@ -2313,7 +2316,8 @@ const ContactFormSection = ({ data, t, isDarkMode, consultationRequestService })
2313
2316
  ] }) }) });
2314
2317
  };
2315
2318
  const ContactInfoSection = ({ data, t, isDarkMode }) => {
2316
- const contactData = (data == null ? void 0 : data.sectionDataBindingItems) || [];
2319
+ var _a;
2320
+ const contactData = ((_a = data == null ? void 0 : data.sectionDataBindingItems) == null ? void 0 : _a.filter((item) => item)) || [];
2317
2321
  if (!contactData.length) return null;
2318
2322
  return /* @__PURE__ */ jsxRuntimeExports.jsx("section", { className: `py-16 ${isDarkMode ? "bg-gray-900" : "bg-slate-50"}`, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
2319
2323
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center mb-12", children: [
@@ -2328,14 +2332,14 @@ const ContactInfoSection = ({ data, t, isDarkMode }) => {
2328
2332
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: `text-lg max-w-3xl mx-auto ${isDarkMode ? "text-gray-300" : "text-slate-600"}`, children: t("contact.team.description") || "Chúng tôi luôn sẵn sàng hỗ trợ bạn với đội ngũ chuyên nghiệp và giàu kinh nghiệm" })
2329
2333
  ] }),
2330
2334
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid md:grid-cols-2 lg:grid-cols-3 gap-8", children: contactData.map((contact, index2) => {
2331
- var _a;
2335
+ var _a2;
2332
2336
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
2333
2337
  "div",
2334
2338
  {
2335
2339
  className: `rounded-2xl p-8 shadow-lg transition-all duration-300 hover:shadow-xl hover:-translate-y-1 border ${isDarkMode ? "bg-gray-800 border-gray-700" : "bg-white border-slate-200"}`,
2336
2340
  children: [
2337
2341
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center mb-6", children: [
2338
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-20 h-20 rounded-full mx-auto mb-4 flex items-center justify-center text-2xl font-bold text-white bg-gradient-to-br from-indigo-500 to-purple-600", children: ((_a = contact.data.name) == null ? void 0 : _a.charAt(0)) || "A" }),
2342
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-20 h-20 rounded-full mx-auto mb-4 flex items-center justify-center text-2xl font-bold text-white bg-gradient-to-br from-indigo-500 to-purple-600", children: ((_a2 = contact.data.name) == null ? void 0 : _a2.charAt(0)) || "A" }),
2339
2343
  /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: `text-xl font-bold mb-2 ${isDarkMode ? "text-white" : "text-slate-800"}`, children: contact.data.name }),
2340
2344
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `inline-block px-3 py-1 rounded-full text-sm font-semibold ${isDarkMode ? "bg-indigo-900/50 text-indigo-300" : "bg-indigo-50 text-indigo-700"}`, children: contact.data.position })
2341
2345
  ] }),