uwu rawr :3
This commit is contained in:
@@ -5,16 +5,17 @@ import { useRouter } from "next/navigation";
|
|||||||
import Layout from "@/components/layout/layout";
|
import Layout from "@/components/layout/layout";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Product } from "@/models/products";
|
import { Product } from "@/models/products";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus, Upload } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
fetchProductData,
|
fetchProductData,
|
||||||
saveProductData,
|
saveProductData,
|
||||||
saveProductImage,
|
saveProductImage,
|
||||||
deleteProductData,
|
deleteProductData,
|
||||||
} from "@/lib/productData";
|
} from "@/lib/productData";
|
||||||
import { ProductModal } from "@/components/modals/product-modal";
|
|
||||||
import ProductTable from "@/components/tables/product-table";
|
import ProductTable from "@/components/tables/product-table";
|
||||||
import { Category } from "@/models/categories"
|
import { Category } from "@/models/categories";
|
||||||
|
import ImportProductsModal from "@/components/modals/import-products-modal";
|
||||||
|
import { ProductModal } from "@/components/modals/product-modal";
|
||||||
|
|
||||||
|
|
||||||
export default function ProductsPage() {
|
export default function ProductsPage() {
|
||||||
@@ -33,6 +34,8 @@ export default function ProductsPage() {
|
|||||||
pricing: [{ minQuantity: 1, pricePerUnit: 0 }],
|
pricing: [{ minQuantity: 1, pricePerUnit: 0 }],
|
||||||
image: null,
|
image: null,
|
||||||
});
|
});
|
||||||
|
const [importModalOpen, setImportModalOpen] = useState(false);
|
||||||
|
const [addProductOpen, setAddProductOpen] = useState(false);
|
||||||
|
|
||||||
// Fetch products and categories
|
// Fetch products and categories
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -214,14 +217,23 @@ export default function ProductsPage() {
|
|||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<h1 className="text-2xl font-semibold text-gray-900 dark:text-white">
|
<h1 className="text-2xl font-semibold">Product Inventory</h1>
|
||||||
Product Inventory
|
<div className="flex items-center gap-3">
|
||||||
</h1>
|
<Button
|
||||||
<Button onClick={handleAddNewProduct}>
|
onClick={() => setImportModalOpen(true)}
|
||||||
<Plus className="mr-2 h-5 w-5" />
|
variant="outline"
|
||||||
Add Product
|
className="gap-2"
|
||||||
</Button>
|
disabled={true}
|
||||||
|
>
|
||||||
|
<Upload className="h-4 w-4" />
|
||||||
|
Import Products
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => setAddProductOpen(true)} className="gap-2">
|
||||||
|
<Plus className="h-4 w-4" />
|
||||||
|
Add Product
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ProductTable
|
<ProductTable
|
||||||
@@ -233,18 +245,26 @@ export default function ProductsPage() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<ProductModal
|
<ProductModal
|
||||||
open={modalOpen}
|
open={addProductOpen}
|
||||||
onClose={() => setModalOpen(false)}
|
onClose={() => setAddProductOpen(false)}
|
||||||
onSave={handleSaveProduct}
|
onSave={handleSaveProduct}
|
||||||
productData={productData}
|
productData={productData}
|
||||||
categories={categories}
|
categories={categories}
|
||||||
editing={editing}
|
editing={editing}
|
||||||
handleChange={handleChange}
|
handleChange={handleChange}
|
||||||
handleTieredPricingChange={handleTieredPricingChange}
|
handleTieredPricingChange={handleTieredPricingChange}
|
||||||
handleAddTier={handleAddTier} // ✅ Ensure this is passed
|
handleAddTier={handleAddTier}
|
||||||
handleRemoveTier={handleRemoveTier} // ✅ Ensure this is passed
|
handleRemoveTier={handleRemoveTier}
|
||||||
setProductData={setProductData}
|
setProductData={setProductData}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ImportProductsModal
|
||||||
|
open={importModalOpen}
|
||||||
|
setOpen={setImportModalOpen}
|
||||||
|
onImportComplete={() => {
|
||||||
|
setProducts(products);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -138,14 +138,12 @@ export default function StorefrontPage() {
|
|||||||
fetchStorefront();
|
fetchStorefront();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// ✅ Handle Form Input Changes
|
|
||||||
const handleInputChange = (
|
const handleInputChange = (
|
||||||
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
||||||
) => {
|
) => {
|
||||||
setStorefront({ ...storefront, [e.target.name]: e.target.value });
|
setStorefront({ ...storefront, [e.target.name]: e.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
// ✅ Save Storefront Changes
|
|
||||||
const saveStorefront = async () => {
|
const saveStorefront = async () => {
|
||||||
try {
|
try {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
@@ -161,7 +159,6 @@ export default function StorefrontPage() {
|
|||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<div className="max-w-4xl mx-auto p-6 space-y-6">
|
<div className="max-w-4xl mx-auto p-6 space-y-6">
|
||||||
{/* PGP Key Section */}
|
|
||||||
<div className="bg-white dark:bg-[#0F0F12] rounded-xl shadow-lg p-6 border dark:border-zinc-700">
|
<div className="bg-white dark:bg-[#0F0F12] rounded-xl shadow-lg p-6 border dark:border-zinc-700">
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
<Key className="h-6 w-6 text-purple-600 dark:text-purple-400" />
|
<Key className="h-6 w-6 text-purple-600 dark:text-purple-400" />
|
||||||
@@ -177,7 +174,6 @@ export default function StorefrontPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Telegram Section */}
|
|
||||||
<div className="bg-white dark:bg-[#0F0F12] rounded-xl shadow-lg p-6 border dark:border-zinc-700">
|
<div className="bg-white dark:bg-[#0F0F12] rounded-xl shadow-lg p-6 border dark:border-zinc-700">
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
<MessageSquare className="h-6 w-6 text-emerald-600 dark:text-emerald-400" />
|
<MessageSquare className="h-6 w-6 text-emerald-600 dark:text-emerald-400" />
|
||||||
@@ -193,7 +189,6 @@ export default function StorefrontPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Welcome Message Section */}
|
|
||||||
<div className="bg-white dark:bg-[#0F0F12] rounded-xl shadow-lg p-6 border dark:border-zinc-700">
|
<div className="bg-white dark:bg-[#0F0F12] rounded-xl shadow-lg p-6 border dark:border-zinc-700">
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
<MessageSquare className="h-6 w-6 text-blue-600 dark:text-blue-400" />
|
<MessageSquare className="h-6 w-6 text-blue-600 dark:text-blue-400" />
|
||||||
@@ -209,7 +204,6 @@ export default function StorefrontPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Shipping Locations Section */}
|
|
||||||
<div className="bg-white dark:bg-[#0F0F12] rounded-xl shadow-lg p-6 border dark:border-zinc-700">
|
<div className="bg-white dark:bg-[#0F0F12] rounded-xl shadow-lg p-6 border dark:border-zinc-700">
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
<Globe className="h-6 w-6 text-blue-600 dark:text-blue-400" />
|
<Globe className="h-6 w-6 text-blue-600 dark:text-blue-400" />
|
||||||
@@ -282,7 +276,6 @@ export default function StorefrontPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Cryptocurrency Wallets Section */}
|
|
||||||
<div className="bg-white dark:bg-[#0F0F12] rounded-xl shadow-lg p-6 border dark:border-zinc-700">
|
<div className="bg-white dark:bg-[#0F0F12] rounded-xl shadow-lg p-6 border dark:border-zinc-700">
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
<Wallet className="h-6 w-6 text-yellow-600 dark:text-yellow-400" />
|
<Wallet className="h-6 w-6 text-yellow-600 dark:text-yellow-400" />
|
||||||
@@ -356,7 +349,6 @@ export default function StorefrontPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Action Buttons */}
|
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setBroadcastOpen(true)}
|
onClick={() => setBroadcastOpen(true)}
|
||||||
|
|||||||
139
components/modals/import-products-modal.tsx
Normal file
139
components/modals/import-products-modal.tsx
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Upload, AlertCircle } from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||||
|
|
||||||
|
interface ImportProductsModalProps {
|
||||||
|
open: boolean;
|
||||||
|
setOpen: (open: boolean) => void;
|
||||||
|
onImportComplete: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ImportProductsModal({ open, setOpen, onImportComplete }: ImportProductsModalProps) {
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
|
|
||||||
|
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const selectedFile = e.target.files?.[0];
|
||||||
|
if (selectedFile && selectedFile.type === "text/plain") {
|
||||||
|
setFile(selectedFile);
|
||||||
|
} else {
|
||||||
|
toast.error("Please select a valid .txt file");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImport = async () => {
|
||||||
|
if (!file) {
|
||||||
|
toast.error("Please select a file first");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsUploading(true);
|
||||||
|
const fileContent = await file.text();
|
||||||
|
|
||||||
|
const products = []
|
||||||
|
const sections = fileContent.split("----------------------------------------------------------")
|
||||||
|
|
||||||
|
for (const section of sections) {
|
||||||
|
if(!section.trim()) continue
|
||||||
|
|
||||||
|
const lines = section.trim().split("\n")
|
||||||
|
const name = lines[0].trim()
|
||||||
|
const category = lines[1].trim().split("->")[0].trim()
|
||||||
|
const subcategory = lines[1].trim().split("->")[1].split("•")[0].trim()
|
||||||
|
|
||||||
|
console.log(`${name} - ${category} - ${subcategory}`)
|
||||||
|
|
||||||
|
const pricing = lines.slice(3).filter(line => line.includes('@')).map(line => {
|
||||||
|
const price = line.split('@')[0].trim()
|
||||||
|
const unit = line.split('@')[1].trim()
|
||||||
|
return { price, unit }
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(pricing)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//toast.success(`Successfully imported ${result.count} products`);
|
||||||
|
onImportComplete();
|
||||||
|
setOpen(false);
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Failed to import products");
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setIsUploading(false);
|
||||||
|
setFile(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogContent className="sm:max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center gap-2">
|
||||||
|
<Upload className="h-5 w-5" />
|
||||||
|
Import Products
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Alert>
|
||||||
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
<AlertDescription>
|
||||||
|
File should be a .txt file with product details in the correct format
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<div className="grid w-full items-center gap-1.5">
|
||||||
|
<label
|
||||||
|
htmlFor="file-upload"
|
||||||
|
className="border-2 border-dashed rounded-lg p-8 text-center cursor-pointer hover:border-gray-400 transition-colors"
|
||||||
|
>
|
||||||
|
{file ? (
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
Selected: {file.name}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
Click to select or drag and drop a .txt file
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<input
|
||||||
|
id="file-upload"
|
||||||
|
type="file"
|
||||||
|
accept=".txt"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
className="hidden"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter className="sm:justify-start">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={handleImport}
|
||||||
|
disabled={!file || isUploading}
|
||||||
|
>
|
||||||
|
{isUploading ? "Importing..." : "Import Products"}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user