feat: containerize
This commit is contained in:
parent
98651de3fa
commit
7e0d4e5f3b
14 changed files with 956 additions and 127 deletions
31
.gitea/workflow/pipeline.yml
Normal file
31
.gitea/workflow/pipeline.yml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
version: "1"
|
||||||
|
name: Build and Deploy Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
|
- name: Log in to Gitea Container Registry
|
||||||
|
run: echo "${{ secrets.GITEA_PASSWORD }}" | docker login ${{ secrets.GITEA_REGISTRY }} -u ${{ secrets.GITEA_USERNAME }} --password-stdin
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
tags: ${{ secrets.GITEA_REGISTRY }}/dotechbro-website:latest
|
||||||
|
|
||||||
|
- name: Log out from Gitea Container Registry
|
||||||
|
run: docker logout ${{ secrets.GITEA_REGISTRY }}
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,5 @@
|
||||||
|
.env
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
|
|
29
Dockerfile
Normal file
29
Dockerfile
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# Use an official Node.js runtime as a parent image
|
||||||
|
FROM node:18 AS build
|
||||||
|
|
||||||
|
# Set the working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package.json and package-lock.json
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Copy the rest of the application code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Use an official Nginx image to serve the built application
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# Copy the built application from the previous stage
|
||||||
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Expose port 80
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# Start Nginx
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
|
@ -1,13 +1,17 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React</title>
|
<title>Vite + React</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.jsx"></script>
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
699
package-lock.json
generated
699
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -10,6 +10,13 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.13.3",
|
||||||
|
"@emotion/styled": "^11.13.0",
|
||||||
|
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^6.6.0",
|
||||||
|
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||||
|
"@mui/material": "^6.1.4",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-router": "^6.27.0",
|
"react-router": "^6.27.0",
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
// src/components/About.jsx
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
const About = () => (
|
const About = () => (
|
||||||
<div className="p-4">
|
<div className="p-8 flex flex-col items-center justify-center min-h-screen bg-gray-100">
|
||||||
<h2 className="text-xl font-bold">About Us</h2>
|
<h2 className="text-4xl font-bold text-center leading-tight">
|
||||||
<p className="mt-2">Do Tech Bro is dedicated to helping minorities, mainly African Americans, pivot into the tech field.</p>
|
About Us
|
||||||
|
</h2>
|
||||||
|
<p className="mt-2 text-xs text-center text-gray-600">
|
||||||
|
Do Tech Bro is dedicated to helping minorities, mainly African Americans, pivot into the tech field.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default About;// src/components/About.jsx
|
export default About;
|
|
@ -1,21 +1,24 @@
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { faServer, faProjectDiagram, faCode } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
const FeaturedRoadmaps = () => (
|
const icons = {
|
||||||
|
"devops-sre": faServer,
|
||||||
|
"non-technical-tech": faProjectDiagram,
|
||||||
|
"development": faCode
|
||||||
|
};
|
||||||
|
|
||||||
|
const FeaturedRoadmaps = ({ roadmaps }) => (
|
||||||
<div className="p-8">
|
<div className="p-8">
|
||||||
<h2 className="text-4xl font-bold text-center mb-8">Featured Roadmaps</h2>
|
<h2 className="text-4xl font-bold text-center mb-8">Featured Roadmaps</h2>
|
||||||
<div className="flex flex-col md:flex-row justify-center items-center space-y-4 md:space-y-0 md:space-x-4">
|
<div className="flex flex-col md:flex-row justify-center items-center space-y-8 md:space-y-0 md:space-x-8">
|
||||||
<Link to="/roadmap" className="bg-white text-gray-900 p-4 rounded-lg shadow-lg w-full md:w-1/3 text-center">
|
{roadmaps.map((roadmap, index) => (
|
||||||
<h3 className="text-2xl font-semibold">Roadmap 1</h3>
|
<Link key={index} to={`/roadmap/${roadmap.id}`} className="bg-white text-gray-900 p-8 rounded-lg shadow-lg w-full md:w-1/3 text-center transform transition-transform hover:scale-105">
|
||||||
<p className="mt-2">Description of Roadmap 1</p>
|
<FontAwesomeIcon icon={icons[roadmap.id]} className="text-green-600 text-4xl mb-4" />
|
||||||
</Link>
|
<h3 className="text-2xl font-semibold">{roadmap.title}</h3>
|
||||||
<Link to="/roadmap" className="bg-white text-gray-900 p-4 rounded-lg shadow-lg w-full md:w-1/3 text-center">
|
<p className="mt-4">{roadmap.description}</p>
|
||||||
<h3 className="text-2xl font-semibold">Roadmap 2</h3>
|
|
||||||
<p className="mt-2">Description of Roadmap 2</p>
|
|
||||||
</Link>
|
|
||||||
<Link to="/roadmap" className="bg-white text-gray-900 p-4 rounded-lg shadow-lg w-full md:w-1/3 text-center">
|
|
||||||
<h3 className="text-2xl font-semibold">Roadmap 3</h3>
|
|
||||||
<p className="mt-2">Description of Roadmap 3</p>
|
|
||||||
</Link>
|
</Link>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,15 +1,24 @@
|
||||||
/* src/components/Header.css */
|
/* src/components/Header.css */
|
||||||
|
.link-underline {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
.link-underline::after {
|
.link-underline::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 0;
|
width: 100%;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
bottom: -2px;
|
bottom: -2px;
|
||||||
left: 0;
|
left: 0;
|
||||||
background-color: #38a169; /* Darker green color */
|
background-color: #38a169;
|
||||||
transition: width 0.3s ease-in-out;
|
/* Darker green color */
|
||||||
|
transform: scaleX(0);
|
||||||
|
transform-origin: bottom right;
|
||||||
|
transition: transform 0.3s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-underline:hover::after {
|
.link-underline:hover::after {
|
||||||
width: 100%;
|
transform: scaleX(1);
|
||||||
|
transform-origin: bottom left;
|
||||||
}
|
}
|
|
@ -4,12 +4,12 @@ import './Header.css'; // Import the custom CSS file
|
||||||
const Header = () => (
|
const Header = () => (
|
||||||
<header className="bg-gray-900 text-white p-4 flex justify-between items-center">
|
<header className="bg-gray-900 text-white p-4 flex justify-between items-center">
|
||||||
<div className="text-2xl font-bold">
|
<div className="text-2xl font-bold">
|
||||||
<Link to="/" className="relative link-underline">DoTechBro.org</Link>
|
<Link to="/" className="relative ">DoTechBro.org</Link>
|
||||||
</div>
|
</div>
|
||||||
<nav className="space-x-4">
|
<nav className="space-x-4">
|
||||||
<Link to="/" className="relative link-underline">Home</Link>
|
<Link to="/" className="relative link-underline">Home</Link>
|
||||||
<Link to="/about" className="relative link-underline">About</Link>
|
<Link to="/about" className="relative link-underline">About</Link>
|
||||||
<Link to="/roadmap" className="hover:underline hover:decoration-green-600">Roadmaps</Link>
|
<Link to="/roadmap" className="relative link-underline">Roadmaps</Link>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,22 @@
|
||||||
|
import { useState } from 'react';
|
||||||
import FeaturedRoadmaps from './FeaturedRoadmaps';
|
import FeaturedRoadmaps from './FeaturedRoadmaps';
|
||||||
|
import MailingListDialog from './MailingListDialog';
|
||||||
|
import roadmaps from '../roadmaps.json';
|
||||||
|
|
||||||
const Home = () => (
|
const Home = () => {
|
||||||
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleDialogOpen = () => {
|
||||||
|
setDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDialogClose = () => {
|
||||||
|
setDialogOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
<div className="flex flex-col items-center justify-center w-full">
|
<div className="flex flex-col items-center justify-center w-full">
|
||||||
{/* Hero Section */}
|
|
||||||
<div className="w-full p-8 flex flex-col md:flex-row items-center justify-center min-h-screen bg-gray-100">
|
<div className="w-full p-8 flex flex-col md:flex-row items-center justify-center min-h-screen bg-gray-100">
|
||||||
<div className="md:w-1/2 flex flex-col items-center text-center">
|
<div className="md:w-1/2 flex flex-col items-center text-center">
|
||||||
<h2 className="text-6xl font-bold leading-tight">
|
<h2 className="text-6xl font-bold leading-tight">
|
||||||
|
@ -13,7 +27,7 @@ const Home = () => (
|
||||||
<p className="mt-4 text-lg max-w-2xl">
|
<p className="mt-4 text-lg max-w-2xl">
|
||||||
Empowering minorities to overcome barriers and succeed in the tech industry with confidence.
|
Empowering minorities to overcome barriers and succeed in the tech industry with confidence.
|
||||||
</p>
|
</p>
|
||||||
<button className="mt-8 px-6 py-3 bg-green-600 text-white text-lg font-semibold rounded hover:bg-green-700 transition duration-300">
|
<button onClick={handleDialogOpen} className="mt-8 px-6 py-3 bg-green-600 text-white text-lg font-semibold rounded hover:bg-green-700 transition duration-300">
|
||||||
Start Today
|
Start Today
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,28 +36,29 @@ const Home = () => (
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Featured Roadmaps Section */}
|
|
||||||
<div className="w-full bg-gray-900 text-white py-16">
|
<div className="w-full bg-gray-900 text-white py-16">
|
||||||
<FeaturedRoadmaps />
|
<FeaturedRoadmaps roadmaps={roadmaps} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Testimonials Section */}
|
<div className="w-full p-8 bg-white text-center my-16">
|
||||||
<div className="w-full p-8 bg-white text-center">
|
|
||||||
<h2 className="text-4xl font-bold mb-8">Testimonials</h2>
|
<h2 className="text-4xl font-bold mb-8">Testimonials</h2>
|
||||||
<p className="text-lg">Coming soon...</p>
|
<p className="text-lg">Coming soon...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Community Section */}
|
|
||||||
<div className="w-full p-8 bg-gray-900 text-white text-center">
|
<div className="w-full p-8 bg-gray-900 text-white text-center">
|
||||||
<h2 className="text-4xl font-bold mb-8">Join Our Community</h2>
|
<h2 className="text-4xl font-bold mb-8">Join Our Community</h2>
|
||||||
<p className="text-lg max-w-2xl mx-auto">
|
<p className="text-lg max-w-2xl mx-auto">
|
||||||
Connect with like-minded individuals, share your journey, and get support from our community.
|
Connect with like-minded individuals, share your journey, and get support from our community.
|
||||||
</p>
|
</p>
|
||||||
<button className="mt-8 px-6 py-3 bg-green-600 text-white text-lg font-semibold rounded hover:bg-green-700 transition duration-300">
|
<button
|
||||||
|
onClick={handleDialogOpen}
|
||||||
|
className="mt-8 px-6 py-3 bg-green-600 text-white text-lg font-semibold rounded hover:bg-green-700 transition duration-300">
|
||||||
Join Now
|
Join Now
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<MailingListDialog open={dialogOpen} onClose={handleDialogClose} />
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
|
};
|
||||||
|
|
||||||
export default Home;
|
export default Home;
|
96
src/components/MailingListDialog.jsx
Normal file
96
src/components/MailingListDialog.jsx
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import TextField from '@mui/material/TextField';
|
||||||
|
|
||||||
|
const MailingListDialog = ({ open, onClose }) => {
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
const [success, setSuccess] = useState('');
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setError('');
|
||||||
|
setSuccess('');
|
||||||
|
|
||||||
|
const form = document.createElement('form');
|
||||||
|
form.action = 'https://tech.us9.list-manage.com/subscribe/post';
|
||||||
|
form.method = 'POST';
|
||||||
|
form.target = '_blank';
|
||||||
|
|
||||||
|
const hiddenFields = {
|
||||||
|
u: 'aaf43cf1740b320b738dade27',
|
||||||
|
id: '344e2f39e6',
|
||||||
|
MERGE1: name,
|
||||||
|
MERGE0: email,
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys(hiddenFields).forEach((key) => {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'hidden';
|
||||||
|
input.name = key;
|
||||||
|
input.value = hiddenFields[key];
|
||||||
|
form.appendChild(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.appendChild(form);
|
||||||
|
form.submit();
|
||||||
|
document.body.removeChild(form);
|
||||||
|
|
||||||
|
setSuccess('Thank you for subscribing!');
|
||||||
|
setName('');
|
||||||
|
setEmail('');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={onClose}>
|
||||||
|
<DialogTitle>Join Our Mailing List</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
Stay updated with the latest news and updates from Do Tech Bro.
|
||||||
|
</DialogContentText>
|
||||||
|
<TextField
|
||||||
|
autoFocus
|
||||||
|
margin="dense"
|
||||||
|
id="name"
|
||||||
|
label="Name"
|
||||||
|
type="text"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
margin="dense"
|
||||||
|
id="email"
|
||||||
|
label="Email Address"
|
||||||
|
type="email"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
/>
|
||||||
|
<DialogContentText className="mt-2 text-xs text-gray-600">
|
||||||
|
We promise not to sell your information or email you more than once or twice a month.
|
||||||
|
</DialogContentText>
|
||||||
|
{error && <DialogContentText className="mt-2 text-xs text-red-600">{error}</DialogContentText>}
|
||||||
|
{success && <DialogContentText className="mt-2 text-xs text-green-600">{success}</DialogContentText>}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={onClose} color="primary">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleSubmit} color="primary">
|
||||||
|
Subscribe
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MailingListDialog;
|
17
src/roadmaps.json
Normal file
17
src/roadmaps.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "devops-sre",
|
||||||
|
"title": "DevOps/SRE",
|
||||||
|
"description": "Become a DevOps or SRE professional, specializing in automating infrastructure, deploying applications, and ensuring system reliability."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "non-technical-tech",
|
||||||
|
"title": "Non-Technical Tech Career",
|
||||||
|
"description": "Explore non-technical roles within the tech industry, such as product management, UX/UI design, or project management."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "development",
|
||||||
|
"title": "Development",
|
||||||
|
"description": "Choose a specific development path (e.g., web, mobile, data science) and acquire the necessary skills to build applications."
|
||||||
|
}
|
||||||
|
]
|
|
@ -5,7 +5,11 @@ export default {
|
||||||
"./src/**/*.{js,jsx,ts,tsx}",
|
"./src/**/*.{js,jsx,ts,tsx}",
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
inter: ['Inter', 'sans-serif'],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue