2 Commits

Author SHA1 Message Date
  khang 136274aa4e contactform added 6 months ago
  khang 8385f4ee7f Projects card added 6 months ago
9 changed files with 362 additions and 214 deletions
Split View
  1. +2
    -1
      .gitignore
  2. +15
    -0
      package-lock.json
  3. +1
    -0
      package.json
  4. +161
    -121
      src/App.css
  5. +7
    -3
      src/App.js
  6. +104
    -0
      src/components/ContactForm.js
  7. +21
    -25
      src/components/Navbar.js
  8. +51
    -0
      src/components/Projects.js
  9. +0
    -64
      src/components/Skills.js

+ 2
- 1
.gitignore View File

@ -10,7 +10,8 @@
# production
/build
# sensitive files
/src/components/apikey.js
# misc
.DS_Store
.env.local


+ 15
- 0
package-lock.json View File

@ -12,6 +12,7 @@
"@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",
@ -7299,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",
@ -23616,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",


+ 1
- 0
package.json View File

@ -7,6 +7,7 @@
"@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",


+ 161
- 121
src/App.css View File

@ -1,4 +1,4 @@
/* custom fonts */
/* Custom fonts */
@font-face {
font-family: centra;
src: url('./assets/font/CentraNo2-Bold.ttf');
@ -15,36 +15,34 @@
font-weight: 400;
}
/* default CSS */
/* Set a minimum height for the parent container */
/* Default CSS */
#root {
min-height: 100vh; /* 100% of the viewport height */
/* You can adjust this value based on your design */
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;
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;
@ -71,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;
@ -111,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;
@ -161,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;
@ -183,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;
@ -203,37 +202,37 @@ 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;
padding: 150px 0 100px 0;
@ -291,55 +290,96 @@ 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;
}
.skill {
padding: 0 0 50px 0;
/* Projects */
.card-custom {
position: relative;
overflow: hidden;
border: none;
cursor: pointer;
transition: transform 0.3s ease-in-out;
}
.skill-bx {
background: #151515;
border-radius: 64px;
text-align: center;
padding: 60px 50px;
margin-top: -60px;
}
.skill h2 {
font-size: 45px;
font-weight: 700;
}
.skill p {
color: #B8B8B8;
font-size: 18px;
letter-spacing: 0.8px;
line-height: 1.5em;
margin: 14px 0 75px 0;
}
.skill-slider {
width: 80%;
margin: 0 auto;
position: relative;
.card-custom:hover {
transform: scale(1.05);
}
.skill-slider .item img {
width: 50%;
margin: 0 auto 15px auto;
.card-custom img {
transition: transform 0.3s ease-in-out;
}
.background-image-left {
top: 28%;
.card-custom:hover img {
transform: scale(1.1);
}
.card-custom .card-body {
position: absolute;
bottom: 0;
width: 40%;
z-index: -4;
left: 0;
width: 100%;
background: rgba(0, 0, 0, 0.7);
color: #fff;
padding: 20px;
transition: opacity 0.3s ease-in-out;
opacity: 0;
transform: translateY(100%);
}
.card-custom:hover .card-body {
opacity: 1;
transform: translateY(0);
}
.card-custom .card-title {
font-size: 20px;
font-weight: bold;
}
.card-custom .card-text {
font-size: 16px;
}
/* Wrapper for Banner and Projects */
.content-wrapper {
background: url('./assets/img/136646.jpg') no-repeat center center;
background-size: cover;
padding-top: 150px; /* Same as the padding top of the banner */
}
/* contactform */
.modal-header {
background-color: #121212;
color: white;
}
.modal-body {
background-color: #f8f9fa;
}
.modal-footer {
background-color: #121212;
}
.btn-primary {
background-color: #007bff;
border-color: #007bff;
}
.btn-primary:hover {
background-color: #0056b3;
border-color: #0056b3;
}

+ 7
- 3
src/App.js View File

@ -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 (
<div className ="App">
<NavBar />
<Banner />
<div className="App">
<NavBar />
<div className="content-wrapper">
<Banner />
<Projects />
</div>
</div>
);
}


+ 104
- 0
src/components/ContactForm.js View File

@ -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 (
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Contact Me</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form onSubmit={handleSubmit}>
<Form.Group controlId="formFirstName">
<Form.Label>First Name</Form.Label>
<Form.Control
type="text"
placeholder="Enter your first name"
name="firstName"
value={formData.firstName}
onChange={handleChange}
required
/>
</Form.Group>
<Form.Group controlId="formLastName">
<Form.Label>Last Name</Form.Label>
<Form.Control
type="text"
placeholder="Enter your last name"
name="lastName"
value={formData.lastName}
onChange={handleChange}
required
/>
</Form.Group>
<Form.Group controlId="formEmail">
<Form.Label>Email</Form.Label>
<Form.Control
type="email"
placeholder="Enter your email"
name="email"
value={formData.email}
onChange={handleChange}
required
/>
</Form.Group>
<Form.Group controlId="formMessage">
<Form.Label>Message</Form.Label>
<Form.Control
as="textarea"
rows={3}
placeholder="Enter your message"
name="message"
value={formData.message}
onChange={handleChange}
required
/>
</Form.Group>
<Button variant="primary" type="submit">
Send Message
</Button>
</Form>
</Modal.Body>
</Modal>
);
};
export default ContactForm;

+ 21
- 25
src/components/Navbar.js View File

@ -1,14 +1,19 @@
import React, { useState, useEffect } from "react";
import { Container, Nav, Navbar } from "react-bootstrap";
import logo from "../assets/img/logo.svg";
// 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 navIcon from '../assets/img/github.svg';
import linkedin from '../assets/img/linkedln.svg';
import ContactForm from './ContactForm';
import navIcon from "../assets/img/github.svg";
import linkedin from "../assets/img/linkedln.svg"
const NavBar = () => {
const [activeLink, setActiveLink] = useState("home");
const [activeLink, setActiveLink] = useState('home');
const [scrolled, setScrolled] = useState(false);
const [showContactForm, setShowContactForm] = useState(false);
const handleShowContactForm = () => setShowContactForm(true);
const handleCloseContactForm = () => setShowContactForm(false);
// Check for scrolling
useEffect(() => {
const onScroll = () => {
if (window.scrollY > 50) {
@ -18,10 +23,9 @@ const NavBar = () => {
}
};
window.addEventListener("scroll", onScroll);
window.addEventListener('scroll', onScroll);
// Remove event listener when component unmounts
return () => window.removeEventListener("scroll", onScroll);
return () => window.removeEventListener('scroll', onScroll);
}, []);
const onUpdateLink = (value) => {
@ -29,7 +33,7 @@ const NavBar = () => {
};
return (
<Navbar expand="lg" className={scrolled ? "scrolled" : ""}>
<Navbar expand="lg" className={scrolled ? 'scrolled' : ''}>
<Container>
<Navbar.Brand href="#home">
<img src={logo} alt="Logo" />
@ -40,27 +44,18 @@ const NavBar = () => {
<Nav.Link
href="#home"
className={
activeLink === "home" ? "active navbar-link" : "navbar-link"
activeLink === 'home' ? 'active navbar-link' : 'navbar-link'
}
onClick={() => onUpdateLink("home")}
onClick={() => onUpdateLink('home')}
>
Home
</Nav.Link>
<Nav.Link
href="#skills"
className={
activeLink === "skills" ? "active navbar-link" : "navbar-link"
}
onClick={() => onUpdateLink("skills")}
>
Skills
</Nav.Link>
<Nav.Link
href="#projects"
className={
activeLink === "projects" ? "active navbar-link" : "navbar-link"
activeLink === 'projects' ? 'active navbar-link' : 'navbar-link'
}
onClick={() => onUpdateLink("projects")}
onClick={() => onUpdateLink('projects')}
>
Projects
</Nav.Link>
@ -74,12 +69,13 @@ const NavBar = () => {
<img src={linkedin} alt="linkedin Icon" />
</a>
</div>
<button className="vvd" onClick={() => console.log("connect")}>
<button className="vvd" onClick={handleShowContactForm}>
<span>Let's connect!</span>
</button>
</span>
</Navbar.Collapse>
</Container>
<ContactForm show={showContactForm} handleClose={handleCloseContactForm} />
</Navbar>
);
};


+ 51
- 0
src/components/Projects.js View File

@ -0,0 +1,51 @@
import React from 'react';
import { Card, Container, Row, Col } from 'react-bootstrap';
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');
};
return (
<section className="projects-section" id="projects">
<Container>
<Row>
{projects.map((project, index) => (
<Col key={index} sm={12} md={6} lg={4}>
<Card className="mb-4" onClick={() => handleCardClick(project.link)}>
<Card.Img variant="top" src={project.imageUrl} />
<Card.Body>
<Card.Title>{project.title}</Card.Title>
<Card.Text>{project.description}</Card.Text>
</Card.Body>
</Card>
</Col>
))}
</Row>
</Container>
</section>
);
};
export default Projects;

+ 0
- 64
src/components/Skills.js View File

@ -1,64 +0,0 @@
import meter1 from "../assets/img/meter1.svg";
import meter2 from "../assets/img/meter2.svg";
import meter3 from "../assets/img/meter3.svg";
import Carousel from 'react-multi-carousel';
import 'react-multi-carousel/lib/styles.css';
import colorSharp from "../assets/img/color-sharp.png"
const Skills = () => {
const responsive = {
superLargeDesktop: {
// the naming can be any, depends on you.
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 (
<section className="skill" id="skills">
<div className="container">
<div className="row">
<div className="col-12">
<div className="skill-bx wow zoomIn">
<h2>Skills</h2>
<p>Lorem Ipsum is simply dummy text of the printing and typesetting industry.<br></br> Lorem Ipsum has been the industry's standard dummy text.</p>
<Carousel responsive={responsive} infinite={true} className="owl-carousel owl-theme skill-slider">
<div className="item">
<img src={meter1} alt="Image" />
<h5>Web Development</h5>
</div>
<div className="item">
<img src={meter2} alt="Image" />
<h5>Brand Identity</h5>
</div>
<div className="item">
<img src={meter3} alt="Image" />
<h5>Logo Design</h5>
</div>
<div className="item">
<img src={meter1} alt="Image" />
<h5>Web Development</h5>
</div>
</Carousel>
</div>
</div>
</div>
</div>
<img className="background-image-left" src={colorSharp} alt="Image" />
</section>
)
}
export default Skills;

Loading…
Cancel
Save