├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── avatar.jpg
├── democv.pdf
├── index.html
├── logo.png
├── manifest.json
├── project1.PNG
├── project2.PNG
└── robots.txt
└── src
├── App.css
├── App.js
├── App.test.js
├── components
├── Contacts.js
├── CustomHook.js
├── Home.js
├── NavBar.js
├── Projects.js
└── Skills.js
├── index.css
├── index.js
├── redux
├── actions.js
├── reducer.js
└── store.js
├── reportWebVitals.js
└── setupTests.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Porfolio using ReactJS
3 |
4 | steps to implement the project
5 |
6 | Step 1:
7 | ```
8 | npm install
9 | ```
10 | Step 2:
11 | ```
12 | npm start
13 | ```
14 | This will launch the development server and open your browser, displaying the application at http://localhost:3000/ (or another port if port 3000 is already in use).
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@fortawesome/fontawesome-svg-core": "^6.5.1",
7 | "@fortawesome/free-brands-svg-icons": "^6.5.1",
8 | "@fortawesome/free-solid-svg-icons": "^6.5.1",
9 | "@fortawesome/react-fontawesome": "^0.2.0",
10 | "@testing-library/jest-dom": "^5.17.0",
11 | "@testing-library/react": "^13.4.0",
12 | "@testing-library/user-event": "^13.5.0",
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-redux": "^9.1.0",
16 | "react-scripts": "5.0.1",
17 | "redux": "^5.0.1",
18 | "web-vitals": "^2.1.4"
19 | },
20 | "scripts": {
21 | "start": "react-scripts start",
22 | "build": "react-scripts build",
23 | "test": "react-scripts test",
24 | "eject": "react-scripts eject"
25 | },
26 | "eslintConfig": {
27 | "extends": [
28 | "react-app",
29 | "react-app/jest"
30 | ]
31 | },
32 | "browserslist": {
33 | "production": [
34 | ">0.2%",
35 | "not dead",
36 | "not op_mini all"
37 | ],
38 | "development": [
39 | "last 1 chrome version",
40 | "last 1 firefox version",
41 | "last 1 safari version"
42 | ]
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/public/avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HoanghoDev/portfolio-react/aa486dc2eb9cbb1dcb8c3d809e1cbd572a86a76b/public/avatar.jpg
--------------------------------------------------------------------------------
/public/democv.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HoanghoDev/portfolio-react/aa486dc2eb9cbb1dcb8c3d809e1cbd572a86a76b/public/democv.pdf
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HoanghoDev/portfolio-react/aa486dc2eb9cbb1dcb8c3d809e1cbd572a86a76b/public/logo.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/project1.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HoanghoDev/portfolio-react/aa486dc2eb9cbb1dcb8c3d809e1cbd572a86a76b/public/project1.PNG
--------------------------------------------------------------------------------
/public/project2.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HoanghoDev/portfolio-react/aa486dc2eb9cbb1dcb8c3d809e1cbd572a86a76b/public/project2.PNG
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=MuseoModerno:wght@200;500;700&family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap');
2 |
3 | @import url('https://fonts.googleapis.com/css2?family=MuseoModerno:wght@200;500;700&display=swap');
4 |
5 | body{
6 | margin: 0;
7 | font-family: Poppins;
8 | background-color: #010824;
9 | color: #eee;
10 | font-size: 15px;
11 | min-height: 100vh;
12 | background-image: linear-gradient(
13 | to right, transparent 0 49px, #eee1 49px
14 | ),
15 | linear-gradient(
16 | to bottom, transparent 0 49px, #eee1 49px
17 | );
18 | background-size: 50px 50px;
19 | &::before{
20 | position: fixed;
21 | width: 400px;
22 | height: 400px;
23 | content: '';
24 | background-image: linear-gradient(
25 | to right, #D02E23, #7A43B6
26 | );
27 | z-index: -1;
28 | top: -200px;
29 | left: calc(50% - 200px);
30 | border-radius: 50% 50% 0% 0%;
31 | pointer-events: none;
32 | filter: blur(250px);
33 | }
34 | }
35 | main{
36 | width: 1300px;
37 | max-width: 100%;
38 | margin: auto;
39 | }
40 |
41 | /* navbar */
42 | header{
43 | position: fixed;
44 | width: 1300px;
45 | max-width: 100%;
46 | height: 60px;
47 | top: 0;
48 | left: 50%;
49 | transform: translateX(-50%);
50 | display: flex;
51 | align-items: center;
52 | justify-content: space-between;
53 | padding: 0 20px;
54 | box-sizing: border-box;
55 | z-index: 100;
56 | backdrop-filter: blur(10px);
57 | & img{
58 | width: 50px;
59 | }
60 | & .logo, nav{
61 | display: flex;
62 | align-items: center;
63 | gap: 30px;
64 | & span{
65 | cursor: pointer;
66 | }
67 | & .active{
68 | color: #e945e3;
69 | text-shadow: 0 0 5px #7A43B6;
70 | }
71 | }
72 | & .icon-bar{
73 | width: 30px;
74 | display: none;
75 | }
76 | }
77 | /* home */
78 | section{
79 | width: 1300px;
80 | max-width: 100%;
81 | margin: auto;
82 | padding: 50px;
83 | box-sizing: border-box;
84 | top: 0;
85 | }
86 | /* .delay-02{
87 | animation-delay: 0.2s!important;
88 | }
89 | .delay-04{
90 | animation-delay: 0.4s!important;
91 | }
92 | .delay-06{
93 | animation-delay: 0.6s!important;
94 | } */
95 | section.home{
96 | padding-top: 150px;
97 | display: grid;
98 | grid-template-columns: repeat(2, 1fr);
99 | gap: 50px;
100 | justify-content: space-between;
101 | align-items: center;
102 |
103 | & .content{
104 | font-family: 'MuseoModerno', system-ui;
105 | & .name{
106 | font-size: 7em;
107 | font-weight: bold;
108 | line-height: 1em;
109 | & span{
110 | background-image: linear-gradient(to right, #7BE728, #F3265F, #7F40AC, #7BE728);
111 | background-size: 200% auto;
112 | -webkit-text-fill-color: transparent;
113 | -webkit-background-clip: text;
114 | animation: shine 5s linear infinite;
115 | }
116 | }
117 | & .des{
118 | color: #eee5;
119 | margin: 30px 0;
120 | border-left: .7em solid #e945e3;
121 | padding-left: 2em;
122 | }
123 | & a{
124 | display: block;
125 | font-size: large;
126 | color: #eee;
127 | font-weight: bold;
128 | text-decoration: none;
129 | border: 1px solid #eee;
130 | background-color:transparent;
131 | width: max-content;
132 | padding: 20px 30px;
133 | border-radius: 40px;
134 | }
135 | }
136 | & .avatar{
137 | text-align: right;
138 | & .card{
139 | display: inline-flex;
140 | flex-direction: column;
141 | width: min(100%, 400px);
142 | box-shadow: 0 150px 150px #e945e333;
143 | transition: 0.5s;
144 | &:hover{
145 | box-shadow: 0 150px 150px #e945e355;
146 | }
147 | & img{
148 | width: 100%;
149 | height: min(50vh, 700px);
150 | object-fit: cover;
151 | object-position: top;
152 | clip-path: polygon(81% 0, 100% 18%, 100% 100%, 0 100%, 0 0);
153 | }
154 | & .info{
155 | background-color: #eee;
156 | color: #010824;
157 | display: grid;
158 | grid-template-columns: repeat(2, 1fr);
159 | text-align: center;
160 | gap: 20px;
161 | padding: 20px;
162 | font-size: 1.2em;
163 | & div:nth-child(1), div:nth-child(2), div:nth-child(3){
164 | font-weight: bold;
165 | }
166 | }
167 | }
168 | }
169 | }
170 | section.skills{
171 | & .title{
172 | text-align: center;
173 | font-size: 5vw;
174 | font-family: 'MuseoModerno', system-ui;
175 | }
176 | & .des{
177 | color: #eee5;
178 | text-align: center;
179 | max-width: 500px;
180 | margin: auto;
181 | }
182 | & .list{
183 | margin-top: 50px;
184 | display: grid;
185 | grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
186 | justify-content: space-between;
187 | gap: 3vw;
188 | position: relative;
189 | &::before{
190 | position: absolute;
191 | content: '';
192 | width: 70%;
193 | height: 70%;
194 | background-image: linear-gradient(
195 | -45deg, red, blue
196 | );
197 | top: 50%;
198 | left: 50%;
199 | transform: translate(-50%, -50%);
200 | pointer-events: none;
201 | border-radius: 20px;
202 | background-size: 200% auto;
203 | }
204 | & .item{
205 | backdrop-filter: blur(50px);
206 | padding: 20px;
207 | border-radius: 20px;
208 | background-color: #01082488;
209 | & svg{
210 | font-size: 30px;
211 | background-color: #eee2;
212 | padding: 10px;
213 | border-radius: 10px;
214 | }
215 | & .des{
216 | text-align: left;
217 | width: 100%;
218 | font-size: small;
219 | }
220 | }
221 | }
222 | }
223 | section.projects{
224 | & .title{
225 | margin-top: 100px;
226 | text-align: center;
227 | font-size: 5vw;
228 | font-family: 'MuseoModerno', system-ui;
229 | }
230 | & .des{
231 | color: #eee5;
232 | text-align: center;
233 | max-width: 500px;
234 | margin: auto;
235 | }
236 | & .list{
237 | margin-top: 50px;
238 | & .item{
239 | display: grid;
240 | grid-template-columns: repeat(2, 1fr);
241 | justify-content: space-between;
242 | align-items: center;
243 | margin-bottom: 100px;
244 | gap: 20px;
245 | & .images{
246 | background-image: linear-gradient(
247 | -45deg, #473bb4, #be24a9
248 | );
249 | padding: 40px;
250 | text-align: center;
251 | border-radius: 20px;
252 | overflow: hidden;
253 |
254 | & img{
255 | height: 400px;
256 | border-radius: 10px;
257 | box-shadow: 0 20px 40px #010824;
258 | }
259 | }
260 | & .content{
261 | & h3{
262 | font-size: 3em;
263 | margin: 0;
264 | }
265 | & .des{
266 | text-align: left;
267 | width: 100%;
268 | }
269 | & .mission{
270 | display: grid;
271 | grid-template-columns: 70px 1fr;
272 | align-items: center;
273 | gap: 10px;
274 | margin-top: 10px;
275 | & div:nth-child(1) svg{
276 | background-color: #eee3;
277 | padding: 20px;
278 | font-size: 20px;
279 | border-radius: 15px;
280 | }
281 | }
282 | }
283 | }
284 | & .item:nth-child(2n){
285 | & .images{
286 | grid-column-start: 2;
287 | grid-column-end: 3;
288 | }
289 | & .content{
290 | grid-column-start: 1;
291 | grid-column-end: 2;
292 | grid-row-start: 1;
293 | }
294 | }
295 | }
296 | }
297 |
298 | section.contacts{
299 | padding-bottom: 220px;
300 | & .title{
301 | text-align: center;
302 | font-size: 5vw;
303 | font-family: 'MuseoModerno', system-ui;
304 | }
305 | & .des{
306 | color: #eee5;
307 | text-align: center;
308 | max-width: 500px;
309 | margin: auto;
310 | }
311 | & .list{
312 | text-align: center;
313 | & .item{
314 | margin-top: 50px;
315 | }
316 | }
317 | }
318 |
319 | /* animation */
320 | .animation{
321 | transform: translateY(50px);
322 | filter: blur(20px);
323 | opacity: 0;
324 | transition: 0.5s;
325 | }
326 | .animation.active{
327 | opacity: 1;
328 | filter: blur(0);
329 | transform: translateY(0);
330 | }
331 |
332 |
333 | @keyframes shine {
334 | to {
335 | background-position: 200% center;
336 | }
337 | }
338 |
339 | @media screen and (max-width: 1023px) {
340 | section.home{
341 | & .content{
342 | & .name{
343 | font-size: 4em;
344 | }
345 | }
346 | & .avatar{
347 | & img{
348 | max-height: 500px;
349 | }
350 | }
351 | }
352 | }
353 | @media screen and (max-width: 767px) {
354 | section{
355 | padding: 20px;
356 | &.home{
357 | grid-template-columns: 1fr;
358 | padding-top: 70px;
359 | & .content{
360 | & a{
361 | margin: auto;
362 | font-size: small;
363 | }
364 | }
365 | & .avatar{
366 | grid-row-start: 1;
367 | }
368 | }
369 | &.projects{
370 | & .list{
371 | & .item{
372 | grid-template-columns: 1fr;
373 | & .content{
374 | & h3{
375 | font-size: 1.2em;
376 | margin-bottom: 1em;
377 | }
378 | }
379 | }
380 | & .item:nth-child(2n){
381 | & .content, .images{
382 | grid-row-start: auto;
383 | grid-column-start: auto;
384 | grid-column-end: auto;
385 | }
386 | }
387 | }
388 | }
389 | }
390 | header{
391 | & nav{
392 | width: 80%;
393 | height: 100vh;
394 | position: fixed;
395 | flex-direction: column;
396 | justify-content: start;
397 | align-items: start;
398 | inset: 0 auto 0 0;
399 | background-color: #eee;
400 | color: #0F1225;
401 | padding: 50px;
402 | box-sizing: border-box;
403 | font-weight: bold;
404 | left: -100%;
405 | transition: .5s;
406 | }
407 | & .icon-bar{
408 | display: block;
409 | }
410 | & nav.active{
411 | left: 0;
412 | }
413 | }
414 | }
415 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import './App.css'
2 | import NavBar from './components/NavBar'
3 | import Home from './components/Home'
4 | import Skills from './components/Skills'
5 | import Projects from './components/Projects'
6 | import Contacts from './components/Contacts'
7 |
8 |
9 | function App() {
10 |
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | );
20 | }
21 |
22 | export default App;
23 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render( );
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/src/components/Contacts.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from 'react'
2 | import CustomHook from './CustomHook';
3 |
4 | function Contacts() {
5 | const [listContacts] = useState([
6 | {
7 | title: 'Phone Number',
8 | value: '+84123XXX'
9 | },{
10 | title: 'Email',
11 | value: 'hohoang.dev@gmail.com'
12 | },{
13 | title: 'Instagram',
14 | value: '@lundev.web'
15 | }
16 | ])
17 | const divs = useRef([]);
18 | const scrollTab = useRef();
19 | CustomHook(scrollTab, divs);
20 |
21 | return (
22 |
23 | el && divs.current.push(el)}>
24 | This is my Contacts
25 |
26 | el && divs.current.push(el)}>
27 | {/* 20 */}
28 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Ullam perspiciatis quae veniam amet nesciunt voluptatibus quis consectetur consequatur quisquam harum.
29 |
30 | el && divs.current.push(el)}>
31 | {
32 | listContacts.map((value, key) => (
33 |
34 |
{value.title}
35 |
{value.value}
36 |
37 | ))
38 | }
39 |
40 |
41 | )
42 | }
43 | export default Contacts
44 |
--------------------------------------------------------------------------------
/src/components/CustomHook.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react'
2 | import { useSelector } from 'react-redux';
3 |
4 | const CustomHook = (refTab = null, refList = null) => {
5 | const scrollTab = refTab;
6 | const divs = refList;
7 | const activeTab = useSelector(state => state.activeTab);
8 |
9 | useEffect(() => {
10 | if(scrollTab.current.classList.contains(activeTab)){
11 | const componentNode = scrollTab.current;
12 | componentNode.scrollIntoView({ behavior: 'smooth' });
13 | }
14 | if(divs !== null){
15 | divs.current.forEach((div) => {
16 | div.classList.add('animation');
17 | });
18 | const handlScroll = () => {
19 | const scrollPosition = window.scrollY;
20 | divs.current.forEach((div) => {
21 | const offsetTop = div.getBoundingClientRect().top + scrollPosition;
22 | if (scrollPosition >= offsetTop - (window.innerHeight / (1.5))) {
23 | div.classList.add('active');
24 | }else{
25 | div.classList.remove('active');
26 | }
27 | });
28 | }
29 | window.addEventListener('scroll', handlScroll);
30 | }
31 | }, [activeTab])
32 | }
33 |
34 | export default CustomHook
35 |
--------------------------------------------------------------------------------
/src/components/Home.js:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react'
2 | import CustomHook from './CustomHook';
3 | function Home() {
4 | const scrollTab = useRef();
5 | CustomHook(scrollTab);
6 |
7 | return (
8 |
9 |
10 |
11 | MY NAME IS LUNDEV
12 |
13 |
14 | {/* 30 */}
15 | Lorem ipsum dolor, sit amet consectetur adipisicing elit. Maiores officiis beatae repellendus rem ullam, ipsam nemo dolorem dolorum illo laborum. Ea sed dolor ab qui, doloremque accusantium esse blanditiis possimus!
16 |
17 |
18 |
19 | Download My CV
20 |
21 |
22 |
23 |
24 |
25 |
26 |
Developer
27 |
VietNamese
28 |
03/12
29 |
Male
30 |
31 |
32 |
33 |
34 | )
35 | }
36 |
37 | export default Home
38 |
--------------------------------------------------------------------------------
/src/components/NavBar.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { connect } from "react-redux";
3 | import { useDispatch } from 'react-redux';
4 | import { changeTabActive } from '../redux/actions';
5 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
6 | import { faBars } from "@fortawesome/free-solid-svg-icons";
7 |
8 | const NavBar = ({activeTab}) => {
9 | const dispatch = useDispatch();
10 | const [linkNav] = useState(['home', 'skills', 'projects', 'contacts']);
11 | const [statusNav, changeStatusNav] = useState(null);
12 | const toggleNav = () => {
13 | changeStatusNav(statusNav === null ? 'active' : null);
14 | }
15 | const changeTab = (value) => {
16 | dispatch(changeTabActive(value));
17 | toggleNav();
18 | }
19 | return (
20 |
21 |
22 |
Portfolio
23 |
24 |
25 | {
26 | linkNav.map(value => (
27 | changeTab(value)}>{value}
30 | ))
31 | }
32 |
33 |
34 |
35 |
36 |
37 | )
38 | }
39 | const mapStateToProps = (state) => ({
40 | activeTab: state.activeTab
41 | });
42 |
43 | export default connect(mapStateToProps, { changeTabActive })(NavBar);
--------------------------------------------------------------------------------
/src/components/Projects.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from 'react'
2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3 | import { faPersonCircleQuestion, faEarthAmericas } from '@fortawesome/free-solid-svg-icons';
4 | import CustomHook from './CustomHook';
5 |
6 | function Projects() {
7 | const [listProjects] = useState([
8 | {
9 | name: 'Project Real-time chating in website',
10 | des: 'Eu voluptate sit do labore consectetur in ad esse qui laborum ad eiusmod. Esse ea velit culpa exercitation anim enim reprehenderit. Fugiat nostrud non dolore aliquip quis in ea amet duis.',
11 | mission: 'Back-end Developer, system analysis and design',
12 | language: 'HTML5, CSS3, React JS, SockerIO,...',
13 | images: '/project1.PNG'
14 | },
15 | {
16 | name: 'Project Real-time chating in website',
17 | des: 'Eu voluptate sit do labore consectetur in ad esse qui laborum ad eiusmod. Esse ea velit culpa exercitation anim enim reprehenderit. Fugiat nostrud non dolore aliquip quis in ea amet duis.',
18 | mission: 'Back-end Developer, system analysis and design',
19 | language: 'HTML5, CSS3, React JS, SockerIO,...',
20 | images: '/project2.PNG'
21 | },
22 | {
23 | name: 'Project Real-time chating in website',
24 | des: 'Eu voluptate sit do labore consectetur in ad esse qui laborum ad eiusmod. Esse ea velit culpa exercitation anim enim reprehenderit. Fugiat nostrud non dolore aliquip quis in ea amet duis.',
25 | mission: 'Back-end Developer, system analysis and design',
26 | language: 'HTML5, CSS3, React JS, SockerIO,...',
27 | images: '/project2.PNG'
28 | },
29 |
30 | ]);
31 | const divs = useRef([]);
32 | const scrollTab = useRef();
33 | CustomHook(scrollTab, divs);
34 | return (
35 |
36 | el && divs.current.push(el)}>
37 | This is my Projects
38 |
39 | el && divs.current.push(el)}>
40 | {/* 20 */}
41 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Ullam perspiciatis quae veniam amet nesciunt voluptatibus quis consectetur consequatur quisquam harum.
42 |
43 |
44 | {
45 | listProjects.map((value, key) => (
46 |
el && divs.current.push(el)}>
47 |
48 |
49 |
50 |
51 |
{value.name}
52 |
{value.des}
53 |
54 |
55 |
56 |
Mission
57 |
{value.mission}
58 |
59 |
60 |
61 |
62 |
63 |
Languages
64 |
{value.language}
65 |
66 |
67 |
68 |
69 | ))
70 | }
71 |
72 |
73 | )
74 | }
75 | export default Projects
76 |
--------------------------------------------------------------------------------
/src/components/Skills.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState } from 'react'
2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3 | import { faReact, faHtml5, faCss3, faJs, faVuejs, faLaravel } from '@fortawesome/free-brands-svg-icons';
4 | import CustomHook from './CustomHook';
5 |
6 | function Skills() {
7 | const divs = useRef([]);
8 | const scrollTab = useRef();
9 | CustomHook(scrollTab, divs);
10 | const [listSkills] = useState([
11 | {
12 | name: 'HTML',
13 | des: 'Eu voluptate sit do labore consectetur in ad esse qui laborum ad eiusmod. Esse ea velit culpa exercitation anim enim reprehenderit. Fugiat nostrud non dolore aliquip quis in ea amet duis.',
14 | icon: faHtml5
15 | },
16 | {
17 | name: 'CSS',
18 | des: 'Ad ad in cillum ut labore irure aliqua. Ex sit dolore ipsum id duis nostrud veniam. Nisi duis ut veniam ut eiusmod occaecat ullamco ullamco. Consequat eu sunt ut elit dolor sint magna magna velit ex. Excepteur occaecat reprehenderit tempor veniam.',
19 | icon: faCss3
20 | },
21 | {
22 | name: 'Javascript',
23 | des: 'Sunt nostrud nulla qui cillum mollit aute anim anim aliqua aute magna tempor. Do culpa culpa excepteur officia ut eu deserunt proident sint non ut do magna minim. Sunt et excepteur tempor culpa irure non exercitation. Amet nostrud ex aute incididunt incididunt ipsum.',
24 | icon: faJs},
25 | {
26 | name: 'ReactJs',
27 | des: 'Voluptate qui adipisicing dolore pariatur laboris deserunt consectetur reprehenderit. Esse dolor elit ullamco duis quis aliquip fugiat ipsum nisi est et. Nisi ut deserunt excepteur irure aliquip proident ',
28 | icon: faReact
29 | },
30 | {
31 | name: 'VueJs',
32 | des: 'Laborum commodo reprehenderit anim sunt est. Aliquip ipsum nisi incididunt enim ex id et sit sint magna. Deserunt minim ullamco aute veniam. Do irure nulla ut quis.',
33 | icon: faVuejs
34 | },
35 | {
36 | name: 'Laravel',
37 | des: 'Ullamco incididunt adipisicing laboris ullamco ipsum quis nulla non. Non et irure amet in sint duis Lorem est eiusmod nisi. Aute dolor eiusmod esse et cupidatat ex minim do reprehenderit ut aute. In commodo do consectetur qui occaecat cupidatat sint ullamco dolor tempor ullamco elit.',
38 | icon: faLaravel
39 | }
40 |
41 | ]);
42 | return (
43 |
44 | el && divs.current.push(el)}>
45 | This is my Skills
46 |
47 | el && divs.current.push(el)}>
48 | {/* 20 */}
49 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Ullam perspiciatis quae veniam amet nesciunt voluptatibus quis consectetur consequatur quisquam harum.
50 |
51 |
52 | {
53 | listSkills.map((value, key) => (
54 |
el && divs.current.push(el)}>
55 |
56 |
{ value.name }
57 |
{value.des}
58 |
59 | ))
60 | }
61 |
62 |
63 | )
64 | }
65 |
66 | export default Skills
67 |
68 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HoanghoDev/portfolio-react/aa486dc2eb9cbb1dcb8c3d809e1cbd572a86a76b/src/index.css
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 | import { Provider } from 'react-redux';
7 | import store from './redux/store';
8 |
9 |
10 | const root = ReactDOM.createRoot(document.getElementById('root'));
11 | root.render(
12 |
13 |
14 | ,
15 | );
16 |
17 | // If you want to start measuring performance in your app, pass a function
18 | // to log results (for example: reportWebVitals(console.log))
19 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
20 | reportWebVitals();
21 |
--------------------------------------------------------------------------------
/src/redux/actions.js:
--------------------------------------------------------------------------------
1 | export const changeTabActive = (data) => ({
2 | type: 'ACTIVE_TAB',
3 | payload: data,
4 | });
--------------------------------------------------------------------------------
/src/redux/reducer.js:
--------------------------------------------------------------------------------
1 | const initialState = {
2 | activeTab: 'home'
3 | };
4 |
5 | const rootReducer = (state = initialState, action) => {
6 | switch (action.type) {
7 | case 'ACTIVE_TAB':
8 | return { ...state, activeTab: action.payload };
9 | default:
10 | return state;
11 | }
12 | };
13 |
14 | export default rootReducer;
--------------------------------------------------------------------------------
/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux';
2 | import rootReducer from './reducer.js';
3 |
4 | const store = createStore(rootReducer);
5 | export default store;
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------