Added contact system
This commit is contained in:
		
							parent
							
								
									8258475a63
								
							
						
					
					
						commit
						844e3cb875
					
				| @ -67,6 +67,9 @@ Enter the followind information: | ||||
| ```txt | ||||
| URL_GRAPHQL=https://url-to.strapi-accords-library.com/graphql | ||||
| ACCESS_TOKEN=genatedcode-by-strapi-api | ||||
| SMTP_HOST=email.provider.com | ||||
| SMTP_USER=email@example.com | ||||
| SMTP_PASSWORD=mypassword123 | ||||
| NEXT_PUBLIC_URL_CMS=https://url-to.strapi-accords-library.com/ | ||||
| NEXT_PUBLIC_URL_IMG=https://url-to.img-accords-library.com/ | ||||
| NEXT_PUBLIC_URL_SELF=https://url-to-front-accords-library.com | ||||
|  | ||||
							
								
								
									
										45
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										45
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -14,6 +14,7 @@ | ||||
|         "@tippyjs/react": "^4.2.6", | ||||
|         "markdown-to-jsx": "^7.1.7", | ||||
|         "next": "^12.1.0", | ||||
|         "nodemailer": "^6.7.3", | ||||
|         "react": "17.0.2", | ||||
|         "react-dom": "17.0.2", | ||||
|         "react-image-lightbox": "^5.1.4", | ||||
| @ -22,6 +23,7 @@ | ||||
|       }, | ||||
|       "devDependencies": { | ||||
|         "@types/node": "17.0.21", | ||||
|         "@types/nodemailer": "^6.4.4", | ||||
|         "@types/react": "17.0.40", | ||||
|         "@types/react-dom": "^17.0.13", | ||||
|         "eslint": "8.10.0", | ||||
| @ -489,6 +491,15 @@ | ||||
|       "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "node_modules/@types/nodemailer": { | ||||
|       "version": "6.4.4", | ||||
|       "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.4.tgz", | ||||
|       "integrity": "sha512-Ksw4t7iliXeYGvIQcSIgWQ5BLuC/mljIEbjf615svhZL10PE9t+ei8O9gDaD3FPCasUJn9KTLwz2JFJyiiyuqw==", | ||||
|       "dev": true, | ||||
|       "dependencies": { | ||||
|         "@types/node": "*" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/@types/parse-json": { | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", | ||||
| @ -2493,9 +2504,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/minimist": { | ||||
|       "version": "1.2.5", | ||||
|       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", | ||||
|       "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", | ||||
|       "version": "1.2.6", | ||||
|       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", | ||||
|       "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "node_modules/ms": { | ||||
| @ -2594,6 +2605,14 @@ | ||||
|       "dev": true, | ||||
|       "peer": true | ||||
|     }, | ||||
|     "node_modules/nodemailer": { | ||||
|       "version": "6.7.3", | ||||
|       "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.3.tgz", | ||||
|       "integrity": "sha512-KUdDsspqx89sD4UUyUKzdlUOper3hRkDVkrKh/89G+d9WKsU5ox51NWS4tB1XR5dPUdR4SP0E3molyEfOvSa3g==", | ||||
|       "engines": { | ||||
|         "node": ">=6.0.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/normalize-path": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", | ||||
| @ -4004,6 +4023,15 @@ | ||||
|       "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "@types/nodemailer": { | ||||
|       "version": "6.4.4", | ||||
|       "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.4.tgz", | ||||
|       "integrity": "sha512-Ksw4t7iliXeYGvIQcSIgWQ5BLuC/mljIEbjf615svhZL10PE9t+ei8O9gDaD3FPCasUJn9KTLwz2JFJyiiyuqw==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@types/node": "*" | ||||
|       } | ||||
|     }, | ||||
|     "@types/parse-json": { | ||||
|       "version": "4.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", | ||||
| @ -5485,9 +5513,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "minimist": { | ||||
|       "version": "1.2.5", | ||||
|       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", | ||||
|       "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", | ||||
|       "version": "1.2.6", | ||||
|       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", | ||||
|       "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "ms": { | ||||
| @ -5549,6 +5577,11 @@ | ||||
|       "dev": true, | ||||
|       "peer": true | ||||
|     }, | ||||
|     "nodemailer": { | ||||
|       "version": "6.7.3", | ||||
|       "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.3.tgz", | ||||
|       "integrity": "sha512-KUdDsspqx89sD4UUyUKzdlUOper3hRkDVkrKh/89G+d9WKsU5ox51NWS4tB1XR5dPUdR4SP0E3molyEfOvSa3g==" | ||||
|     }, | ||||
|     "normalize-path": { | ||||
|       "version": "3.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", | ||||
|  | ||||
| @ -16,6 +16,7 @@ | ||||
|     "@tippyjs/react": "^4.2.6", | ||||
|     "markdown-to-jsx": "^7.1.7", | ||||
|     "next": "^12.1.0", | ||||
|     "nodemailer": "^6.7.3", | ||||
|     "react": "17.0.2", | ||||
|     "react-dom": "17.0.2", | ||||
|     "react-image-lightbox": "^5.1.4", | ||||
| @ -24,6 +25,7 @@ | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@types/node": "17.0.21", | ||||
|     "@types/nodemailer": "^6.4.4", | ||||
|     "@types/react": "17.0.40", | ||||
|     "@types/react-dom": "^17.0.13", | ||||
|     "eslint": "8.10.0", | ||||
|  | ||||
| @ -125,6 +125,13 @@ query getWebsiteInterface($language_code: String) { | ||||
|         members | ||||
|         sharing_policy | ||||
|         contact_us | ||||
|         email | ||||
|         email_gdpr_notice | ||||
|         message | ||||
|         send | ||||
|         response_invalid_code | ||||
|         response_invalid_email | ||||
|         response_email_success | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @ -214,6 +214,13 @@ export type GetWebsiteInterfaceQuery = { | ||||
|         members: string; | ||||
|         sharing_policy: string; | ||||
|         contact_us: string; | ||||
|         email: string; | ||||
|         email_gdpr_notice: string; | ||||
|         message: string; | ||||
|         send: string; | ||||
|         response_invalid_code: string; | ||||
|         response_invalid_email: string; | ||||
|         response_email_success: string; | ||||
|       }; | ||||
|     }>; | ||||
|   }; | ||||
|  | ||||
							
								
								
									
										225
									
								
								src/pages/about-us/contact.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								src/pages/about-us/contact.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,225 @@ | ||||
| import SubPanel from "components/Panels/SubPanel"; | ||||
| import { AppStaticProps, getAppStaticProps } from "queries/getAppStaticProps"; | ||||
| import ReturnButton, { | ||||
|   ReturnButtonType, | ||||
| } from "components/PanelComponents/ReturnButton"; | ||||
| import AppLayout from "components/AppLayout"; | ||||
| import ContentPanel from "components/Panels/ContentPanel"; | ||||
| import { GetStaticProps } from "next"; | ||||
| import { getPost, getPostLanguages } from "graphql/operations"; | ||||
| import { GetPostQuery } from "graphql/operations-types"; | ||||
| import { useRouter } from "next/router"; | ||||
| import LanguageSwitcher from "components/LanguageSwitcher"; | ||||
| import Markdawn from "components/Markdown/Markdawn"; | ||||
| import { RequestMailProps, ResponseMailProps } from "pages/api/mail"; | ||||
| import { useState } from "react"; | ||||
| import InsetBox from "components/InsetBox"; | ||||
| import { randomInt } from "queries/helpers"; | ||||
| import TOC from "components/Markdown/TOC"; | ||||
| 
 | ||||
| interface ContactProps extends AppStaticProps { | ||||
|   post: GetPostQuery["posts"]["data"][number]["attributes"]; | ||||
|   locales: string[]; | ||||
| } | ||||
| 
 | ||||
| export default function AboutUs(props: ContactProps): JSX.Element { | ||||
|   const { langui, post, locales } = props; | ||||
|   const router = useRouter(); | ||||
|   const [formResponse, setFormResponse] = useState(""); | ||||
|   const [formCompleted, setFormCompleted] = useState(false); | ||||
| 
 | ||||
|   const random1 = randomInt(0, 10); | ||||
|   const random2 = randomInt(0, 10); | ||||
| 
 | ||||
|   const subPanel = ( | ||||
|     <SubPanel> | ||||
|       <ReturnButton | ||||
|         href="/about-us" | ||||
|         displayOn={ReturnButtonType.Desktop} | ||||
|         langui={langui} | ||||
|         title={langui.about_us} | ||||
|         horizontalLine | ||||
|       /> | ||||
|       {post.translations.length > 0 && post.translations[0].body && ( | ||||
|         <TOC | ||||
|           text={post.translations[0].body} | ||||
|           router={router} | ||||
|           title={post.translations[0].title} | ||||
|         /> | ||||
|       )} | ||||
|     </SubPanel> | ||||
|   ); | ||||
| 
 | ||||
|   const contentPanel = ( | ||||
|     <ContentPanel> | ||||
|       <ReturnButton | ||||
|         href="/about-us" | ||||
|         displayOn={ReturnButtonType.Mobile} | ||||
|         langui={langui} | ||||
|         title={langui.about_us} | ||||
|         className="mb-10" | ||||
|       /> | ||||
|       {locales.includes(router.locale || "en") ? ( | ||||
|         <Markdawn router={router} text={post.translations[0].body} /> | ||||
|       ) : ( | ||||
|         <LanguageSwitcher | ||||
|           locales={locales} | ||||
|           router={router} | ||||
|           languages={props.languages} | ||||
|           langui={props.langui} | ||||
|         /> | ||||
|       )} | ||||
| 
 | ||||
|       <div className="flex flex-col gap-8 text-center"> | ||||
|         <form | ||||
|           className={`gap-8 grid ${ | ||||
|             formCompleted && | ||||
|             "opacity-60 cursor-not-allowed touch-none pointer-events-none" | ||||
|           }`}
 | ||||
|           onSubmit={(e) => { | ||||
|             e.preventDefault(); | ||||
| 
 | ||||
|             if (e.target.verif.value == random1 + random2 && !formCompleted) { | ||||
|               const content: RequestMailProps = { | ||||
|                 name: e.target.name.value, | ||||
|                 email: e.target.email.value, | ||||
|                 message: e.target.message.value, | ||||
|                 formName: "Contact Form", | ||||
|               }; | ||||
|               fetch("/api/mail", { | ||||
|                 method: "POST", | ||||
|                 body: JSON.stringify(content), | ||||
|                 headers: { | ||||
|                   "Content-type": "application/json; charset=UTF-8", | ||||
|                 }, | ||||
|               }) | ||||
|                 .then((response) => response.json()) | ||||
|                 .then((data: ResponseMailProps) => { | ||||
|                   switch (data.code) { | ||||
|                     case "OKAY": | ||||
|                       setFormResponse(langui.response_email_success); | ||||
|                       setFormCompleted(true); | ||||
|                       break; | ||||
| 
 | ||||
|                     case "EENVELOPE": | ||||
|                       langui.response_invalid_email; | ||||
|                       break; | ||||
| 
 | ||||
|                     default: | ||||
|                       setFormResponse(data.message || ""); | ||||
|                       break; | ||||
|                   } | ||||
|                 }); | ||||
|             } else { | ||||
|               setFormResponse(langui.response_invalid_code); | ||||
|             } | ||||
| 
 | ||||
|             router.replace("#send-response"); | ||||
|             e.target.verif.value = ""; | ||||
|           }} | ||||
|         > | ||||
|           <div className="flex flex-col place-items-center gap-1"> | ||||
|             <label htmlFor="name">{langui.name}:</label> | ||||
|             <input | ||||
|               type="text" | ||||
|               className="mobile:w-full" | ||||
|               name="name" | ||||
|               id="name" | ||||
|               required | ||||
|               disabled={formCompleted} | ||||
|             /> | ||||
|           </div> | ||||
| 
 | ||||
|           <div className="flex flex-col place-items-center gap-1"> | ||||
|             <label htmlFor="email">{langui.email}:</label> | ||||
|             <input | ||||
|               type="email" | ||||
|               className="mobile:w-full" | ||||
|               name="email" | ||||
|               id="email" | ||||
|               required | ||||
|               disabled={formCompleted} | ||||
|             /> | ||||
|             <p className="text-sm text-dark italic opacity-70"> | ||||
|               {langui.email_gdpr_notice} | ||||
|             </p> | ||||
|           </div> | ||||
| 
 | ||||
|           <div className="flex flex-col place-items-center gap-1 w-full"> | ||||
|             <label htmlFor="message">{langui.message}:</label> | ||||
|             <textarea | ||||
|               name="message" | ||||
|               id="message" | ||||
|               className="w-full" | ||||
|               rows={8} | ||||
|               required | ||||
|               disabled={formCompleted} | ||||
|             /> | ||||
|           </div> | ||||
| 
 | ||||
|           <div className="grid grid-cols-2 place-items-center"> | ||||
|             <div className="flex flex-row place-items-center gap-2"> | ||||
|               <label | ||||
|                 className="flex-shrink-0" | ||||
|                 htmlFor="verif" | ||||
|               >{`${random1} + ${random2} =`}</label> | ||||
|               <input | ||||
|                 className="w-24" | ||||
|                 type="number" | ||||
|                 name="verif" | ||||
|                 id="verif" | ||||
|                 required | ||||
|                 disabled={formCompleted} | ||||
|               /> | ||||
|             </div> | ||||
| 
 | ||||
|             <input | ||||
|               type="submit" | ||||
|               value={langui.send} | ||||
|               className="w-min !px-6" | ||||
|               disabled={formCompleted} | ||||
|             /> | ||||
|           </div> | ||||
|         </form> | ||||
| 
 | ||||
|         <div id="send-response"> | ||||
|           {formResponse && ( | ||||
|             <InsetBox> | ||||
|               <p>{formResponse}</p> | ||||
|             </InsetBox> | ||||
|           )} | ||||
|         </div> | ||||
|       </div> | ||||
|     </ContentPanel> | ||||
|   ); | ||||
| 
 | ||||
|   return ( | ||||
|     <AppLayout | ||||
|       navTitle={"Contact"} | ||||
|       subPanel={subPanel} | ||||
|       contentPanel={contentPanel} | ||||
|       {...props} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export const getStaticProps: GetStaticProps = async (context) => { | ||||
|   const slug = "contact"; | ||||
|   const props: ContactProps = { | ||||
|     ...(await getAppStaticProps(context)), | ||||
|     post: ( | ||||
|       await getPost({ | ||||
|         slug: slug, | ||||
|         language_code: context.locale || "en", | ||||
|       }) | ||||
|     ).posts.data[0].attributes, | ||||
|     locales: ( | ||||
|       await getPostLanguages({ slug: slug }) | ||||
|     ).posts.data[0].attributes.translations.map((translation) => { | ||||
|       return translation.language.data.attributes.code; | ||||
|     }), | ||||
|   }; | ||||
|   return { | ||||
|     props: props, | ||||
|   }; | ||||
| }; | ||||
| @ -84,7 +84,7 @@ export const getStaticProps: GetStaticProps = async (context) => { | ||||
|     ...(await getAppStaticProps(context)), | ||||
|     post: ( | ||||
|       await getPost({ | ||||
|         slug: "sharing-policy", | ||||
|         slug: slug, | ||||
|         language_code: context.locale || "en", | ||||
|       }) | ||||
|     ).posts.data[0].attributes, | ||||
|  | ||||
							
								
								
									
										51
									
								
								src/pages/api/mail.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/pages/api/mail.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | ||||
| import type { NextApiRequest, NextApiResponse } from "next"; | ||||
| import nodemailer from "nodemailer"; | ||||
| import { SMTPError } from "nodemailer/lib/smtp-connection"; | ||||
| 
 | ||||
| export type ResponseMailProps = { | ||||
|   code?: string; | ||||
|   message?: string; | ||||
| }; | ||||
| 
 | ||||
| export type RequestMailProps = { | ||||
|   name: string; | ||||
|   email: string; | ||||
|   message: string; | ||||
|   formName: string; | ||||
| }; | ||||
| 
 | ||||
| export default async function Mail( | ||||
|   req: NextApiRequest, | ||||
|   res: NextApiResponse<ResponseMailProps> | ||||
| ) { | ||||
|   if (req.method === "POST") { | ||||
|     const body = req.body as RequestMailProps; | ||||
| 
 | ||||
|     let transporter = nodemailer.createTransport({ | ||||
|       host: process.env.SMTP_HOST, | ||||
|       port: 587, | ||||
|       secure: false, // true for 465, false for other ports
 | ||||
|       auth: { | ||||
|         user: process.env.SMTP_USER, | ||||
|         pass: process.env.SMTP_PASSWORD, | ||||
|       }, | ||||
|     }); | ||||
| 
 | ||||
|     // send mail with defined transport object
 | ||||
|     let info = await transporter | ||||
|       .sendMail({ | ||||
|         from: `"${body.name}" <${body.email}>`, | ||||
|         to: "contact@accords-library.com", | ||||
|         subject: `New ${body.formName} from ${body.name}`, | ||||
|         text: body.message, | ||||
|       }) | ||||
|       .catch((reason: SMTPError) => { | ||||
|         res.status(reason.responseCode || 500).json({ | ||||
|           code: reason.code, | ||||
|           message: reason.response, | ||||
|         }); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   res.status(200).json({ code: "OKAY" }); | ||||
| } | ||||
| @ -72,7 +72,7 @@ export default function Editor(props: EditorProps): JSX.Element { | ||||
|               target.select(); | ||||
|               event.preventDefault(); | ||||
|             }} | ||||
|             className="bg-mid rounded-xl p-8 w-full font-monospace" | ||||
|             className="font-monospace" | ||||
|           /> | ||||
|         </div> | ||||
|         <div> | ||||
|  | ||||
| @ -291,3 +291,7 @@ export function slugify(string: string | undefined): string { | ||||
|     .replace(/ /gi, "-") | ||||
|     .toLowerCase(); | ||||
| } | ||||
| 
 | ||||
| export function randomInt(min: number, max: number) { | ||||
|   return Math.floor(Math.random() * (max - min)) + min; | ||||
| } | ||||
|  | ||||
| @ -136,16 +136,26 @@ | ||||
| 
 | ||||
|   /* INPUT */ | ||||
| 
 | ||||
|   input { | ||||
|     @apply rounded-full p-2 text-center bg-light outline outline-mid outline-2 outline-offset-[-2px] hover:outline-[transparent] text-dark hover:bg-mid transition-all; | ||||
|   input, | ||||
|   textarea { | ||||
|     @apply rounded-full p-2 text-center bg-light outline outline-mid outline-2 outline-offset-[-2px] hover:outline-[transparent] text-dark hover:bg-mid transition-all placeholder:text-dark placeholder:opacity-60; | ||||
|   } | ||||
| 
 | ||||
|   input::placeholder { | ||||
|     @apply text-dark opacity-60; | ||||
|   } | ||||
| 
 | ||||
|   input:focus-visible { | ||||
|     @apply outline-none bg-mid shadow-inner-sm; | ||||
|   input:focus-visible, | ||||
|   textarea:focus-within { | ||||
|     @apply outline-none bg-mid shadow-inner-sm shadow-shade; | ||||
|   } | ||||
| 
 | ||||
|   textarea { | ||||
|     @apply rounded-2xl text-left p-6; | ||||
|   } | ||||
| 
 | ||||
|   input[type="submit"] { | ||||
|     @apply grid place-content-center place-items-center border-[1px] border-dark text-dark rounded-full px-4 pt-[0.4rem] pb-[0.5rem] transition-all cursor-pointer hover:text-light hover:bg-dark hover:drop-shadow-shade-lg active:bg-black active:text-light active:drop-shadow-black-lg active:border-black; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DrMint
						DrMint