├── .nvmrc
├── src
├── App.css
├── pages
│ ├── Home
│ │ ├── Home.module.css
│ │ ├── SectionFive
│ │ │ ├── SectionFive.module.css
│ │ │ └── SectionFive.jsx
│ │ ├── SectionTwo
│ │ │ ├── SectionTwo.module.css
│ │ │ └── SectionTwo.jsx
│ │ ├── SectionSeven
│ │ │ ├── SectionSeven.module.css
│ │ │ └── SectionSeven.jsx
│ │ ├── Home.jsx
│ │ ├── SectionFour
│ │ │ ├── SectionFour.module.css
│ │ │ └── SectionFour.jsx
│ │ ├── SectionThree
│ │ │ ├── SectionThree.module.css
│ │ │ └── SectionThree.jsx
│ │ ├── SectionSix
│ │ │ ├── SectionSix.module.css
│ │ │ └── SectionSix.jsx
│ │ └── SectionOne
│ │ │ ├── SectionOne.jsx
│ │ │ └── SectionOne.module.css
│ ├── Kenya
│ │ └── Kenya.jsx
│ ├── Morocco
│ │ └── Morocco.jsx
│ ├── Namibia
│ │ └── Namibia.jsx
│ ├── Nigeria
│ │ └── Nigeria.jsx
│ ├── CompanyRegistration
│ │ ├── sortedCountries.jsx
│ │ ├── CompanyRegistration.module.css
│ │ ├── ContactInformation.jsx
│ │ ├── BasicInformation.jsx
│ │ └── CompanyRegistration.jsx
│ ├── Register
│ │ ├── Register.module.css
│ │ └── Register.jsx
│ └── Login
│ │ ├── Login.module.css
│ │ └── Login.jsx
├── assets
│ ├── login.webp
│ ├── flag-japan.png
│ ├── invest_africa.webp
│ ├── fonts
│ │ ├── Poppins-Bold.woff2
│ │ ├── Poppins-Medium.woff2
│ │ ├── Poppins-Regular.woff2
│ │ └── Poppins-SemiBold.woff2
│ └── images
│ │ └── Home
│ │ ├── SectionThree
│ │ ├── map.webp
│ │ ├── kenya.webp
│ │ ├── morocco.webp
│ │ ├── namibia.webp
│ │ └── nigeria.webp
│ │ ├── home-section-1.webp
│ │ ├── home-section-2.webp
│ │ ├── home-section-3.webp
│ │ ├── SectionSix
│ │ ├── Section6.png
│ │ ├── thumbnailOne.png
│ │ └── thumbnailThree.png
│ │ ├── SectionFour
│ │ └── sectionfour.webp
│ │ ├── SectionTwo
│ │ ├── pro_business.webp
│ │ ├── growing_economies.webp
│ │ └── youthful_population.webp
│ │ ├── SectionFive
│ │ ├── industries_food.webp
│ │ ├── industries_power.webp
│ │ ├── industries_health.webp
│ │ ├── industries_mining.webp
│ │ ├── industries_oil_gas.webp
│ │ ├── industries_tourism.webp
│ │ ├── industries_agriculture.webp
│ │ ├── industries_manufacturing.webp
│ │ └── industries_infrastructure.webp
│ │ └── SectionSeven
│ │ └── home_contactus.webp
├── ScrolltoTop.js
├── setupTests.js
├── reportWebVitals.js
├── index.jsx
├── components
│ ├── CountryDescription
│ │ ├── CountryDescription.module.css
│ │ └── CountryDescription.jsx
│ ├── Button
│ │ ├── OutlinedButton
│ │ │ ├── OutlinedButton.jsx
│ │ │ └── OutlinedButton.module.css
│ │ ├── Button.module.css
│ │ └── Button.jsx
│ ├── ForgotPassword
│ │ ├── ForgotPassword.css
│ │ └── ForgotPassword.jsx
│ ├── InputBox
│ │ ├── DropdownBox.jsx
│ │ ├── TextBox.jsx
│ │ ├── InputBox.module.css
│ │ └── InputBox.jsx
│ ├── Cards
│ │ ├── Cards.jsx
│ │ └── Cards.module.css
│ ├── CardTransition
│ │ └── CardTransition.jsx
│ ├── Footer
│ │ ├── Footer.jsx
│ │ └── Footer.module.css
│ ├── Alert
│ │ ├── Alert.jsx
│ │ └── Alert.module.css
│ ├── FadeTransition
│ │ └── FadeTransition.jsx
│ └── Navbar
│ │ ├── Navbar.module.css
│ │ └── Navbar.jsx
├── data
│ ├── oppurtunities.js
│ ├── countries.js
│ ├── whyInvest.js
│ ├── successStories.js
│ ├── industries.js
│ └── countryContent.js
├── hooks
│ └── useStaggered.jsx
├── App.jsx
├── utils
│ ├── validateUser.jsx
│ └── validateCompany.jsx
├── index.css
└── reset.css
├── .env.dist
├── public
├── favicon.ico
├── robots.txt
└── manifest.json
├── .husky
└── pre-commit
├── .prettierrc
├── .gitattributes
├── .prettierignore
├── .gitignore
├── .hintrc
├── vite.config.js
├── .github
└── workflows
│ └── ci.yml
├── LICENSE
├── .eslintrc.cjs
├── README.md
├── index.html
└── package.json
/.nvmrc:
--------------------------------------------------------------------------------
1 | 20.10.0
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.env.dist:
--------------------------------------------------------------------------------
1 | VITE_APP_BACKEND_URL=
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/pages/Home/Home.module.css:
--------------------------------------------------------------------------------
1 | .home_wrapper {
2 | position: relative;
3 | margin: 0;
4 | }
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/src/assets/login.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/login.webp
--------------------------------------------------------------------------------
/src/assets/flag-japan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/flag-japan.png
--------------------------------------------------------------------------------
/src/assets/invest_africa.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/invest_africa.webp
--------------------------------------------------------------------------------
/src/assets/fonts/Poppins-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/fonts/Poppins-Bold.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/Poppins-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/fonts/Poppins-Medium.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/Poppins-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/fonts/Poppins-Regular.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/Poppins-SemiBold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/fonts/Poppins-SemiBold.woff2
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionThree/map.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionThree/map.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/home-section-1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/home-section-1.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/home-section-2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/home-section-2.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/home-section-3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/home-section-3.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionSix/Section6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionSix/Section6.png
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionThree/kenya.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionThree/kenya.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionThree/morocco.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionThree/morocco.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionThree/namibia.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionThree/namibia.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionThree/nigeria.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionThree/nigeria.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionFour/sectionfour.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionFour/sectionfour.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionSix/thumbnailOne.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionSix/thumbnailOne.png
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionTwo/pro_business.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionTwo/pro_business.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionSix/thumbnailThree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionSix/thumbnailThree.png
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionFive/industries_food.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionFive/industries_food.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionFive/industries_power.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionFive/industries_power.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionSeven/home_contactus.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionSeven/home_contactus.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionTwo/growing_economies.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionTwo/growing_economies.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionFive/industries_health.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionFive/industries_health.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionFive/industries_mining.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionFive/industries_mining.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionFive/industries_oil_gas.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionFive/industries_oil_gas.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionFive/industries_tourism.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionFive/industries_tourism.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionTwo/youthful_population.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionTwo/youthful_population.webp
--------------------------------------------------------------------------------
/src/pages/Kenya/Kenya.jsx:
--------------------------------------------------------------------------------
1 | function Kenya() {
2 | return (
3 |
4 |
Kenya!!
5 |
6 | );
7 | }
8 |
9 | export default Kenya;
10 |
--------------------------------------------------------------------------------
/src/pages/Morocco/Morocco.jsx:
--------------------------------------------------------------------------------
1 | function Morocco() {
2 | return (
3 |
4 |
Marocco
5 |
6 | );
7 | }
8 |
9 | export default Morocco;
10 |
--------------------------------------------------------------------------------
/src/pages/Namibia/Namibia.jsx:
--------------------------------------------------------------------------------
1 | function Namibia() {
2 | return (
3 |
4 |
Namibia
5 |
6 | );
7 | }
8 |
9 | export default Namibia;
10 |
--------------------------------------------------------------------------------
/src/pages/Nigeria/Nigeria.jsx:
--------------------------------------------------------------------------------
1 | function Nigeria() {
2 | return (
3 |
4 |
Nigeria
5 |
6 | );
7 | }
8 |
9 | export default Nigeria;
10 |
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionFive/industries_agriculture.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionFive/industries_agriculture.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionFive/industries_manufacturing.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionFive/industries_manufacturing.webp
--------------------------------------------------------------------------------
/src/assets/images/Home/SectionFive/industries_infrastructure.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ha12rY/another-portfolio/HEAD/src/assets/images/Home/SectionFive/industries_infrastructure.webp
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "printWidth": 100,
8 | "arrowParens": "avoid",
9 | "endOfLine": "lf"
10 | }
11 |
--------------------------------------------------------------------------------
/src/ScrolltoTop.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useLocation } from 'react-router-dom';
3 |
4 | export default function ScrollToTop() {
5 | const { pathname } = useLocation();
6 |
7 | useEffect(() => {
8 | window.scrollTo(0, 0);
9 | }, [pathname]);
10 |
11 | return null;
12 | }
13 |
--------------------------------------------------------------------------------
/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 | /* eslint-disable import/no-extraneous-dependencies */
6 | import '@testing-library/jest-dom';
7 |
--------------------------------------------------------------------------------
/src/pages/CompanyRegistration/sortedCountries.jsx:
--------------------------------------------------------------------------------
1 | import { countries } from 'countries-list';
2 |
3 | const countryOptions = Object.values(countries).map(country => ({
4 | label: country.name,
5 | value: country.name,
6 | }));
7 |
8 | countryOptions.sort((a, b) => a.label.localeCompare(b.label));
9 |
10 | export default countryOptions;
11 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set the default behavior, in case people don't have core.autocrlf set.
2 | * text=auto eol=lf
3 |
4 | # Treat .jpg, .png, and .gif files as binary, so Git won't try to change them.
5 | *.jpg binary
6 | *.png binary
7 | *.gif binary
8 |
9 | # Indicate that files in the scripts directory should be treated as shell scripts.
10 | scripts/** eol=lf
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Invest Africa",
3 | "name": "Invest Africa from Adam-I Japan",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Dependency directories
2 | /node_modules
3 |
4 | # Build output directories
5 | /build
6 | /dist
7 |
8 | # Hidden system files
9 | .*.swp
10 | .DS_Store
11 |
12 | # Misc
13 | # Add other directories or files that don't need formatting
14 | # such as images, design files, etc.
15 |
16 | # Configuration files
17 | /.eslintrc.js
18 | /.github/workflows/ci.yml
19 | /.gitignore
20 | /.env
21 | /.nvrmc
22 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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
17 | .env.local
18 | .env.production
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/.hintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "development"
4 | ],
5 | "hints": {
6 | "compat-api/css": [
7 | "default",
8 | {
9 | "ignore": [
10 | "scale"
11 | ]
12 | }
13 | ]
14 | },
15 | "browserslist": [
16 | "defaults",
17 | "not ie 11",
18 | "not firefox <= 115",
19 | "not safari <= 16.5",
20 | "not and_ff <= 116",
21 | "not firefox <= 116",
22 | "not opera <= 101"
23 | ]
24 | }
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | import { defineConfig } from 'vite';
3 | import react from '@vitejs/plugin-react';
4 | import viteTsconfigPaths from 'vite-tsconfig-paths';
5 |
6 | export default defineConfig({
7 | base: '',
8 | build: {
9 | outDir: './build',
10 | },
11 | publicDir: './public',
12 | plugins: [react(), viteTsconfigPaths()],
13 | server: {
14 | host: '0.0.0.0',
15 | open: true,
16 | port: 3000,
17 | },
18 | });
19 |
--------------------------------------------------------------------------------
/src/pages/Home/SectionFive/SectionFive.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | background-color: white;
3 | padding: 3rem;
4 | }
5 |
6 | .card_wrapper {
7 | display: flex;
8 | flex-wrap: wrap;
9 | justify-content: center;
10 | gap: 2rem;
11 | }
12 |
13 | .title_wrapper {
14 | margin-block-end: 4rem;
15 | }
16 |
17 | .title {
18 | color: var(--clr-green);
19 | font-weight: var(--fw-semibold);
20 | }
21 |
22 | @media (max-width: 600px) {
23 | .wrapper {
24 | padding: 2rem;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/pages/Home/SectionTwo/SectionTwo.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | background-color: white;
3 | padding: 3rem;
4 | }
5 |
6 | .card_wrapper {
7 | display: flex;
8 | flex-wrap: wrap;
9 | justify-content: center;
10 | gap: 2.5rem;
11 | }
12 |
13 | .title {
14 | font-weight: var(--fw-semibold);
15 | color: var(--clr-green);
16 | margin-bottom: 3rem;
17 | }
18 |
19 | @media (max-width: 992px) {
20 | .wrapper {
21 | padding: 2rem;
22 | }
23 |
24 | .card_wrapper {
25 | gap: 2rem;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: React CI/CD Pipeline
2 |
3 | on:
4 | pull_request:
5 | branches: [ main, dev ]
6 | types: [opened, synchronize, reopened]
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v2
14 |
15 | - name: Set up Node.js
16 | uses: actions/setup-node@v2
17 | with:
18 | node-version: '20.10.0'
19 |
20 | - name: Install dependencies
21 | run: npm install
22 |
23 | - name: Run lint
24 | run: npm run lint
25 |
26 | # - name: Run tests
27 | # run: npm test
28 |
29 | - name: Build
30 | run: npm run build
31 |
--------------------------------------------------------------------------------
/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import { BrowserRouter } from 'react-router-dom';
5 | import App from './App';
6 | import reportWebVitals from './reportWebVitals';
7 |
8 | const root = ReactDOM.createRoot(document.getElementById('root'));
9 | root.render(
10 |
11 |
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/components/CountryDescription/CountryDescription.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | display: flex;
3 | flex-direction: column;
4 | gap: 1rem;
5 | margin-bottom: 2rem;
6 | }
7 |
8 | .content {
9 | display: flex;
10 | align-items: center;
11 | gap: 0.5rem;
12 | }
13 |
14 | .description {
15 | width: 100%;
16 | height: 70px; /* ?Prevent map jumping around on hover */
17 | }
18 |
19 | .description p {
20 | color: black;
21 | font-size: var(--fs-smaller);
22 | line-height: normal;
23 | }
24 |
25 | @media (max-width: 992px) {
26 | .description p {
27 | max-width: 60vw;
28 | }
29 | }
30 |
31 | @media (max-width: 600px) {
32 | .description {
33 | height: 110px; /* ?Prevent map jumping around on tap */
34 | }
35 |
36 | .description p {
37 | max-width: 80vw;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/pages/Home/SectionTwo/SectionTwo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import FadeTransition from '../../../components/FadeTransition/FadeTransition';
3 | import CardTransition from '../../../components/CardTransition/CardTransition';
4 | import styles from './SectionTwo.module.css';
5 | import investData from '../../../data/whyInvest';
6 |
7 | function SectionTwo() {
8 | return (
9 |
10 |
11 | Why Invest in Africa?
12 |
13 |
14 |
15 | );
16 | }
17 |
18 | export default SectionTwo;
19 |
--------------------------------------------------------------------------------
/src/components/Button/OutlinedButton/OutlinedButton.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styles from './OutlinedButton.module.css';
4 |
5 | function OutlinedButton(props) {
6 | const { color, onClick } = props;
7 | const colorClass = color === 'white' ? styles.white_color : styles.black_color;
8 | return (
9 |
10 |
13 |
14 | );
15 | }
16 |
17 | OutlinedButton.propTypes = {
18 | color: PropTypes.oneOf(['white', 'black']),
19 | onClick: PropTypes.func,
20 | };
21 |
22 | OutlinedButton.defaultProps = {
23 | color: 'white',
24 | onClick: null,
25 | };
26 |
27 | export default OutlinedButton;
28 |
--------------------------------------------------------------------------------
/src/pages/Home/SectionSeven/SectionSeven.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | position: relative;
3 | background-image: url('../../../assets/images/Home/SectionSeven/home_contactus.webp');
4 | background-repeat: no-repeat;
5 | background-position: center center;
6 | background-size: cover;
7 | height: 90vh;
8 | overflow-y: hidden;
9 | }
10 |
11 | .content {
12 | position: absolute;
13 | padding: 3rem;
14 | }
15 |
16 | .title {
17 | color: var(--clr-white90);
18 | font-weight: var(--fw-semibold);
19 | padding-block-end: 2rem;
20 | }
21 |
22 | @media (max-width: 992px) {
23 | .wrapper {
24 | height: 75vh;
25 | }
26 | .content {
27 | padding: 3rem 2rem;
28 | }
29 | }
30 |
31 | @media (max-width: 600px) {
32 | .title {
33 | font-size: var(--fs-h4);
34 | }
35 | .content {
36 | padding: 3rem 2rem;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/data/oppurtunities.js:
--------------------------------------------------------------------------------
1 | const oppurtunities = [
2 | {
3 | id: 1,
4 | title: '-Human Capital: ',
5 | body: "Investing in Africa's human capital promises access to a dynamic, rapidly growing workforce primed for innovation.",
6 | },
7 | {
8 | id: 2,
9 | title: '-Natural Resources: ',
10 | body: "Africa's rich natural resources offer lucrative opportunities for sustainable extraction and value-added processing.",
11 | },
12 | {
13 | id: 3,
14 | title: '-Finance: ',
15 | body: "Africa's expanding financial sector presents unique investment prospects in fintech and inclusive banking services.",
16 | },
17 | {
18 | id: 4,
19 | title: '-Infrastructure: ',
20 | body: "Investing in Africa's infrastructure development is key to unlocking economic potential and ensuring high-yield returns.",
21 | },
22 | ];
23 |
24 | export default oppurtunities;
25 |
--------------------------------------------------------------------------------
/src/pages/Home/Home.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './Home.module.css';
3 | import Footer from '../../components/Footer/Footer';
4 | import SectionOne from './SectionOne/SectionOne';
5 | import SectionTwo from './SectionTwo/SectionTwo';
6 | import SectionThree from './SectionThree/SectionThree';
7 | import SectionFour from './SectionFour/SectionFour';
8 | import SectionFive from './SectionFive/SectionFive';
9 | import SectionSix from './SectionSix/SectionSix';
10 | import SectionSeven from './SectionSeven/SectionSeven';
11 |
12 | function Home() {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | }
26 |
27 | export default Home;
28 |
--------------------------------------------------------------------------------
/src/components/Button/Button.module.css:
--------------------------------------------------------------------------------
1 | .button {
2 | color: black;
3 | padding: 1rem 3rem;
4 | background-color: var(--clr-white70);
5 | border-radius: 500px;
6 | transition: background-color 0.1s linear;
7 | }
8 |
9 | .button:hover,
10 | .button:focus-visible {
11 | background-color: var(--clr-white90);
12 | transition: background-color 0.1s linear;
13 | }
14 |
15 | .button:active {
16 | background-color: var(--clr-white70);
17 | }
18 |
19 | .button:disabled,
20 | .button[disabled]{
21 | opacity: 0.5;
22 | pointer-events: none;
23 | }
24 |
25 | .text {
26 | font-weight: var(--fw-semibold);
27 | }
28 |
29 | .action {
30 | background-color: var(--clr-lyellow);
31 | }
32 |
33 | .action:hover,
34 | .action:focus-visible {
35 | background-color: var(--clr-yellow);
36 | transition: background-color 0.1s linear;
37 | }
38 |
39 | .action:active {
40 | background-color: var(--clr-lyellow);
41 | }
42 |
--------------------------------------------------------------------------------
/src/pages/Home/SectionFive/SectionFive.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './SectionFive.module.css';
3 | import industries from '../../../data/industries';
4 | import FadeTransition from '../../../components/FadeTransition/FadeTransition';
5 | import CardTransition from '../../../components/CardTransition/CardTransition';
6 |
7 | function SectionFive() {
8 | return (
9 |
10 |
17 | Our leading Industires
18 |
19 |
24 |
25 | );
26 | }
27 |
28 | export default SectionFive;
29 |
--------------------------------------------------------------------------------
/src/components/Button/Button.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styles from './Button.module.css';
4 |
5 | function Button(props) {
6 | const { isAction, text, isDisabled, isSubmit, onClick } = props;
7 | return (
8 |
9 |
17 |
18 | );
19 | }
20 |
21 | Button.propTypes = {
22 | text: PropTypes.string.isRequired,
23 | onClick: PropTypes.func,
24 | isDisabled: PropTypes.bool,
25 | isSubmit: PropTypes.bool,
26 | isAction: PropTypes.bool,
27 | };
28 |
29 | Button.defaultProps = {
30 | onClick: () => {},
31 | isDisabled: false,
32 | isSubmit: false,
33 | isAction: false,
34 | };
35 |
36 | export default Button;
37 |
--------------------------------------------------------------------------------
/src/components/ForgotPassword/ForgotPassword.css:
--------------------------------------------------------------------------------
1 | .container{
2 | display: flex;
3 | flex-direction:column;
4 | align-items: center;
5 | margin-top: 150px;
6 | }
7 |
8 | .email{
9 | display:flex;
10 | flex-direction: column;
11 | }
12 |
13 | .emails{
14 |
15 | width: 250px;
16 | height: 20px;
17 | padding: 10px;
18 | }
19 | .emails:focus{
20 | outline: none;
21 | }
22 |
23 |
24 | .verifyBtn{
25 | margin-top: 15px;
26 | padding: 10px;
27 | border-color: rgb(30, 216, 132);
28 | border-radius: 4px;
29 | color:green;
30 | cursor:pointer;
31 | }
32 |
33 | .backBtn{
34 | margin-top: 15px;
35 | padding: 10px;
36 | border:none;
37 | color:green;
38 | border-radius: 4px;
39 | cursor:pointer;
40 | }
41 |
42 | .otp{
43 | display: flex;
44 | flex-direction: row;
45 | }
46 |
47 | .otpVerifyBtn{
48 | margin-left: 10px;
49 | border-color: green;
50 | color:green;
51 | border-radius: 5px;
52 | cursor:pointer;
53 | }
--------------------------------------------------------------------------------
/src/components/Button/OutlinedButton/OutlinedButton.module.css:
--------------------------------------------------------------------------------
1 | .button {
2 | border: 5px solid var(--clr-white90);
3 | padding: 1rem 4rem;
4 | text-transform: uppercase;
5 | text-align: center;
6 | font-weight: var(--fw-medium);
7 | font-size: var(--fs-p);
8 | cursor: pointer;
9 | transition:
10 | border-color 0.3s linear,
11 | color 0.3s linear;
12 | }
13 |
14 | .white_color {
15 | border-color: var(--clr-white90);
16 | color: var(--clr-white90);
17 | }
18 |
19 | .black_color {
20 | border-color: var(--clr-black90);
21 | color: var(--clr-black90);
22 | }
23 |
24 | .button:hover,
25 | .button:focus-visible {
26 | border-color: var(--clr-lyellow);
27 | color: var(--clr-lyellow);
28 | }
29 |
30 | .button:active {
31 | border-color: var(--clr-yellow);
32 | color: var(--clr-yellow);
33 | transition-duration: 0s;
34 | }
35 |
36 | @media (max-width: 600px) {
37 | .button {
38 | border-width: 3px;
39 | font-size: var(--fs-smaller);
40 | padding: 0.75rem 2rem;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/data/countries.js:
--------------------------------------------------------------------------------
1 | import moroccoImg from '../assets/images/Home/SectionThree/morocco.webp';
2 | import nigeriaImg from '../assets/images/Home/SectionThree/nigeria.webp';
3 | import kenyaImg from '../assets/images/Home/SectionThree/kenya.webp';
4 | import namibiaImg from '../assets/images/Home/SectionThree/namibia.webp';
5 |
6 | const countries = [
7 | {
8 | id: 1,
9 | title: 'Morocco',
10 | image: moroccoImg,
11 | alt: 'An image of a Moroccan beach',
12 | linkUrl: '/Morocco',
13 | },
14 | {
15 | id: 2,
16 | title: 'Nigeria',
17 | image: nigeriaImg,
18 | alt: 'An image of a Nigerian cityside',
19 | linkUrl: '/Nigeria',
20 | },
21 | {
22 | id: 3,
23 | title: 'Kenya',
24 | image: kenyaImg,
25 | alt: 'An image of the Kenyan skyline',
26 | linkUrl: '/Kenya',
27 | },
28 | {
29 | id: 4,
30 | title: 'Namibia',
31 | image: namibiaImg,
32 | alt: 'An image of a Nambian beachside',
33 | linkUrl: '/Namibia',
34 | },
35 | ];
36 |
37 | export default countries;
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Roshin Nishad
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/components/CountryDescription/CountryDescription.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import FadeTransition from '../FadeTransition/FadeTransition';
3 | import styles from './CountryDescription.module.css';
4 |
5 | function CountryDescription({ title, body, color }) {
6 | const fwBold = '800';
7 | const countryNamestyles = { color, fontWeight: fwBold, textTransform: 'uppercase' };
8 | const bulletstyles = {
9 | backgroundColor: color,
10 | width: '0.625rem',
11 | height: '1.9rem',
12 | };
13 |
14 | return (
15 |
16 |
20 |
23 |
24 | );
25 | }
26 |
27 | CountryDescription.propTypes = {
28 | title: PropTypes.string.isRequired,
29 | body: PropTypes.string.isRequired,
30 | color: PropTypes.string.isRequired,
31 | };
32 |
33 | export default CountryDescription;
34 |
--------------------------------------------------------------------------------
/src/data/whyInvest.js:
--------------------------------------------------------------------------------
1 | import youthfulPopulation from '../assets/images/Home/SectionTwo/youthful_population.webp';
2 | import proBusiness from '../assets/images/Home/SectionTwo/pro_business.webp';
3 | import growingEconomies from '../assets/images/Home/SectionTwo/growing_economies.webp';
4 |
5 | const investData = [
6 | {
7 | id: 1,
8 | title: 'PRO BUSINESS',
9 | image: proBusiness,
10 | alt: 'A beautiful building',
11 | body: 'Business-friendly environment with political stability, resilient economy, dynamic investor friendly environment and Government incentives',
12 | },
13 | {
14 | id: 2,
15 | title: 'YOUTHFUL POPULATION',
16 | image: youthfulPopulation,
17 | alt: 'A beautiful building',
18 | body: 'Tap into a burgeoning youth market brimming with 1.46 billion potential consumers, a demographic primed for dynamic growth and innovation.',
19 | },
20 | {
21 | id: 3,
22 | title: 'GROWING ECONOMIES',
23 | image: growingEconomies,
24 | alt: 'A beautiful building',
25 | body: 'Leverage a robust economic landscape with a GDP of $108 billion, reflecting a flourishing market ripe for lucrative investment opportunities.',
26 | },
27 | ];
28 |
29 | export default investData;
30 |
--------------------------------------------------------------------------------
/src/hooks/useStaggered.jsx:
--------------------------------------------------------------------------------
1 | import { useReducedMotion } from 'framer-motion';
2 |
3 | const useStaggered = (options = {}) => {
4 | const defaultAmount = 0.5;
5 | const defaultTranslate = 50;
6 | const amount = options.amount || defaultAmount;
7 | const translate = options.translate || defaultTranslate;
8 |
9 | const shouldReduceMotion = useReducedMotion();
10 |
11 | const parentVariants = {
12 | initial: { opacity: 0 },
13 | enter: {
14 | opacity: 1,
15 | transition: { duration: 0.5, staggerChildren: 0.1 },
16 | },
17 | };
18 |
19 | const childVariants = {
20 | initial: {
21 | translateY: `${translate}px`,
22 | },
23 | enter: {
24 | translateY: 0,
25 | transition: { ease: [0, 0, 0.5, 1] },
26 | },
27 | };
28 |
29 | const reducedParentMotionProps = shouldReduceMotion
30 | ? {}
31 | : {
32 | variants: parentVariants,
33 | initial: 'initial',
34 | whileInView: 'enter',
35 | viewport: { once: true, amount },
36 | };
37 |
38 | const reducedChildMotionProps = shouldReduceMotion ? {} : { variants: childVariants };
39 |
40 | return { reducedParentMotionProps, reducedChildMotionProps };
41 | };
42 |
43 | export default useStaggered;
44 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true,
5 | jest: true,
6 | },
7 | extends: ['airbnb', 'prettier', 'plugin:prettier/recommended'],
8 | overrides: [
9 | {
10 | env: {
11 | node: true,
12 | },
13 | files: ['.eslintrc.{js,cjs}'],
14 | parserOptions: {
15 | sourceType: 'script',
16 | },
17 | },
18 | ],
19 | parserOptions: {
20 | ecmaVersion: 'latest',
21 | sourceType: 'module',
22 | ecmaFeatures: {
23 | jsx: true,
24 | },
25 | },
26 | rules: {
27 | 'react/react-in-jsx-scope': 'off',
28 | 'react/forbid-prop-types': 'off',
29 | 'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx'] }],
30 | quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: true }],
31 | 'prettier/prettier': [
32 | 'error',
33 | {
34 | trailingComma: 'es5',
35 | singleQuote: true,
36 | printWidth: 100,
37 | tabWidth: 2,
38 | semi: true,
39 | endOfLine: 'lf',
40 | arrowParens: 'avoid',
41 | },
42 | ],
43 | },
44 | settings: {
45 | react: {
46 | version: 'detect',
47 | },
48 | },
49 | plugins: ['react', 'prettier', 'import'],
50 | };
51 |
--------------------------------------------------------------------------------
/src/components/InputBox/DropdownBox.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styles from './InputBox.module.css';
4 |
5 | function DropdownBox(props) {
6 | const { parentProps, options, wrapperClass } = props;
7 | const { value, name, onChange, id, disabled, required } = parentProps;
8 | return (
9 |
10 |
24 |
25 | );
26 | }
27 |
28 | DropdownBox.propTypes = {
29 | parentProps: PropTypes.shape({
30 | onChange: PropTypes.func.isRequired,
31 | id: PropTypes.string.isRequired,
32 | name: PropTypes.string.isRequired,
33 | value: PropTypes.string,
34 | disabled: PropTypes.bool,
35 | required: PropTypes.bool,
36 | }).isRequired,
37 | options: PropTypes.node.isRequired,
38 | wrapperClass: PropTypes.string.isRequired,
39 | };
40 |
41 | export default DropdownBox;
42 |
--------------------------------------------------------------------------------
/src/pages/Home/SectionFour/SectionFour.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | position: relative;
3 | width: 100%;
4 | isolation: isolate;
5 | }
6 |
7 | .background {
8 | position: absolute;
9 | inset: 12px;
10 | border-radius: 2rem;
11 | background-image: url('../../../assets/images/Home/SectionFour/sectionfour.webp');
12 | background-size: cover;
13 | background-position: 75% center;
14 | z-index: -1;
15 | }
16 |
17 | .background::after {
18 | content: '';
19 | position: absolute;
20 | border-radius: inherit;
21 | inset: 0;
22 | background-color: rgb(0, 0, 0, 0.3);
23 | z-index: 1;
24 | }
25 |
26 | .content {
27 | width: 100%;
28 | height: 100%;
29 | padding: 6rem 3rem;
30 | }
31 |
32 | .text {
33 | color: var(--clr-white90);
34 | }
35 |
36 | .title {
37 | font-weight: var(--fw-semibold);
38 | padding-block-end: 3rem;
39 | max-width: 20ch;
40 | }
41 |
42 | .body_wrapper {
43 | display: flex;
44 | flex-direction: column;
45 | gap: 1.5rem;
46 | }
47 |
48 | .text_wrapper {
49 | max-width: 60ch;
50 | }
51 |
52 | .mini_title {
53 | color: var(--clr-yellow);
54 | font-weight: var(--fw-medium);
55 | }
56 |
57 | @media (max-width: 600px) {
58 | .background {
59 | inset: 0;
60 | border-radius: 0;
61 | }
62 | .content {
63 | padding: 5rem 2rem;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Adam-i Frontend (Internship)
2 |
3 | ## Development Workflow
4 |
5 | - Before making a Pull Request (PR), run `npm run postinstall` to perform eslinting and prettier checks. Ensure checks pass before PR.
6 | - PRs must only be made to the `dev` branch.
7 | - After completing a milestone, merge `dev` to `main`, and then `main` to `azure-main` for cloud deployment.
8 | - Use meaningful one-line summaries for your commit messages.
9 | - Pull from the branch before each commit to minimize conflicts.
10 | - Provide a summary and bullet points in the body of your PR.
11 |
12 | ## Local Development
13 |
14 | - Start the development server with `npm run start`; it will open at `localhost:3000`.
15 | - Follow the Airbnb ESLint style guideline for code consistency.
16 |
17 | ## Cloud Deployment
18 |
19 | - Push to `azure-main` to trigger automatic CI/CD updates on the cloud. Monitor the process in the GitHub Actions tab.
20 | - Use `npx prisma studio` for a web dashboard view of the database.
21 |
22 | ## Guidelines and Configurations
23 |
24 | - Ensure your Git configurations are set for case sensitivity and automatic rebasing.
25 | - Run linting and formatting as part of your development routine.
26 |
27 | ## Additional Notes
28 |
29 | - Always ensure that no sensitive information is included in commits to the public repository.
30 |
--------------------------------------------------------------------------------
/src/pages/Home/SectionThree/SectionThree.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | background-color: var(--clr-bg-lgray);
3 | padding: 3rem;
4 | }
5 |
6 | .title_wrapper {
7 | margin-block-end: 4rem;
8 | }
9 |
10 | .title {
11 | color: var(--clr-green);
12 | font-weight: var(--fw-semibold);
13 | }
14 |
15 | .card_wrapper {
16 | display: flex;
17 | flex-wrap: wrap;
18 | justify-content: center;
19 | column-gap: 8rem;
20 | row-gap: 4rem;
21 | }
22 |
23 | .horizontal_line {
24 | width: 95%;
25 | margin: 0 auto;
26 | height: 3px;
27 | background-color: black;
28 | margin-block: 6rem;
29 | border-radius: 100vw;
30 | }
31 |
32 | .content_map_wrapper {
33 | padding-block-end: 2rem;
34 | display: flex;
35 | flex-wrap: wrap;
36 | justify-content: space-evenly;
37 | align-items: center;
38 | }
39 |
40 | .map {
41 | -webkit-tap-highlight-color: transparent;
42 | width: 520px;
43 | }
44 |
45 | area:focus-within {
46 | outline: 3px solid var(--clr-yellow);
47 | }
48 |
49 | @media (max-width: 992px) {
50 | .card_wrapper {
51 | column-gap: 4rem;
52 | }
53 |
54 | .content_map_wrapper {
55 | justify-content: center;
56 | }
57 | }
58 |
59 | @media (max-width: 600px) {
60 | .content_map_wrapper {
61 | display: flex;
62 | }
63 |
64 | .map {
65 | padding-block-start: 2rem;
66 | width: 80vw;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/pages/Home/SectionSeven/SectionSeven.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react';
2 | import { motion, useScroll, useTransform, useReducedMotion } from 'framer-motion';
3 | import { useMediaQuery } from '@react-hook/media-query';
4 | import styles from './SectionSeven.module.css';
5 | import ContactUs from '../../../components/Button/OutlinedButton/OutlinedButton';
6 |
7 | function getMaxPercentage(isNotDesktop) {
8 | if (isNotDesktop) return '35vh';
9 | return '40vh';
10 | }
11 |
12 | function SectionSeven() {
13 | const isNotDesktop = useMediaQuery('(max-width: 992px)');
14 |
15 | const ref = useRef(null);
16 | const shouldReduceMotion = useReducedMotion();
17 | const { scrollYProgress } = useScroll({
18 | target: ref,
19 | offset: ['start end', 'end end'],
20 | });
21 | const maxPercentage = getMaxPercentage(isNotDesktop);
22 | const motionY = useTransform(scrollYProgress, [0, 1], ['0vh', maxPercentage]);
23 | const y = shouldReduceMotion ? '0vh' : motionY;
24 | return (
25 |
26 |
27 |
28 | Partner with us to build
29 |
30 | Prosperity for Africa.
31 |
32 |
33 |
34 |
35 | );
36 | }
37 |
38 | export default SectionSeven;
39 |
--------------------------------------------------------------------------------
/src/pages/Home/SectionSix/SectionSix.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | background-color: var(--clr-bg-gray);
3 | padding: 5rem 3rem;
4 | }
5 |
6 | .title {
7 | font-weight: var(--fw-semibold);
8 | color: var(--clr-yellow);
9 | margin-bottom: 3rem;
10 | }
11 |
12 | .content_wrapper {
13 | display: flex;
14 | flex-direction: column;
15 | gap: 3rem;
16 | }
17 |
18 | .stories_wrapper {
19 | display: flex;
20 | align-items: center;
21 | justify-content: space-evenly;
22 | flex-wrap: wrap;
23 | gap: 3rem;
24 | color: white;
25 | }
26 |
27 | .thumbnail {
28 | width: 540px;
29 | height: 320px;
30 | border-radius: 1rem;
31 | outline: 2px solid rgba(255, 255, 255, 0.21);
32 | outline-offset: -2px;
33 | }
34 |
35 | .body_wrapper {
36 | display: flex;
37 | flex-direction: column;
38 | gap: 1rem;
39 | }
40 |
41 | .body_title {
42 | font-weight: var(--fw-semibold);
43 | }
44 |
45 | .body_footer {
46 | font-weight: var(--fw-semibold);
47 | color: var(--clr-yellow);
48 | }
49 |
50 | @media (max-width: 992px) {
51 | .wrapper {
52 | padding: 2rem;
53 | }
54 |
55 | .thumbnail {
56 | width: 500px;
57 | height: 280px;
58 | }
59 |
60 | .body_wrapper p {
61 | max-width: 70vw;
62 | }
63 | }
64 |
65 | @media (max-width: 600px) {
66 | .thumbnail {
67 | width: 80vw;
68 | height: initial;
69 | }
70 |
71 | .body_wrapper p {
72 | max-width: 80vw;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { Route, Routes } from 'react-router-dom';
2 | import './App.css';
3 | import Login from './pages/Login/Login';
4 | import Home from './pages/Home/Home';
5 | import ScrollToTop from './ScrolltoTop';
6 | import ForgotPassword from './components/ForgotPassword/ForgotPassword';
7 | import Register from './pages/Register/Register';
8 | import Morocco from './pages/Morocco/Morocco';
9 | import Kenya from './pages/Kenya/Kenya';
10 | import Nigeria from './pages/Nigeria/Nigeria';
11 | import Namibia from './pages/Namibia/Namibia';
12 | import CompanyRegistration from './pages/CompanyRegistration/CompanyRegistration';
13 | import NavBar from './components/Navbar/Navbar';
14 |
15 | function App() {
16 | return (
17 |
18 |
19 |
20 |
21 | } />
22 | } />
23 | } />
24 | } />
25 | } />
26 | } />
27 | } />
28 | } />
29 | } />
30 |
31 |
32 | );
33 | }
34 | export default App;
35 |
--------------------------------------------------------------------------------
/src/utils/validateUser.jsx:
--------------------------------------------------------------------------------
1 | import Joi from 'joi';
2 |
3 | export const userSchema = Joi.object({
4 | name: Joi.string().max(255).required().messages({
5 | 'string.base': 'Name must be text.',
6 | 'string.max': 'Name must not exceed 255 characters.',
7 | }),
8 | email: Joi.string()
9 | .email({ tlds: { allow: false } })
10 | .max(255)
11 | .required()
12 | .messages({
13 | 'string.email': 'Please enter a valid email address.',
14 | 'string.max': 'Email must not exceed 255 characters.',
15 | }),
16 | accountPassword: Joi.string()
17 | .pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d$_.!@#%^&*()\-+=]{8,128}$/)
18 | .required()
19 | .messages({
20 | 'string.pattern.base': 'Password requirements not met.',
21 | }),
22 | confirmPassword: Joi.valid(Joi.ref('accountPassword')).required().messages({
23 | 'any.only': 'Passwords must match.',
24 | }),
25 | }).with('accountPassword', 'confirmPassword');
26 |
27 | export const validateUserData = data => {
28 | const { error } = userSchema.validate(data, { abortEarly: false });
29 | if (!error) return null;
30 |
31 | const customErrors = error.details.reduce((acc, detail) => {
32 | const { message } = detail;
33 |
34 | // *Remove empty field warning.
35 | if (message.includes('is not allowed to be empty')) {
36 | acc[detail.path[0]] = '';
37 | return acc;
38 | }
39 |
40 | acc[detail.path[0]] = message;
41 |
42 | return acc;
43 | }, {});
44 |
45 | return customErrors;
46 | };
47 |
--------------------------------------------------------------------------------
/src/components/ForgotPassword/ForgotPassword.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useNavigate } from 'react-router-dom';
3 |
4 | import './ForgotPassword.css';
5 |
6 | function ForgotPassword() {
7 | const navigation = useNavigate();
8 | const [emailDiv, setEmailDiv] = useState(true);
9 | const [otpDiv, setOtpDiv] = useState(false);
10 | return (
11 |
47 | );
48 | }
49 |
50 | export default ForgotPassword;
51 |
--------------------------------------------------------------------------------
/src/data/successStories.js:
--------------------------------------------------------------------------------
1 | import image1 from '../assets/images/Home/SectionSix/thumbnailOne.png';
2 | import image2 from '../assets/images/Home/SectionSix/Section6.png';
3 | import image3 from '../assets/images/Home/SectionSix/thumbnailThree.png';
4 |
5 | const stories = [
6 | {
7 | id: 1,
8 | image: image1,
9 | alt: '',
10 | title: 'DAIMLER BENZ GmbH',
11 | body: 'Morocco has made the strategic choice to develop its renewable energies and reduce its carbon footprint. This decarbonization project will allow industries to gain in competitiveness. It will be a structuring element in theyears to come, and it is totally in line with our vision.',
12 | footer: 'Daimler Benz CEO, Luca De Meo',
13 | },
14 | {
15 | id: 2,
16 | image: image2,
17 | alt: '',
18 | title: "Carl's Jr. GROUP",
19 | body: 'Kenya is embracing the digital revolution, pioneering unprecedented tech advancements. This surge in innovation positions businesses at the forefront of the information era. These technological leaps enhance market competitiveness.',
20 | footer: "Carl's Jr. Senior VP, George Forenthal",
21 | },
22 | {
23 | id: 3,
24 | image: image3,
25 | alt: '',
26 | title: 'Visit Namibia Ltd.',
27 | body: 'Namibia is committed to harnessing its natural resources sustainably, fostering a green economy that powers growth. This dedication to eco-friendly practices propels the nation towards a leading role in conservation-driven commerce.',
28 | footer: 'Visit Namibia CFO, Rosanne Juliette',
29 | },
30 | ];
31 |
32 | export default stories;
33 |
--------------------------------------------------------------------------------
/src/pages/Home/SectionSix/SectionSix.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-props-no-spreading */
2 | import React from 'react';
3 | import { motion } from 'framer-motion';
4 | import styles from './SectionSix.module.css';
5 | import stories from '../../../data/successStories';
6 | import FadeTransition from '../../../components/FadeTransition/FadeTransition';
7 | import useStaggered from '../../../hooks/useStaggered';
8 |
9 | function SectionSix() {
10 | const { reducedParentMotionProps, reducedChildMotionProps } = useStaggered({
11 | amount: 0.3,
12 | translate: 100,
13 | });
14 | return (
15 |
16 |
17 | Success Stories
18 |
19 |
20 | {stories.map(data => (
21 |
22 |
23 |
24 |
{data.title}
25 |
"{data.body}"
26 |
{data.footer}
27 |
28 |
29 | ))}
30 |
31 |
32 | );
33 | }
34 |
35 | export default SectionSix;
36 |
--------------------------------------------------------------------------------
/src/components/Cards/Cards.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import PropTypes from 'prop-types';
4 | import { RiArrowRightUpLine } from 'react-icons/ri';
5 | import styles from './Cards.module.css';
6 |
7 | function Cards(props) {
8 | const { title, image, alt, body, isGreen, linkUrl } = props;
9 |
10 | const color = isGreen ? 'lgreen' : 'yellow';
11 | const style = {
12 | color: `var(--clr-${color})`,
13 | };
14 | const content = (
15 |
16 |
17 |

22 |
23 |
24 | {title}
25 |
26 | {isGreen && }
27 |
28 |
29 | {body &&
{body}
}
30 |
31 | );
32 | return (
33 |
34 | {linkUrl ? (
35 |
36 | {content}
37 |
38 | ) : (
39 | content
40 | )}
41 |
42 | );
43 | }
44 |
45 | Cards.propTypes = {
46 | title: PropTypes.string.isRequired,
47 | image: PropTypes.string.isRequired,
48 | alt: PropTypes.string.isRequired,
49 | body: PropTypes.string,
50 | isGreen: PropTypes.bool,
51 | linkUrl: PropTypes.string,
52 | };
53 |
54 | Cards.defaultProps = {
55 | isGreen: false,
56 | body: '',
57 | linkUrl: '',
58 | };
59 |
60 | export default Cards;
61 |
--------------------------------------------------------------------------------
/src/pages/Register/Register.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | background: url('../../assets/login.webp') no-repeat center center fixed;
3 | background-size: cover;
4 | min-height: var(--desktop-nav-fullheight);
5 | }
6 |
7 | .blur_wrapper {
8 | padding: 3rem;
9 | padding-inline-end: 6rem;
10 | background-color: var(--clr-bg-orange50);
11 | -webkit-backdrop-filter: blur(16px);
12 | backdrop-filter: blur(16px);
13 | min-height: var(--desktop-nav-fullheight);
14 | width: fit-content;
15 | max-width: 100%;
16 | }
17 |
18 | .blur_wrapper h2 {
19 | color: var(--clr-white90);
20 | font-weight: var(--fw-semibold);
21 | margin-block-end: 1rem;
22 | }
23 |
24 | .register_form {
25 | display: flex;
26 | flex-direction: column;
27 | gap: 1rem;
28 | }
29 |
30 | .register_input {
31 | display: flex;
32 | flex-direction: column;
33 | gap: 1rem;
34 | }
35 |
36 | .login_link {
37 | color: var(--clr-white70);
38 | margin-block: 1rem;
39 | }
40 |
41 | .link {
42 | color: var(--clr-white90);
43 | }
44 |
45 | .link:hover,
46 | .link:focus {
47 | color: white;
48 | text-decoration: underline;
49 | }
50 |
51 | .aboutus {
52 | color: var(--clr-white70);
53 | margin-block-start: 2rem;
54 | max-width: 40ch;
55 | }
56 |
57 | .tooltip_wrapper {
58 | max-width: 60vw;
59 | }
60 |
61 | .tooltip_title {
62 | margin-block-end: 0.5rem;
63 | font-weight: var(--fw-medium);
64 | }
65 |
66 | .tooltip_list_wrapper {
67 | display: grid;
68 | gap: 0.5rem;
69 | }
70 |
71 | @media (max-width: 600px) {
72 | .wrapper {
73 | background-attachment: initial;
74 | min-height: var(--mobile-nav-fullheight);
75 | }
76 | .blur_wrapper {
77 | min-height: var(--mobile-nav-fullheight);
78 | padding: 2rem;
79 | width: 100%;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/components/InputBox/TextBox.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styles from './InputBox.module.css';
4 |
5 | function TextBox(props) {
6 | const { parentProps, wrapperClass, addressHeight } = props;
7 | const { value, name, type, min, max, onChange, id, disabled, required } = parentProps;
8 | // ! addressHeight is true if isAddress is true.
9 | return (
10 |
11 | {addressHeight ? (
12 |
22 | ) : (
23 |
35 | )}
36 |
37 | );
38 | }
39 |
40 | TextBox.propTypes = {
41 | parentProps: PropTypes.shape({
42 | onChange: PropTypes.func.isRequired,
43 | id: PropTypes.string.isRequired,
44 | name: PropTypes.string.isRequired,
45 | type: PropTypes.string.isRequired,
46 | min: PropTypes.string,
47 | max: PropTypes.string,
48 | value: PropTypes.string,
49 | disabled: PropTypes.bool,
50 | required: PropTypes.bool,
51 | }).isRequired,
52 | wrapperClass: PropTypes.string.isRequired,
53 | addressHeight: PropTypes.string.isRequired,
54 | };
55 |
56 | export default TextBox;
57 |
--------------------------------------------------------------------------------
/src/pages/Home/SectionOne/SectionOne.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import FadeTransition from '../../../components/FadeTransition/FadeTransition';
3 | import styles from './SectionOne.module.css';
4 | import backgroundOne from '../../../assets/images/Home/home-section-1.webp';
5 | import backgroundTwo from '../../../assets/images/Home/home-section-2.webp';
6 | import backgroundThree from '../../../assets/images/Home/home-section-3.webp';
7 |
8 | function SectionOne() {
9 | const [activeImage, setActiveImage] = useState(0);
10 | const backgrounds = [backgroundOne, backgroundTwo, backgroundThree];
11 |
12 | useEffect(() => {
13 | const startImageRotation = () => {
14 | return setInterval(() => {
15 | setActiveImage(pastImage => (pastImage + 1) % backgrounds.length);
16 | }, 3000);
17 | };
18 |
19 | // * Initial delay for the Heading Gradient Animation.
20 | const timer = setTimeout(() => {
21 | const interval = startImageRotation();
22 | return () => clearInterval(interval);
23 | }, 1500);
24 | return () => clearTimeout(timer);
25 | }, [backgrounds.length]);
26 |
27 | return (
28 |
29 |
30 |
34 |
35 |
36 |
Invest in Africa
37 |
38 | Shaping Tomorrow Together,
39 |
40 | Invest in Africa's Potential.
41 |
42 |
43 |
44 | );
45 | }
46 |
47 | export default SectionOne;
48 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
25 | Invest Africa
26 |
27 |
28 |
29 |
30 |
31 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/components/CardTransition/CardTransition.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-props-no-spreading */
2 | import React from 'react';
3 | import { motion } from 'framer-motion';
4 | import { useMediaQuery } from '@react-hook/media-query';
5 | import PropTypes from 'prop-types';
6 | import Cards from '../Cards/Cards';
7 | import useStaggered from '../../hooks/useStaggered';
8 |
9 | export default function CardTransition(props) {
10 | const { object, className, color, isGreen, amountList } = props;
11 |
12 | const isTablet = useMediaQuery('(max-width: 992px)');
13 | const isMobile = useMediaQuery('(max-width: 600px)');
14 | const getAmountByScreenSize = () => {
15 | if (isMobile) return amountList[2];
16 | if (isTablet) return amountList[1];
17 | return amountList[0];
18 | };
19 | const { reducedParentMotionProps, reducedChildMotionProps } = useStaggered({
20 | amount: getAmountByScreenSize(),
21 | translate: 200,
22 | });
23 |
24 | return (
25 |
26 | {object.map(data => (
27 |
28 |
38 |
39 | ))}
40 |
41 | );
42 | }
43 |
44 | CardTransition.propTypes = {
45 | object: PropTypes.oneOfType([PropTypes.array, PropTypes.object]).isRequired,
46 | className: PropTypes.string,
47 | color: PropTypes.string,
48 | isGreen: PropTypes.bool,
49 | amountList: PropTypes.array,
50 | };
51 |
52 | CardTransition.defaultProps = {
53 | className: null,
54 | color: null,
55 | isGreen: false,
56 | amountList: [0.75, 0.5, 'some'],
57 | };
58 |
--------------------------------------------------------------------------------
/src/components/Cards/Cards.module.css:
--------------------------------------------------------------------------------
1 | .content {
2 | display: flex;
3 | flex-direction: column;
4 | gap: 1rem;
5 | width: 24rem;
6 | justify-content: center;
7 | }
8 |
9 | .card {
10 | position: relative;
11 | height: 16rem;
12 | overflow: hidden;
13 | border-radius: 0.75rem;
14 | }
15 |
16 | .image {
17 | height: 100%;
18 | width: 100%;
19 | object-fit: cover;
20 | transition: scale 0.5s ease-out;
21 | }
22 |
23 | .card:hover .image_hover {
24 | scale: 1.2;
25 | }
26 |
27 | .border {
28 | outline: solid 6px var(--clr-green);
29 | transition: outline-color 500ms ease;
30 | }
31 |
32 | .link:hover .border {
33 | outline: solid 6px var(--clr-lgreen);
34 | }
35 |
36 | .link {
37 | cursor: pointer;
38 | }
39 |
40 | /* ?Accessibilty keyboard navigation */
41 | .link:focus-within .card,
42 | .link:focus-visible .card {
43 | outline: solid 6px var(--clr-yellow);
44 | }
45 |
46 | .link:focus-within .image_hover,
47 | .link:focus-visible .image_hover {
48 | scale: 1.2;
49 | }
50 |
51 | .title_container {
52 | display: flex;
53 | position: absolute;
54 | background-color: rgba(0, 0, 0, 0.7);
55 | inset: auto 0 0 0;
56 | height: 4rem;
57 | align-items: center;
58 | padding-inline-start: 1rem;
59 | }
60 |
61 | .title {
62 | text-transform: uppercase;
63 | font-weight: var(--fw-semibold);
64 | }
65 |
66 | .arrow_icon {
67 | color: var(--clr-lgreen);
68 | font-size: var(--fs-h4);
69 | stroke-width: 0.5px;
70 | }
71 |
72 | .body {
73 | color: black;
74 | font-size: var(--fs-smaller);
75 | font-weight: var(--fw-semibold);
76 | }
77 |
78 | @media (max-width: 992px) {
79 | .content {
80 | width: 18rem;
81 | }
82 |
83 | .card {
84 | height: 14rem;
85 | }
86 |
87 | .title {
88 | font-size: var(--fs-p);
89 | }
90 | }
91 |
92 | @media (max-width: 600px) {
93 | .content {
94 | max-width: 100%;
95 | }
96 |
97 | .card {
98 | height: 12rem;
99 | }
100 |
101 | .title_container {
102 | height: 3rem;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/pages/Home/SectionFour/SectionFour.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-props-no-spreading */
2 | import React from 'react';
3 | import { motion, useReducedMotion } from 'framer-motion';
4 | import { useMediaQuery } from '@react-hook/media-query';
5 | import styles from './SectionFour.module.css';
6 | import oppurtunities from '../../../data/oppurtunities';
7 | import FadeTransition from '../../../components/FadeTransition/FadeTransition';
8 | import useStaggered from '../../../hooks/useStaggered';
9 |
10 | function SectionFour() {
11 | const { reducedParentMotionProps, reducedChildMotionProps } = useStaggered();
12 | const shouldReduceMotion = useReducedMotion();
13 | const isMobile = useMediaQuery('(max-width: 600px)');
14 | const reducedMotionProps =
15 | shouldReduceMotion || isMobile
16 | ? {}
17 | : {
18 | initial: { borderRadius: '3rem', inset: '18px' },
19 | whileInView: { borderRadius: '0rem', inset: '0px' },
20 | transition: { duration: 0.5, ease: [0, 0, 0.5, 1] },
21 | viewport: { amount: 0.5 },
22 | };
23 |
24 | return (
25 |
26 |
27 |
28 |
29 |
30 | Crafting Tomorrow's Success Stories Today
31 |
32 |
33 |
34 | {oppurtunities.map(data => (
35 |
40 | {data.title}
41 | {data.body}
42 |
43 | ))}
44 |
45 |
46 |
47 | );
48 | }
49 |
50 | export default SectionFour;
51 |
--------------------------------------------------------------------------------
/src/components/Footer/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import { FaFacebookF, FaTwitter, FaYoutube, FaInstagram } from 'react-icons/fa6';
4 | import FadeTransition from '../FadeTransition/FadeTransition';
5 | import styles from './Footer.module.css';
6 |
7 | export default function Footer() {
8 | return (
9 |
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adam-i",
3 | "version": "0.1.0",
4 | "type": "module",
5 | "private": true,
6 | "dependencies": {
7 | "@react-hook/media-query": "^1.1.1",
8 | "classnames": "^2.5.1",
9 | "countries-list": "^3.0.6",
10 | "dotenv": "^16.4.1",
11 | "framer-motion": "^11.0.6",
12 | "joi": "^17.12.0",
13 | "prop-types": "^15.8.1",
14 | "react": "^18.2.0",
15 | "react-dom": "^18.2.0",
16 | "react-icons": "^4.12.0",
17 | "react-img-map-area": "^1.0.0",
18 | "react-router-dom": "^6.21.1",
19 | "react-tooltip": "^5.26.0",
20 | "web-vitals": "^2.1.4"
21 | },
22 | "scripts": {
23 | "start": "vite",
24 | "build": "vite build",
25 | "serve": "vite preview",
26 | "lint": "eslint \"src/**/*.{js,jsx}\" --fix",
27 | "prettier": "prettier --write \"src/**/*.{js,jsx}\"",
28 | "postinstall": "npm run lint && npm run prettier"
29 | },
30 | "eslintConfig": {
31 | "extends": [
32 | "react-app",
33 | "react-app/jest"
34 | ]
35 | },
36 | "browserslist": {
37 | "production": [
38 | ">0.2%",
39 | "not dead",
40 | "not op_mini all"
41 | ],
42 | "development": [
43 | "last 1 chrome version",
44 | "last 1 firefox version",
45 | "last 1 safari version"
46 | ]
47 | },
48 | "devDependencies": {
49 | "@testing-library/jest-dom": "^5.17.0",
50 | "@testing-library/react": "^13.4.0",
51 | "@testing-library/user-event": "^13.5.0",
52 | "@vitejs/plugin-react": "^4.2.1",
53 | "eslint": "^8.56.0",
54 | "eslint-config-airbnb": "^19.0.4",
55 | "eslint-config-prettier": "^9.1.0",
56 | "eslint-plugin-import": "^2.29.1",
57 | "eslint-plugin-jsx-a11y": "^6.8.0",
58 | "eslint-plugin-prettier": "^5.0.1",
59 | "eslint-plugin-react": "^7.33.2",
60 | "eslint-plugin-react-hooks": "^4.6.0",
61 | "husky": "^8.0.3",
62 | "lint-staged": "^15.2.0",
63 | "prettier": "^3.1.1",
64 | "vite": "^5.1.4",
65 | "vite-tsconfig-paths": "^4.3.1"
66 | },
67 | "husky": {
68 | "hooks": {
69 | "pre-commit": "lint-staged"
70 | }
71 | },
72 | "lint-staged": {
73 | "*.{js,jsx}": [
74 | "npm run lint",
75 | "prettier --write"
76 | ]
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/components/InputBox/InputBox.module.css:
--------------------------------------------------------------------------------
1 | .input_wrapper {
2 | position: relative;
3 | color: var(--clr-black90);
4 | margin-block-start: 0.5rem;
5 | }
6 |
7 | .label {
8 | display: flex;
9 | gap: 0.5rem;
10 | color: var(--clr-white90);
11 | font-size: var(--fs-small);
12 | }
13 |
14 | .infoButton {
15 | display: flex;
16 | align-items: center;
17 | }
18 |
19 | .input {
20 | border-radius: 12px;
21 | border: 0;
22 | background-color: var(--clr-white70);
23 | padding-inline: 1rem;
24 | font-size: var(--fs-smallest);
25 | font-weight: var(--fw-semibold);
26 | }
27 |
28 | .input:focus-within {
29 | outline: 3px solid var(--clr-white90);
30 | outline-offset: -3px;
31 | }
32 |
33 | select.input {
34 | padding-inline-end: 2.5rem;
35 | }
36 |
37 | option {
38 | background-color: var(--clr-bg-gray);
39 | color: var(--clr-white90);
40 | font-weight: var(--fw-medium);
41 | }
42 |
43 | input[type="number"] {
44 | -webkit-appearance: textfield;
45 | -moz-appearance: textfield;
46 | appearance: textfield;
47 | }
48 | input[type=number]::-webkit-inner-spin-button,
49 | input[type=number]::-webkit-outer-spin-button {
50 | -webkit-appearance: none;
51 | }
52 |
53 | .icon {
54 | position: absolute;
55 | top: 50%;
56 | right: 1rem;
57 | transform: translateY(-50%);
58 | }
59 |
60 | .message {
61 | margin-block-start: 0.25rem;
62 | color: var(--clr-mred);
63 | font-size: var(--fs-smallest);
64 | font-weight: var(--fw-medium);
65 | max-width: 30ch;
66 | }
67 |
68 | .size_xlarge {
69 | height: 3rem;
70 | width: 40rem;
71 | border-radius: 16px;
72 | }
73 |
74 | .size_large {
75 | height: 2.5rem;
76 | width: 20rem;
77 | }
78 |
79 | .size_small {
80 | height: 2.5rem;
81 | width: 12rem;
82 | }
83 |
84 | .size_tablet_xlarge {
85 | height: 2.5rem;
86 | width: 32rem;
87 | }
88 |
89 | .size_tablet_large {
90 | height: 2.5rem;
91 | width: 16rem;
92 | }
93 |
94 | .size_tablet_small {
95 | height: 2.5rem;
96 | width: 10rem;
97 | }
98 |
99 | .size_mobile_large {
100 | height: 2.5rem;
101 | width: 100%;
102 | max-width: 400px;
103 | }
104 |
105 | .size_mobile_small {
106 | height: 2.5rem;
107 | width: 9rem;
108 | }
109 |
--------------------------------------------------------------------------------
/src/pages/CompanyRegistration/CompanyRegistration.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | background: url('../../assets/login.webp') no-repeat center center fixed;
3 | background-size: cover;
4 | min-height: var(--desktop-nav-fullheight);
5 | }
6 |
7 | .blur_wrapper {
8 | padding: 3rem;
9 | background-color: var(--clr-bg-orange50);
10 | -webkit-backdrop-filter: blur(16px);
11 | backdrop-filter: blur(16px);
12 | min-height: var(--desktop-nav-fullheight);
13 | width: 100%;
14 | }
15 |
16 | .title,
17 | .subtitle {
18 | color: var(--clr-white90);
19 | font-weight: var(--fw-semibold);
20 | }
21 |
22 | .subtitle {
23 | margin-block: 1rem 3rem;
24 | }
25 |
26 | .page_indicator {
27 | color: var(--clr-white70);
28 | }
29 |
30 | .inputs_wrapper {
31 | display: flex;
32 | flex-direction: column;
33 | gap: 2rem;
34 | }
35 |
36 | .input_group {
37 | display: flex;
38 | gap: 1rem;
39 | flex-wrap: wrap;
40 | }
41 |
42 | .button_wrapper {
43 | display: flex;
44 | flex-wrap: wrap;
45 | gap: 1rem;
46 | margin-block-start: 3rem;
47 | }
48 |
49 | .login_link {
50 | font-size: 1rem;
51 | color: var(--clr-white70);
52 | margin-block-start: 2rem;
53 | }
54 |
55 | .login_link a {
56 | color: var(--clr-white90);
57 | text-decoration: none;
58 | cursor: pointer;
59 | }
60 |
61 | .login_link a:hover,
62 | .login_link a:focus-visible {
63 | text-decoration: underline;
64 | }
65 |
66 | .aboutus {
67 | color: var(--clr-white70);
68 | margin-block-start: 2rem;
69 | max-width: 40ch;
70 | }
71 |
72 | .tooltip_wrapper {
73 | max-width: 60vw;
74 | }
75 |
76 | .tooltip_title {
77 | margin-block-end: 0.5rem;
78 | font-weight: var(--fw-medium);
79 | }
80 |
81 | .tooltip_list_wrapper {
82 | display: grid;
83 | gap: 0.5rem;
84 | }
85 |
86 | @media (max-width: 600px) {
87 | .wrapper {
88 | background-attachment: initial;
89 | min-height: var(--mobile-nav-fullheight);
90 | }
91 |
92 | .blur_wrapper {
93 | min-height: var(--mobile-nav-fullheight);
94 | padding: 2rem;
95 | }
96 |
97 | .inputs_wrapper {
98 | gap: 1rem;
99 | }
100 |
101 | .input_group:not(.small_inputs) {
102 | display: grid;
103 | }
104 |
105 | .button_wrapper {
106 | justify-content: center;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/data/industries.js:
--------------------------------------------------------------------------------
1 | import manufacturing from '../assets/images/Home/SectionFive/industries_manufacturing.webp';
2 | import mining from '../assets/images/Home/SectionFive/industries_mining.webp';
3 | import tourism from '../assets/images/Home/SectionFive/industries_tourism.webp';
4 | import infrastructure from '../assets/images/Home/SectionFive/industries_infrastructure.webp';
5 | import oilGas from '../assets/images/Home/SectionFive/industries_oil_gas.webp';
6 | import health from '../assets/images/Home/SectionFive/industries_health.webp';
7 | import power from '../assets/images/Home/SectionFive/industries_power.webp';
8 | import foodIndustry from '../assets/images/Home/SectionFive/industries_food.webp';
9 | import agriculture from '../assets/images/Home/SectionFive/industries_agriculture.webp';
10 |
11 | const industries = [
12 | {
13 | id: 1,
14 | title: 'MANUFACTURING',
15 | image: manufacturing,
16 | alt: 'A beautiful building',
17 | linkUrl: 'https://investaftica.jp',
18 | },
19 | {
20 | id: 2,
21 | title: 'MINING',
22 | image: mining,
23 | alt: 'A beautiful building',
24 | linkUrl: 'https://investaftica.jp',
25 | },
26 | {
27 | id: 3,
28 | title: 'TOURISM',
29 | image: tourism,
30 | alt: 'A beautiful building',
31 | linkUrl: 'https://investaftica.jp',
32 | },
33 | {
34 | id: 4,
35 | title: 'INFRASTRUCTURE',
36 | image: infrastructure,
37 | alt: 'A beautiful building',
38 | linkUrl: 'https://investaftica.jp',
39 | },
40 | {
41 | id: 5,
42 | title: 'OIL & GAS',
43 | image: oilGas,
44 | alt: 'A beautiful building',
45 | linkUrl: 'https://investaftica.jp',
46 | },
47 | {
48 | id: 6,
49 | title: 'HEALTH',
50 | image: health,
51 | alt: 'A beautiful building',
52 | linkUrl: 'https://investaftica.jp',
53 | },
54 | {
55 | id: 7,
56 | title: 'POWER',
57 | image: power,
58 | alt: 'A beautiful building',
59 | linkUrl: 'https://investaftica.jp',
60 | },
61 | {
62 | id: 8,
63 | title: 'FOOD INDUSTRY',
64 | image: foodIndustry,
65 | alt: 'A beautiful building',
66 | linkUrl: 'https://investaftica.jp',
67 | },
68 | {
69 | id: 9,
70 | title: 'AGRICULTURE',
71 | image: agriculture,
72 | alt: 'A beautiful building',
73 | linkUrl: 'https://investaftica.jp',
74 | },
75 | ];
76 |
77 | export default industries;
78 |
--------------------------------------------------------------------------------
/src/components/Footer/Footer.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | background: var(--clr-bg-gray);
3 | }
4 | .content {
5 | padding: 2rem 3rem;
6 | display: flex;
7 | flex-direction: row;
8 | justify-content: center;
9 | }
10 |
11 | .socialmedia {
12 | display: flex;
13 | flex-direction: column;
14 | justify-content: center;
15 | gap: 2rem;
16 | color: var(--clr-lyellow);
17 | }
18 |
19 | .socialmedia_link {
20 | display: flex;
21 | align-items: center;
22 | gap: 1rem;
23 | transition: color 300ms ease;
24 | }
25 |
26 | .socialmedia_link p {
27 | font-size: var(--fs-smaller);
28 | font-weight: var(--fw-medium);
29 | }
30 |
31 | .socialmedia_link:hover,
32 | .socialmedia_link:focus {
33 | color: var(--clr-yellow);
34 | text-decoration: underline;
35 | }
36 |
37 | .divider {
38 | border-left: 2px solid var(--clr-lyellow);
39 | margin-inline-start: auto;
40 | margin-inline-end: auto;
41 | }
42 |
43 | .contactinfo {
44 | margin-inline-start: auto;
45 | color: var(--clr-lyellow);
46 | text-align: right;
47 | max-width: initial;
48 | }
49 |
50 | .subtitle {
51 | margin-block-start: 0.5rem;
52 | color: var(--clr-yellow);
53 | }
54 |
55 | .title {
56 | font-weight: var(--fw-bold);
57 | }
58 |
59 | .icon {
60 | height: 2rem;
61 | width: 2rem;
62 | padding: 0.2rem;
63 | text-align: center;
64 | border-radius: 50%;
65 | color: var(--clr-lyellow);
66 | transition: color 300ms ease;
67 | }
68 |
69 | .icon:hover,
70 | .icon:focus {
71 | color: var(--clr-yellow);
72 | background-color: black;
73 | }
74 |
75 | .spacing {
76 | display: none;
77 | }
78 |
79 | @media (max-width: 992px) {
80 | .content {
81 | flex-direction: column;
82 | align-items: center;
83 | text-align: center;
84 | gap: 1rem;
85 | }
86 |
87 | .socialmedia {
88 | flex-direction: row;
89 | margin-inline-start: 0;
90 | }
91 |
92 | .contactinfo {
93 | margin-inline-start: 0;
94 | margin-inline-end: 0;
95 | text-align: center;
96 | }
97 |
98 | .spacing {
99 | display: block;
100 | }
101 | }
102 |
103 | @media (max-width: 600px) {
104 | .socialmedia {
105 | align-items: center;
106 | justify-content: center;
107 | flex-wrap: wrap;
108 | gap: 1.5rem;
109 | }
110 |
111 | .contactinfo {
112 | margin-inline-start: 0;
113 | margin-inline-end: 0;
114 | text-align: center;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/pages/Login/Login.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | background: url('../../assets/login.webp') no-repeat center center fixed;
3 | background-size: cover;
4 | min-height: var(--desktop-nav-fullheight);
5 | }
6 |
7 | .blur_wrapper {
8 | padding: 3rem;
9 | padding-inline-end: 6rem;
10 | background-color: var(--clr-bg-orange50);
11 | -webkit-backdrop-filter: blur(16px);
12 | backdrop-filter: blur(16px);
13 | min-height: var(--desktop-nav-fullheight);
14 | width: fit-content;
15 | max-width: 100%;
16 | }
17 |
18 | .blur_wrapper h2 {
19 | color: var(--clr-white90);
20 | font-weight: var(--fw-semibold);
21 | margin-block-end: 1rem;
22 | }
23 |
24 | .login_form {
25 | display: flex;
26 | flex-direction: column;
27 | gap: 1rem;
28 | }
29 |
30 | .login_input {
31 | display: flex;
32 | flex-direction: column;
33 | gap: 1.5rem;
34 | }
35 |
36 | .register_link {
37 | color: var(--clr-white70);
38 | margin-block: 1rem;
39 | }
40 |
41 | .link {
42 | color: var(--clr-white90);
43 | }
44 |
45 | .link:hover,
46 | .link:focus {
47 | color: white;
48 | text-decoration: underline;
49 | }
50 |
51 | .checkbox_wrapper {
52 | color: var(--clr-white90);
53 | position: relative;
54 | cursor: pointer;
55 | padding-inline-start: 2rem;
56 | margin-block-end: 1rem;
57 | }
58 |
59 | .checkbox_wrapper:focus .checkbox_icon {
60 | outline: 3px solid var(--clr-white90);
61 | }
62 |
63 | .checkbox_wrapper:focus {
64 | outline: none;
65 | }
66 |
67 | .checkbox_wrapper input[type='checkbox'] {
68 | display: none;
69 | }
70 |
71 | .checkbox_wrapper .checkbox_icon {
72 | position: absolute;
73 | top: 0;
74 | left: 0;
75 | height: 20px;
76 | width: 20px;
77 | background-color: var(--clr-white70);
78 | border-radius: 4px;
79 | cursor: pointer;
80 | }
81 |
82 | .checkbox_wrapper input:checked + .checkbox_icon::before {
83 | content: '✔';
84 | display: block;
85 | position: absolute;
86 | top: 50%;
87 | left: 50%;
88 | transform: translate(-50%, -50%);
89 | color: var(--clr-black90);
90 | }
91 |
92 | .aboutus {
93 | color: var(--clr-white70);
94 | margin-block: 2rem;
95 | max-width: 40ch;
96 | }
97 |
98 | @media (max-width: 600px) {
99 | .wrapper {
100 | background-attachment: initial;
101 | min-height: var(--mobile-nav-fullheight);
102 | }
103 | .blur_wrapper {
104 | min-height: var(--mobile-nav-fullheight);
105 | padding: 2rem;
106 | width: 100%;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/pages/CompanyRegistration/ContactInformation.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/forbid-prop-types */
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 | import styles from './CompanyRegistration.module.css';
5 | import InputBox from '../../components/InputBox/InputBox';
6 |
7 | function ContactInformation(props) {
8 | const { formData, errors, handleChange } = props;
9 | return (
10 |
11 |
12 |
23 |
34 |
35 |
36 |
47 |
58 |
59 |
60 |
71 |
72 |
73 | );
74 | }
75 |
76 | ContactInformation.propTypes = {
77 | formData: PropTypes.shape({
78 | personOfContact: PropTypes.string.isRequired,
79 | position: PropTypes.string.isRequired,
80 | phoneNumber: PropTypes.string.isRequired,
81 | email: PropTypes.string.isRequired,
82 | companyWebsite: PropTypes.string.isRequired,
83 | }).isRequired,
84 | errors: PropTypes.object.isRequired,
85 | handleChange: PropTypes.func.isRequired,
86 | };
87 | export default ContactInformation;
88 |
--------------------------------------------------------------------------------
/src/components/Alert/Alert.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import PropTypes from 'prop-types';
4 | import styles from './Alert.module.css';
5 |
6 | function Alert(props) {
7 | const { message, state, isActive } = props;
8 | const wrapperRef = useRef(null);
9 | const messageRef = useRef(null);
10 | const [wrapperWidth, setWrapperWidth] = useState('3rem');
11 |
12 | useEffect(() => {
13 | const colors = {
14 | success: {
15 | bubbleColor: 'var(--clr-lgreen)',
16 | boxShadowColorInitial: 'var(--clr-lgreen)',
17 | boxShadowColorMid: 'var(--clr-green)',
18 | },
19 | error: {
20 | bubbleColor: 'var(--clr-lred)',
21 | boxShadowColorInitial: 'var(--clr-lred)',
22 | boxShadowColorMid: 'var(--clr-red)',
23 | },
24 | neutral: {
25 | bubbleColor: 'var(--clr-lyellow)',
26 | boxShadowColorInitial: 'var(--clr-lyellow)',
27 | boxShadowColorMid: 'var(--clr-yellow)',
28 | },
29 | };
30 |
31 | const colorState = colors[state] || colors.neutral;
32 |
33 | if (wrapperRef.current) {
34 | wrapperRef.current.style.setProperty('--bubble-color', colorState.bubbleColor);
35 | wrapperRef.current.style.setProperty(
36 | '--box-shadow-color-initial',
37 | colorState.boxShadowColorInitial
38 | );
39 | wrapperRef.current.style.setProperty('--box-shadow-color-mid', colorState.boxShadowColorMid);
40 | }
41 | }, [state]);
42 |
43 | useEffect(() => {
44 | if (isActive && messageRef.current) {
45 | const messageWidth = messageRef.current.offsetWidth;
46 | const padding = 40;
47 | setWrapperWidth(`${messageWidth + padding}px`);
48 | } else {
49 | setWrapperWidth('3rem');
50 | }
51 | }, [isActive, message]);
52 |
53 | return ReactDOM.createPortal(
54 |
59 |
64 |
65 |
69 | {message}
70 |
71 |
72 |
,
73 | document.getElementById('root')
74 | );
75 | }
76 |
77 | Alert.propTypes = {
78 | message: PropTypes.string,
79 | state: PropTypes.oneOf(['success', 'neutral', 'error', '']),
80 | isActive: PropTypes.bool,
81 | };
82 |
83 | Alert.defaultProps = {
84 | message: '',
85 | state: 'neutral',
86 | isActive: false,
87 | };
88 |
89 | export default Alert;
90 |
--------------------------------------------------------------------------------
/src/pages/Home/SectionOne/SectionOne.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | --height: 85vh;
3 |
4 | background-color: black;
5 | position: relative;
6 | overflow: hidden;
7 | width: 100%;
8 | height: var(--height);
9 | }
10 |
11 | .background {
12 | position: absolute;
13 | inset: 0;
14 | background-size: cover;
15 | background-position: center center;
16 | }
17 |
18 | .content {
19 | position: relative;
20 | width: 100%;
21 | height: 100%;
22 | padding-block: calc(var(--height) / 3);
23 | padding-inline: 3rem;
24 | background: linear-gradient(90deg, rgba(0, 0, 0, 0.3) 40%, transparent 80%);
25 | }
26 |
27 | .text {
28 | color: var(--clr-white90);
29 | }
30 |
31 | .title {
32 | --bg-size: 250%;
33 |
34 | font-weight: var(--fw-semibold);
35 | padding-block-end: 1rem;
36 | }
37 |
38 | .subtitle {
39 | line-height: 130%;
40 | }
41 |
42 | /* *Start for animated gradient text and fade-in */
43 | @media (prefers-reduced-motion: no-preference) {
44 | .title {
45 | transform: translateY(25px);
46 | opacity: 0;
47 | pointer-events: none;
48 | background: linear-gradient(
49 | 120deg,
50 | white 0%,
51 | var(--clr-lyellow) 4%,
52 | var(--clr-yellow) 14%,
53 | var(--clr-yellow) 21%,
54 | black 45%,
55 | black 51%,
56 | var(--clr-yellow) 59%,
57 | black 68%,
58 | var(--clr-lyellow) 83%,
59 | var(--clr-yellow) 86%,
60 | var(--clr-lyellow) 89%,
61 | black 94%
62 | )
63 | var(--bg-size) 0 / var(--bg-size) 100%;
64 | color: black;
65 | -webkit-background-clip: text;
66 | background-clip: text;
67 | animation:
68 | fade-in 500ms cubic-bezier(0, 0, 0.5, 1) forwards,
69 | move-bg 3s cubic-bezier(0.29, 0.31, 0, 1.01) forwards 500ms,
70 | color-to-white 500ms linear forwards 3.5s;
71 | }
72 |
73 | .subtitle {
74 | opacity: 0;
75 | transform: translateY(25px);
76 | pointer-events: none;
77 | animation: fade-in 500ms cubic-bezier(0, 0, 0.5, 1) forwards 2.5s;
78 | }
79 |
80 | @keyframes fade-in {
81 | to {
82 | transform: translateY(0);
83 | opacity: 1;
84 | pointer-events: auto;
85 | }
86 | }
87 |
88 | @keyframes move-bg {
89 | 100% {
90 | color: transparent;
91 | background-position: 0 0;
92 | }
93 | }
94 |
95 | @keyframes color-to-white {
96 | to {
97 | color: white;
98 | }
99 | }
100 | }
101 | /* *End for animated gradient text and fade-in */
102 |
103 | @media (max-width: 600px) {
104 | .wrapper {
105 | --height: 75vh;
106 | }
107 | .content {
108 | padding-inline: 2rem;
109 | background-color: rgba(0, 0, 0, 0.3);
110 | }
111 | .text {
112 | text-align: center;
113 | }
114 | .title {
115 | /* *Ensuring black area of gradient not present on text at end */
116 | --bg-size: 275%;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/components/FadeTransition/FadeTransition.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { motion, AnimatePresence, useReducedMotion } from 'framer-motion';
3 | import PropTypes from 'prop-types';
4 |
5 | export default function FadeTransition(props) {
6 | const shouldReduceMotion = useReducedMotion();
7 | const {
8 | children,
9 | keys,
10 | mode,
11 | duration,
12 | variants,
13 | className,
14 | style,
15 | translate,
16 | scrollAnimation,
17 | once,
18 | amount,
19 | top,
20 | bottom,
21 | staggerChildren,
22 | } = props;
23 |
24 | const finalDuration = scrollAnimation ? duration : duration / 2;
25 |
26 | // * start - variants for motion element
27 | const syncVariants = {
28 | initial: { opacity: 0, translateY: `${translate}px` },
29 | enter: {
30 | opacity: 1,
31 | translateY: '0px',
32 | transition: { duration: finalDuration, staggerChildren, ease: [0, 0, 0.5, 1] },
33 | },
34 | exit: { opacity: 0, translateY: `${translate}px`, transition: { duration: finalDuration } },
35 | };
36 |
37 | const waitVariants = {
38 | initial: { opacity: 0, translateY: `${translate}px` },
39 | enter: {
40 | opacity: 1,
41 | translateY: '0px',
42 | transition: {
43 | duration: finalDuration,
44 | delay: finalDuration,
45 | staggerChildren,
46 | ease: [0, 0, 0.5, 1],
47 | },
48 | },
49 | exit: { opacity: 0, translateY: `${translate}px`, transition: { duration: finalDuration } },
50 | };
51 | // * end - variants for motion element
52 |
53 | const chosenVariants = variants === 'sync' ? syncVariants : waitVariants;
54 |
55 | const animationProps = scrollAnimation
56 | ? { whileInView: 'enter', viewport: { once, amount, margin: `${top}px 0px ${bottom}px 0px` } }
57 | : { animate: 'enter', exit: 'exit' };
58 |
59 | const reducedMotionProps = shouldReduceMotion
60 | ? {}
61 | : { key: keys, initial: 'initial', ...animationProps, variants: chosenVariants };
62 |
63 | return (
64 |
65 |
71 | {children}
72 |
73 |
74 | );
75 | }
76 |
77 | FadeTransition.propTypes = {
78 | keys: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
79 | variants: PropTypes.oneOf(['sync', 'wait']),
80 | mode: PropTypes.oneOf(['sync', 'wait', 'popLayout']),
81 | duration: PropTypes.number,
82 | children: PropTypes.node.isRequired,
83 | className: PropTypes.string,
84 | style: PropTypes.object,
85 | translate: PropTypes.number,
86 | scrollAnimation: PropTypes.bool,
87 | once: PropTypes.bool,
88 | amount: PropTypes.oneOfType([PropTypes.oneOf(['some', 'all']), PropTypes.number]),
89 | top: PropTypes.number,
90 | bottom: PropTypes.number,
91 | staggerChildren: PropTypes.number,
92 | };
93 |
94 | FadeTransition.defaultProps = {
95 | keys: null,
96 | className: null,
97 | variants: 'sync',
98 | mode: 'sync',
99 | duration: 0.5,
100 | style: null,
101 | translate: 0,
102 | scrollAnimation: false,
103 | once: true,
104 | amount: 'some',
105 | top: 0,
106 | bottom: 0,
107 | staggerChildren: 0,
108 | };
109 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url(reset.css);
2 |
3 | @font-face {
4 | font-family: 'Poppins';
5 | src: url('./assets/fonts/Poppins-Regular.woff2');
6 | font-weight: 500;
7 | font-display: swap;
8 | }
9 | @font-face {
10 | font-family: 'Poppins';
11 | src: url('./assets/fonts/Poppins-Medium.woff2');
12 | font-weight: 600;
13 | font-display: swap;
14 | }
15 | @font-face {
16 | font-family: 'Poppins';
17 | src: url('./assets/fonts/Poppins-SemiBold.woff2');
18 | font-weight: 700;
19 | font-display: swap;
20 | }
21 | @font-face {
22 | font-family: 'Poppins';
23 | src: url('./assets/fonts/Poppins-Bold.woff2');
24 | font-weight: 800;
25 | font-display: swap;
26 | }
27 |
28 | :root {
29 | color-scheme: dark;
30 | --font-stack: 'Poppins', system-ui, 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI',
31 | 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
32 |
33 | --desktop-nav-fullheight: calc(100vh - 56px);
34 | --mobile-nav-fullheight: calc(100vh - 48px);
35 |
36 | --clr-white90: #ffffffe6;
37 | --clr-white70: #ffffffb3;
38 |
39 | --clr-black90: #000000e6;
40 | --clr-black70: #000000b3;
41 |
42 | --clr-bg-gray: #121214;
43 | --clr-bg-lgray: #f0f0f4;
44 | --clr-bg-gray90: #1f1f22e6;
45 | --clr-bg-lgray90: #f0f0f4e6;
46 | --clr-bg-gray70: #1f1f22b3;
47 | --clr-bg-lgray70: #f0f0f4b3;
48 | --clr-bg-orange50: #150a0685;
49 |
50 | --clr-red: #ff0000;
51 | --clr-mred: #ff6767;
52 | --clr-lred: #ff7e7e;
53 | --clr-yellow: #ffcc00;
54 | --clr-lyellow: #ffe478;
55 | --clr-green: #006239;
56 | --clr-lgreen: #23c83e;
57 |
58 | --fw-regular: 500;
59 | --fw-medium: 600;
60 | --fw-semibold: 700;
61 | --fw-bold: 800;
62 |
63 | --fs-h1: clamp(0.8rem, 8vw + 0.8rem, 5.4rem);
64 | --fs-h2: clamp(0.8rem, 7vw + 0.4rem, 3.5rem);
65 | --fs-h3: clamp(0.6rem, 7vw + 0.4rem, 3rem);
66 | --fs-h4: clamp(0.5rem, 6vw + 0.2rem, 2.4rem);
67 | --fs-h5: clamp(0.4rem, 5vw + 0.2rem, 1.5rem);
68 | --fs-p: clamp(0.3rem, 3vw + 0.3rem, 1.25rem);
69 | --fs-small: clamp(0.25rem, 3vw + 0.3rem, 1.1rem);
70 | --fs-smaller: clamp(0.2rem, 3vw + 0.3rem, 1rem);
71 | --fs-smallest: clamp(0.2rem, 3vw + 0.3rem, 0.9rem);
72 | }
73 |
74 | html {
75 | background-color: #fff;
76 | box-sizing: border-box;
77 | scroll-behavior: smooth;
78 | scroll-padding-block-start: 250px;
79 | max-width: 1600px;
80 | margin-inline: auto;
81 | }
82 |
83 | *,
84 | *::before,
85 | *::after {
86 | box-sizing: inherit;
87 | min-width: 0;
88 | }
89 |
90 | body {
91 | font-family: var(--font-stack);
92 | -webkit-font-smoothing: antialiased;
93 | -moz-osx-font-smoothing: grayscale;
94 | min-height: 100vh;
95 | }
96 |
97 | code {
98 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
99 | }
100 |
101 | h1,
102 | h2,
103 | h3,
104 | h4,
105 | h5,
106 | h6,
107 | p {
108 | font-weight: initial;
109 | padding: 0;
110 | margin: 0;
111 | }
112 |
113 | h1 {
114 | font-size: var(--fs-h1);
115 | letter-spacing: -1.2px;
116 | }
117 |
118 | h2 {
119 | font-size: var(--fs-h2);
120 | letter-spacing: -0.7px;
121 | }
122 |
123 | h3 {
124 | font-size: var(--fs-h3);
125 | letter-spacing: -0.48px;
126 | }
127 |
128 | h4 {
129 | font-size: var(--fs-h4);
130 | letter-spacing: -0.28px;
131 | }
132 |
133 | h5 {
134 | font-size: var(--fs-h5);
135 | letter-spacing: -0.1px;
136 | }
137 |
138 | p {
139 | font-size: var(--fs-p);
140 | line-height: 2;
141 | max-width: 40ch;
142 | }
143 |
--------------------------------------------------------------------------------
/src/components/InputBox/InputBox.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useMediaQuery } from '@react-hook/media-query';
3 | import { FaChevronDown } from 'react-icons/fa6';
4 | import { AiOutlineInfoCircle } from 'react-icons/ai';
5 | import classNames from 'classnames';
6 | import PropTypes from 'prop-types';
7 | import styles from './InputBox.module.css';
8 | import TextBox from './TextBox';
9 | import DropdownBox from './DropdownBox';
10 |
11 | function getResponsiveClassName(size, isAddress, isTablet, isMobile) {
12 | if (isAddress) {
13 | // ?If isAddress is true, always use the largest size for the respective media query
14 | if (isMobile) {
15 | return styles.size_mobile_large;
16 | }
17 | if (isTablet) {
18 | return styles.size_tablet_xlarge;
19 | }
20 | return styles.size_xlarge;
21 | }
22 | // ?Regular size handling
23 | if (isMobile) {
24 | return classNames({
25 | [styles.size_mobile_large]: size === 'large',
26 | [styles.size_mobile_small]: size === 'small',
27 | });
28 | }
29 | if (isTablet) {
30 | return classNames({
31 | [styles.size_tablet_large]: size === 'large',
32 | [styles.size_tablet_small]: size === 'small',
33 | });
34 | }
35 | return classNames({
36 | [styles.size_large]: size === 'large',
37 | [styles.size_small]: size === 'small',
38 | });
39 | }
40 |
41 | function InputBox(props) {
42 | const [isTooltipOpen, setIsTooltipOpen] = useState(false);
43 |
44 | const toggleTooltip = () => {
45 | setIsTooltipOpen(!isTooltipOpen);
46 | };
47 |
48 | const { message, isDropdown, label, id, size, tooltip, isAddress, children } = props;
49 | const options = children;
50 |
51 | const isTablet = useMediaQuery('(max-width: 992px)');
52 | const isMobile = useMediaQuery('(max-width: 600px)');
53 | const wrapperClass = getResponsiveClassName(size, isAddress, isTablet, isMobile);
54 | const addressHeight = isAddress ? '10rem' : '';
55 |
56 | return (
57 |
58 |
79 |
80 | {!isDropdown ? (
81 |
82 | ) : (
83 |
84 | )}
85 | {isDropdown && }
86 |
87 |
{message}
88 |
89 | );
90 | }
91 |
92 | InputBox.propTypes = {
93 | label: PropTypes.string.isRequired,
94 | id: PropTypes.string.isRequired,
95 | message: PropTypes.string,
96 | tooltip: PropTypes.string,
97 | isDropdown: PropTypes.bool,
98 | isAddress: PropTypes.bool,
99 | size: PropTypes.oneOf(['large', 'small']),
100 | children: PropTypes.node,
101 | };
102 |
103 | InputBox.defaultProps = {
104 | message: '',
105 | tooltip: '',
106 | isDropdown: false,
107 | isAddress: false,
108 | size: 'large',
109 | children: '',
110 | };
111 |
112 | export default InputBox;
113 |
--------------------------------------------------------------------------------
/src/utils/validateCompany.jsx:
--------------------------------------------------------------------------------
1 | import Joi from 'joi';
2 | import { countries } from 'countries-list';
3 |
4 | const validCountries = Object.values(countries).map(country => country.name);
5 |
6 | export const companySchema = Joi.object({
7 | companyName: Joi.string().max(255).required().messages({
8 | 'string.base': 'Input must be text.',
9 | 'string.max': 'Input must not exceed 255 characters.',
10 | }),
11 | businessType: Joi.string().max(255).required().messages({
12 | 'string.base': 'Input must be text.',
13 | 'string.max': 'Input must not exceed 255 characters.',
14 | }),
15 | numberOfEmployees: Joi.number().integer().min(1).max(10000000).required().messages({
16 | 'number.base': 'Input must be an integer.',
17 | 'number.min': 'Input cannot be below 1.',
18 | 'number.max': 'Input cannot be above 10000000.',
19 | }),
20 | yearOfEstablishment: Joi.number()
21 | .integer()
22 | .min(1500)
23 | .max(new Date().getFullYear())
24 | .required()
25 | .messages({
26 | 'number.base': 'Input must be a number.',
27 | 'number.min': 'Input cannot be below 1500.',
28 | 'number.max': `Input cannot be in the future.`,
29 | }),
30 | country: Joi.string()
31 | .valid(...validCountries)
32 | .required()
33 | .messages({
34 | 'string.base': 'Country must be text.',
35 | 'any.only': 'Please select a valid country.',
36 | }),
37 | city: Joi.string().max(255).required().messages({
38 | 'string.base': 'Input must be text.',
39 | 'string.max': 'Input must not exceed 255 characters.',
40 | }),
41 | postCode: Joi.string().max(255).allow('').allow(null).messages({
42 | 'string.max': 'Input must not exceed 255 characters.',
43 | }),
44 | industry: Joi.string().max(255).required().messages({
45 | 'string.base': 'Input must be text.',
46 | 'string.max': 'Input must not exceed 255 characters.',
47 | }),
48 | accountPassword: Joi.string()
49 | .pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d$_.!@#%^&*()\-+=]{8,128}$/)
50 | .required()
51 | .messages({
52 | 'string.pattern.base': 'Password requirements not met.',
53 | }),
54 | confirmPassword: Joi.valid(Joi.ref('accountPassword')).required().messages({
55 | 'any.only': 'Passwords must match.',
56 | }),
57 | companyAddress: Joi.string().max(512).required().messages({
58 | 'string.base': 'Input must be text.',
59 | 'string.max': 'Input must not exceed 512 characters.',
60 | }),
61 | personOfContact: Joi.string().max(255).required().messages({
62 | 'string.base': 'Input must be text.',
63 | 'string.max': 'Input must not exceed 255 characters.',
64 | }),
65 | position: Joi.string().max(255).required().messages({
66 | 'string.base': 'Input must be text.',
67 | 'string.max': 'Input must not exceed 255 characters.',
68 | }),
69 | phoneNumber: Joi.string()
70 | .pattern(/^\+?[0-9-\s]*[0-9]{8,}[0-9-\s]*$/)
71 | .required()
72 | .messages({
73 | 'string.pattern.base': 'Input must be a valid phone number.',
74 | }),
75 | email: Joi.string()
76 | .email({ tlds: { allow: false } })
77 | .max(255)
78 | .required()
79 | .messages({
80 | 'string.email': 'Please enter a valid email address.',
81 | 'string.max': 'Email must not exceed 255 characters.',
82 | }),
83 | companyWebsite: Joi.string()
84 | .uri({ scheme: ['http', 'https'] })
85 | .required()
86 | .messages({
87 | 'string.uri': 'Please enter a valid URL with http or https.',
88 | 'string.uriCustomScheme': 'Please enter a valid URL with http or https.',
89 | }),
90 | }).with('accountPassword', 'confirmPassword');
91 |
92 | export const validateCompanyData = data => {
93 | const { error } = companySchema.validate(data, { abortEarly: false });
94 | if (!error) return null;
95 |
96 | const customErrors = error.details.reduce((acc, detail) => {
97 | const { message } = detail;
98 |
99 | // *Remove empty field warning.
100 | if (message.includes('is not allowed to be empty')) {
101 | acc[detail.path[0]] = '';
102 | return acc;
103 | }
104 |
105 | acc[detail.path[0]] = message;
106 |
107 | return acc;
108 | }, {});
109 |
110 | return customErrors;
111 | };
112 |
--------------------------------------------------------------------------------
/src/data/countryContent.js:
--------------------------------------------------------------------------------
1 | const content = [
2 | {
3 | id: 1,
4 | title: 'MOROCCO',
5 | body: 'Morocco boasts strategic access to European markets, a stable political climate, and thriving tourism and renewable energy sectors.',
6 | color: '#004F9F',
7 | },
8 | {
9 | id: 2,
10 | title: 'NIGERIA',
11 | body: "Nigeria, Africa's largest economy, offers vast oil resources, a growing young workforce, and expanding tech and entertainment industries.",
12 | color: '#007736',
13 | },
14 | {
15 | id: 3,
16 | title: 'KENYA',
17 | body: 'Kenya shines with a strong ICT sector, significant agricultural exports, a strategic location for trade, and development focus.',
18 | color: '#D51224',
19 | },
20 | {
21 | id: 4,
22 | title: 'NAMIBIA',
23 | body: 'Namibia presents opportunities with its rich mineral deposits, renewable energy potential, political stability, and progressive investment policies.',
24 | color: '#EF7D00',
25 | },
26 | ];
27 |
28 | const morocco = [
29 | {
30 | id: 1,
31 | title: 'Land Area',
32 | body: 'Morocco spans 446,550 sq. km, offering room for industrial and touristic projects.',
33 | color: '#004F9F',
34 | },
35 | {
36 | id: 2,
37 | title: 'Population',
38 | body: 'The population of 36.9 million in Morocco spells opportunity for consumer-driven growth.',
39 | color: '#007736',
40 | },
41 | {
42 | id: 3,
43 | title: 'GDP',
44 | body: "Morocco's GDP reached US$119 billion in 2022, reflecting its economic resilience and potential.",
45 | color: '#D51224',
46 | },
47 | {
48 | id: 4,
49 | title: 'Labour Force',
50 | body: "With 11.8 million people, Morocco's labour force is ready to meet business demands.",
51 | color: '#EF7D00',
52 | },
53 | ];
54 |
55 | const nigeria = [
56 | {
57 | id: 1,
58 | title: 'Land Area',
59 | body: 'Nigeria offers a substantial land area of 923,769 sq. km for development and agriculture.',
60 | color: '#004F9F',
61 | },
62 | {
63 | id: 2,
64 | title: 'Population',
65 | body: "As of 2023, Nigeria's population of 230.8 million promises a vast consumer base.",
66 | color: '#007736',
67 | },
68 | {
69 | id: 3,
70 | title: 'GDP',
71 | body: "Nigeria's GDP stood at US$400 billion in 2022, indicating a significant economic footprint.",
72 | color: '#D51224',
73 | },
74 | {
75 | id: 4,
76 | title: 'Labour Force',
77 | body: 'With a labour force of 126 million in 2022, Nigeria boasts a substantial workforce.',
78 | color: '#EF7D00',
79 | },
80 | ];
81 |
82 | const kenya = [
83 | {
84 | id: 1,
85 | title: 'Land Area',
86 | body: "Kenya's 580,367 sq. km land supports diverse commercial and agricultural initiatives.",
87 | color: '#004F9F',
88 | },
89 | {
90 | id: 2,
91 | title: 'Population',
92 | body: 'With 47.6 million people, Kenya provides a robust market for new businesses.',
93 | color: '#007736',
94 | },
95 | {
96 | id: 3,
97 | title: 'GDP',
98 | body: "The GDP of US$95.5 billion in 2022 underscores Kenya's growing economic landscape.",
99 | color: '#D51224',
100 | },
101 | {
102 | id: 4,
103 | title: 'Labour Force',
104 | body: "Kenya's workforce of 19.1 million is an asset for productivity and local talent.",
105 | color: '#EF7D00',
106 | },
107 | ];
108 |
109 | const namibia = [
110 | {
111 | id: 1,
112 | title: 'Land Area',
113 | body: "Namibia's expansive 824,292 sq. km terrain is rich in natural resources and beauty.",
114 | color: '#004F9F',
115 | },
116 | {
117 | id: 2,
118 | title: 'Population',
119 | body: 'Namibia, with a population of 2.5 million, offers a stable, growing consumer segment.',
120 | color: '#007736',
121 | },
122 | {
123 | id: 3,
124 | title: 'GDP',
125 | body: "The GDP of US$12.3 billion in 2022 indicates Namibia's steady economic environment.",
126 | color: '#D51224',
127 | },
128 | {
129 | id: 4,
130 | title: 'Labour Force',
131 | body: 'A labour force of 1.3 million in Namibia provides a foundation for skilled labor.',
132 | color: '#EF7D00',
133 | },
134 | ];
135 |
136 | export { content, morocco, nigeria, kenya, namibia };
137 |
--------------------------------------------------------------------------------
/src/components/Alert/Alert.module.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --text-transition-delay: 200ms;
3 | --text-transition-duration: 200ms;
4 | --grace-transition-duration: 200ms;
5 | --width-transition-duration: 400ms;
6 | --scale-transition-duration: 300ms;
7 | --bottom-transition-duration: 300ms;
8 | --bubble-animation-duration: 2000ms;
9 | --width-close-transition-duration: 300ms;
10 | --bubble-animation-delay: calc(var(--bubble-animation-duration) / 2);
11 | --pill-transition-delay: calc(var(--bubble-animation-delay) + var(--width-transition-duration));
12 | --pill-close-transition-delay: calc(
13 | var(--width-close-transition-duration) + var(--grace-transition-duration)
14 | );
15 | --pill-scale-close-transition-delay: calc(
16 | var(--pill-close-transition-delay) + var(--scale-transition-duration)
17 | );
18 |
19 | --bubble-color: var(--clr-lyellow);
20 | --box-shadow-color-initial: var(--clr-lyellow);
21 | --box-shadow-color-mid: var(--clr-yellow);
22 | }
23 |
24 | /* *Fade-out animation for wrapper */
25 | .wrapper {
26 | position: fixed;
27 | bottom: -10px;
28 | left: 50%;
29 | transform: translateX(-50%) scale(0);
30 | height: 3rem;
31 | z-index: 1000;
32 | transition:
33 | bottom var(--bottom-transition-duration) linear var(--pill-scale-close-transition-delay),
34 | transform var(--scale-transition-duration) cubic-bezier(.25,.64,.51,.99) var(--pill-close-transition-delay),
35 | width var(--width-close-transition-duration) cubic-bezier(0.01, 0.8, 0.56, 1)
36 | var(--text-transition-delay);
37 | }
38 |
39 | /* *Fade-in animation for wrapper */
40 | .wrapper_active {
41 | bottom: 40px;
42 | transform: translateX(-50%) scale(1);
43 | transition:
44 | bottom var(--bottom-transition-duration) ease-in,
45 | transform var(--scale-transition-duration) ease-in,
46 | width var(--width-transition-duration) cubic-bezier(0, 0.46, 0.36, 1.34)
47 | var(--bubble-animation-delay);
48 | }
49 |
50 | /* *Fade-out animation for bubble (none) */
51 | .background_wrapper {
52 | position: absolute;
53 | top: 50%;
54 | left: 50%;
55 | transform: translate(-50%, -50%);
56 | background-color: var(--bubble-color);
57 | border-radius: 100vw;
58 | height: 0%;
59 | width: 0%;
60 | z-index: -1;
61 | }
62 |
63 | /* *Fade-in animation for bubble */
64 | .background_wrapper_active {
65 | animation: expandAndShrink var(--bubble-animation-duration) cubic-bezier(0.42, 0, 0.33, 1.02)
66 | forwards;
67 | }
68 |
69 | /* *Fade-out animation for alert border */
70 | .alert_wrapper {
71 | display: grid;
72 | place-items: center;
73 | background-color: var(--clr-bg-gray90);
74 | -webkit-backdrop-filter: blur(20px) saturate(180%);
75 | backdrop-filter: blur(20px) saturate(180%);
76 | border-radius: 100vw;
77 | white-space: nowrap;
78 | height: 100%;
79 | width: 100%;
80 | z-index: 1;
81 | }
82 |
83 | /* *Fade-in animation for alert border */
84 | .alert_wrapper_active {
85 | animation: rotateBoxShadowColor 3s ease-in-out forwards
86 | calc(var(--pill-transition-delay) + var(--grace-transition-duration));
87 | }
88 |
89 | /* *Fade-out animation for alert message */
90 | .message {
91 | font-size: var(--fs-small);
92 | color: transparent;
93 | transition: color var(--text-transition-duration) ease-out;
94 | }
95 |
96 | /* *Fade-in animation for alert message */
97 | .message_active {
98 | color: white;
99 | transition: color var(--text-transition-duration) ease-out var(--pill-transition-delay);
100 | }
101 |
102 | /* *Bubble Keyframe */
103 | @keyframes expandAndShrink {
104 | 0%,
105 | 100% {
106 | height: 0%;
107 | width: 0%;
108 | opacity: 0;
109 | }
110 | 10% {
111 | height: 50%;
112 | width: 50%;
113 | opacity: 1;
114 | }
115 | 30%,
116 | 40% {
117 | height: 180%;
118 | width: 180%;
119 | }
120 | 60% {
121 | height: 0%;
122 | width: 0%;
123 | opacity: 1;
124 | }
125 | }
126 |
127 | /* *Alert Border (Box-Shadow) Keyframe */
128 | @keyframes rotateBoxShadowColor {
129 | 0%,
130 | 100% {
131 | box-shadow: 0px 0px 0px 4px transparent;
132 | }
133 | 10%,
134 | 50%,
135 | 85% {
136 | box-shadow: 0px 0px 0px 4px var(--box-shadow-color-initial);
137 | }
138 | 30%,
139 | 70% {
140 | box-shadow: 0px 0px 0px 4px var(--box-shadow-color-mid);
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/components/Navbar/Navbar.module.css:
--------------------------------------------------------------------------------
1 | .nav {
2 | color: black;
3 | padding: 0.5rem 2rem;
4 | display: flex;
5 | justify-content: space-between;
6 | align-items: center;
7 | position: sticky;
8 | inset: 0 0 auto 0;
9 | height: 56px; /* !Desktop Navbar height */
10 | z-index: 9999;
11 | isolation: isolate;
12 | }
13 |
14 | .nav::before {
15 | content: '';
16 | position: absolute;
17 | inset: 0;
18 | background-color: var(--clr-white70);
19 | -webkit-backdrop-filter: blur(20px) saturate(180%);
20 | backdrop-filter: blur(20px) saturate(180%);
21 | z-index: -1;
22 | }
23 |
24 | .logo_wrapper {
25 | display: flex;
26 | align-items: center;
27 | }
28 |
29 | .logo_wrapper:has(:focus-visible) {
30 | outline: 2px solid var(--clr-lgreen);
31 | }
32 |
33 | .logo {
34 | width: 72px;
35 | aspect-ratio: 1 / 0.6;
36 | }
37 |
38 | .skip_nav {
39 | pointer-events: none;
40 | position: absolute;
41 | left: 130px;
42 | opacity: 0;
43 | z-index: 5;
44 | background-color: var(--clr-bg-lgray);
45 | border-radius: 100vw;
46 | font-weight: var(--fw-semibold);
47 | padding: 0.75rem 1.5rem;
48 | }
49 |
50 | .skip_nav:focus-visible {
51 | opacity: 1;
52 | }
53 |
54 | .menu_icon {
55 | display: none;
56 | }
57 |
58 | .flag {
59 | width: 18px;
60 | height: 18px;
61 | }
62 |
63 | .extra_nav_styling {
64 | display: flex;
65 | margin-inline-end: 1rem;
66 | gap: 4vw;
67 | }
68 |
69 | .nav_links {
70 | font-weight: var(--fw-semibold);
71 | font-size: var(--fs-smaller);
72 | text-transform: uppercase;
73 | cursor: pointer;
74 | }
75 |
76 | .nav_link {
77 | line-height: 1.5;
78 | transition: all 0.3s ease;
79 | }
80 |
81 | .nav_link:hover,
82 | .nav_link:focus-within {
83 | text-decoration: underline 2px;
84 | text-underline-offset: 4px;
85 | color: var(--clr-lgreen);
86 | }
87 |
88 | /* ?For nav_links with dropdowns */
89 |
90 | .dropdown_container {
91 | position: relative;
92 | }
93 |
94 | .dropdown_container::before {
95 | /* ?extending size of button to allow hover on dropdown */
96 | content: '';
97 | position: absolute;
98 | inset: 0 0 -36px 0;
99 | z-index: -1;
100 | display: none;
101 | }
102 |
103 | .dropdown_container:hover::before {
104 | display: block;
105 | }
106 |
107 | .dropdown_button {
108 | display: flex;
109 | gap: 0.5rem;
110 | text-transform: uppercase;
111 | }
112 |
113 | .dropdown_button:focus {
114 | outline: 0; /* ?nav_links are already clearly focusable (FF fix) */
115 | }
116 |
117 | .dropdown_content {
118 | display: none;
119 | position: absolute;
120 | padding: 1rem;
121 | top: 50%;
122 | left: 50%;
123 | transform: translate(-50%, 28px); /* ?28px to start for dropdown to start at bottom of nav */
124 | text-align: center;
125 | background-color: white;
126 | }
127 |
128 | .dropdown_content .nav_links {
129 | display: flex;
130 | flex-direction: column;
131 | gap: 1rem;
132 | }
133 |
134 | .dropdown_container:hover .dropdown_content,
135 | .dropdown_container:focus-within .dropdown_content {
136 | display: block;
137 | }
138 |
139 | .scrolled_dropdown {
140 | background-color: var(--clr-white70);
141 | -webkit-backdrop-filter: blur(20px) saturate(180%);
142 | backdrop-filter: blur(20px) saturate(180%);
143 | }
144 |
145 | .hide {
146 | display: none;
147 | }
148 |
149 | @media (max-width: 992px) {
150 | .extra_nav_styling {
151 | display: none;
152 | gap: initial;
153 | margin-inline-end: 0;
154 | }
155 |
156 | .menu_icon {
157 | display: block;
158 | cursor: pointer;
159 | z-index: 2;
160 | height: 24px;
161 | width: 24px;
162 | }
163 |
164 | .icon {
165 | display: none;
166 | }
167 |
168 | .mobile_nav {
169 | display: flex;
170 | flex-direction: column;
171 | position: fixed;
172 | top: 0;
173 | right: -50%;
174 | bottom: 0;
175 | width: 50%;
176 | padding-block: 6rem 2rem;
177 | padding-inline-end: 2rem;
178 | background-color: rgb(255, 255, 255);
179 | overflow-y: auto;
180 | transition: right 0.3s ease-in-out;
181 | -webkit-tap-highlight-color: transparent;
182 | }
183 |
184 | .mobile_nav_open {
185 | right: 0;
186 | transition: right 0.3s ease-in-out;
187 | }
188 |
189 | .dropdown_content .nav_links {
190 | gap: 2rem;
191 | }
192 |
193 | .nav_links {
194 | font-weight: var(--fw-medium);
195 | font-size: var(--fs-p);
196 | text-transform: initial;
197 | text-align: right;
198 | gap: 2rem;
199 | }
200 |
201 | .dropdown_button:not(.lang_button) {
202 | display: none;
203 | }
204 |
205 | .lang_button {
206 | margin-inline-start: auto;
207 | }
208 |
209 | .dropdown_content {
210 | display: block;
211 | position: initial;
212 | background-color: initial;
213 | padding: initial;
214 | width: initial;
215 | transform: initial;
216 | text-align: initial;
217 | transition: initial;
218 | }
219 | }
220 |
221 | @media (max-width: 600px) {
222 | .nav {
223 | height: 48px; /* !Mobile Navbar height */
224 | padding-inline: 1rem 2rem;
225 | }
226 |
227 | .skip_nav {
228 | display: none;
229 | }
230 |
231 | .logo {
232 | width: 64px;
233 | }
234 |
235 | .mobile_nav {
236 | right: -75%;
237 | width: 75%;
238 | }
239 |
240 | .mobile_nav_open {
241 | right: 0;
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/src/pages/Login/Login.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/no-noninteractive-tabindex */
2 | import React, { useState } from 'react';
3 | import { Link, useNavigate } from 'react-router-dom';
4 | import styles from './Login.module.css';
5 | import Button from '../../components/Button/Button';
6 | import Alert from '../../components/Alert/Alert';
7 | import InputBox from '../../components/InputBox/InputBox';
8 | import FadeTransition from '../../components/FadeTransition/FadeTransition';
9 |
10 | function Login() {
11 | const LOGIN_URL = `${import.meta.env.VITE_APP_BACKEND_URL}/login`;
12 | const navigate = useNavigate();
13 | const [buttonText, setButtonText] = useState('Login');
14 |
15 | const alertDuration = 5000;
16 | const [alert, setAlert] = useState({ message: '', state: '', active: false });
17 |
18 | const toggleAlert = () => {
19 | setAlert(prevAlert => ({
20 | ...prevAlert,
21 | active: !prevAlert.active,
22 | }));
23 | };
24 |
25 | const [formData, setFormData] = React.useState({
26 | email: '',
27 | accountPassword: '',
28 | accountType: '',
29 | remember: '',
30 | });
31 |
32 | const handleChange = event => {
33 | const { name, value, type, checked } = event.target;
34 | setFormData(prevFormData => ({
35 | ...prevFormData,
36 | [name]: type === 'checkbox' ? checked : value,
37 | }));
38 | };
39 |
40 | const handleSubmit = async event => {
41 | event.preventDefault();
42 | const data = {
43 | email: formData.email,
44 | accountPassword: formData.accountPassword,
45 | accountType: formData.accountType,
46 | remember: formData.remember,
47 | };
48 | setButtonText('...');
49 | try {
50 | const response = await fetch(LOGIN_URL, {
51 | method: 'POST',
52 | headers: {
53 | 'Content-Type': 'application/json',
54 | },
55 | body: JSON.stringify(data),
56 | });
57 | let alertMessage = '';
58 | let alertState = '';
59 |
60 | if (response.ok) {
61 | alertMessage = 'Login successful. Redirecting...';
62 | alertState = 'success';
63 | setTimeout(() => {
64 | navigate('/');
65 | }, alertDuration + 1000);
66 | setButtonText('Login');
67 | } else {
68 | alertMessage = 'Login failed, please retry.';
69 | alertState = 'error';
70 | setButtonText('Login');
71 | }
72 |
73 | // !Start Alert with set alert-data.
74 | setAlert(prevAlert => ({
75 | ...prevAlert,
76 | message: alertMessage,
77 | state: alertState,
78 | }));
79 | toggleAlert();
80 |
81 | const alertTimeout = setTimeout(() => {
82 | toggleAlert();
83 | }, alertDuration);
84 |
85 | return () => clearTimeout(alertTimeout);
86 | // *catch when unknown error happens
87 | } catch (error) {
88 | setAlert(prevAlert => ({
89 | ...prevAlert,
90 | message: error.toString(),
91 | state: 'error',
92 | }));
93 | toggleAlert();
94 |
95 | const alertTimeout = setTimeout(() => {
96 | toggleAlert();
97 | }, alertDuration);
98 | return () => clearTimeout(alertTimeout);
99 | }
100 | };
101 |
102 | return (
103 |
104 |
105 |
106 |
175 |
176 | © {new Date().getFullYear()} - Invest Africa :: Powered by Adam-i Japan
177 |
178 |
179 |
180 |
181 |
182 | );
183 | }
184 |
185 | export default Login;
186 |
--------------------------------------------------------------------------------
/src/pages/CompanyRegistration/BasicInformation.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/forbid-prop-types */
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 | import { Tooltip } from 'react-tooltip';
5 | import countryOptions from './sortedCountries';
6 | import styles from './CompanyRegistration.module.css';
7 | import InputBox from '../../components/InputBox/InputBox';
8 |
9 | function BasicInformation(props) {
10 | const { formData, errors, isTouched, handleChange } = props;
11 |
12 | return (
13 |
14 |
15 |
26 |
37 |
38 |
39 |
40 |
52 |
65 |
66 |
67 |
68 |
79 | {countryOptions.map(country => (
80 |
83 | ))}
84 |
85 |
96 |
106 |
107 |
108 |
109 |
120 |
121 |
122 |
134 |
146 |
157 |
158 |
Password Requirements:
159 |
160 | - 8-128 characters with a mix of letters and numbers
161 | - Must include both upper and lower case letters
162 |
163 |
164 |
165 |
166 |
167 |
168 |
179 |
180 |
181 | );
182 | }
183 |
184 | BasicInformation.propTypes = {
185 | formData: PropTypes.shape({
186 | companyName: PropTypes.string.isRequired,
187 | businessType: PropTypes.string.isRequired,
188 | numberOfEmployees: PropTypes.string.isRequired,
189 | yearOfEstablishment: PropTypes.string.isRequired,
190 | country: PropTypes.string.isRequired,
191 | city: PropTypes.string.isRequired,
192 | postCode: PropTypes.string,
193 | industry: PropTypes.string.isRequired,
194 | accountPassword: PropTypes.string.isRequired,
195 | confirmPassword: PropTypes.string.isRequired,
196 | companyAddress: PropTypes.string.isRequired,
197 | }).isRequired,
198 | errors: PropTypes.object.isRequired,
199 | isTouched: PropTypes.object.isRequired,
200 | handleChange: PropTypes.func.isRequired,
201 | };
202 |
203 | export default BasicInformation;
204 |
--------------------------------------------------------------------------------
/src/pages/Home/SectionThree/SectionThree.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/no-noninteractive-tabindex */
2 | import React, { useState } from 'react';
3 | import { motion, AnimatePresence } from 'framer-motion';
4 | import useImgMapArea from 'react-img-map-area';
5 | import FadeTransition from '../../../components/FadeTransition/FadeTransition';
6 | import CardTransition from '../../../components/CardTransition/CardTransition';
7 | import CountryDescription from '../../../components/CountryDescription/CountryDescription';
8 | import countries from '../../../data/countries';
9 | import { content, morocco, nigeria, kenya, namibia } from '../../../data/countryContent';
10 | import styles from './SectionThree.module.css';
11 | import map from '../../../assets/images/Home/SectionThree/map.webp';
12 |
13 | function SectionThree() {
14 | const [activeCountry, setActiveCountry] = useState(content);
15 | const [countryName, setCountryName] = useState('Map of Africa');
16 | useImgMapArea();
17 |
18 | const titleVariants = {
19 | initial: {
20 | opacity: 0,
21 | translateY: 10,
22 | },
23 | enter: {
24 | opacity: 1,
25 | translateY: 0,
26 | transition: { duration: 0.25, delay: 0.25 },
27 | },
28 | exit: {
29 | opacity: 0,
30 | translateY: -10,
31 | transition: { duration: 0.25 },
32 | },
33 | };
34 |
35 | return (
36 |
37 |
44 | Choose a Country
45 |
46 |
47 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
69 | {countryName}
70 |
71 |
72 |
73 |
74 |
75 | {activeCountry.map(country => (
76 |
82 | ))}
83 |
84 |
85 |

86 |
87 |
189 |
190 |
191 |
192 |
193 | );
194 | }
195 |
196 | export default SectionThree;
197 |
--------------------------------------------------------------------------------
/src/pages/Register/Register.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Link, useNavigate } from 'react-router-dom';
3 | import { Tooltip } from 'react-tooltip';
4 | import { validateUserData } from '../../utils/validateUser';
5 | import styles from './Register.module.css';
6 | import Button from '../../components/Button/Button';
7 | import Alert from '../../components/Alert/Alert';
8 | import InputBox from '../../components/InputBox/InputBox';
9 | import FadeTransition from '../../components/FadeTransition/FadeTransition';
10 |
11 | function Register() {
12 | const REGISTRATION_URL = `${import.meta.env.VITE_APP_BACKEND_URL}/userregister`;
13 |
14 | const navigate = useNavigate();
15 | const [buttonText, setButtonText] = useState('Register');
16 |
17 | const [formData, setFormData] = useState({
18 | name: '',
19 | email: '',
20 | accountPassword: '',
21 | confirmPassword: '',
22 | });
23 | const [errors, setErrors] = useState({});
24 | const [confirmPasswordTouched, setConfirmPasswordTouched] = useState(false);
25 |
26 | const alertDuration = 5000;
27 | const [alert, setAlert] = useState({ message: '', state: '', active: false });
28 |
29 | const toggleAlert = () => {
30 | setAlert(prevAlert => ({
31 | ...prevAlert,
32 | active: !prevAlert.active,
33 | }));
34 | };
35 |
36 | useEffect(() => {
37 | const validationErrors = validateUserData(formData);
38 | setErrors(validationErrors || {});
39 | }, [formData]);
40 |
41 | const handleChange = event => {
42 | const { name, value } = event.target;
43 | setFormData(prevFormData => ({
44 | ...prevFormData,
45 | [name]: value,
46 | }));
47 |
48 | // *Prevent password must match error until interacted
49 | if (name === 'confirmPassword') {
50 | setConfirmPasswordTouched(true);
51 | }
52 | };
53 |
54 | const handleSubmit = async event => {
55 | event.preventDefault();
56 | const data = {
57 | name: formData.name,
58 | email: formData.email,
59 | accountPassword: formData.accountPassword,
60 | confirmPassword: formData.confirmPassword,
61 | };
62 | setButtonText('...');
63 |
64 | try {
65 | const response = await fetch(REGISTRATION_URL, {
66 | method: 'POST',
67 | headers: {
68 | 'Content-Type': 'application/json',
69 | },
70 | body: JSON.stringify(data),
71 | });
72 |
73 | let alertMessage = '';
74 | let alertState = '';
75 |
76 | if (response.ok) {
77 | alertMessage = 'Signup successful. Redirecting...';
78 | alertState = 'success';
79 | setTimeout(() => {
80 | navigate('/');
81 | }, alertDuration + 1000);
82 | setButtonText('Register');
83 | } else {
84 | alertMessage = 'Signup failed, please retry.';
85 | alertState = 'error';
86 | setButtonText('Register');
87 | }
88 |
89 | // !Start Alert with set alert-data.
90 | setAlert(prevAlert => ({
91 | ...prevAlert,
92 | message: alertMessage,
93 | state: alertState,
94 | }));
95 | toggleAlert();
96 |
97 | const alertTimeout = setTimeout(() => {
98 | toggleAlert();
99 | }, alertDuration);
100 | return () => clearTimeout(alertTimeout);
101 | } catch (error) {
102 | setAlert(prevAlert => ({
103 | ...prevAlert,
104 | message: error.toString(),
105 | state: 'error',
106 | }));
107 | toggleAlert();
108 | setButtonText('Register');
109 |
110 | const alertTimeout = setTimeout(() => {
111 | toggleAlert();
112 | }, alertDuration);
113 | return () => clearTimeout(alertTimeout);
114 | }
115 | };
116 |
117 | return (
118 |
119 |
120 |
121 |
122 |
199 |
200 | © {new Date().getFullYear()} - Invest Africa :: Powered by Adam-i Japan
201 |
202 |
203 |
204 |
205 |
206 |
207 | );
208 | }
209 |
210 | export default Register;
211 |
--------------------------------------------------------------------------------
/src/pages/CompanyRegistration/CompanyRegistration.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Link, useNavigate } from 'react-router-dom';
3 | import BasicInformation from './BasicInformation';
4 | import ContactInformation from './ContactInformation';
5 | import { validateCompanyData } from '../../utils/validateCompany';
6 | import styles from './CompanyRegistration.module.css';
7 | import Alert from '../../components/Alert/Alert';
8 | import Button from '../../components/Button/Button';
9 | import FadeTransition from '../../components/FadeTransition/FadeTransition';
10 |
11 | function CompanyRegistration() {
12 | const REGISTRATION_URL = `${import.meta.env.VITE_APP_BACKEND_URL}/companyregister`;
13 |
14 | const navigate = useNavigate();
15 | const [buttonText, setButtonText] = useState('Register');
16 | const [step, setStep] = useState(1);
17 | const [formData, setFormData] = useState({
18 | companyName: '',
19 | businessType: '',
20 | numberOfEmployees: '',
21 | yearOfEstablishment: '',
22 | country: '',
23 | city: '',
24 | postCode: '',
25 | industry: '',
26 | accountPassword: '',
27 | confirmPassword: '',
28 | companyAddress: '',
29 | personOfContact: '',
30 | position: '',
31 | phoneNumber: '',
32 | email: '',
33 | companyWebsite: '',
34 | });
35 |
36 | const [errors, setErrors] = useState({});
37 | const [isTouched, setIsTouched] = useState({
38 | confirmPassword: false,
39 | numberOfEmployees: false,
40 | yearOfEstablishment: false,
41 | });
42 |
43 | const alertDuration = 5000;
44 | const [alert, setAlert] = useState({ message: '', state: '', active: false });
45 |
46 | const toggleAlert = () => {
47 | setAlert(prevAlert => ({
48 | ...prevAlert,
49 | active: !prevAlert.active,
50 | }));
51 | };
52 |
53 | useEffect(() => {
54 | const validationErrors = validateCompanyData({
55 | ...formData,
56 | numberOfEmployees: Number(formData.numberOfEmployees),
57 | yearOfEstablishment: Number(formData.yearOfEstablishment),
58 | });
59 | setErrors(validationErrors || {});
60 | }, [formData]);
61 |
62 | const handleChange = event => {
63 | const { name, value } = event.target;
64 | setFormData(prevFormData => ({
65 | ...prevFormData,
66 | [name]: value,
67 | }));
68 |
69 | // *Prevent password, n.Employees or year.Establishment errors until interacted
70 | if (name === 'confirmPassword') {
71 | setIsTouched({ confirmPassword: true });
72 | }
73 | if (name === 'numberOfEmployees') {
74 | setIsTouched({ numberOfEmployees: true });
75 | }
76 | if (name === 'yearOfEstablishment') {
77 | setIsTouched({ yearOfEstablishment: true });
78 | }
79 | };
80 |
81 | const handleSubmit = async event => {
82 | event.preventDefault();
83 | const data = {
84 | companyName: formData.companyName,
85 | businessType: formData.businessType,
86 | numberOfEmployees: Number(formData.numberOfEmployees),
87 | yearOfEstablishment: Number(formData.yearOfEstablishment),
88 | country: formData.country,
89 | city: formData.city,
90 | postCode: formData.postCode,
91 | industry: formData.industry,
92 | accountPassword: formData.accountPassword,
93 | confirmPassword: formData.confirmPassword,
94 | companyAddress: formData.companyAddress,
95 | personOfContact: formData.personOfContact,
96 | position: formData.position,
97 | phoneNumber: formData.phoneNumber,
98 | email: formData.email,
99 | companyWebsite: formData.companyWebsite,
100 | };
101 | setButtonText('...');
102 |
103 | try {
104 | const response = await fetch(REGISTRATION_URL, {
105 | method: 'POST',
106 | headers: {
107 | 'Content-Type': 'application/json',
108 | },
109 | body: JSON.stringify(data),
110 | });
111 |
112 | let alertMessage = '';
113 | let alertState = '';
114 |
115 | if (response.ok) {
116 | alertMessage = 'Signup successful. Redirecting...';
117 | alertState = 'success';
118 | setTimeout(() => {
119 | navigate('/');
120 | }, alertDuration + 1000);
121 | setButtonText('Register');
122 | } else {
123 | alertMessage = 'Signup failed, please retry.';
124 | alertState = 'error';
125 | setButtonText('Register');
126 | }
127 |
128 | // !Start Alert with set alert-data.
129 | setAlert(prevAlert => ({
130 | ...prevAlert,
131 | message: alertMessage,
132 | state: alertState,
133 | }));
134 | toggleAlert();
135 |
136 | const alertTimeout = setTimeout(() => {
137 | toggleAlert();
138 | }, alertDuration);
139 |
140 | return () => clearTimeout(alertTimeout);
141 | // *catch when unknown error happens
142 | } catch (error) {
143 | setAlert(prevAlert => ({
144 | ...prevAlert,
145 | message: error.toString(),
146 | state: 'error',
147 | }));
148 | toggleAlert();
149 |
150 | const alertTimeout = setTimeout(() => {
151 | toggleAlert();
152 | }, alertDuration);
153 | return () => clearTimeout(alertTimeout);
154 | }
155 | };
156 |
157 | const goToNextStep = () => {
158 | setStep(2);
159 | window.scroll(0, 0);
160 | };
161 |
162 | const goToPreviousStep = () => {
163 | setStep(1);
164 | window.scroll(0, 0);
165 | };
166 |
167 | const loginLink = (
168 |
169 | Have an account?{' '}
170 |
171 | Login
172 |
173 |
174 | );
175 |
176 | return (
177 |
218 | );
219 | }
220 |
221 | export default CompanyRegistration;
222 |
--------------------------------------------------------------------------------
/src/reset.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
2 | /*! Includes Modifications by GetPsyched6 | https://github.com/GetPsyched6 */
3 |
4 | /* Document
5 | ========================================================================== */
6 |
7 | /**
8 | * 1. Correct the line height in all browsers.
9 | * 2. Prevent adjustments of font size after orientation changes in iOS.
10 | */
11 |
12 | html {
13 | line-height: 1.15; /* 1 */
14 | -webkit-text-size-adjust: 100%; /* 2 */
15 | text-size-adjust: 100%; /* 3 */
16 | }
17 |
18 | /* Sections
19 | ========================================================================== */
20 |
21 | /**
22 | * Remove the margin in all browsers.
23 | */
24 |
25 | body {
26 | margin: 0;
27 | padding: 0;
28 | }
29 |
30 | /**
31 | * Render the `main` element consistently in IE.
32 | */
33 |
34 | main {
35 | display: block;
36 | }
37 |
38 | /**
39 | * Correct the font size and margin on `h1` elements within `section` and
40 | * `article` contexts in Chrome, Firefox, and Safari.
41 | */
42 |
43 | h1 {
44 | font-size: 2em;
45 | margin: 0.67em 0;
46 | font-weight: normal;
47 | }
48 |
49 | /* Grouping content
50 | ========================================================================== */
51 |
52 | /**
53 | * 1. Add the correct box sizing in Firefox.
54 | * 2. Show the overflow in Edge and IE.
55 | */
56 |
57 | hr {
58 | box-sizing: content-box; /* 1 */
59 | height: 0; /* 1 */
60 | overflow: visible; /* 2 */
61 | }
62 |
63 | /**
64 | * 1. Correct the inheritance and scaling of font size in all browsers.
65 | * 2. Correct the odd `em` font sizing in all browsers.
66 | */
67 |
68 | pre {
69 | font-family: monospace, monospace; /* 1 */
70 | font-size: 1em; /* 2 */
71 | }
72 |
73 | /* Text-level semantics
74 | ========================================================================== */
75 |
76 | /**
77 | * Remove the gray background on active links in IE 10.
78 | */
79 |
80 | a {
81 | background-color: transparent;
82 | }
83 |
84 | /**
85 | * Default class to remove anchor styling.
86 | */
87 |
88 | a,
89 | a:hover,
90 | a:visited,
91 | a:focus,
92 | a:active {
93 | text-decoration: none;
94 | color: inherit;
95 | outline: 0;
96 | }
97 |
98 | /**
99 | * 1. Remove the bottom border in Chrome 57-
100 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
101 | */
102 |
103 | abbr[title] {
104 | border-bottom: none; /* 1 */
105 | text-decoration: underline; /* 2 */
106 | text-decoration: underline dotted; /* 2 */
107 | }
108 |
109 | /**
110 | * Add the correct font weight in Chrome, Edge, and Safari.
111 | */
112 |
113 | b,
114 | strong {
115 | font-weight: bolder;
116 | }
117 |
118 | /**
119 | * 1. Correct the inheritance and scaling of font size in all browsers.
120 | * 2. Correct the odd `em` font sizing in all browsers.
121 | */
122 |
123 | code,
124 | kbd,
125 | samp {
126 | font-family: monospace, monospace; /* 1 */
127 | font-size: 1em; /* 2 */
128 | }
129 |
130 | /**
131 | * Add the correct font size in all browsers.
132 | */
133 |
134 | small {
135 | font-size: 80%;
136 | }
137 |
138 | /**
139 | * Prevent `sub` and `sup` elements from affecting the line height in
140 | * all browsers.
141 | */
142 |
143 | sub,
144 | sup {
145 | font-size: 75%;
146 | line-height: 0;
147 | position: relative;
148 | vertical-align: baseline;
149 | }
150 |
151 | sub {
152 | bottom: -0.25em;
153 | }
154 |
155 | sup {
156 | top: -0.5em;
157 | }
158 |
159 | /* Embedded content
160 | ========================================================================== */
161 |
162 | /**
163 | * Remove the border on images inside links in IE 10.
164 | */
165 |
166 | img {
167 | /*PLease check this youtube video for understanding why these properties were used
168 | -> https://www.youtube.com/watch?v=345V2MU3E_w
169 | */
170 | border:0;
171 | font-style:italic;
172 | vertical-align:middle;
173 | height:auto;
174 | border-style: none;
175 | max-width: 100%;
176 | shape-margin:1rem;
177 | }
178 |
179 | /* Forms
180 | ========================================================================== */
181 |
182 | /**
183 | * 1. Change the font styles in all browsers.
184 | * 2. Remove the margin in Firefox and Safari.
185 | */
186 |
187 | button,
188 | input,
189 | optgroup,
190 | select,
191 | textarea {
192 | font: inherit; /* 1 */
193 | font-size: 100%; /* 1 */
194 | line-height: 1.15; /* 1 */
195 | margin: 0; /* 2 */
196 | padding: 0; /* 2 */
197 | background: none;
198 | color: inherit;
199 | border: none;
200 | cursor: pointer;
201 | outline: inherit;
202 | }
203 |
204 | ul,
205 | ol,
206 | li {
207 | font: inherit; /* 1 */
208 | font-size: 100%; /* 1 */
209 | padding: 0;
210 | margin: 0;
211 | list-style: none;
212 |
213 | }
214 |
215 | select {
216 | -webkit-appearance: none;
217 | appearance: none;
218 | }
219 |
220 | /**
221 | * Show the overflow in IE.
222 | * 1. Show the overflow in Edge.
223 | */
224 |
225 | button,
226 | input {
227 | /* 1 */
228 | overflow: visible;
229 | }
230 |
231 | /**
232 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
233 | * 1. Remove the inheritance of text transform in Firefox.
234 | */
235 |
236 | button,
237 | select {
238 | /* 1 */
239 | text-transform: none;
240 | }
241 |
242 | /**
243 | * Correct the inability to style clickable types in iOS and Safari.
244 | */
245 |
246 | button,
247 | [type='button'],
248 | [type='reset'],
249 | [type='submit'] {
250 | -webkit-appearance: button;
251 | appearance: button;
252 | }
253 |
254 | /**
255 | * Remove the inner border and padding in Firefox.
256 | */
257 |
258 | button::-moz-focus-inner,
259 | [type='button']::-moz-focus-inner,
260 | [type='reset']::-moz-focus-inner,
261 | [type='submit']::-moz-focus-inner {
262 | border-style: none;
263 | padding: 0;
264 | }
265 |
266 | /**
267 | * Restore the focus styles unset by the previous rule.
268 | */
269 |
270 | button:-moz-focusring,
271 | [type='button']:-moz-focusring,
272 | [type='reset']:-moz-focusring,
273 | [type='submit']:-moz-focusring {
274 | outline: 1px dotted ButtonText;
275 | }
276 |
277 | /**
278 | * Correct the padding in Firefox.
279 | */
280 |
281 | fieldset {
282 | padding: 0.35em 0.75em 0.625em;
283 | }
284 |
285 | /**
286 | * 1. Correct the text wrapping in Edge and IE.
287 | * 2. Correct the color inheritance from `fieldset` elements in IE.
288 | * 3. Remove the padding so developers are not caught out when they zero out
289 | * `fieldset` elements in all browsers.
290 | */
291 |
292 | legend {
293 | box-sizing: border-box; /* 1 */
294 | color: inherit; /* 2 */
295 | display: table; /* 1 */
296 | max-width: 100%; /* 1 */
297 | padding: 0; /* 3 */
298 | white-space: normal; /* 1 */
299 | }
300 |
301 | /**
302 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
303 | */
304 |
305 | progress {
306 | vertical-align: baseline;
307 | }
308 |
309 | /**
310 | * Remove the default vertical scrollbar in IE 10+.
311 | */
312 |
313 | textarea {
314 | overflow: auto;
315 | }
316 |
317 | /**
318 | * 1. Add the correct box sizing in IE 10.
319 | * 2. Remove the padding in IE 10.
320 | */
321 |
322 | [type='checkbox'],
323 | [type='radio'] {
324 | box-sizing: border-box; /* 1 */
325 | padding: 0; /* 2 */
326 | }
327 |
328 | /**
329 | * Correct the cursor style of increment and decrement buttons in Chrome.
330 | */
331 |
332 | [type='number']::-webkit-inner-spin-button,
333 | [type='number']::-webkit-outer-spin-button {
334 | height: auto;
335 | }
336 |
337 | /**
338 | * 1. Correct the odd appearance in Chrome and Safari.
339 | * 2. Correct the outline style in Safari.
340 | */
341 |
342 | [type='search'] {
343 | -webkit-appearance: textfield; /* 1 */
344 | appearance: textfield; /* 2 */
345 | outline-offset: -2px; /* 3 */
346 | }
347 |
348 | /**
349 | * Remove the inner padding in Chrome and Safari on macOS.
350 | */
351 |
352 | [type='search']::-webkit-search-decoration {
353 | -webkit-appearance: none;
354 | }
355 |
356 | /**
357 | * 1. Correct the inability to style clickable types in iOS and Safari.
358 | * 2. Change font properties to `inherit` in Safari.
359 | */
360 |
361 | ::-webkit-file-upload-button {
362 | -webkit-appearance: button; /* 1 */
363 | font: inherit; /* 2 */
364 | }
365 |
366 | /* Interactive
367 | ========================================================================== */
368 |
369 | /*
370 | * Add the correct display in Edge, IE 10+, and Firefox.
371 | */
372 |
373 | details {
374 | display: block;
375 | }
376 |
377 | /*
378 | * Add the correct display in all browsers.
379 | */
380 |
381 | summary {
382 | display: list-item;
383 | }
384 |
385 | /* Misc
386 | ========================================================================== */
387 |
388 | /**
389 | * Add the correct display in IE 10+.
390 | */
391 |
392 | template {
393 | display: none;
394 | }
395 |
396 | /**
397 | * Add the correct display in IE 10.
398 | */
399 |
400 | [hidden] {
401 | display: none;
402 | }
403 |
--------------------------------------------------------------------------------
/src/components/Navbar/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useMediaQuery } from '@react-hook/media-query';
3 | import { FaChevronDown, FaBars } from 'react-icons/fa6';
4 | import { Link, useLocation } from 'react-router-dom';
5 | import { motion } from 'framer-motion';
6 | import japanFlag from '../../assets/flag-japan.png';
7 | import logo from '../../assets/invest_africa.webp';
8 | import styles from './Navbar.module.css';
9 |
10 | function NavBar() {
11 | const [isOpen, setIsOpen] = useState(false);
12 | const [isDropdownOpen, setIsDropdownOpen] = useState(false);
13 | const [isScrolled, setIsScrolled] = useState(false);
14 |
15 | const toggleNav = () => {
16 | setIsOpen(!isOpen);
17 | };
18 |
19 | const toggleDropdown = () => {
20 | setIsDropdownOpen(!isDropdownOpen);
21 | };
22 |
23 | const handleDropdownLinkClick = () => {
24 | const dropdownContent = document.querySelector(`.${styles.dropdown_content}`);
25 | dropdownContent.classList.toggle(styles.hide);
26 | };
27 |
28 | let mainContent = '#main-content';
29 |
30 | const location = useLocation();
31 |
32 | switch (location.pathname) {
33 | case '/login':
34 | mainContent = '#email';
35 | break;
36 | case '/register':
37 | mainContent = '#name';
38 | break;
39 | case '/companyregister':
40 | mainContent = '#companyName';
41 | break;
42 | default:
43 | mainContent = '#main-content';
44 | break;
45 | }
46 |
47 | const isNotDesktop = useMediaQuery('(max-width: 992px)');
48 |
49 | const getMainNavClasses = () => {
50 | let classes = `${styles.nav_links} ${styles.extra_nav_styling}`;
51 | if (isNotDesktop) {
52 | classes += ` ${styles.mobile_nav}`;
53 | }
54 | if (isNotDesktop && isOpen) {
55 | classes += ` ${styles.mobile_nav_open}`;
56 | }
57 | return classes;
58 | };
59 |
60 | useEffect(() => {
61 | // ? Function to handle the closing of the nav when clicking outside
62 | const handleClickOutside = event => {
63 | if (!event.target.closest(`.${styles.nav}`) && isOpen) {
64 | setIsOpen(false);
65 | }
66 | };
67 |
68 | // ? Function to handle the change in the navbar style on scroll
69 | const handleScroll = () => {
70 | const offset = window.scrollY;
71 | const threshold = 72;
72 | setIsScrolled(offset > threshold);
73 | };
74 |
75 | document.addEventListener('click', handleClickOutside);
76 | window.addEventListener('scroll', handleScroll);
77 |
78 | // ? Managing the overflow style on the body based on the nav state
79 | document.body.style.overflow = isOpen ? 'hidden' : 'visible';
80 |
81 | // ? Setting the initial state based on the initial scroll position
82 | handleScroll();
83 |
84 | return () => {
85 | document.removeEventListener('click', handleClickOutside);
86 | window.removeEventListener('scroll', handleScroll);
87 | document.body.style.overflow = 'visible';
88 | };
89 | }, [isOpen]);
90 |
91 | // * start - Variants to animate dropdowns
92 | const dropdownVariants = {
93 | initial: {
94 | opacity: 0,
95 | },
96 | enter: {
97 | opacity: 1,
98 | },
99 | };
100 | const responsiveVariants = isNotDesktop ? {} : dropdownVariants;
101 | // * end - Variants to animate dropdowns
102 |
103 | return (
104 |
275 | );
276 | }
277 |
278 | export default NavBar;
279 |
--------------------------------------------------------------------------------