fix(admin): attach jwt auth headers to all protected admin api endpoints

This commit is contained in:
Your Name committed 2026-10-19 20:08:11 +07:00
1 parent 911614f862
commit af703fe2ac
29 files changed
+125 -50

No files matched your search

@@ -15,6 +15,7 @@ import {
Package Package
} from 'lucide-react'; } from 'lucide-react';
import { API_BASE, type Category } from '@homaroma/types'; import { API_BASE, type Category } from '@homaroma/types';
import { getAuthHeaders } from '../utils/auth';
interface CategoryBulkJsonModalProps { interface CategoryBulkJsonModalProps {
isOpen: boolean; isOpen: boolean;
@@ -256,7 +257,7 @@ ${JSON.stringify(
try { try {
const res = await fetch(`${API_BASE}/categories/bulk-import`, { const res = await fetch(`${API_BASE}/categories/bulk-import`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify(parsedImportData), body: JSON.stringify(parsedImportData),
}); });
@@ -16,6 +16,7 @@ import {
Filter Filter
} from 'lucide-react'; } from 'lucide-react';
import { API_BASE, type AttributeDefinition, type Category } from '@homaroma/types'; import { API_BASE, type AttributeDefinition, type Category } from '@homaroma/types';
import { getAuthHeaders } from '../utils/auth';
interface ProductAttributeBulkJsonModalProps { interface ProductAttributeBulkJsonModalProps {
isOpen: boolean; isOpen: boolean;
@@ -370,7 +371,7 @@ Please output ONLY the valid JSON Array without surrounding markdown or explanat
try { try {
const res = await fetch(`${API_BASE}/product-attributes/definitions/bulk-import`, { const res = await fetch(`${API_BASE}/product-attributes/definitions/bulk-import`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify(parsedImportData), body: JSON.stringify(parsedImportData),
}); });
@@ -12,6 +12,7 @@ import {
Sparkles, Sparkles,
} from 'lucide-react'; } from 'lucide-react';
import { API_BASE, type RawMaterial } from '@homaroma/types'; import { API_BASE, type RawMaterial } from '@homaroma/types';
import { getAuthHeaders } from '../utils/auth';
interface RawMaterialBulkJsonModalProps { interface RawMaterialBulkJsonModalProps {
isOpen: boolean; isOpen: boolean;
@@ -143,7 +144,7 @@ export default function RawMaterialBulkJsonModal({
try { try {
const res = await fetch(`${API_BASE}/raw-materials/bulk`, { const res = await fetch(`${API_BASE}/raw-materials/bulk`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify(parsedPreview.items), body: JSON.stringify(parsedPreview.items),
}); });
+2 -1
View File
@@ -2,6 +2,7 @@ import { useState, useEffect, useMemo, useRef } from 'react'
import { useParams, useNavigate, Link } from 'react-router-dom' import { useParams, useNavigate, Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { API_BASE } from '@homaroma/types' import { API_BASE } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
import { ArrowLeft, Save, Loader2, Image as ImageIcon } from 'lucide-react' import { ArrowLeft, Save, Loader2, Image as ImageIcon } from 'lucide-react'
import ReactQuill from 'react-quill-new' import ReactQuill from 'react-quill-new'
import 'react-quill-new/dist/quill.snow.css' import 'react-quill-new/dist/quill.snow.css'
@@ -59,7 +60,7 @@ export default function ArticleEditPage() {
const payload = { title, slug, coverImage, content, isPublished } const payload = { title, slug, coverImage, content, isPublished }
const res = await fetch(`${API_BASE}/articles${isNew ? '' : `/${id}`}`, { const res = await fetch(`${API_BASE}/articles${isNew ? '' : `/${id}`}`, {
method: isNew ? 'POST' : 'PUT', method: isNew ? 'POST' : 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify(payload) body: JSON.stringify(payload)
}) })
if (!res.ok) throw new Error(await res.text()) if (!res.ok) throw new Error(await res.text())
+5 -1
View File
@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { API_BASE, type Article } from '@homaroma/types' import { API_BASE, type Article } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
import { Plus, Edit, Trash2, Eye, EyeOff } from 'lucide-react' import { Plus, Edit, Trash2, Eye, EyeOff } from 'lucide-react'
export default function ArticlesPage() { export default function ArticlesPage() {
@@ -26,7 +27,10 @@ export default function ArticlesPage() {
const handleDelete = async (id: number) => { const handleDelete = async (id: number) => {
if (!confirm('Are you sure you want to delete this article?')) return if (!confirm('Are you sure you want to delete this article?')) return
try { try {
const res = await fetch(`${API_BASE}/articles/${id}`, { method: 'DELETE' }) const res = await fetch(`${API_BASE}/articles/${id}`, {
method: 'DELETE',
headers: getAuthHeaders(),
})
if (res.ok) { if (res.ok) {
setArticles(prev => prev.filter(a => a.id !== id)) setArticles(prev => prev.filter(a => a.id !== id))
} }
+6 -4
View File
@@ -2,6 +2,7 @@ import { useState, useEffect, useMemo } from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { API_BASE, type Brand } from '@homaroma/types' import { API_BASE, type Brand } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
import { import {
Award, Award,
Plus, Plus,
@@ -105,7 +106,7 @@ export default function BrandsPage() {
try { try {
const res = await fetch(`${API_BASE}/products/brands`, { const res = await fetch(`${API_BASE}/products/brands`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify({ name: newBrandName.trim() }) body: JSON.stringify({ name: newBrandName.trim() })
}) })
const data = await res.json() const data = await res.json()
@@ -133,7 +134,7 @@ export default function BrandsPage() {
try { try {
const res = await fetch(`${API_BASE}/products/brands/${editingBrand.id}`, { const res = await fetch(`${API_BASE}/products/brands/${editingBrand.id}`, {
method: 'PUT', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify({ name: editBrandName.trim() }) body: JSON.stringify({ name: editBrandName.trim() })
}) })
const data = await res.json() const data = await res.json()
@@ -163,7 +164,8 @@ export default function BrandsPage() {
try { try {
const res = await fetch(`${API_BASE}/products/brands/${brand.id}`, { const res = await fetch(`${API_BASE}/products/brands/${brand.id}`, {
method: 'DELETE' method: 'DELETE',
headers: getAuthHeaders(),
}) })
const data = await res.json() const data = await res.json()
if (res.ok && data.success) { if (res.ok && data.success) {
@@ -200,7 +202,7 @@ export default function BrandsPage() {
try { try {
const res = await fetch(`${API_BASE}/products/brands/${translatingBrand.id}/translations`, { const res = await fetch(`${API_BASE}/products/brands/${translatingBrand.id}/translations`, {
method: 'PATCH', method: 'PATCH',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify({ name: translationsMap }) body: JSON.stringify({ name: translationsMap })
}) })
const data = await res.json() const data = await res.json()
+3 -1
View File
@@ -2,6 +2,7 @@ import { useState, useEffect, useMemo } from 'react'
import { useNavigate, Link } from 'react-router-dom' import { useNavigate, Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { API_BASE, getCategoryImage, resolveCategoryMediaUrl, type Category } from '@homaroma/types' import { API_BASE, getCategoryImage, resolveCategoryMediaUrl, type Category } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
import { import {
Edit3, Edit3,
Package, Package,
@@ -75,7 +76,8 @@ export default function CategoriesPage() {
if (!confirm(t('categories.confirmDelete', `Are you sure you want to delete category "${catName}"?`))) return if (!confirm(t('categories.confirmDelete', `Are you sure you want to delete category "${catName}"?`))) return
try { try {
const res = await fetch(`${API_BASE}/categories/${id}`, { const res = await fetch(`${API_BASE}/categories/${id}`, {
method: 'DELETE' method: 'DELETE',
headers: getAuthHeaders(),
}) })
if (res.ok) { if (res.ok) {
fetchData() fetchData()
+5 -3
View File
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'
import { useParams, useNavigate, Link } from 'react-router-dom' import { useParams, useNavigate, Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { API_BASE, type Category, type CategoryMedia, type CategoryMediaRole, type CategoryFeaturedSeries, type CategoryUspBenefit, type CategoryFilterConfig } from '@homaroma/types' import { API_BASE, type Category, type CategoryMedia, type CategoryMediaRole, type CategoryFeaturedSeries, type CategoryUspBenefit, type CategoryFilterConfig } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
import { import {
ArrowLeft, ArrowLeft,
Save, Save,
@@ -335,7 +336,7 @@ export default function CategoryEditPage() {
// Step 1: Save base category // Step 1: Save base category
const res = await fetch(url, { const res = await fetch(url, {
method, method,
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify({ body: JSON.stringify({
name: masterName, name: masterName,
imageUrl: thumbnail.url, imageUrl: thumbnail.url,
@@ -367,7 +368,7 @@ export default function CategoryEditPage() {
if (targetId) { if (targetId) {
await fetch(`${API_BASE}/categories/${targetId}/translations`, { await fetch(`${API_BASE}/categories/${targetId}/translations`, {
method: 'PATCH', method: 'PATCH',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify({ name }) body: JSON.stringify({ name })
}) })
} }
@@ -396,7 +397,8 @@ export default function CategoryEditPage() {
try { try {
const res = await fetch(`${API_BASE}/categories/${id}`, { const res = await fetch(`${API_BASE}/categories/${id}`, {
method: 'DELETE' method: 'DELETE',
headers: getAuthHeaders(),
}) })
if (res.ok) { if (res.ok) {
navigate('/categories') navigate('/categories')
+4 -2
View File
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { API_BASE } from '@homaroma/types' import { API_BASE } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
import { Edit3, Ticket, Trash2, Plus, X, Power, PowerOff } from 'lucide-react' import { Edit3, Ticket, Trash2, Plus, X, Power, PowerOff } from 'lucide-react'
interface Coupon { interface Coupon {
@@ -50,7 +51,7 @@ export default function CouponsPage() {
const res = await fetch(url, { const res = await fetch(url, {
method, method,
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify({ body: JSON.stringify({
code: editingCoupon.code, code: editingCoupon.code,
type: editingCoupon.type, type: editingCoupon.type,
@@ -80,7 +81,8 @@ export default function CouponsPage() {
if (!confirm('Are you sure you want to delete this coupon?')) return if (!confirm('Are you sure you want to delete this coupon?')) return
try { try {
const res = await fetch(`${API_BASE}/coupons/${id}`, { const res = await fetch(`${API_BASE}/coupons/${id}`, {
method: 'DELETE' method: 'DELETE',
headers: getAuthHeaders(),
}) })
if (res.ok) { if (res.ok) {
fetchData() fetchData()
+4 -1
View File
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { API_BASE } from '@homaroma/types' import { API_BASE } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
interface Customer { interface Customer {
id: number id: number
@@ -25,7 +26,9 @@ export default function CustomersPage() {
const fetchCustomers = async () => { const fetchCustomers = async () => {
try { try {
const res = await fetch(`${API_BASE}/customers`) const res = await fetch(`${API_BASE}/customers`, {
headers: getAuthHeaders(),
})
const data = await res.json() const data = await res.json()
setCustomers(data) setCustomers(data)
} catch (err) { } catch (err) {
+5 -4
View File
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Package, Tag, Layers, TrendingUp } from 'lucide-react' import { Package, Tag, Layers, TrendingUp } from 'lucide-react'
import { API_BASE, type Category, type Product } from '@homaroma/types' import { API_BASE, type Category, type Product } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
import { useStoreSettings } from '../contexts/StoreSettingsContext' import { useStoreSettings } from '../contexts/StoreSettingsContext'
export default function DashboardPage() { export default function DashboardPage() {
@@ -22,10 +23,10 @@ export default function DashboardPage() {
const fetchData = async () => { const fetchData = async () => {
try { try {
const [catRes, prodRes, orderRes, customerRes] = await Promise.all([ const [catRes, prodRes, orderRes, customerRes] = await Promise.all([
fetch(`${API_BASE}/categories`), fetch(`${API_BASE}/categories`, { headers: getAuthHeaders() }),
fetch(`${API_BASE}/products`), fetch(`${API_BASE}/products`, { headers: getAuthHeaders() }),
fetch(`${API_BASE}/orders`), fetch(`${API_BASE}/orders`, { headers: getAuthHeaders() }),
fetch(`${API_BASE}/customers`) fetch(`${API_BASE}/customers`, { headers: getAuthHeaders() })
]) ])
const cats: Category[] = await catRes.json() const cats: Category[] = await catRes.json()
const prods: Product[] = await prodRes.json() const prods: Product[] = await prodRes.json()
+5 -1
View File
@@ -1,6 +1,7 @@
import React, { useState, useEffect, useCallback, useRef } from 'react'; import React, { useState, useEffect, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { API_BASE } from '@homaroma/types'; import { API_BASE } from '@homaroma/types';
import { getAuthHeaders } from '../utils/auth';
import { Upload, Loader2, Image as ImageIcon, Trash2, ClipboardPaste } from 'lucide-react'; import { Upload, Loader2, Image as ImageIcon, Trash2, ClipboardPaste } from 'lucide-react';
interface MediaItem { interface MediaItem {
@@ -21,7 +22,9 @@ export default function MediaLibraryPage() {
const fetchMedia = async () => { const fetchMedia = async () => {
try { try {
setLoading(true); setLoading(true);
const res = await fetch(`${API_BASE}/media`); const res = await fetch(`${API_BASE}/media`, {
headers: getAuthHeaders(),
});
if (res.ok) { if (res.ok) {
const data = await res.json(); const data = await res.json();
setMedia(data); setMedia(data);
@@ -113,6 +116,7 @@ export default function MediaLibraryPage() {
setDeletingId(id); setDeletingId(id);
const res = await fetch(`${API_BASE}/media/${id}`, { const res = await fetch(`${API_BASE}/media/${id}`, {
method: 'DELETE', method: 'DELETE',
headers: getAuthHeaders(),
}); });
if (res.ok) { if (res.ok) {
await fetchMedia(); await fetchMedia();
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { API_BASE } from '@homaroma/types' import { API_BASE } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
import { Download, Loader2, Mail } from 'lucide-react' import { Download, Loader2, Mail } from 'lucide-react'
interface Subscriber { interface Subscriber {
@@ -14,7 +15,9 @@ export default function NewsletterSubscribersPage() {
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
useEffect(() => { useEffect(() => {
fetch(`${API_BASE}/newsletter/subscribers`) fetch(`${API_BASE}/newsletter/subscribers`, {
headers: getAuthHeaders(),
})
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
if (Array.isArray(data)) { if (Array.isArray(data)) {
+5 -2
View File
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'
import { useParams, Link } from 'react-router-dom' import { useParams, Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { API_BASE, formatPrice, type Order } from '@homaroma/types' import { API_BASE, formatPrice, type Order } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
import { ArrowLeft, CheckCircle2, ExternalLink, Loader2, Truck, XCircle, FileImage } from 'lucide-react' import { ArrowLeft, CheckCircle2, ExternalLink, Loader2, Truck, XCircle, FileImage } from 'lucide-react'
export default function OrderDetailPage() { export default function OrderDetailPage() {
@@ -13,7 +14,9 @@ export default function OrderDetailPage() {
const fetchOrder = async () => { const fetchOrder = async () => {
try { try {
const res = await fetch(`${API_BASE}/orders/${id}`) const res = await fetch(`${API_BASE}/orders/${id}`, {
headers: getAuthHeaders(),
})
const data = await res.json() const data = await res.json()
setOrder(data) setOrder(data)
} catch (err) { } catch (err) {
@@ -32,7 +35,7 @@ export default function OrderDetailPage() {
try { try {
await fetch(`${API_BASE}/orders/${id}/status`, { await fetch(`${API_BASE}/orders/${id}/status`, {
method: 'PATCH', method: 'PATCH',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify({ status }) body: JSON.stringify({ status })
}) })
await fetchOrder() await fetchOrder()
+4 -1
View File
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { API_BASE, formatPrice, type Order } from '@homaroma/types' import { API_BASE, formatPrice, type Order } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
import { Search, ChevronRight, Clock, CheckCircle2, Truck, XCircle } from 'lucide-react' import { Search, ChevronRight, Clock, CheckCircle2, Truck, XCircle } from 'lucide-react'
export default function OrdersPage() { export default function OrdersPage() {
@@ -14,7 +15,9 @@ export default function OrdersPage() {
const fetchData = async () => { const fetchData = async () => {
setLoading(true) setLoading(true)
try { try {
const res = await fetch(`${API_BASE}/orders`) const res = await fetch(`${API_BASE}/orders`, {
headers: getAuthHeaders(),
})
const data = await res.json() const data = await res.json()
setOrders(Array.isArray(data) ? data : []) setOrders(Array.isArray(data) ? data : [])
} catch (err) { } catch (err) {
+2 -1
View File
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { API_BASE, type StoreSettings } from '@homaroma/types' import { API_BASE, type StoreSettings } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
import { Save, Loader2, Landmark, Truck } from 'lucide-react' import { Save, Loader2, Landmark, Truck } from 'lucide-react'
export default function PaymentSettingsPage() { export default function PaymentSettingsPage() {
@@ -32,7 +33,7 @@ export default function PaymentSettingsPage() {
try { try {
const res = await fetch(`${API_BASE}/settings`, { const res = await fetch(`${API_BASE}/settings`, {
method: 'PUT', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify({ body: JSON.stringify({
shippingFee: parseFloat(shippingFee) || 0, shippingFee: parseFloat(shippingFee) || 0,
bankName, bankAccountName, bankAccountNumber, bankName, bankAccountName, bankAccountNumber,
@@ -2,6 +2,7 @@ import { useState, useEffect, useMemo } from 'react'
import { useParams, useNavigate, Link } from 'react-router-dom' import { useParams, useNavigate, Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { API_BASE, type AttributeDefinition, type Category } from '@homaroma/types' import { API_BASE, type AttributeDefinition, type Category } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
import { import {
ArrowLeft, ArrowLeft,
Save, Save,
@@ -488,7 +489,7 @@ export default function ProductAttributeEditPage() {
const res = await fetch(url, { const res = await fetch(url, {
method, method,
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify(payload), body: JSON.stringify(payload),
}) })
@@ -517,7 +518,10 @@ export default function ProductAttributeEditPage() {
try { try {
setSaving(true) setSaving(true)
const res = await fetch(`${API_BASE}/product-attributes/definitions/${id}`, { method: 'DELETE' }) const res = await fetch(`${API_BASE}/product-attributes/definitions/${id}`, {
method: 'DELETE',
headers: getAuthHeaders(),
})
if (!res.ok) { if (!res.ok) {
const errData = await res.json().catch(() => null) const errData = await res.json().catch(() => null)
throw new Error(errData?.message || 'Failed to delete attribute definition') throw new Error(errData?.message || 'Failed to delete attribute definition')
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react'
import { useNavigate, Link } from 'react-router-dom' import { useNavigate, Link } from 'react-router-dom'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { API_BASE, type AttributeDefinition, type Category } from '@homaroma/types' import { API_BASE, type AttributeDefinition, type Category } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
import { import {
Edit3, Edit3,
FileJson, FileJson,
@@ -53,7 +54,10 @@ export default function ProductAttributesPage() {
const handleDelete = async (id: number, label: string) => { const handleDelete = async (id: number, label: string) => {
if (!confirm(t('common.confirmDelete', `Are you sure you want to delete attribute "${label}"?`))) return if (!confirm(t('common.confirmDelete', `Are you sure you want to delete attribute "${label}"?`))) return
try { try {
const res = await fetch(`${API_BASE}/product-attributes/definitions/${id}`, { method: 'DELETE' }) const res = await fetch(`${API_BASE}/product-attributes/definitions/${id}`, {
method: 'DELETE',
headers: getAuthHeaders(),
})
if (!res.ok) { if (!res.ok) {
const errData = await res.json().catch(() => null) const errData = await res.json().catch(() => null)
throw new Error(errData?.message || 'Failed to delete attribute definition') throw new Error(errData?.message || 'Failed to delete attribute definition')
+5 -2
View File
@@ -16,6 +16,7 @@ import {
FileJson FileJson
} from 'lucide-react' } from 'lucide-react'
import { API_BASE, type Product } from '@homaroma/types' import { API_BASE, type Product } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
import ProductBomPromptModal from '../components/ProductBomPromptModal' import ProductBomPromptModal from '../components/ProductBomPromptModal'
import ProductBomImportModal from '../components/ProductBomImportModal' import ProductBomImportModal from '../components/ProductBomImportModal'
@@ -54,7 +55,9 @@ export default function ProductBomPage() {
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
try { try {
const rmRes = await fetch(`${API_BASE}/raw-materials`) const rmRes = await fetch(`${API_BASE}/raw-materials`, {
headers: getAuthHeaders(),
})
if (rmRes.ok) { if (rmRes.ok) {
const rmData = await rmRes.json() const rmData = await rmRes.json()
setRawMaterials(rmData) setRawMaterials(rmData)
@@ -126,7 +129,7 @@ export default function ProductBomPage() {
const response = await fetch(`${API_BASE}/products/${id}`, { const response = await fetch(`${API_BASE}/products/${id}`, {
method: 'PUT', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify(payload), body: JSON.stringify(payload),
}) })
+3 -2
View File
@@ -24,6 +24,7 @@ import {
Upload Upload
} from 'lucide-react' } from 'lucide-react'
import { API_BASE, type AttributeDefinition, type Brand, type Category, type Product, type ProductAttribute, type ProductImage, type Variant } from '@homaroma/types' import { API_BASE, type AttributeDefinition, type Brand, type Category, type Product, type ProductAttribute, type ProductImage, type Variant } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
import MDEditor from '@uiw/react-md-editor' import MDEditor from '@uiw/react-md-editor'
import MediaLibraryModal from '../components/MediaLibraryModal' import MediaLibraryModal from '../components/MediaLibraryModal'
import ContentLanguageTabs from '../components/ContentLanguageTabs' import ContentLanguageTabs from '../components/ContentLanguageTabs'
@@ -408,7 +409,7 @@ export default function ProductEditPage() {
const response = await fetch(url, { const response = await fetch(url, {
method, method,
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify({ body: JSON.stringify({
categoryId, categoryId,
brandId: brandId ? Number(brandId) : undefined, brandId: brandId ? Number(brandId) : undefined,
@@ -444,7 +445,7 @@ export default function ProductEditPage() {
const attributeResponse = await fetch(`${API_BASE}/products/${savedProductId}/attributes`, { const attributeResponse = await fetch(`${API_BASE}/products/${savedProductId}/attributes`, {
method: 'PUT', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify({ body: JSON.stringify({
attributes: attributes.map(buildAttributePayload), attributes: attributes.map(buildAttributePayload),
}), }),
+9 -5
View File
@@ -22,6 +22,7 @@ import {
import { useNavigate, useSearchParams } from 'react-router-dom' import { useNavigate, useSearchParams } from 'react-router-dom'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { API_BASE, getCategoryImage, formatPrice, type Category, type Product } from '@homaroma/types' import { API_BASE, getCategoryImage, formatPrice, type Category, type Product } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
import ProductJsonGuideModal from '../components/ProductJsonGuideModal' import ProductJsonGuideModal from '../components/ProductJsonGuideModal'
import ProductOptionUxModal from '../components/products/ProductOptionUxModal' import ProductOptionUxModal from '../components/products/ProductOptionUxModal'
@@ -175,7 +176,7 @@ export default function ProductsPage() {
try { try {
const res = await fetch(`${API_BASE}/products/bulk`, { const res = await fetch(`${API_BASE}/products/bulk`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify(payloadArray) body: JSON.stringify(payloadArray)
}) })
@@ -237,7 +238,7 @@ export default function ProductsPage() {
const updatedImages = [...(selectedProduct.images || []), ...newImages] const updatedImages = [...(selectedProduct.images || []), ...newImages]
const updateRes = await fetch(`${API_BASE}/products/${selectedProduct.id}`, { const updateRes = await fetch(`${API_BASE}/products/${selectedProduct.id}`, {
method: 'PUT', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify({ body: JSON.stringify({
categoryId: selectedProduct.categoryId, categoryId: selectedProduct.categoryId,
options: selectedProduct.options, options: selectedProduct.options,
@@ -302,7 +303,7 @@ export default function ProductsPage() {
const updatedImages = (selectedProduct.images || []).filter(img => img.url !== imgUrl) const updatedImages = (selectedProduct.images || []).filter(img => img.url !== imgUrl)
const updateRes = await fetch(`${API_BASE}/products/${selectedProduct.id}`, { const updateRes = await fetch(`${API_BASE}/products/${selectedProduct.id}`, {
method: 'PUT', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify({ body: JSON.stringify({
categoryId: selectedProduct.categoryId, categoryId: selectedProduct.categoryId,
options: selectedProduct.options, options: selectedProduct.options,
@@ -367,7 +368,10 @@ export default function ProductsPage() {
const handleDelete = async (id: number) => { const handleDelete = async (id: number) => {
if (!confirm(t('common.confirmDelete', 'Are you sure you want to delete this product?'))) return if (!confirm(t('common.confirmDelete', 'Are you sure you want to delete this product?'))) return
try { try {
const res = await fetch(`${API_BASE}/products/${id}`, { method: 'DELETE' }) const res = await fetch(`${API_BASE}/products/${id}`, {
method: 'DELETE',
headers: getAuthHeaders(),
})
if (res.ok) { if (res.ok) {
setSelectedIds(prev => prev.filter(item => item !== id)) setSelectedIds(prev => prev.filter(item => item !== id))
fetchData() fetchData()
@@ -389,7 +393,7 @@ export default function ProductsPage() {
try { try {
const res = await fetch(`${API_BASE}/products/bulk-delete`, { const res = await fetch(`${API_BASE}/products/bulk-delete`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify({ ids: selectedIds }) body: JSON.stringify({ ids: selectedIds })
}) })
+6 -2
View File
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { API_BASE } from '@homaroma/types' import { API_BASE } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
import { Plus, Edit2, Trash2, X } from 'lucide-react' import { Plus, Edit2, Trash2, X } from 'lucide-react'
interface Promotion { interface Promotion {
@@ -94,7 +95,7 @@ export default function PromotionsPage() {
const method = editingId ? 'PUT' : 'POST' const method = editingId ? 'PUT' : 'POST'
await fetch(url, { await fetch(url, {
method, method,
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify(payload) body: JSON.stringify(payload)
}) })
setIsModalOpen(false) setIsModalOpen(false)
@@ -107,7 +108,10 @@ export default function PromotionsPage() {
const handleDelete = async (id: number) => { const handleDelete = async (id: number) => {
if (!confirm('Are you sure you want to delete this promotion?')) return if (!confirm('Are you sure you want to delete this promotion?')) return
try { try {
await fetch(`${API_BASE}/promotions/${id}`, { method: 'DELETE' }) await fetch(`${API_BASE}/promotions/${id}`, {
method: 'DELETE',
headers: getAuthHeaders(),
})
fetchPromotions() fetchPromotions()
} catch (err) { } catch (err) {
console.error(err) console.error(err)
@@ -14,6 +14,7 @@ import {
AlertCircle AlertCircle
} from 'lucide-react'; } from 'lucide-react';
import { API_BASE } from '@homaroma/types'; import { API_BASE } from '@homaroma/types';
import { getAuthHeaders } from '../utils/auth';
const COMMON_UNITS = [ const COMMON_UNITS = [
{ label: 'ชิ้น (piece)', value: 'piece' }, { label: 'ชิ้น (piece)', value: 'piece' },
@@ -46,7 +47,9 @@ export default function RawMaterialDetailPage() {
if (isNew) return; if (isNew) return;
setLoading(true); setLoading(true);
fetch(`${API_BASE}/raw-materials/${id}`) fetch(`${API_BASE}/raw-materials/${id}`, {
headers: getAuthHeaders(),
})
.then(async (res) => { .then(async (res) => {
if (!res.ok) throw new Error('ไม่พบข้อมูลวัตถุดิบ'); if (!res.ok) throw new Error('ไม่พบข้อมูลวัตถุดิบ');
return await res.json(); return await res.json();
@@ -97,7 +100,7 @@ export default function RawMaterialDetailPage() {
const res = await fetch(url, { const res = await fetch(url, {
method, method,
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify(payload), body: JSON.stringify(payload),
}); });
@@ -127,6 +130,7 @@ export default function RawMaterialDetailPage() {
try { try {
const res = await fetch(`${API_BASE}/raw-materials/${id}`, { const res = await fetch(`${API_BASE}/raw-materials/${id}`, {
method: 'DELETE', method: 'DELETE',
headers: getAuthHeaders(),
}); });
if (!res.ok) { if (!res.ok) {
const data = await res.json(); const data = await res.json();
+9 -3
View File
@@ -16,6 +16,7 @@ import {
Pencil, Pencil,
} from 'lucide-react'; } from 'lucide-react';
import { API_BASE, type RawMaterial } from '@homaroma/types'; import { API_BASE, type RawMaterial } from '@homaroma/types';
import { getAuthHeaders } from '../utils/auth';
import RawMaterialBulkJsonModal from '../components/RawMaterialBulkJsonModal'; import RawMaterialBulkJsonModal from '../components/RawMaterialBulkJsonModal';
export default function RawMaterialsPage() { export default function RawMaterialsPage() {
@@ -51,7 +52,7 @@ export default function RawMaterialsPage() {
try { try {
const res = await fetch(`${API_BASE}/raw-materials/${item.id}`, { const res = await fetch(`${API_BASE}/raw-materials/${item.id}`, {
method: 'PUT', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify({ body: JSON.stringify({
pricePerUnit: val, pricePerUnit: val,
}), }),
@@ -78,7 +79,9 @@ export default function RawMaterialsPage() {
const fetchMaterials = async () => { const fetchMaterials = async () => {
setLoading(true); setLoading(true);
try { try {
const res = await fetch(`${API_BASE}/raw-materials`); const res = await fetch(`${API_BASE}/raw-materials`, {
headers: getAuthHeaders(),
});
if (res.ok) { if (res.ok) {
setMaterials(await res.json()); setMaterials(await res.json());
} }
@@ -97,7 +100,10 @@ export default function RawMaterialsPage() {
if (!confirm(`คุณแน่ใจหรือไม่ว่าต้องการลบวัตถุดิบ "${name}"?`)) return; if (!confirm(`คุณแน่ใจหรือไม่ว่าต้องการลบวัตถุดิบ "${name}"?`)) return;
try { try {
const res = await fetch(`${API_BASE}/raw-materials/${id}`, { method: 'DELETE' }); const res = await fetch(`${API_BASE}/raw-materials/${id}`, {
method: 'DELETE',
headers: getAuthHeaders(),
});
if (res.ok) { if (res.ok) {
fetchMaterials(); fetchMaterials();
} else { } else {
+2 -1
View File
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { API_BASE, type StoreSettings, type StoreSettingsTranslation } from '@homaroma/types'; import { API_BASE, type StoreSettings, type StoreSettingsTranslation } from '@homaroma/types';
import { getAuthHeaders } from '../utils/auth';
import { import {
Save, Save,
Loader2, Loader2,
@@ -257,7 +258,7 @@ export default function StoreSettingsPage() {
const res = await fetch(`${API_BASE}/settings`, { const res = await fetch(`${API_BASE}/settings`, {
method: 'PUT', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify(payload), body: JSON.stringify(payload),
}); });
+2 -1
View File
@@ -1,5 +1,6 @@
import { useState, useEffect, useRef } from 'react' import { useState, useEffect, useRef } from 'react'
import { API_BASE, type StoreSettings } from '@homaroma/types' import { API_BASE, type StoreSettings } from '@homaroma/types'
import { getAuthHeaders } from '../utils/auth'
import ThemeLivePreview from '../components/ThemeLivePreview' import ThemeLivePreview from '../components/ThemeLivePreview'
import { import {
Save, Save,
@@ -492,7 +493,7 @@ export default function ThemeSettingsPage() {
try { try {
const res = await fetch(`${API_BASE}/settings`, { const res = await fetch(`${API_BASE}/settings`, {
method: 'PUT', method: 'PUT',
headers: { 'Content-Type': 'application/json' }, headers: getAuthHeaders(),
body: JSON.stringify({ body: JSON.stringify({
themeTokens: { themeTokens: {
...themeTokens, ...themeTokens,
@@ -13,6 +13,7 @@ import {
SlidersHorizontal, SlidersHorizontal,
} from 'lucide-react'; } from 'lucide-react';
import { API_BASE } from '@homaroma/types'; import { API_BASE } from '@homaroma/types';
import { getAuthHeaders } from '../../utils/auth';
import ProfitabilityHeaderTabs from './ProfitabilityHeaderTabs'; import ProfitabilityHeaderTabs from './ProfitabilityHeaderTabs';
function fmtMoney(val: number | null | undefined): string { function fmtMoney(val: number | null | undefined): string {
@@ -110,7 +111,9 @@ export default function CostProfitabilityMatrixPage() {
sortBy, sortBy,
}); });
const res = await fetch(`${API_BASE}/profitability/matrix?${params.toString()}`); const res = await fetch(`${API_BASE}/profitability/matrix?${params.toString()}`, {
headers: getAuthHeaders(),
});
if (!res.ok) throw new Error(`HTTP error ${res.status}`); if (!res.ok) throw new Error(`HTTP error ${res.status}`);
const json = await res.json(); const json = await res.json();
setData(json); setData(json);
@@ -10,6 +10,7 @@ import {
TrendingUp, TrendingUp,
} from 'lucide-react'; } from 'lucide-react';
import { API_BASE } from '@homaroma/types'; import { API_BASE } from '@homaroma/types';
import { getAuthHeaders } from '../../utils/auth';
import ProfitabilityHeaderTabs from './ProfitabilityHeaderTabs'; import ProfitabilityHeaderTabs from './ProfitabilityHeaderTabs';
import DateRangeSelector, { type DateFilterState } from './DateRangeSelector'; import DateRangeSelector, { type DateFilterState } from './DateRangeSelector';
@@ -92,7 +93,9 @@ export default function MrpPlanningPage() {
...(dateFilter.endDate ? { endDate: dateFilter.endDate } : {}), ...(dateFilter.endDate ? { endDate: dateFilter.endDate } : {}),
}); });
const res = await fetch(`${API_BASE}/profitability/mrp?${params.toString()}`); const res = await fetch(`${API_BASE}/profitability/mrp?${params.toString()}`, {
headers: getAuthHeaders(),
});
if (!res.ok) throw new Error(`HTTP error ${res.status}`); if (!res.ok) throw new Error(`HTTP error ${res.status}`);
const json = await res.json(); const json = await res.json();
setData(json); setData(json);
@@ -12,6 +12,7 @@ import {
ShieldCheck, ShieldCheck,
} from 'lucide-react'; } from 'lucide-react';
import { API_BASE } from '@homaroma/types'; import { API_BASE } from '@homaroma/types';
import { getAuthHeaders } from '../../utils/auth';
import ProfitabilityHeaderTabs from './ProfitabilityHeaderTabs'; import ProfitabilityHeaderTabs from './ProfitabilityHeaderTabs';
import DateRangeSelector, { type DateFilterState } from './DateRangeSelector'; import DateRangeSelector, { type DateFilterState } from './DateRangeSelector';
@@ -121,7 +122,9 @@ export default function OmnichannelMarginPage() {
...(dateFilter.endDate ? { endDate: dateFilter.endDate } : {}), ...(dateFilter.endDate ? { endDate: dateFilter.endDate } : {}),
}); });
const res = await fetch(`${API_BASE}/profitability/omnichannel?${params.toString()}`); const res = await fetch(`${API_BASE}/profitability/omnichannel?${params.toString()}`, {
headers: getAuthHeaders(),
});
if (!res.ok) throw new Error(`HTTP error ${res.status}`); if (!res.ok) throw new Error(`HTTP error ${res.status}`);
const json = await res.json(); const json = await res.json();
setData(json); setData(json);