diff --git a/README.md b/README.md
index ee42f26..29f8bce 100644
--- a/README.md
+++ b/README.md
@@ -381,9 +381,9 @@ For each technology, display its `name` and `image`, and wrap them in a React Ro
---
-### Iteration 6 | Technology Details
+### Bonus: Iteration 6 | Technology Details
-In this iteration, you will work on the `TechologyPage` component to display the information of a specific technology.
+In this iteration, you will work on the `TechologyPage` component to display information about a specific technology.
The component should take 1 prop:
@@ -485,16 +485,19 @@ To set up the React Router in your React application, follow these steps:
npm install react-router-dom
```
-2. Import the `BrowserRouter` component in your app's entry point (usually `index.js`) and wrap your ` ` component with it:
+2. Import the `BrowserRouter` component in your app's entry point (usually `main.jsx`) and wrap your ` ` component with it:
```jsx
import { BrowserRouter as Router } from "react-router-dom";
-ReactDOM.render(
-
-
- ,
- document.getElementById("root")
+const root = ReactDOM.createRoot(document.getElementById("root"));
+
+root.render(
+
+
+
+
+
);
```
diff --git a/index.html b/index.html
index 79c4701..ba47f90 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,7 @@
-
Vite + React
+ LAB Stack Tracker
diff --git a/package-lock.json b/package-lock.json
index eadb7c4..0880248 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,8 @@
"version": "0.0.0",
"dependencies": {
"react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react-dom": "^18.2.0",
+ "react-router-dom": "^7.1.1"
},
"devDependencies": {
"@types/react": "^18.0.37",
@@ -935,6 +936,12 @@
"@types/chai": "*"
}
},
+ "node_modules/@types/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
+ "license": "MIT"
+ },
"node_modules/@types/node": {
"version": "20.2.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz",
@@ -1505,6 +1512,15 @@
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
"dev": true
},
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -3555,6 +3571,46 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-router": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.1.tgz",
+ "integrity": "sha512-39sXJkftkKWRZ2oJtHhCxmoCrBCULr/HAH4IT5DHlgu/Q0FCPV0S4Lx+abjDTx/74xoZzNYDYbOZWlJjruyuDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/cookie": "^0.6.0",
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0",
+ "turbo-stream": "2.4.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.1.1.tgz",
+ "integrity": "sha512-vSrQHWlJ5DCfyrhgo0k6zViOe9ToK8uT5XGSmnuC2R3/g261IdIMpZVqfjD6vWSXdnf5Czs4VA/V60oVR6/jnA==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.1.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
"node_modules/regexp.prototype.flags": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz",
@@ -3723,6 +3779,12 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3990,6 +4052,12 @@
"node": ">=14"
}
},
+ "node_modules/turbo-stream": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
+ "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
+ "license": "ISC"
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -5038,6 +5106,11 @@
"@types/chai": "*"
}
},
+ "@types/cookie": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
+ },
"@types/node": {
"version": "20.2.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz",
@@ -5464,6 +5537,11 @@
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
"dev": true
},
+ "cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="
+ },
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -6960,6 +7038,25 @@
"integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==",
"dev": true
},
+ "react-router": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.1.tgz",
+ "integrity": "sha512-39sXJkftkKWRZ2oJtHhCxmoCrBCULr/HAH4IT5DHlgu/Q0FCPV0S4Lx+abjDTx/74xoZzNYDYbOZWlJjruyuDQ==",
+ "requires": {
+ "@types/cookie": "^0.6.0",
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0",
+ "turbo-stream": "2.4.0"
+ }
+ },
+ "react-router-dom": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.1.1.tgz",
+ "integrity": "sha512-vSrQHWlJ5DCfyrhgo0k6zViOe9ToK8uT5XGSmnuC2R3/g261IdIMpZVqfjD6vWSXdnf5Czs4VA/V60oVR6/jnA==",
+ "requires": {
+ "react-router": "7.1.1"
+ }
+ },
"regexp.prototype.flags": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz",
@@ -7073,6 +7170,11 @@
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
+ "set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="
+ },
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -7274,6 +7376,11 @@
"punycode": "^2.3.0"
}
},
+ "turbo-stream": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
+ "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g=="
+ },
"type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
diff --git a/package.json b/package.json
index 727f1c1..12e68e0 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,8 @@
},
"dependencies": {
"react": "^18.2.0",
- "react-dom": "^18.2.0"
+ "react-dom": "^18.2.0",
+ "react-router-dom": "^7.1.1"
},
"devDependencies": {
"@types/react": "^18.0.37",
diff --git a/src/App.css b/src/App.css
index b9d355d..e9ce2ca 100644
--- a/src/App.css
+++ b/src/App.css
@@ -1,42 +1,123 @@
+
#root {
max-width: 1280px;
margin: 0 auto;
- padding: 2rem;
- text-align: center;
+ width: 100%;
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
}
-.logo {
- height: 6em;
+nav{
+ width: 100%;
+ padding: 1em;
+ background-color: rgb(100, 108, 255);
+ color: #FFF;
+ font-weight: bold;
+}
+
+.pages-wrapper{
padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
+
+.pages-wrapper h1{
+ width: 100%;
+ text-align: center;
}
-.logo.react:hover {
- filter: drop-shadow(0 0 2em #61dafbaa);
+
+.tracks-wrapper{
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
}
-@keyframes logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
+.texh-wrapper{
+ text-align: center;
}
-@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
- }
+.tech-wrapper{
+ display: flex;
+ flex-direction: row;
+ flex-wrap: no-wrap;
+ overflow: scroll;
+ margin: 1em;
+ padding-top: 2em;
}
-.card {
- padding: 2em;
+/* .tech-wrapper::-webkit-scrollbar {
+ display: none; /* Hides the scrollbar
+} */
+
+.company-details{
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
}
-.read-the-docs {
- color: #888;
+.tech-details{
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+}
+
+.image-container{
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ margin: 1em;
+ padding: 1em;
+ height: 15em;
+ justify-content: center;
+ align-items: center;
+}
+
+.image-container-sml{
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ margin: 1em;
+ padding: 1em;
+ height: 8em;
+ width: 8em;
+ justify-content: center;
+ align-items: center;
}
+
+.square{
+ width: 15em;
+ border-radius: 3px;
+ box-shadow: rgb(139, 138, 138) 1px 1px 1px 1px;
+}
+
+.long{
+ width: 30em;
+}
+
+.image-container h3{
+ width:100%;
+ text-align: center;
+}
+
+.image-container img{
+ width: 5em;
+ height: auto;
+}
+
+.image-container-sml img{
+ width: 2em;
+ height: auto;
+}
+
+.square-sml{
+ border-radius: 3px;
+ box-shadow: rgb(139, 138, 138) 1px 1px 1px 1px;
+}
+
+.image-container a{
+ color: black;
+}
\ No newline at end of file
diff --git a/src/App.jsx b/src/App.jsx
index 1b51d87..56677d7 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,9 +1,22 @@
import "./App.css";
+import Navbar from "./components/Navbar";
+import HomePage from "./pages/HomePage";
+import CompanyPage from "./pages/CompanyPage";
+import TechnologyPage from "./pages/TechnologyPage";
+import { Routes, Route } from 'react-router-dom';
+
+import companiesData from './companies.json';
+import technologiesData from './technologies.json';
function App() {
return (
-
LAB | React Stack Tracker
+
+
+ } />
+ } />
+ } />
+
);
}
diff --git a/src/components/CompanyCard.jsx b/src/components/CompanyCard.jsx
new file mode 100644
index 0000000..7ba6672
--- /dev/null
+++ b/src/components/CompanyCard.jsx
@@ -0,0 +1,14 @@
+import { Link } from 'react-router-dom';
+
+function CompanyCard({company}) {
+ return (
+
+
+
{company.name}
+
+
+
+ );
+}
+
+export default CompanyCard;
\ No newline at end of file
diff --git a/src/components/CompanyDetails.jsx b/src/components/CompanyDetails.jsx
new file mode 100644
index 0000000..917f1e1
--- /dev/null
+++ b/src/components/CompanyDetails.jsx
@@ -0,0 +1,16 @@
+function CompanyDetails({ company }) {
+ return (
+
+
+
+
+
+
{company.name}
+ About
+ {company.description}
+
+
+ );
+}
+
+export default CompanyDetails;
\ No newline at end of file
diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx
index 09e2806..7b3e2da 100644
--- a/src/components/Navbar.jsx
+++ b/src/components/Navbar.jsx
@@ -1,5 +1,5 @@
function Navbar() {
- return Navbar ;
+ return StackTracker
;
}
export default Navbar;
diff --git a/src/components/TechStackCard.jsx b/src/components/TechStackCard.jsx
new file mode 100644
index 0000000..cd1e661
--- /dev/null
+++ b/src/components/TechStackCard.jsx
@@ -0,0 +1,29 @@
+import { useSearchParams, useNavigate } from 'react-router-dom';
+
+function TechStackCard({technology, companySlug}) {
+ const navigate = useNavigate();
+ const [searchParams, setSearchParams] = useSearchParams("");
+
+ const navigateWithParams = () => {
+ const queryString = {slug: companySlug};
+ const sp = new URLSearchParams();
+ sp.append("company", companySlug);
+ navigate({
+ pathname: `/tech/${technology.slug}`,
+ search: sp.toString()
+ })
+ }
+
+ return (
+
+ {/*
*/}
+
+
+
+
{technology.name}
+ {/* */}
+
+ );
+}
+
+export default TechStackCard;
\ No newline at end of file
diff --git a/src/index.css b/src/index.css
index b121ef9..e7505b6 100644
--- a/src/index.css
+++ b/src/index.css
@@ -2,7 +2,6 @@
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
-
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
@@ -22,16 +21,20 @@ a:hover {
body {
margin: 0;
display: flex;
- place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
- font-size: 3.2em;
+ font-size: 1.2em;
line-height: 1.1;
}
+h3{
+ font-size: 0.8em;
+ line-height: 0;
+}
+
button {
border-radius: 8px;
border: 1px solid transparent;
diff --git a/src/main.jsx b/src/main.jsx
index 54b39dd..0db4c0a 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -3,8 +3,13 @@ import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
+import { BrowserRouter as Router } from "react-router-dom";
+
+
ReactDOM.createRoot(document.getElementById('root')).render(
-
+
+
+
,
)
diff --git a/src/pages/CompanyPage.jsx b/src/pages/CompanyPage.jsx
index cc5903c..bbbae9c 100644
--- a/src/pages/CompanyPage.jsx
+++ b/src/pages/CompanyPage.jsx
@@ -1,7 +1,37 @@
-function CompanyPage() {
+import { useEffect, useState } from 'react';
+import { useParams, Link, useSearchParams } from 'react-router-dom';
+import CompanyDetails from '../components/CompanyDetails';
+import TechStackCard from '../components/TechStackCard';
+
+function CompanyPage(props) {
+ const { companySlug } = useParams();
+ const [company, setCompany] = useState([]);
+ const [companyTech, setCompanyTech] = useState([]);
+
+ useEffect(() => {
+ props.companies.forEach(companyEl => {
+ if(companyEl.slug===companySlug){
+ //console.log(companyEl);
+ setCompany(companyEl);
+ setCompanyTech(companyEl.techStack);
+ }
+ })
+ },[company]);
+
return (
-
-
CompanyPage
+
+
+
Company Profile
+ < Back
+
+
+
+
+ {companyTech.map((technology, index) =>
+
+ )}
+
+
);
}
diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx
index e97e7de..98f0f51 100644
--- a/src/pages/HomePage.jsx
+++ b/src/pages/HomePage.jsx
@@ -1,7 +1,17 @@
-function HomePage() {
+import CompanyCard from "../components/CompanyCard";
+
+function HomePage(props) {
return (
-
-
HomePage
+
+
StackTracker: Discover Tech Stacks Used by Top Companies
+
+ {props.companies.map((company) => {
+ return (
+
+ );
+ })}
+
+
);
}
diff --git a/src/pages/TechnologyPage.jsx b/src/pages/TechnologyPage.jsx
index 30f9414..8949cc9 100644
--- a/src/pages/TechnologyPage.jsx
+++ b/src/pages/TechnologyPage.jsx
@@ -1,7 +1,44 @@
-function TechnologyPage() {
+import { useState, useEffect } from "react";
+import { useParams, Link, useSearchParams } from "react-router-dom";
+
+function TechnologyPage(props) {
+
+ const { slug } = useParams();
+ const [searchParams, setSearchParams] = useSearchParams();
+ const params = searchParams.getAll("company");
+ const [foundTech, setFoundTech] = useState([]);
+
+ useEffect(()=>{
+ props.technologies.map((techEl) => {
+ if(techEl.slug === slug){
+ setFoundTech(techEl);
+ }
+ });
+ },[foundTech])
+
+
return (
-
-
TechnologyPage
+
+
Technology Details
+
+
< Back
+
+ {foundTech.length===0 &&
Tech not found! }
+
+ {foundTech.length!==0 && (
+
+
+
+
+
+
+
{foundTech.name}
+ About
+ {foundTech.description}
+
+
+
+ )}
);
}