Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 68 additions & 16 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -1,32 +1,84 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
background-color: black;
font-family: 'Roboto', sans-serif;
margin: 20px;
background-color: black;
font-family: "Roboto", sans-serif;
margin: 20px;
}

.container {
width: 960px;
margin: 0 auto;
width: 960px;
margin: 0 auto;
}
/* TODO: Add css here */
.tabs {
display: flex;
background-color: #333;
}

.tab-button {
flex: 1;
padding: 10px 20px;
border: none;
cursor: pointer;
background-color: #333;
color: #fff;
height: 50px;
}

.tab-button.active {
background-color: #114277;
}

@media only screen and (max-width: 960px) {
.tab-button:hover {
background-color: #555;
}

.tab-content {
padding: 0;
margin: 0;
background-color: white;
}

.tab-content div {
padding: 20px;
background-color: white;
}

.container {
width: 100%;
}
.title {
padding-top: 20px;
padding-left: 20px;
font-size: 1.5rem;
}

.text {
font-size: 1rem;
line-height: 1.5;
}
.error {
color: red;
display: flex;
align-items: center;
justify-content: center;
height: 200px;
}
.loading {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
}

@media only screen and (max-width: 960px) {
.container {
width: 100%;
}

.content {
padding: 50px;
}
}
.content {
padding: 50px;
}
}
126 changes: 93 additions & 33 deletions src/components/Tabs.jsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,99 @@
import React from 'react';
import React, { useState, useReducer, useEffect } from "react";

const initialState = {
data: {},
error: null,
};

const reducer = (state, action) => {
switch (action.type) {
case "FETCH_SUCCESS":
return {
...state,
data: {
...state.data,
[action.tabIndex]: action.data,
},
error: null,
};
case "FETCH_ERROR":
return {
...state,
error: action.error,
};
default:
return state;
}
};

const Tabs = () => {
const [activeTab, setActiveTab] = useState(0);
const [state, dispatch] = useReducer(reducer, initialState);

// Fetch tab content
const fetchTabContent = async (tabIndex) => {
// check if data for active tab is already cached and skip fetching if its already cached
if (state.data[tabIndex]) {
console.log("Using cached data:", state.data[tabIndex]);
return;
}

try {
const response = await fetch(
"https://thingproxy.freeboard.io/fetch/https://loripsum.net/api/"
);
if (!response.ok) {
throw new Error("Network response was not ok");
}
const data = await response.text();
// console.log("Fetched data:", data);
dispatch({ type: "FETCH_SUCCESS", tabIndex, data });
} catch (error) {
console.error("Error fetching data:", error);
dispatch({ type: "FETCH_ERROR", tabIndex, error: "Error fetching data" });
}
};

// const tabs = [
// {
// id: 1,
// tabTitle: 'Tab 1',
// title: 'Title 1',
// content: 'In sint do non adipisicing incididunt excepteur sit. Voluptate esse aliqua pariatur dolor ex occaecat sunt eu. Pariatur ullamco id dolore sint proident sint nostrud nisi sit id est. Duis et excepteur cupidatat sint nisi dolore qui irure qui in id excepteur irure laboris. Pariatur mollit duis cupidatat nisi Lorem non et in dolor aliquip ea sint aute. Dolore aute duis laboris exercitation occaecat sunt. Enim veniam Lorem do ipsum aliqua qui eu ipsum consectetur ex dolore ea ipsum.'
// },
// {
// id: 2,
// tabTitle: 'Tab 2',
// title: 'Title 2',
// content: 'Non aliquip fugiat velit ad officia Lorem tempor cillum incididunt elit proident mollit. Reprehenderit qui nisi ut occaecat minim velit deserunt occaecat quis magna mollit. Veniam proident consectetur sunt mollit est magna Lorem voluptate enim cupidatat consequat. Et pariatur aliquip commodo nisi deserunt exercitation enim officia voluptate in nisi. Eu ea esse qui est ea pariatur nostrud non elit irure. Ad exercitation Lorem exercitation ipsum eiusmod ea duis ad mollit veniam aliquip veniam. Lorem pariatur elit ea duis.'
// },
// {
// id: 3,
// tabTitle: 'Tab 3',
// title: 'Title 3',
// content: 'Deserunt et elit elit ad dolor magna. Nisi amet consectetur Lorem eiusmod dolore adipisicing do reprehenderit. Voluptate consequat magna nostrud in officia labore. Minim excepteur consectetur quis nostrud nisi magna duis sunt sint qui. Fugiat ea reprehenderit eiusmod proident officia. Consequat labore qui velit Lorem consectetur incididunt ut nisi.'
// },
// {
// id: 4,
// tabTitle: 'Tab 4',
// title: 'Title 4',
// content: 'Minim in dolor do fugiat laborum duis labore consectetur. Amet ut sint ipsum dolor ad nostrud commodo sunt veniam enim aliquip nulla sint ullamco. Do cupidatat et quis laborum esse est commodo. Commodo sunt consectetur do consequat minim occaecat id magna ullamco consequat irure.'
// }
// ];

return (
<div className='container'>
{/* TODO Add tabs here */}
// fetch data whenever active tab changes
useEffect(() => {
fetchTabContent(activeTab);
}, [activeTab]);

const tabs = ["Tab 1", "Tab 2", "Tab 3", "Tab 4"];

return (
<div className="container">
{/* TODO Add tabs here */}
<div className="container">
<div className="tabs">
{tabs.map((tab, index) => (
<button
key={index}
onClick={() => setActiveTab(index)}
className={`tab-button ${activeTab === index ? "active" : ""}`}
>
{tab}
</button>
))}
</div>
<div className="tab-content">
{state.error ? (
<p className="error">{state.error}</p>
) : state.data[activeTab] ? (
<>
<h2 className="title">Title {activeTab + 1}</h2>
<div
className="text"
dangerouslySetInnerHTML={{ __html: state.data[activeTab] }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid using dangerouslySetInnerHTML to prevent XSS vulnerabilities

Using dangerouslySetInnerHTML can expose users to cross-site scripting (XSS) attacks if the content is not properly sanitized. Consider sanitizing the content before rendering, or using a library like DOMPurify to sanitize the HTML.

Apply this diff to address the issue:

+ // At the top of your file, import DOMPurify
+ import DOMPurify from 'dompurify';
...
-                    dangerouslySetInnerHTML={{ __html: state.data[activeTab] }}
+                    dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(state.data[activeTab]) }}

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 87-87: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

/>
</>
) : (
<p className="loading">Loading content...</p>
)}
</div>
);
}
</div>
</div>
);
};

export default Tabs;