diff --git a/src/app/(app)/(home)/[category]/[subcategory]/page.tsx b/src/app/(app)/(home)/[category]/[subcategory]/page.tsx
index d9b637f..6f29e43 100644
--- a/src/app/(app)/(home)/[category]/[subcategory]/page.tsx
+++ b/src/app/(app)/(home)/[category]/[subcategory]/page.tsx
@@ -1,14 +1,34 @@
+import { Suspense } from 'react';
+import { dehydrate, HydrationBoundary } from '@tanstack/react-query';
+
+import { getQueryClient, trpc } from '@/trpc/server';
+import {
+ ProductList,
+ ProductListSkeleton,
+} from '@/modules/products/ui/components/product-list';
+
interface Props {
params: Promise<{
- category: string;
subcategory: string;
}>;
}
const Page = async ({ params }: Props) => {
- const { category, subcategory } = await params;
+ const { subcategory } = await params;
+ const queryClient = getQueryClient();
+ void queryClient.prefetchQuery(
+ trpc.products.getMany.queryOptions({
+ category: subcategory,
+ })
+ );
- return
Subcategory Page: {subcategory} in Category: {category}
;
+ return (
+
+ }>
+
+
+
+ );
};
export default Page;
diff --git a/src/app/(app)/(home)/[category]/page.tsx b/src/app/(app)/(home)/[category]/page.tsx
index ce6694a..2c62289 100644
--- a/src/app/(app)/(home)/[category]/page.tsx
+++ b/src/app/(app)/(home)/[category]/page.tsx
@@ -1,3 +1,12 @@
+import { Suspense } from 'react';
+import { dehydrate, HydrationBoundary } from '@tanstack/react-query';
+
+import { getQueryClient, trpc } from '@/trpc/server';
+import {
+ ProductList,
+ ProductListSkeleton,
+} from '@/modules/products/ui/components/product-list';
+
interface Props {
params: Promise<{
category: string;
@@ -6,8 +15,20 @@ interface Props {
const Page = async ({ params }: Props) => {
const { category } = await params;
+ const queryClient = getQueryClient();
+ void queryClient.prefetchQuery(
+ trpc.products.getMany.queryOptions({
+ category,
+ })
+ );
- return Category Page: {category}
;
+ return (
+
+ }>
+
+
+
+ );
};
export default Page;
diff --git a/src/collections/Categories.ts b/src/collections/Categories.ts
index 92a8839..5b4159b 100644
--- a/src/collections/Categories.ts
+++ b/src/collections/Categories.ts
@@ -2,6 +2,9 @@ import type { CollectionConfig } from "payload";
export const Categories: CollectionConfig = {
slug: "categories",
+ admin: {
+ useAsTitle: 'name',
+ },
fields: [
{
name: 'name',
diff --git a/src/collections/Products.ts b/src/collections/Products.ts
new file mode 100644
index 0000000..239a7a2
--- /dev/null
+++ b/src/collections/Products.ts
@@ -0,0 +1,48 @@
+import { CollectionConfig } from "payload";
+
+export const Products: CollectionConfig = {
+ slug: "products",
+ fields: [
+ {
+ name: 'name',
+ type: 'text',
+ required: true,
+ },
+ {
+ name: 'description',
+ type: 'text',
+ },
+ {
+ name: 'price',
+ type: 'number',
+ required: true,
+ admin: {
+ description: 'Price in USD',
+ }
+ },
+ {
+ name: 'category',
+ type: 'relationship',
+ relationTo: 'categories',
+ hasMany: false,
+ },
+ {
+ name: 'images',
+ type: 'upload',
+ relationTo: 'media',
+ },
+ {
+ name: 'refundPolicy',
+ type: 'select',
+ options: [
+ '30-day',
+ '14-day',
+ '7-day',
+ '3-day',
+ '1-day',
+ 'no-refunds',
+ ],
+ defaultValue: '30-day',
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/modules/categories/server/procedures.ts b/src/modules/categories/server/procedures.ts
index f378f2a..9cf3944 100644
--- a/src/modules/categories/server/procedures.ts
+++ b/src/modules/categories/server/procedures.ts
@@ -6,7 +6,8 @@ export const categoriesRouter = createTRPCRouter({
const data = await ctx.db.find({
collection: 'categories',
- depth: 1,
+ depth: 1, // Populated subcategories, subcategories.[0] will be a type of "Category"
+ pagination: false,
where: {
parent: {
exists: false,
diff --git a/src/modules/products/server/procedures.ts b/src/modules/products/server/procedures.ts
new file mode 100644
index 0000000..9408c20
--- /dev/null
+++ b/src/modules/products/server/procedures.ts
@@ -0,0 +1,58 @@
+import type { Where } from "payload";
+import { baseProcedure, createTRPCRouter } from "@/trpc/init";
+import { z } from "zod";
+import { Category } from "@/payload-types";
+
+export const productsRouter = createTRPCRouter({
+ getMany: baseProcedure.input(z.object({
+ category: z.string().nullable().optional(),
+ })).query(async ({ ctx, input }) => {
+ const where: Where = {}
+
+ if (input.category) {
+ const categoriesData = await ctx.db.find({
+ collection: 'categories',
+ limit: 1,
+ depth: 1, // Populated subcategories, subcategories.[0] will be a type of "Category"
+ pagination: false,
+ where: {
+ slug: {
+ equals: input.category
+ }
+ }
+ })
+
+ const formattedData = categoriesData.docs.map((category) => ({
+ ...category,
+ subcategories: (category.subcategories?.docs ?? []).map((doc) => ({
+ // Because of "depth: 1" we are confident "doc" will be a tpe of "Category"
+ ...(doc as Category),
+ subcategories: undefined,
+ })),
+ }));
+
+ const subCategoriesSlugs = [];
+ const parentCategory = formattedData[0];
+
+ if (parentCategory) {
+ subCategoriesSlugs.push(
+ ...parentCategory.subcategories.map((subcategory) => subcategory.slug)
+ );
+ }
+
+ if (parentCategory) {
+ where['category.slug'] = {
+ in: [parentCategory.slug, ...subCategoriesSlugs],
+ }
+ }
+ }
+
+ const data = await ctx.db.find({
+ collection: 'products',
+ depth: 1, // Populated "category" & "image"
+ where,
+ });
+
+ return data;
+ })
+})
\ No newline at end of file
diff --git a/src/modules/products/type.ts b/src/modules/products/type.ts
new file mode 100644
index 0000000..e79f6de
--- /dev/null
+++ b/src/modules/products/type.ts
@@ -0,0 +1,5 @@
+import { inferRouterOutputs } from "@trpc/server";
+
+import { AppRouter } from "@/trpc/routers/_app";
+
+export type ProductsGetManyOutput = inferRouterOutputs["products"]["getMany"];
\ No newline at end of file
diff --git a/src/modules/products/ui/components/product-list.tsx b/src/modules/products/ui/components/product-list.tsx
new file mode 100644
index 0000000..5db4a9a
--- /dev/null
+++ b/src/modules/products/ui/components/product-list.tsx
@@ -0,0 +1,23 @@
+'use client';
+
+import { useSuspenseQuery } from '@tanstack/react-query';
+
+import { useTRPC } from '@/trpc/client';
+
+interface Props {
+ category: string;
+}
+
+
+export const ProductList = ({ category }: Props) => {
+ const trpc = useTRPC();
+ const { data } = useSuspenseQuery(trpc.products.getMany.queryOptions({
+ category
+ }));
+
+ return {JSON.stringify(data)}
;
+};
+
+export const ProductListSkeleton = () => {
+ return Loading...
;
+};
diff --git a/src/payload-types.ts b/src/payload-types.ts
index 7885ecb..0784f2c 100644
--- a/src/payload-types.ts
+++ b/src/payload-types.ts
@@ -70,6 +70,7 @@ export interface Config {
users: User;
media: Media;
categories: Category;
+ products: Product;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
@@ -83,6 +84,7 @@ export interface Config {
users: UsersSelect | UsersSelect;
media: MediaSelect | MediaSelect;
categories: CategoriesSelect | CategoriesSelect;
+ products: ProductsSelect | ProductsSelect;
'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect;
'payload-preferences': PayloadPreferencesSelect | PayloadPreferencesSelect;
'payload-migrations': PayloadMigrationsSelect | PayloadMigrationsSelect;
@@ -174,6 +176,24 @@ export interface Category {
updatedAt: string;
createdAt: string;
}
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "products".
+ */
+export interface Product {
+ id: string;
+ name: string;
+ description?: string | null;
+ /**
+ * Price in USD
+ */
+ price: number;
+ category?: (string | null) | Category;
+ images?: (string | null) | Media;
+ refundPolicy?: ('30-day' | '14-day' | '7-day' | '3-day' | '1-day' | 'no-refunds') | null;
+ updatedAt: string;
+ createdAt: string;
+}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents".
@@ -192,6 +212,10 @@ export interface PayloadLockedDocument {
| ({
relationTo: 'categories';
value: string | Category;
+ } | null)
+ | ({
+ relationTo: 'products';
+ value: string | Product;
} | null);
globalSlug?: string | null;
user: {
@@ -282,6 +306,20 @@ export interface CategoriesSelect {
updatedAt?: T;
createdAt?: T;
}
+/**
+ * This interface was referenced by `Config`'s JSON-Schema
+ * via the `definition` "products_select".
+ */
+export interface ProductsSelect {
+ name?: T;
+ description?: T;
+ price?: T;
+ category?: T;
+ images?: T;
+ refundPolicy?: T;
+ updatedAt?: T;
+ createdAt?: T;
+}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents_select".
diff --git a/src/payload.config.ts b/src/payload.config.ts
index ccf3adf..26a05bc 100644
--- a/src/payload.config.ts
+++ b/src/payload.config.ts
@@ -10,6 +10,7 @@ import sharp from 'sharp'
import { Users } from './collections/Users';
import { Media } from './collections/Media'
import { Categories } from './collections/Categories'
+import { Products } from './collections/Products'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
@@ -22,7 +23,7 @@ export default buildConfig({
},
},
cookiePrefix: 'funroad',
- collections: [Users, Media, Categories],
+ collections: [Users, Media, Categories, Products],
editor: lexicalEditor(),
secret: process.env.PAYLOAD_SECRET || '',
typescript: {
diff --git a/src/trpc/routers/_app.ts b/src/trpc/routers/_app.ts
index f7ea56b..0432115 100644
--- a/src/trpc/routers/_app.ts
+++ b/src/trpc/routers/_app.ts
@@ -1,11 +1,12 @@
import { authRouter } from "@/modules/auth/server/procedures";
import { categoriesRouter } from '@/modules/categories/server/procedures';
-
+import { productsRouter } from "@/modules/products/server/procedures";
import { createTRPCRouter } from '../init';
export const appRouter = createTRPCRouter({
auth: authRouter,
+ products: productsRouter,
categories: categoriesRouter,
});
// export type definition of API