Skip to content

Commit 5323a27

Browse files
committed
feat: Enhance API proxy configuration and image URL resolution across components
1 parent 67ddeb0 commit 5323a27

File tree

13 files changed

+129
-22
lines changed

13 files changed

+129
-22
lines changed

frontend/.env.production

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# ===================================
2+
# SMALLTREND POS - PRODUCTION CONFIGURATION
3+
# ===================================
4+
# Dùng relative path /api để Nginx proxy forward tới backend
5+
6+
# ===================================
7+
# API CONFIGURATION
8+
# ===================================
9+
# URL backend API (không có trailing slash)
10+
# Dùng /api để tương thích với Nginx proxy cấu hình trong production
11+
VITE_API_BASE_URL=/api
12+
13+
# Timeout cho API calls (milliseconds)
14+
VITE_API_TIMEOUT=30000
15+
16+
# ===================================
17+
# AUTHENTICATION
18+
# ===================================
19+
# Key để lưu JWT token trong localStorage
20+
VITE_TOKEN_KEY=smalltrend_token
21+
VITE_REFRESH_TOKEN_KEY=smalltrend_refresh_token
22+
23+
# ===================================
24+
# APP CONFIGURATION
25+
# ===================================
26+
VITE_APP_NAME=SmallTrend POS
27+
VITE_APP_VERSION=1.0.0
28+
VITE_DEFAULT_LANGUAGE=vi
29+
30+
# ===================================
31+
# FEATURE FLAGS
32+
# ===================================
33+
# Sử dụng mock data từ db.json thay vì API thật
34+
VITE_ENABLE_MOCK_DATA=false
35+
36+
# Bật/tắt chế độ debug (hiển thị log chi tiết)
37+
VITE_ENABLE_DEBUG=false
38+
39+
# ===================================
40+
# PAGINATION
41+
# ===================================
42+
# Số items mặc định trên mỗi trang
43+
VITE_DEFAULT_PAGE_SIZE=20
44+
45+
# Số items tối đa có thể lấy trong 1 request
46+
VITE_MAX_PAGE_SIZE=100

frontend/nginx/default.conf

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,37 @@ server {
55
root /usr/share/nginx/html;
66
index index.html;
77

8+
# ─── Frontend Routes (SPA) ───
89
location / {
910
try_files $uri $uri/ /index.html;
1011
}
12+
13+
# ─── API Proxy (Backend) ───
14+
# Forward all /api/* requests to backend service (Docker network or external URL)
15+
location /api/ {
16+
proxy_pass http://backend:8081/api/;
17+
proxy_http_version 1.1;
18+
proxy_set_header Host $host;
19+
proxy_set_header X-Real-IP $remote_addr;
20+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
21+
proxy_set_header X-Forwarded-Proto $scheme;
22+
proxy_set_header Connection "upgrade";
23+
proxy_set_header Upgrade $http_upgrade;
24+
25+
# Handle CORS headers if needed
26+
proxy_set_header Origin $http_origin;
27+
proxy_set_header Access-Control-Allow-Origin $http_origin;
28+
proxy_pass_request_headers on;
29+
}
30+
31+
# ─── File Uploads Proxy ───
32+
# Forward /uploads/* requests to backend service
33+
location /uploads/ {
34+
proxy_pass http://backend:8081/uploads/;
35+
proxy_http_version 1.1;
36+
proxy_set_header Host $host;
37+
proxy_set_header X-Real-IP $remote_addr;
38+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
39+
proxy_set_header X-Forwarded-Proto $scheme;
40+
}
1141
}

frontend/src/pages/CRM/homepage.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
Ticket,
99
} from "lucide-react";
1010
import { useActiveCampaigns, useDiscountedVariants, useAllVariants } from '../../hooks/useEventData';
11+
import { resolveImageUrl } from '../../utils/inventory';
1112
import { useVouchers } from '../../hooks/useVouchers';
1213
import { useGifts } from '../../hooks/useGifts';
1314
import { useProductCombos } from '../../hooks/product_combos';
@@ -174,8 +175,7 @@ function GiftCard({ gift }) {
174175

175176
const getComboImage = (combo) => {
176177
if (!combo?.imageUrl) return COMBO_PLACEHOLDER;
177-
if (combo.imageUrl.startsWith("http")) return combo.imageUrl;
178-
return `http://localhost:8081${combo.imageUrl.startsWith("/") ? "" : "/"}${combo.imageUrl}`;
178+
return resolveImageUrl(combo.imageUrl) || COMBO_PLACEHOLDER;
179179
};
180180

181181
function ComboCard({ combo }) {

frontend/src/pages/Products/ProductManager/ComboDetail.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import EditComboModal from "./EditComboModal";
1010
import { useProductCombos } from "../../../hooks/product_combos";
1111
import { useAuth } from "../../../context/AuthContext";
1212
import { isProductReadOnlyRole } from "../../../utils/rolePermissions";
13+
import { resolveImageUrl } from "../../../utils/inventory";
1314

1415
const ComboDetail = () => {
1516
const navigate = useNavigate();
@@ -138,7 +139,7 @@ const ComboDetail = () => {
138139
<div className="aspect-[4/3] bg-white rounded-lg flex items-center justify-center overflow-hidden">
139140
{combo.imageUrl ? (
140141
<img
141-
src={combo.imageUrl.startsWith("http") ? combo.imageUrl : `http://localhost:8081${combo.imageUrl.startsWith("/") ? "" : "/"}${combo.imageUrl}`}
142+
src={resolveImageUrl(combo.imageUrl) || ''}
142143
alt={combo.comboName}
143144
className="w-full h-full object-contain"
144145
/>

frontend/src/pages/Products/ProductManager/ComboManage.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useNavigate, useLocation } from "react-router-dom";
99
import EditComboModal from "./EditComboModal";
1010
import { useAuth } from "../../../context/AuthContext";
1111
import { canManageProducts } from "../../../utils/roleUtils";
12+
import { resolveImageUrl } from "../../../utils/inventory";
1213

1314
import { useProductCombos } from "../../../hooks/product_combos";
1415

@@ -259,7 +260,7 @@ const ComboManage = () => {
259260
<div className="w-12 h-12 bg-gradient-to-br from-blue-100 to-indigo-100 rounded-xl flex items-center justify-center shadow-sm overflow-hidden">
260261
{combo.imageUrl ? (
261262
<img
262-
src={combo.imageUrl.startsWith('http') ? combo.imageUrl : `http://localhost:8081${combo.imageUrl.startsWith('/') ? '' : '/'}${combo.imageUrl}`}
263+
src={resolveImageUrl(combo.imageUrl) || ''}
263264
alt={combo.comboName}
264265
className="w-full h-full object-cover"
265266
/>

frontend/src/pages/Products/ProductManager/EditComboModal.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Input } from "../../../components/product/input";
55
import { Label } from "../../../components/product/label";
66
import { Textarea } from "../../../components/product/textarea";
77
import axios from "../../../config/axiosConfig";
8+
import { resolveImageUrl } from "../../../utils/inventory";
89

910
// Modal Popup dùng chung để sửa thông tin của một Combo đã tồn tại
1011
// Nhận vào state combo được chọn từ component cha và gọi hàm onSave khi hoàn thành
@@ -735,7 +736,7 @@ const EditComboModal = ({ combo, combos = [], isOpen, onClose, onSave }) => {
735736
{imagePreview ? (
736737
<div className="relative flex-1 rounded-2xl overflow-hidden group border border-gray-100 shadow-sm">
737738
<img
738-
src={imagePreview.startsWith('blob:') || imagePreview.startsWith('http') ? imagePreview : `http://localhost:8081${imagePreview.startsWith('/') ? '' : '/'}${imagePreview}`}
739+
src={resolveImageUrl(imagePreview) || ''}
739740
alt="Combo Visual"
740741
className="w-full h-full object-contain rounded-2xl bg-white"
741742
/>

frontend/src/pages/Products/ProductManager/EditProductModal.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { useFetchCategories } from "../../../hooks/categories";
88
import { useFetchBrands } from "../../../hooks/brands";
99
import { useFetchTaxRates } from "../../../hooks/taxRates";
1010
import api from "../../../config/axiosConfig";
11+
import { resolveImageUrl } from "../../../utils/inventory";
1112

1213
/**
1314
* Component Modal sửa Chỉnh Phông Thông tin Gốc của một Sản Phẩm.
@@ -275,7 +276,7 @@ export function EditProductModal({ product, isOpen, onClose, onSave }) {
275276
<div className="relative flex-1 rounded-2xl overflow-hidden group border border-gray-100 shadow-sm">
276277
{/* Hỗ trợ String xử lý đường dẫn Image Config nếu là Link hệ thống Local từ BaseURL backend hay Link Full Http CDN */}
277278
<img
278-
src={imagePreview.startsWith('blob:') || imagePreview.startsWith('http') ? imagePreview : `http://localhost:8081${imagePreview.startsWith('/') ? '' : '/'}${imagePreview}`}
279+
src={resolveImageUrl(imagePreview) || ''}
279280
alt="Product Master Visual"
280281
className="w-full h-full object-contain rounded-2xl bg-white"
281282
style={{ minHeight: '280px' }}

frontend/src/pages/Products/ProductManager/EditVariantModal.jsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "../../../components/pr
77
import UnitConversionSection from "./UnitConversionSection";
88
import { useFetchUnits } from "../../../hooks/product_variants";
99
import api from "../../../config/axiosConfig";
10+
import { resolveImageUrl, getApiOrigin } from "../../../utils/inventory";
1011

1112
// Modal Component hiển thị thông tin và cho phép Chỉnh sửa một Variant (Loại sản phẩm)
1213
// Cho phép update SKU, Barcode, PLU Code, giá bán, hình ảnh...
@@ -51,13 +52,7 @@ export function EditVariantModal({ variant, parentProduct, isOpen, onClose, onSa
5152
: (variant.is_active ?? true),
5253
});
5354
setImageFile(null);
54-
setImagePreview(
55-
variant.image_url
56-
? variant.image_url.startsWith("http")
57-
? variant.image_url
58-
: `http://localhost:8081${variant.image_url.startsWith("/") ? "" : "/"}${variant.image_url}`
59-
: null,
60-
);
55+
setImagePreview(resolveImageUrl(variant.image_url) || null);
6156
setErrorMsg("");
6257

6358
const attrsObj = variant.attributes || {};
@@ -232,8 +227,8 @@ export function EditVariantModal({ variant, parentProduct, isOpen, onClose, onSa
232227
let imageUrl = null;
233228
if (imageFile) {
234229
imageUrl = await uploadImage();
235-
// Return full path without localhost URL to save on DB
236-
if (imageUrl) imageUrl = imageUrl.replace("http://localhost:8081", "");
230+
// Return full path without server origin to save on DB
231+
if (imageUrl) imageUrl = imageUrl.replace(getApiOrigin(), "");
237232
} else if (imagePreview) {
238233
// Keep the old image
239234
imageUrl = variant.image_url;

frontend/src/pages/Products/ProductManager/ProductDetail.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { useFetchVariants, useFetchUnits } from "../../../hooks/product_variants
1818
import api from "../../../config/axiosConfig";
1919
import { useAuth } from "../../../context/AuthContext";
2020
import { canManageProducts } from "../../../utils/roleUtils";
21+
import { resolveImageUrl } from "../../../utils/inventory";
2122

2223
/**
2324
* Màn hình chi tiết sản phẩm (ProductDetail).
@@ -437,7 +438,7 @@ function ProductDetail() {
437438
<div className="aspect-square bg-white flex items-center justify-center p-4 relative group">
438439
{product.image_url ? (
439440
<img
440-
src={product.image_url.startsWith('http') ? product.image_url : `http://localhost:8081${product.image_url.startsWith('/') ? '' : '/'}${product.image_url}`}
441+
src={resolveImageUrl(product.image_url) || ''}
441442
alt={product.name}
442443
className="w-full h-full object-contain drop-shadow-sm group-hover:scale-105 transition-transform duration-300"
443444
/>
@@ -575,7 +576,7 @@ function ProductDetail() {
575576
<div className="w-9 h-9 mx-auto bg-white border border-gray-200 rounded-lg flex items-center justify-center overflow-hidden">
576577
{variant.image_url ? (
577578
<img
578-
src={variant.image_url.startsWith('http') ? variant.image_url : `http://localhost:8081${variant.image_url.startsWith('/') ? '' : '/'}${variant.image_url}`}
579+
src={resolveImageUrl(variant.image_url) || ''}
579580
alt={variant.name}
580581
className="w-full h-full object-cover"
581582
/>

frontend/src/pages/Products/ProductManager/ProductList.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export function ProductListScreen() {
3232

3333
// Chuẩn hoá domain backend để khi ảnh chỉ là path tương đối
3434
// thì vẫn render đúng trên giao diện.
35-
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8081/api';
35+
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL || '/api';
3636

3737
// Hàm dùng chung: đổi imageUrl thành URL đầy đủ để hiển thị ảnh sản phẩm.
3838
const apiOrigin = apiBaseUrl.replace(/\/api\/?$/, '');

0 commit comments

Comments
 (0)