feat: add image upload functionality for menu items
Release package / release (pull_request) Has been cancelled
Release package / release (pull_request) Has been cancelled
This commit is contained in:
@@ -1,103 +1,10 @@
|
|||||||
# Coffee Shop Frontend - TODO
|
# TODO - Add image upload field for manager add/update menu item
|
||||||
|
|
||||||
## Completed Features & Implementations
|
- [x] Update GraphQL queries/mutations in `lib/manager-context.tsx` to include `imageUrl`
|
||||||
|
- [x] Update `components/organisms/manager/ProductModal.tsx`
|
||||||
### A. Dead Code Removed
|
- [x] Add file input for image selection
|
||||||
|
- [x] Add upload button to call `POST /api/file`
|
||||||
- [x] lib/constants.ts - Removed unused NAV_LINKS export
|
- [x] Store returned URL string into `form.imageUrl`
|
||||||
- [x] lib/types.ts - Removed unused NavLink interface
|
- [x] Show upload/loading/error states and image preview
|
||||||
- [x] components/Navbar.tsx - Removed trivial handleClick wrapper; inlined
|
- [x] Update `components/organisms/manager/ProductsTab.tsx` to show image thumbnail in table
|
||||||
onCategoryChange call
|
- [x] Mark TODO progress after each step completed
|
||||||
- [x] components/Navbar.tsx - Removed unused Link import
|
|
||||||
|
|
||||||
### B. Bugs / Inaccuracies Fixed
|
|
||||||
|
|
||||||
- [x] layouts/header.tsx - Fixed JSDoc: 3-column -> 2-column layout (no center
|
|
||||||
section exists)
|
|
||||||
- [x] app/page.tsx - Added available !== false filter to product list
|
|
||||||
- [x] app/page.tsx - Fixed setState-in-effect lint error: moved initial sidebar
|
|
||||||
state to lazy useState initializer
|
|
||||||
- [x] next.config.ts - Added explanatory JSDoc comment
|
|
||||||
|
|
||||||
### C. Documentation Updated
|
|
||||||
|
|
||||||
- [x] README.md - Fixed file structure tree, removed SCSS, fixed dark mode note,
|
|
||||||
updated tech table
|
|
||||||
- [x] components/COMPONENTS.md - Fixed CartProduct styling (was outdated
|
|
||||||
w-64/text-red-500/bg-blue-600); added Navbar, Header, Footer sections
|
|
||||||
|
|
||||||
### D. New Documentation Created
|
|
||||||
|
|
||||||
- [x] WORKFLOW.md - Architecture, data flow, design token system, how-to guides,
|
|
||||||
dev workflow
|
|
||||||
|
|
||||||
### E. Mini-test Results
|
|
||||||
|
|
||||||
- [x] npm run lint - PASSED (0 errors, 0 warnings)
|
|
||||||
- [x] npm run build - PASSED (Compiled successfully, TypeScript clean, static
|
|
||||||
pages generated)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Pending Features (Future Work)
|
|
||||||
|
|
||||||
### Cart & Ordering
|
|
||||||
|
|
||||||
- [ ] Implement cart checkout flow (app/(main)/cart or modal)
|
|
||||||
- [ ] Cart sidebar/modal with item list and total
|
|
||||||
- [ ] Order submission API integration
|
|
||||||
- [ ] Payment page implementation (app/(main)/payment)
|
|
||||||
- [ ] Order history/tracking page
|
|
||||||
- [ ] Toast notifications for cart actions
|
|
||||||
|
|
||||||
### Authentication & User Management
|
|
||||||
|
|
||||||
- [ ] Real backend authentication (replace MOCK_AUTH_DB)
|
|
||||||
- [ ] Real OTP delivery service (SMS integration)
|
|
||||||
- [ ] User profile page with edit capability
|
|
||||||
- [ ] Password reset/recovery flow
|
|
||||||
- [ ] Session management and token refresh
|
|
||||||
|
|
||||||
### Manager Features
|
|
||||||
|
|
||||||
- [ ] Manager dashboard page (app/(manager)/page.tsx)
|
|
||||||
- [ ] Product management (add/edit/delete)
|
|
||||||
- [ ] Category management
|
|
||||||
- [ ] Order management & tracking
|
|
||||||
- [ ] Sales analytics/dashboard
|
|
||||||
- [ ] Inventory management
|
|
||||||
|
|
||||||
### Backend Integration
|
|
||||||
|
|
||||||
- [ ] Replace MOCK_PRODUCTS with API calls (GET /api/products)
|
|
||||||
- [ ] Replace MOCK_SHOPS with API calls (GET /api/shops)
|
|
||||||
- [ ] Replace MOCK_USERS with real authentication (POST /api/auth/login)
|
|
||||||
- [ ] Real product images (replace placeholder.jpg)
|
|
||||||
- [ ] Image upload for products
|
|
||||||
|
|
||||||
### UX Improvements
|
|
||||||
|
|
||||||
- [ ] Dark mode toggle (CSS variables prepared, toggle UI needed)
|
|
||||||
- [ ] Loading skeletons for product grid
|
|
||||||
- [ ] Product detail modal/page with full description
|
|
||||||
- [ ] Wishlist/favorites feature
|
|
||||||
- [ ] Sort products (price, rating, etc.)
|
|
||||||
- [ ] Filter by price range
|
|
||||||
- [ ] Quantity selector in product card
|
|
||||||
- [ ] Related products suggestions
|
|
||||||
|
|
||||||
### Performance & SEO
|
|
||||||
|
|
||||||
- [ ] Dynamic route generation for products (app/(main)/product/[id]/page.tsx)
|
|
||||||
- [ ] Dynamic route generation for shops (app/(feed)/shop/[id]/page.tsx)
|
|
||||||
- [ ] Meta tags and Open Graph for SEO
|
|
||||||
- [ ] Image optimization and lazy loading
|
|
||||||
- [ ] Code splitting and dynamic imports
|
|
||||||
|
|
||||||
### Accessibility & Testing
|
|
||||||
|
|
||||||
- [ ] Keyboard navigation testing
|
|
||||||
- [ ] ARIA labels audit
|
|
||||||
- [ ] Unit tests for contexts
|
|
||||||
- [ ] E2E tests for user flows
|
|
||||||
- [ ] Accessibility audit (WCAG 2.1 AA)
|
|
||||||
|
|||||||
@@ -14,13 +14,60 @@ export default function ProductModal({
|
|||||||
const [form, setForm] = useState<MenuItemEntity>({
|
const [form, setForm] = useState<MenuItemEntity>({
|
||||||
name: product?.name ?? "",
|
name: product?.name ?? "",
|
||||||
price: product?.price ?? 0,
|
price: product?.price ?? 0,
|
||||||
imageUrl: product?.imageUrl ?? "/imgs/products/placeholder.jpg",
|
imageUrl: product?.imageUrl ?? "",
|
||||||
description: product?.description ?? "",
|
description: product?.description ?? "",
|
||||||
available: product?.available ?? true,
|
available: product?.available ?? true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [uploading, setUploading] = useState(false);
|
||||||
|
const [uploadError, setUploadError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const inputCls =
|
||||||
|
"text-foreground w-full rounded-xl border border-(--color-border) bg-white px-3 py-2 text-sm transition outline-none focus:border-(--color-primary) focus:ring-2 focus:ring-(--color-primary)/20";
|
||||||
|
|
||||||
|
const uploadImage = async (file: File) => {
|
||||||
|
setUploading(true);
|
||||||
|
setUploadError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const body = new FormData();
|
||||||
|
body.append("file", file);
|
||||||
|
|
||||||
|
const res = await fetch("/api/file", {
|
||||||
|
method: "POST",
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error("Upload ảnh thất bại.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const raw = await res.text();
|
||||||
|
|
||||||
|
let filename = "";
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw.trim());
|
||||||
|
filename = parsed.filename || "";
|
||||||
|
} catch {
|
||||||
|
const match = raw.match(/"filename"\s*:\s*"([^"]+)"/i);
|
||||||
|
filename = match?.[1] || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filename) {
|
||||||
|
throw new Error("Không nhận được filename ảnh từ server.");
|
||||||
|
}
|
||||||
|
|
||||||
|
setForm((prev) => ({ ...prev, imageUrl: filename }));
|
||||||
|
} catch (error: any) {
|
||||||
|
setUploadError(error?.message || "Không thể upload ảnh.");
|
||||||
|
} finally {
|
||||||
|
setUploading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (isEdit && product) {
|
if (isEdit && product) {
|
||||||
onSave({ ...form, id: product.id });
|
onSave({ ...form, id: product.id });
|
||||||
} else {
|
} else {
|
||||||
@@ -28,14 +75,21 @@ export default function ProductModal({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const inputCls =
|
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
"text-foreground w-full rounded-xl border border-(--color-border) bg-white px-3 py-2 text-sm transition outline-none focus:border-(--color-primary) focus:ring-2 focus:ring-(--color-primary)/20";
|
const file = e.target.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
await uploadImage(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toDisplayUrl = (filename: string) =>
|
||||||
|
filename && !filename.startsWith("/") && !filename.startsWith("http")
|
||||||
|
? `/api/file/${filename}`
|
||||||
|
: filename;
|
||||||
|
|
||||||
|
const previewUrl = toDisplayUrl(form.imageUrl);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4 backdrop-blur-sm">
|
||||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4 backdrop-blur-sm"
|
|
||||||
onClick={(e) => e.target === e.currentTarget && onClose()}
|
|
||||||
>
|
|
||||||
<div className="w-full max-w-lg rounded-2xl bg-white shadow-2xl">
|
<div className="w-full max-w-lg rounded-2xl bg-white shadow-2xl">
|
||||||
<div className="flex items-center justify-between border-b border-(--color-border-light) px-6 py-4">
|
<div className="flex items-center justify-between border-b border-(--color-border-light) px-6 py-4">
|
||||||
<h2 className="text-foreground text-lg font-bold">
|
<h2 className="text-foreground text-lg font-bold">
|
||||||
@@ -83,6 +137,46 @@ export default function ProductModal({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="mb-1 block text-sm font-medium text-(--color-text-secondary)">
|
||||||
|
Ảnh món
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
disabled={uploading}
|
||||||
|
className="text-foreground w-full rounded-xl border border-(--color-border) bg-white px-3 py-2 text-sm disabled:opacity-60"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{uploading && (
|
||||||
|
<p className="text-sm text-(--color-text-muted)">
|
||||||
|
<i className="fa-solid fa-spinner mr-1 animate-spin"></i>
|
||||||
|
Đang upload ảnh...
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{previewUrl && (
|
||||||
|
<div className="mt-2">
|
||||||
|
<img
|
||||||
|
src={previewUrl}
|
||||||
|
alt="Preview ảnh món"
|
||||||
|
className="h-24 w-24 rounded-lg border border-(--color-border-light) object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{uploadError && (
|
||||||
|
<p className="mt-1 text-sm text-red-500">
|
||||||
|
<i className="fa-solid fa-circle-exclamation mr-1"></i>
|
||||||
|
{uploadError}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 block text-sm font-medium text-(--color-text-secondary)">
|
<label className="mb-1 block text-sm font-medium text-(--color-text-secondary)">
|
||||||
Mô tả
|
Mô tả
|
||||||
@@ -131,7 +225,8 @@ export default function ProductModal({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="flex-1 cursor-pointer rounded-xl border-none bg-(--color-primary) px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-(--color-primary-dark) active:scale-95"
|
disabled={uploading}
|
||||||
|
className="flex-1 cursor-pointer rounded-xl border-none bg-(--color-primary) px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-(--color-primary-dark) active:scale-95 disabled:opacity-60"
|
||||||
>
|
>
|
||||||
{isEdit ? "Lưu thay đổi" : "Thêm món"}
|
{isEdit ? "Lưu thay đổi" : "Thêm món"}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ function formatPrice(price: number) {
|
|||||||
return price.toLocaleString("vi-VN") + "đ";
|
return price.toLocaleString("vi-VN") + "đ";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toDisplayUrl(filename: string) {
|
||||||
|
if (!filename) return "/imgs/products/placeholder.jpg";
|
||||||
|
if (filename.startsWith("/") || filename.startsWith("http")) return filename;
|
||||||
|
return `/api/file/${filename}`;
|
||||||
|
}
|
||||||
|
|
||||||
export default function ProductsTab() {
|
export default function ProductsTab() {
|
||||||
const {
|
const {
|
||||||
products,
|
products,
|
||||||
@@ -102,6 +108,9 @@ export default function ProductsTab() {
|
|||||||
<table className="min-w-full divide-y divide-(--color-border-light) text-sm">
|
<table className="min-w-full divide-y divide-(--color-border-light) text-sm">
|
||||||
<thead className="bg-background">
|
<thead className="bg-background">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th className="px-4 py-3 text-left font-semibold text-(--color-text-secondary)">
|
||||||
|
Ảnh
|
||||||
|
</th>
|
||||||
<th className="px-4 py-3 text-left font-semibold text-(--color-text-secondary)">
|
<th className="px-4 py-3 text-left font-semibold text-(--color-text-secondary)">
|
||||||
Tên món
|
Tên món
|
||||||
</th>
|
</th>
|
||||||
@@ -133,6 +142,13 @@ export default function ProductsTab() {
|
|||||||
key={p.id}
|
key={p.id}
|
||||||
className="hover:bg-background transition-colors"
|
className="hover:bg-background transition-colors"
|
||||||
>
|
>
|
||||||
|
<td className="px-4 py-3">
|
||||||
|
<img
|
||||||
|
src={toDisplayUrl(p.imageUrl)}
|
||||||
|
alt={p.name}
|
||||||
|
className="h-10 w-10 rounded-lg border border-(--color-border-light) object-cover"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-foreground font-medium">{p.name}</p>
|
<p className="text-foreground font-medium">{p.name}</p>
|
||||||
|
|||||||
+21
-1
@@ -51,6 +51,7 @@ const GET_EATERY_MENU = gql`
|
|||||||
menuItems {
|
menuItems {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
imageUrl
|
||||||
available
|
available
|
||||||
description
|
description
|
||||||
price
|
price
|
||||||
@@ -64,6 +65,7 @@ const ADD_MENU_ITEM = gql`
|
|||||||
addMenuItem(menuItem: $menuItem) {
|
addMenuItem(menuItem: $menuItem) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
imageUrl
|
||||||
available
|
available
|
||||||
description
|
description
|
||||||
price
|
price
|
||||||
@@ -76,6 +78,7 @@ const UPDATE_MENU_ITEM = gql`
|
|||||||
updateMenuItem(menuItem: $menuItem) {
|
updateMenuItem(menuItem: $menuItem) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
imageUrl
|
||||||
available
|
available
|
||||||
description
|
description
|
||||||
price
|
price
|
||||||
@@ -129,7 +132,24 @@ export function ManagerProvider({ children }: { children: ReactNode }) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data) setProducts((prev) => [...prev, data.addMenuItem]);
|
if (!data) return;
|
||||||
|
|
||||||
|
// addMenuItem backend does not persist imageUrl (constructor only maps name+price).
|
||||||
|
// If the caller provided an imageUrl, patch it immediately via updateMenuItem
|
||||||
|
// which uses MapStruct and correctly saves all fields.
|
||||||
|
if (product.imageUrl && data.addMenuItem?.id) {
|
||||||
|
const { data: updated } = await mutateUpdateMenuItem({
|
||||||
|
variables: {
|
||||||
|
menuItem: { id: data.addMenuItem.id, imageUrl: product.imageUrl },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (updated) {
|
||||||
|
setProducts((prev) => [...prev, updated.updateMenuItem]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setProducts((prev) => [...prev, data.addMenuItem]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateProduct = async (product: MenuItemEntity) => {
|
const updateProduct = async (product: MenuItemEntity) => {
|
||||||
|
|||||||
Generated
-19
@@ -164,7 +164,6 @@
|
|||||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.0",
|
"@babel/code-frame": "^7.29.0",
|
||||||
"@babel/generator": "^7.29.0",
|
"@babel/generator": "^7.29.0",
|
||||||
@@ -1429,7 +1428,6 @@
|
|||||||
"integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==",
|
"integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-token": "^6.0.0",
|
"@octokit/auth-token": "^6.0.0",
|
||||||
"@octokit/graphql": "^9.0.3",
|
"@octokit/graphql": "^9.0.3",
|
||||||
@@ -2429,7 +2427,6 @@
|
|||||||
"integrity": "sha512-3DgfkukFyC/sE/VuYjaUUWoFfuVjPK55vOFDsxD56XXynFMCZDYFogH2l/hDfOsQAm1myoU/1xByJ3tWqtulXA==",
|
"integrity": "sha512-3DgfkukFyC/sE/VuYjaUUWoFfuVjPK55vOFDsxD56XXynFMCZDYFogH2l/hDfOsQAm1myoU/1xByJ3tWqtulXA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/generator": "^7.28.0",
|
"@babel/generator": "^7.28.0",
|
||||||
"@babel/parser": "^7.28.0",
|
"@babel/parser": "^7.28.0",
|
||||||
@@ -2565,7 +2562,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
@@ -2635,7 +2631,6 @@
|
|||||||
"integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==",
|
"integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.57.2",
|
"@typescript-eslint/scope-manager": "8.57.2",
|
||||||
"@typescript-eslint/types": "8.57.2",
|
"@typescript-eslint/types": "8.57.2",
|
||||||
@@ -3209,7 +3204,6 @@
|
|||||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -3832,7 +3826,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@@ -5340,7 +5333,6 @@
|
|||||||
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
|
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -5526,7 +5518,6 @@
|
|||||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rtsao/scc": "^1.1.0",
|
"@rtsao/scc": "^1.1.0",
|
||||||
"array-includes": "^3.1.9",
|
"array-includes": "^3.1.9",
|
||||||
@@ -6824,7 +6815,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.0.tgz",
|
||||||
"integrity": "sha512-BBvQ/406p+4CZbTpCbVPSxfzrZrbnuWSP1ELYgyS6B+hNeKzgrdB4JczCa5VZUBQrDa9hUngm0KnexY6pJRN5Q==",
|
"integrity": "sha512-BBvQ/406p+4CZbTpCbVPSxfzrZrbnuWSP1ELYgyS6B+hNeKzgrdB4JczCa5VZUBQrDa9hUngm0KnexY6pJRN5Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
|
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
|
||||||
}
|
}
|
||||||
@@ -8523,7 +8513,6 @@
|
|||||||
"integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
|
"integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"marked": "bin/marked.js"
|
"marked": "bin/marked.js"
|
||||||
},
|
},
|
||||||
@@ -10927,7 +10916,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -11750,7 +11738,6 @@
|
|||||||
"integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
|
"integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
@@ -12065,7 +12052,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -12075,7 +12061,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
@@ -12451,7 +12436,6 @@
|
|||||||
"integrity": "sha512-WRgl5GcypwramYX4HV+eQGzUbD7UUbljVmS+5G1uMwX/wLgYuJAxGeerXJDMO2xshng4+FXqCgyB5QfClV6WjA==",
|
"integrity": "sha512-WRgl5GcypwramYX4HV+eQGzUbD7UUbljVmS+5G1uMwX/wLgYuJAxGeerXJDMO2xshng4+FXqCgyB5QfClV6WjA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@semantic-release/commit-analyzer": "^13.0.1",
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
||||||
"@semantic-release/error": "^4.0.0",
|
"@semantic-release/error": "^4.0.0",
|
||||||
@@ -13709,7 +13693,6 @@
|
|||||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -13935,7 +13918,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -14587,7 +14569,6 @@
|
|||||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user