Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions SUGGESTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Suggestions for Improving the Website

## Easy Changes

### Improve SEO and Performance
1. Optimize images by compressing them and using modern formats like WebP.
2. Implement lazy loading for images to improve page load times.
3. Add meta tags for better SEO.
4. Use a Content Delivery Network (CDN) to serve static assets faster.

### Add More Interactive Elements
1. Add subtle CSS animations to existing components like buttons, images, and text.
2. Implement scroll animations to make elements appear as the user scrolls down the page.

## Medium Changes

### Add Dynamic Content with React State
1. Create a new interactive FAQ accordion component that expands and collapses answers when clicked.
2. Add a carousel component to showcase testimonials or case studies.

### Add Interactive Forms and Modals
1. Enhance the existing contact form by adding form validation and interactive feedback messages.
2. Create a modal component that displays additional information or images when clicked.

## Hard Changes

### Advanced SEO and Performance Improvements
1. Implement server-side rendering (SSR) for better SEO and faster initial page loads.
2. Use code splitting to load only the necessary JavaScript for each page.

### Advanced Interactive Elements
1. Add complex animations and transitions using libraries like Framer Motion.
2. Implement real-time features like live chat or notifications using WebSockets.
31 changes: 31 additions & 0 deletions gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,37 @@ module.exports = {
},
}, // must be after other CSS plugins
'gatsby-plugin-netlify', // make sure to keep it last in the array
{
resolve: 'gatsby-plugin-sharp',
options: {
defaults: {
formats: ['auto', 'webp', 'avif'],
placeholder: 'dominantColor',
quality: 50,
breakpoints: [750, 1080, 1366, 1920],
backgroundColor: 'transparent',
tracedSVGOptions: {},
blurredOptions: {},
jpgOptions: {},
pngOptions: {},
webpOptions: {},
avifOptions: {},
},
},
},
{
resolve: 'gatsby-transformer-sharp',
options: {
// The option defaults to true
checkSupportedExtensions: true,
},
},
{
resolve: 'gatsby-image',
options: {
// Add any options here
},
},
],
// for avoiding CORS while developing Netlify Functions locally
// read more: https://www.gatsbyjs.org/docs/api-proxy/#advanced-proxying
Expand Down
39 changes: 39 additions & 0 deletions src/components/Carousel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import Slider from 'react-slick';
import PropTypes from 'prop-types';
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";

const Carousel = ({ items }) => {
const settings = {
dots: true,
infinite: true,
speed: 500,
slidesToShow: 1,
slidesToScroll: 1,
autoplay: true,
autoplaySpeed: 3000,
};

return (
<Slider {...settings}>
{items.map((item, index) => (
<div key={index}>
<h3>{item.title}</h3>
<p>{item.content}</p>
</div>
))}
</Slider>
);
};

Carousel.propTypes = {
items: PropTypes.arrayOf(
PropTypes.shape({
title: PropTypes.string.isRequired,
content: PropTypes.string.isRequired,
})
).isRequired,
};

export default Carousel;
57 changes: 52 additions & 5 deletions src/components/ContactUs.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,50 @@ export default class ContactUs extends React.Component {
super(props);
this.submitForm = this.submitForm.bind(this);
this.state = {
status: Status.Initial
status: Status.Initial,
name: '',
email: '',
message: '',
errors: {
name: '',
email: '',
message: ''
}
};
}

validateForm() {
const { name, email, message } = this.state;
let errors = { name: '', email: '', message: '' };
let formIsValid = true;

if (!name) {
formIsValid = false;
errors.name = 'Name is required';
}

if (!email) {
formIsValid = false;
errors.email = 'Email is required';
} else if (!/\S+@\S+\.\S+/.test(email)) {
formIsValid = false;
errors.email = 'Email is invalid';
}

if (!message) {
formIsValid = false;
errors.message = 'Message is required';
}

this.setState({ errors });
return formIsValid;
}

submitForm(ev) {
ev.preventDefault();
if (!this.validateForm()) {
return;
}
const form = ev.target;
const data = new FormData(form);
const xhr = new XMLHttpRequest();
Expand All @@ -36,7 +74,13 @@ export default class ContactUs extends React.Component {
this.setState({status: Status.Processing});
}

handleChange = (event) => {
const { name, value } = event.target;
this.setState({ [name]: value });
}

render() {
const { errors } = this.state;
return <span>
<h1 className="has-text-centered is-size-3-mobile is-size-2-desktop has-text-weight-bold">Contact Us</h1>
<p className="has-text-centered has-text-weight-bold">We respond to 100% of messages. Ask us anything.</p>
Expand All @@ -47,20 +91,23 @@ export default class ContactUs extends React.Component {
<div>
<label className="has-text-weight-bold">Message:</label>
<div style={{margin: "auto"}}>
<textarea id="message" name="message" maxLength="6000" rows="7" style={{width: "100%"}}/>
<textarea id="message" name="message" maxLength="6000" rows="7" style={{width: "100%"}} onChange={this.handleChange}/>
{errors.message && <p className="has-text-danger">{errors.message}</p>}
</div>
</div>
<div className="has-text-left has-text-weight-bold" style={{width: "100%", display: "table"}}>
<div style={{width: "50%", float: "left"}}>
<label>Name:</label>
<div>
<input type="text" className="form-control" id="name" name="name" required style={{width: "95%"}}/>
<input type="text" className="form-control" id="name" name="name" required style={{width: "95%"}} onChange={this.handleChange}/>
{errors.name && <p className="has-text-danger">{errors.name}</p>}
</div>
</div>
<div style={{width: "50%", float: "left"}}>
<label>Email:</label>
<div>
<input type="email" className="form-control" id="email" name="_replyto" required style={{width: "100%"}}/>
<input type="email" className="form-control" id="email" name="_replyto" required style={{width: "100%"}} onChange={this.handleChange}/>
{errors.email && <p className="has-text-danger">{errors.email}</p>}
</div>
</div>
</div>
Expand Down Expand Up @@ -88,4 +135,4 @@ export default class ContactUs extends React.Component {
getSubmitButton() {
return <button type="submit" className="btn">Send &rarr;</button>;
}
}
}
28 changes: 28 additions & 0 deletions src/components/FAQAccordion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React, { useState } from 'react';

const FAQAccordion = ({ faqs }) => {
const [activeIndex, setActiveIndex] = useState(null);

const handleToggle = (index) => {
setActiveIndex(activeIndex === index ? null : index);
};

return (
<div className="faq-accordion">
{faqs.map((faq, index) => (
<div key={index} className="faq-item">
<div className="faq-question" onClick={() => handleToggle(index)}>
{faq.question}
</div>
{activeIndex === index && (
<div className="faq-answer">
{faq.answer}
</div>
)}
</div>
))}
</div>
);
};

export default FAQAccordion;
74 changes: 44 additions & 30 deletions src/components/ModalImage.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,57 @@
import React, {useState} from "react";

import React, { useState } from "react";

export default function ModalImage({
src,
altText,
thumbnailWidth,
thumbnailHeight,
modalImageWidth,
modalImageMaxHeight
}) {
const [isActive, setActive] = useState(false);
const toggleActive = () => setActive(!isActive);
return (
<span className="modal-image">
<img src={src} alt={altText}
onClick={toggleActive}
width={thumbnailWidth || '200'} height={thumbnailHeight} style={{marginLeft: "20px", cursor: "pointer"}}/>
<div className={`modal ${isActive ? " is-active" : ''}`}>
<div className="modal-background" onClick={() => {
setActive(!isActive)
}}/>
src,
altText,
thumbnailWidth,
thumbnailHeight,
modalImageWidth,
modalImageMaxHeight,
additionalContent
}) {
const [isActive, setActive] = useState(false);
const toggleActive = () => setActive(!isActive);

return (
<span className="modal-image">
<img
src={src}
alt={altText}
onClick={toggleActive}
width={thumbnailWidth || "200"}
height={thumbnailHeight}
style={{ marginLeft: "20px", cursor: "pointer" }}
/>
<div className={`modal ${isActive ? " is-active" : ""}`}>
<div
className="modal-background"
onClick={() => {
setActive(!isActive);
}}
/>
<div className="modal-content">
<p className="image">
<img
src={src}
alt={altText}
style={{
maxWidth: modalImageWidth || '800px',
maxHeight: modalImageMaxHeight || "96vh"
}}
src={src}
alt={altText}
style={{
maxWidth: modalImageWidth || "800px",
maxHeight: modalImageMaxHeight || "96vh"
}}
/>
</p>
{additionalContent && (
<div className="additional-content">
{additionalContent}
</div>
)}
</div>
<button
className="modal-close is-large"
aria-label="close"
onClick={toggleActive}
className="modal-close is-large"
aria-label="close"
onClick={toggleActive}
/>
</div>
</span>
);
);
}
Loading