Skip to content

Commit

Permalink
refactor: form page
Browse files Browse the repository at this point in the history
  • Loading branch information
Kiranism committed Oct 18, 2024
1 parent b94eb78 commit 7274e6f
Show file tree
Hide file tree
Showing 11 changed files with 142 additions and 60 deletions.
25 changes: 23 additions & 2 deletions app/dashboard/product/[productId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
import { Breadcrumbs } from '@/components/breadcrumbs';
import FormCardSkeleton from '@/components/form-card-skeleton';
import PageContainer from '@/components/layout/page-container';
import { ProductViewPage } from '@/sections/product/view';
import { Suspense } from 'react';

export const metadata = {
title: 'Dashboard : Product View'
};

export default function Page() {
return <ProductViewPage />;
const breadcrumbItems = [
{ title: 'Dashboard', link: '/dashboard' },
{ title: 'Product', link: '/dashboard/product' },
{ title: 'Create', link: '/dashboard/product/create' }
];

type PageProps = { params: { productId: string } };

export default function Page({ params }: PageProps) {
return (
<PageContainer scrollable>
<div className="flex-1 space-y-4">
<Breadcrumbs items={breadcrumbItems} />
<Suspense fallback={<FormCardSkeleton />}>
<ProductViewPage productId={params.productId} />
</Suspense>
</div>
</PageContainer>
);
}
3 changes: 1 addition & 2 deletions app/dashboard/product/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ export default async function Page({ searchParams }: pageProps) {
// Allow nested RSCs to access the search params (in a type-safe way)
searchParamsCache.parse(searchParams);

// This key is used for invoke suspense if any of the search params changed (used for filters).
const key = serialize({ ...searchParams });

console.log('key', key);

return (
<PageContainer>
<div className="space-y-4">
Expand Down
52 changes: 52 additions & 0 deletions components/form-card-skeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import { Card, CardContent, CardHeader } from './ui/card';
import { Skeleton } from './ui/skeleton';

export default function FormCardSkeleton() {
return (
<Card className="mx-auto w-full">
<CardHeader>
<Skeleton className="h-8 w-48" /> {/* Title */}
</CardHeader>
<CardContent>
<div className="space-y-8">
{/* Image upload area skeleton */}
<div className="space-y-6">
<Skeleton className="h-4 w-16" /> {/* Label */}
<Skeleton className="h-32 w-full rounded-lg" /> {/* Upload area */}
</div>

{/* Grid layout for form fields */}
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
{/* Product Name field */}
<div className="space-y-2">
<Skeleton className="h-4 w-24" /> {/* Label */}
<Skeleton className="h-10 w-full" /> {/* Input */}
</div>

{/* Category field */}
<div className="space-y-2">
<Skeleton className="h-4 w-20" /> {/* Label */}
<Skeleton className="h-10 w-full" /> {/* Select */}
</div>

{/* Price field */}
<div className="space-y-2">
<Skeleton className="h-4 w-16" /> {/* Label */}
<Skeleton className="h-10 w-full" /> {/* Input */}
</div>
</div>

{/* Description field */}
<div className="space-y-2">
<Skeleton className="h-4 w-24" /> {/* Label */}
<Skeleton className="h-32 w-full" /> {/* Textarea */}
</div>

{/* Submit button */}
<Skeleton className="h-10 w-28" />
</div>
</CardContent>
</Card>
);
}
2 changes: 1 addition & 1 deletion components/ui/dropdown-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className
)}
Expand Down
1 change: 1 addition & 0 deletions components/ui/table/data-table.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client';
import { Button } from '@/components/ui/button';
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area';
import {
Expand Down
25 changes: 25 additions & 0 deletions constants/mock-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,31 @@ export const fakeProducts = {
limit,
products: paginatedProducts
};
},

// Get a specific product by its ID
async getProductById(id: number) {
await delay(2000); // Simulate a delay

// Find the product by its ID
const product = this.records.find((product) => product.id === id);

if (!product) {
return {
success: false,
message: `Product with ID ${id} not found`
};
}

// Mock current time
const currentTime = new Date().toISOString();

return {
success: true,
time: currentTime,
message: `Product with ID ${id} found`,
product
};
}
};

Expand Down
32 changes: 13 additions & 19 deletions sections/product/product-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from '@/components/ui/select';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { FileUploader } from '@/components/file-uploader';
import { Product } from '@/constants/mock-api';

const MAX_FILE_SIZE = 5000000;
const ACCEPTED_IMAGE_TYPES = [
Expand All @@ -49,26 +50,24 @@ const formSchema = z.object({
name: z.string().min(2, {
message: 'Product name must be at least 2 characters.'
}),
category: z.array(z.string()).refine((value) => value.some((item) => item), {
message: 'You have to select at least one category.'
}),
price: z.string().refine((value) => !isNaN(parseFloat(value)), {
message: 'Price must be a valid number.'
}),
category: z.string(),
price: z.number(),
description: z.string().min(10, {
message: 'Description must be at least 10 characters.'
})
});

export default function ProductForm() {
export default function ProductForm({ initialData }: { initialData: Product }) {
const defaultValues = {
name: initialData?.name || '',
category: initialData?.category || '',
price: initialData?.price || 0,
description: initialData?.description || ''
};

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
name: '',
category: [],
price: '',
description: ''
}
values: defaultValues
});

function onSubmit(values: z.infer<typeof formSchema>) {
Expand Down Expand Up @@ -132,9 +131,7 @@ export default function ProductForm() {
<FormItem>
<FormLabel>Category</FormLabel>
<Select
onValueChange={(value) =>
field.onChange([...field.value, value])
}
onValueChange={(value) => field.onChange(value)}
value={field.value[field.value.length - 1]}
>
<FormControl>
Expand All @@ -152,9 +149,6 @@ export default function ProductForm() {
</SelectItem>
</SelectContent>
</Select>
<FormDescription>
Selected categories: {field.value.join(', ')}
</FormDescription>
<FormMessage />
</FormItem>
)}
Expand Down
2 changes: 1 addition & 1 deletion sections/product/product-tables/cell-action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const CellAction: React.FC<CellActionProps> = ({ data }) => {
<DropdownMenuLabel>Actions</DropdownMenuLabel>

<DropdownMenuItem
onClick={() => router.push(`/dashboard/user/${data.id}`)}
onClick={() => router.push(`/dashboard/product/${data.id}`)}
>
<Edit className="mr-2 h-4 w-4" /> Update
</DropdownMenuItem>
Expand Down
15 changes: 0 additions & 15 deletions sections/product/product-tables/index.tsx

This file was deleted.

11 changes: 9 additions & 2 deletions sections/product/view/product-listing.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Product } from '@/constants/data';
import { fakeProducts } from '@/constants/mock-api';
import { searchParamsCache } from '@/lib/searchparams';
import ProductTable from '../product-tables';
import { columns } from '../product-tables/columns';
import { DataTable as ProductTable } from '@/components/ui/table/data-table';

type ProductListingPage = {};

Expand All @@ -23,5 +24,11 @@ export default async function ProductListingPage({}: ProductListingPage) {
const totalProducts = data.total_products;
const products: Product[] = data.products;

return <ProductTable data={products} totalData={totalProducts} />;
return (
<ProductTable
columns={columns}
data={products}
totalItems={totalProducts}
/>
);
}
34 changes: 16 additions & 18 deletions sections/product/view/product-view-page.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import { Breadcrumbs } from '@/components/breadcrumbs';
import { ScrollArea } from '@/components/ui/scroll-area';
import React from 'react';
import { fakeProducts, Product } from '@/constants/mock-api';
import ProductForm from '../product-form';
import PageContainer from '@/components/layout/page-container';
import { notFound } from 'next/navigation';

const breadcrumbItems = [
{ title: 'Dashboard', link: '/dashboard' },
{ title: 'Product', link: '/dashboard/product' },
{ title: 'Create', link: '/dashboard/product/create' }
];
type TProductViewPageProps = {
productId: string;
};

export default function ProductViewPage() {
return (
<PageContainer scrollable>
<div className="flex-1 space-y-4">
<Breadcrumbs items={breadcrumbItems} />
<ProductForm />
</div>
</PageContainer>
);
export default async function ProductViewPage({
productId
}: TProductViewPageProps) {
const data = await fakeProducts.getProductById(Number(productId));
const product = data.product as Product;

if (!product) {
return notFound();
}

return <ProductForm initialData={product} />;
}

0 comments on commit 7274e6f

Please sign in to comment.