Skip to content

Commit 13a60da

Browse files
author
Rajat Saxena
committed
common product-card in featured widget
1 parent 751c20a commit 13a60da

File tree

10 files changed

+273
-72
lines changed

10 files changed

+273
-72
lines changed

apps/web/app/(with-contexts)/(with-layout)/products/products-list.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
"use client";
22

3-
import { useMemo } from "react";
4-
import { SkeletonCard } from "@components/skeleton-card";
5-
import { ContentCard } from "./content-card";
3+
import { useMemo, useContext } from "react";
64
import { PaginationControls } from "@components/public/pagination";
75
import { Constants, Course } from "@courselit/common-models";
86
import { useProducts } from "@/hooks/use-products";
97
import { BookOpen } from "lucide-react";
108
import { EmptyState } from "./empty-state";
119
import { Button } from "@components/ui/button";
12-
10+
import { SkeletonCard, ProductCard } from "@courselit/components-library";
11+
import { SiteInfoContext } from "@components/contexts";
1312
const ITEMS_PER_PAGE = 9;
1413

1514
export function ProductsList({
@@ -21,6 +20,7 @@ export function ProductsList({
2120
itemsPerPage?: number;
2221
onPageChange: (page: number) => void;
2322
}) {
23+
const siteinfo = useContext(SiteInfoContext);
2424
const filters = useMemo(
2525
() => [Constants.CourseType.COURSE.toUpperCase()],
2626
[],
@@ -59,9 +59,10 @@ export function ProductsList({
5959
<SkeletonCard key={index} />
6060
))
6161
: products.map((product: Course) => (
62-
<ContentCard
62+
<ProductCard
6363
key={product.courseId}
6464
product={product}
65+
siteinfo={siteinfo}
6566
/>
6667
))}
6768
</div>

apps/web/components/public/base-layout/template/widget-by-name.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from "react";
22
import widgets from "../../../../ui-config/widgets";
33
import type { AppState, AppDispatch } from "@courselit/state-management";
44
import { COMPONENT_MISSING_SUFFIX } from "../../../../ui-config/strings";
5+
import WidgetErrorBoundary from "./widget-error-boundary";
56

67
interface WidgetByNameProps {
78
id: string;
@@ -24,15 +25,19 @@ const WidgetByName = ({
2425
}: WidgetByNameProps) => {
2526
if (!widgets[name]) return <>{`${name} ${COMPONENT_MISSING_SUFFIX}`}</>;
2627

27-
return React.createElement(widgets[name].widget, {
28-
name,
29-
settings,
30-
state,
31-
dispatch,
32-
id,
33-
pageData,
34-
editing,
35-
});
28+
return (
29+
<WidgetErrorBoundary widgetName={name}>
30+
{React.createElement(widgets[name].widget, {
31+
name,
32+
settings,
33+
state,
34+
dispatch,
35+
id,
36+
pageData,
37+
editing,
38+
})}
39+
</WidgetErrorBoundary>
40+
);
3641
};
3742

3843
export default WidgetByName;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React, { Component, ErrorInfo, ReactNode } from "react";
2+
import { capitalize } from "@courselit/utils";
3+
4+
interface Props {
5+
children: ReactNode;
6+
widgetName: string;
7+
}
8+
9+
interface State {
10+
hasError: boolean;
11+
error: Error | null;
12+
}
13+
14+
class WidgetErrorBoundary extends Component<Props, State> {
15+
public state: State = {
16+
hasError: false,
17+
error: null,
18+
};
19+
20+
public static getDerivedStateFromError(error: Error): State {
21+
return { hasError: true, error };
22+
}
23+
24+
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
25+
console.error("Widget Error:", error, errorInfo);
26+
}
27+
28+
public render(): JSX.Element {
29+
if (this.state.hasError) {
30+
return (
31+
<div className="p-4 border border-red-200 rounded-md bg-red-50">
32+
<h3 className="text-red-800 font-medium mb-2">
33+
Error in <b>{capitalize(this.props.widgetName)}</b>{" "}
34+
block
35+
</h3>
36+
<p className="text-red-600 text-sm">
37+
{this.state.error?.message || "Something went wrong"}
38+
</p>
39+
</div>
40+
);
41+
}
42+
43+
return <>{this.props.children}</>;
44+
}
45+
}
46+
47+
export default WidgetErrorBoundary;

packages/common-models/src/course.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Group from "./group";
33
import { ProductPriceType, CourseType, ProductAccessType } from "./constants";
44
import { PaymentPlan } from "./payment-plan";
55
import Lesson from "./lesson";
6+
import User from "./user";
67

78
export type ProductPriceType =
89
(typeof ProductPriceType)[keyof typeof ProductPriceType];
@@ -33,4 +34,5 @@ export interface Course {
3334
updatedAt: Date;
3435
leadMagnet?: boolean;
3536
lessons?: Lesson[];
37+
user: User;
3638
}

packages/common-widgets/src/featured/widget.tsx

Lines changed: 82 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
import React, { useEffect, useState } from "react";
44
import { Course, WidgetProps } from "@courselit/common-models";
55
import {
6-
CourseItem,
76
TextRenderer,
8-
Skeleton,
7+
SkeletonCard,
8+
ProductCard,
99
} from "@courselit/components-library";
1010
import { actionCreators } from "@courselit/state-management";
1111
import {
@@ -47,18 +47,32 @@ export default function Widget({
4747
const productsArgs = getGraphQLQueryStringFromObject(products);
4848
const query = `
4949
query {
50-
product: getCourses(ids: ${productsArgs}) {
51-
title,
52-
description,
50+
product: getProducts(ids: ${productsArgs}, limit: 1000, publicView: true) {
51+
title
52+
courseId
5353
featuredImage {
54-
file,
55-
thumbnail,
56-
caption
57-
},
58-
courseId,
59-
cost,
60-
type,
54+
thumbnail
55+
file
56+
}
6157
pageId
58+
type
59+
paymentPlans {
60+
planId
61+
name
62+
type
63+
oneTimeAmount
64+
emiAmount
65+
emiTotalInstallments
66+
subscriptionMonthlyAmount
67+
subscriptionYearlyAmount
68+
}
69+
defaultPaymentPlan
70+
user {
71+
name
72+
avatar {
73+
thumbnail
74+
}
75+
}
6276
}
6377
}
6478
`;
@@ -106,53 +120,63 @@ export default function Widget({
106120
<h2 className="text-4xl mb-4">{title}</h2>
107121
{description && <TextRenderer json={description} />}
108122
</div>
109-
{productItems.length === 0 && (
110-
<div className="flex flex-wrap gap-[1%]">
111-
<div className="basis-full md:basis-[49.5%] lg:basis-[32.6666%] mb-6">
112-
<div className="mb-4">
113-
<Skeleton className="h-[200px] lg:h-[220px] w-full mb-4" />
114-
<Skeleton className="h-[16px] w-full mb-1" />
115-
<Skeleton className="h-[20px] w-full mb-1" />
116-
<Skeleton className="h-[18px] w-full" />
117-
</div>
118-
</div>
119-
<div className="basis-full md:basis-[49.5%] lg:basis-[32.6666%] mb-6">
120-
<div className="mb-4">
121-
<Skeleton className="h-[200px] lg:h-[220px] w-full mb-4" />
122-
<Skeleton className="h-[16px] w-full mb-1" />
123-
<Skeleton className="h-[20px] w-full mb-1" />
124-
<Skeleton className="h-[18px] w-full" />
125-
</div>
126-
</div>
127-
<div className="basis-full md:basis-[49.5%] lg:basis-[32.6666%] mb-6">
128-
<div className="mb-4">
129-
<Skeleton className="h-[200px] lg:h-[220px] w-full mb-4" />
130-
<Skeleton className="h-[16px] w-full mb-1" />
131-
<Skeleton className="h-[20px] w-full mb-1" />
132-
<Skeleton className="h-[18px] w-full" />
133-
</div>
134-
</div>
135-
</div>
136-
)}
137-
{productItems.length > 0 && (
138-
<div className="flex flex-wrap gap-[1%]">
139-
{productItems.map(
140-
(course: Course, index: number) => (
141-
<div
123+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
124+
{productItems.length === 0 && (
125+
<>
126+
{Array.from({ length: 3 }).map((_, index) => (
127+
<SkeletonCard key={index} />
128+
))}
129+
</>
130+
// <div className="flex flex-wrap gap-[1%]">
131+
// <div className="basis-full md:basis-[49.5%] lg:basis-[32.6666%] mb-6">
132+
// <div className="mb-4">
133+
// <Skeleton className="h-[200px] lg:h-[220px] w-full mb-4" />
134+
// <Skeleton className="h-[16px] w-full mb-1" />
135+
// <Skeleton className="h-[20px] w-full mb-1" />
136+
// <Skeleton className="h-[18px] w-full" />
137+
// </div>
138+
// </div>
139+
// <div className="basis-full md:basis-[49.5%] lg:basis-[32.6666%] mb-6">
140+
// <div className="mb-4">
141+
// <Skeleton className="h-[200px] lg:h-[220px] w-full mb-4" />
142+
// <Skeleton className="h-[16px] w-full mb-1" />
143+
// <Skeleton className="h-[20px] w-full mb-1" />
144+
// <Skeleton className="h-[18px] w-full" />
145+
// </div>
146+
// </div>
147+
// <div className="basis-full md:basis-[49.5%] lg:basis-[32.6666%] mb-6">
148+
// <div className="mb-4">
149+
// <Skeleton className="h-[200px] lg:h-[220px] w-full mb-4" />
150+
// <Skeleton className="h-[16px] w-full mb-1" />
151+
// <Skeleton className="h-[20px] w-full mb-1" />
152+
// <Skeleton className="h-[18px] w-full" />
153+
// </div>
154+
// </div>
155+
// </div>
156+
)}
157+
{productItems.length > 0 && (
158+
<>
159+
{productItems.map((course: Course) => (
160+
<ProductCard
142161
key={course.courseId}
143-
className="basis-full md:basis-[49.5%] lg:basis-[32.6666%] mb-6"
144-
>
145-
<CourseItem
146-
course={course}
147-
siteInfo={state.siteinfo}
148-
freeCostCaption={"FREE"}
149-
key={index}
150-
/>
151-
</div>
152-
),
153-
)}
154-
</div>
155-
)}
162+
product={course}
163+
siteinfo={state.siteinfo}
164+
/>
165+
// <div
166+
// key={course.courseId}
167+
// className="basis-full md:basis-[49.5%] lg:basis-[32.6666%] mb-6"
168+
// >
169+
// <CourseItem
170+
// course={course}
171+
// siteInfo={state.siteinfo}
172+
// freeCostCaption={"FREE"}
173+
// key={index}
174+
// />
175+
// </div>
176+
))}
177+
</>
178+
)}
179+
</div>
156180
</div>
157181
</div>
158182
</section>

packages/components-library/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ export * from "./toast2";
5555
export * from "./paginated-table";
5656
import getSymbolFromCurrency from "currency-symbol-map";
5757
export * from "./content-card";
58+
export * from "./skeleton-card";
59+
export * from "./product-card";
5860

5961
export {
6062
PriceTag,

0 commit comments

Comments
 (0)