completed user admin page

This commit is contained in:
PxlLoewe
2025-03-15 11:09:55 -07:00
parent abf3475c7c
commit 37d02ea0bc
23 changed files with 567 additions and 106 deletions

View File

@@ -1,3 +1,4 @@
API_PORT=
MOODLE_TOKEN=
MOODLE_URL=
MAIL_SERVER=

View File

@@ -1,5 +1,17 @@
import "dotenv/config";
import "modules/chron";
import express from "express";
import cors from "cors";
import router from "routes/router";
// Add API eventually
console.log("VAR hub Server started");
const app = express();
app.use(express.json());
app.use(cors());
app.use(router);
const port = process.env.API_PORT || 3003;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});

View File

@@ -0,0 +1,202 @@
import * as React from "react";
import { User } from "@repo/db";
import { Html, render } from "@react-email/components";
const styles = `
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: inherit !important;
}
#MessageViewBody a {
color: inherit;
text-decoration: none;
}
p {
line-height: inherit;
}
.desktop_hide,
.desktop_hide table {
mso-hide: all;
display: none;
max-height: 0px;
overflow: hidden;
}
.image_block img + div {
display: none;
}
sup,
sub {
font-size: 75%;
line-height: 0;
}
.menu_block.desktop_hide .menu-links span {
mso-hide: all;
}
@media (max-width: 700px) {
.desktop_hide table.icons-inner {
display: inline-block !important;
}
.icons-inner {
text-align: center;
}
.icons-inner td {
margin: 0 auto;
}
}
`;
const Template = ({ user, password }: { user: User; password: string }) => (
<Html lang="de">
<meta content="text/html; charset=utf-8" httpEquiv="Content-Type" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
<link
href="https://fonts.googleapis.com/css?family=Montserrat"
rel="stylesheet"
type="text/css"
/>
<style>{styles}</style>
<body style={{ backgroundColor: "#FFFFFF", margin: 0, padding: 0 }}>
<table width="100%">
<tbody>
<tr>
<td>
<table
align="center"
width="680"
style={{ margin: "0 auto", color: "#000000" }}
>
<tbody>
<tr>
<td style={{ textAlign: "center", paddingTop: "30px" }}>
<img
src={`${process.env.HUB_URL}/mail/var_logo.png`}
alt="Logo"
width="80"
style={{ display: "block", margin: "0 auto" }}
/>
</td>
</tr>
<tr>
<td
style={{
textAlign: "center",
fontSize: "46px",
color: "#011936",
}}
>
<strong>Passwort geändert</strong>
</td>
</tr>
<tr>
<td
style={{
textAlign: "center",
fontSize: "30px",
color: "#011936",
}}
>
Hallo {user.firstname},
</td>
</tr>
<tr>
<td
style={{
textAlign: "center",
fontSize: "18px",
color: "#011936",
padding: "20px",
}}
>
Dein Passwort wurde erfolgreich geändert. Wenn du diese
Änderung nicht vorgenommen hast, kontaktiere bitte sofort
unseren Support.
</td>
</tr>
<tr>
<td
style={{
textAlign: "center",
fontSize: "18px",
color: "#011936",
padding: "20px",
}}
>
Dein neues Passwort lautet: <strong>{password}</strong>
</td>
</tr>
<tr>
<td style={{ textAlign: "center", paddingTop: "20px" }}>
<a
href="https://your-platform.com"
style={{
padding: "10px",
textDecoration: "none",
borderRadius: "20px",
color: "#011936",
}}
>
Impressum
</a>
<span style={{ margin: "0 10px" }}>|</span>
<a
href="https://your-platform.com"
style={{
padding: "10px",
textDecoration: "none",
borderRadius: "20px",
color: "#011936",
}}
>
Datenschutzerklärung
</a>
<span style={{ margin: "0 10px" }}>|</span>
<a
href="https://your-platform.com"
style={{
padding: "10px",
textDecoration: "none",
borderRadius: "20px",
color: "#011936",
}}
>
Knowledgebase
</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</body>
</Html>
);
export function renderPasswordChanged({
user,
password,
}: {
user: User;
password: string;
}) {
return render(<Template user={user} password={password} />);
}

View File

@@ -1,6 +1,7 @@
import { Event, User } from "@repo/db";
import nodemailer from "nodemailer";
import { renderCourseCompleted } from "./mail-templates/CourseCompleted";
import { renderPasswordChanged } from "./mail-templates/PasswordChanged";
let transporter: nodemailer.Transporter | null = null;
@@ -42,18 +43,39 @@ export const sendCourseCompletedEmail = async (
console.error("Transporter is not initialized");
return;
}
transporter.sendMail(
{
from: process.env.MAIL_USER,
to,
subject: `Kurs ${event.name} erfolgreich abgeschlossen`,
html: emailHtml,
},
(error, info) => {
if (error) {
console.error("Error sending email:", error);
} else {
}
},
);
sendMail(to, `Kurs ${event.name} erfolgreich abgeschlossen`, emailHtml);
};
export const sendPasswordChanged = async (
to: string,
user: User,
password: string,
) => {
const emailHtml = await renderPasswordChanged({ user, password });
await sendMail(to, `Dein Passwort wurde geändert`, emailHtml);
};
export const sendMail = async (to: string, subject: string, html: string) =>
new Promise<void>(async (resolve, reject) => {
if (!transporter) {
console.error("Transporter is not initialized");
return;
}
await transporter.sendMail(
{
from: process.env.MAIL_USER,
to,
subject: subject,
html,
},
(error, info) => {
if (error) {
reject(error);
console.error("Error sending email:", error);
} else {
resolve();
}
},
);
});

View File

@@ -18,8 +18,10 @@
"dependencies": {
"@react-email/components": "^0.0.33",
"axios": "^1.7.9",
"cors": "^2.8.5",
"cron": "^4.1.0",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"nodemailer": "^6.10.0",
"react": "^19.0.0"
}

View File

@@ -0,0 +1,56 @@
import { Router } from "express";
import { sendMail } from "modules/mail";
import { sendPasswordChanged, sendCourseCompletedEmail } from "modules/mail";
const router = Router();
router.post("/send", async (req, res) => {
console.log(req.body);
const { to, subject, html } = req.body;
try {
await sendMail(to, subject, html);
// Send email logic here
res.status(200).json({ message: "Email sent successfully" });
} catch (error) {
res.status(500).json({ error: "Failed to send email" });
}
});
router.post("/template/:template", async (req, res) => {
const { template } = req.params;
const { to, data } = req.body;
if (!to || !data) {
res.status(400).json({ error: "Missing required fields" });
return;
}
if (!data.user) {
res.status(400).json({ error: "Missing user data" });
return;
}
console.log("template", template);
switch (template) {
case "password-change":
if (!data.password) {
res.status(400).json({ error: "Missing password data" });
return;
}
await sendPasswordChanged(to, data.user, data.password);
break;
case "course-completed":
if (!data.event) {
res.status(400).json({ error: "Missing event data" });
return;
}
await sendCourseCompletedEmail(to, data.user, data.event);
break;
default:
res.status(400).json({ error: "Invalid template" });
return;
}
res.status(200).json({ message: "Email sent successfully" });
});
export default router;

View File

@@ -0,0 +1,8 @@
import { Router } from "express";
import mailRouter from "./mail";
const router = Router();
router.use("/mail", mailRouter);
export default router;