fix: resolve CI and workspace lint errors (admin + storefront)
Some checks failed
CI / Lint, Typecheck & Test (push) Failing after 2m11s
Some checks failed
CI / Lint, Typecheck & Test (push) Failing after 2m11s
- Allow require() in next.config.js (eslint-disable) for both apps - Replace all catch (e: any) with catch (e: unknown) and proper error handling - Remove no-explicit-any: add types (PreviewProduct, ProductImage, Id<addresses>, ProductDetailReview, error payloads) across admin and storefront - Admin: use next/image in ImageUploadSection and ProductImageCarousel; remove unused layout fonts and sidebar imports; fix products page useMemo deps - Storefront: use Link for /sign-in in header actions; fix useAddressMutations and product detail types; remove unused imports/vars and fix useMemo deps Made-with: Cursor
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-require-imports -- Next.js config commonly uses require */
|
||||
const path = require("path");
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
|
||||
@@ -5,7 +5,10 @@ import { useQuery } from "convex/react"
|
||||
import { api } from "../../../../../../convex/_generated/api"
|
||||
import type { Id } from "../../../../../../convex/_generated/dataModel"
|
||||
import { ProductSearchSection } from "../../../components/shared/ProductSearchSection"
|
||||
import { ProductImageCarousel } from "../../../components/images/ProductImageCarousel"
|
||||
import {
|
||||
ProductImageCarousel,
|
||||
type ProductImage,
|
||||
} from "../../../components/images/ProductImageCarousel"
|
||||
import { ImageUploadSection } from "../../../components/images/ImageUploadSection"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
@@ -79,7 +82,7 @@ export default function ImagesPage() {
|
||||
</div>
|
||||
) : (
|
||||
<ProductImageCarousel
|
||||
images={images as any}
|
||||
images={images as ProductImage[]}
|
||||
onAddMore={() => setShowUpload(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -53,8 +53,8 @@ export default function EditProductPage() {
|
||||
categoryId: payload.categoryId as Id<"categories">,
|
||||
})
|
||||
router.push("/products")
|
||||
} catch (e: any) {
|
||||
setError(e?.message ?? "Failed to save product. Please try again.")
|
||||
} catch (e: unknown) {
|
||||
setError(e instanceof Error ? e.message : "Failed to save product. Please try again.")
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
}
|
||||
@@ -64,8 +64,8 @@ export default function EditProductPage() {
|
||||
try {
|
||||
await archiveProduct({ id: productId })
|
||||
router.push("/products")
|
||||
} catch (e: any) {
|
||||
setError(e?.message ?? "Failed to archive product.")
|
||||
} catch (e: unknown) {
|
||||
setError(e instanceof Error ? e.message : "Failed to archive product.")
|
||||
setIsArchiving(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ export default function NewProductPage() {
|
||||
categoryId: payload.categoryId as Id<"categories">,
|
||||
})
|
||||
router.push("/products")
|
||||
} catch (e: any) {
|
||||
setError(e?.message ?? "Failed to create product. Please try again.")
|
||||
} catch (e: unknown) {
|
||||
setError(e instanceof Error ? e.message : "Failed to create product. Please try again.")
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,11 +199,14 @@ export default function ProductsPage() {
|
||||
? searchResults === undefined
|
||||
: listStatus === "LoadingFirstPage"
|
||||
|
||||
const rawProducts = isSearching ? (searchResults ?? []) : listResults
|
||||
const rawProducts = useMemo(
|
||||
() => (isSearching ? (searchResults ?? []) : listResults),
|
||||
[isSearching, searchResults, listResults],
|
||||
)
|
||||
|
||||
const products = useMemo(() => {
|
||||
if (!sortField) return rawProducts
|
||||
return [...rawProducts].sort((a: any, b: any) => {
|
||||
return [...rawProducts].sort((a: PreviewProduct, b: PreviewProduct) => {
|
||||
const aVal: string =
|
||||
sortField === "name"
|
||||
? (a.name ?? "")
|
||||
@@ -235,8 +238,8 @@ export default function ProductsPage() {
|
||||
setVisibleCols((prev) => ({ ...prev, [key]: checked }))
|
||||
}
|
||||
|
||||
function openPreview(product: any) {
|
||||
setPreviewProduct(product as PreviewProduct)
|
||||
function openPreview(product: PreviewProduct) {
|
||||
setPreviewProduct(product)
|
||||
setPreviewOpen(true)
|
||||
}
|
||||
|
||||
@@ -374,7 +377,7 @@ export default function ProductsPage() {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
products.map((product: any) => {
|
||||
products.map((product: PreviewProduct) => {
|
||||
const statusCfg =
|
||||
STATUS_CONFIG[product.status as keyof typeof STATUS_CONFIG]
|
||||
return (
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
import type { Metadata } from "next";
|
||||
import { DM_Sans, Geist, Geist_Mono } from "next/font/google";
|
||||
import { DM_Sans } from "next/font/google";
|
||||
import { ClerkProvider } from "@clerk/nextjs";
|
||||
import { ConvexClientProvider } from "@repo/convex";
|
||||
import { Toaster } from "sonner";
|
||||
import "./globals.css";
|
||||
|
||||
const dmSans = DM_Sans({subsets:['latin'],variable:'--font-sans'});
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
const dmSans = DM_Sans({ subsets: ["latin"], variable: "--font-sans" });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useRef } from "react"
|
||||
import Image from "next/image"
|
||||
import { useMutation } from "convex/react"
|
||||
import { api } from "../../../../../convex/_generated/api"
|
||||
import type { Id } from "../../../../../convex/_generated/dataModel"
|
||||
@@ -61,8 +62,8 @@ export function ImageUploadSection({
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const errJson = await res.json().catch(() => ({}))
|
||||
throw new Error((errJson as any).detail ?? "Background removal failed")
|
||||
const errJson = (await res.json().catch(() => ({}))) as { detail?: string }
|
||||
throw new Error(errJson.detail ?? "Background removal failed")
|
||||
}
|
||||
|
||||
const blob = await res.blob()
|
||||
@@ -100,8 +101,8 @@ export function ImageUploadSection({
|
||||
})
|
||||
|
||||
if (!uploadRes.ok) {
|
||||
const errJson = await uploadRes.json().catch(() => ({}))
|
||||
throw new Error((errJson as any).error ?? "Upload failed")
|
||||
const errJson = (await uploadRes.json().catch(() => ({}))) as { error?: string }
|
||||
throw new Error(errJson.error ?? "Upload failed")
|
||||
}
|
||||
|
||||
const { url } = await uploadRes.json()
|
||||
@@ -188,8 +189,14 @@ export function ImageUploadSection({
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-medium text-muted-foreground">Original</p>
|
||||
{localUrl && (
|
||||
<div className="aspect-square overflow-hidden rounded-md border bg-muted">
|
||||
<img src={localUrl} alt="Original" className="h-full w-full object-contain" />
|
||||
<div className="relative aspect-square overflow-hidden rounded-md border bg-muted">
|
||||
<Image
|
||||
src={localUrl}
|
||||
alt="Original"
|
||||
fill
|
||||
className="object-contain"
|
||||
unoptimized
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -199,11 +206,13 @@ export function ImageUploadSection({
|
||||
{isProcessing ? (
|
||||
<Skeleton className="aspect-square w-full rounded-md" />
|
||||
) : processedUrl ? (
|
||||
<div className="aspect-square overflow-hidden rounded-md border bg-[conic-gradient(#e5e7eb_25%,_#fff_25%,_#fff_50%,_#e5e7eb_50%,_#e5e7eb_75%,_#fff_75%)] bg-[length:16px_16px]">
|
||||
<img
|
||||
<div className="relative aspect-square overflow-hidden rounded-md border bg-[conic-gradient(#e5e7eb_25%,_#fff_25%,_#fff_50%,_#e5e7eb_50%,_#e5e7eb_75%,_#fff_75%)] bg-[length:16px_16px]">
|
||||
<Image
|
||||
src={processedUrl}
|
||||
alt="Background removed"
|
||||
className="h-full w-full object-contain"
|
||||
fill
|
||||
className="object-contain"
|
||||
unoptimized
|
||||
/>
|
||||
</div>
|
||||
) : processingError ? (
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import Image from "next/image"
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
@@ -35,7 +36,7 @@ import { Button } from "@/components/ui/button"
|
||||
import { HugeiconsIcon } from "@hugeicons/react"
|
||||
import { Delete02Icon, ImageAdd01Icon, DragDropVerticalIcon } from "@hugeicons/core-free-icons"
|
||||
|
||||
interface ProductImage {
|
||||
export interface ProductImage {
|
||||
_id: Id<"productImages">
|
||||
url: string
|
||||
alt?: string
|
||||
@@ -105,11 +106,12 @@ function SortableImageCard({
|
||||
|
||||
{/* Row 2 in DOM → Row 2 visually (middle stays middle) */}
|
||||
{/* Image — appears in CENTER */}
|
||||
<div className="w-28 aspect-square rotate-180 overflow-hidden rounded-md border bg-muted">
|
||||
<img
|
||||
<div className="relative w-28 aspect-square rotate-180 overflow-hidden rounded-md border bg-muted">
|
||||
<Image
|
||||
src={image.url}
|
||||
alt={image.alt ?? "Product image"}
|
||||
className="h-full w-full object-contain"
|
||||
fill
|
||||
className="object-contain"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ export function ProductSearchSection({ onSelect, onClear, selectedId }: ProductS
|
||||
|
||||
{!isLoading && results && results.length > 0 && (
|
||||
<ul className="max-w-sm divide-y rounded-md border">
|
||||
{results.map((product: any) => (
|
||||
{results.map((product: { _id: string; name: string }) => (
|
||||
<li key={product._id}>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
import { Store01Icon } from "@hugeicons/core-free-icons";
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
|
||||
@@ -24,8 +24,8 @@ export function AcceptReturnButton({ orderId }: Props) {
|
||||
} else {
|
||||
toast.error((result as { success: false; code: string; message: string }).message)
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast.error(e?.message ?? "Failed to accept return.")
|
||||
} catch (e: unknown) {
|
||||
toast.error(e instanceof Error ? e.message : "Failed to accept return.")
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ export function CreateLabelButton({ orderId }: Props) {
|
||||
} else {
|
||||
toast.error((result as { success: false; code: string; message: string }).message)
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast.error(e?.message ?? "Failed to create shipping label.")
|
||||
} catch (e: unknown) {
|
||||
toast.error(e instanceof Error ? e.message : "Failed to create shipping label.")
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ export function IssueRefundButton({ orderId, total, currency }: Props) {
|
||||
await issueRefund({ orderId })
|
||||
toast.success("Refund issued.")
|
||||
setOpen(false)
|
||||
} catch (e: any) {
|
||||
toast.error(e?.message ?? "Failed to issue refund.")
|
||||
} catch (e: unknown) {
|
||||
toast.error(e instanceof Error ? e.message : "Failed to issue refund.")
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ export function MarkReturnReceivedButton({ orderId }: Props) {
|
||||
await markReceived({ id: orderId })
|
||||
toast.success("Return marked as received.")
|
||||
setOpen(false)
|
||||
} catch (e: any) {
|
||||
toast.error(e?.message ?? "Failed to mark return as received.")
|
||||
} catch (e: unknown) {
|
||||
toast.error(e instanceof Error ? e.message : "Failed to mark return as received.")
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
@@ -62,8 +62,8 @@ export function UpdateStatusDialog({ orderId, currentStatus }: Props) {
|
||||
const label = ORDER_STATUS_CONFIG[selectedStatus as OrderStatus]?.label
|
||||
toast.success(`Status updated to ${label ?? selectedStatus}`)
|
||||
setOpen(false)
|
||||
} catch (e: any) {
|
||||
toast.error(e?.message ?? "Failed to update status.")
|
||||
} catch (e: unknown) {
|
||||
toast.error(e instanceof Error ? e.message : "Failed to update status.")
|
||||
} finally {
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ export function ProductSearchSection({ onSelect, onClear, selectedId }: ProductS
|
||||
|
||||
{!isLoading && results && results.length > 0 && (
|
||||
<ul className="max-w-sm divide-y rounded-md border">
|
||||
{results.map((product: any) => (
|
||||
{results.map((product: { _id: string; name: string }) => (
|
||||
<li key={product._id}>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -68,8 +68,8 @@ export function CreateVariantDialog({
|
||||
|
||||
toast.success(`Variant "${values.name}" created`)
|
||||
onOpenChange(false)
|
||||
} catch (e: any) {
|
||||
toast.error(e?.message ?? "Failed to create variant")
|
||||
} catch (e: unknown) {
|
||||
toast.error(e instanceof Error ? e.message : "Failed to create variant")
|
||||
} finally {
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
|
||||
@@ -69,8 +69,8 @@ export function EditVariantDialog({
|
||||
|
||||
toast.success(`"${values.name}" updated`)
|
||||
onOpenChange(false)
|
||||
} catch (e: any) {
|
||||
toast.error(e?.message ?? "Failed to update variant")
|
||||
} catch (e: unknown) {
|
||||
toast.error(e instanceof Error ? e.message : "Failed to update variant")
|
||||
} finally {
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
|
||||
@@ -103,8 +103,8 @@ function VariantActionsMenu({
|
||||
try {
|
||||
await updateVariant({ id: variant._id, isActive: !variant.isActive })
|
||||
toast.success(`"${variant.name}" ${variant.isActive ? "deactivated" : "activated"}`)
|
||||
} catch (e: any) {
|
||||
toast.error(e?.message ?? "Failed to update variant")
|
||||
} catch (e: unknown) {
|
||||
toast.error(e instanceof Error ? e.message : "Failed to update variant")
|
||||
} finally {
|
||||
setIsToggling(false)
|
||||
}
|
||||
@@ -116,8 +116,8 @@ function VariantActionsMenu({
|
||||
await deleteVariant({ id: variant._id })
|
||||
toast.success(`"${variant.name}" deleted`)
|
||||
setDeleteOpen(false)
|
||||
} catch (e: any) {
|
||||
toast.error(e?.message ?? "Failed to delete variant")
|
||||
} catch (e: unknown) {
|
||||
toast.error(e instanceof Error ? e.message : "Failed to delete variant")
|
||||
} finally {
|
||||
setIsDeleting(false)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user