Compare commits
6 Commits
3d9e3814fe
...
5259c41b13
| Author | SHA1 | Date | |
|---|---|---|---|
| 5259c41b13 | |||
| 5aa8a32020 | |||
| b58a04b030 | |||
| e1615f9345 | |||
| ce760eb721 | |||
| 109cd7660a |
@@ -16,6 +16,7 @@ import { Box, Flex } from "@chakra-ui/react";
|
|||||||
import { Footer } from "./components/footer/Footer";
|
import { Footer } from "./components/footer/Footer";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { API_BASE } from "@/config/api.config";
|
import { API_BASE } from "@/config/api.config";
|
||||||
|
import { ContactPage } from "./pages/ContactPage";
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
@@ -80,6 +81,7 @@ function App() {
|
|||||||
<Route path="/" element={<HomePage />} />
|
<Route path="/" element={<HomePage />} />
|
||||||
<Route path="/my-loans" element={<MyLoansPage />} />
|
<Route path="/my-loans" element={<MyLoansPage />} />
|
||||||
<Route path="/landingpage" element={<Landingpage />} />
|
<Route path="/landingpage" element={<Landingpage />} />
|
||||||
|
<Route path="/contact" element={<ContactPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ import { setIsLoggedInAtom, triggerLogoutAtom } from "@/states/Atoms";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
CircleUserRound,
|
CircleUserRound,
|
||||||
Code,
|
|
||||||
LifeBuoy,
|
LifeBuoy,
|
||||||
LogOut,
|
LogOut,
|
||||||
CalendarPlus,
|
CalendarPlus,
|
||||||
MoreVertical,
|
MoreVertical,
|
||||||
Languages,
|
Languages,
|
||||||
Table,
|
Table,
|
||||||
|
ContactRound,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useUserContext } from "@/states/Context";
|
import { useUserContext } from "@/states/Context";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -144,7 +144,7 @@ export const Header = () => {
|
|||||||
window.open(
|
window.open(
|
||||||
"https://git.the1s.de/Matthias-Claudius-Schule/borrow-system/wiki",
|
"https://git.the1s.de/Matthias-Claudius-Schule/borrow-system/wiki",
|
||||||
"_blank",
|
"_blank",
|
||||||
"noopener,noreferrer"
|
"noopener,noreferrer",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
children={
|
children={
|
||||||
@@ -155,18 +155,12 @@ export const Header = () => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
value="source-code"
|
value="contact"
|
||||||
onSelect={() =>
|
onSelect={() => navigate("/contact", { replace: true })}
|
||||||
window.open(
|
|
||||||
"https://git.the1s.de/Matthias-Claudius-Schule/borrow-system",
|
|
||||||
"_blank",
|
|
||||||
"noopener,noreferrer"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
children={
|
children={
|
||||||
<HStack gap={3}>
|
<HStack gap={3}>
|
||||||
<Code size={16} />
|
<ContactRound size={16} />
|
||||||
<Text as="span">{t("source-code")}</Text>
|
<Text as="span">{t("contact")}</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -296,17 +290,15 @@ export const Header = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a
|
<Button
|
||||||
href="https://git.the1s.de/Matthias-Claudius-Schule/borrow-system"
|
variant={"outline"}
|
||||||
target="_blank"
|
onClick={() => navigate("/contact", { replace: true })}
|
||||||
>
|
>
|
||||||
<Button variant="ghost">
|
<HStack gap={2}>
|
||||||
<HStack gap={2}>
|
<ContactRound size={18} />
|
||||||
<Code size={18} />
|
<Text as="span">{t("contact")}</Text>
|
||||||
<Text as="span">{t("source-code")}</Text>
|
</HStack>
|
||||||
</HStack>
|
</Button>
|
||||||
</Button>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<Button onClick={logout} variant="outline" colorScheme="red">
|
<Button onClick={logout} variant="outline" colorScheme="red">
|
||||||
<HStack gap={2}>
|
<HStack gap={2}>
|
||||||
@@ -318,7 +310,13 @@ export const Header = () => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{/* User Info Dialoge */}
|
{/* User Info Dialoge */}
|
||||||
{userDialog && <UserDialogue setUserDialog={setUserDialog} fullname={fullname} randomColor={randomColor} />}
|
{userDialog && (
|
||||||
|
<UserDialogue
|
||||||
|
setUserDialog={setUserDialog}
|
||||||
|
fullname={fullname}
|
||||||
|
randomColor={randomColor}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
77
FrontendV2/src/pages/ContactPage.tsx
Normal file
77
FrontendV2/src/pages/ContactPage.tsx
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { Field, Textarea, Button, Alert, Container } from "@chakra-ui/react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { API_BASE } from "@/config/api.config";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import { Header } from "@/components/Header";
|
||||||
|
|
||||||
|
interface Alert {
|
||||||
|
type: "info" | "warning" | "success" | "error" | "neutral";
|
||||||
|
headline: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ContactPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
const [alert, setAlert] = useState<Alert | null>(null);
|
||||||
|
|
||||||
|
const sendMessage = async () => {
|
||||||
|
// Logic to send the message
|
||||||
|
const result = await fetch(`${API_BASE}/api/users/contact`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${Cookies.get("token") || ""}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ message }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.ok) {
|
||||||
|
setAlert({
|
||||||
|
type: "success",
|
||||||
|
headline: t("contactPage_successHeadline"),
|
||||||
|
text: t("contactPage_successText"),
|
||||||
|
});
|
||||||
|
setMessage("");
|
||||||
|
} else {
|
||||||
|
setAlert({
|
||||||
|
type: "error",
|
||||||
|
headline: t("contactPage_errorHeadline"),
|
||||||
|
text: t("contactPage_errorText"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="px-6 sm:px-8 pt-10">
|
||||||
|
<Header />
|
||||||
|
<Field.Root invalid={message === ""}>
|
||||||
|
<Field.Label>
|
||||||
|
{t("contactPage_messageLabel")}
|
||||||
|
<Field.RequiredIndicator />
|
||||||
|
</Field.Label>
|
||||||
|
<Textarea
|
||||||
|
placeholder={t("contactPage_messagePlaceholder")}
|
||||||
|
variant="subtle"
|
||||||
|
value={message}
|
||||||
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
|
/>
|
||||||
|
{message === "" && (
|
||||||
|
<Field.ErrorText>{t("contactPage_messageErrorText")}</Field.ErrorText>
|
||||||
|
)}
|
||||||
|
</Field.Root>
|
||||||
|
{alert && (
|
||||||
|
<Alert.Root status={alert.type}>
|
||||||
|
<Alert.Indicator />
|
||||||
|
<Alert.Content>
|
||||||
|
<Alert.Title>{alert.headline}</Alert.Title>
|
||||||
|
<Alert.Description>{alert.text}</Alert.Description>
|
||||||
|
</Alert.Content>
|
||||||
|
</Alert.Root>
|
||||||
|
)}
|
||||||
|
<Button onClick={sendMessage}>{t("contactPage_sendButton")}</Button>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -72,5 +72,14 @@
|
|||||||
"last-borrowed-person": "Zuletzt ausgeliehen von",
|
"last-borrowed-person": "Zuletzt ausgeliehen von",
|
||||||
"currently-borrowed-by": "Derzeit ausgeliehen von",
|
"currently-borrowed-by": "Derzeit ausgeliehen von",
|
||||||
"back": "Zurückgehen",
|
"back": "Zurückgehen",
|
||||||
"landingpage": "Übersichtsseite"
|
"landingpage": "Übersichtsseite",
|
||||||
|
"contactPage_successHeadline": "Nachricht erfolgreich gesendet",
|
||||||
|
"contactPage_successText": "Vielen Dank, dass Sie uns kontaktiert haben. Wir werden uns so schnell wie möglich bei Ihnen melden.",
|
||||||
|
"contactPage_errorHeadline": "Fehler beim Senden der Nachricht",
|
||||||
|
"contactPage_errorText": "Beim Senden Ihrer Nachricht ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.",
|
||||||
|
"contactPage_sendButton": "Nachricht senden",
|
||||||
|
"contactPage_messageLabel": "Nachricht",
|
||||||
|
"contactPage_messagePlaceholder": "Geben Sie hier Ihre Nachricht ein...",
|
||||||
|
"contactPage_messageErrorText": "Dieses Feld darf nicht leer sein.",
|
||||||
|
"contact": "Kontakt"
|
||||||
}
|
}
|
||||||
@@ -72,5 +72,14 @@
|
|||||||
"last-borrowed-person": "Last borrowed by",
|
"last-borrowed-person": "Last borrowed by",
|
||||||
"currently-borrowed-by": "Currently borrowed by",
|
"currently-borrowed-by": "Currently borrowed by",
|
||||||
"back": "Go back",
|
"back": "Go back",
|
||||||
"landingpage": "Overview page"
|
"landingpage": "Overview page",
|
||||||
|
"contactPage_successHeadline": "Message sent successfully",
|
||||||
|
"contactPage_successText": "Thank you for contacting us. We will get back to you as soon as possible.",
|
||||||
|
"contactPage_errorHeadline": "Error sending message",
|
||||||
|
"contactPage_errorText": "An error occurred while sending your message. Please try again later.",
|
||||||
|
"contactPage_sendButton": "Send message",
|
||||||
|
"contactPage_messageLabel": "Message",
|
||||||
|
"contactPage_messagePlaceholder": "Enter your message here...",
|
||||||
|
"contactPage_messageErrorText": "This field cannot be empty.",
|
||||||
|
"contact": "Contact"
|
||||||
}
|
}
|
||||||
45
backendV2/routes/app/services/mailer_v2.js
Normal file
45
backendV2/routes/app/services/mailer_v2.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import nodemailer from "nodemailer";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
export function sendMail(username, message) {
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
host: process.env.MAIL_HOST,
|
||||||
|
port: process.env.MAIL_PORT,
|
||||||
|
secure: true,
|
||||||
|
auth: {
|
||||||
|
user: process.env.MAIL_USER,
|
||||||
|
pass: process.env.MAIL_PASSWORD,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const mailText = `Neue Kontaktanfrage im Ausleihsystem.\n\nBenutzername: ${username}\n\nNachricht:\n${message}`;
|
||||||
|
|
||||||
|
const mailHtml = `<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Neue Nachricht im Ausleihsystem</title>
|
||||||
|
</head>
|
||||||
|
<body style="font-family: Arial, sans-serif; line-height: 1.5; color: #222;">
|
||||||
|
<h2>Neue Nachricht im Ausleihsystem</h2>
|
||||||
|
<p><strong>Benutzername:</strong> ${username}</p>
|
||||||
|
<p><strong>Nachricht:</strong></p>
|
||||||
|
<p style="white-space: pre-line;">${message}</p>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
const info = await transporter.sendMail({
|
||||||
|
from: '"Ausleihsystem" <noreply@mcs-medien.de>',
|
||||||
|
to: process.env.MAIL_SENDEES_CONTACT,
|
||||||
|
subject: "Sie haben eine neue Nachricht!",
|
||||||
|
text: mailText,
|
||||||
|
html: mailHtml,
|
||||||
|
});
|
||||||
|
|
||||||
|
// debugging logs
|
||||||
|
// console.log("Message sent:", info.messageId);
|
||||||
|
})();
|
||||||
|
// console.log("sendMailLoan called");
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ dotenv.config();
|
|||||||
|
|
||||||
// database funcs import
|
// database funcs import
|
||||||
import { loginFunc, changePassword } from "./database/userMgmt.database.js";
|
import { loginFunc, changePassword } from "./database/userMgmt.database.js";
|
||||||
|
import { sendMail } from "./services/mailer_v2.js";
|
||||||
|
|
||||||
router.post("/login", async (req, res) => {
|
router.post("/login", async (req, res) => {
|
||||||
const result = await loginFunc(req.body.username, req.body.password);
|
const result = await loginFunc(req.body.username, req.body.password);
|
||||||
@@ -35,4 +36,13 @@ router.post("/change-password", authenticate, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post("/contact", authenticate, async (req, res) => {
|
||||||
|
const message = req.body.message;
|
||||||
|
const username = req.user.username;
|
||||||
|
|
||||||
|
sendMail(username, message);
|
||||||
|
|
||||||
|
res.status(200).json({ message: "Contact message sent successfully" });
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- proxynet
|
- proxynet
|
||||||
build: ./backendV2
|
build: ./backendV2
|
||||||
|
ports:
|
||||||
|
- "8004:8004"
|
||||||
environment:
|
environment:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
DB_HOST: mysql_v2
|
DB_HOST: mysql_v2
|
||||||
|
|||||||
Reference in New Issue
Block a user