Skip to content

Commit 4429bc4

Browse files
author
Rajat Saxena
committed
Community membership should unlock included products
Fixes #617
1 parent 80bd7d2 commit 4429bc4

35 files changed

Lines changed: 1606 additions & 899 deletions

File tree

apps/web/app/(with-contexts)/dashboard/(sidebar)/community/[id]/manage/page.tsx

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import {
2929
Badge,
3030
Form,
3131
FormField,
32-
getSymbolFromCurrency,
3332
Image,
3433
Link,
3534
MediaSelector,
@@ -225,6 +224,8 @@ export default function Page({
225224
planId
226225
name
227226
type
227+
entityId
228+
entityType
228229
oneTimeAmount
229230
emiAmount
230231
emiTotalInstallments
@@ -301,6 +302,8 @@ export default function Page({
301302
planId
302303
name
303304
type
305+
entityId
306+
entityType
304307
oneTimeAmount
305308
emiAmount
306309
emiTotalInstallments
@@ -511,8 +514,8 @@ export default function Page({
511514

512515
const onPlanArchived = async (planId: string) => {
513516
const query = `
514-
mutation ArchivePlan($planId: String!, $entityId: String!, $entityType: MembershipEntityType!) {
515-
plan: archivePlan(planId: $planId, entityId: $entityId, entityType: $entityType) {
517+
mutation ArchivePlan($planId: String!) {
518+
plan: archivePlan(planId: $planId) {
516519
planId
517520
name
518521
type
@@ -531,9 +534,6 @@ export default function Page({
531534
query,
532535
variables: {
533536
planId,
534-
entityId: id,
535-
entityType:
536-
MembershipEntityType.COMMUNITY.toUpperCase(),
537537
},
538538
})
539539
.setIsGraphQLEndpoint(true)
@@ -789,23 +789,11 @@ export default function Page({
789789
...plan,
790790
type: plan.type.toLowerCase() as PaymentPlanType,
791791
}))}
792-
onPlanSubmit={onPlanSubmitted}
793792
onPlanArchived={onPlanArchived}
794-
allowedPlanTypes={[
795-
paymentPlanType.SUBSCRIPTION,
796-
paymentPlanType.FREE,
797-
paymentPlanType.ONE_TIME,
798-
paymentPlanType.EMI,
799-
]}
800-
currencySymbol={getSymbolFromCurrency(
801-
siteinfo.currencyISOCode || "USD",
802-
)}
803-
currencyISOCode={
804-
siteinfo.currencyISOCode?.toUpperCase() || "USD"
805-
}
806793
onDefaultPlanChanged={onDefaultPlanChanged}
807794
defaultPaymentPlanId={defaultPaymentPlan}
808-
paymentMethod={siteinfo.paymentMethod}
795+
entityId={id}
796+
entityType={MembershipEntityType.COMMUNITY}
809797
/>
810798
</div>
811799
<Separator className="my-8" />
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Metadata, ResolvingMetadata } from "next";
2+
import { EDIT_PAYMENT_PLAN_HEADER } from "@/ui-config/strings";
3+
4+
export async function generateMetadata(
5+
{
6+
params,
7+
}: {
8+
params: any;
9+
},
10+
parent: ResolvingMetadata,
11+
): Promise<Metadata> {
12+
const { type, id } = params;
13+
14+
return {
15+
title: `${EDIT_PAYMENT_PLAN_HEADER} | ${(await parent)?.title?.absolute}`,
16+
};
17+
}
18+
19+
export default function Layout({ children }: { children: React.ReactNode }) {
20+
return children;
21+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
"use client";
2+
3+
import { useParams, useRouter } from "next/navigation";
4+
import { useEffect } from "react";
5+
import { Loader2 } from "lucide-react";
6+
import { Constants, MembershipEntityType } from "@courselit/common-models";
7+
import DashboardContent from "@/components/admin/dashboard-content";
8+
import { PaymentPlanForm } from "@/components/admin/payments/payment-plan-form";
9+
import {
10+
COMMUNITY_SETTINGS,
11+
EDIT_PAYMENT_PLAN_HEADER,
12+
EDIT_PAYMENT_PLAN_DESCRIPTION,
13+
} from "@/ui-config/strings";
14+
import { usePaymentPlan } from "@/hooks/use-paymentplan";
15+
import { useEntityValidation } from "../../use-entity-validation";
16+
import { truncate } from "@courselit/utils";
17+
18+
const {
19+
MembershipEntityType: membershipEntityType,
20+
PaymentPlanType: paymentPlanType,
21+
} = Constants;
22+
23+
export default function EditPaymentPlanPage() {
24+
const params = useParams();
25+
const router = useRouter();
26+
27+
const entityType = params?.type as MembershipEntityType;
28+
const entityId = params?.id as string;
29+
const planId = params?.planid as string;
30+
const { product, community } = useEntityValidation(entityType, entityId);
31+
32+
const breadcrumbs = [
33+
{
34+
label:
35+
entityType === membershipEntityType.COMMUNITY
36+
? truncate(community?.name || "...", 10)
37+
: truncate(product?.title || "...", 10),
38+
href: `/dashboard/${entityType}/${entityId}`,
39+
},
40+
{
41+
label: COMMUNITY_SETTINGS,
42+
href: `/dashboard/${entityType === membershipEntityType.COMMUNITY ? "community" : "product"}/${entityId}/manage`,
43+
},
44+
{ label: EDIT_PAYMENT_PLAN_HEADER, href: "#" },
45+
];
46+
47+
const { paymentPlan, loaded: paymentPlanLoaded } = usePaymentPlan(
48+
planId,
49+
entityId,
50+
entityType,
51+
);
52+
53+
useEffect(() => {
54+
if (paymentPlanLoaded && !paymentPlan) {
55+
router.push(
56+
`/dashboard/${entityType === membershipEntityType.COMMUNITY ? "community" : "product"}/${entityId}/manage`,
57+
);
58+
}
59+
}, [paymentPlanLoaded, paymentPlan, router, entityId, entityType]);
60+
61+
if (!paymentPlanLoaded) {
62+
return (
63+
<DashboardContent breadcrumbs={breadcrumbs}>
64+
<div className="flex items-center justify-center h-64">
65+
<Loader2 className="h-8 w-8 animate-spin" />
66+
</div>
67+
</DashboardContent>
68+
);
69+
}
70+
71+
return (
72+
<DashboardContent breadcrumbs={breadcrumbs}>
73+
<div className="space-y-2">
74+
<div className="flex flex-col lg:flex-row gap-4 lg:items-center justify-between mb-8">
75+
<div>
76+
<h1 className="text-4xl font-semibold">
77+
{EDIT_PAYMENT_PLAN_HEADER}
78+
</h1>
79+
<p className="text-muted-foreground">
80+
{EDIT_PAYMENT_PLAN_DESCRIPTION} &quot;
81+
{paymentPlan?.name || ""}&quot;
82+
</p>
83+
</div>
84+
</div>
85+
</div>
86+
<PaymentPlanForm
87+
initialData={
88+
paymentPlan
89+
? {
90+
name: paymentPlan.name,
91+
description: paymentPlan.description,
92+
type: paymentPlan.type,
93+
oneTimeAmount: paymentPlan.oneTimeAmount,
94+
emiAmount: paymentPlan.emiAmount,
95+
emiTotalInstallments:
96+
paymentPlan.emiTotalInstallments,
97+
subscriptionMonthlyAmount:
98+
paymentPlan.subscriptionMonthlyAmount,
99+
subscriptionYearlyAmount:
100+
paymentPlan.subscriptionYearlyAmount,
101+
includedProducts: paymentPlan.includedProducts,
102+
subscriptionType:
103+
paymentPlan.type ===
104+
paymentPlanType.SUBSCRIPTION
105+
? paymentPlan.subscriptionYearlyAmount
106+
? "yearly"
107+
: "monthly"
108+
: "monthly",
109+
}
110+
: undefined
111+
}
112+
entityId={entityId}
113+
entityType={entityType}
114+
/>
115+
</DashboardContent>
116+
);
117+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Metadata, ResolvingMetadata } from "next";
2+
import { NEW_PAYMENT_PLAN_HEADER } from "@/ui-config/strings";
3+
4+
export async function generateMetadata(
5+
{
6+
params,
7+
}: {
8+
params: any;
9+
},
10+
parent: ResolvingMetadata,
11+
): Promise<Metadata> {
12+
const { type, id } = params;
13+
14+
return {
15+
title: `${NEW_PAYMENT_PLAN_HEADER} | ${(await parent)?.title?.absolute}`,
16+
};
17+
}
18+
19+
export default function Layout({ children }: { children: React.ReactNode }) {
20+
return children;
21+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"use client";
2+
3+
import { useParams } from "next/navigation";
4+
import { Constants, MembershipEntityType } from "@courselit/common-models";
5+
import DashboardContent from "@/components/admin/dashboard-content";
6+
import { PaymentPlanForm } from "@/components/admin/payments/payment-plan-form";
7+
import {
8+
COMMUNITY_SETTINGS,
9+
NEW_PAYMENT_PLAN_HEADER,
10+
NEW_PAYMENT_PLAN_DESCRIPTION,
11+
} from "@/ui-config/strings";
12+
import { useEntityValidation } from "../use-entity-validation";
13+
import { truncate } from "@courselit/utils";
14+
15+
const { MembershipEntityType: membershipEntityType } = Constants;
16+
17+
export default function NewPaymentPlanPage() {
18+
const params = useParams();
19+
const entityType = params?.type as MembershipEntityType;
20+
const entityId = params?.id as string;
21+
const { product, community } = useEntityValidation(entityType, entityId);
22+
23+
const breadcrumbs = [
24+
{
25+
label:
26+
entityType === membershipEntityType.COMMUNITY
27+
? truncate(community?.name || "...", 10)
28+
: truncate(product?.title || "...", 10),
29+
href: `/dashboard/${entityType}/${entityId}`,
30+
},
31+
{
32+
label: COMMUNITY_SETTINGS,
33+
href: `/dashboard/${entityType}/${entityId}/manage`,
34+
},
35+
{ label: NEW_PAYMENT_PLAN_HEADER, href: "#" },
36+
];
37+
38+
return (
39+
<DashboardContent breadcrumbs={breadcrumbs}>
40+
<div className="space-y-2">
41+
<div className="flex flex-col lg:flex-row gap-4 lg:items-center justify-between mb-8">
42+
<div>
43+
<h1 className="text-4xl font-semibold">
44+
{NEW_PAYMENT_PLAN_HEADER}
45+
</h1>
46+
<p className="text-muted-foreground">
47+
{NEW_PAYMENT_PLAN_DESCRIPTION}{" "}
48+
{entityType.toLowerCase()}
49+
</p>
50+
</div>
51+
</div>
52+
</div>
53+
<PaymentPlanForm entityId={entityId} entityType={entityType} />
54+
</DashboardContent>
55+
);
56+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { useEffect } from "react";
2+
import { useRouter } from "next/navigation";
3+
import { Constants, MembershipEntityType } from "@courselit/common-models";
4+
import { useCommunity } from "@/hooks/use-community";
5+
import useProduct from "@/hooks/use-product";
6+
7+
const { MembershipEntityType: membershipEntityType } = Constants;
8+
9+
export function useEntityValidation(
10+
entityType: MembershipEntityType,
11+
entityId: string,
12+
) {
13+
const router = useRouter();
14+
15+
const { community, loaded: communityLoaded } = useCommunity(
16+
entityType === membershipEntityType.COMMUNITY ? entityId : null,
17+
);
18+
const { product, loaded: productLoaded } = useProduct(
19+
entityType === membershipEntityType.COURSE ? entityId : null,
20+
);
21+
22+
// Redirect if community is not found
23+
useEffect(() => {
24+
if (
25+
entityType === membershipEntityType.COMMUNITY &&
26+
communityLoaded &&
27+
!community
28+
) {
29+
router.push("/dashboard/communities");
30+
}
31+
}, [communityLoaded, community, entityType, router]);
32+
33+
// Redirect if product is not found
34+
useEffect(() => {
35+
if (
36+
entityType === membershipEntityType.COURSE &&
37+
productLoaded &&
38+
!product
39+
) {
40+
router.push("/dashboard/products");
41+
}
42+
}, [productLoaded, product, entityType, router]);
43+
44+
return {
45+
loaded:
46+
entityType === membershipEntityType.COMMUNITY
47+
? communityLoaded
48+
: productLoaded,
49+
community,
50+
product,
51+
};
52+
}

apps/web/app/(with-contexts)/dashboard/(sidebar)/product/[id]/manage/page.tsx

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ import {
4747
} from "@components/contexts";
4848
import { truncate } from "@ui-lib/utils";
4949
import {
50-
getSymbolFromCurrency,
5150
MediaSelector,
5251
TextEditor,
5352
TextEditorEmptyDoc,
@@ -580,16 +579,6 @@ export default function SettingsPage() {
580579
...plan,
581580
type: plan.type.toLowerCase() as PaymentPlanType,
582581
}))}
583-
onPlanSubmit={async (values) => {
584-
try {
585-
await onPlanSubmitted(values);
586-
} catch (err: any) {
587-
toast({
588-
title: TOAST_TITLE_ERROR,
589-
description: err.message,
590-
});
591-
}
592-
}}
593582
onPlanArchived={async (id) => {
594583
try {
595584
await onPlanArchived(id);
@@ -601,18 +590,6 @@ export default function SettingsPage() {
601590
});
602591
}
603592
}}
604-
allowedPlanTypes={[
605-
paymentPlanType.SUBSCRIPTION,
606-
paymentPlanType.FREE,
607-
paymentPlanType.ONE_TIME,
608-
paymentPlanType.EMI,
609-
]}
610-
currencySymbol={getSymbolFromCurrency(
611-
siteinfo.currencyISOCode || "USD",
612-
)}
613-
currencyISOCode={
614-
siteinfo.currencyISOCode?.toUpperCase() || "USD"
615-
}
616593
onDefaultPlanChanged={async (id) => {
617594
try {
618595
await onDefaultPlanChanged(id);
@@ -624,7 +601,8 @@ export default function SettingsPage() {
624601
}
625602
}}
626603
defaultPaymentPlanId={defaultPaymentPlan}
627-
paymentMethod={siteinfo.paymentMethod}
604+
entityId={productId}
605+
entityType={MembershipEntityType.COURSE}
628606
/>
629607
</div>
630608

0 commit comments

Comments
 (0)