Skip to content

Commit

Permalink
Add services and customer care sections to homepage; Add newsletter s…
Browse files Browse the repository at this point in the history
…ubscription form to footer
  • Loading branch information
aelassas committed Dec 26, 2024
1 parent 917664e commit cece32d
Show file tree
Hide file tree
Showing 10 changed files with 465 additions and 8 deletions.
6 changes: 3 additions & 3 deletions api/src/controllers/userController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1494,16 +1494,16 @@ export const sendEmail = async (req: Request, res: Response) => {
}

const { body }: { body: movininTypes.SendEmailPayload } = req
const { from, to, subject, message } = body
const { from, to, subject, message, isContactForm } = body

const mailOptions: nodemailer.SendMailOptions = {
from: env.SMTP_FROM,
to,
subject: i18n.t('CONTACT_SUBJECT'),
subject: isContactForm ? i18n.t('CONTACT_SUBJECT') : subject,
html:
`<p>
${i18n.t('FROM')}: ${from}<br>
${`${i18n.t('SUBJECT')}: ${subject}<br>`}
${(isContactForm && `${i18n.t('SUBJECT')}: ${subject}<br>`) || ''}
${(message && `${i18n.t('MESSAGE')}:<br>${message.replace(/(?:\r\n|\r|\n)/g, '<br>')}<br>`) || ''}
</p>`,
}
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/assets/css/footer.css
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ div.footer div.footer-contact .social-icon:hover {
color: #1a1a1a;
}

div.footer div.newsletter {
margin-top: 80px;
}

div.footer section.payment {
width: 100%;
display: flex;
Expand Down
128 changes: 127 additions & 1 deletion frontend/src/assets/css/home.css
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ div.home-subtitle {

div.home div.search {
z-index: 2;
background-color: #F9F9F9;
background-color: #fff;
width: 100%;
display: flex;
flex-direction: column;
Expand All @@ -102,6 +102,55 @@ div.home div.search div.home-search {
width: fit-content;
}

div.home div.services {
background-color: #fff;
width: 100%;
padding: 40px 0 100px 0;
display: flex;
flex-direction: column;
align-items: center;
}

div.home div.services h1 {
font-weight: 500;
padding: 0 10px;
text-align: center;
}

div.home div.services div.services-boxes {
display: grid;
grid-template-columns: auto auto auto;
}

div.home div.services div.services-boxes div.services-box {
display: flex;
flex-direction: column;
align-items: center;
width: 400px;
margin: 20px;
background-color: #F9F9F9;
padding: 25px;
text-align: center;
}

div.home div.services div.services-boxes div.services-box div.services-icon-wrapper {
padding: 20px;
}

div.home div.services div.services-boxes div.services-box div.services-icon-wrapper .services-icon {
font-size: 48px;
}

div.home div.services div.services-boxes div.services-box div.services-text-wrapper {
display: flex;
flex-direction: column;
}

div.home div.services div.services-boxes div.services-box div.services-text-wrapper span.services-title {
font-weight: bold;
margin-bottom: 15px;
}

div.home div.destinations {
width: 100%;
display: flex;
Expand All @@ -126,6 +175,62 @@ div.home div.home-map {
background-color: #fff;
}

div.home div.customer-care {
background-color: #f9f9f9;
width: 100%;
padding: 140px 0;
display: flex;
flex-direction: column;
align-items: center;
}

div.home div.customer-care div.customer-care-wrapper {
width: 1200px;
display: flex;
flex-direction: row;
}

div.home div.customer-care div.customer-care-text {
padding: 0 160px;
width: 900px;
}

div.home div.customer-care div.customer-care-text div.customer-care-content {
margin-bottom: 30px;
}

div.home div.customer-care div.customer-care-text div.customer-care-boxes {
display: grid;
grid-template-columns: auto auto;
margin-bottom: 40px;
}

div.home div.customer-care div.customer-care-text div.customer-care-boxes div.customer-care-box {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
margin: 5px 0;
}

div.home div.customer-care div.customer-care-text div.customer-care-boxes .customer-care-icon {
margin-right: 10px;
}

div.home div.customer-care div.customer-care-img {
padding: 40px;
flex: 40%;
display: flex;
flex-direction: column;
align-items: center;
}

div.home div.customer-care div.customer-care-img img {
height: 160px;
margin-left: -80px;
display: none;
}

/* Device width is less than or equal to 960px */

@media only screen and (width <=960px) {
Expand Down Expand Up @@ -154,6 +259,11 @@ div.home div.home-map {
max-width: 480px;
}

div.home div.services div.services-boxes {
display: flex;
flex-direction: column;
}

div.home div.home-map {
width: 100%;
padding: 5px;
Expand All @@ -163,6 +273,22 @@ div.home div.home-map {
min-height: 0;
height: 340px;
}

div.home div.customer-care div.customer-care-wrapper {
width: 100%;
display: flex;
flex-direction: column;
}

div.home div.customer-care div.customer-care-text {
padding: 0 20px;
width: 100%;
}

div.home div.customer-care div.customer-care-img img {
height: 160px;
margin-left: 0;
}
}

/* Device width is greater than or equal to 960px */
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/assets/css/newsletter-form.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
form.newsletter-form div.form {
display: flex;
flex-direction: row;
align-items: center;
}

form.newsletter-form div.form .input {
margin-right: 15px;
max-width: 240px;
}

form.newsletter-form div.form .btn {
height: 40px;
padding: 0 30px;
min-width: 120px;
}
4 changes: 4 additions & 0 deletions frontend/src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Instagram,
} from '@mui/icons-material'
import { strings } from '@/lang/footer'
import NewsletterForm from '@/components/NewsletterForm'

import SecurePayment from '@/assets/img/secure-payment.png'
import '@/assets/css/footer.css'
Expand Down Expand Up @@ -49,6 +50,9 @@ const Footer = () => {
<IconButton href="https://www.linkedin.com/" target="_blank" aria-label="LinkedIn" className="social-icon"><LinkedIn /></IconButton>
<IconButton href="https://www.instagram.com/" target="_blank" aria-label="Instagram" className="social-icon"><Instagram /></IconButton>
</div>
<div className="newsletter">
<NewsletterForm />
</div>
</div>
</section>
<section className="payment">
Expand Down
125 changes: 125 additions & 0 deletions frontend/src/components/NewsletterForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React, { useState } from 'react'
import {
Button,
FormControl,
FormHelperText,
InputLabel,
OutlinedInput,
CircularProgress,
} from '@mui/material'
import validator from 'validator'
import * as movininTypes from ':movinin-types'
import env from '@/config/env.config'
import { strings as commonStrings } from '@/lang/common'
import { strings } from '@/lang/newsletter-form'
import * as helper from '@/common/helper'
import * as UserService from '@/services/UserService'
import { useRecaptchaContext, RecaptchaContextType } from '@/context/RecaptchaContext'

import '@/assets/css/newsletter-form.css'

const NewsletterForm = () => {
const { reCaptchaLoaded, generateReCaptchaToken } = useRecaptchaContext() as RecaptchaContextType

const [email, setEmail] = useState('')
const [emailValid, setEmailValid] = useState(true)
const [submitting, setSubmitting] = useState(false)

const validateEmail = (_email: string) => {
if (_email) {
if (validator.isEmail(_email)) {
setEmailValid(true)
return true
}

setEmailValid(false)
return false
}

setEmailValid(true)
return false
}

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
try {
e.preventDefault()
setSubmitting(true)

if (!validateEmail(email)) {
return
}

let recaptchaToken = ''
if (reCaptchaLoaded) {
recaptchaToken = await generateReCaptchaToken()
if (!(await helper.verifyReCaptcha(recaptchaToken))) {
recaptchaToken = ''
}

if (!recaptchaToken) {
helper.error('reCAPTCHA error')
}
}

const payload: movininTypes.SendEmailPayload = {
from: email,
to: env.CONTACT_EMAIL,
subject: 'New Newsletter Subscription',
message: '',
isContactForm: false,
}
const status = await UserService.sendEmail(payload)

if (status === 200) {
setEmail('')
helper.info(strings.SUCCESS)
} else {
helper.error()
}
} catch (err) {
helper.error(err)
} finally {
setSubmitting(false)
}
}

return (
<form className="newsletter-form" onSubmit={handleSubmit}>
<h1>{strings.TITLE}</h1>
<p>{strings.SUB_TITLE}</p>

<div className="form">
<FormControl fullWidth margin="normal" size="small" className="input">
<InputLabel className="required">{commonStrings.EMAIL}</InputLabel>
<OutlinedInput
type="text"
label={commonStrings.EMAIL}
size="small"
error={!emailValid}
required
autoComplete="off"
value={email}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value)

if (!e.target.value) {
setEmailValid(true)
}
}}
/>
<FormHelperText error={!emailValid}>{(!emailValid && commonStrings.EMAIL_NOT_VALID) || ''}</FormHelperText>
</FormControl>

<Button type="submit" variant="contained" className="btn-primary btn" aria-label="Subscribe">
{
submitting
? <CircularProgress color="inherit" size={24} />
: strings.SUBSCRIBE
}
</Button>
</div>
</form>
)
}

export default NewsletterForm
Loading

0 comments on commit cece32d

Please sign in to comment.