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}

+ logo + +
+ ); +} + +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 ( +
+
+ logo +
+
+

{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 ; + return ; } 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 ( +
+ {/* */} +
+ tech image +
+

{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 && ( +
+
+
+ logo +
+
+

{foundTech.name}

+

About

+ {foundTech.description} +
+
+
+ )}
); }