diff --git a/mailers/surveyFeedbackMailer.ts b/mailers/surveyFeedbackMailer.ts new file mode 100644 index 000000000..a38bd9bff --- /dev/null +++ b/mailers/surveyFeedbackMailer.ts @@ -0,0 +1,154 @@ +import Mailjet from "node-mailjet" +import previewEmail from "preview-email" +import { getPrdOrStgDomain } from "src/core/components/links/getDomain" + +type SurveyFeedbackMailer = { + userMail: string + userFirstname: string + userLastname: string + feedbackLocation: { lng: number; lat: number } + feedbackCategory: string + feedbackText: string + lineID: string +} + +export function surveyFeedbackMailer({ + userMail, + userFirstname, + userLastname, + feedbackLocation, + feedbackCategory, + feedbackText, + lineID, +}: SurveyFeedbackMailer) { + const origin = getPrdOrStgDomain() + // mailjet format + const msg = { + From: { Email: "", Name: "Trassenscout" }, + To: [{ Email: userMail }], + Subject: "Beteiligung zum Radnetz Brandenburg: Ihr eingereichter Hinweis", + TextPart: ` + Guten Tag, ${userFirstname} ${userLastname}, + + vielen Dank für Ihre Teilnahme! Hiermit bestätigen wir Ihnen den Eingang Ihres Beitrags mit folgenden Angaben: + + Eingangsdatum: ${new Date().toLocaleDateString("de-DE")} + Kategorie: ${feedbackCategory} + ID der gewählten Verbindung im Netzentwurf: ${lineID} + Ihr Hinweis: + ${feedbackText} + Ortsbezug des Beitrags (in OpenStreetMap): https://www.openstreetmap.org/?mlat=${ + feedbackLocation.lat + }&mlon=${feedbackLocation.lng}=16 + + Hinweis: Beachten Sie, dass Sie für jeden eingereichten Hinweis eine separate E-Mail erhalten. Dies ist eine automatisiert versandte E-Mail, auf die Sie nicht antworten können. + + Nach Abschluss der Beteiligung werden Ihre Hinweise fachlich geprüft und in die Abwägung zur Überarbeitung des Zielnetzentwurfs mit einbezogen. + + Mit freundlichen Grüßen + i.A. das Team von FixMyCity + + FixMyCity GmbH + Oberlandstraße 26-35 + 12099 Berlin + + mail: info@fixmycity.de + www.fixmycity.de + `, + HTMLPart: ` +

+ Guten Tag, ${userFirstname} ${userLastname}, +

+

+ vielen Dank für Ihre Teilnahme! Hiermit bestätigen wir Ihnen den Eingang Ihres Beitrags mit + folgenden Angaben: +

+

+ Eingangsdatum: ${new Date().toLocaleDateString("de-DE")}
+ Kategorie: ${feedbackCategory}
+ ID der gewählten Verbindung im Netzentwurf: ${lineID} +
+ Ihr Hinweis:
+ ${feedbackText} +
+ + Ortsbezug des Beitrags (in OpenStreetMap) + +

+

+ Hinweis: + Beachten Sie, dass Sie für jeden eingereichten Hinweis eine separate E-Mail erhalten. Dies + ist eine automatisiert versandte E-Mail, auf die Sie nicht antworten können. +

+

+ Nach Abschluss der Beteiligung werden Ihre Hinweise fachlich geprüft und in die Abwägung zur + Überarbeitung des Zielnetzentwurfs mit einbezogen. +

+

+ Mit freundlichen Grüßen
+ i.A. das Team von FixMyCity +

+

+ FixMyCity GmbH +
+ Oberlandstraße 26-35 +
+ 12099 Berlin +
+ + mail: info@fixmycity.de + +
+ + www.fixmycity.de + +
+ + + LinkedIn + + +

+ FixMyCity Logo + `, + } + + return { + async send() { + if (process.env.NODE_ENV !== "development") { + const mailjet = Mailjet.apiConnect( + // @ts-ignore + process.env.MAILJET_APIKEY_PUBLIC, + process.env.MAILJET_APIKEY_PRIVATE, + ) + const request = mailjet.post("send", { version: "v3.1" }).request({ Messages: [msg] }) + request + .then((result) => { + console.log(result.body) + }) + .catch((err) => { + console.error(err.statusCode) + }) + } else { + // Preview email in the browser + var { From, To, Subject, TextPart, HTMLPart } = msg + await previewEmail({ + from: `${From.Name} <${From.Email}>`, + // @ts-ignore + to: `${To[0]?.Name || ""} <${To[0].Email}>`, + subject: Subject, + text: TextPart, + html: HTMLPart, + }) + } + }, + } +} diff --git a/src/survey-public/components/Question.tsx b/src/survey-public/components/Question.tsx index cdef9b455..0f54c384c 100644 --- a/src/survey-public/components/Question.tsx +++ b/src/survey-public/components/Question.tsx @@ -1,4 +1,5 @@ import { + TFeedbackQuestion, TQuestion, TSingleOrMultiResponseProps, TTextProps, @@ -89,10 +90,11 @@ const components = { custom: CustomComponent, } -type Props = { question: TQuestion; className?: string } +type Props = { question: TQuestion | TFeedbackQuestion; className?: string } export const Question: React.FC = ({ question, className }) => { const { id, help, label, component, props } = question + // @ts-expect-error const Component = components[component] || null return (
diff --git a/src/survey-public/components/SurveyMainPage.tsx b/src/survey-public/components/SurveyMainPage.tsx index 86b19057d..557e1de6a 100644 --- a/src/survey-public/components/SurveyMainPage.tsx +++ b/src/survey-public/components/SurveyMainPage.tsx @@ -11,7 +11,9 @@ import { SurveySpinnerLayover } from "src/survey-public/components/core/layout/S import { Feedback } from "src/survey-public/components/feedback/Feedback" import { ProgressContext } from "src/survey-public/context/contexts" import { scrollToTopWithDelay } from "src/survey-public/utils/scrollToTopWithDelay" +import surveyFeedbackEmail from "src/survey-responses/mutations/surveyFeedbackEmail" +import { useParams } from "next/navigation" import createSurveyResponse from "src/survey-responses/mutations/createSurveyResponse" import createSurveySession from "src/survey-sessions/mutations/createSurveySession" import { @@ -23,6 +25,7 @@ import { TResponseConfig, TSurvey, } from "./types" +import { useParam } from "@blitzjs/next" type Props = { startContent: React.ReactNode @@ -33,7 +36,7 @@ type Props = { surveyDefinition: TSurvey responseConfig: TResponseConfig surveyId: number - // clean up after BB ? - initial view state of surveymapline depending on institution + // todo survey clean up or refactor after survey BB ? - initial view state of surveymapline depending on institution // prop institutionsBboxes might be cleaned up after BB in this and the other components it is passed through institutionsBboxes?: TInstitutionsBboxes } @@ -57,6 +60,9 @@ export const SurveyMainPage: React.FC = ({ const [feedbackKey, setFeedbackKey] = useState(1) const [createSurveySessionMutation] = useMutation(createSurveySession) const [createSurveyResponseMutation] = useMutation(createSurveyResponse) + const [surveyFeedbackEmailMutation] = useMutation(surveyFeedbackEmail) + const surveySlug = useParam("surveySlug", "string") + useEffect(() => { const root = document.documentElement root.style.setProperty("--survey-primary-color", surveyDefinition.primaryColor) @@ -117,6 +123,12 @@ export const SurveyMainPage: React.FC = ({ data: JSON.stringify(feedbackResponses), source: "FORM", }) + // todo survey clean up or refactor after survey BB + if (surveySlug === "radnetz-brandenburg") + await surveyFeedbackEmailMutation({ + surveySessionId: surveySessionId_, + data: feedbackResponses, + }) })() setTimeout(() => { diff --git a/src/survey-public/components/core/links/styles.ts b/src/survey-public/components/core/links/styles.ts index bc9bb449d..e0bea4d68 100644 --- a/src/survey-public/components/core/links/styles.ts +++ b/src/survey-public/components/core/links/styles.ts @@ -43,7 +43,7 @@ export const surveyWhiteButtonStyles = clsx( // primary color survey BUTTON // for link elements -const primaryColorButtonStylesForLinkElement = clsx( +export const primaryColorButtonStylesForLinkElement = clsx( buttonBase, "text-white bg-[var(--survey-primary-color)]", activeStylePrimaryColorLinkElement, @@ -51,7 +51,7 @@ const primaryColorButtonStylesForLinkElement = clsx( // for button elements export const surveyPrimaryColorButtonStyles = clsx( buttonBase, - "enabled:text-white enabled:bg-[var(--survey-primary-color)]", + "enabled:text-white bg-[var(--survey-primary-color)]", "disabled:bg-[var(--survey-light-color)] disabled:text-white", activeStylePrimaryColorButtonElement, ) diff --git a/src/survey-public/components/feedback/FeedbackFirstPage.tsx b/src/survey-public/components/feedback/FeedbackFirstPage.tsx index cfe2dce7a..cf34e8906 100644 --- a/src/survey-public/components/feedback/FeedbackFirstPage.tsx +++ b/src/survey-public/components/feedback/FeedbackFirstPage.tsx @@ -20,7 +20,7 @@ type Props = { feedbackCategoryId: number setIsCompleted: any institutionsBboxes?: TInstitutionsBboxes - // clean up after BB: legend for line map + // todo survey clean up or refactor after survey BB: legend for line map legend?: Record> } @@ -34,7 +34,7 @@ export const FeedbackFirstPage: React.FC = ({ institutionsBboxes, legend, }) => { - // clean up after BB ? - initial view state of surveymapline depending on institution + // todo survey clean up or refactor after survey BB ? - initial view state of surveymapline depending on institution // maybe this logic should be used for SurveyMap in future surveys const router = useRouter() const { id } = router.query @@ -52,7 +52,7 @@ export const FeedbackFirstPage: React.FC = ({ return ( <> - {/* clean up after BB */} + {/* todo survey clean up or refactor after survey BB */} {/* This map to select a line is custom for BB survey and is going to be deleted afterwards */} {surveySlug === "radnetz-brandenburg" && ( <> diff --git a/src/survey-public/components/feedback/FeedbackSecondPage.tsx b/src/survey-public/components/feedback/FeedbackSecondPage.tsx index b5948fae8..a275752e7 100644 --- a/src/survey-public/components/feedback/FeedbackSecondPage.tsx +++ b/src/survey-public/components/feedback/FeedbackSecondPage.tsx @@ -1,13 +1,15 @@ import { SurveyButton } from "../core/buttons/SurveyButton" import { SurveyButtonWrapper } from "../core/buttons/SurveyButtonWrapper" +import { useFormContext } from "react-hook-form" import { MapProvider } from "react-map-gl" import { SurveyScreenHeader } from "src/survey-public/components/core/layout/SurveyScreenHeader" -import { TMapProps, TPage, TQuestion } from "src/survey-public/components/types" +import { TFeedbackQuestion, TMapProps, TPage, TTextProps } from "src/survey-public/components/types" import { Question } from "../Question" +import { SurveyLabeledTextareaField } from "../core/form/SurveyLabeledTextareaField" import { SurveyMap } from "../maps/SurveyMap" import { SurveyMapLegend } from "../maps/SurveyMapLegend" -import { useFormContext } from "react-hook-form" +import { SurveyH2 } from "../core/Text" export { FORM_ERROR } from "src/core/components/forms" @@ -57,7 +59,7 @@ export const FeedbackSecondPage: React.FC = ({ config: mapProps.config, }} pinId={pinId} - // clean up after BB line selection is removed + // todo survey clean up or refactor after survey BB line selection lineGeometryId={lineGeometryId} /> @@ -66,8 +68,23 @@ export const FeedbackSecondPage: React.FC = ({ )}
{userTextIndices.map((questionId) => { - const q = questions!.find((q: TQuestion) => q.id === questionId) - if (q) return + const q = questions!.find((q) => q.id === questionId) + if (q) { + const userTextQuestionProps = q.props as TTextProps + return ( + <> + {q.label.de} * + {q.help &&
{q.help.de}
} + + + ) + } })}
diff --git a/src/survey-public/components/maps/SurveyMap.tsx b/src/survey-public/components/maps/SurveyMap.tsx index b6f76d201..fc6b3bf33 100644 --- a/src/survey-public/components/maps/SurveyMap.tsx +++ b/src/survey-public/components/maps/SurveyMap.tsx @@ -33,7 +33,7 @@ export type SurveyMapProps = { } setIsMapDirty: any pinId: number - // clean up after BB line selection is removed + // todo survey clean up or refactor after survey BBline selection lineGeometryId?: number } @@ -42,8 +42,8 @@ export const SurveyMap: React.FC = ({ pinId, className, setIsMapDirty, - // clean up after BB line selection is removed lineGeometryId, + // todo survey clean up or refactor after survey BB line selection }) => { const { mainMap } = useMap() const [events, logEvents] = useState>({}) @@ -57,7 +57,7 @@ export const SurveyMap: React.FC = ({ } const { getValues, setValue } = useFormContext() - // clean up after BB line selection is removed + // todo survey clean up or refactor after survey BB line selection // take line geometry from form context const selectedLine = getValues()[`custom-${lineGeometryId}`] || null const mapBounds: { bounds: [number, number, number, number] } = { @@ -68,7 +68,7 @@ export const SurveyMap: React.FC = ({ : projectMap.config.bounds, } - // clean up after BB (center of line option) + // todo survey clean up or refactor after survey BB (center of line option) // take pinPosition from form context - if it is not defined use center of selected line - if we do not have a selected line use initialMarker fallback from feedback.ts configuration const pinPosition = getValues()[`map-${pinId}`] || @@ -154,7 +154,7 @@ export const SurveyMap: React.FC = ({ // @ts-expect-error: See https://github.com/visgl/react-map-gl/issues/2310 RTLTextPlugin={null} > - {/* clean up after BB line selection is removed */} + {/* // todo survey clean up or refactor after survey BB line selection */} {selectedLine && ( { + let [isOpen, setIsOpen] = useState(false) + + const closeModal = () => { + setIsOpen(false) + } + const closeModalAndOpenAtlas = () => { + window.open( + "https://radverkehrsatlas.de/regionen/bb-beteiligung?v=1&map=11/52.397/13.034&config=!(i~bikelanes-minimal~a~~sc~!(i~bikelanes~s~!(i~hidden~a~_F)(i~default~a)(i~details~a~_F)(i~width~a~_F)))(i~poi~a~_F~sc~!(i~poi~s~!(i~hidden~a~_F)(i~default~a)(i~education~a~_F))(i~poiPlaces~s~!(i~hidden~a~_F)(i~default~a)(i~circle~a~_F))(i~poiBoundaries~s~!(i~hidden~a)(i~default~a~_F)(i~category*_district*_label~a~_F)(i~category*_municipality~a~_F)(i~category*_municipality*_label~a~_F))(i~poiPlusBarriers~s~!(i~default~a~_F))(i~poiPlusLanduse~s~!(i~default~a~_F))(i~poiPlusPublicTransport~s~!(i~default~a~_F)))(i~roads~a~_F~sc~!(i~roads~s~!(i~hidden~a~_F)(i~default~a)(i~sidestreets~a~_F)(i~mainstreets~a~_F))(i~maxspeed~s~!(i~hidden~a)(i~default~a~_F)(i~below30~a~_F)(i~above40~a~_F))(i~roads*_plus*_footways~s~!(i~default~a~_F)))(i~mapillary~a~_F~sc~!(i~mapillaryCoverage~s~!(i~hidden~a~_F)(i~default~a)(i~all~a~_F)(i~age~a~_F)(i~pano~a~_F)))~", + "_blank", + ) + setIsOpen(false) + } + + const openModal = () => { + setIsOpen(true) + } + return ( <> Beteiligung der Kommunen und Landkreise zum Radnetz Brandenburg @@ -49,19 +72,86 @@ const StartContent: React.FC = () => { FixMyCity GmbH. Um die Belange aller Baulastträger zu berücksichtigen, besteht zudem eine enge Zusammenarbeit mit den Landkreisen, kreisfreien Städten und Kommunen. - -
- Radverkehrsatlas Land Brandenburg -
-
+ Radverkehrsatlas Land Brandenburg + + + + + +
+ + +
+
+ + + + Achtung: + +
+

+ Durch Aufrufen der Seite bestätige ich, dass der Link zum Radverkehrsatlas und + die enthaltenen Daten vertraulich behandelt und nicht an unbefugte + Dritte weitergegeben werden.{" "} +

+
+ + +
+ +
+
+
+
+
+
+
+ Die Erarbeitung des baulastträgerübergreifenden Konzepts erfolgt durch die vom Ministerium für Landesplanung und Infrastruktur beauftragten Dienstleister Rambøll Deutschland GmbH und diff --git a/src/survey-public/radnetz-brandenburg/data/README.md b/src/survey-public/radnetz-brandenburg/data/README.md index 48d6925fc..ec81552e1 100644 --- a/src/survey-public/radnetz-brandenburg/data/README.md +++ b/src/survey-public/radnetz-brandenburg/data/README.md @@ -15,3 +15,7 @@ 2. `tippecanoe --maximum-zoom=g -rg --drop-densest-as-needed --extend-zooms-if-still-dropping --layer=default /Users/fmc/Downloads/TS_BB_Netzentwurf.geojson --output /Users/fmc/Downloads/TS_BB_Netzentwurf.mbtiles` 3. Datei austauschen unter https://cloud.maptiler.com/tiles/64022d33-fe65-45c9-b023-bac5e5871e1c/upload 4. Im Kartenstil testen, ob die Daten richtig sind bzw. nach etwas Warten in der Umfrage + +## Special Features / Clean Up and Refactoring after BB survey; + +The survey still works for other configurations but some features are built specifically for the "radnetz-brandenburg"-survey. These parts are partially not dynamiy but hard coded. The comment `todo survey clean up or refactor after survey BB` has been added to the respective parts of the code. diff --git a/src/survey-responses/mutations/surveyFeedbackEmail.ts b/src/survey-responses/mutations/surveyFeedbackEmail.ts new file mode 100644 index 000000000..cc26b358b --- /dev/null +++ b/src/survey-responses/mutations/surveyFeedbackEmail.ts @@ -0,0 +1,50 @@ +import { resolver } from "@blitzjs/rpc" +import db from "db" +import { surveyFeedbackMailer } from "mailers/surveyFeedbackMailer" +import { z } from "zod" +import { feedbackDefinition } from "src/survey-public/radnetz-brandenburg/data/feedback" + +export const SurveyFeedbackMail = z.object({ + surveySessionId: z.number(), + data: z.record(z.any()), +}) + +export default resolver.pipe( + resolver.zod(SurveyFeedbackMail), + async ({ surveySessionId, data }) => { + // Get first survey part for personal data and email adress + const surveyPart1 = await db.surveyResponse.findFirst({ + where: { surveySessionId: surveySessionId, surveyPart: 1 }, + }) + + const categories = Object.fromEntries( + // @ts-expect-error + feedbackDefinition.pages[0]?.questions + // todo survey clean up or refactor after survey BB + // the category ref is hard coded + .find((q) => q.id == 22) + // @ts-expect-error + .props.responses.map((response) => [response.id, response.text.de]), + ) + + if (surveyPart1) { + const parsedSurveyPart1 = JSON.parse(surveyPart1?.data) + // Send the email + // todo survey clean up or refactor after survey BB + // the refs are hard coded to reduce the complexity of the code as we do not know if we keep this feature + await surveyFeedbackMailer({ + // @ts-expect-error + userMail: parsedSurveyPart1["3"], + // @ts-expect-error + userFirstname: parsedSurveyPart1["1"], + // @ts-expect-error + userLastname: parsedSurveyPart1["2"], + feedbackLocation: data["24"], + feedbackCategory: categories[data["22"]], + feedbackText: data["25"], + lineID: data["20"], + }).send() + } + return + }, +)