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