diff --git a/apps/core/app/dashboard/products/_components/table/product-table-head.tsx b/apps/core/app/dashboard/products/_components/table/product-table-head.tsx new file mode 100644 index 00000000..1f173328 --- /dev/null +++ b/apps/core/app/dashboard/products/_components/table/product-table-head.tsx @@ -0,0 +1,31 @@ +import { Table, TableBody, TableHeader } from "@repo/ui/components/table"; +import { ProdcutTableHead } from "./product-table-head/product-table-head"; +import { ProductTableRow } from "./product-table-row/product-table-row"; +import { ProductTablePages } from "./product-table-pages/product-table-pages"; + +const ProductTable = () => { + return ( + <> + + + + + + + +
+ + + ); +}; +export { ProductTable }; diff --git a/apps/core/app/dashboard/products/_components/table/product-table-head/product-table-head.tsx b/apps/core/app/dashboard/products/_components/table/product-table-head/product-table-head.tsx new file mode 100644 index 00000000..4957240a --- /dev/null +++ b/apps/core/app/dashboard/products/_components/table/product-table-head/product-table-head.tsx @@ -0,0 +1,46 @@ +import { TableHead, TableRow } from "@repo/ui/components/table"; +import Typography from "@repo/ui/components/typography"; + +const ProdcutTableHead = () => { + return ( + + + + # + + + + + Product Name + + + + + Status + + + + + Sales + + + + + Version + + + + + Earining + + + + + Actions + + + + ); +}; + +export { ProdcutTableHead }; diff --git a/apps/core/app/dashboard/products/_components/table/product-table-pages/product-table-pages.tsx b/apps/core/app/dashboard/products/_components/table/product-table-pages/product-table-pages.tsx new file mode 100644 index 00000000..fb70f8d8 --- /dev/null +++ b/apps/core/app/dashboard/products/_components/table/product-table-pages/product-table-pages.tsx @@ -0,0 +1,53 @@ +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@repo/ui/components/select"; +import Typography from "@repo/ui/components/typography"; +import { useState } from "react"; + +interface ProductTablePageProps { + page?: number; + limit: number; + total: number; + onChange?: (pageNum: string) => void; +} + +const ProductTablePages = (props: ProductTablePageProps) => { + const { page = 1, total, limit, onChange } = props; + const [selectPage, setSelectPage] = useState(String(page)); + const getPages = () => { + const pages: number[] = []; + for (let i = 1; i <= Math.floor(total / limit); i++) { + pages.push(i); + } + return pages; + }; + return total === 0 ? null : ( +
+ + Page + + + Of {total} +
+ ); +}; +export { ProductTablePages }; diff --git a/apps/core/app/dashboard/products/_components/table/product-table-row/product-table-row.tsx b/apps/core/app/dashboard/products/_components/table/product-table-row/product-table-row.tsx new file mode 100644 index 00000000..15006b29 --- /dev/null +++ b/apps/core/app/dashboard/products/_components/table/product-table-row/product-table-row.tsx @@ -0,0 +1,117 @@ +import Image from "next/image"; +import IconHeart from "@repo/icons/heart"; +import MessegeIcon from "@repo/icons/message"; +import { TableCell, TableRow } from "@repo/ui/components/table"; +import Typography from "@repo/ui/components/typography"; +import { Badge } from "@repo/ui/components/badge"; +import DotsVerticalIcon from "@repo/icons/dotsVertical"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@repo/ui/components/dropdown-menu"; + +interface ProductTableRowProps { + rowIndex: number; + imgSrc?: string; + productName: string; + likes?: number; + comments?: number; + status: string; + sales: number; + version: string; + earning: string; + totalPrice: string; +} + +const ProductTableRow = (props: ProductTableRowProps) => { + const { + rowIndex, + imgSrc = "/images/product-placeholder.jpg", + productName, + status, + sales, + version, + earning, + likes, + comments, + totalPrice, + } = props; + // Crypite - Saas Crypto Currency website design + return ( + + + {rowIndex} + + +
+
+ Product Placeholder +
+
+ + {productName} + +
+ + + {likes || 0} + + + + {comments || 0} + +
+
+
+
+ + + {status} + + + + + {sales} + ({totalPrice}) + + + + {version} + + {earning} + + + + + + + + + {}} className="cursor-pointer"> + Edit + + {}} className="cursor-pointer"> + View product + + {}} className="cursor-pointer"> + + Delete + + + + + +
+ ); +}; + +export { ProductTableRow }; diff --git a/apps/core/app/dashboard/products/_components/tabs/product-tabs.tsx b/apps/core/app/dashboard/products/_components/tabs/product-tabs.tsx new file mode 100644 index 00000000..f6b1d087 --- /dev/null +++ b/apps/core/app/dashboard/products/_components/tabs/product-tabs.tsx @@ -0,0 +1,35 @@ +"use client"; +import { Tabs, TabsList, TabsTrigger } from "@repo/ui/components/tabs"; +import Typography from "@repo/ui/components/typography"; +import { Dispatch, SetStateAction } from "react"; + +interface ProductTabsProps { + tabs: { value: string; label: string }[]; + setProductTab: Dispatch>; +} + +const ProductTabs = (props: ProductTabsProps) => { + const { tabs, setProductTab } = props; + const handleValueChange = (value: string) => { + setProductTab(value); + }; + + return ( + + + {tabs.map((tab, index) => ( + + + {tab.label} + + ))} + + + ); +}; + +export { ProductTabs }; diff --git a/apps/core/app/dashboard/products/page.tsx b/apps/core/app/dashboard/products/page.tsx new file mode 100644 index 00000000..440e4f5e --- /dev/null +++ b/apps/core/app/dashboard/products/page.tsx @@ -0,0 +1,33 @@ +"use client"; +import CirclePlusIcon from "@repo/icons/circle-plus"; +import { Button } from "@repo/ui/components/button"; +import Typography from "@repo/ui/components/typography"; +import { useState } from "react"; +import { ProductTabs } from "./_components/tabs/product-tabs"; +import { ProductTable } from "./_components/table/product-table-head"; + +const tabs = [ + { value: "all", label: "Products" }, + { value: "drafts", label: "Drafts" }, +]; + +const ProductsListPage = () => { + const [productTab, setProductTab] = useState(tabs[0]?.value || ""); + + return ( +
+ + Products + +
+ + +
+ +
+ ); +}; +export default ProductsListPage; diff --git a/packages/apis/src/services/core/shop/products/get/get-shop-product-list.schema.ts b/packages/apis/src/services/core/shop/products/get/get-shop-product-list.schema.ts new file mode 100644 index 00000000..036cd236 --- /dev/null +++ b/packages/apis/src/services/core/shop/products/get/get-shop-product-list.schema.ts @@ -0,0 +1,98 @@ +import { apiResponseSchema } from "#schema/api-response-schema"; +import { z } from "zod"; + +// Request +export const getShopProductListRequestSchemaTransformed = z + .object({ + page: z.number(), + pageSize: z.number(), + }) + .transform((data) => ({ ...data, page_size: data.pageSize })); + +// Response +export const getShopProductListResponseSchemaTransofrmed = apiResponseSchema + .extend({ + data: z.object({ + count: z.number(), + next: z.string(), + previous: z.string(), + results: z.array( + z.object({ + id: z.number(), + is_published: z.boolean(), + created_at: z.date(), + created_by: z.object({ + username: z.string(), + email: z.string(), + }), + versions: z.array( + z.object({ + id: z.number(), + version: z.string(), + notes: z.string(), + name: z.string(), + blurb: z.string(), + description: z.string(), + live_preview: z.string(), + embed: z.string(), + price: z.string(), + highlights: z.object({}), + discount: z.number(), + tags: z.array( + z.object({ + id: z.number(), + title: z.string(), + }), + ), + file_formats: z.array( + z.object({ + id: z.number(), + name: z.string(), + icon: z.string(), + }), + ), + images: z.array( + z.object({ + id: z.number(), + file: z.string(), + extension: z.string(), + mimetype: z.string(), + created_at: z.string(), + }), + ), + previews: z.array( + z.object({ + id: z.number(), + file: z.string(), + extension: z.string(), + mimetype: z.string(), + created_at: z.string(), + }), + ), + thumbnail: z.object({ + id: z.number(), + file: z.string(), + extension: z.string(), + mimetype: z.string(), + created_at: z.string(), + }), + file: z.object({ + id: z.number(), + file: z.string(), + extension: z.string(), + mimetype: z.string(), + created_at: z.string(), + }), + active: z.boolean(), + }), + ), + }), + ), + }), + }) + .transform((data) => data); + +export const getShopProductListSchema = { + response: getShopProductListResponseSchemaTransofrmed, + request: getShopProductListRequestSchemaTransformed, +}; diff --git a/packages/apis/src/services/core/shop/products/get/get-shop-product-list.ts b/packages/apis/src/services/core/shop/products/get/get-shop-product-list.ts new file mode 100644 index 00000000..797a53b5 --- /dev/null +++ b/packages/apis/src/services/core/shop/products/get/get-shop-product-list.ts @@ -0,0 +1,32 @@ +import { coreApi } from "#instance/core-api"; +import path from "path"; +import { ApiResponse } from "@repo/apis/types/api.types"; +import { requestHandler } from "@repo/apis/utils/request-handler"; +import { getShopProductListSchema as schema } from "./get-shop-product-list.schema"; +import { + GetShopProductListRequest, + GetShopProductListResponse, + GetShopProductListResponseTransformed, +} from "./get-shop-product-list.types"; + +const getShopProductListURL = () => path.join("/shop/products/"); + +export const getShopProductList = async ( + props: GetShopProductListRequest, +): Promise> => { + const payloadParsed = schema.request.parse(props); + const URL = getShopProductListURL(); + + const response = await requestHandler( + () => + coreApi.get(URL, { params: payloadParsed }), + schema.response._def.schema, + { + isMock: false, + }, + ); + + const dataParsed = schema.response.parse(response.data); + + return { ...response, data: dataParsed }; +}; diff --git a/packages/apis/src/services/core/shop/products/get/get-shop-product-list.types.ts b/packages/apis/src/services/core/shop/products/get/get-shop-product-list.types.ts new file mode 100644 index 00000000..090d354c --- /dev/null +++ b/packages/apis/src/services/core/shop/products/get/get-shop-product-list.types.ts @@ -0,0 +1,16 @@ +import { z } from "zod"; +import { getShopProductListSchema } from "./get-shop-product-list.schema"; + +// Response +export type GetShopProductListRequest = z.input; + +export type GetShopProductListRequestTransofrmed = z.infer< + typeof getShopProductListSchema.request +>; + +// Request +export type GetShopProductListResponse = z.input; + +export type GetShopProductListResponseTransformed = z.infer< + typeof getShopProductListSchema.response +>; diff --git a/packages/apis/src/services/core/shop/products/get/use-get-shop-product-list.ts b/packages/apis/src/services/core/shop/products/get/use-get-shop-product-list.ts new file mode 100644 index 00000000..b25ff1e8 --- /dev/null +++ b/packages/apis/src/services/core/shop/products/get/use-get-shop-product-list.ts @@ -0,0 +1,31 @@ +import { + ApiError, + ApiResponse, + UseQueryProps, + WithParams, +} from "@repo/apis/types/api.types"; +import { useQuery } from "@tanstack/react-query"; +import { getShopProductList } from "./get-shop-product-list"; +import { + GetShopProductListRequest, + GetShopProductListResponseTransformed, +} from "./get-shop-product-list.types"; + +export type UseGetShopProductListProps = UseQueryProps< + ApiResponse, + WithParams +>; + +export const getShopProductListQueryKey = () => ["getShopProductList"]; + +export const UseGetShopProductList = (props: UseGetShopProductListProps) => { + const { params, ...resProps } = props; + + const query = useQuery, ApiError>({ + queryKey: getShopProductListQueryKey(), + queryFn: () => getShopProductList(params), + ...resProps, + }); + + return query; +}; diff --git a/packages/icons/src/components/circle-plus.tsx b/packages/icons/src/components/circle-plus.tsx new file mode 100644 index 00000000..411116c7 --- /dev/null +++ b/packages/icons/src/components/circle-plus.tsx @@ -0,0 +1,25 @@ +import { IconProps } from "../types/types"; +const CirclePlusIcon = (props: IconProps) => { + const { size = 24, color = "currentColor", ...resProps } = props; + + return ( + + + + ); +}; + +export default CirclePlusIcon; diff --git a/packages/icons/src/components/dotsVertical.tsx b/packages/icons/src/components/dotsVertical.tsx new file mode 100644 index 00000000..21ab118e --- /dev/null +++ b/packages/icons/src/components/dotsVertical.tsx @@ -0,0 +1,39 @@ +import { IconProps } from "../types/types"; +const DotsVerticalIcon = (props: IconProps) => { + const { size = 25, color = "currentColor", ...resProps } = props; + + return ( + + + + + + ); +}; + +export default DotsVerticalIcon; diff --git a/packages/icons/src/components/message.tsx b/packages/icons/src/components/message.tsx new file mode 100644 index 00000000..fd891e44 --- /dev/null +++ b/packages/icons/src/components/message.tsx @@ -0,0 +1,25 @@ +import { IconProps } from "../types/types"; + +const MessegeIcon = (props: IconProps) => { + const { size = 20, color = "currentColor", ...resProps } = props; + + return ( + + + + ); +}; + +export default MessegeIcon; diff --git a/packages/ui/src/components/atoms/table.tsx b/packages/ui/src/components/atoms/table.tsx new file mode 100644 index 00000000..aac2ee9c --- /dev/null +++ b/packages/ui/src/components/atoms/table.tsx @@ -0,0 +1,117 @@ +import * as React from "react"; + +import { cn } from "@repo/ui/lib/utils"; + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)); +Table.displayName = "Table"; + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableHeader.displayName = "TableHeader"; + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableBody.displayName = "TableBody"; + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className, + )} + {...props} + /> +)); +TableFooter.displayName = "TableFooter"; + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableRow.displayName = "TableRow"; + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableHead.displayName = "TableHead"; + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableCell.displayName = "TableCell"; + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableCaption.displayName = "TableCaption"; + +export { + Table, + TableBody, + TableCaption, + TableCell, + TableFooter, + TableHead, + TableHeader, + TableRow, +};