Skip to content

Commit

Permalink
Added merch products page
Browse files Browse the repository at this point in the history
  • Loading branch information
limivann committed Jan 9, 2024
1 parent 3dba1ee commit 43dba55
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 57 deletions.
3 changes: 3 additions & 0 deletions apps/cms/src/@types/IProduct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Product } from "./Product";

export interface IProduct extends Product {}
24 changes: 24 additions & 0 deletions apps/cms/src/@types/Product.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export class Product {
constructor(
public id: string = "",
public name: string = "",
public price: number = 0, // Price of the product in cents ($19 = 1900).
public images: string[] = [], // URL of the images.
public sizes: ProductSizeTypes[] = [],
public productCategory: ProductCategoryType = "",
public isAvailable?: boolean
) {}
}

export type ProductSizeTypes =
| "3xs"
| "xxs"
| "xs"
| "s"
| "m"
| "l"
| "xl"
| "2xl"
| "3xl";

export type ProductCategoryType = string;
100 changes: 81 additions & 19 deletions apps/cms/src/admin/utils/RenderCellFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,104 @@ import React from "react";
import payload from "payload";

export class RenderCellFactory {

static get(element: unknown, key: string) {
console.log(key)
console.log("key", key);
if (element[key] == undefined) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
payload.logger.error(`Attribute ${key} cannot be found in element ${element.toString()}`);
payload.logger.error(
`Attribute ${key} cannot be found in element ${element.toString()}`
);
return null;
}

const isImageUrl = new RegExp("http(s?):\\/\\/.*.(jpg|png|jpeg)$");

if (Array.isArray(element[key])) {
if (
element[key].every((item: string) =>
isImageUrl.test((item as string).toString())
)
) {
// If the element is an array, render images accordingly
const ImagesComponent: React.FC<{ children?: React.ReactNode[] }> = ({
children,
}) => (
<span>
{children.map((imageUrl: string, index: number) => (
<img
key={index}
src={imageUrl}
alt={`image ${index + 1}`}
style={{ paddingTop: 10, paddingBottom: 10 }}
/>
))}
</span>
);
const ImagesComponentCell = (row, data) => (
<ImagesComponent>{data}</ImagesComponent>
);
return ImagesComponentCell;
} else {
// If the element is an array of strings, render them
const StringsComponent: React.FC<{ children?: React.ReactNode[] }> = ({
children,
}) => (
<span>
{children.map((text: string, index: number) => (
<span key={index}>
{index > 0 && ", "} {text}
</span>
))}
</span>
);
const StringsComponentCell = (row, data) => (
<StringsComponent>{data}</StringsComponent>
);
return StringsComponentCell;
}
}

if (isImageUrl.test((element[key] as string).toString())) {
const ImageComponent: React.FC<{children?: React.ReactNode}> = ({ children }) => (
const ImageComponent: React.FC<{ children?: React.ReactNode }> = ({
children,
}) => (
<span>
<img src={children.toString()} alt="image of object"/>
<img src={children.toString()} alt="image of object" />
</span>
);
const ImageComponentCell = (row, data) => <ImageComponent>{data}</ImageComponent>;
const ImageComponentCell = (row, data) => (
<ImageComponent>{data}</ImageComponent>
);
return ImageComponentCell;
}

if (typeof element[key] == 'object') {
const DateComponent: React.FC<{children?: React.ReactNode}> = ({ children }) => (
<span>
{(children as unknown as Date).toDateString()}
</span>
if (typeof element[key] == "object") {
const DateComponent: React.FC<{ children?: React.ReactNode }> = ({
children,
}) => <span>{(children as unknown as Date).toDateString()}</span>;
const DateComponentCell = (row, data) => (
<DateComponent>{data}</DateComponent>
);
return DateComponentCell;
}

if (typeof element[key] === "boolean") {
// If the element is a boolean, render "Yes" or "No"
const BooleanComponent: React.FC<{ children?: React.ReactNode }> = ({
children,
}) => <span>{children ? "Yes" : "No"}</span>;
const BooleanComponentCell = (row, data) => (
<BooleanComponent>{data}</BooleanComponent>
);
const DateComponentCell = (row, data) => <DateComponent>{data}</DateComponent>;
return DateComponentCell
return BooleanComponentCell;
}

const TextComponent: React.FC<{children?: React.ReactNode}> = ({ children }) => (
<span>
{children}
</span>
const TextComponent: React.FC<{ children?: React.ReactNode }> = ({
children,
}) => <span>{children}</span>;
const TextComponentCell = (row, data) => (
<TextComponent>{data}</TextComponent>
);
const TextComponentCell = (row, data) => <TextComponent>{data}</TextComponent>;
return TextComponentCell
return TextComponentCell;
}
}
62 changes: 57 additions & 5 deletions apps/cms/src/admin/views/MerchProducts.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,63 @@
import React from "react";
import React, { useEffect, useState } from "react";
import { Button } from "payload/components/elements";
import { AdminView } from "payload/config";
import ViewTemplate from "./ViewTemplate";
import { Column } from "payload/dist/admin/components/elements/Table/types";
import { RenderCellFactory } from "../utils/RenderCellFactory";
import SortedColumn from "../utils/SortedColumn";
import { Table } from "payload/dist/admin/components/elements/Table";
import { Product } from "../../@types/Product";
import { IProduct } from "../../@types/IProduct";
import ProductsApi from "../../apis/products.api";

const MerchProducts: AdminView = ({ user, canAccessAdmin }) => {
// Get data from API
const [data, setData] = useState<IProduct[]>(null);
useEffect(() => {
ProductsApi.getProducts()
.then((res: IProduct[]) => setData(res))
.catch((error) => console.log(error));
}, []);

// Output human-readable table headers based on the attribute names from the API
function prettifyKey(str: string): string {
let res = "";
for (const i of str.split("_")) {
res += i.charAt(0).toUpperCase() + i.slice(1) + " ";
}
return res;
}

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

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

const col: Column = {
accessor: key,
components: {
Heading: (
<SortedColumn
label={prettifyKey(key)}
name={key}
data={data as never[]}
/>
),
renderCell: renderCell,
},
label: "",
name: "",
active: true,
};
tableCols.push(col);
}
console.log(tableCols);

return (
<ViewTemplate
user={user}
Expand All @@ -12,13 +66,11 @@ const MerchProducts: AdminView = ({ user, canAccessAdmin }) => {
keywords=""
title="Merchandise Products"
>
<p>
Here is a custom route that was added in the Payload config. It uses the
Default Template, so the sidebar is rendered.
</p>
<Button el="link" to={"/admin"} buttonStyle="primary">
Go to Main Admin View
</Button>

<Table data={data} columns={tableCols} />
</ViewTemplate>
);
};
Expand Down
60 changes: 27 additions & 33 deletions apps/cms/src/admin/views/MerchSales.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from "react";
import { Button } from 'payload/components/elements';
import { AdminView } from 'payload/config';
import { Button } from "payload/components/elements";
import { AdminView } from "payload/config";
import ViewTemplate from "./ViewTemplate";
import { Column } from "payload/dist/admin/components/elements/Table/types";
import { Order } from "../../@types/Order";
Expand All @@ -11,34 +11,32 @@ import SortedColumn from "../utils/SortedColumn";
import { Table } from "payload/dist/admin/components/elements/Table";

const MerchSales: AdminView = ({ user, canAccessAdmin }) => {

// Get data from API
const [data, setData] = useState<IOrder[]>(null);
useEffect(() => {
OrdersApi.getOrders()
.then(
(res: IOrder[]) => setData(res)
)
.then((res: IOrder[]) => setData(res))
.catch((error) => console.log(error));
}, []);

// Output human-readable table headers based on the attribute names from the API
function prettifyKey(str: string): string {
let res = "";
for (const i of str.split('_')) {
res += i.charAt(0).toUpperCase() + i.slice(1) + " "
for (const i of str.split("_")) {
res += i.charAt(0).toUpperCase() + i.slice(1) + " ";
}
return res;
}

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

const tableCols = new Array<Column>();
for (const key of Object.keys(new Order())) {
const renderCell: React.FC<{children?: React.ReactNode}> = RenderCellFactory.get(data[0], key);
const renderCell: React.FC<{ children?: React.ReactNode }> =
RenderCellFactory.get(data[0], key);

const col: Column = {
accessor: key,
Expand All @@ -47,38 +45,34 @@ const MerchSales: AdminView = ({ user, canAccessAdmin }) => {
<SortedColumn
label={prettifyKey(key)}
name={key}
data={data as never[]}/>
data={data as never[]}
/>
),
renderCell: renderCell
renderCell: renderCell,
},
label: "",
name: "",
active: true
}
active: true,
};
tableCols.push(col);
}
console.log(tableCols)
console.log(tableCols);

return (
<ViewTemplate
user={user}
canAccessAdmin={canAccessAdmin}
description=""
keywords=""
title="Merchandise Sales"
>
<Button
el="link"
to={"/admin"}
buttonStyle="primary"
<ViewTemplate
user={user}
canAccessAdmin={canAccessAdmin}
description=""
keywords=""
title="Merchandise Sales"
>
Go to Main Admin View
</Button>

<Table data={data} columns={tableCols}/>
<Button el="link" to={"/admin"} buttonStyle="primary">
Go to Main Admin View
</Button>

</ViewTemplate>
<Table data={data} columns={tableCols} />
</ViewTemplate>
);
};

export default MerchSales
export default MerchSales;
51 changes: 51 additions & 0 deletions apps/cms/src/apis/products.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { IProduct } from "../@types/IProduct";

// todo turn into real api
class ProductsApi {
// eslint-disable-next-line @typescript-eslint/require-await
async getProducts(): Promise<any> {
const res: IProduct[] = [
{
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"],
productCategory: "shirt",
isAvailable: true,
},
{
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"],
productCategory: "sweater",
isAvailable: true,
},
{
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"],
productCategory: "hat",
isAvailable: false,
},
];

return res;
}
}

export default new ProductsApi();

0 comments on commit 43dba55

Please sign in to comment.