From 4ad0ed7dbcea9f89c9784d87095a1f9b23f8f73f Mon Sep 17 00:00:00 2001 From: root Date: Sat, 14 Feb 2026 17:28:19 +0500 Subject: [PATCH] UI fixes and mobile responsive --- src/app/dashboard/inventory/page.tsx | 721 ++++++++++-------- .../juno-engine/email-templates/page.tsx | 516 ++++++------- .../juno-engine/knowledge-base/page.tsx | 79 +- src/app/dashboard/juno-engine/layout.tsx | 59 +- .../juno-engine/tickets/[ticketId]/page.tsx | 295 ++++--- src/app/dashboard/page.tsx | 306 +++++--- src/components/MobileSidebar.tsx | 119 ++- src/components/Sidebar.tsx | 63 +- src/components/kb/AskSection.tsx | 89 ++- src/components/kb/DocumentsSection.tsx | 68 +- src/components/kb/FaqSection.tsx | 152 ++-- 11 files changed, 1473 insertions(+), 994 deletions(-) diff --git a/src/app/dashboard/inventory/page.tsx b/src/app/dashboard/inventory/page.tsx index adad69a..16da638 100644 --- a/src/app/dashboard/inventory/page.tsx +++ b/src/app/dashboard/inventory/page.tsx @@ -19,8 +19,12 @@ import { X, CheckCircle2, ChevronLeft, - ChevronRight + ChevronRight, + Plus } from "lucide-react" +import { createPortal } from "react-dom" +import { motion, AnimatePresence } from "framer-motion" +import { useTheme } from "@/components/ThemeProvider" interface ShopifyProduct { id: number @@ -79,7 +83,15 @@ export default function InventoryPage() { const [statusFilter, setStatusFilter] = useState("all") const [vendorFilter, setVendorFilter] = useState("all") const [showFilters, setShowFilters] = useState(false) - const menuRefs = useRef<{ [key: number]: HTMLDivElement | null }>({}) + const [mounted, setMounted] = useState(false) + const [menuRect, setMenuRect] = useState<{ top: number; left: number } | null>(null) + const menuRefs = useRef<{ [key: number]: HTMLButtonElement | null }>({}) + const { theme } = useTheme() + const isLight = theme === "light" + + useEffect(() => { + setMounted(true) + }, []) useEffect(() => { const fetchStoreInfo = async () => { @@ -133,10 +145,10 @@ export default function InventoryPage() { // First get the active store const storeRes = await fetch("/api/stores") if (!storeRes.ok) return - + const storeData = await storeRes.json() const activeStore = storeData.stores?.find((s: { isActive: boolean }) => s.isActive) - + if (!activeStore) { setConnectedSuppliers([]) return @@ -147,27 +159,27 @@ export default function InventoryPage() { if (res.ok) { const data = await res.json() // Only show CONNECTED suppliers for the active store - + const connected = data.connections?.filter( // eslint-disable-next-line @typescript-eslint/no-explicit-any - (c: any) => c.status === "CONNECTED" && - c.supplier && - c.store?.id === activeStore.id + (c: any) => c.status === "CONNECTED" && + c.supplier && + c.store?.id === activeStore.id // eslint-disable-next-line @typescript-eslint/no-explicit-any ).map((c: any) => c.supplier) || [] - + // Remove duplicates by supplier ID (in case of any edge cases) const uniqueSuppliers = Array.from( new Map(connected.map((s: Supplier) => [s.id, s])).values() ) - + setConnectedSuppliers(uniqueSuppliers as Supplier[]) } } catch { // Silent fail - suppliers not critical for initial load } } - + // Fetch store info, inventory, syncs, and suppliers fetchStoreInfo() fetchInventory() @@ -201,8 +213,19 @@ export default function InventoryPage() { } }, [openMenuId]) - const handleMenuToggle = (productId: number) => { - setOpenMenuId(openMenuId === productId ? null : productId) + const handleMenuToggle = (productId: number, e: React.MouseEvent) => { + e.stopPropagation() + if (openMenuId === productId) { + setOpenMenuId(null) + setMenuRect(null) + } else { + const rect = (e.currentTarget as HTMLElement).getBoundingClientRect() + setMenuRect({ + top: rect.bottom + window.scrollY, + left: rect.right + window.scrollX + }) + setOpenMenuId(productId) + } } const handleViewProduct = (product: ShopifyProduct) => { @@ -307,12 +330,12 @@ export default function InventoryPage() { p.variants.length, new Date(p.created_at).toLocaleDateString() ]) - + const csvContent = [ csvHeaders.join(","), ...csvRows.map(row => row.join(",")) ].join("\n") - + const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" }) const link = document.createElement("a") const url = URL.createObjectURL(blob) @@ -336,306 +359,402 @@ export default function InventoryPage() { const uniqueVendors = Array.from(new Set(products.map(p => p.vendor))).sort() return ( -
-
-
-

Inventory

-

Manage your synced Shopify products and variants.

-
-
- -
-
- -
-
-
- - setSearchTerm(e.target.value)} - className="w-full h-10 md:h-11 pl-10 md:pl-11 pr-4 bg-white/5 border border-white/10 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500/50 text-white placeholder:text-slate-500 text-sm font-medium transition-all" - /> + <> +
+
+
+

Inventory

+

Manage your synced Shopify products and variants.

-
- - {showFilters && ( -
-
-
- - -
-
- - -
-
+ +
+
+
+ + setSearchTerm(e.target.value)} + className="w-full h-10 md:h-11 pl-10 md:pl-11 pr-4 bg-white/5 border border-white/10 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500/50 text-white placeholder:text-slate-500 text-sm font-medium transition-all" + /> +
+
+ + {showFilters && ( +
+
+
+ + +
+
+ + +
+ -
-
- )} -
-
- -
-
- {loading ? ( -
- -

Syncing with Shopify...

-
- ) : error ? ( -
-
- -
-
-

{error}

-

Check your Shopify API permissions (read_products) and try again.

-
-
- ) : filteredProducts.length === 0 ? ( -
- -

No products found.

-
- ) : ( - - - - - - - - - - - - - {filteredProducts.map((product) => ( - - - - - - - - - ))} - -
ProductStatusInventorySupplierSyncActions
-
-
- {product.image?.src ? ( - - ) : ( - - )} -
-
- {product.title} - ID: {product.id} -
-
-
- - {product.status} - - -
- {product.variants.length} Variants - Total Sync Stock -
-
- {(() => { - const sync = isProductSynced(product.id) - const syncStatus = getProductSyncStatus(product.id) - return sync ? ( - {sync.supplier.companyName} - ) : syncStatus?.status === "PENDING" ? ( - Pending... - ) : ( - — - ) - })()} - - {(() => { - const sync = isProductSynced(product.id) - const syncStatus = getProductSyncStatus(product.id) - return sync ? ( - - Synced - - ) : syncStatus?.status === "PENDING" ? ( - - Pending - - ) : ( - - ) - })()} - -
{ menuRefs.current[product.id] = el }}> - - {openMenuId === product.id && ( -
- - - -
- {storeDomain && ( - setOpenMenuId(null)} - className="w-full px-4 py-2.5 text-left text-sm font-medium text-slate-700 hover:bg-indigo-500 hover:text-white transition-colors flex items-center gap-2" - > - - View on Storefront - - )} -
- )} -
-
- )} -
-
- - {/* Pagination removed as per requirements */} -
- - {/* Supplier Selection Modal */} - {showSupplierModal && selectedProduct && ( -
-
-
-
-

Select Supplier

-

Choose a supplier to sync "{selectedProduct.title}"

-
- -
-
- {connectedSuppliers.length === 0 ? ( -
- -

No connected suppliers

-

Please connect with suppliers first from the Suppliers page.

-
- ) : ( -
- {connectedSuppliers.map((supplier) => ( - - ))} +
)}
+ +
+
+ {loading ? ( +
+ +

Syncing with Shopify...

+
+ ) : error ? ( +
+
+ +
+
+

{error}

+

Check your Shopify API permissions (read_products) and try again.

+
+
+ ) : filteredProducts.length === 0 ? ( +
+ +

No products found.

+
+ ) : ( + + + + + + + + + + + + + {filteredProducts.map((product) => ( + + + + + + + + + ))} + +
ProductStatusInventorySupplierSyncActions
+
+
+ {product.image?.src ? ( + + ) : ( + + )} +
+
+ {product.title} + ID: {product.id} +
+
+
+ + {product.status} + + +
+ {product.variants.length} Variants + Total Sync Stock +
+
+ {(() => { + const sync = isProductSynced(product.id) + const syncStatus = getProductSyncStatus(product.id) + return sync ? ( + {sync.supplier.companyName} + ) : syncStatus?.status === "PENDING" ? ( + Pending... + ) : ( + — + ) + })()} + + {(() => { + const sync = isProductSynced(product.id) + const syncStatus = getProductSyncStatus(product.id) + return sync ? ( + + Synced + + ) : syncStatus?.status === "PENDING" ? ( + + Pending + + ) : ( + + ) + })()} + +
+ +
+
+ )} +
+
+ + {/* Pagination removed as per requirements */}
+ + {/* Supplier Selection Modal */} + {showSupplierModal && selectedProduct && ( +
+
+
+
+

Select Supplier

+

Choose a supplier to sync "{selectedProduct.title}"

+
+ +
+
+ {connectedSuppliers.length === 0 ? ( +
+ +

No connected suppliers

+

Please connect with suppliers first from the Suppliers page.

+
+ ) : ( +
+ {connectedSuppliers.map((supplier) => ( + + ))} +
+ )} +
+
+
+ )} +
+ + {mounted && createPortal( + + {openMenuId !== null && menuRect !== null && ( + <> +
{ + setOpenMenuId(null) + setMenuRect(null) + }} + /> + { + setOpenMenuId(null) + setMenuRect(null) + }} + /> + + )} + , + document.body )} -
+ ) } + +function ActionsMenuContent({ + openMenuId, + menuRect, + isLight, + products, + storeDomain, + handleViewProduct, + handleCopyProductId, + handleRefreshProduct, + onClose +}: { + openMenuId: number; + menuRect: { top: number; left: number }; + isLight: boolean; + products: ShopifyProduct[]; + storeDomain: string | null; + handleViewProduct: (product: ShopifyProduct) => void; + handleCopyProductId: (productId: number) => void; + handleRefreshProduct: () => void; + onClose: () => void; +}) { + const product = products.find(p => p.id === openMenuId); + if (!product) return null; + + return ( + +
+ + + + + {storeDomain && product.handle && ( + <> + + + ); +} diff --git a/src/app/dashboard/juno-engine/email-templates/page.tsx b/src/app/dashboard/juno-engine/email-templates/page.tsx index 8519d80..0a7ea3f 100644 --- a/src/app/dashboard/juno-engine/email-templates/page.tsx +++ b/src/app/dashboard/juno-engine/email-templates/page.tsx @@ -205,8 +205,8 @@ export default function EmailTemplatesPage() { value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} className={`w-full h-10 md:h-11 pl-10 md:pl-11 pr-4 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500/30 transition-all text-sm font-medium ${isLight - ? "bg-white border-slate-200 text-slate-900 placeholder:text-slate-400 focus:border-indigo-500" - : "bg-white/5 border-white/10 text-white placeholder:text-slate-500 focus:border-indigo-500/50" + ? "bg-white border-slate-200 text-slate-900 placeholder:text-slate-400 focus:border-indigo-500" + : "bg-white/5 border-white/10 text-white placeholder:text-slate-500 focus:border-indigo-500/50" }`} />
@@ -245,8 +245,8 @@ export default function EmailTemplatesPage() { type="button" onClick={() => startEditTemplate(template)} className={`backdrop-blur-xl p-4 md:p-6 rounded-xl md:rounded-2xl border transition-all group text-left ${isLight - ? "bg-white border-slate-200 hover:bg-slate-50 hover:border-indigo-300 shadow-sm" - : "bg-white/5 border-white/10 hover:bg-white/[0.07] hover:border-indigo-500/30" + ? "bg-white border-slate-200 hover:bg-slate-50 hover:border-indigo-300 shadow-sm" + : "bg-white/5 border-white/10 hover:bg-white/[0.07] hover:border-indigo-500/30" }`} >
@@ -300,272 +300,276 @@ export default function EmailTemplatesPage() {
- - {editing && mounted && createPortal( -
- {/* Backdrop */} - !saving && setEditing(null)} - className={`absolute inset-0 pointer-events-auto backdrop-blur-sm ${isLight ? "bg-black/40" : "bg-black/60" - }`} - /> + {mounted && createPortal( + + {editing && ( +
+ {/* Backdrop */} + !saving && setEditing(null)} + className={`absolute inset-0 pointer-events-auto backdrop-blur-sm ${isLight ? "bg-black/40" : "bg-black/60" + }`} + /> - {/* Modal */} - -
-
-

- {editing.id ? "Edit template" : "New template"} -

-

- Use {"{{variable}}"} placeholders to personalize replies. -

-
- -
- -
-
-
-
- - - setEditing((prev) => - prev - ? { ...prev, name: e.target.value } - : prev, - ) - } - placeholder="e.g. Shipping Update" - className={`w-full h-11 px-4 rounded-xl transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500/30 ${isLight - ? "bg-slate-50 border border-slate-200 text-slate-900 placeholder:text-slate-400 focus:border-indigo-500" - : "bg-white/5 border border-white/10 text-white placeholder:text-slate-500 focus:border-indigo-500" - }`} - /> -
- -
-
- - -
-
- - -
-
- -
- - - setEditing((prev) => - prev - ? { - ...prev, - description: e.target.value, - } - : prev, - ) - } - placeholder="Short summary for your team" - className={`w-full h-11 px-4 rounded-xl transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500/30 ${isLight - ? "bg-slate-50 border border-slate-200 text-slate-900 placeholder:text-slate-400 focus:border-indigo-500" - : "bg-white/5 border border-white/10 text-white placeholder:text-slate-500 focus:border-indigo-500" - }`} - /> -
-
- -
-

- Available variables +

+

+ {editing.id ? "Edit template" : "New template"} +

+

+ Use {"{{variable}}"} placeholders to personalize replies.

-
- {[ - { key: "{{customer_name}}", label: "Customer's name" }, - { key: "{{customer_email}}", label: "Customer's email" }, - { key: "{{store_name}}", label: "Store name" }, - { key: "{{ticket_summary}}", label: "Email summary" } - ].map((v) => ( -
- - {v.key} - - {v.label} -
- ))} -
-
- -
-
- - - Markdown & HTML supported - -
-