diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b6bfb5b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +node_modules +build +.dockerignore +Dockerfile +.git +.gitignore +README.md +npm-debug.log diff --git a/.gitignore b/.gitignore index 4d29575..44c92ca 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,8 @@ # production /build - +# sensitive files +/src/components/apikey.js # misc .DS_Store .env.local diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a7f2adb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +# Step 1: Use a base image with Node.js +FROM node:16 AS build + +# Step 2: Set the working directory inside the container +WORKDIR /app + +# Step 3: Copy package.json and package-lock.json (or yarn.lock) +COPY package*.json ./ + +# Step 4: Install dependencies +RUN npm install + +# Step 5: Copy the rest of your application code +COPY . . + +# Step 6: Build the React application +RUN npm run build + +# Step 7: Use a base image to serve the application +FROM nginx:alpine + +# Step 8: Copy the build output from the previous stage to the Nginx public directory +COPY --from=build /app/build /usr/share/nginx/html + +# Step 9: Expose port 80 to the outside +EXPOSE 80 + +# Step 10: Start the Nginx server +CMD ["nginx", "-g", "daemon off;"] diff --git a/README.md b/README.md index 14306c3..199e5b3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,63 @@ +# My Portfolio -### `npm start` +This is a personal portfolio web application built using React. It showcases various projects and skills and includes a contact form for users to get in touch. -Runs the app in the development mode.\ -Open [http://localhost:3000](http://localhost:3000) to view it in your browser. +## Table of Contents + +- [Getting Started](#getting-started) +- [Features](#features) +- [Installation](#installation) +- [Usage](#usage) +- [EmailJS Setup](#emailjs-setup) +- [Contributing](#contributing) +- [License](#license) + +## Getting Started + +To get a local copy of the project up and running, follow these simple steps. + +### Prerequisites + +Make sure you have the following installed: + +- [Node.js](https://nodejs.org/) (v14 or later) +- [npm](https://www.npmjs.com/) (comes with Node.js) +- [EmailJS](https://www.emailjs.com) + +Create an account on EmailJS, then create a new email service and template. Copy the Service ID, Template ID, and User ID. + +### EmailJS Configuration + +1. **Create a configuration file:** + + Create a file named `emailConfig.js` in the `src/components` directory with the following content: + + ```javascript + // src/components/emailConfig.js + + const emailConfig = { + serviceId: 'yourserviceid', // Replace with your EmailJS Service ID + templateId: 'yourtemplateid', // Replace with your EmailJS Template ID + userId: 'youruserid' // Replace with your EmailJS User ID + }; + + export default emailConfig; +you then import it to the ContactForm.js + +### Installation + +1. **Install the dependencies:** + + +bash + npm install + + +## Usage + +### Running the App + +To start the app in development mode: + +bash + npm start \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 86a5673..ddb86f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,10 +12,12 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "bootstrap": "^5.3.3", + "emailjs-com": "^3.2.0", "react": "^18.3.1", "react-bootstrap": "^2.10.4", "react-bootstrap-icons": "^1.11.4", "react-dom": "^18.3.1", + "react-multi-carousel": "^2.8.5", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" } @@ -7298,6 +7300,15 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.822.tgz", "integrity": "sha512-qJzHIt4dRRFKjHHvaExCrG95F65kUP3xysaEZ4I2+/R/uIyr5Ar5g/rkAnrRz0parRUYwzpqN8Pz1HgoiYQPpg==" }, + "node_modules/emailjs-com": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/emailjs-com/-/emailjs-com-3.2.0.tgz", + "integrity": "sha512-Prbz3E1usiAwGjMNYRv6EsJ5c373cX7/AGnZQwOfrpNJrygQJ15+E9OOq4pU8yC977Z5xMetRfc3WmDX6RcjAA==", + "deprecated": "The SDK name changed to @emailjs/browser", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/emittery": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", @@ -15176,6 +15187,14 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "node_modules/react-multi-carousel": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/react-multi-carousel/-/react-multi-carousel-2.8.5.tgz", + "integrity": "sha512-C5DAvJkfzR2JK9YixZ3oyF9x6R4LW6nzTpIXrl9Oujxi4uqP9SzVVCjl+JLM3tSdqdjAx/oWZK3dTVBSR73Q+w==", + "engines": { + "node": ">=8" + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -23607,6 +23626,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.822.tgz", "integrity": "sha512-qJzHIt4dRRFKjHHvaExCrG95F65kUP3xysaEZ4I2+/R/uIyr5Ar5g/rkAnrRz0parRUYwzpqN8Pz1HgoiYQPpg==" }, + "emailjs-com": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/emailjs-com/-/emailjs-com-3.2.0.tgz", + "integrity": "sha512-Prbz3E1usiAwGjMNYRv6EsJ5c373cX7/AGnZQwOfrpNJrygQJ15+E9OOq4pU8yC977Z5xMetRfc3WmDX6RcjAA==" + }, "emittery": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", @@ -29066,6 +29090,11 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-multi-carousel": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/react-multi-carousel/-/react-multi-carousel-2.8.5.tgz", + "integrity": "sha512-C5DAvJkfzR2JK9YixZ3oyF9x6R4LW6nzTpIXrl9Oujxi4uqP9SzVVCjl+JLM3tSdqdjAx/oWZK3dTVBSR73Q+w==" + }, "react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", diff --git a/package.json b/package.json index e73cf98..7d4ea1d 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,12 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "bootstrap": "^5.3.3", + "emailjs-com": "^3.2.0", "react": "^18.3.1", "react-bootstrap": "^2.10.4", "react-bootstrap-icons": "^1.11.4", "react-dom": "^18.3.1", + "react-multi-carousel": "^2.8.5", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, diff --git a/src/App.css b/src/App.css index 11f535b..f4381fd 100644 --- a/src/App.css +++ b/src/App.css @@ -1,4 +1,4 @@ -/* custom fonts */ +/* Custom fonts */ @font-face { font-family: centra; src: url('./assets/font/CentraNo2-Bold.ttf'); @@ -15,32 +15,34 @@ font-weight: 400; } -/* default CSS */ +/* Default CSS */ +#root { + min-height: 100vh; +} * { margin: 0; padding: 0; box-sizing: border-box; -} +} -html { +html { scroll-behavior: smooth; scroll-padding-top: 75px; -} +} -body { +body { font-weight: 400; - overflow: hidden; position: relative; color: #685454 !important; font-family: 'Centra', sans-serif !important; -} -h1, h2, h3, h4, h5, h6 { - margin: 0; - padding: 0; - line-height: normal; -} +} +h1, h2, h3, h4, h5, h6 { + margin: 0; + padding: 0; + line-height: normal; +} p, a, li, button, ul { margin: 0; @@ -67,35 +69,36 @@ input:focus, textarea:focus, select:focus { outline: none; } -@media (min-width:1700px) { - main .container { - max-width: 100%; - padding: 0 150px; - } +@media (min-width: 1700px) { + main .container { + max-width: 100%; + padding: 0 150px; + } } p.success { - color: green; + color: green; } p.danger { - color: red; + color: red; } -/************ Navbar Css ************/ + +/* Navbar CSS */ nav.navbar { padding: 18px 0; position: fixed; width: 100%; top: 0; z-index: 9999; - transition: 0.32s ease-in-out; + transition: 0.32s ease-in-out; } nav.navbar.scrolled { padding: 0px 0; background-color: #121212; } nav.navbar a.navbar-brand { - width: 9%; + width: 9%; } nav.navbar .navbar-nav .nav-link.navbar-link { font-weight: 400; @@ -107,48 +110,48 @@ nav.navbar .navbar-nav .nav-link.navbar-link { } nav.navbar .navbar-nav a.nav-link.navbar-link:hover, nav.navbar .navbar-nav a.nav-link.navbar-link.active { - opacity: 1; + opacity: 1; } span.navbar-text { - display: flex; - align-items: center; + display: flex; + align-items: center; } .social-icon { - display: inline-block; - margin-left: 14px; + display: inline-block; + margin-left: 14px; } .social-icon a { - width: 42px; - height: 42px; - background: rgba(217, 217, 217, 0.1); - display: inline-flex; - border-radius: 50%; - margin-right: 6px; - align-items: center; - justify-content: center; - line-height: 1; - border: 1px solid rgba(255, 255, 255, 0.5); + width: 42px; + height: 42px; + background: rgba(217, 217, 217, 0.1); + display: inline-flex; + border-radius: 50%; + margin-right: 6px; + align-items: center; + justify-content: center; + line-height: 1; + border: 1px solid rgba(255, 255, 255, 0.5); } .social-icon a::before { - content: ""; - width: 42px; - height: 42px; - position: absolute; - background-color: #ffffff; - border-radius: 50%; - transform: scale(0); - transition: 0.3s ease-in-out; + content: ""; + width: 42px; + height: 42px; + position: absolute; + background-color: #ffffff; + border-radius: 50%; + transform: scale(0); + transition: 0.3s ease-in-out; } .social-icon a:hover::before { - transform: scale(1); + transform: scale(1); } .social-icon a img { - width: 40%; - z-index: 1; - transition: 0.3s ease-in-out; + width: 40%; + z-index: 1; + transition: 0.3s ease-in-out; } .social-icon a:hover img { - filter: brightness(0) saturate(100%) invert(0%) sepia(7%) saturate(98%) hue-rotate(346deg) brightness(95%) contrast(86%); + filter: brightness(0) saturate(100%) invert(0%) sepia(7%) saturate(98%) hue-rotate(346deg) brightness(95%) contrast(86%); } .navbar-text button { font-weight: 700; @@ -157,9 +160,9 @@ span.navbar-text { padding: 18px 34px; font-size: 18px; margin-left: 18px; - position: relative; - background-color: transparent; - transition: 0.3s ease-in-out; + position: relative; + background-color: transparent; + transition: 0.3s ease-in-out; } .navbar-text button span { z-index: 1; @@ -179,15 +182,15 @@ span.navbar-text { color: #121212; } .navbar-text button:hover::before { - content: ""; - width: 100%; - height: 100%; - position: absolute; + content: ""; + width: 100%; + height: 100%; + position: absolute; } nav.navbar .navbar-toggler:active, nav.navbar .navbar-toggler:focus { - outline: none; - box-shadow: none; + outline: none; + box-shadow: none; } nav.navbar .navbar-toggler-icon { width: 24px; @@ -199,43 +202,42 @@ nav.navbar .navbar-toggler-icon { top: -2px; } nav.navbar .navbar-toggler-icon:focus { - border-bottom: 2px solid #fff; + border-bottom: 2px solid #fff; } nav.navbar .navbar-toggler-icon:after, nav.navbar .navbar-toggler-icon:before { - width: 24px; - position: absolute; - height: 2px; - background-color: #fff; - top: 0; - left: 0; - content: ''; - z-index: 2; - transition: all 300ms linear; + width: 24px; + position: absolute; + height: 2px; + background-color: #fff; + top: 0; + left: 0; + content: ''; + z-index: 2; + transition: all 300ms linear; } nav.navbar .navbar-toggler-icon:after { - top: 8px; + top: 8px; } nav.navbar .navbar-toggler[aria-expanded="true"] .navbar-toggler-icon:after { - transform: rotate(45deg); - background-color: #fff; - height: 2px; + transform: rotate(45deg); + background-color: #fff; + height: 2px; } nav.navbar .navbar-toggler[aria-expanded="true"] .navbar-toggler-icon:before { - transform: translateY(8px) rotate(-45deg); - background-color: #fff; - height: 2px; + transform: translateY(8px) rotate(-45deg); + background-color: #fff; + height: 2px; } nav.navbar .navbar-toggler[aria-expanded="true"] .navbar-toggler-icon { - border-color: transparent; + border-color: transparent; } -/* banner */ +/* Banner */ .banner { margin-top: 0; - margin-bottom: 0; padding: 150px 0 100px 0; position: relative; - background-image: url('./assets/img/01.jpg'); + background-image: url('./assets/img/136646.jpg'); background-position: top center; background-size: cover; background-repeat: no-repeat; @@ -288,57 +290,101 @@ nav.navbar .navbar-toggler[aria-expanded="true"] .navbar-toggler-icon { animation: updown 3s linear infinite; } @keyframes updown { - 0% { - transform: translateY(-20px); - } - 50% { - transform: translateY(20px); - } - 100% { - transform: translateY(-20px); - } + 0% { + transform: translateY(-20px); + } + 50% { + transform: translateY(20px); + } + 100% { + transform: translateY(-20px); + } } .txt-rotate > .wrap { border-right: 0.08em solid #666; } +/* General styling for projects section */ +.projects-section { + padding: 0 0 50px 0; + position: relative; +} + +/* Styling for the carousel container */ +.skill-slider { + width: 80%; + margin: 0 auto; + position: relative; +} -.App { - text-align: center; +/* Styling for the cards within the carousel */ +.card-custom { + background-color: #463c3c; /* Light grey background for cards */ + border-radius: 20px; /* Rounded corners */ + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Shadow effect */ + overflow: hidden; /* Ensure content doesn't overflow */ + position: relative; /* Allow for overlapping */ + transition: transform 250ms, box-shadow 250ms; /* Smooth transitions */ } -.App-logo { - height: 40vmin; - pointer-events: none; +/* Image styling */ +.card-custom img { + width: 100%; + border-bottom: 1px solid #ddd; /* Divider line between image and text */ } -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } +/* Styling for card content */ +.card-custom-body { + padding: 1rem; } -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); +/* Hover effects for the card */ +.card-custom:hover { + transform: translateY(-10px) rotate(3deg); /* Move up and rotate */ + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); /* Enhance shadow on hover */ +} + +/* Styling to ensure cards overlap */ +.card-custom:nth-child(n+2) { + margin-left: -40px; /* Overlap effect */ +} + +/* Adjust carousel arrows */ +.react-multi-carousel-arrow { + background-color: #333; /* Dark background for arrows */ + border-radius: 50%; /* Round arrows */ + height: 30px; + width: 30px; + z-index: 10; +} + +.react-multi-carousel-arrow::before { + font-size: 20px; + color: #fff; /* White arrow color */ +} + + +/* contactform */ + +.modal-header { + background-color: #121212; color: white; } -.App-link { - color: #61dafb; +.modal-body { + background-color: #f8f9fa; } -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } +.modal-footer { + background-color: #121212; +} + +.btn-primary { + background-color: #007bff; + border-color: #007bff; } +.btn-primary:hover { + background-color: #0056b3; + border-color: #0056b3; +} diff --git a/src/App.js b/src/App.js index 740ae14..8e4cfc2 100644 --- a/src/App.js +++ b/src/App.js @@ -1,13 +1,17 @@ import './App.css'; import NavBar from './components/Navbar'; import Banner from './components/banner'; +import Projects from './components/Projects'; import 'bootstrap/dist/css/bootstrap.min.css'; function App() { return ( -
- - +
+ +
+ + +
); } diff --git a/src/assets/img/136646.jpg b/src/assets/img/136646.jpg new file mode 100644 index 0000000..5b881d9 Binary files /dev/null and b/src/assets/img/136646.jpg differ diff --git a/src/assets/img/39668.jpg b/src/assets/img/39668.jpg new file mode 100644 index 0000000..464c364 Binary files /dev/null and b/src/assets/img/39668.jpg differ diff --git a/src/assets/img/github.svg b/src/assets/img/github.svg new file mode 100644 index 0000000..248b6d7 --- /dev/null +++ b/src/assets/img/github.svg @@ -0,0 +1,4 @@ + + GitHub + + diff --git a/src/assets/img/linkedln.svg b/src/assets/img/linkedln.svg new file mode 100644 index 0000000..7c4be60 --- /dev/null +++ b/src/assets/img/linkedln.svg @@ -0,0 +1,4 @@ + + LinkedIn + + diff --git a/src/components/ContactForm.js b/src/components/ContactForm.js new file mode 100644 index 0000000..d034d93 --- /dev/null +++ b/src/components/ContactForm.js @@ -0,0 +1,104 @@ +// src/components/ContactForm.js +import React, { useState } from 'react'; +import { Modal, Button, Form } from 'react-bootstrap'; +import emailjs from 'emailjs-com'; +import emailConfig from './apikey'; +const ContactForm = ({ show, handleClose }) => { + const [formData, setFormData] = useState({ + firstName: '', + lastName: '', + email: '', + message: '' + }); + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData({ + ...formData, + [name]: value + }); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + + emailjs.send( + emailConfig.serviceId, // EmailJS service ID + emailConfig.templateId, // EmailJS template ID + formData, + emailConfig.userId // EmailJS user ID + ) + .then((result) => { + alert('Message sent successfully!'); + handleClose(); + }, (error) => { + alert('Failed to send message, please try again.'); + }); + }; + + return ( + + + Contact Me + + +
+ + First Name + + + + + Last Name + + + + + Email + + + + + Message + + + + +
+
+
+ ); +}; + +export default ContactForm; diff --git a/src/components/Navbar.js b/src/components/Navbar.js index c745341..56997d1 100644 --- a/src/components/Navbar.js +++ b/src/components/Navbar.js @@ -1,17 +1,19 @@ -import Container from 'react-bootstrap/Container'; -import Nav from 'react-bootstrap/Nav'; -import Navbar from 'react-bootstrap/Navbar'; -import { useState, useEffect} from "react"; +// src/components/NavBar.js +import React, { useState, useEffect } from 'react'; +import { Container, Nav, Navbar } from 'react-bootstrap'; import logo from '../assets/img/logo.svg'; -import navIcon1 from '../assets/img/nav-icon1.svg'; -import navIcon3 from '../assets/img/nav-icon2.svg'; -import navIcon2 from '../assets/img/nav-icon3.svg'; - +import navIcon from '../assets/img/github.svg'; +import linkedin from '../assets/img/linkedln.svg'; +import ContactForm from './ContactForm'; const NavBar = () => { const [activeLink, setActiveLink] = useState('home'); const [scrolled, setScrolled] = useState(false); -//check for scrolling + const [showContactForm, setShowContactForm] = useState(false); + + const handleShowContactForm = () => setShowContactForm(true); + const handleCloseContactForm = () => setShowContactForm(false); + useEffect(() => { const onScroll = () => { if (window.scrollY > 50) { @@ -19,43 +21,63 @@ const NavBar = () => { } else { setScrolled(false); } - } + }; - window.addEventListener("scroll", onScroll); - // remove event listener when components unmounts - return () => window.removeEventListener("scroll", onScroll); - }, []) + window.addEventListener('scroll', onScroll); + + return () => window.removeEventListener('scroll', onScroll); + }, []); const onUpdateLink = (value) => { - setActiveLink(value) - } + setActiveLink(value); + }; + return ( - + - logo + Logo - + -
- nav-icon1 - nav-icon2 - nav-icon3 -
- + +
+
); -} +}; -export default NavBar; \ No newline at end of file +export default NavBar; diff --git a/src/components/Projects.js b/src/components/Projects.js new file mode 100644 index 0000000..7869d7f --- /dev/null +++ b/src/components/Projects.js @@ -0,0 +1,67 @@ +import React from 'react'; +import Carousel from 'react-multi-carousel'; +import 'react-multi-carousel/lib/styles.css'; + +const projects = [ + { + title: 'Project One', + description: 'This is a brief description of project one.', + imageUrl: 'https://via.placeholder.com/150', + link: 'https://github.com/yourusername/project-one' + }, + { + title: 'Project Two', + description: 'This is a brief description of project two.', + imageUrl: 'https://via.placeholder.com/150', + link: 'https://github.com/yourusername/project-two' + }, + { + title: 'Project Three', + description: 'This is a brief description of project three.', + imageUrl: 'https://via.placeholder.com/150', + link: 'https://github.com/yourusername/project-three' + } +]; + +const Projects = () => { + const handleCardClick = (link) => { + window.open(link, '_blank'); + }; + + const responsive = { + superLargeDesktop: { + breakpoint: { max: 4000, min: 3000 }, + items: 5 + }, + desktop: { + breakpoint: { max: 3000, min: 1024 }, + items: 3 + }, + tablet: { + breakpoint: { max: 1024, min: 464 }, + items: 2 + }, + mobile: { + breakpoint: { max: 464, min: 0 }, + items: 1 + } + }; + + return ( +
+ + {projects.map((project, index) => ( +
handleCardClick(project.link)}> + {project.title} +
+
{project.title}
+

{project.description}

+
+
+ ))} +
+
+ ); +}; + +export default Projects;