Skip to content

Commit 6e28b1d

Browse files
authored
Initial Commit 2
1 parent ffe447e commit 6e28b1d

File tree

91 files changed

+4964
-0
lines changed

Some content is hidden

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

91 files changed

+4964
-0
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"use server";
2+
3+
import { getServerSession } from "next-auth";
4+
import { render } from "@react-email/render";
5+
6+
import { SendMailToAll } from "./schema";
7+
import { InputType, ReturnType } from "./types";
8+
9+
import { prismadb } from "@/lib/prisma";
10+
import resendHelper from "@/lib/resend";
11+
import { authOptions } from "@/lib/auth";
12+
import { createSafeAction } from "@/lib/create-safe-action";
13+
import MessageToAllUsers from "@/emails/admin/MessageToAllUser";
14+
import sendEmail from "@/lib/sendmail";
15+
16+
const handler = async (data: InputType): Promise<ReturnType> => {
17+
const session = await getServerSession(authOptions);
18+
19+
if (!session) {
20+
return {
21+
error: "You must be authenticated.",
22+
};
23+
}
24+
25+
//Only admin can send mail to all users
26+
if (!session.user.isAdmin) {
27+
return {
28+
error: "You are not authorized to perform this action.",
29+
};
30+
}
31+
32+
const resend = await resendHelper();
33+
34+
const { title, message } = data;
35+
36+
if (!title || !message) {
37+
return {
38+
error: "Title and message are required.",
39+
};
40+
}
41+
42+
try {
43+
const users = await prismadb.users.findMany({
44+
/* where: {
45+
email: {
46+
//contains: "pavel@softbase.cz",
47+
equals: "pavel@softbase.cz",
48+
},
49+
}, */
50+
});
51+
//console.log(users.length, "user.length");
52+
53+
//For each user, send mail
54+
for (const user of users) {
55+
const resendKey = await prismadb.systemServices.findFirst({
56+
where: {
57+
name: "resend_smtp",
58+
},
59+
});
60+
61+
if (!resendKey?.serviceKey || !process.env.RESEND_API_KEY) {
62+
const emailHtml = render(
63+
MessageToAllUsers({
64+
title: title,
65+
message: message,
66+
username: user?.name!,
67+
})
68+
);
69+
70+
//send via sendmail
71+
await sendEmail({
72+
from: process.env.EMAIL_FROM as string,
73+
to: user.email || "info@softbase.cz",
74+
subject: title,
75+
text: message,
76+
html: await emailHtml,
77+
});
78+
}
79+
80+
//send via Resend.com
81+
await resend.emails.send({
82+
from:
83+
process.env.NEXT_PUBLIC_APP_NAME +
84+
" <" +
85+
process.env.EMAIL_FROM +
86+
">",
87+
to: user?.email!,
88+
subject: title,
89+
text: message, // Add this line to fix the types issue
90+
react: MessageToAllUsers({
91+
title: title,
92+
message: message,
93+
username: user?.name!,
94+
}),
95+
});
96+
}
97+
} catch (error) {
98+
console.log(error);
99+
return {
100+
error: "Failed to send mail to all users.",
101+
};
102+
}
103+
104+
return { data: { title: title, message: message } };
105+
};
106+
107+
export const sendMailToAll = createSafeAction(SendMailToAll, handler);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { z } from "zod";
2+
3+
export const SendMailToAll = z.object({
4+
title: z
5+
.string()
6+
.min(3, {
7+
message: "Title must be at least 3 characters long",
8+
}),
9+
message: z
10+
.string()
11+
.min(3, {
12+
message: "Message must be at least 3 characters long",
13+
}),
14+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { z } from "zod";
2+
import { ActionState } from "@/lib/create-safe-action";
3+
import { SendMailToAll } from "./schema";
4+
5+
type Message = {
6+
title: string;
7+
message: string;
8+
};
9+
10+
export type InputType = z.infer<typeof SendMailToAll>;
11+
export type ReturnType = ActionState<InputType, Message>;

actions/ai/generate-site.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
2+
"use server";
3+
4+
import { prismadb } from "@/lib/prisma";
5+
import { revalidatePath } from "next/cache";
6+
import { logActivity } from "@/actions/audit";
7+
8+
// Mock templates for different industries
9+
const templates: any = {
10+
home: {
11+
hero: { title: "Future of {{brand}}", subtitle: "Experience the premium standard in {{industry}}.", primaryAction: "Get Started" },
12+
features: [
13+
{ title: "Premium Quality", description: "Only the finest materials.", icon: "Shield" },
14+
{ title: "Global Reach", description: "Serving customers worldwide.", icon: "Globe" },
15+
{ title: "24/7 Support", description: "We are here when you need us.", icon: "Zap" }
16+
]
17+
},
18+
about: {
19+
hero: { title: "About {{brand}}", subtitle: "Our story of excellence.", primaryAction: "Contact Us" },
20+
content: "Founded with a vision to revolutionize {{industry}}, {{brand}} has set the gold standard."
21+
},
22+
pricing: {
23+
hero: { title: "Simple Pricing", subtitle: "Choose the plan that fits you.", primaryAction: "View Plans" },
24+
plans: [
25+
{ plan: "Starter", price: "$29", features: "Basic Access, Support", recommended: "false" },
26+
{ plan: "Pro", price: "$99", features: "All Features, Priority Support", recommended: "true" }
27+
]
28+
}
29+
};
30+
31+
export async function generateSite(data: {
32+
brandName: string;
33+
description: string;
34+
pages: Record<string, boolean>;
35+
customReqs?: string;
36+
}) {
37+
console.log("Generating site for:", data.brandName);
38+
39+
try {
40+
const createdPages = [];
41+
42+
for (const [pageKey, isSelected] of Object.entries(data.pages)) {
43+
if (!isSelected) continue;
44+
45+
const template = templates[pageKey] || templates.home; // Fallback
46+
const title = pageKey.charAt(0).toUpperCase() + pageKey.slice(1);
47+
const slug = pageKey === 'home' ? 'home' : `${pageKey}`;
48+
49+
// Replace placeholders
50+
const heroTitle = template.hero?.title.replace("{{brand}}", data.brandName) || title;
51+
const heroSubtitle = template.hero?.subtitle.replace("{{industry}}", data.description) || "Welcome to our site.";
52+
53+
// Construct Puck JSON
54+
// We'll create a structured page based on the type
55+
let content: any = [];
56+
57+
if (pageKey === 'header' || pageKey === 'footer') {
58+
// Component pages
59+
content = [
60+
{ type: "ContainerBlock", props: { className: "p-4 bg-slate-900 text-white", id: "nav" } }
61+
];
62+
} else {
63+
// Formatting content for Puck
64+
content.push({
65+
type: "HeroBlock",
66+
props: {
67+
title: heroTitle,
68+
subtitle: heroSubtitle,
69+
primaryAction: template.hero?.primaryAction || "Learn More",
70+
bgImage: "https://images.unsplash.com/photo-1620121692029-d088224ddc74?auto=format&fit=crop&w=1600&q=80"
71+
}
72+
});
73+
74+
if (template.features) {
75+
content.push({
76+
type: "FeaturesGridBlock",
77+
props: {
78+
title: "Why Choose Us",
79+
features: template.features
80+
}
81+
});
82+
}
83+
84+
if (pageKey === 'contact') {
85+
content.push({
86+
type: "HeadingBlock",
87+
props: { title: "Contact Us", align: "center", size: "xl" }
88+
});
89+
content.push({
90+
type: "TextBlock",
91+
props: { content: "Get in touch with our team at support@ledger.ai", align: "center" }
92+
});
93+
// Add a mockup form via CodeBlock for now
94+
content.push({
95+
type: "CodeBlock",
96+
props: { code: '<form class="max-w-md mx-auto space-y-4 text-white"><input type="email" placeholder="Email" class="w-full p-2 rounded bg-white/10 border border-white/20" /><button class="w-full bg-indigo-600 p-2 rounded">Send</button></form>' }
97+
});
98+
}
99+
100+
if (pageKey === 'pricing') {
101+
content.push({
102+
type: "PricingBlock",
103+
props: template.plans[1] // Add the Pro plan
104+
});
105+
}
106+
}
107+
108+
// Create or Update Page in DB
109+
const page = await prismadb.landingPage.upsert({
110+
where: { slug: slug },
111+
update: {
112+
content: { content, root: { props: { title: data.brandName + " - " + title } } },
113+
title: `${data.brandName} - ${title}`,
114+
description: `Generated ${title} page for ${data.brandName}`
115+
},
116+
create: {
117+
slug: slug,
118+
title: `${data.brandName} - ${title}`,
119+
description: `Generated ${title} page for ${data.brandName}`,
120+
content: { content, root: { props: { title: data.brandName + " - " + title } } }
121+
}
122+
});
123+
createdPages.push(page);
124+
}
125+
126+
await logActivity("AI Site Generation", "Genius Mode", `Generated ${createdPages.length} pages for brand: ${data.brandName}`);
127+
128+
revalidatePath("/cms/landing");
129+
return { success: true, pages: createdPages };
130+
131+
} catch (error) {
132+
console.error("Genius Mode Error:", error);
133+
return { success: false, error: "Failed to generate site." };
134+
}
135+
}

actions/ai/manage-models.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
2+
"use server";
3+
4+
import { prismadb } from "@/lib/prisma";
5+
import { revalidatePath } from "next/cache";
6+
7+
export const updateModelPricing = async (formData: FormData) => {
8+
const id = formData.get("id") as string;
9+
const inputPrice = parseFloat(formData.get("inputPrice") as string);
10+
const outputPrice = parseFloat(formData.get("outputPrice") as string);
11+
const isActive = formData.get("isActive") === "on";
12+
const isDefault = formData.get("isDefault") === "on";
13+
14+
if (isDefault) {
15+
// Unset other defaults if this one is being set to true
16+
// We need to know the provider to unset defaults only for that provider?
17+
// Logic in original file was global?
18+
// Original: `where: { isDefault: true }`. This unsets defaults GLOBALLY.
19+
// This seems wrong if we want defaults per provider.
20+
// But let's stick to the original logic for now or improve it?
21+
// Since we added `provider` logic for defaults elsewhere, let's look up the model first.
22+
23+
const model = await prismadb.aiModel.findUnique({ where: { id } });
24+
if (model) {
25+
await prismadb.aiModel.updateMany({
26+
where: { provider: model.provider, isDefault: true },
27+
data: { isDefault: false }
28+
});
29+
}
30+
}
31+
32+
await prismadb.aiModel.update({
33+
where: { id },
34+
data: {
35+
inputPrice,
36+
outputPrice,
37+
isActive,
38+
isDefault
39+
}
40+
});
41+
42+
revalidatePath("/partners/ai-pricing");
43+
revalidatePath("/admin/ai-setup");
44+
revalidatePath("/partners/ai-system-config");
45+
};

0 commit comments

Comments
 (0)