Skip to content

Commit ade19de

Browse files
committed
v1.0.6c - CMS-77 CMS-78 CMS-79 CMS-80 CMS-81 CMS-83 CMS-84 feat: Complete overhaul of all pages, introduce subscription client and newsletter management, new blog post pages, and enhance location features with updated UI components and visuals. Needs further testing, and pricing components from PortalPay docs.
1 parent 1ba1b21 commit ade19de

File tree

139 files changed

+6727
-2989
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

139 files changed

+6727
-2989
lines changed

actions/audit.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ export async function logActivityInternal(
88
userId: string,
99
action: string,
1010
resource: string,
11-
details?: string
11+
details?: string,
12+
metadata?: Record<string, any>
1213
) {
1314
try {
1415
await prismadb.systemActivity.create({
@@ -17,6 +18,7 @@ export async function logActivityInternal(
1718
action,
1819
resource,
1920
details,
21+
metadata,
2022
},
2123
});
2224
} catch (error) {
@@ -27,13 +29,14 @@ export async function logActivityInternal(
2729
export async function logActivity(
2830
action: string,
2931
resource: string,
30-
details?: string
32+
details?: string,
33+
metadata?: Record<string, any>
3134
) {
3235
try {
3336
const session = await getServerSession(authOptions);
3437
if (!session?.user?.id) return;
3538

36-
await logActivityInternal(session.user.id, action, resource, details);
39+
await logActivityInternal(session.user.id, action, resource, details, metadata);
3740
} catch (error) {
3841
console.error("Failed to log activity:", error);
3942
}

actions/cms/generate-blog-post.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,20 @@ export async function generateBlogPost(topic: string) {
5959
temperature: isReasoningModel(model.modelId) ? undefined : 1,
6060
});
6161

62+
// Log Activity
63+
const { logActivityInternal } = await import("@/actions/audit");
64+
await logActivityInternal(
65+
userId,
66+
"Generated Blog Post",
67+
"Content AI",
68+
`Generated draft for topic: "${topic}"`,
69+
{
70+
title: object.title,
71+
category: object.category,
72+
slug: object.slug
73+
}
74+
);
75+
6276
return object;
6377
} catch (error: any) {
6478
console.error("Error generating blog post:", {

actions/cms/save-landing-page.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,15 @@ export async function saveLandingPage(id: string, data: any) {
1515
revalidatePath(`/cms/landing`);
1616
revalidatePath(`/landing`);
1717

18-
await logActivity("Update Landing Page", "Landing Page", `Updated content for page ID: ${id}`);
18+
await logActivity(
19+
"Update Landing Page",
20+
"Landing Page",
21+
`Updated content for page ID: ${id}`,
22+
{
23+
pageId: id,
24+
sectionsCount: Array.isArray(data) ? data.length : (Array.isArray(data?.content) ? data.content.length : 0)
25+
}
26+
);
1927

2028
return { success: true };
2129
} catch (error) {

actions/cms/subscriptions.ts

Lines changed: 78 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,141 @@
11
"use server";
22

33
import { prismadb } from "@/lib/prisma";
4-
import { getServerSession } from "next-auth";
5-
import { authOptions } from "@/lib/auth";
64
import { revalidatePath } from "next/cache";
7-
import { logActivity } from "@/lib/cms/audit";
85

9-
export async function getSubscribers(page = 1, limit = 20, search = "") {
10-
const session = await getServerSession(authOptions);
11-
if (!session?.user?.isAdmin) {
12-
return { success: false, error: "Unauthorized" };
6+
export async function getClients(query?: string) {
7+
try {
8+
const where: any = {
9+
assigned_team: {
10+
isNot: null
11+
}
12+
};
13+
14+
if (query) {
15+
where.OR = [
16+
{ name: { contains: query, mode: "insensitive" } },
17+
{ email: { contains: query, mode: "insensitive" } },
18+
{ assigned_team: { name: { contains: query, mode: "insensitive" } } }
19+
];
20+
}
21+
22+
const users = await prismadb.users.findMany({
23+
where: {
24+
...where,
25+
email: { not: "admin@ledger1.ai" },
26+
},
27+
include: {
28+
assigned_team: {
29+
include: {
30+
assigned_plan: true
31+
}
32+
},
33+
assigned_role: true
34+
},
35+
orderBy: { created_on: 'desc' }
36+
});
37+
38+
return users.filter(u => u.assigned_team);
39+
} catch (error) {
40+
console.error("Failed to get clients:", error);
41+
return [];
1342
}
43+
}
1444

45+
// Updated to match SubscriptionClient requirement
46+
export async function getSubscribers(page: number = 1, limit: number = 20, query: string = "") {
1547
try {
16-
const skip = (page - 1) * limit;
17-
1848
const where: any = {};
19-
if (search) {
49+
if (query) {
2050
where.OR = [
21-
{ email: { contains: search, mode: "insensitive" } },
22-
{ firstName: { contains: search, mode: "insensitive" } },
23-
{ lastName: { contains: search, mode: "insensitive" } },
51+
{ email: { contains: query, mode: "insensitive" } },
52+
{ firstName: { contains: query, mode: "insensitive" } },
53+
{ lastName: { contains: query, mode: "insensitive" } }
2454
];
2555
}
2656

27-
const [subscribers, total] = await Promise.all([
28-
prismadb.subscriber.findMany({
57+
const skip = (page - 1) * limit;
58+
59+
const [data, total] = await Promise.all([
60+
prismadb.newsletterSubscriber.findMany({
2961
where,
3062
skip,
3163
take: limit,
32-
orderBy: { createdAt: "desc" },
64+
orderBy: { createdAt: 'desc' }
3365
}),
34-
prismadb.subscriber.count({ where }),
66+
prismadb.newsletterSubscriber.count({ where })
3567
]);
3668

3769
return {
3870
success: true,
39-
data: subscribers,
71+
data,
4072
pagination: {
73+
page,
74+
limit,
4175
total,
42-
pages: Math.ceil(total / limit),
43-
current: page,
44-
limit
76+
totalPages: Math.ceil(total / limit)
4577
}
4678
};
4779
} catch (error) {
48-
console.error("[GET_SUBSCRIBERS]", error);
80+
console.error("Failed to get subscribers:", error);
4981
return { success: false, error: "Failed to fetch subscribers" };
5082
}
5183
}
5284

53-
export async function addSubscriber(data: { email: string; firstName?: string; lastName?: string }) {
54-
const session = await getServerSession(authOptions);
55-
if (!session?.user?.isAdmin) {
56-
return { success: false, error: "Unauthorized" };
57-
}
85+
// Backward compatibility alias if needed, or strictly use getSubscribers
86+
export async function getNewsletterSubscribers(query?: string) {
87+
// Reuse new logic but return array (old behavior)
88+
const result = await getSubscribers(1, 1000, query); // Default to first 1000
89+
return result.success ? result.data : [];
90+
}
5891

92+
export async function addSubscriber(data: { email: string, firstName?: string, lastName?: string }) {
5993
try {
60-
const existing = await prismadb.subscriber.findUnique({
61-
where: { email: data.email },
94+
if (!data.email) return { success: false, error: "Email is required" };
95+
96+
const existing = await prismadb.newsletterSubscriber.findFirst({
97+
where: { email: data.email }
6298
});
6399

64100
if (existing) {
65101
return { success: false, error: "Subscriber already exists" };
66102
}
67103

68-
const subscriber = await prismadb.subscriber.create({
104+
await prismadb.newsletterSubscriber.create({
69105
data: {
70106
email: data.email,
71107
firstName: data.firstName,
72108
lastName: data.lastName,
73-
subscribedToBlog: true,
74-
subscribedToCareers: true,
75-
},
109+
status: "SUBSCRIBED",
110+
source: "Admin Manual Add"
111+
}
76112
});
77113

78-
await logActivity("ADD_SUBSCRIBER", "Subscription", `Added subscriber ${data.email}`);
79-
revalidatePath("/cms/subscriptions");
80-
return { success: true, data: subscriber };
114+
revalidatePath("/cms/subscriptions/newsletter");
115+
return { success: true };
81116
} catch (error) {
82-
console.error("[ADD_SUBSCRIBER]", error);
117+
console.error("Add subscriber error:", error);
83118
return { success: false, error: "Failed to add subscriber" };
84119
}
85120
}
86121

87122
export async function deleteSubscriber(id: string) {
88-
const session = await getServerSession(authOptions);
89-
if (!session?.user?.isAdmin) {
90-
return { success: false, error: "Unauthorized" };
91-
}
92-
93123
try {
94-
const sub = await prismadb.subscriber.delete({
95-
where: { id },
96-
});
97-
98-
await logActivity("DELETE_SUBSCRIBER", "Subscription", `Deleted subscriber ${sub.email}`);
99-
revalidatePath("/cms/subscriptions");
124+
await prismadb.newsletterSubscriber.delete({ where: { id } });
125+
revalidatePath("/cms/subscriptions/newsletter");
100126
return { success: true };
101127
} catch (error) {
102128
return { success: false, error: "Failed to delete subscriber" };
103129
}
104130
}
105131

106-
export async function updateSubscriberPreferences(id: string, updates: { subscribedToBlog?: boolean; subscribedToCareers?: boolean; isActive?: boolean }) {
107-
const session = await getServerSession(authOptions);
108-
if (!session?.user?.isAdmin) {
109-
return { success: false, error: "Unauthorized" };
110-
}
111-
132+
export async function updateSubscriberPreferences(id: string, data: { subscribedToBlog?: boolean, subscribedToCareers?: boolean }) {
112133
try {
113-
await prismadb.subscriber.update({
134+
await prismadb.newsletterSubscriber.update({
114135
where: { id },
115-
data: updates,
136+
data
116137
});
117-
118-
revalidatePath("/cms/subscriptions");
138+
revalidatePath("/cms/subscriptions/newsletter");
119139
return { success: true };
120140
} catch (error) {
121141
return { success: false, error: "Failed to update preferences" };

actions/cms/users.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import { prismadb } from "@/lib/prisma";
44
import { revalidatePath } from "next/cache";
55
import { hash } from "bcryptjs";
6+
import { logActivityInternal } from "@/actions/audit";
7+
import { getServerSession } from "next-auth";
8+
import { authOptions } from "@/lib/auth";
69

710
export async function getUsers(query?: string) {
811
const where: any = {};
@@ -78,6 +81,21 @@ export async function upsertUser(data: any) {
7881
});
7982
}
8083

84+
const logSession = await getServerSession(authOptions);
85+
logActivityInternal(
86+
logSession?.user?.id || "system",
87+
id ? "Updated User" : "Created User",
88+
"Team Management",
89+
`User ${updateData.email} was ${id ? "updated" : "created"}`,
90+
{
91+
email: updateData.email,
92+
roleId: updateData.roleId,
93+
status: updateData.userStatus
94+
}
95+
);
96+
97+
98+
8199
revalidatePath("/cms/settings/team");
82100
return { success: true };
83101
} catch (error: any) {
@@ -98,6 +116,17 @@ export async function deleteUser(id: string) {
98116
// Delete = Hard Delete.
99117

100118
await prismadb.users.delete({ where: { id } });
119+
120+
const logSession = await getServerSession(authOptions);
121+
logActivityInternal(
122+
logSession?.user?.id || "system",
123+
"Deleted User",
124+
"Team Management",
125+
`User ${id} was permanently deleted`,
126+
{ userId: id }
127+
);
128+
129+
101130
revalidatePath("/cms/settings/team");
102131
return { success: true };
103132
} catch (error: any) {
@@ -111,6 +140,17 @@ export async function toggleUserStatus(id: string, status: "ACTIVE" | "INACTIVE"
111140
where: { id },
112141
data: { userStatus: status }
113142
});
143+
144+
const logSession = await getServerSession(authOptions);
145+
logActivityInternal(
146+
logSession?.user?.id || "system",
147+
"Updated User Status",
148+
"Team Management",
149+
`User ${id} status changed to ${status}`,
150+
{ userId: id, newStatus: status }
151+
);
152+
153+
114154
revalidatePath("/cms/settings/team");
115155
return { success: true };
116156
} catch (error) {
@@ -201,6 +241,17 @@ export async function resetUserPassword(userId: string) {
201241
data: { password: hashedPassword }
202242
});
203243

244+
const logSession = await getServerSession(authOptions);
245+
logActivityInternal(
246+
logSession?.user?.id || "system",
247+
"Reset User Password",
248+
"Security",
249+
`Admin reset password for user ${user.email}`,
250+
{ userId: user.id, email: user.email }
251+
);
252+
253+
254+
204255
// Dynamically import sendEmail to avoid top-level issues if any
205256
const sendEmail = (await import("@/lib/sendmail")).default;
206257

0 commit comments

Comments
 (0)