diff --git a/.gitignore b/.gitignore index 315cb60..ac07e0b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ # misc .DS_Store +.env .env.local .env.development.local .env.test.local diff --git a/package.json b/package.json index 8d3b02c..4c2f9ab 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@testing-library/user-event": "^12.1.10", "add": "^2.0.6", "aos": "^3.0.0-beta.6", + "axios": "^0.23.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-multi-carousel": "^2.6.5", diff --git a/public/manifest.json b/public/manifest.json index 468bae1..080d6c7 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,25 +1,25 @@ { - 'short_name': 'React App', - 'name': 'Create React App Sample', - 'icons': [ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ { - 'src': 'favicon.ico', - 'sizes': '64x64 32x32 24x24 16x16', - 'type': 'image/x-icon' + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" }, { - 'src': 'logo192.png', - 'type': 'image/png', - 'sizes': '192x192' + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" }, { - 'src': 'logo512.png', - 'type': 'image/png', - 'sizes': '512x512' + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" } ], - 'start_url': '.', - 'display': 'standalone', - 'theme_color': '#000000', - 'background_color': '#ffffff' + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" } diff --git a/src/App.js b/src/App.js index 9db09df..4504864 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import Home from './container'; diff --git a/src/assets/form/Frame.svg b/src/assets/form/Frame.svg new file mode 100644 index 0000000..5e1c91a --- /dev/null +++ b/src/assets/form/Frame.svg @@ -0,0 +1,779 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/form/bg.png b/src/assets/form/bg.png new file mode 100644 index 0000000..de4478e Binary files /dev/null and b/src/assets/form/bg.png differ diff --git a/src/config/index.js b/src/config/index.js new file mode 100644 index 0000000..9f849bc --- /dev/null +++ b/src/config/index.js @@ -0,0 +1,9 @@ +const publicRuntimeConfig = { + NODE_ENV: process.env.NODE_ENV || 'production', + API_URL: process.env.REACT_APP_API_URL, + LOCAL_STORAGE_TOKEN_NAME: 'token', +}; + +export const { NODE_ENV, API_URL, LOCAL_STORAGE_TOKEN_NAME } = publicRuntimeConfig; + +export default publicRuntimeConfig.NODE_ENV; diff --git a/src/container/FAQ/Question/question.style.js b/src/container/FAQ/Question/question.style.js index eb06455..a8ed338 100644 --- a/src/container/FAQ/Question/question.style.js +++ b/src/container/FAQ/Question/question.style.js @@ -1,3 +1,5 @@ +import styled from 'styled-components'; + const StyledContainer = styled.div` display: flex; flex-direction: column; diff --git a/src/container/Header/NavBar/index.js b/src/container/Header/NavBar/index.js index 7cb11d6..a4221fd 100644 --- a/src/container/Header/NavBar/index.js +++ b/src/container/Header/NavBar/index.js @@ -13,13 +13,15 @@ const NavBar = () => { Hoạt động - + Timeline FAQ - ĐĂNG KÍ + + ĐĂNG KÍ + ); }; diff --git a/src/container/activities/index.js b/src/container/activities/index.js index 107ced7..3dfcf55 100644 --- a/src/container/activities/index.js +++ b/src/container/activities/index.js @@ -77,7 +77,9 @@ const Activities = () => { Đến với FCode các bạn sẽ được tham gia rất nhiều hoạt động bổ ích, cũng như trau dồi thêm được nhiều kinh nghiệp quý giá - ĐĂNG KÍ + + ĐĂNG KÍ + diff --git a/src/container/activities/style.js b/src/container/activities/style.js index 2cf0c52..c61ced7 100644 --- a/src/container/activities/style.js +++ b/src/container/activities/style.js @@ -60,15 +60,14 @@ export const Detail = styled.p` export const RegisButton = styled.button` width: 98px; height: 44px; - left: calc(50% - 98px / 2); - top: 0px; + transition: all 0.3s; background: #00db96; border-radius: 4px; border: none; color: white; cursor: pointer; - transition: all 0.3s; - box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px; + box-shadow: 0px 4px 5px rgba(0, 0, 0, 0.14), 0px 1px 10px rgba(0, 0, 0, 0.12), + 0px 2px 4px rgba(0, 0, 0, 0.2); &:hover { filter: brightness(95%); } @@ -239,6 +238,9 @@ export const TBHead = styled.h2` @media only screen and (max-width: 1240px) { font-size: 5vw; } + @media only screen and (min-width: 768px) { + margin-bottom: 30px; + } `; export const TBDetail = styled.p` diff --git a/src/container/form/index.js b/src/container/form/index.js new file mode 100644 index 0000000..65a5696 --- /dev/null +++ b/src/container/form/index.js @@ -0,0 +1,218 @@ +import React, { useRef, useState } from 'react'; + +import { useHistory } from 'react-router-dom'; + +import background from '../../assets/form/Frame.svg'; +import { put } from '../../utils/Apicaller'; +import Popup from './popup'; +import { + SectionWrapper, + FormContainer, + FormContent, + ImageContainer, + FormImage, + FormTitle, + FormHead, + FormDetail, + FormWrap, + Label, + NameInput, + InputSmall, + InputSmallLeft, + OptionContainer, + Select, + Option, + SmallSelect, + FormLineWrap, + Input, + CheckBox, + SubmitButton, +} from './style.js'; + +const initialFormData = Object.freeze({ + lname: '', + fname: '', + spec: 'Kĩ thuật phần mềm', + id: '', + sem: 'LUK1', + phone: '', + confirm: false, +}); + +const Form = () => { + const [popupSpec, setPopupSpec] = useState({ isShowing: false, type: '' }); + const [submit, setSubmit] = useState(initialFormData); + const history = useHistory(); + const svg = useRef(); + const handleChange = (e) => { + setSubmit({ + ...submit, + [e.target.name]: + e.target.type === 'checkbox' ? e.target.checked : e.target.value.trim(), + }); + }; + const handleSubmit = async (e) => { + e.preventDefault(); + if (submit.lname === '' || submit.id === '' || submit.fname === '' || submit.phone === '') { + setPopupSpec({ isShowing: true, type: 'missing' }); + } else if (!submit.confirm) { + setPopupSpec({ isShowing: true, type: 'notConfirmed' }); + } else { + setPopupSpec({ isShowing: true, type: 'success' }); + await put('/api/students', { + student: submit, + }); + } + }; + + const animate = () => { + const img = svg.current.contentDocument; + let paths = img.querySelectorAll('path'); + [...paths].forEach((item) => { + item.style.strokeDasharray = item.getTotalLength(); + item.style.fillOpacity = 0; + item.style.strokeWidth = '0.5px'; + item.animate( + [ + { + strokeDashoffset: item.getTotalLength(), + stroke: '#333', + fillOpacity: 0, + }, + { + strokeDashoffset: 0, + stroke: '#333', + fillOpacity: 0, + }, + { + stroke: 'white', + fillOpacity: '0.5', + }, + { + stroke: 'white', + fillOpacity: '1', + }, + ], + { + duration: 4000, + fill: 'forwards', + easing: 'linear', + } + ); + }); + }; + return ( + + {popupSpec.isShowing ? ( + setPopupSpec({ isShowing: false, type: '' })} + redirect={() => history.push('/')} + /> + ) : null} + + + + Đăng kí + + Bạn điền đầy đủ các thông tin dưới đây để nhận thử thách sắp tới từ CLB + nhé! + + + + + + handleChange(e)} + > + handleChange(e)} + > + + + + + + + handleChange(e)} + > + + + + handleChange(e)}> + + + + + + + + + + + + handleChange(e)} + > + handleChange(e)} + > + + + handleSubmit(e)}>ĐẮNG KÍ + + + + + + + ); +}; + +export default Form; diff --git a/src/container/form/popup/index.js b/src/container/form/popup/index.js new file mode 100644 index 0000000..86d86a0 --- /dev/null +++ b/src/container/form/popup/index.js @@ -0,0 +1,60 @@ +import React from 'react'; + +import { Box, Content, Title, Head, Detail, CloseButton, Overlay, PopupOverlay } from './style.js'; + +const Missing = (props) => { + return ( + + + <Head>Đăng kí thất bại</Head> + <Detail>Bạn chưa điền đầy đủ thông tin</Detail> + + ĐÃ HIỂU + + ); +}; +const NotConfirmed = (props) => { + return ( + + + <Head>Đăng kí thất bại</Head> + <Detail>Bạn chưa đồng ý tham gia thử thách</Detail> + + ĐÃ HIỂU + + ); +}; +const Success = (props) => { + return ( + + + <Head>Đăng kí thành công</Head> + <Detail>Bạn hãy chờ thông báo tiếp theo từ CLB nhé</Detail> + + ĐÃ HIỂU + + ); +}; + +const Popup = (props) => { + const renderSwitch = (param) => { + switch (param) { + case 'missing': + return ; + case 'notConfirmed': + return ; + case 'success': + return ; + default: + return null; + } + }; + return ( + + + {renderSwitch(props.type)} + + ); +}; + +export default Popup; diff --git a/src/container/form/popup/style.js b/src/container/form/popup/style.js new file mode 100644 index 0000000..0c2ca49 --- /dev/null +++ b/src/container/form/popup/style.js @@ -0,0 +1,78 @@ +import styled from 'styled-components'; + +export const PopupOverlay = styled.div` + position: fixed; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + overflow: hidden; + z-index: 99; +`; +export const Box = styled.div` + width: 450px; + height: 200px; + background: #ffffff; + filter: drop-shadow(0px 2px 4px rgba(0, 0, 0, 0.14)) + drop-shadow(0px 3px 4px rgba(0, 0, 0, 0.12)) drop-shadow(0px 1px 5px rgba(0, 0, 0, 0.2)); + + border-radius: 12px; + left: 50%; + top: 50%; + margin: auto; +`; + +export const Content = styled.div` + align-items: center; + text-align: center; + padding-top: 20px; +`; + +export const Title = styled.div` + margin-bottom: 25px; +`; + +export const Head = styled.h2` + font-family: Roboto; + font-style: normal; + font-weight: 500; + font-size: 36px; + color: #00d17d; + margin-bottom: 15px; +`; + +export const Detail = styled.p` + font-family: Roboto; + font-style: normal; + font-weight: normal; + font-size: 18px; + color: rgba(0, 0, 0, 0.6); +`; + +export const CloseButton = styled.button` + width: 98px; + height: 44px; + background: #00d17d; + border-radius: 4px; + border: none; + color: white; + font-family: Roboto; + font-style: normal; + font-weight: 500; + font-size: 14px; + cursor: pointer; + box-shadow: 0px 4px 5px rgba(0, 0, 0, 0.14), 0px 1px 10px rgba(0, 0, 0, 0.12), + 0px 2px 4px rgba(0, 0, 0, 0.2); + transition: all 0.3s; + &:hover { + filter: brightness(95%); + } +`; + +export const Overlay = styled.div` + position: absolute; + background-color: rgb(0, 0, 0, 0.3); + width: 100%; + height: 100%; +`; diff --git a/src/container/form/style.js b/src/container/form/style.js new file mode 100644 index 0000000..432e833 --- /dev/null +++ b/src/container/form/style.js @@ -0,0 +1,213 @@ +import styled from 'styled-components'; + +export const SectionWrapper = styled.div` + overflow-x: hidden; + @media (max-width: 568px) { + padding: 8px; + box-sizing: border-box; + } +`; + +export const FormContainer = styled.div` + padding-top: 100px; + width: 100%; + min-height: 100vh; + display: grid; + gap: 25px; + grid-template-rows: 1fr; + grid-template-columns: 1fr 1fr; + + @media (max-width: 1200px) { + grid-template-columns: 1fr 0px; + gap: 0px; + } +`; + +export const FormContent = styled.div` + max-width: 450px; + align-items: center; + margin: auto; +`; + +export const FormTitle = styled.div``; + +export const FormHead = styled.h2` + font-family: Roboto; + font-style: normal; + font-weight: bold; + font-size: 64px; + color: #262727; +`; + +export const FormDetail = styled.p` + font-family: Roboto; + font-style: normal; + font-weight: normal; + font-size: 24px; + color: rgba(0, 0, 0, 0.6); +`; + +export const FormWrap = styled.form` + margin-top: 50px; +`; + +export const Label = styled.label` + font-family: Roboto; + font-style: normal; + font-weight: 500; + font-size: 14px; + line-height: 16px; + color: rgba(0, 0, 0, 0.6); + margin-bottom: 10px; +`; + +export const NameInput = styled.div` + margin-bottom: 20px; + display: flex; + gap: 30px; + + @media (max-width: 576px) { + flex-direction: column; + } +`; + +export const Input = styled.input` + height: 56px; + width: 100%; + border: 1px solid rgba(0, 0, 0, 0.36); + box-sizing: border-box; + border-radius: 4px; + margin-right: 26px; + padding-left: 15px; + margin-bottom: 20px; + padding-right: 15px; + &:focus { + outline: none; + } + ::placeholder { + font-family: Roboto; + font-style: normal; + font-weight: normal; + font-size: 16px; + line-height: 19px; + color: rgba(0, 0, 0, 0.36); + } +`; + +export const InputSmallLeft = styled.input` + width: 100%; + height: 56px; + border: 1px solid rgba(0, 0, 0, 0.36); + box-sizing: border-box; + border-radius: 4px; + padding-left: 15px; + padding-right: 15px; + &:focus { + outline: none; + } + ::placeholder { + font-family: Roboto; + font-style: normal; + font-weight: normal; + font-size: 16px; + line-height: 19px; + color: rgba(0, 0, 0, 0.36); + } +`; + +export const InputSmall = styled.input` + width: 100%; + height: 56px; + border: 1px solid rgba(0, 0, 0, 0.36); + box-sizing: border-box; + border-radius: 4px; + padding-left: 15px; + padding-right: 15px; + &:focus { + outline: none; + } + ::placeholder { + font-family: Roboto; + font-style: normal; + font-weight: normal; + font-size: 16px; + line-height: 19px; + color: rgba(0, 0, 0, 0.36); + } +`; + +export const OptionContainer = styled.div` + display: flex; + margin-bottom: 20px; + justify-content: space-between; + gap: 30px; + + @media (max-width: 576px) { + flex-direction: column; + } +`; + +export const Select = styled.select` + width: 100%; + height: 56px; + padding-left: 15px; + padding-right: 15px; + outline: none; + border: 1px solid rgba(0, 0, 0, 0.36); + margin-bottom: 20px; +`; + +export const Option = styled.option` + height: 100px; +`; + +export const SmallSelect = styled.select` + outline: none; + width: 100%; + height: 56px; + border: 1px solid rgba(0, 0, 0, 0.36); + padding-left: 15px; + padding-right: 15px; +`; + +export const SmallOption = styled.option``; + +export const FormLineWrap = styled.div` + width: 100%; +`; + +export const CheckBox = styled.input` + margin-right: 12px; +`; + +export const SubmitButton = styled.button` + width: 120px; + height: 44px; + background: #00d17d; + box-shadow: 0px 4px 5px rgba(0, 0, 0, 0.14), 0px 1px 10px rgba(0, 0, 0, 0.12), + 0px 2px 4px rgba(0, 0, 0, 0.2); + border-radius: 4px; + margin-top: 50px; + color: white; + border: none; + cursor: pointer; + transition: all 0.3s; + &:hover { + filter: brightness(95%); + } +`; + +export const ImageContainer = styled.div` + position: absolute; + margin-top: auto; + right: 0; + z-index: -1; + @media (max-width: 1200px) { + display: none; + } +`; + +export const FormImage = styled.object` + width: 100%; + height: 100%; +`; diff --git a/src/container/home/Landing/About/index.js b/src/container/home/Landing/About/index.js index 05b1e4f..0ebbb02 100644 --- a/src/container/home/Landing/About/index.js +++ b/src/container/home/Landing/About/index.js @@ -20,8 +20,12 @@ const About = () => { Còn chờ gì nữa, nhanh tay đăng kí nào các bạn! - ĐĂNG KÍ - TIMELINE + + ĐĂNG KÍ + + + TIMELINE + ); diff --git a/src/container/home/submission/index.js b/src/container/home/submission/index.js index d9d6583..5c0be93 100644 --- a/src/container/home/submission/index.js +++ b/src/container/home/submission/index.js @@ -51,7 +51,7 @@ const Submission = () => { return ( - + Giai Đoạn Tuyển Chọn diff --git a/src/container/home/submission/items/index.js b/src/container/home/submission/items/index.js index 07af1c1..0426af8 100644 --- a/src/container/home/submission/items/index.js +++ b/src/container/home/submission/items/index.js @@ -29,7 +29,7 @@ const Items = (props) => { if (isDeatails !== 'Đăng kí') { setOpen(true); } else { - history.push('/activities'); + history.push('/form'); } }; const closePopup = () => setOpen(false); diff --git a/src/container/index.js b/src/container/index.js index 8eb25de..c2aafcd 100644 --- a/src/container/index.js +++ b/src/container/index.js @@ -6,6 +6,7 @@ import FAQ from './FAQ'; import Footer from './Footer'; import Header from './Header'; import Activities from './activities'; +import Form from './form'; import Home from './home'; const LandingPage = () => { @@ -19,6 +20,9 @@ const LandingPage = () => { <Route exact path="/activities"> <Activities /> </Route> + <Route exact path="/form"> + <Form /> + </Route> <Route exact path="/faq"> <FAQ /> </Route> diff --git a/src/index.css b/src/index.css index 6f2fe34..c4d4754 100644 --- a/src/index.css +++ b/src/index.css @@ -9,3 +9,7 @@ img { pointer-events: none; } + +a { + text-decoration: none; +} diff --git a/src/utils/Apicaller.js b/src/utils/Apicaller.js new file mode 100644 index 0000000..484d0f1 --- /dev/null +++ b/src/utils/Apicaller.js @@ -0,0 +1,29 @@ +import axios from 'axios'; + +import { API_URL } from '../config'; + +const request = (endpoint, method, headers, params, body) => { + return axios({ + url: API_URL + endpoint, + method: method, + headers: Object.assign({}, headers), + params: Object.assign({}, params), + data: body, + }); +}; + +export const get = (endpoint, params, headers) => { + return request(endpoint, 'GET', headers, params); +}; + +export const post = (endpoint, body, params, headers) => { + return request(endpoint, 'POST', headers, params, body); +}; + +export const put = (endpoint, body, params, headers) => { + return request(endpoint, 'PUT', headers, params, body); +}; + +export const remove = (endpoint, body, params, headers) => { + return request(endpoint, 'DELETE', headers, params, body); +}; diff --git a/src/utils/index.js b/src/utils/index.js deleted file mode 100644 index e69de29..0000000 diff --git a/yarn.lock b/yarn.lock index b7031f4..5f853c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -944,6 +944,13 @@ aws4@^1.8.0: resolved "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== +axios@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.23.0.tgz#b0fa5d0948a8d1d75e3d5635238b6c4625b05149" + integrity sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg== + dependencies: + follow-redirects "^1.14.4" + axobject-query@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-0.1.0.tgz" @@ -4084,7 +4091,7 @@ flatten@^1.0.2: resolved "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz" integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== -follow-redirects@^1.0.0: +follow-redirects@^1.0.0, follow-redirects@^1.14.4: version "1.14.4" resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz" integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==