Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement edit and delete feature for products #168

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions apps/cms/src/@types/Product.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export class Product {
constructor(
public id = '',
public name = '',
public colors= '',
public sizes = '',
public price = '',
public category = ''
) { }
}
143 changes: 92 additions & 51 deletions apps/cms/src/admin/views/MerchProducts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,51 @@ import { Button } from "payload/components/elements";
import { AdminViewComponent } from "payload/config";
import ViewTemplate from "./ViewTemplate";
import { Column } from "payload/dist/admin/components/elements/Table/types";
import { Product } from "../../@types/Product";
import ProductsApi from "../../apis/products.api";
import { RenderCellFactory } from "../utils/RenderCellFactory";
import SortedColumn from "../utils/SortedColumn";
import { Table } from "payload/dist/admin/components/elements/Table";
import { Product } from "types";
import ProductsApi from "../../apis/products.api";
import { useHistory } from 'react-router-dom';
import { toast } from "react-toastify";
import { prettifyKey } from "../../utilities/prettifyKey";

const MerchProducts: AdminViewComponent = ({ user, canAccessAdmin }) => {
// Get data from API
const [data, setData] = useState<Product[]>(null);
const history = useHistory();
useEffect(() => {
ProductsApi.getProducts()
.then((res: Product[]) => setData(res))
.catch((error) => console.log(error));
}, []);
const fetchProducts = async () => {
try {
const products: Product[] = await ProductsApi.getProducts();
setData(products);
} catch (error) {
setData([]);
if (error instanceof Error) {
toast.error(error.message);
} else {
toast.error("An unknown error occurred");
}
}
};
// eslint-disable-next-line @typescript-eslint/no-floating-promises
fetchProducts();
}, []);

// Do not load table until we receive the data
if (data == null) {
return <div> Loading... </div>;
}

const tableCols = new Array<Column>();
if (data && data.length > 0) {
const sampleProduct = data[0];
const keys = Object.keys(sampleProduct);
for (const key of keys) {
if (data && data.length > 0) {
for (const key of Object.keys(new Product())) {
const renderCellComponent = RenderCellFactory.get(data[0], key);
const renderCell: React.FC<{ children?: React.ReactNode }> =
RenderCellFactory.get(sampleProduct, key);
renderCellComponent instanceof Promise
? renderCellComponent
: renderCellComponent;

const col: Column = {
accessor: key,
components: {
Expand All @@ -43,53 +60,78 @@ const MerchProducts: AdminViewComponent = ({ user, canAccessAdmin }) => {
),
renderCell: renderCell,
},
label: "",
name: "",
label: prettifyKey(key), // Assigning the prettified key to the label
name: key,
active: true,
};

tableCols.push(col);
}
}

const editColumn: Column = {
accessor: "edit",
components: {
Heading: <div>Edit</div>,
renderCell: (data: Product) => (
<Button onClick={() => handleEdit(data)}>Edit</Button>
),
},
label: "Edit",
name: "edit",
active: true,
};

const editColumn: Column = {
accessor: "edit",
components: {
Heading: <div>Edit</div>,
renderCell: ({ children }) => (
<Button onClick={() => handleEdit(children as string)}>Edit</Button>
),
},
label: "Edit",
name: "edit",
active: true,
};
tableCols.push(editColumn);

tableCols.push(editColumn);
const handleEdit = (data: Product) => {
const productId = data.id;
// Navigate to the edit page
history.push(`/admin/collections/products/${productId}`);
};

const deleteColumn: Column = {
accessor: "delete",
components: {
Heading: <div>Delete</div>,
renderCell: ({ children }) => (
<Button onClick={() => handleDelete(children as string)}>Delete</Button>
),
},
label: "Delete",
name: "delete",
active: true,
};
const deleteColumn: Column = {
accessor: "delete",
components: {
Heading: <div>Delete</div>,
renderCell: (data: Product) => (
<Button onClick={() => {
void (async () => {
await handleDelete(data);
})();
}}
>Delete</Button>
),
},
label: "Delete",
name: "delete",
active: true,
};

tableCols.push(deleteColumn);
tableCols.push(deleteColumn);

const handleEdit = (orderId: string) => {
console.log(`Dummy. Order ID: ${orderId}`);
};

const handleDelete = (orderId: string) => {
console.log(`Dummy. Order ID: ${orderId}`);
};

console.log(tableCols);
const handleDelete = async (data: Product) => {
const productId = data.id;
try {
// Show a confirmation prompt (optional)
if (window.confirm('Are you sure you want to delete this product?')) {
// Call the delete API
await ProductsApi.deleteProduct(productId);

// After deletion, update the data state to reflect the removal
setData((prevData) => prevData.filter((product) => product.id !== productId));

// Optionally, show a success message
toast.success('Product deleted successfully');
}
} catch (error) {
if (error instanceof Error) {
toast.error(error.message);
} else {
toast.error('An unknown error occurred');
}
}
};
}

return (
<ViewTemplate
Expand All @@ -102,8 +144,7 @@ const MerchProducts: AdminViewComponent = ({ user, canAccessAdmin }) => {
<Button el="link" to={"/admin"} buttonStyle="primary">
Go to Main Admin View
</Button>

<Table data={data} columns={tableCols} />
{data && data.length > 0 && <Table data={data} columns={tableCols} />}
</ViewTemplate>
);
};
Expand Down
78 changes: 21 additions & 57 deletions apps/cms/src/apis/products.api.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Zhixuan, thanks for your work. Could you revert the changes on this file for now? The API changes for Product should addressed in a separate ticket

Original file line number Diff line number Diff line change
@@ -1,64 +1,28 @@
import { Product } from "types";
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/require-await */
import { Product } from "../@types/Product";
// todo turn into real api
class ProductsApi {
// eslint-disable-next-line @typescript-eslint/require-await
async getProducts(): Promise<any> {
const res: Product[] = [
{
id: "1",
name: "product1",
price: 1000,
images: [
"https://i.kym-cdn.com/entries/icons/original/000/033/421/cover2.jpg",
"https://i.pinimg.com/474x/c0/f9/f1/c0f9f10a0061a8dd1080d7d9e560579c.jpg",
],
sizes: ["s", "m", "l", "xl"],
category: "shirt",
is_available: true,
colors: ["black,white,blue"],
stock: {
black: { S: 10, M: 15, L: 20, XL: 5 },
white: { S: 12, M: 17, L: 22, XL: 7 },
blue: { S: 8, M: 13, L: 18, XL: 3 }
},
},
{
id: "2",
name: "product2",
price: 2000,
images: [
"https://i.kym-cdn.com/photos/images/newsfeed/002/164/493/b8b.jpg",
"https://i.kym-cdn.com/entries/icons/original/000/033/421/cover2.jpg",
],
sizes: ["s", "m"],
category: "sweater",
is_available: true,
colors: ["blue"],
stock: {
blue: { S: 8, M: 13, L: 18, XL: 3 }
},
},
{
id: "3",
name: "product3",
price: 3000,
images: [
"https://i.kym-cdn.com/entries/icons/original/000/033/421/cover2.jpg",
"https://i.kym-cdn.com/photos/images/newsfeed/002/164/493/b8b.jpg",
"https://i.pinimg.com/474x/c0/f9/f1/c0f9f10a0061a8dd1080d7d9e560579c.jpg",
],
sizes: ["xs", "s", "m", "l"],
category: "hat",
is_available: false,
colors: ["white"],
stock: {
white: { S: 12, M: 17, L: 22, XL: 7 }
},
},
];
async getProducts(): Promise<Product[]> {
const req = await fetch(`${process.env.PAYLOAD_PUBLIC_SERVER_URL}/api/products`);
const products = await req.json();
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return products?.docs as Product[];
}

return res;
async deleteProduct(id: string): Promise<void> {
try {
const response = await fetch(`${process.env.PAYLOAD_PUBLIC_SERVER_URL}/api/products/${id}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error('Failed to delete product');
}
} catch (error) {
throw new Error(error instanceof Error ? error.message : 'An unknown error occurred');
}
}

}

export default new ProductsApi();
5 changes: 5 additions & 0 deletions packages/eslint-config-custom/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ module.exports = {
],
plugins: ["@typescript-eslint"],
rules: {
"@typescript-eslint/no-misused-promises": ["error", {
"checksVoidReturn": {
"attributes": false
}
}],
"@typescript-eslint/no-empty-interface": ["off", "never"],
"@typescript-eslint/no-unused-vars": [
"error",
Expand Down
Loading